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