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