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