1 /**
2  * Copyright © 2019 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "extensions/openpower-pels/src.hpp"
17 #include "mocks.hpp"
18 #include "pel_utils.hpp"
19 
20 #include <fstream>
21 
22 #include <gtest/gtest.h>
23 
24 using namespace openpower::pels;
25 using ::testing::_;
26 using ::testing::DoAll;
27 using ::testing::InvokeWithoutArgs;
28 using ::testing::NiceMock;
29 using ::testing::Return;
30 using ::testing::SetArgReferee;
31 using ::testing::Throw;
32 namespace fs = std::filesystem;
33 
34 const auto testRegistry = R"(
35 {
36 "PELs":
37 [
38     {
39         "Name": "xyz.openbmc_project.Error.Test",
40         "Subsystem": "bmc_firmware",
41         "SRC":
42         {
43             "ReasonCode": "0xABCD",
44             "Words6To9":
45             {
46                 "6":
47                 {
48                     "Description": "Component ID",
49                     "AdditionalDataPropSource": "COMPID"
50                 },
51                 "7":
52                 {
53                     "Description": "Failure count",
54                     "AdditionalDataPropSource": "FREQUENCY"
55                 },
56                 "8":
57                 {
58                     "Description": "Time period",
59                     "AdditionalDataPropSource": "DURATION"
60                 },
61                 "9":
62                 {
63                     "Description": "Error code",
64                     "AdditionalDataPropSource": "ERRORCODE"
65                 }
66             }
67         },
68         "Documentation":
69         {
70             "Description": "A Component Fault",
71             "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4",
72             "MessageArgSources":
73             [
74                 "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9"
75             ]
76         }
77     }
78 ]
79 }
80 )";
81 
82 class SRCTest : public ::testing::Test
83 {
84   protected:
85     static void SetUpTestCase()
86     {
87         char path[] = "/tmp/srctestXXXXXX";
88         regDir = mkdtemp(path);
89     }
90 
91     static void TearDownTestCase()
92     {
93         fs::remove_all(regDir);
94     }
95 
96     static std::string writeData(const char* data)
97     {
98         fs::path path = regDir / "registry.json";
99         std::ofstream stream{path};
100         stream << data;
101         return path;
102     }
103 
104     static fs::path regDir;
105 };
106 
107 fs::path SRCTest::regDir{};
108 
109 TEST_F(SRCTest, UnflattenFlattenTestNoCallouts)
110 {
111     auto data = pelDataFactory(TestPELType::primarySRCSection);
112 
113     Stream stream{data};
114     SRC src{stream};
115 
116     EXPECT_TRUE(src.valid());
117 
118     EXPECT_EQ(src.header().id, 0x5053);
119     EXPECT_EQ(src.header().size, 0x50);
120     EXPECT_EQ(src.header().version, 0x01);
121     EXPECT_EQ(src.header().subType, 0x01);
122     EXPECT_EQ(src.header().componentID, 0x0202);
123 
124     EXPECT_EQ(src.version(), 0x02);
125     EXPECT_EQ(src.flags(), 0x00);
126     EXPECT_EQ(src.hexWordCount(), 9);
127     EXPECT_EQ(src.size(), 0x48);
128 
129     const auto& hexwords = src.hexwordData();
130     EXPECT_EQ(0x02020255, hexwords[0]);
131     EXPECT_EQ(0x03030310, hexwords[1]);
132     EXPECT_EQ(0x04040404, hexwords[2]);
133     EXPECT_EQ(0x05050505, hexwords[3]);
134     EXPECT_EQ(0x06060606, hexwords[4]);
135     EXPECT_EQ(0x07070707, hexwords[5]);
136     EXPECT_EQ(0x08080808, hexwords[6]);
137     EXPECT_EQ(0x09090909, hexwords[7]);
138 
139     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
140     EXPECT_FALSE(src.callouts());
141 
142     // Flatten
143     std::vector<uint8_t> newData;
144     Stream newStream{newData};
145 
146     src.flatten(newStream);
147     EXPECT_EQ(data, newData);
148 }
149 
150 TEST_F(SRCTest, UnflattenFlattenTest2Callouts)
151 {
152     auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts);
153 
154     Stream stream{data};
155     SRC src{stream};
156 
157     EXPECT_TRUE(src.valid());
158     EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC.
159 
160     // Spot check the SRC fields, but they're the same as above
161     EXPECT_EQ(src.asciiString(), "BD8D5678                        ");
162 
163     // There should be 2 callouts
164     const auto& calloutsSection = src.callouts();
165     ASSERT_TRUE(calloutsSection);
166     const auto& callouts = calloutsSection->callouts();
167     EXPECT_EQ(callouts.size(), 2);
168 
169     // spot check that each callout has the right substructures
170     EXPECT_TRUE(callouts.front()->fruIdentity());
171     EXPECT_FALSE(callouts.front()->pceIdentity());
172     EXPECT_FALSE(callouts.front()->mru());
173 
174     EXPECT_TRUE(callouts.back()->fruIdentity());
175     EXPECT_TRUE(callouts.back()->pceIdentity());
176     EXPECT_TRUE(callouts.back()->mru());
177 
178     // Flatten
179     std::vector<uint8_t> newData;
180     Stream newStream{newData};
181 
182     src.flatten(newStream);
183     EXPECT_EQ(data, newData);
184 }
185 
186 // Create an SRC from the message registry
187 TEST_F(SRCTest, CreateTestNoCallouts)
188 {
189     message::Entry entry;
190     entry.src.type = 0xBD;
191     entry.src.reasonCode = 0xABCD;
192     entry.subsystem = 0x42;
193     entry.src.hexwordADFields = {
194         {5, {"TEST1", "DESCR1"}}, // Not a user defined word
195         {6, {"TEST1", "DESCR1"}},
196         {7, {"TEST2", "DESCR2"}},
197         {8, {"TEST3", "DESCR3"}},
198         {9, {"TEST4", "DESCR4"}}};
199 
200     // Values for the SRC words pointed to above
201     std::vector<std::string> adData{"TEST1=0x12345678", "TEST2=12345678",
202                                     "TEST3=0XDEF", "TEST4=Z"};
203     AdditionalData ad{adData};
204     NiceMock<MockDataInterface> dataIface;
205 
206     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
207 
208     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
209                                       "system/entry"};
210     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
211         .WillOnce(Return(std::vector<bool>{false, false, false}));
212 
213     SRC src{entry, ad, dataIface};
214 
215     EXPECT_TRUE(src.valid());
216     EXPECT_FALSE(src.isPowerFaultEvent());
217     EXPECT_EQ(src.size(), baseSRCSize);
218 
219     const auto& hexwords = src.hexwordData();
220 
221     // The spec always refers to SRC words 2 - 9, and as the hexwordData()
222     // array index starts at 0 use the math in the [] below to make it easier
223     // to tell what is being accessed.
224     EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0);    // Partition dump status
225     EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0);    // Partition boot type
226     EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format
227     EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position
228     EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0xABCD0000); // Motherboard CCIN
229 
230     // Validate more fields here as the code starts filling them in.
231 
232     // Ensure hex word 5 wasn't allowed to be set to TEST1's contents
233     EXPECT_EQ(hexwords[5 - 2], 0);
234 
235     // The user defined hex word fields specifed in the additional data.
236     EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1
237     EXPECT_EQ(hexwords[7 - 2], 12345678);   // TEST2
238     EXPECT_EQ(hexwords[8 - 2], 0xdef);      // TEST3
239     EXPECT_EQ(hexwords[9 - 2], 0);          // TEST4, but can't convert a 'Z'
240 
241     EXPECT_EQ(src.asciiString(), "BD42ABCD                        ");
242 
243     // No callouts
244     EXPECT_FALSE(src.callouts());
245 
246     // May as well spot check the flatten/unflatten
247     std::vector<uint8_t> data;
248     Stream stream{data};
249     src.flatten(stream);
250 
251     stream.offset(0);
252     SRC newSRC{stream};
253 
254     EXPECT_TRUE(newSRC.valid());
255     EXPECT_EQ(newSRC.asciiString(), src.asciiString());
256     EXPECT_FALSE(newSRC.callouts());
257 }
258 
259 // Test when the CCIN string isn't a 4 character number
260 TEST_F(SRCTest, BadCCINTest)
261 {
262     message::Entry entry;
263     entry.src.type = 0xBD;
264     entry.src.reasonCode = 0xABCD;
265     entry.subsystem = 0x42;
266 
267     std::vector<std::string> adData{};
268     AdditionalData ad{adData};
269     NiceMock<MockDataInterface> dataIface;
270 
271     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
272                                       "system/entry"};
273     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
274         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
275 
276     // First it isn't a number, then it is too long,
277     // then it is empty.
278     EXPECT_CALL(dataIface, getMotherboardCCIN)
279         .WillOnce(Return("X"))
280         .WillOnce(Return("12345"))
281         .WillOnce(Return(""));
282 
283     // The CCIN in the first half should still be 0 each time.
284     {
285         SRC src{entry, ad, dataIface};
286         EXPECT_TRUE(src.valid());
287         const auto& hexwords = src.hexwordData();
288         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
289     }
290 
291     {
292         SRC src{entry, ad, dataIface};
293         EXPECT_TRUE(src.valid());
294         const auto& hexwords = src.hexwordData();
295         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
296     }
297 
298     {
299         SRC src{entry, ad, dataIface};
300         EXPECT_TRUE(src.valid());
301         const auto& hexwords = src.hexwordData();
302         EXPECT_EQ(hexwords[3 - 2] & 0xFFFF0000, 0x00000000);
303     }
304 }
305 
306 // Test the getErrorDetails function
307 TEST_F(SRCTest, MessageSubstitutionTest)
308 {
309     auto path = SRCTest::writeData(testRegistry);
310     message::Registry registry{path};
311     auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode);
312 
313     std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4",
314                                     "DURATION=30", "ERRORCODE=0x01ABCDEF"};
315     AdditionalData ad{adData};
316     NiceMock<MockDataInterface> dataIface;
317 
318     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
319                                       "system/entry"};
320     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
321         .WillOnce(Return(std::vector<bool>{false, false, false}));
322 
323     SRC src{*entry, ad, dataIface};
324     EXPECT_TRUE(src.valid());
325 
326     auto errorDetails = src.getErrorDetails(registry, DetailLevel::message);
327     ASSERT_TRUE(errorDetails);
328     EXPECT_EQ(errorDetails.value(),
329               "Comp 0x00000001 failed 0x00000004 times over 0x0000001E secs "
330               "with ErrorCode 0x01ABCDEF");
331 }
332 // Test that an inventory path callout string is
333 // converted into the appropriate FRU callout.
334 TEST_F(SRCTest, InventoryCalloutTest)
335 {
336     message::Entry entry;
337     entry.src.type = 0xBD;
338     entry.src.reasonCode = 0xABCD;
339     entry.subsystem = 0x42;
340 
341     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
342     AdditionalData ad{adData};
343     NiceMock<MockDataInterface> dataIface;
344 
345     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
346         .WillOnce(Return("UTMS-P1"));
347 
348     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
349         .Times(1)
350         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
351                         SetArgReferee<3>("123456789ABC")));
352 
353     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
354                                       "system/entry"};
355     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
356         .WillOnce(Return(std::vector<bool>{false, false, false}));
357 
358     SRC src{entry, ad, dataIface};
359     EXPECT_TRUE(src.valid());
360 
361     ASSERT_TRUE(src.callouts());
362 
363     EXPECT_EQ(src.callouts()->callouts().size(), 1);
364 
365     auto& callout = src.callouts()->callouts().front();
366 
367     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
368     EXPECT_EQ(callout->priority(), 'H');
369 
370     auto& fru = callout->fruIdentity();
371 
372     EXPECT_EQ(fru->getPN().value(), "1234567");
373     EXPECT_EQ(fru->getCCIN().value(), "CCCC");
374     EXPECT_EQ(fru->getSN().value(), "123456789ABC");
375 
376     // flatten and unflatten
377     std::vector<uint8_t> data;
378     Stream stream{data};
379     src.flatten(stream);
380 
381     stream.offset(0);
382     SRC newSRC{stream};
383     EXPECT_TRUE(newSRC.valid());
384     ASSERT_TRUE(src.callouts());
385     EXPECT_EQ(src.callouts()->callouts().size(), 1);
386 }
387 
388 // Test that when the location code can't be obtained that
389 // no callout is added.
390 TEST_F(SRCTest, InventoryCalloutNoLocCodeTest)
391 {
392     message::Entry entry;
393     entry.src.type = 0xBD;
394     entry.src.reasonCode = 0xABCD;
395     entry.subsystem = 0x42;
396 
397     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
398     AdditionalData ad{adData};
399     NiceMock<MockDataInterface> dataIface;
400 
401     auto func = []() {
402         throw sdbusplus::exception::SdBusError(5, "Error");
403         return std::string{};
404     };
405 
406     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
407         .Times(1)
408         .WillOnce(InvokeWithoutArgs(func));
409 
410     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
411                                       "system/entry"};
412     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
413         .WillOnce(Return(std::vector<bool>{false, false, false}));
414 
415     EXPECT_CALL(dataIface, getHWCalloutFields(_, _, _, _)).Times(0);
416 
417     SRC src{entry, ad, dataIface};
418     EXPECT_TRUE(src.valid());
419 
420     ASSERT_FALSE(src.callouts());
421 
422     // flatten and unflatten
423     std::vector<uint8_t> data;
424     Stream stream{data};
425     src.flatten(stream);
426 
427     stream.offset(0);
428     SRC newSRC{stream};
429     EXPECT_TRUE(newSRC.valid());
430     ASSERT_FALSE(src.callouts());
431 }
432 
433 // Test that when the VPD can't be obtained that
434 // a callout is still created.
435 TEST_F(SRCTest, InventoryCalloutNoVPDTest)
436 {
437     message::Entry entry;
438     entry.src.type = 0xBD;
439     entry.src.reasonCode = 0xABCD;
440     entry.subsystem = 0x42;
441 
442     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
443     AdditionalData ad{adData};
444     NiceMock<MockDataInterface> dataIface;
445 
446     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
447         .Times(1)
448         .WillOnce(Return("UTMS-P10"));
449 
450     auto func = []() { throw sdbusplus::exception::SdBusError(5, "Error"); };
451 
452     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
453         .Times(1)
454         .WillOnce(InvokeWithoutArgs(func));
455 
456     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
457                                       "system/entry"};
458     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
459         .WillOnce(Return(std::vector<bool>{false, false, false}));
460 
461     SRC src{entry, ad, dataIface};
462     EXPECT_TRUE(src.valid());
463     ASSERT_TRUE(src.callouts());
464     EXPECT_EQ(src.callouts()->callouts().size(), 1);
465 
466     auto& callout = src.callouts()->callouts().front();
467     EXPECT_EQ(callout->locationCode(), "UTMS-P10");
468     EXPECT_EQ(callout->priority(), 'H');
469 
470     auto& fru = callout->fruIdentity();
471 
472     EXPECT_EQ(fru->getPN(), "");
473     EXPECT_EQ(fru->getCCIN(), "");
474     EXPECT_EQ(fru->getSN(), "");
475     EXPECT_FALSE(fru->getMaintProc());
476 
477     // flatten and unflatten
478     std::vector<uint8_t> data;
479     Stream stream{data};
480     src.flatten(stream);
481 
482     stream.offset(0);
483     SRC newSRC{stream};
484     EXPECT_TRUE(newSRC.valid());
485     ASSERT_TRUE(src.callouts());
486     EXPECT_EQ(src.callouts()->callouts().size(), 1);
487 }
488 
489 TEST_F(SRCTest, RegistryCalloutTest)
490 {
491     message::Entry entry;
492     entry.src.type = 0xBD;
493     entry.src.reasonCode = 0xABCD;
494     entry.subsystem = 0x42;
495 
496     entry.callouts = R"(
497         [
498         {
499             "System": "systemA",
500             "CalloutList":
501             [
502                 {
503                     "Priority": "high",
504                     "SymbolicFRU": "service_docs"
505                 },
506                 {
507                     "Priority": "medium",
508                     "Procedure": "bmc_code"
509                 }
510             ]
511         },
512         {
513             "System": "systemB",
514             "CalloutList":
515             [
516                 {
517                     "Priority": "high",
518                     "LocCode": "P0-C8",
519                     "SymbolicFRUTrusted": "service_docs"
520                 },
521                 {
522                     "Priority": "medium",
523                     "SymbolicFRUTrusted": "service_docs"
524                 }
525             ]
526         },
527         {
528             "System": "systemC",
529             "CalloutList":
530             [
531                 {
532                     "Priority": "high",
533                     "LocCode": "P0-C8"
534                 },
535                 {
536                     "Priority": "medium",
537                     "LocCode": "P0-C9"
538                 }
539             ]
540         }
541         ])"_json;
542 
543     {
544         // Call out a symbolic FRU and a procedure
545         AdditionalData ad;
546         NiceMock<MockDataInterface> dataIface;
547         std::vector<std::string> names{"systemA"};
548 
549         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
550 
551         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
552                                           "system/entry"};
553         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
554             .WillOnce(Return(std::vector<bool>{false, false, false}));
555 
556         SRC src{entry, ad, dataIface};
557 
558         auto& callouts = src.callouts()->callouts();
559         ASSERT_EQ(callouts.size(), 2);
560 
561         EXPECT_EQ(callouts[0]->locationCodeSize(), 0);
562         EXPECT_EQ(callouts[0]->priority(), 'H');
563 
564         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
565         EXPECT_EQ(callouts[1]->priority(), 'M');
566 
567         auto& fru1 = callouts[0]->fruIdentity();
568         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
569         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
570         EXPECT_FALSE(fru1->getMaintProc());
571         EXPECT_FALSE(fru1->getSN());
572         EXPECT_FALSE(fru1->getCCIN());
573 
574         auto& fru2 = callouts[1]->fruIdentity();
575         EXPECT_EQ(fru2->getMaintProc().value(), "BMC0001");
576         EXPECT_EQ(fru2->failingComponentType(),
577                   src::FRUIdentity::maintenanceProc);
578         EXPECT_FALSE(fru2->getPN());
579         EXPECT_FALSE(fru2->getSN());
580         EXPECT_FALSE(fru2->getCCIN());
581     }
582 
583     {
584         // Call out a trusted symbolic FRU with a location code, and
585         // another one without.
586         AdditionalData ad;
587         NiceMock<MockDataInterface> dataIface;
588         std::vector<std::string> names{"systemB"};
589 
590         EXPECT_CALL(dataIface, expandLocationCode).WillOnce(Return("P0-C8"));
591         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
592 
593         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
594                                           "system/entry"};
595         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
596             .WillOnce(Return(std::vector<bool>{false, false, false}));
597 
598         SRC src{entry, ad, dataIface};
599 
600         auto& callouts = src.callouts()->callouts();
601         EXPECT_EQ(callouts.size(), 2);
602 
603         EXPECT_EQ(callouts[0]->locationCode(), "P0-C8");
604         EXPECT_EQ(callouts[0]->priority(), 'H');
605 
606         EXPECT_EQ(callouts[1]->locationCodeSize(), 0);
607         EXPECT_EQ(callouts[1]->priority(), 'M');
608 
609         auto& fru1 = callouts[0]->fruIdentity();
610         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
611         EXPECT_EQ(fru1->failingComponentType(),
612                   src::FRUIdentity::symbolicFRUTrustedLocCode);
613         EXPECT_FALSE(fru1->getMaintProc());
614         EXPECT_FALSE(fru1->getSN());
615         EXPECT_FALSE(fru1->getCCIN());
616 
617         // It asked for a trusted symbolic FRU, but no location code
618         // was provided so it is switched back to a normal one
619         auto& fru2 = callouts[1]->fruIdentity();
620         EXPECT_EQ(fru2->getPN().value(), "SVCDOCS");
621         EXPECT_EQ(fru2->failingComponentType(), src::FRUIdentity::symbolicFRU);
622         EXPECT_FALSE(fru2->getMaintProc());
623         EXPECT_FALSE(fru2->getSN());
624         EXPECT_FALSE(fru2->getCCIN());
625     }
626 
627     {
628         // Two hardware callouts
629         AdditionalData ad;
630         NiceMock<MockDataInterface> dataIface;
631         std::vector<std::string> names{"systemC"};
632 
633         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
634                                           "system/entry"};
635         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
636             .WillOnce(Return(std::vector<bool>{false, false, false}));
637 
638         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
639 
640         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
641             .WillOnce(Return("UXXX-P0-C8"));
642 
643         EXPECT_CALL(dataIface, expandLocationCode("P0-C9", 0))
644             .WillOnce(Return("UXXX-P0-C9"));
645 
646         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C8", 0, false))
647             .WillOnce(Return(std::vector<std::string>{
648                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
649 
650         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
651             .WillOnce(Return(std::vector<std::string>{
652                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1"}));
653 
654         EXPECT_CALL(
655             dataIface,
656             getHWCalloutFields(
657                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _,
658                 _))
659             .Times(1)
660             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
661                             SetArgReferee<2>("CCCC"),
662                             SetArgReferee<3>("123456789ABC")));
663 
664         EXPECT_CALL(
665             dataIface,
666             getHWCalloutFields(
667                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu1", _, _,
668                 _))
669             .Times(1)
670             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
671                             SetArgReferee<2>("DDDD"),
672                             SetArgReferee<3>("23456789ABCD")));
673 
674         SRC src{entry, ad, dataIface};
675 
676         auto& callouts = src.callouts()->callouts();
677         EXPECT_EQ(callouts.size(), 2);
678 
679         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C8");
680         EXPECT_EQ(callouts[0]->priority(), 'H');
681 
682         auto& fru1 = callouts[0]->fruIdentity();
683         EXPECT_EQ(fru1->getPN().value(), "1234567");
684         EXPECT_EQ(fru1->getCCIN().value(), "CCCC");
685         EXPECT_EQ(fru1->getSN().value(), "123456789ABC");
686 
687         EXPECT_EQ(callouts[1]->locationCode(), "UXXX-P0-C9");
688         EXPECT_EQ(callouts[1]->priority(), 'M');
689 
690         auto& fru2 = callouts[1]->fruIdentity();
691         EXPECT_EQ(fru2->getPN().value(), "2345678");
692         EXPECT_EQ(fru2->getCCIN().value(), "DDDD");
693         EXPECT_EQ(fru2->getSN().value(), "23456789ABCD");
694     }
695 }
696 
697 // Test that a symbolic FRU with a trusted location code callout
698 // from the registry can get its location from the
699 // CALLOUT_INVENTORY_PATH AdditionalData entry.
700 TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
701 {
702     message::Entry entry;
703     entry.src.type = 0xBD;
704     entry.src.reasonCode = 0xABCD;
705     entry.subsystem = 0x42;
706 
707     entry.callouts = R"(
708         [{
709             "CalloutList":
710             [
711                 {
712                     "Priority": "high",
713                     "SymbolicFRUTrusted": "service_docs",
714                     "UseInventoryLocCode": true
715                 },
716                 {
717                     "Priority": "medium",
718                     "LocCode": "P0-C8",
719                     "SymbolicFRUTrusted": "pwrsply"
720                 }
721             ]
722         }])"_json;
723 
724     {
725         // The location code for the first symbolic FRU callout will
726         // come from this inventory path since UseInventoryLocCode is set.
727         // In this case there will be no normal FRU callout for the motherboard.
728         std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
729         AdditionalData ad{adData};
730         NiceMock<MockDataInterface> dataIface;
731         std::vector<std::string> names{"systemA"};
732 
733         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
734                                           "system/entry"};
735         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
736             .WillOnce(Return(std::vector<bool>{false, false, false}));
737 
738         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
739 
740         EXPECT_CALL(dataIface, getLocationCode("motherboard"))
741             .Times(1)
742             .WillOnce(Return("Ufcs-P10"));
743 
744         EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
745             .WillOnce(Return("Ufcs-P0-C8"));
746 
747         SRC src{entry, ad, dataIface};
748 
749         auto& callouts = src.callouts()->callouts();
750         EXPECT_EQ(callouts.size(), 2);
751 
752         // The location code for the first symbolic FRU callout with a
753         // trusted location code comes from the motherboard.
754         EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
755         EXPECT_EQ(callouts[0]->priority(), 'H');
756         auto& fru1 = callouts[0]->fruIdentity();
757         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
758         EXPECT_EQ(fru1->failingComponentType(),
759                   src::FRUIdentity::symbolicFRUTrustedLocCode);
760 
761         // The second trusted symbolic FRU callouts uses the location
762         // code in the registry as usual.
763         EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
764         EXPECT_EQ(callouts[1]->priority(), 'M');
765         auto& fru2 = callouts[1]->fruIdentity();
766         EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
767         EXPECT_EQ(fru2->failingComponentType(),
768                   src::FRUIdentity::symbolicFRUTrustedLocCode);
769     }
770 
771     {
772         // This time say we want to use the location code from
773         // the inventory, but don't pass it in and the callout should
774         // end up a regular symbolic FRU
775         entry.callouts = R"(
776         [{
777             "CalloutList":
778             [
779                 {
780                     "Priority": "high",
781                     "SymbolicFRUTrusted": "service_docs",
782                     "UseInventoryLocCode": true
783                 }
784             ]
785         }])"_json;
786 
787         AdditionalData ad;
788         NiceMock<MockDataInterface> dataIface;
789         std::vector<std::string> names{"systemA"};
790 
791         std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
792                                           "system/entry"};
793         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
794             .WillOnce(Return(std::vector<bool>{false, false, false}));
795 
796         EXPECT_CALL(dataIface, getSystemNames).WillOnce(Return(names));
797 
798         SRC src{entry, ad, dataIface};
799 
800         auto& callouts = src.callouts()->callouts();
801         EXPECT_EQ(callouts.size(), 1);
802 
803         EXPECT_EQ(callouts[0]->locationCode(), "");
804         EXPECT_EQ(callouts[0]->priority(), 'H');
805         auto& fru1 = callouts[0]->fruIdentity();
806         EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
807         EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
808     }
809 }
810 
811 // Test looking up device path fails in the callout jSON.
812 TEST_F(SRCTest, DevicePathCalloutTest)
813 {
814     message::Entry entry;
815     entry.src.type = 0xBD;
816     entry.src.reasonCode = 0xABCD;
817     entry.subsystem = 0x42;
818 
819     const auto calloutJSON = R"(
820     {
821         "I2C":
822         {
823             "14":
824             {
825                 "114":
826                 {
827                     "Callouts":[
828                     {
829                         "Name": "/chassis/motherboard/cpu0",
830                         "LocationCode": "P1-C40",
831                         "Priority": "H"
832                     },
833                     {
834                         "Name": "/chassis/motherboard",
835                         "LocationCode": "P1",
836                         "Priority": "M"
837                     },
838                     {
839                         "Name": "/chassis/motherboard/bmc",
840                         "LocationCode": "P1-C15",
841                         "Priority": "L"
842                     }
843                     ],
844                     "Dest": "proc 0 target"
845                 }
846             }
847         }
848     })";
849 
850     auto dataPath = getPELReadOnlyDataPath();
851     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
852     file << calloutJSON;
853     file.close();
854 
855     NiceMock<MockDataInterface> dataIface;
856     std::vector<std::string> names{"systemA"};
857 
858     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
859                                       "system/entry"};
860     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
861         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
862 
863     EXPECT_CALL(dataIface, getSystemNames)
864         .Times(5)
865         .WillRepeatedly(Return(names));
866 
867     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C40", 0, false))
868         .Times(3)
869         .WillRepeatedly(Return(std::vector<std::string>{
870             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
871 
872     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
873         .Times(3)
874         .WillRepeatedly(Return(std::vector<std::string>{
875             "/xyz/openbmc_project/inventory/chassis/motherboard"}));
876 
877     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
878         .Times(3)
879         .WillRepeatedly(Return(std::vector<std::string>{
880             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc"}));
881 
882     EXPECT_CALL(dataIface, expandLocationCode("P1-C40", 0))
883         .Times(3)
884         .WillRepeatedly(Return("Ufcs-P1-C40"));
885 
886     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
887         .Times(3)
888         .WillRepeatedly(Return("Ufcs-P1"));
889 
890     EXPECT_CALL(dataIface, expandLocationCode("P1-C15", 0))
891         .Times(3)
892         .WillRepeatedly(Return("Ufcs-P1-C15"));
893 
894     EXPECT_CALL(
895         dataIface,
896         getHWCalloutFields(
897             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
898         .Times(3)
899         .WillRepeatedly(DoAll(SetArgReferee<1>("1234567"),
900                               SetArgReferee<2>("CCCC"),
901                               SetArgReferee<3>("123456789ABC")));
902     EXPECT_CALL(
903         dataIface,
904         getHWCalloutFields("/xyz/openbmc_project/inventory/chassis/motherboard",
905                            _, _, _))
906         .Times(3)
907         .WillRepeatedly(DoAll(SetArgReferee<1>("7654321"),
908                               SetArgReferee<2>("MMMM"),
909                               SetArgReferee<3>("CBA987654321")));
910     EXPECT_CALL(
911         dataIface,
912         getHWCalloutFields(
913             "/xyz/openbmc_project/inventory/chassis/motherboard/bmc", _, _, _))
914         .Times(3)
915         .WillRepeatedly(DoAll(SetArgReferee<1>("7123456"),
916                               SetArgReferee<2>("BBBB"),
917                               SetArgReferee<3>("C123456789AB")));
918 
919     // Call this below with different AdditionalData values that
920     // result in the same callouts.
921     auto checkCallouts = [&entry, &dataIface](const auto& items) {
922         AdditionalData ad{items};
923         SRC src{entry, ad, dataIface};
924 
925         ASSERT_TRUE(src.callouts());
926         auto& callouts = src.callouts()->callouts();
927 
928         ASSERT_EQ(callouts.size(), 3);
929 
930         {
931             EXPECT_EQ(callouts[0]->priority(), 'H');
932             EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P1-C40");
933 
934             auto& fru = callouts[0]->fruIdentity();
935             EXPECT_EQ(fru->getPN().value(), "1234567");
936             EXPECT_EQ(fru->getCCIN().value(), "CCCC");
937             EXPECT_EQ(fru->getSN().value(), "123456789ABC");
938         }
939         {
940             EXPECT_EQ(callouts[1]->priority(), 'M');
941             EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P1");
942 
943             auto& fru = callouts[1]->fruIdentity();
944             EXPECT_EQ(fru->getPN().value(), "7654321");
945             EXPECT_EQ(fru->getCCIN().value(), "MMMM");
946             EXPECT_EQ(fru->getSN().value(), "CBA987654321");
947         }
948         {
949             EXPECT_EQ(callouts[2]->priority(), 'L');
950             EXPECT_EQ(callouts[2]->locationCode(), "Ufcs-P1-C15");
951 
952             auto& fru = callouts[2]->fruIdentity();
953             EXPECT_EQ(fru->getPN().value(), "7123456");
954             EXPECT_EQ(fru->getCCIN().value(), "BBBB");
955             EXPECT_EQ(fru->getSN().value(), "C123456789AB");
956         }
957     };
958 
959     {
960         // Callouts based on the device path
961         std::vector<std::string> items{
962             "CALLOUT_ERRNO=5",
963             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
964             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
965 
966         checkCallouts(items);
967     }
968 
969     {
970         // Callouts based on the I2C bus and address
971         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
972                                        "CALLOUT_IIC_ADDR=0x72"};
973         checkCallouts(items);
974     }
975 
976     {
977         // Also based on I2C bus and address, but with bus = /dev/i2c-14
978         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=14",
979                                        "CALLOUT_IIC_ADDR=0x72"};
980         checkCallouts(items);
981     }
982 
983     {
984         // Callout not found
985         std::vector<std::string> items{
986             "CALLOUT_ERRNO=5",
987             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
988             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-24/24-0012"};
989 
990         AdditionalData ad{items};
991         SRC src{entry, ad, dataIface};
992 
993         EXPECT_FALSE(src.callouts());
994         ASSERT_EQ(src.getDebugData().size(), 1);
995         EXPECT_EQ(src.getDebugData()[0],
996                   "Problem looking up I2C callouts on 24 18: "
997                   "[json.exception.out_of_range.403] key '24' not found");
998     }
999 
1000     {
1001         // Callout not found
1002         std::vector<std::string> items{"CALLOUT_ERRNO=5", "CALLOUT_IIC_BUS=22",
1003                                        "CALLOUT_IIC_ADDR=0x99"};
1004         AdditionalData ad{items};
1005         SRC src{entry, ad, dataIface};
1006 
1007         EXPECT_FALSE(src.callouts());
1008         ASSERT_EQ(src.getDebugData().size(), 1);
1009         EXPECT_EQ(src.getDebugData()[0],
1010                   "Problem looking up I2C callouts on 22 153: "
1011                   "[json.exception.out_of_range.403] key '22' not found");
1012     }
1013 
1014     fs::remove_all(dataPath);
1015 }
1016 
1017 // Test when callouts are passed in via JSON
1018 TEST_F(SRCTest, JsonCalloutsTest)
1019 {
1020     const auto jsonCallouts = R"(
1021         [
1022             {
1023                 "LocationCode": "P0-C1",
1024                 "Priority": "H",
1025                 "MRUs": [
1026                     {
1027                         "ID": 42,
1028                         "Priority": "H"
1029                     },
1030                     {
1031                         "ID": 43,
1032                         "Priority": "M"
1033                     }
1034                 ]
1035             },
1036             {
1037                 "InventoryPath": "/inv/system/chassis/motherboard/cpu0",
1038                 "Priority": "M",
1039                 "Guarded": true,
1040                 "Deconfigured": true
1041             },
1042             {
1043                 "Procedure": "PROCEDU",
1044                 "Priority": "A"
1045             },
1046             {
1047                 "SymbolicFRU": "TRUSTED",
1048                 "Priority": "B",
1049                 "TrustedLocationCode": true,
1050                 "LocationCode": "P1-C23"
1051             },
1052             {
1053                 "SymbolicFRU": "FRUTST1",
1054                 "Priority": "C",
1055                 "LocationCode": "P1-C24"
1056             },
1057             {
1058                 "SymbolicFRU": "FRUTST2LONG",
1059                 "Priority": "L"
1060             },
1061             {
1062                 "Procedure": "fsi_path",
1063                 "Priority": "L"
1064             },
1065             {
1066                 "SymbolicFRU": "ambient_temp",
1067                 "Priority": "L"
1068             }
1069         ]
1070     )"_json;
1071 
1072     message::Entry entry;
1073     entry.src.type = 0xBD;
1074     entry.src.reasonCode = 0xABCD;
1075     entry.subsystem = 0x42;
1076 
1077     AdditionalData ad;
1078     NiceMock<MockDataInterface> dataIface;
1079 
1080     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1081                                       "system/entry"};
1082     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1083         .WillOnce(Return(std::vector<bool>{false, false, false}));
1084 
1085     // Callout 0 mock calls
1086     {
1087         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1088             .Times(1)
1089             .WillOnce(Return("UXXX-P0-C1"));
1090         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1091             .Times(1)
1092             .WillOnce(Return(std::vector<std::string>{
1093                 "/inv/system/chassis/motherboard/bmc"}));
1094         EXPECT_CALL(
1095             dataIface,
1096             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1097             .Times(1)
1098             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1099                             SetArgReferee<2>("CCCC"),
1100                             SetArgReferee<3>("123456789ABC")));
1101     }
1102     // Callout 1 mock calls
1103     {
1104         EXPECT_CALL(dataIface,
1105                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1106             .WillOnce(Return("UYYY-P5"));
1107         EXPECT_CALL(
1108             dataIface,
1109             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1110             .Times(1)
1111             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1112                             SetArgReferee<2>("DDDD"),
1113                             SetArgReferee<3>("23456789ABCD")));
1114     }
1115     // Callout 3 mock calls
1116     {
1117         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1118             .Times(1)
1119             .WillOnce(Return("UXXX-P1-C23"));
1120     }
1121     // Callout 4 mock calls
1122     {
1123         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1124             .Times(1)
1125             .WillOnce(Return("UXXX-P1-C24"));
1126     }
1127 
1128     SRC src{entry, ad, jsonCallouts, dataIface};
1129     ASSERT_TRUE(src.callouts());
1130 
1131     // Check the guarded and deconfigured flags
1132     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1133 
1134     const auto& callouts = src.callouts()->callouts();
1135     ASSERT_EQ(callouts.size(), 8);
1136 
1137     // Check callout 0
1138     {
1139         EXPECT_EQ(callouts[0]->priority(), 'H');
1140         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1141 
1142         auto& fru = callouts[0]->fruIdentity();
1143         EXPECT_EQ(fru->getPN().value(), "1234567");
1144         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1145         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1146         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1147 
1148         auto& mruCallouts = callouts[0]->mru();
1149         ASSERT_TRUE(mruCallouts);
1150         auto& mrus = mruCallouts->mrus();
1151         ASSERT_EQ(mrus.size(), 2);
1152         EXPECT_EQ(mrus[0].id, 42);
1153         EXPECT_EQ(mrus[0].priority, 'H');
1154         EXPECT_EQ(mrus[1].id, 43);
1155         EXPECT_EQ(mrus[1].priority, 'M');
1156     }
1157 
1158     // Check callout 1
1159     {
1160         EXPECT_EQ(callouts[1]->priority(), 'M');
1161         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1162 
1163         auto& fru = callouts[1]->fruIdentity();
1164         EXPECT_EQ(fru->getPN().value(), "2345678");
1165         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1166         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1167         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1168     }
1169 
1170     // Check callout 2
1171     {
1172         EXPECT_EQ(callouts[2]->priority(), 'A');
1173         EXPECT_EQ(callouts[2]->locationCode(), "");
1174 
1175         auto& fru = callouts[2]->fruIdentity();
1176         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1177         EXPECT_EQ(fru->failingComponentType(),
1178                   src::FRUIdentity::maintenanceProc);
1179     }
1180 
1181     // Check callout 3
1182     {
1183         EXPECT_EQ(callouts[3]->priority(), 'B');
1184         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1185 
1186         auto& fru = callouts[3]->fruIdentity();
1187         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1188         EXPECT_EQ(fru->failingComponentType(),
1189                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1190     }
1191 
1192     // Check callout 4
1193     {
1194         EXPECT_EQ(callouts[4]->priority(), 'C');
1195         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1196 
1197         auto& fru = callouts[4]->fruIdentity();
1198         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1199         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1200     }
1201 
1202     // Check callout 5
1203     {
1204         EXPECT_EQ(callouts[5]->priority(), 'L');
1205         EXPECT_EQ(callouts[5]->locationCode(), "");
1206 
1207         auto& fru = callouts[5]->fruIdentity();
1208         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1209         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1210     }
1211 
1212     // Check callout 6
1213     {
1214         EXPECT_EQ(callouts[6]->priority(), 'L');
1215         EXPECT_EQ(callouts[6]->locationCode(), "");
1216 
1217         auto& fru = callouts[6]->fruIdentity();
1218         EXPECT_EQ(fru->getMaintProc().value(), "BMC0004");
1219         EXPECT_EQ(fru->failingComponentType(),
1220                   src::FRUIdentity::maintenanceProc);
1221     }
1222 
1223     // Check callout 7
1224     {
1225         EXPECT_EQ(callouts[7]->priority(), 'L');
1226         EXPECT_EQ(callouts[7]->locationCode(), "");
1227 
1228         auto& fru = callouts[7]->fruIdentity();
1229         EXPECT_EQ(fru->getPN().value(), "AMBTEMP");
1230         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1231     }
1232 
1233     // Check that it didn't find any errors
1234     const auto& data = src.getDebugData();
1235     EXPECT_TRUE(data.empty());
1236 }
1237 
1238 TEST_F(SRCTest, JsonBadCalloutsTest)
1239 {
1240     // The first call will have a Throw in a mock call.
1241     // The second will have a different Throw in a mock call.
1242     // The others have issues with the Priority field.
1243     const auto jsonCallouts = R"(
1244         [
1245             {
1246                 "LocationCode": "P0-C1",
1247                 "Priority": "H"
1248             },
1249             {
1250                 "LocationCode": "P0-C2",
1251                 "Priority": "H"
1252             },
1253             {
1254                 "LocationCode": "P0-C3"
1255             },
1256             {
1257                 "LocationCode": "P0-C4",
1258                 "Priority": "X"
1259             }
1260         ]
1261     )"_json;
1262 
1263     message::Entry entry;
1264     entry.src.type = 0xBD;
1265     entry.src.reasonCode = 0xABCD;
1266     entry.subsystem = 0x42;
1267 
1268     AdditionalData ad;
1269     NiceMock<MockDataInterface> dataIface;
1270 
1271     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1272                                       "system/entry"};
1273     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1274         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1275 
1276     // Callout 0 mock calls
1277     // Expand location code will fail, so the unexpanded location
1278     // code should show up in the callout instead.
1279     {
1280         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1281             .WillOnce(Throw(std::runtime_error("Fail")));
1282 
1283         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1284             .Times(1)
1285             .WillOnce(Return(std::vector<std::string>{
1286                 "/inv/system/chassis/motherboard/bmc"}));
1287         EXPECT_CALL(
1288             dataIface,
1289             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1290             .Times(1)
1291             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1292                             SetArgReferee<2>("CCCC"),
1293                             SetArgReferee<3>("123456789ABC")));
1294     }
1295 
1296     // Callout 1 mock calls
1297     // getInventoryFromLocCode will fail
1298     {
1299         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1300             .Times(1)
1301             .WillOnce(Return("UXXX-P0-C2"));
1302 
1303         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1304             .Times(1)
1305             .WillOnce(Throw(std::runtime_error("Fail")));
1306     }
1307 
1308     SRC src{entry, ad, jsonCallouts, dataIface};
1309 
1310     ASSERT_TRUE(src.callouts());
1311 
1312     const auto& callouts = src.callouts()->callouts();
1313 
1314     // Only the first callout was successful
1315     ASSERT_EQ(callouts.size(), 1);
1316 
1317     {
1318         EXPECT_EQ(callouts[0]->priority(), 'H');
1319         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1320 
1321         auto& fru = callouts[0]->fruIdentity();
1322         EXPECT_EQ(fru->getPN().value(), "1234567");
1323         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1324         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1325         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1326     }
1327 
1328     const auto& data = src.getDebugData();
1329     ASSERT_EQ(data.size(), 4);
1330     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1331     EXPECT_STREQ(data[1].c_str(),
1332                  "Failed extracting callout data from JSON: Unable to "
1333                  "get inventory path from location code: P0-C2: Fail");
1334     EXPECT_STREQ(data[2].c_str(),
1335                  "Failed extracting callout data from JSON: "
1336                  "[json.exception.out_of_range.403] key 'Priority' not found");
1337     EXPECT_STREQ(data[3].c_str(),
1338                  "Failed extracting callout data from JSON: Invalid "
1339                  "priority 'X' found in JSON callout");
1340 }
1341 
1342 // Test that an inventory path callout can have
1343 // a different priority than H.
1344 TEST_F(SRCTest, InventoryCalloutTestPriority)
1345 {
1346     message::Entry entry;
1347     entry.src.type = 0xBD;
1348     entry.src.reasonCode = 0xABCD;
1349     entry.subsystem = 0x42;
1350 
1351     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1352                                     "CALLOUT_PRIORITY=M"};
1353     AdditionalData ad{adData};
1354     NiceMock<MockDataInterface> dataIface;
1355 
1356     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1357                                       "system/entry"};
1358     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1359         .WillOnce(Return(std::vector<bool>{false, false, false}));
1360 
1361     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1362         .WillOnce(Return("UTMS-P1"));
1363 
1364     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1365         .Times(1)
1366         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1367                         SetArgReferee<3>("123456789ABC")));
1368 
1369     SRC src{entry, ad, dataIface};
1370     EXPECT_TRUE(src.valid());
1371 
1372     ASSERT_TRUE(src.callouts());
1373 
1374     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1375 
1376     auto& callout = src.callouts()->callouts().front();
1377 
1378     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1379     EXPECT_EQ(callout->priority(), 'M');
1380 }
1381 
1382 // Test for bmc & platform dump status bits
1383 TEST_F(SRCTest, DumpStatusBitsCheck)
1384 {
1385     message::Entry entry;
1386     entry.src.type = 0xBD;
1387     entry.src.reasonCode = 0xABCD;
1388     entry.subsystem = 0x42;
1389 
1390     AdditionalData ad;
1391     NiceMock<MockDataInterface> dataIface;
1392     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1393                                       "system/entry"};
1394 
1395     {
1396         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1397             .WillOnce(Return(std::vector<bool>{true, false, false}));
1398 
1399         SRC src{entry, ad, dataIface};
1400         EXPECT_TRUE(src.valid());
1401 
1402         const auto& hexwords = src.hexwordData();
1403         EXPECT_EQ(0x00080055, hexwords[0]);
1404     }
1405 
1406     {
1407         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1408             .WillOnce(Return(std::vector<bool>{false, true, false}));
1409 
1410         SRC src{entry, ad, dataIface};
1411         EXPECT_TRUE(src.valid());
1412 
1413         const auto& hexwords = src.hexwordData();
1414         EXPECT_EQ(0x00000255, hexwords[0]);
1415     }
1416 
1417     {
1418         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1419             .WillOnce(Return(std::vector<bool>{false, false, true}));
1420 
1421         SRC src{entry, ad, dataIface};
1422         EXPECT_TRUE(src.valid());
1423 
1424         const auto& hexwords = src.hexwordData();
1425         EXPECT_EQ(0x00000455, hexwords[0]);
1426     }
1427 
1428     {
1429         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1430             .WillOnce(Return(std::vector<bool>{true, true, true}));
1431 
1432         SRC src{entry, ad, dataIface};
1433         EXPECT_TRUE(src.valid());
1434 
1435         const auto& hexwords = src.hexwordData();
1436         EXPECT_EQ(0x00080655, hexwords[0]);
1437     }
1438 }
1439 
1440 // Test SRC with additional data - PEL_SUBSYSTEM
1441 TEST_F(SRCTest, TestPELSubsystem)
1442 {
1443     message::Entry entry;
1444     entry.src.type = 0xBD;
1445     entry.src.reasonCode = 0xABCD;
1446     entry.subsystem = 0x42;
1447 
1448     // Values for the SRC words pointed to above
1449     std::vector<std::string> adData{"PEL_SUBSYSTEM=0x20"};
1450     AdditionalData ad{adData};
1451     NiceMock<MockDataInterface> dataIface;
1452 
1453     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
1454 
1455     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1456                                       "system/entry"};
1457     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1458         .WillOnce(Return(std::vector<bool>{false, false, false}));
1459 
1460     SRC src{entry, ad, dataIface};
1461 
1462     EXPECT_TRUE(src.valid());
1463 
1464     EXPECT_EQ(src.asciiString(), "BD20ABCD                        ");
1465 }
1466 
1467 void setAsciiString(std::vector<uint8_t>& src, const std::string& value)
1468 {
1469     assert(40 + value.size() <= src.size());
1470 
1471     for (size_t i = 0; i < value.size(); i++)
1472     {
1473         src[40 + i] = value[i];
1474     }
1475 }
1476 
1477 TEST_F(SRCTest, TestGetProgressCode)
1478 {
1479     {
1480         // A real SRC with CC009184
1481         std::vector<uint8_t> src{
1482             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1483             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1484             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1485             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1486             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1487 
1488         EXPECT_EQ(SRC::getProgressCode(src), 0xCC009184);
1489     }
1490 
1491     {
1492         // A real SRC with STANDBY
1493         std::vector<uint8_t> src{
1494             2,  0,  0,  1,  0,  0,  0,  72, 0,  0,  0,  0,  0,  0,  0,
1495             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1496             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  83, 84, 65, 78, 68,
1497             66, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1498             32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1499 
1500         EXPECT_EQ(SRC::getProgressCode(src), 0);
1501     }
1502 
1503     {
1504         // A real SRC with CC009184, but 1 byte too short
1505         std::vector<uint8_t> src{
1506             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1507             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1508             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1509             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1510             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1511         src.resize(71);
1512         EXPECT_EQ(SRC::getProgressCode(src), 0);
1513     }
1514 
1515     {
1516         // A few different ones
1517         const std::map<std::string, uint32_t> progressCodes{
1518             {"12345678", 0x12345678}, {"ABCDEF00", 0xABCDEF00},
1519             {"abcdef00", 0xABCDEF00}, {"X1234567", 0},
1520             {"1234567X", 0},          {"1       ", 0}};
1521 
1522         std::vector<uint8_t> src(72, 0x0);
1523 
1524         for (const auto& [code, expected] : progressCodes)
1525         {
1526             setAsciiString(src, code);
1527             EXPECT_EQ(SRC::getProgressCode(src), expected);
1528         }
1529 
1530         // empty
1531         src.clear();
1532         EXPECT_EQ(SRC::getProgressCode(src), 0);
1533     }
1534 }
1535 
1536 // Test progress is in right SRC hex data field
1537 TEST_F(SRCTest, TestProgressCodeField)
1538 {
1539     message::Entry entry;
1540     entry.src.type = 0xBD;
1541     entry.src.reasonCode = 0xABCD;
1542     entry.subsystem = 0x42;
1543 
1544     AdditionalData ad;
1545     NiceMock<MockDataInterface> dataIface;
1546     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1547                                       "system/entry"};
1548     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1549         .WillOnce(Return(std::vector<bool>{false, false, false}));
1550     EXPECT_CALL(dataIface, getRawProgressSRC())
1551         .WillOnce(Return(std::vector<uint8_t>{
1552             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1553             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1554             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1555             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1556             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32}));
1557 
1558     SRC src{entry, ad, dataIface};
1559     EXPECT_TRUE(src.valid());
1560 
1561     // Verify that the hex vlue is set at the right hexword
1562     EXPECT_EQ(src.hexwordData()[2], 0xCC009184);
1563 }
1564