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 "elog_entry.hpp"
17 #include "extensions/openpower-pels/generic.hpp"
18 #include "extensions/openpower-pels/pel.hpp"
19 #include "mocks.hpp"
20 #include "pel_utils.hpp"
21 
22 #include <filesystem>
23 #include <fstream>
24 
25 #include <gtest/gtest.h>
26 
27 namespace fs = std::filesystem;
28 using namespace openpower::pels;
29 using ::testing::Return;
30 
31 class PELTest : public CleanLogID
32 {
33 };
34 
35 TEST_F(PELTest, FlattenTest)
36 {
37     auto data = pelDataFactory(TestPELType::pelSimple);
38     auto pel = std::make_unique<PEL>(data);
39 
40     // Check a few fields
41     EXPECT_TRUE(pel->valid());
42     EXPECT_EQ(pel->id(), 0x80818283);
43     EXPECT_EQ(pel->plid(), 0x50515253);
44     EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
45     EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
46 
47     // Test that data in == data out
48     auto flattenedData = pel->data();
49     EXPECT_EQ(data, flattenedData);
50     EXPECT_EQ(flattenedData.size(), pel->size());
51 }
52 
53 TEST_F(PELTest, CommitTimeTest)
54 {
55     auto data = pelDataFactory(TestPELType::pelSimple);
56     auto pel = std::make_unique<PEL>(data);
57 
58     auto origTime = pel->commitTime();
59     pel->setCommitTime();
60     auto newTime = pel->commitTime();
61 
62     EXPECT_NE(origTime, newTime);
63 
64     // Make a new PEL and check new value is still there
65     auto newData = pel->data();
66     auto newPel = std::make_unique<PEL>(newData);
67     EXPECT_EQ(newTime, newPel->commitTime());
68 }
69 
70 TEST_F(PELTest, AssignIDTest)
71 {
72     auto data = pelDataFactory(TestPELType::pelSimple);
73     auto pel = std::make_unique<PEL>(data);
74 
75     auto origID = pel->id();
76     pel->assignID();
77     auto newID = pel->id();
78 
79     EXPECT_NE(origID, newID);
80 
81     // Make a new PEL and check new value is still there
82     auto newData = pel->data();
83     auto newPel = std::make_unique<PEL>(newData);
84     EXPECT_EQ(newID, newPel->id());
85 }
86 
87 TEST_F(PELTest, WithLogIDTest)
88 {
89     auto data = pelDataFactory(TestPELType::pelSimple);
90     auto pel = std::make_unique<PEL>(data, 0x42);
91 
92     EXPECT_TRUE(pel->valid());
93     EXPECT_EQ(pel->obmcLogID(), 0x42);
94 }
95 
96 TEST_F(PELTest, InvalidPELTest)
97 {
98     auto data = pelDataFactory(TestPELType::pelSimple);
99 
100     // Too small
101     data.resize(PrivateHeader::flattenedSize());
102 
103     auto pel = std::make_unique<PEL>(data);
104 
105     EXPECT_TRUE(pel->privateHeader().valid());
106     EXPECT_FALSE(pel->userHeader().valid());
107     EXPECT_FALSE(pel->valid());
108 
109     // Now corrupt the private header
110     data = pelDataFactory(TestPELType::pelSimple);
111     data.at(0) = 0;
112     pel = std::make_unique<PEL>(data);
113 
114     EXPECT_FALSE(pel->privateHeader().valid());
115     EXPECT_TRUE(pel->userHeader().valid());
116     EXPECT_FALSE(pel->valid());
117 }
118 
119 TEST_F(PELTest, EmptyDataTest)
120 {
121     std::vector<uint8_t> data;
122     auto pel = std::make_unique<PEL>(data);
123 
124     EXPECT_FALSE(pel->privateHeader().valid());
125     EXPECT_FALSE(pel->userHeader().valid());
126     EXPECT_FALSE(pel->valid());
127 }
128 
129 TEST_F(PELTest, CreateFromRegistryTest)
130 {
131     message::Entry regEntry;
132     uint64_t timestamp = 5;
133 
134     regEntry.name = "test";
135     regEntry.subsystem = 5;
136     regEntry.actionFlags = 0xC000;
137     regEntry.src.type = 0xBD;
138     regEntry.src.reasonCode = 0x1234;
139 
140     std::vector<std::string> data{"KEY1=VALUE1"};
141     AdditionalData ad{data};
142     MockDataInterface dataIface;
143 
144     PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, ad,
145             dataIface};
146 
147     EXPECT_TRUE(pel.valid());
148     EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
149     EXPECT_EQ(pel.userHeader().severity(), 0x40);
150 
151     EXPECT_EQ(pel.primarySRC().value()->asciiString(),
152               "BD051234                        ");
153 
154     // Check that certain optional sections have been created
155     size_t mtmsCount = 0;
156     size_t euhCount = 0;
157     size_t udCount = 0;
158 
159     for (const auto& section : pel.optionalSections())
160     {
161         if (section->header().id ==
162             static_cast<uint16_t>(SectionID::failingMTMS))
163         {
164             mtmsCount++;
165         }
166         else if (section->header().id ==
167                  static_cast<uint16_t>(SectionID::extendedUserHeader))
168         {
169             euhCount++;
170         }
171         else if (section->header().id ==
172                  static_cast<uint16_t>(SectionID::userData))
173         {
174             udCount++;
175         }
176     }
177 
178     EXPECT_EQ(mtmsCount, 1);
179     EXPECT_EQ(euhCount, 1);
180     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
181 }
182 
183 // Test that we'll create Generic optional sections for sections that
184 // there aren't explicit classes for.
185 TEST_F(PELTest, GenericSectionTest)
186 {
187     auto data = pelDataFactory(TestPELType::pelSimple);
188 
189     std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX'
190                                   0x00, 0x18, // Size
191                                   0x01, 0x02, // version, subtype
192                                   0x03, 0x04, // comp ID
193 
194                                   // some data
195                                   0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
196                                   0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
197                                   0x00};
198 
199     std::vector<uint8_t> section2{
200         0x59, 0x59, // ID 'YY'
201         0x00, 0x20, // Size
202         0x01, 0x02, // version, subtype
203         0x03, 0x04, // comp ID
204 
205         // some data
206         0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
207         0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
208 
209     // Add the new sections at the end
210     data.insert(data.end(), section1.begin(), section1.end());
211     data.insert(data.end(), section2.begin(), section2.end());
212 
213     // Increment the section count
214     data.at(27) += 2;
215     auto origData = data;
216 
217     PEL pel{data};
218 
219     const auto& sections = pel.optionalSections();
220 
221     bool foundXX = false;
222     bool foundYY = false;
223 
224     // Check that we can find these 2 Generic sections
225     for (const auto& section : sections)
226     {
227         if (section->header().id == 0x5858)
228         {
229             foundXX = true;
230             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
231         }
232         else if (section->header().id == 0x5959)
233         {
234             foundYY = true;
235             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
236         }
237     }
238 
239     EXPECT_TRUE(foundXX);
240     EXPECT_TRUE(foundYY);
241 
242     // Now flatten and check
243     auto newData = pel.data();
244 
245     EXPECT_EQ(origData, newData);
246 }
247 
248 // Test that an invalid section will still get a Generic object
249 TEST_F(PELTest, InvalidGenericTest)
250 {
251     auto data = pelDataFactory(TestPELType::pelSimple);
252 
253     // Not a valid section
254     std::vector<uint8_t> section1{0x01, 0x02, 0x03};
255 
256     data.insert(data.end(), section1.begin(), section1.end());
257 
258     // Increment the section count
259     data.at(27) += 1;
260 
261     PEL pel{data};
262     EXPECT_FALSE(pel.valid());
263 
264     const auto& sections = pel.optionalSections();
265 
266     bool foundGeneric = false;
267     for (const auto& section : sections)
268     {
269         if (dynamic_cast<Generic*>(section.get()) != nullptr)
270         {
271             foundGeneric = true;
272             EXPECT_EQ(section->valid(), false);
273             break;
274         }
275     }
276 
277     EXPECT_TRUE(foundGeneric);
278 }
279 
280 // Create a UserData section out of AdditionalData
281 TEST_F(PELTest, MakeUDSectionTest)
282 {
283     std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
284                                 "ESEL=TEST"};
285     AdditionalData additionalData{ad};
286 
287     auto ud = util::makeADUserDataSection(additionalData);
288 
289     EXPECT_TRUE(ud->valid());
290     EXPECT_EQ(ud->header().id, 0x5544);
291     EXPECT_EQ(ud->header().version, 0x01);
292     EXPECT_EQ(ud->header().subType, 0x01);
293     EXPECT_EQ(ud->header().componentID, 0x2000);
294 
295     const auto& d = ud->data();
296 
297     std::string jsonString{d.begin(), d.end()};
298 
299     std::string expectedJSON =
300         R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
301 
302     // The actual data is null padded to a 4B boundary.
303     std::vector<uint8_t> expectedData;
304     expectedData.resize(52, '\0');
305     memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
306 
307     EXPECT_EQ(d, expectedData);
308 
309     // Ensure we can read this as JSON
310     auto newJSON = nlohmann::json::parse(jsonString);
311     EXPECT_EQ(newJSON["KEY1"], "VALUE1");
312     EXPECT_EQ(newJSON["KEY2"], "VALUE2");
313     EXPECT_EQ(newJSON["KEY3"], "VALUE3");
314 }
315 
316 // Create the UserData section that contains system info
317 TEST_F(PELTest, SysInfoSectionTest)
318 {
319     MockDataInterface dataIface;
320 
321     EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
322     EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
323     EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
324     EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
325 
326     std::string pid = "_PID=" + std::to_string(getpid());
327     std::vector<std::string> ad{pid};
328     AdditionalData additionalData{ad};
329 
330     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
331 
332     EXPECT_TRUE(ud->valid());
333     EXPECT_EQ(ud->header().id, 0x5544);
334     EXPECT_EQ(ud->header().version, 0x01);
335     EXPECT_EQ(ud->header().subType, 0x01);
336     EXPECT_EQ(ud->header().componentID, 0x2000);
337 
338     // Pull out the JSON data and check it.
339     const auto& d = ud->data();
340     std::string jsonString{d.begin(), d.end()};
341     auto json = nlohmann::json::parse(jsonString);
342 
343     // Ensure the 'Process Name' entry contains 'pel_test'
344     auto name = json["Process Name"].get<std::string>();
345     EXPECT_NE(name.find("pel_test"), std::string::npos);
346 
347     auto version = json["BMC Version ID"].get<std::string>();
348     EXPECT_EQ(version, "ABCD1234");
349 
350     auto state = json["BMCState"].get<std::string>();
351     EXPECT_EQ(state, "Ready");
352 
353     state = json["ChassisState"].get<std::string>();
354     EXPECT_EQ(state, "On");
355 
356     state = json["HostState"].get<std::string>();
357     EXPECT_EQ(state, "Off");
358 }
359 
360 // Test that the sections that override
361 //     virtual std::optional<std::string> Section::getJSON() const
362 // return valid JSON.
363 TEST_F(PELTest, SectionJSONTest)
364 {
365     auto data = pelDataFactory(TestPELType::pelSimple);
366     PEL pel{data};
367 
368     // Check that all JSON returned from the sections is
369     // parseable by nlohmann::json, which will throw an
370     // exception and fail the test if there is a problem.
371 
372     // The getJSON() response needs to be wrapped in a { } to make
373     // actual valid JSON (PEL::toJSON() usually handles that).
374 
375     auto jsonString = pel.privateHeader().getJSON();
376 
377     // PrivateHeader always prints JSON
378     ASSERT_TRUE(jsonString);
379     *jsonString = '{' + *jsonString + '}';
380     auto json = nlohmann::json::parse(*jsonString);
381 
382     jsonString = pel.userHeader().getJSON();
383 
384     // UserHeader always prints JSON
385     ASSERT_TRUE(jsonString);
386     *jsonString = '{' + *jsonString + '}';
387     json = nlohmann::json::parse(*jsonString);
388 
389     for (const auto& section : pel.optionalSections())
390     {
391         // The optional sections may or may not have implemented getJSON().
392         jsonString = section->getJSON();
393         if (jsonString)
394         {
395             *jsonString = '{' + *jsonString + '}';
396             auto json = nlohmann::json::parse(*jsonString);
397         }
398     }
399 }
400