1 #pragma once
2 
3 #include "tinyxml2.h"
4 
5 #include <phosphor-logging/elog-errors.hpp>
6 #include <phosphor-logging/log.hpp>
7 
8 #include <map>
9 #include <sstream>
10 #include <stack>
11 #include <string>
12 #include <variant>
13 #include <vector>
14 
15 std::string mapAttrTypeToRedfish(const std::string_view typeDbus)
16 {
17     std::string ret;
18     if (typeDbus == "xyz.openbmc_project.BIOSConfig.Manager."
19                     "AttributeType.Enumeration")
20     {
21         ret = "Enumeration";
22     }
23     else if (typeDbus == "xyz.openbmc_project.BIOSConfig."
24                          "Manager.AttributeType.String")
25     {
26         ret = "String";
27     }
28     else if (typeDbus == "xyz.openbmc_project.BIOSConfig."
29                          "Manager.AttributeType.Password")
30     {
31         ret = "Password";
32     }
33     else if (typeDbus == "xyz.openbmc_project.BIOSConfig."
34                          "Manager.AttributeType.Integer")
35     {
36         ret = "Integer";
37     }
38     else if (typeDbus == "xyz.openbmc_project.BIOSConfig."
39                          "Manager.AttributeType.Boolean")
40     {
41         ret = "Boolean";
42     }
43     else
44     {
45         ret = "UNKNOWN";
46     }
47 
48     return ret;
49 }
50 
51 namespace bios
52 {
53 /* Can hold one 'option'
54  * For example
55  *  <option text="TIS" value="0x0"/>
56  */
57 using OptionType = std::tuple<std::string, std::variant<int64_t, std::string>>;
58 
59 /* Can hold one 'options'
60  * For example
61  *  <options>
62  *		<option text="TIS" value="0x0"/>
63  *		<option text="PTP FIFO" value="0x1"/>
64  *		<option text="PTP CRB" value="0x2"/>
65  *	</options>
66  */
67 using OptionTypeVector = std::vector<OptionType>;
68 
69 /* Can hold one 'knob'
70  * For example
71  *  <knob  type="scalar" setupType="oneof" name="TpmDeviceInterfaceAttempt"
72  *  varstoreIndex="14" prompt="Attempt PTP TPM Device Interface"
73  *  description="Attempt PTP TPM Device Interface: PTP FIFO, PTP CRB" size="1"
74  *  offset="0x0005" depex="Sif( _LIST_ TpmDevice _EQU_ 0 1 ) _AND_ Sif(
75  *  TpmDeviceInterfacePtpFifoSupported _EQU_ 0 OR
76  *  TpmDeviceInterfacePtpCrbSupported _EQU_ 0 )" default="0x00"
77  *CurrentVal="0x00"> <options> <option text="TIS" value="0x0"/> <option
78  *text="PTP FIFO" value="0x1"/> <option text="PTP CRB" value="0x2"/>
79  *		</options>
80  *	</knob>
81  */
82 using BiosBaseTableTypeEntry =
83     std::tuple<std::string, bool, std::string, std::string, std::string,
84                std::variant<int64_t, std::string>,
85                std::variant<int64_t, std::string>, OptionTypeVector>;
86 
87 /* Can hold one 'biosknobs'
88  * biosknobs has array of 'knob' */
89 using BiosBaseTableType = std::map<std::string, BiosBaseTableTypeEntry>;
90 
91 namespace knob
92 {
93 /* These are the operators we support in a 'depex' expression
94  * Note: We also support '_LIST_', 'Sif', 'Gif', 'Dif', and 'NOT'. But they are
95  * handeled sepeartely. */
96 enum class DepexOperators
97 {
98     unknown = 0,
99     OR,
100     AND,
101     LTE,
102     LT,
103     EQU,
104     NEQ,
105     MODULO
106 };
107 
108 namespace option
109 {
110 /* Can hold one 'option' */
111 struct option
112 {
113     option(std::string text, std::string value) :
114         text(std::move(text)), value(std::move(value))
115     {}
116 
117     std::string text;
118     std::string value;
119 };
120 } // namespace option
121 
122 /* Can hold one 'knob' */
123 struct knob
124 {
125     knob(std::string nameStr, std::string currentValStr, int currentVal,
126          std::string descriptionStr, std::string defaultStr,
127          std::string promptStr, std::string depexStr,
128          std::string& setupTypeStr) :
129         nameStr(std::move(nameStr)),
130         currentValStr(std::move(currentValStr)), currentVal(currentVal),
131         descriptionStr(std::move(descriptionStr)),
132         defaultStr(std::move(defaultStr)), promptStr(std::move(promptStr)),
133         depexStr(std::move(depexStr)), depex(false),
134         readOnly(("ReadOnly" == setupTypeStr) ? true : false)
135     {}
136 
137     bool depex;
138     bool readOnly;
139     int currentVal;
140 
141     std::string nameStr;
142     std::string currentValStr;
143     std::string descriptionStr;
144     std::string defaultStr;
145     std::string promptStr;
146     std::string depexStr;
147 
148     /* Can hold one 'options' */
149     std::vector<option::option> options;
150 };
151 } // namespace knob
152 
153 /* Class capable of computing 'depex' expression. */
154 class Depex
155 {
156   public:
157     Depex(std::vector<knob::knob>& knobs) : mKnobs(knobs)
158     {}
159 
160     /* Compute 'depex' expression of all knobs in 'biosknobs'. */
161     void compute()
162     {
163         mError.clear();
164 
165         for (auto& knob : mKnobs)
166         {
167             /* if 'depex' == "TRUE" no need to execute expression. */
168             if ("TRUE" == knob.depexStr)
169             {
170                 knob.depex = true;
171             }
172             else if (!knob.readOnly)
173             {
174                 int value = 0;
175 
176                 if (!evaluateExpression(knob.depexStr, value))
177                 {
178                     mError.emplace_back("bad depex: " + knob.depexStr +
179                                         " in knob: " + knob.nameStr);
180                 }
181                 else
182                 {
183                     if (value)
184                     {
185                         knob.depex = true;
186                     }
187                 }
188             }
189         }
190     }
191 
192     /* Returns the number of 'knob's which have a bad 'depex' expression. */
193     size_t getErrorCount()
194     {
195         return mError.size();
196     }
197 
198     /* Prints all the 'knob's which have a bad 'depex' expression. */
199     void printError()
200     {
201         for (auto& error : mError)
202         {
203             phosphor::logging::log<phosphor::logging::level::ERR>(
204                 error.c_str());
205         }
206     }
207 
208   private:
209     /* Returns 'true' if the argument string is a number. */
210     bool isNumber(const std::string& s)
211     {
212         return !s.empty() &&
213                std::find_if(s.begin(), s.end(), [](unsigned char c) {
214                    return !std::isdigit(c);
215                }) == s.end();
216     }
217 
218     /* Returns 'true' if the argument string is hex representation of a number.
219      */
220     bool isHexNotation(std::string const& s)
221     {
222         return s.compare(0, 2, "0x") == 0 && s.size() > 2 &&
223                s.find_first_not_of("0123456789abcdefABCDEF", 2) ==
224                    std::string::npos;
225     }
226 
227     /* Function to find current value of a 'knob'
228      * search is done using 'knob' attribute 'name' */
229     bool getValue(std::string& variableName, int& value)
230     {
231         for (auto& knob : mKnobs)
232         {
233             if (knob.nameStr == variableName)
234             {
235                 value = knob.currentVal;
236                 return true;
237             }
238         }
239 
240         std::string error =
241             "Unable to find knob: " + variableName + " in knob list\n";
242         phosphor::logging::log<phosphor::logging::level::ERR>(error.c_str());
243 
244         return false;
245     }
246 
247     /* Get the expression enclosed within brackets, i.e., between '(' and ')' */
248     bool getSubExpression(const std::string& expression,
249                           std::string& subExpression, size_t& i)
250     {
251         int level = 1;
252         subExpression.clear();
253 
254         for (; i < expression.length(); i++)
255         {
256             if (expression[i] == '(')
257             {
258                 ++level;
259             }
260             else if (expression[i] == ')')
261             {
262                 --level;
263                 if (level == 0)
264                 {
265                     break;
266                 }
267             }
268 
269             subExpression.push_back(expression[i]);
270         }
271 
272         if (!subExpression.empty())
273         {
274             return true;
275         }
276 
277         return false;
278     }
279 
280     /* Function to handle operator '_LIST_'
281      * Convert a '_LIST_' expression to a normal expression
282      * Example "_LIST_ VariableA _EQU_ 0 1" is converted to "VariableA _EQU_ 0
283      * OR VariableA _EQU_ 1" */
284     bool getListExpression(const std::string& expression,
285                            std::string& subExpression, size_t& i)
286     {
287         subExpression.clear();
288 
289         int cnt = 0;
290         std::string variableStr;
291         std::string operatorStr;
292 
293         for (; i < expression.length(); i++)
294         {
295             if (expression[i] == '(')
296             {
297                 return false;
298             }
299             else if (expression[i] == ')')
300             {
301                 break;
302             }
303             else if (expression[i] == ' ')
304             {
305                 /* whitespace */
306                 continue;
307             }
308             else
309             {
310                 std::string word;
311 
312                 /* Get the next word in expression string */
313                 while ((i < expression.length()) && (expression[i] != ' '))
314                 {
315                     word.push_back(expression[i++]);
316                 }
317 
318                 if (word == "_OR_" || word == "OR" || word == "_AND_" ||
319                     word == "AND" || word == "NOT")
320                 {
321                     i = i - word.length();
322                     break;
323                 }
324 
325                 ++cnt;
326 
327                 if (cnt == 1)
328                 {
329                     variableStr = word;
330                 }
331                 else if (cnt == 2)
332                 {
333                     operatorStr = word;
334                 }
335                 else
336                 {
337                     if (cnt > 3)
338                     {
339                         subExpression += " OR ";
340                     }
341 
342                     subExpression += "( ";
343                     subExpression += variableStr;
344                     subExpression += " ";
345                     subExpression += operatorStr;
346                     subExpression += " ";
347                     subExpression += word;
348                     subExpression += " )";
349                 }
350             }
351         }
352 
353         if (!subExpression.empty())
354         {
355             return true;
356         }
357 
358         return false;
359     }
360 
361     /* Function to handle operator 'NOT'
362      * 1) Find the variable
363      * 2) apply NOT on the variable */
364     bool getNotValue(const std::string& expression, size_t& i, int& value)
365     {
366         std::string word;
367 
368         for (; i < expression.length(); i++)
369         {
370             if (expression[i] == ' ')
371             {
372                 /* whitespace */
373                 continue;
374             }
375             else
376             {
377                 /* Get the next word in expression string */
378                 while ((i < expression.length()) && (expression[i] != ' '))
379                 {
380                     word.push_back(expression[i++]);
381                 }
382 
383                 break;
384             }
385         }
386 
387         if (!word.empty())
388         {
389             if (getValue(word, value))
390             {
391                 value = !value;
392                 return true;
393             }
394         }
395 
396         return false;
397     }
398 
399     /* 1) Pop one operator from operator stack, example 'OR'
400      * 2) Pop two variable from variable stack, example VarA and VarB
401      * 3) Push back result of 'VarA OR VarB' to variable stack
402      * 4) Repeat till operator stack is empty
403      *
404      * The last variable in variable stack is the output of the expression. */
405     bool evaluateExprStack(std::stack<int>& values,
406                            std::stack<knob::DepexOperators>& operators,
407                            int& output)
408     {
409         if (values.size() != (operators.size() + 1))
410         {
411             return false;
412         }
413 
414         while (!operators.empty())
415         {
416             int b = values.top();
417             values.pop();
418 
419             int a = values.top();
420             values.pop();
421 
422             switch (operators.top())
423             {
424                 case knob::DepexOperators::OR:
425                     values.emplace(a | b);
426                     break;
427 
428                 case knob::DepexOperators::AND:
429                     values.emplace(a & b);
430                     break;
431 
432                 case knob::DepexOperators::EQU:
433                     if (a == b)
434                     {
435                         values.emplace(1);
436                         break;
437                     }
438 
439                     values.emplace(0);
440                     break;
441 
442                 case knob::DepexOperators::NEQ:
443                     if (a != b)
444                     {
445                         values.emplace(1);
446                         break;
447                     }
448 
449                     values.emplace(0);
450                     break;
451 
452                 case knob::DepexOperators::LTE:
453                     if (a <= b)
454                     {
455                         values.emplace(1);
456                         break;
457                     }
458 
459                     values.emplace(0);
460                     break;
461 
462                 case knob::DepexOperators::LT:
463                     if (a < b)
464                     {
465                         values.emplace(1);
466                         break;
467                     }
468 
469                     values.emplace(0);
470                     break;
471 
472                 case knob::DepexOperators::MODULO:
473                     if (b == 0)
474                     {
475                         return false;
476                     }
477                     values.emplace(a % b);
478                     break;
479 
480                 default:
481                     return false;
482             }
483 
484             operators.pop();
485         }
486 
487         if (values.size() == 1)
488         {
489             output = values.top();
490             values.pop();
491 
492             return true;
493         }
494 
495         return false;
496     }
497 
498     /* Evaluvate one 'depex' expression
499      * 1) Find a word in expression string
500      * 2) If word is a variable push to variable stack
501      * 3) If word is a operator push to operator stack
502      *
503      * Execute the stack at end to get the result of expression. */
504     bool evaluateExpression(const std::string& expression, int& output)
505     {
506         if (expression.empty())
507         {
508             return false;
509         }
510 
511         size_t i;
512         int value;
513         std::stack<int> values;
514         std::stack<knob::DepexOperators> operators;
515         std::string subExpression;
516 
517         for (i = 0; i < expression.length(); i++)
518         {
519             if (expression[i] == ' ')
520             {
521                 /* whitespace */
522                 continue;
523             }
524             else
525             {
526                 std::string word;
527 
528                 /* Get the next word in expression string */
529                 while ((i < expression.length()) && (expression[i] != ' '))
530                 {
531                     word.push_back(expression[i++]);
532                 }
533 
534                 if (word == "_OR_" || word == "OR")
535                 {
536                     /* OR and AND has more precedence than other operators
537                      * To handle statements like "a != b or c != d"
538                      * we need to execute, for above example, both '!=' before
539                      * 'or' */
540                     if (!operators.empty())
541                     {
542                         if (!evaluateExprStack(values, operators, value))
543                         {
544                             return false;
545                         }
546 
547                         values.emplace(value);
548                     }
549 
550                     operators.emplace(knob::DepexOperators::OR);
551                 }
552                 else if (word == "_AND_" || word == "AND")
553                 {
554                     /* OR and AND has more precedence than other operators
555                      * To handle statements like "a == b and c == d"
556                      * we need to execute, for above example, both '==' before
557                      * 'and' */
558                     if (!operators.empty())
559                     {
560                         if (!evaluateExprStack(values, operators, value))
561                         {
562                             return false;
563                         }
564 
565                         values.emplace(value);
566                     }
567 
568                     operators.emplace(knob::DepexOperators::AND);
569                 }
570                 else if (word == "_LTE_")
571                 {
572                     operators.emplace(knob::DepexOperators::LTE);
573                 }
574                 else if (word == "_LT_")
575                 {
576                     operators.emplace(knob::DepexOperators::LT);
577                 }
578                 else if (word == "_NEQ_")
579                 {
580                     operators.emplace(knob::DepexOperators::NEQ);
581                 }
582                 else if (word == "_EQU_")
583                 {
584                     operators.emplace(knob::DepexOperators::EQU);
585                 }
586                 else if (word == "%")
587                 {
588                     operators.emplace(knob::DepexOperators::MODULO);
589                 }
590                 else
591                 {
592                     /* Handle 'Sif(', 'Gif(', 'Dif(' and '('
593                      * by taking the inner/sub expression and evaluating it */
594                     if (word.back() == '(')
595                     {
596                         if (!getSubExpression(expression, subExpression, i))
597                             break;
598 
599                         if (!evaluateExpression(subExpression, value))
600                             break;
601                     }
602                     else if (word == "_LIST_")
603                     {
604                         if (!getListExpression(expression, subExpression, i))
605                             break;
606 
607                         --i;
608 
609                         if (!evaluateExpression(subExpression, value))
610                             break;
611                     }
612                     else if (word == "NOT")
613                     {
614                         if (!getNotValue(expression, i, value))
615                             break;
616                     }
617                     else if (isNumber(word) || isHexNotation(word))
618                     {
619                         try
620                         {
621                             value = std::stoi(word);
622                         }
623                         catch (std::exception& ex)
624                         {
625                             phosphor::logging::log<
626                                 phosphor::logging::level::ERR>(ex.what());
627                             return false;
628                         }
629                     }
630                     else
631                     {
632                         if (!getValue(word, value))
633                             break;
634                     }
635 
636                     values.emplace(value);
637                 }
638             }
639         }
640 
641         if (i == expression.length())
642         {
643             if (evaluateExprStack(values, operators, output))
644             {
645                 return true;
646             }
647         }
648 
649         return false;
650     }
651 
652   private:
653     /* To store all 'knob's in 'biosknobs' */
654     std::vector<knob::knob>& mKnobs;
655 
656     /* To store all bad 'depex' expression */
657     std::vector<std::string> mError;
658 };
659 
660 class Xml
661 {
662   public:
663     Xml(const char* filePath) : mDepex(std::make_unique<Depex>(mKnobs))
664     {
665         if (!getKnobs(filePath))
666         {
667             std::string error =
668                 "Unable to get knobs in file: " + std::string(filePath);
669             throw std::runtime_error(error);
670         }
671     }
672 
673     /* Fill Bios table with all 'knob's which have output of 'depex' expression
674      * as 'true' */
675     bool getBaseTable(bios::BiosBaseTableType& baseTable)
676     {
677         baseTable.clear();
678 
679         for (auto& knob : mKnobs)
680         {
681             if (knob.depex)
682             {
683                 std::string text =
684                     "xyz.openbmc_project.BIOSConfig.Manager.BoundType.OneOf";
685                 bios::OptionTypeVector options;
686 
687                 for (auto& option : knob.options)
688                 {
689                     options.emplace_back(text, option.value);
690                 }
691 
692                 bios::BiosBaseTableTypeEntry baseTableEntry = std::make_tuple(
693                     "xyz.openbmc_project.BIOSConfig.Manager.AttributeType."
694                     "String",
695                     false, knob.nameStr, knob.descriptionStr, "./",
696                     knob.currentValStr, knob.defaultStr, options);
697 
698                 baseTable.emplace(knob.nameStr, baseTableEntry);
699             }
700         }
701 
702         if (!baseTable.empty())
703         {
704             return true;
705         }
706 
707         return false;
708     }
709 
710     /* Execute all 'depex' expression */
711     bool doDepexCompute()
712     {
713         mDepex->compute();
714 
715         if (mDepex->getErrorCount())
716         {
717             mDepex->printError();
718             return false;
719         }
720 
721         return true;
722     }
723 
724   private:
725     /* Get 'option' */
726     void getOption(tinyxml2::XMLElement* pOption)
727     {
728         if (pOption)
729         {
730             std::string valueStr;
731             std::string textStr;
732 
733             if (pOption->Attribute("text"))
734                 valueStr = pOption->Attribute("text");
735 
736             if (pOption->Attribute("value"))
737                 textStr = pOption->Attribute("value");
738 
739             mKnobs.back().options.emplace_back(pOption->Attribute("text"),
740                                                pOption->Attribute("value"));
741         }
742     }
743 
744     /* Get 'options' */
745     void getOptions(tinyxml2::XMLElement* pKnob)
746     {
747         uint16_t reserveCnt = 0;
748 
749         /* Get node options inside knob */
750         tinyxml2::XMLElement* pOptions = pKnob->FirstChildElement("options");
751 
752         if (pOptions)
753         {
754             for (tinyxml2::XMLElement* pOption =
755                      pOptions->FirstChildElement("option");
756                  pOption; pOption = pOption->NextSiblingElement("option"))
757             {
758                 ++reserveCnt;
759             }
760 
761             mKnobs.back().options.reserve(reserveCnt);
762 
763             /* Loop through all option inside options */
764             for (tinyxml2::XMLElement* pOption =
765                      pOptions->FirstChildElement("option");
766                  pOption; pOption = pOption->NextSiblingElement("option"))
767             {
768                 getOption(pOption);
769             }
770         }
771     }
772 
773     /* Get 'knob' */
774     void getKnob(tinyxml2::XMLElement* pKnob)
775     {
776         if (pKnob)
777         {
778             int currentVal = 0;
779             std::string nameStr;
780             std::string currentValStr;
781             std::string descriptionStr;
782             std::string defaultStr;
783             std::string depexStr;
784             std::string promptStr;
785             std::string setupTypeStr;
786 
787             if (!pKnob->Attribute("name") || !pKnob->Attribute("CurrentVal"))
788             {
789                 return;
790             }
791 
792             nameStr = pKnob->Attribute("name");
793             currentValStr = pKnob->Attribute("CurrentVal");
794 
795             try
796             {
797                 currentVal = std::stoi(currentValStr);
798             }
799             catch (std::exception& ex)
800             {
801                 phosphor::logging::log<phosphor::logging::level::ERR>(
802                     ex.what());
803                 return;
804             }
805 
806             if (pKnob->Attribute("description"))
807                 descriptionStr = pKnob->Attribute("description");
808 
809             if (pKnob->Attribute("default"))
810                 defaultStr = pKnob->Attribute("default");
811 
812             if (pKnob->Attribute("depex"))
813                 depexStr = pKnob->Attribute("depex");
814 
815             if (pKnob->Attribute("prompt"))
816                 promptStr = pKnob->Attribute("prompt");
817 
818             if (pKnob->Attribute("setupType"))
819                 setupTypeStr = pKnob->Attribute("setupType");
820 
821             mKnobs.emplace_back(nameStr, currentValStr, currentVal,
822                                 descriptionStr, defaultStr, promptStr, depexStr,
823                                 setupTypeStr);
824 
825             getOptions(pKnob);
826         }
827     }
828 
829     /* Get 'biosknobs' */
830     bool getKnobs(const char* biosXmlFilePath)
831     {
832         uint16_t reserveCnt = 0;
833 
834         mKnobs.clear();
835 
836         tinyxml2::XMLDocument biosXml;
837 
838         /* Load the XML file into the Doc instance */
839         biosXml.LoadFile(biosXmlFilePath);
840 
841         /* Get 'SYSTEM' */
842         tinyxml2::XMLElement* pRootElement = biosXml.RootElement();
843         if (pRootElement)
844         {
845             /* Get 'biosknobs' inside 'SYSTEM' */
846             tinyxml2::XMLElement* pBiosknobs =
847                 pRootElement->FirstChildElement("biosknobs");
848             if (pBiosknobs)
849             {
850                 for (tinyxml2::XMLElement* pKnob =
851                          pBiosknobs->FirstChildElement("knob");
852                      pKnob; pKnob = pKnob->NextSiblingElement("knob"))
853                 {
854                     ++reserveCnt;
855                 }
856 
857                 /* reserve before emplace_back will avoids realloc(s) */
858                 mKnobs.reserve(reserveCnt);
859 
860                 for (tinyxml2::XMLElement* pKnob =
861                          pBiosknobs->FirstChildElement("knob");
862                      pKnob; pKnob = pKnob->NextSiblingElement("knob"))
863                 {
864                     getKnob(pKnob);
865                 }
866             }
867         }
868 
869         if (!mKnobs.empty())
870         {
871             return true;
872         }
873 
874         return false;
875     }
876 
877   private:
878     /* To store all 'knob's in 'biosknobs' */
879     std::vector<knob::knob> mKnobs;
880 
881     /* Object of Depex class to compute 'depex' expression */
882     std::unique_ptr<Depex> mDepex;
883 };
884 } // namespace bios
885