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(
648                 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
649 
650         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C9", 0, false))
651             .WillOnce(Return(
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(
870             Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
871 
872     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
873         .Times(3)
874         .WillRepeatedly(
875             Return("/xyz/openbmc_project/inventory/chassis/motherboard"));
876 
877     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1-C15", 0, false))
878         .Times(3)
879         .WillRepeatedly(
880             Return("/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("/inv/system/chassis/motherboard/bmc"));
1093         EXPECT_CALL(
1094             dataIface,
1095             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1096             .Times(1)
1097             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1098                             SetArgReferee<2>("CCCC"),
1099                             SetArgReferee<3>("123456789ABC")));
1100     }
1101     // Callout 1 mock calls
1102     {
1103         EXPECT_CALL(dataIface,
1104                     getLocationCode("/inv/system/chassis/motherboard/cpu0"))
1105             .WillOnce(Return("UYYY-P5"));
1106         EXPECT_CALL(
1107             dataIface,
1108             getHWCalloutFields("/inv/system/chassis/motherboard/cpu0", _, _, _))
1109             .Times(1)
1110             .WillOnce(DoAll(SetArgReferee<1>("2345678"),
1111                             SetArgReferee<2>("DDDD"),
1112                             SetArgReferee<3>("23456789ABCD")));
1113     }
1114     // Callout 3 mock calls
1115     {
1116         EXPECT_CALL(dataIface, expandLocationCode("P1-C23", 0))
1117             .Times(1)
1118             .WillOnce(Return("UXXX-P1-C23"));
1119     }
1120     // Callout 4 mock calls
1121     {
1122         EXPECT_CALL(dataIface, expandLocationCode("P1-C24", 0))
1123             .Times(1)
1124             .WillOnce(Return("UXXX-P1-C24"));
1125     }
1126 
1127     SRC src{entry, ad, jsonCallouts, dataIface};
1128     ASSERT_TRUE(src.callouts());
1129 
1130     // Check the guarded and deconfigured flags
1131     EXPECT_TRUE(src.hexwordData()[3] & 0x03000000);
1132 
1133     const auto& callouts = src.callouts()->callouts();
1134     ASSERT_EQ(callouts.size(), 8);
1135 
1136     // Check callout 0
1137     {
1138         EXPECT_EQ(callouts[0]->priority(), 'H');
1139         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1140 
1141         auto& fru = callouts[0]->fruIdentity();
1142         EXPECT_EQ(fru->getPN().value(), "1234567");
1143         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1144         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1145         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1146 
1147         auto& mruCallouts = callouts[0]->mru();
1148         ASSERT_TRUE(mruCallouts);
1149         auto& mrus = mruCallouts->mrus();
1150         ASSERT_EQ(mrus.size(), 2);
1151         EXPECT_EQ(mrus[0].id, 42);
1152         EXPECT_EQ(mrus[0].priority, 'H');
1153         EXPECT_EQ(mrus[1].id, 43);
1154         EXPECT_EQ(mrus[1].priority, 'M');
1155     }
1156 
1157     // Check callout 1
1158     {
1159         EXPECT_EQ(callouts[1]->priority(), 'M');
1160         EXPECT_EQ(callouts[1]->locationCode(), "UYYY-P5");
1161 
1162         auto& fru = callouts[1]->fruIdentity();
1163         EXPECT_EQ(fru->getPN().value(), "2345678");
1164         EXPECT_EQ(fru->getCCIN().value(), "DDDD");
1165         EXPECT_EQ(fru->getSN().value(), "23456789ABCD");
1166         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1167     }
1168 
1169     // Check callout 2
1170     {
1171         EXPECT_EQ(callouts[2]->priority(), 'A');
1172         EXPECT_EQ(callouts[2]->locationCode(), "");
1173 
1174         auto& fru = callouts[2]->fruIdentity();
1175         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1176         EXPECT_EQ(fru->failingComponentType(),
1177                   src::FRUIdentity::maintenanceProc);
1178     }
1179 
1180     // Check callout 3
1181     {
1182         EXPECT_EQ(callouts[3]->priority(), 'B');
1183         EXPECT_EQ(callouts[3]->locationCode(), "UXXX-P1-C23");
1184 
1185         auto& fru = callouts[3]->fruIdentity();
1186         EXPECT_EQ(fru->getPN().value(), "TRUSTED");
1187         EXPECT_EQ(fru->failingComponentType(),
1188                   src::FRUIdentity::symbolicFRUTrustedLocCode);
1189     }
1190 
1191     // Check callout 4
1192     {
1193         EXPECT_EQ(callouts[4]->priority(), 'C');
1194         EXPECT_EQ(callouts[4]->locationCode(), "UXXX-P1-C24");
1195 
1196         auto& fru = callouts[4]->fruIdentity();
1197         EXPECT_EQ(fru->getPN().value(), "FRUTST1");
1198         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1199     }
1200 
1201     // Check callout 5
1202     {
1203         EXPECT_EQ(callouts[5]->priority(), 'L');
1204         EXPECT_EQ(callouts[5]->locationCode(), "");
1205 
1206         auto& fru = callouts[5]->fruIdentity();
1207         EXPECT_EQ(fru->getPN().value(), "FRUTST2");
1208         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1209     }
1210 
1211     // Check callout 6
1212     {
1213         EXPECT_EQ(callouts[6]->priority(), 'L');
1214         EXPECT_EQ(callouts[6]->locationCode(), "");
1215 
1216         auto& fru = callouts[6]->fruIdentity();
1217         EXPECT_EQ(fru->getMaintProc().value(), "BMC0004");
1218         EXPECT_EQ(fru->failingComponentType(),
1219                   src::FRUIdentity::maintenanceProc);
1220     }
1221 
1222     // Check callout 7
1223     {
1224         EXPECT_EQ(callouts[7]->priority(), 'L');
1225         EXPECT_EQ(callouts[7]->locationCode(), "");
1226 
1227         auto& fru = callouts[7]->fruIdentity();
1228         EXPECT_EQ(fru->getPN().value(), "AMBTEMP");
1229         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::symbolicFRU);
1230     }
1231 
1232     // Check that it didn't find any errors
1233     const auto& data = src.getDebugData();
1234     EXPECT_TRUE(data.empty());
1235 }
1236 
1237 TEST_F(SRCTest, JsonBadCalloutsTest)
1238 {
1239     // The first call will have a Throw in a mock call.
1240     // The second will have a different Throw in a mock call.
1241     // The others have issues with the Priority field.
1242     const auto jsonCallouts = R"(
1243         [
1244             {
1245                 "LocationCode": "P0-C1",
1246                 "Priority": "H"
1247             },
1248             {
1249                 "LocationCode": "P0-C2",
1250                 "Priority": "H"
1251             },
1252             {
1253                 "LocationCode": "P0-C3"
1254             },
1255             {
1256                 "LocationCode": "P0-C4",
1257                 "Priority": "X"
1258             }
1259         ]
1260     )"_json;
1261 
1262     message::Entry entry;
1263     entry.src.type = 0xBD;
1264     entry.src.reasonCode = 0xABCD;
1265     entry.subsystem = 0x42;
1266 
1267     AdditionalData ad;
1268     NiceMock<MockDataInterface> dataIface;
1269 
1270     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1271                                       "system/entry"};
1272     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1273         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1274 
1275     // Callout 0 mock calls
1276     // Expand location code will fail, so the unexpanded location
1277     // code should show up in the callout instead.
1278     {
1279         EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1280             .WillOnce(Throw(std::runtime_error("Fail")));
1281 
1282         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1283             .Times(1)
1284             .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
1285         EXPECT_CALL(
1286             dataIface,
1287             getHWCalloutFields("/inv/system/chassis/motherboard/bmc", _, _, _))
1288             .Times(1)
1289             .WillOnce(DoAll(SetArgReferee<1>("1234567"),
1290                             SetArgReferee<2>("CCCC"),
1291                             SetArgReferee<3>("123456789ABC")));
1292     }
1293 
1294     // Callout 1 mock calls
1295     // getInventoryFromLocCode will fail
1296     {
1297         EXPECT_CALL(dataIface, expandLocationCode("P0-C2", 0))
1298             .Times(1)
1299             .WillOnce(Return("UXXX-P0-C2"));
1300 
1301         EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C2", 0, false))
1302             .Times(1)
1303             .WillOnce(Throw(std::runtime_error("Fail")));
1304     }
1305 
1306     SRC src{entry, ad, jsonCallouts, dataIface};
1307 
1308     ASSERT_TRUE(src.callouts());
1309 
1310     const auto& callouts = src.callouts()->callouts();
1311 
1312     // Only the first callout was successful
1313     ASSERT_EQ(callouts.size(), 1);
1314 
1315     {
1316         EXPECT_EQ(callouts[0]->priority(), 'H');
1317         EXPECT_EQ(callouts[0]->locationCode(), "P0-C1");
1318 
1319         auto& fru = callouts[0]->fruIdentity();
1320         EXPECT_EQ(fru->getPN().value(), "1234567");
1321         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1322         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1323         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1324     }
1325 
1326     const auto& data = src.getDebugData();
1327     ASSERT_EQ(data.size(), 4);
1328     EXPECT_STREQ(data[0].c_str(), "Unable to expand location code P0-C1: Fail");
1329     EXPECT_STREQ(data[1].c_str(),
1330                  "Failed extracting callout data from JSON: Unable to "
1331                  "get inventory path from location code: P0-C2: Fail");
1332     EXPECT_STREQ(data[2].c_str(),
1333                  "Failed extracting callout data from JSON: "
1334                  "[json.exception.out_of_range.403] key 'Priority' not found");
1335     EXPECT_STREQ(data[3].c_str(),
1336                  "Failed extracting callout data from JSON: Invalid "
1337                  "priority 'X' found in JSON callout");
1338 }
1339 
1340 // Test that an inventory path callout can have
1341 // a different priority than H.
1342 TEST_F(SRCTest, InventoryCalloutTestPriority)
1343 {
1344     message::Entry entry;
1345     entry.src.type = 0xBD;
1346     entry.src.reasonCode = 0xABCD;
1347     entry.subsystem = 0x42;
1348 
1349     std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard",
1350                                     "CALLOUT_PRIORITY=M"};
1351     AdditionalData ad{adData};
1352     NiceMock<MockDataInterface> dataIface;
1353 
1354     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1355                                       "system/entry"};
1356     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1357         .WillOnce(Return(std::vector<bool>{false, false, false}));
1358 
1359     EXPECT_CALL(dataIface, getLocationCode("motherboard"))
1360         .WillOnce(Return("UTMS-P1"));
1361 
1362     EXPECT_CALL(dataIface, getHWCalloutFields("motherboard", _, _, _))
1363         .Times(1)
1364         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1365                         SetArgReferee<3>("123456789ABC")));
1366 
1367     SRC src{entry, ad, dataIface};
1368     EXPECT_TRUE(src.valid());
1369 
1370     ASSERT_TRUE(src.callouts());
1371 
1372     EXPECT_EQ(src.callouts()->callouts().size(), 1);
1373 
1374     auto& callout = src.callouts()->callouts().front();
1375 
1376     EXPECT_EQ(callout->locationCode(), "UTMS-P1");
1377     EXPECT_EQ(callout->priority(), 'M');
1378 }
1379 
1380 // Test for bmc & platform dump status bits
1381 TEST_F(SRCTest, DumpStatusBitsCheck)
1382 {
1383     message::Entry entry;
1384     entry.src.type = 0xBD;
1385     entry.src.reasonCode = 0xABCD;
1386     entry.subsystem = 0x42;
1387 
1388     AdditionalData ad;
1389     NiceMock<MockDataInterface> dataIface;
1390     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1391                                       "system/entry"};
1392 
1393     {
1394         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1395             .WillOnce(Return(std::vector<bool>{true, false, false}));
1396 
1397         SRC src{entry, ad, dataIface};
1398         EXPECT_TRUE(src.valid());
1399 
1400         const auto& hexwords = src.hexwordData();
1401         EXPECT_EQ(0x00080055, hexwords[0]);
1402     }
1403 
1404     {
1405         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1406             .WillOnce(Return(std::vector<bool>{false, true, false}));
1407 
1408         SRC src{entry, ad, dataIface};
1409         EXPECT_TRUE(src.valid());
1410 
1411         const auto& hexwords = src.hexwordData();
1412         EXPECT_EQ(0x00000255, hexwords[0]);
1413     }
1414 
1415     {
1416         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1417             .WillOnce(Return(std::vector<bool>{false, false, true}));
1418 
1419         SRC src{entry, ad, dataIface};
1420         EXPECT_TRUE(src.valid());
1421 
1422         const auto& hexwords = src.hexwordData();
1423         EXPECT_EQ(0x00000455, hexwords[0]);
1424     }
1425 
1426     {
1427         EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1428             .WillOnce(Return(std::vector<bool>{true, true, true}));
1429 
1430         SRC src{entry, ad, dataIface};
1431         EXPECT_TRUE(src.valid());
1432 
1433         const auto& hexwords = src.hexwordData();
1434         EXPECT_EQ(0x00080655, hexwords[0]);
1435     }
1436 }
1437 
1438 // Test SRC with additional data - PEL_SUBSYSTEM
1439 TEST_F(SRCTest, TestPELSubsystem)
1440 {
1441     message::Entry entry;
1442     entry.src.type = 0xBD;
1443     entry.src.reasonCode = 0xABCD;
1444     entry.subsystem = 0x42;
1445 
1446     // Values for the SRC words pointed to above
1447     std::vector<std::string> adData{"PEL_SUBSYSTEM=0x20"};
1448     AdditionalData ad{adData};
1449     NiceMock<MockDataInterface> dataIface;
1450 
1451     EXPECT_CALL(dataIface, getMotherboardCCIN).WillOnce(Return("ABCD"));
1452 
1453     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1454                                       "system/entry"};
1455     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1456         .WillOnce(Return(std::vector<bool>{false, false, false}));
1457 
1458     SRC src{entry, ad, dataIface};
1459 
1460     EXPECT_TRUE(src.valid());
1461 
1462     EXPECT_EQ(src.asciiString(), "BD20ABCD                        ");
1463 }
1464 
1465 void setAsciiString(std::vector<uint8_t>& src, const std::string& value)
1466 {
1467     assert(40 + value.size() <= src.size());
1468 
1469     for (size_t i = 0; i < value.size(); i++)
1470     {
1471         src[40 + i] = value[i];
1472     }
1473 }
1474 
1475 TEST_F(SRCTest, TestGetProgressCode)
1476 {
1477     {
1478         // A real SRC with CC009184
1479         std::vector<uint8_t> src{
1480             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1481             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1482             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1483             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1484             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1485 
1486         EXPECT_EQ(SRC::getProgressCode(src), 0xCC009184);
1487     }
1488 
1489     {
1490         // A real SRC with STANDBY
1491         std::vector<uint8_t> src{
1492             2,  0,  0,  1,  0,  0,  0,  72, 0,  0,  0,  0,  0,  0,  0,
1493             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
1494             0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  83, 84, 65, 78, 68,
1495             66, 89, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
1496             32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
1497 
1498         EXPECT_EQ(SRC::getProgressCode(src), 0);
1499     }
1500 
1501     {
1502         // A real SRC with CC009184, but 1 byte too short
1503         std::vector<uint8_t> src{
1504             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1505             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1506             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1507             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1508             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32};
1509         src.resize(71);
1510         EXPECT_EQ(SRC::getProgressCode(src), 0);
1511     }
1512 
1513     {
1514         // A few different ones
1515         const std::map<std::string, uint32_t> progressCodes{
1516             {"12345678", 0x12345678}, {"ABCDEF00", 0xABCDEF00},
1517             {"abcdef00", 0xABCDEF00}, {"X1234567", 0},
1518             {"1234567X", 0},          {"1       ", 0}};
1519 
1520         std::vector<uint8_t> src(72, 0x0);
1521 
1522         for (const auto& [code, expected] : progressCodes)
1523         {
1524             setAsciiString(src, code);
1525             EXPECT_EQ(SRC::getProgressCode(src), expected);
1526         }
1527 
1528         // empty
1529         src.clear();
1530         EXPECT_EQ(SRC::getProgressCode(src), 0);
1531     }
1532 }
1533 
1534 // Test progress is in right SRC hex data field
1535 TEST_F(SRCTest, TestProgressCodeField)
1536 {
1537     message::Entry entry;
1538     entry.src.type = 0xBD;
1539     entry.src.reasonCode = 0xABCD;
1540     entry.subsystem = 0x42;
1541 
1542     AdditionalData ad;
1543     NiceMock<MockDataInterface> dataIface;
1544     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1545                                       "system/entry"};
1546     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1547         .WillOnce(Return(std::vector<bool>{false, false, false}));
1548     EXPECT_CALL(dataIface, getRawProgressSRC())
1549         .WillOnce(Return(std::vector<uint8_t>{
1550             2,  8,   0,  9,   0,   0,  0,  72, 0,  0,  0,  224, 0,  0,  0,
1551             0,  204, 0,  145, 132, 0,  0,  0,  0,  0,  0,  0,   0,  0,  0,
1552             0,  0,   0,  0,   0,   0,  0,  0,  0,  0,  67, 67,  48, 48, 57,
1553             49, 56,  52, 32,  32,  32, 32, 32, 32, 32, 32, 32,  32, 32, 32,
1554             32, 32,  32, 32,  32,  32, 32, 32, 32, 32, 32, 32}));
1555 
1556     SRC src{entry, ad, dataIface};
1557     EXPECT_TRUE(src.valid());
1558 
1559     // Verify that the hex vlue is set at the right hexword
1560     EXPECT_EQ(src.hexwordData()[2], 0xCC009184);
1561 }
1562