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