├── .coveragerc
├── .flake8
├── .gitignore
├── ConfirmVersionAndTag.py
├── LICENSE
├── MANIFEST.in
├── MuPythonLibrary
├── ACPI
│ └── DMARParser.py
├── MuAnsiHandler.py
├── MuAnsiHandler_test.py
├── MuFileHandler.py
├── MuJunitReport.py
├── MuMarkdownHandler.py
├── MuStringHandler.py
├── TPM
│ ├── Tpm2Defs.py
│ ├── Tpm2Defs_Test.py
│ ├── Tpm2PolicyCalc.py
│ ├── Tpm2PolicyCalc_Test.py
│ ├── Tpm2Simulator.py
│ ├── Tpm2Stream.py
│ └── Tpm2Stream_Test.py
├── Uefi
│ ├── BmpObject.py
│ ├── BmpObject_test.py
│ ├── Capsule
│ │ ├── CatGenerator.py
│ │ ├── CatGenerator_test.py
│ │ ├── InfGenerator.py
│ │ ├── InfGenerator_test.py
│ │ └── __init__.py
│ ├── EdkII
│ │ ├── Parsers
│ │ │ ├── BaseParser.py
│ │ │ ├── BuildReportParser.py
│ │ │ ├── DecParser.py
│ │ │ ├── DscParser.py
│ │ │ ├── FdfParser.py
│ │ │ ├── InfParser.py
│ │ │ ├── OverrideParser.py
│ │ │ ├── OverrideParser_Test.py
│ │ │ ├── TargetTxtParser.py
│ │ │ └── __init__.py
│ │ ├── PathUtilities.py
│ │ ├── PiFirmwareFile.py
│ │ ├── PiFirmwareVolume.py
│ │ ├── VariableFormat.py
│ │ ├── VariableFormat_Test.py
│ │ └── __init__.py
│ ├── FtwWorkingBlockFormat.py
│ ├── UefiAuthenticatedVariablesStructureSupport.py
│ ├── UefiMultiPhase.py
│ ├── UefiStatusCode.py
│ ├── VariableStoreManipulation.py
│ ├── WinCert.py
│ └── __init__.py
├── UtilityFunctions.py
├── Windows
│ ├── VsWhereUtilities.py
│ ├── VsWhereUtilities_test.py
│ └── __init__.py
├── __init__.py
├── bin
│ ├── __init__.py
│ └── vswhere.md
├── feature_GetHostInfo.md
└── feature_MuAnsiHandler.md
├── README.rst
├── RepoDetails.md
├── azure-pipelines-pr-gate.yml
├── azure-pipelines-release.yml
├── developing.md
├── publishing.md
├── requirements.publisher.txt
├── requirements.txt
├── setup.py
└── using.md
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit = *_tests.py
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | #E266 too many leading '#' for block comment
3 | #E722 do not use bare 'except'
4 | ignore = E266,E722
5 | max_line_length = 120
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.exe
2 | *.pyc
3 | Lib
4 | dist
5 | *.egg-info
6 | build.
7 | /cov_html
8 | /.pytest_cache
9 | /pytest_MuPythonLibrary_report.html
10 | /.coverage
11 | /cov.xml
12 | /test.junit.xml
13 | flake8.err.log
14 | /.eggs
15 |
--------------------------------------------------------------------------------
/ConfirmVersionAndTag.py:
--------------------------------------------------------------------------------
1 | '''
2 | Quick script to check that the wheel/package created is aligned on a git tag.
3 | Official releases should not be made from non-tagged code.
4 | '''
5 |
6 | import glob
7 | import os
8 | import sys
9 |
10 | p = os.path.join(os.getcwd(), "dist")
11 | whlfile = glob.glob(os.path.join(p, "*.whl"))
12 | if(len(whlfile) != 1):
13 | for filename in whlfile:
14 | print(filename)
15 | raise Exception("Too many wheel files")
16 | v = whlfile[0].split("-")[1]
17 | if v.count(".") > 2:
18 | raise Exception("Version %s not in format major.minor.patch" % v)
19 | print("version: " + str(v))
20 | sys.exit(0)
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018, Microsoft Corporation
2 |
3 | All rights reserved. Redistribution and use in source and binary forms, with or
4 | without modification, are permitted provided that the following conditions are
5 | met:
6 | 1. Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 | 2. Redistributions in binary form must reproduce the above copyright notice,
9 | this list of conditions and the following disclaimer in the documentation
10 | and/or other materials provided with the distribution.
11 |
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
15 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
16 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
17 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
18 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
19 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
20 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
21 | OF THE POSSIBILITY OF SUCH DAMAGE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | exclude *.yml
2 | exclude *.md
3 | exclude *.txt
4 | exclude .flake8
5 | exclude .coveragerc
6 | exclude .gitignore
7 | include MuPythonLibrary/bin/*.exe
--------------------------------------------------------------------------------
/MuPythonLibrary/MuAnsiHandler_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import logging
3 | from MuPythonLibrary.MuAnsiHandler import ColoredFormatter
4 | from MuPythonLibrary.MuAnsiHandler import ColoredStreamHandler
5 |
6 | try:
7 | from StringIO import StringIO
8 | except ImportError:
9 | from io import StringIO
10 |
11 |
12 | class MuAnsiHandlerTest(unittest.TestCase):
13 |
14 | # we are mainly looking for exception to be thrown
15 |
16 | record = logging.makeLogRecord({"name": "", "level": logging.CRITICAL, "levelno": logging.CRITICAL,
17 | "levelname": "CRITICAL", "path": "test_path", "lineno": 0,
18 | "msg": "Test message"})
19 | record2 = logging.makeLogRecord({"name": "", "level": logging.INFO, "levelno": logging.INFO,
20 | "levelname": "INFO", "path": "test_path", "lineno": 0,
21 | "msg": "Test message"})
22 |
23 | def test_colored_formatter_init(self):
24 | formatter = ColoredFormatter("%(levelname)s - %(message)s")
25 | # if we didn't throw an exception, then we are good
26 | self.assertNotEqual(formatter, None)
27 |
28 | def test_colored_formatter_to_output_ansi(self):
29 | formatter = ColoredFormatter("%(levelname)s - %(message)s")
30 |
31 | output = formatter.format(MuAnsiHandlerTest.record)
32 | self.assertNotEqual(output, None)
33 | CSI = '\033['
34 | self.assertGreater(len(output), 0, "We should have some output")
35 | self.assertFalse((CSI not in output), "There was supposed to be a ANSI control code in that %s" % output)
36 |
37 | def test_color_handler_to_strip_ansi(self):
38 | stream = StringIO()
39 | # make sure we set out handler to strip the control sequence
40 | handler = ColoredStreamHandler(stream, strip=True, convert=False)
41 | formatter = ColoredFormatter("%(levelname)s - %(message)s")
42 | handler.formatter = formatter
43 | handler.level = logging.NOTSET
44 |
45 | handler.emit(MuAnsiHandlerTest.record)
46 | handler.flush()
47 |
48 | CSI = '\033['
49 |
50 | # check for ANSI escape code in stream
51 | stream.seek(0)
52 | lines = stream.readlines()
53 | self.assertGreater(len(lines), 0, "We should have some output %s" % lines)
54 | for line in lines:
55 | if CSI in line:
56 | self.fail("A control sequence was not stripped! %s" % lines)
57 |
58 | def test_color_handler_not_strip_ansi(self):
59 | stream = StringIO()
60 | formatter = ColoredFormatter("%(levelname)s - %(message)s")
61 | handler = ColoredStreamHandler(stream, strip=False, convert=False)
62 | handler.formatter = formatter
63 | handler.level = logging.NOTSET
64 |
65 | handler.emit(MuAnsiHandlerTest.record2)
66 | handler.flush()
67 |
68 | CSI = '\033['
69 |
70 | found_csi = False
71 | stream.seek(0)
72 | lines = stream.readlines()
73 | self.assertGreater(len(lines), 0, "We should have some output %s" % lines)
74 | for line in lines:
75 | if CSI in line:
76 | found_csi = True
77 | self.assertTrue(found_csi, "We are supposed to to have found an ANSI control character %s" % lines)
78 |
--------------------------------------------------------------------------------
/MuPythonLibrary/MuFileHandler.py:
--------------------------------------------------------------------------------
1 | # @file MuAnsiHandler.py
2 | # Handle basic logging outputting to files
3 | ##
4 | # Copyright (c) 2018, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS
22 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCEOR OTHERWISE)
24 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 | # POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 | import logging
28 |
29 |
30 | class FileHandler(logging.FileHandler):
31 | def __init__(self, filename, mode='w+'):
32 | logging.FileHandler.__init__(self, filename, mode=mode)
33 |
34 | def handle(self, record):
35 | """
36 | Conditionally emit the specified logging record.
37 | Emission depends on filters which may have been added to the handler.
38 | Wrap the actual emission of the record with acquisition/release of
39 | the I/O thread lock. Returns whether the filter passed the record for
40 | emission.
41 | """
42 |
43 | rv = self.filter(record)
44 | if rv and record.levelno >= self.level:
45 | self.acquire()
46 | try:
47 | self.emit(record)
48 | finally:
49 | self.release()
50 | return rv
51 |
--------------------------------------------------------------------------------
/MuPythonLibrary/MuJunitReport.py:
--------------------------------------------------------------------------------
1 | # @file MuJunitReport.py
2 | # This module contains support for Outputting Junit xml.
3 | #
4 | # Used to support CI/CD and exporting test results for other tools.
5 | # This does test report generation without being a test runner.
6 | ##
7 | # Copyright (c) 2018, Microsoft Corporation
8 | #
9 | # All rights reserved.
10 | # Redistribution and use in source and binary forms, with or without
11 | # modification, are permitted provided that the following conditions are met:
12 | # 1. Redistributions of source code must retain the above copyright notice,
13 | # this list of conditions and the following disclaimer.
14 | # 2. Redistributions in binary form must reproduce the above copyright notice,
15 | # this list of conditions and the following disclaimer in the documentation
16 | # and/or other materials provided with the distribution.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
26 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | ##
29 | import time
30 |
31 |
32 | class MuError(object):
33 | def __init__(self, type, msg):
34 | self.Message = msg
35 | self.Type = type
36 |
37 |
38 | class MuFailure(object):
39 | def __init__(self, type, msg):
40 | self.Message = msg
41 | self.Type = type
42 |
43 | ##
44 | # Test Case class
45 | #
46 | ##
47 |
48 |
49 | class MuTestCase(object):
50 | NEW = 1
51 | SKIPPED = 2
52 | FAILED = 3
53 | ERROR = 4
54 | SUCCESS = 5
55 |
56 | def __init__(self, Name, ClassName):
57 | self.Name = Name
58 | self.ClassName = ClassName
59 | self.Time = 0
60 | self.Status = MuTestCase.NEW
61 |
62 | self.FailureMsg = None
63 | self.ErrorMsg = None
64 | self._TestSuite = None
65 | self.StdErr = ""
66 | self.StdOut = ""
67 | self._StartTime = time.time()
68 |
69 | def SetFailed(self, Msg, Type):
70 | if(self.Status != MuTestCase.NEW):
71 | raise Exception("Can't Set to failed. State must be in NEW")
72 | self.Time = time.time() - self._StartTime
73 | self.Status = MuTestCase.FAILED
74 | self.FailureMsg = MuFailure(Type, Msg)
75 |
76 | def SetError(self, Msg, Type):
77 | if(self.Status != MuTestCase.NEW):
78 | raise Exception("Can't Set to error. State must be in NEW")
79 | self.Time = time.time() - self._StartTime
80 | self.Status = MuTestCase.ERROR
81 | self.ErrorMsg = MuError(Type, Msg)
82 |
83 | def SetSuccess(self):
84 | if(self.Status != MuTestCase.NEW):
85 | raise Exception("Can't Set to success. State must be in NEW")
86 | self.Status = MuTestCase.SUCCESS
87 | self.Time = time.time() - self._StartTime
88 |
89 | def SetSkipped(self):
90 | if(self.Status != MuTestCase.NEW):
91 | raise Exception("Can't Set to skipped. State must be in NEW")
92 | self.Status = MuTestCase.SKIPPED
93 | self.Time = time.time() - self._StartTime
94 |
95 | def LogStdOut(self, msg):
96 | self.StdOut += msg.strip() + "\n "
97 |
98 | def LogStdError(self, msg):
99 | self.StdErr += msg.strip() + "\n "
100 |
101 | def Output(self, outstream):
102 | outstream.write(''.format(self.ClassName, self.Name, self.Time))
103 | if self.Status == MuTestCase.SKIPPED:
104 | outstream.write('')
105 | elif self.Status == MuTestCase.FAILED:
106 | outstream.write(''.format(self.FailureMsg.Message,
107 | self.FailureMsg.Type))
108 | elif self.Status == MuTestCase.ERROR:
109 | outstream.write(''.format(self.ErrorMsg.Message, self.ErrorMsg.Type))
110 | elif self.Status != MuTestCase.SUCCESS:
111 | raise Exception("Can't output a testcase {0}.{1} in invalid state {2}".format(self.ClassName,
112 | self.Name, self.Status))
113 |
114 | outstream.write('' + self.StdOut + '')
115 | outstream.write('' + self.StdErr + '')
116 | outstream.write('')
117 |
118 |
119 | ##
120 | # Test Suite class. Create new suites by using the MuTestReport Object
121 | #
122 | #
123 | ##
124 | class MuTestSuite(object):
125 | def __init__(self, Name, Package, Id):
126 | self.Name = Name
127 | self.Package = Package
128 | self.TestId = Id
129 | self.TestCases = []
130 |
131 | def create_new_testcase(self, name, classname):
132 | tc = MuTestCase(name, classname)
133 | self.TestCases.append(tc)
134 | tc._TestSuite = self
135 | return tc
136 |
137 | def Output(self, outstream):
138 | Errors = 0
139 | Failures = 0
140 | Skipped = 0
141 | Tests = len(self.TestCases)
142 |
143 | for a in self.TestCases:
144 | if(a.Status == MuTestCase.FAILED):
145 | Failures += 1
146 | elif(a.Status == MuTestCase.ERROR):
147 | Errors += 1
148 | elif(a.Status == MuTestCase.SKIPPED):
149 | Skipped += 1
150 |
151 | outstream.write(''.format(self.TestId, self.Name, self.Package,
153 | Errors, Tests, Failures, Skipped))
154 |
155 | for a in self.TestCases:
156 | a.Output(outstream)
157 |
158 | outstream.write('')
159 |
160 | ##
161 | # Test Report. Top level object test repoting.
162 | #
163 | #
164 | ##
165 |
166 |
167 | class MuJunitReport(object):
168 | def __init__(self):
169 | self.TestSuites = []
170 |
171 | def create_new_testsuite(self, name, package):
172 | id = len(self.TestSuites)
173 | ts = MuTestSuite(name, package, id)
174 | self.TestSuites.append(ts)
175 | return ts
176 |
177 | def Output(self, filepath):
178 | f = open(filepath, "w")
179 | f.write('')
180 | f.write('')
181 | f.write('')
182 | for a in self.TestSuites:
183 | a.Output(f)
184 | f.write('')
185 | f.close()
186 |
--------------------------------------------------------------------------------
/MuPythonLibrary/MuMarkdownHandler.py:
--------------------------------------------------------------------------------
1 | # @file MuAnsiHandler.py
2 | # Handle basic logging outputting to markdown
3 | ##
4 | # Copyright (c) 2018, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS
22 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCEOR OTHERWISE)
24 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 | # POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 | import logging
28 |
29 |
30 | class MarkdownFileHandler(logging.FileHandler):
31 | def __init__(self, filename, mode='w+'):
32 | logging.FileHandler.__init__(self, filename, mode=mode)
33 | if self.stream.writable:
34 | self.stream.write(" # Build Report\n")
35 | self.stream.write("[Go to table of contents](#table-of-contents)\n")
36 | self.stream.write("=====\n")
37 | self.stream.write(" [Go to Error List](#error-list)\n")
38 | self.stream.write("=====\n")
39 | self.contents = []
40 | self.error_records = []
41 |
42 | def emit(self, record):
43 | if self.stream is None:
44 | self.stream = self._open()
45 | msg = record.message.strip("#- ")
46 |
47 | if len(msg) > 0:
48 | if logging.getLevelName(record.levelno) == "SECTION":
49 | self.contents.append((msg, []))
50 | msg = "## " + msg
51 | elif record.levelno == logging.CRITICAL:
52 | section_index = len(self.contents) - 1
53 | if section_index >= 0:
54 | self.contents[section_index][1].append(msg)
55 | msg = "### " + msg
56 | elif record.levelno == logging.ERROR:
57 | self.error_records.append(record)
58 | msg = "#### ERROR: " + msg
59 | elif record.levelno == logging.WARNING:
60 | msg = " _ WARNING: " + msg + "_"
61 | else:
62 | msg = " " + msg
63 | stream = self.stream
64 | # issue 35046: merged two stream.writes into one.
65 | stream.write(msg + self.terminator)
66 |
67 | # self.flush()
68 |
69 | def handle(self, record):
70 | """
71 | Conditionally emit the specified logging record.
72 | Emission depends on filters which may have been added to the handler.
73 | Wrap the actual emission of the record with acquisition/release of
74 | the I/O thread lock. Returns whether the filter passed the record for
75 | emission.
76 | """
77 |
78 | rv = self.filter(record)
79 | if rv and record.levelno >= self.level:
80 | self.acquire()
81 | try:
82 | self.emit(record)
83 | finally:
84 | self.release()
85 | return rv
86 |
87 | @staticmethod
88 | def __convert_to_markdownlink(text):
89 | # Using info from here https://stackoverflow.com/a/38507669
90 | # get rid of uppercase characters
91 | text = text.lower().strip()
92 | # get rid of punctuation
93 | text = text.replace(".", "").replace(",", "").replace("-", "")
94 | # replace spaces
95 | text = text.replace(" ", "-")
96 | return text
97 |
98 | def _output_error(self, record):
99 | output = " + \"{0}\" from {1}:{2}\n".format(record.msg, record.pathname, record.lineno)
100 | self.stream.write(output)
101 |
102 | def close(self):
103 | self.stream.write("## Table of Contents\n")
104 | for item, subsections in self.contents:
105 | link = MarkdownFileHandler.__convert_to_markdownlink(item)
106 | self.stream.write("+ [{0}](#{1})\n".format(item, link))
107 | for section in subsections:
108 | section_link = MarkdownFileHandler.__convert_to_markdownlink(section)
109 | self.stream.write(" + [{0}](#{1})\n".format(section, section_link))
110 |
111 | self.stream.write("## Error List\n")
112 | if len(self.error_records) == 0:
113 | self.stream.write(" No errors found")
114 | for record in self.error_records:
115 | self._output_error(record)
116 |
117 | self.flush()
118 | self.stream.close()
119 |
--------------------------------------------------------------------------------
/MuPythonLibrary/MuStringHandler.py:
--------------------------------------------------------------------------------
1 | # @file MuStringHandler.py
2 | # Handle basic logging by streaming into stringIO
3 | ##
4 | # Copyright (c) 2018, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,DATA, OR PROFITS; OR BUSINESS
22 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCEOR OTHERWISE)
24 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 | # POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 | import logging
28 | try:
29 | from StringIO import StringIO
30 | except ImportError:
31 | from io import StringIO
32 |
33 |
34 | class StringStreamHandler(logging.StreamHandler):
35 | terminator = '\n'
36 |
37 | def __init__(self):
38 | logging.Handler.__init__(self)
39 | self.stream = StringIO()
40 |
41 | def handle(self, record):
42 | """
43 | Conditionally emit the specified logging record.
44 | Emission depends on filters which may have been added to the handler.
45 | Wrap the actual emission of the record with acquisition/release of
46 | the I/O thread lock. Returns whether the filter passed the record for
47 | emission.
48 | """
49 |
50 | rv = self.filter(record)
51 | if rv and record.levelno >= self.level:
52 | self.acquire()
53 | try:
54 | self.emit(record)
55 | finally:
56 | self.release()
57 | return rv
58 |
59 | def readlines(self, hint=-1):
60 | return self.stream.readlines(hint)
61 |
62 | def seek_start(self):
63 | self.stream.seek(0, 0)
64 |
65 | def seek_end(self):
66 | self.stream.seek(2, 0)
67 |
68 | def seek(self, offset, whence):
69 | return self.stream.seek(offset, whence)
70 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2Defs_Test.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2Defs_Test.py
2 | # This file contains utility classes to help interpret definitions from the
3 | # Tpm20.h header file in TianoCore.
4 | #
5 | ##
6 | # Copyright (c) 2017, Microsoft Corporation
7 | #
8 | # All rights reserved.
9 | # Redistribution and use in source and binary forms, with or without
10 | # modification, are permitted provided that the following conditions are met:
11 | # 1. Redistributions of source code must retain the above copyright notice,
12 | # this list of conditions and the following disclaimer.
13 | # 2. Redistributions in binary form must reproduce the above copyright notice,
14 | # this list of conditions and the following disclaimer in the documentation
15 | # and/or other materials provided with the distribution.
16 | #
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | ##
28 |
29 | import unittest
30 | import MuPythonLibrary.TPM.Tpm2Defs as t2d
31 |
32 |
33 | class TestCommandCode(unittest.TestCase):
34 |
35 | def test_get_code_returns_codes(self):
36 | self.assertEqual(t2d.CommandCode.get_code('TPM_CC_Clear'), 0x00000126)
37 | self.assertEqual(t2d.CommandCode.get_code('TPM_CC_ActivateCredential'), 0x00000147)
38 |
39 | def test_get_code_returns_none_if_not_found(self):
40 | self.assertEqual(t2d.CommandCode.get_code('I_AM_NOT_A_VALID_CODE'), None)
41 | self.assertEqual(t2d.CommandCode.get_code(None), None)
42 |
43 | def test_get_string_returns_strings(self):
44 | self.assertEqual(t2d.CommandCode.get_string(0x00000126), 'TPM_CC_Clear')
45 | self.assertEqual(t2d.CommandCode.get_string(0x00000147), 'TPM_CC_ActivateCredential')
46 |
47 | def test_get_string_returns_none_if_not_found(self):
48 | self.assertEqual(t2d.CommandCode.get_string(0xFFFFFFFF), None)
49 |
50 |
51 | if __name__ == '__main__':
52 | unittest.main()
53 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2PolicyCalc.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2PolicyCalc.py
2 | # This file contains classes used to calculate TPM 2.0 policies
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 |
29 | import Tpm2Defs as t2d
30 | import hashlib
31 | import struct
32 |
33 |
34 | # ========================================================================================
35 | ##
36 | # POLICY TREE CLASSES
37 | # These are used to describe a final policy structure.
38 | # You can construct nodes to form complex policies from the policy primitive classes.
39 | ##
40 | # PolicyTreeOr <--- Tree Node
41 | # / \
42 | # PolicyTreeSolo PolicyTreeAnd <--- Tree Nodes
43 | # / / \
44 | # PolicyCommandCode PolicyLocality PolicyCommandCode <--- Primitives
45 | ##
46 | # ========================================================================================
47 |
48 |
49 | class PolicyHasher(object):
50 | def __init__(self, hash_type):
51 | if hash_type not in ['sha256', 'sha384']:
52 | raise ValueError("Invalid hash type '%s'!" % hash_type)
53 |
54 | self.hash_type = hash_type
55 | self.hash_size = {
56 | 'sha256': 32,
57 | 'sha384': 48
58 | }[hash_type]
59 |
60 | def get_size(self):
61 | return self.hash_size
62 |
63 | def hash(self, data):
64 | hash_obj = None
65 | if self.hash_type == 'sha256':
66 | hash_obj = hashlib.sha256()
67 | else:
68 | hash_obj = hashlib.sha384()
69 |
70 | hash_obj.update(data)
71 |
72 | return hash_obj.digest()
73 |
74 |
75 | class PolicyCalculator(object):
76 | def __init__(self, primitive_dict, policy_tree):
77 | # For now, we'll leave this pretty sparse.
78 | # We should have WAY more testing for this stuff.
79 | self.primitive_dict = primitive_dict
80 | self.policy_tree = policy_tree
81 |
82 | def generate_digest(self, digest_type):
83 | pass
84 |
85 |
86 | class PolicyTreeOr(object):
87 | def __init__(self, components):
88 | # OR connections can only be 8 digests long.
89 | # They CAN, however, be links of ORs.
90 | if len(components) > 8:
91 | raise ValueError("OR junctions cannot contain more than 8 sub-policies!")
92 |
93 | self.components = components
94 |
95 | def get_type(self):
96 | return 'or'
97 |
98 | def validate(self):
99 | result = True
100 |
101 | for component in self.components:
102 | # All components must be convertible into a policy.
103 | if not hasattr(component, 'get_policy'):
104 | result = False
105 |
106 | # All components must also be valid.
107 | if not hasattr(component, 'validate') or not component.validate():
108 | result = False
109 |
110 | return result
111 |
112 | def get_policy_buffer(self, hash_obj):
113 | concat_policy_buffer = b'\x00' * hash_obj.get_size()
114 | concat_policy_buffer += struct.pack(">L", t2d.TPM_CC_PolicyOR)
115 | concat_policy_buffer += b''.join([component.get_policy(hash_obj) for component in self.components])
116 | return concat_policy_buffer
117 |
118 | def get_policy(self, hash_obj):
119 | return hash_obj.hash(self.get_policy_buffer(hash_obj))
120 |
121 |
122 | class PolicyTreeAnd(object):
123 | def __init__(self, components):
124 | # ANDs must only be composed of primitives. For simplicity, I guess.
125 | # Honestly, this has spiralled out of control, but something is better than nothing.
126 | for component in components:
127 | if not hasattr(component, 'get_buffer_for_digest'):
128 | raise ValueError("AND junctions must consist of primitives!")
129 |
130 | self.components = components
131 |
132 | def get_type(self):
133 | return 'and'
134 |
135 | def validate(self):
136 | return True
137 |
138 | def get_policy(self, hash_obj):
139 | current_digest = b'\x00' * hash_obj.get_size()
140 | for component in self.components:
141 | current_digest = hash_obj.hash(current_digest + component.get_buffer_for_digest())
142 | return current_digest
143 |
144 |
145 | class PolicyTreeSolo(object):
146 | """This object should only be used to put a single policy claim under an OR"""
147 |
148 | def __init__(self, policy_obj):
149 | if not hasattr(policy_obj, 'get_buffer_for_digest'):
150 | raise ValueError("Supplied policy object is missing required functionality!")
151 |
152 | self.policy_obj = policy_obj
153 |
154 | def get_type(self):
155 | return 'solo'
156 |
157 | def validate(self):
158 | return True
159 |
160 | def get_policy_buffer(self, hash_obj):
161 | return (b'\x00' * hash_obj.get_size()) + self.policy_obj.get_buffer_for_digest()
162 |
163 | def get_policy(self, hash_obj):
164 | return hash_obj.hash(self.get_policy_buffer(hash_obj))
165 |
166 |
167 | # ========================================================================================
168 | ##
169 | # POLICY PRIMITIVES
170 | # These classes are used to describe a single assertion (eg. PolicyLocality) and
171 | # can be used with the PolicyTree classes to construct complex policies.
172 | ##
173 | # ========================================================================================
174 |
175 |
176 | class PolicyLocality(object):
177 |
178 | def __init__(self, localities):
179 | # Update the bitfield with the requested localities.
180 | if localities is not None:
181 | self.bitfield = self.calc_bitfield_from_list(localities)
182 | else:
183 | self.bitfield = 0b00000000
184 |
185 | def get_bitfield(self):
186 | return self.bitfield
187 |
188 | def calc_bitfield_from_list(self, localities):
189 | bitfield = 0b00000000
190 |
191 | # First, we need to validate all of the localities in the list.
192 | for value in localities:
193 | # If the value is in a bad range, we're done here.
194 | if not (0 <= value < 5) and not (32 <= value < 256):
195 | raise ValueError("Invalid locality '%d'!" % value)
196 | # An "upper" locality must be individual. Cannot combine with 0-4.
197 | if (32 <= value < 256) and len(localities) > 1:
198 | raise ValueError("Cannot combine locality '%d' with others!" % value)
199 |
200 | # If the list is empty... well, we're done.
201 | if len(localities) == 0:
202 | pass
203 |
204 | # Now, if we're an "upper" locality, that's a simple value.
205 | elif len(localities) == 1 and (32 <= localities[0] < 256):
206 | bitfield = localities[0]
207 |
208 | # We have to actually "think" to calculate the "lower" localities.
209 | else:
210 | for value in localities:
211 | bitfield |= 1 << value
212 |
213 | return bitfield
214 |
215 | def get_buffer_for_digest(self):
216 | # NOTE: We force big-endian to match the marshalling in the TPM.
217 | return struct.pack(">LB", t2d.TPM_CC_PolicyLocality, self.bitfield)
218 |
219 |
220 | class PolicyCommandCode(object):
221 |
222 | def __init__(self, command_code_string=None):
223 | # Check to make sure that a command_code can be found.
224 | str_command_code_string = str(command_code_string)
225 | command_code = t2d.CommandCode.get_code(str_command_code_string)
226 | if command_code is None:
227 | raise ValueError("Command code '%s' unknown!" % str_command_code_string)
228 | self.command_code_string = str_command_code_string
229 |
230 | def get_code(self):
231 | return self.command_code_string
232 |
233 | def get_buffer_for_digest(self):
234 | # NOTE: We force big-endian to match the marshalling in the TPM.
235 | return struct.pack(">LL", t2d.CommandCode.get_code('TPM_CC_PolicyCommandCode'),
236 | t2d.CommandCode.get_code(self.command_code_string))
237 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2PolicyCalc_Test.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2PolicyCalc_Test.py
2 | # This file contains classes used to calculate TPM 2.0 policies
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import unittest
29 | import MuPythonLibrary.TPM.Tpm2PolicyCalc as t2pc
30 |
31 |
32 | class TestPolicyLocality(unittest.TestCase):
33 |
34 | def test_create_with_empty_list(self):
35 | policy = t2pc.PolicyLocality(None)
36 | self.assertEqual(policy.get_bitfield(), 0)
37 | policy2 = t2pc.PolicyLocality(())
38 | self.assertEqual(policy2.get_bitfield(), 0)
39 |
40 | def test_create_with_base_localities(self):
41 | policy = t2pc.PolicyLocality([0, 2, 4])
42 | self.assertEqual(policy.get_bitfield(), 0b00010101)
43 | policy2 = t2pc.PolicyLocality([1, 2])
44 | self.assertEqual(policy2.get_bitfield(), 0b00000110)
45 | policy3 = t2pc.PolicyLocality([3])
46 | self.assertEqual(policy3.get_bitfield(), 0b00001000)
47 | policy4 = t2pc.PolicyLocality([57])
48 | self.assertEqual(policy4.get_bitfield(), 57)
49 |
50 | def test_create_with_invalid_localites(self):
51 | with self.assertRaises(ValueError):
52 | t2pc.PolicyLocality([5])
53 | with self.assertRaises(ValueError):
54 | t2pc.PolicyLocality([12])
55 | with self.assertRaises(ValueError):
56 | t2pc.PolicyLocality([31])
57 | with self.assertRaises(ValueError):
58 | t2pc.PolicyLocality([256])
59 |
60 | def test_create_with_mixed_lower_and_upper(self):
61 | with self.assertRaises(ValueError):
62 | t2pc.PolicyLocality([1, 4, 35])
63 | with self.assertRaises(ValueError):
64 | t2pc.PolicyLocality([36, 128])
65 |
66 | def test_get_buffer(self):
67 | self.assertEqual(t2pc.PolicyLocality([0, 2, 4]).get_buffer_for_digest(), bytearray.fromhex("0000016F" + "15"))
68 | self.assertEqual(t2pc.PolicyLocality([34]).get_buffer_for_digest(), bytearray.fromhex("0000016F" + "22"))
69 |
70 |
71 | class TestPolicyCommandCode(unittest.TestCase):
72 |
73 | def test_create_with_no_code(self):
74 | with self.assertRaises(ValueError):
75 | t2pc.PolicyCommandCode(None)
76 |
77 | def test_create_with_invalid_code(self):
78 | with self.assertRaises(ValueError):
79 | t2pc.PolicyCommandCode("MonkeyValue")
80 | with self.assertRaises(ValueError):
81 | t2pc.PolicyCommandCode(12)
82 | with self.assertRaises(ValueError):
83 | t2pc.PolicyCommandCode({})
84 |
85 | def test_create_with_valid_codes(self):
86 | policy = t2pc.PolicyCommandCode('TPM_CC_Clear')
87 | self.assertEqual(policy.get_code(), 'TPM_CC_Clear')
88 | policy = t2pc.PolicyCommandCode('TPM_CC_ClearControl')
89 | self.assertEqual(policy.get_code(), 'TPM_CC_ClearControl')
90 | policy = t2pc.PolicyCommandCode('TPM_CC_Quote')
91 | self.assertEqual(policy.get_code(), 'TPM_CC_Quote')
92 |
93 | def test_get_buffer(self):
94 | self.assertEqual(t2pc.PolicyCommandCode('TPM_CC_Clear').get_buffer_for_digest(),
95 | bytearray.fromhex("0000016C" + "00000126"))
96 | self.assertEqual(t2pc.PolicyCommandCode('TPM_CC_ClearControl').get_buffer_for_digest(),
97 | bytearray.fromhex("0000016C" + "00000127"))
98 |
99 |
100 | class TestPolicyTreeSolo(unittest.TestCase):
101 |
102 | def test_policy_command_code(self):
103 | expected_result_1 = bytearray.fromhex("940CFB4217BB1EDCF7FB41937CA974AA68E698AB78B8124B070113E211FD46FC")
104 | expected_result_2 = bytearray.fromhex("C4DFABCEDA8DE836C95661952892B1DEF7203AFB46FEFEC43FFCFC93BE540730")
105 | expected_result_3 = bytearray.fromhex("1D2DC485E177DDD0A40A344913CEEB420CAA093C42587D2E1B132B157CCB5DB0")
106 |
107 | test1 = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_ClearControl"))
108 | test2 = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_Clear"))
109 | test3 = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_NV_UndefineSpaceSpecial"))
110 |
111 | phash = t2pc.PolicyHasher('sha256')
112 | self.assertEqual(test1.get_policy(phash), expected_result_1)
113 | self.assertEqual(test2.get_policy(phash), expected_result_2)
114 | self.assertEqual(test3.get_policy(phash), expected_result_3)
115 |
116 | def test_policy_locality(self):
117 | expected_result = bytearray.fromhex("07039B45BAF2CC169B0D84AF7C53FD1622B033DF0A5DCDA66360AA99E54947CD")
118 |
119 | test = t2pc.PolicyTreeSolo(t2pc.PolicyLocality([3, 4]))
120 |
121 | phash = t2pc.PolicyHasher('sha256')
122 | self.assertEqual(test.get_policy(phash), expected_result)
123 |
124 |
125 | class TestPolicyTreeAnd(unittest.TestCase):
126 |
127 | def test_single_and_should_match_solo(self):
128 | soloTest = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_Clear"))
129 | andTest = t2pc.PolicyTreeAnd([t2pc.PolicyCommandCode("TPM_CC_Clear")])
130 |
131 | phash = t2pc.PolicyHasher('sha256')
132 | self.assertEqual(soloTest.get_policy(phash), andTest.get_policy(phash))
133 |
134 |
135 | class TestPolicyTreeOr(unittest.TestCase):
136 |
137 | def test_single_and_should_match_solo(self):
138 | expected_result = bytearray.fromhex("3F44FB41486D4A36A8ADCA2203E73A5068BFED5FDCE5092B9A3C6CCE8ABF3B0C")
139 |
140 | test1 = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_ClearControl"))
141 | test2 = t2pc.PolicyTreeSolo(t2pc.PolicyCommandCode("TPM_CC_Clear"))
142 | orTest = t2pc.PolicyTreeOr([test1, test2])
143 |
144 | phash = t2pc.PolicyHasher('sha256')
145 | self.assertEqual(orTest.get_policy(phash), expected_result)
146 |
147 |
148 | class TestPolicyTree(unittest.TestCase):
149 |
150 | def test_complex_policy_1(self):
151 | expected_result = bytearray.fromhex("DFFDB6C8EAFCBE691E358882B18703121EAB40DE2386F7A8E7B4A06591E1F0EE")
152 |
153 | # Computation details:
154 | # A = TPM2_PolicyLocality(3 & 4)
155 | # B = TPM2_PolicyCommandCode(TPM_CC_NV_UndefineSpaceSpecial)
156 | # C = TPM2_PolicyCommandCode(TPM_CC_NV_Write)
157 | # policy = {{A} AND {C}} OR {{A} AND {B}}
158 |
159 | a = t2pc.PolicyLocality([3, 4])
160 | b = t2pc.PolicyCommandCode('TPM_CC_NV_UndefineSpaceSpecial')
161 | c = t2pc.PolicyCommandCode('TPM_CC_NV_Write')
162 |
163 | leg1 = t2pc.PolicyTreeAnd([a, c])
164 | leg2 = t2pc.PolicyTreeAnd([a, b])
165 | final = t2pc.PolicyTreeOr([leg1, leg2])
166 |
167 | phash = t2pc.PolicyHasher('sha256')
168 | self.assertEqual(final.get_policy(phash), expected_result)
169 |
170 |
171 | if __name__ == '__main__':
172 | unittest.main()
173 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2Simulator.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2Simulator.py
2 | # This file contains transportation layer classes for interacting with the TPM 2.0 simulator.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import socket
29 | import struct
30 | import Tpm2Stream as t2s
31 | import Tpm2Defs as t2d
32 |
33 | PLAT_COMMANDS = {
34 | 'TPM_SIGNAL_POWER_ON': 1,
35 | 'TPM_SIGNAL_POWER_OFF': 2,
36 | 'TPM_SIGNAL_PHYS_PRES_ON': 3,
37 | 'TPM_SIGNAL_PHYS_PRES_OFF': 4,
38 | 'TPM_SIGNAL_HASH_START': 5,
39 | 'TPM_SIGNAL_HASH_DATA': 6,
40 | # {UINT32 BufferSize, BYTE[BufferSize] Buffer}
41 | 'TPM_SIGNAL_HASH_END': 7,
42 | 'TPM_SEND_COMMAND': 8,
43 | # {BYTE Locality, UINT32 InBufferSize, BYTE[InBufferSize] InBuffer} ->
44 | # {UINT32 OutBufferSize, BYTE[OutBufferSize] OutBuffer}
45 | 'TPM_SIGNAL_CANCEL_ON': 9,
46 | 'TPM_SIGNAL_CANCEL_OFF': 10,
47 | 'TPM_SIGNAL_NV_ON': 11,
48 | 'TPM_SIGNAL_NV_OFF': 12,
49 | 'TPM_SIGNAL_KEY_CACHE_ON': 13,
50 | 'TPM_SIGNAL_KEY_CACHE_OFF': 14,
51 | 'TPM_REMOTE_HANDSHAKE': 15,
52 | 'TPM_SET_ALTERNATIVE_RESULT': 16,
53 | 'TPM_SIGNAL_RESET': 17,
54 | 'TPM_SESSION_END': 20,
55 | 'TPM_STOP': 21,
56 | 'TPM_GET_COMMAND_RESPONSE_SIZES': 25,
57 | 'TPM_TEST_FAILURE_MODE': 30,
58 | }
59 |
60 |
61 | class TpmSimulator(object):
62 |
63 | def __init__(self, host='localhost', port=2321):
64 | super(TpmSimulator, self).__init__()
65 |
66 | # Connect to the control socket.
67 | self.platSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
68 | self.platSock.connect((host, port + 1))
69 |
70 | # Connect to the simulator socket.
71 | self.tpmSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
72 | self.tpmSock.connect((host, port))
73 |
74 | # Power cycle the TPM.
75 | self.platSock.send(struct.pack(">L", PLAT_COMMANDS['TPM_SIGNAL_POWER_OFF']))
76 | self.platSock.send(struct.pack(">L", PLAT_COMMANDS['TPM_SIGNAL_POWER_ON']))
77 |
78 | # Enable the NV space.
79 | self.platSock.send(struct.pack(">L", PLAT_COMMANDS['TPM_SIGNAL_NV_ON']))
80 |
81 | def send_raw_data(self, data):
82 | print("RAW -->: " + str(data).encode('hex'))
83 | self.tpmSock.send(data)
84 |
85 | def read_raw_data(self, count):
86 | data = self.tpmSock.recv(count)
87 | print("RAW <--: " + str(data).encode('hex'))
88 | return data
89 |
90 | def send_data(self, data):
91 | # Send the "I'm about to send data" command.
92 | self.send_raw_data(struct.pack(">L", PLAT_COMMANDS['TPM_SEND_COMMAND']))
93 | # Send the locality for the data.
94 | self.send_raw_data(struct.pack(">b", 0x03))
95 | # Send the size of the data.
96 | self.send_raw_data(struct.pack(">L", len(data)))
97 |
98 | # Now, send the data itself.
99 | self.send_raw_data(data)
100 |
101 | # Poll until a result is available.
102 | # NOTE: This shouldn't be necessary and denotes a lack of understanding...
103 | while True:
104 | result_size = self.read_raw_data(4)
105 | result_size = struct.unpack(">L", result_size)[0]
106 | if (result_size > 0):
107 | break
108 |
109 | return self.read_raw_data(result_size)
110 |
111 | def startup(self, type):
112 | stream = t2s.Tpm2CommandStream(t2d.TPM_ST_NO_SESSIONS, 0x00, t2d.TPM_CC_Startup)
113 | stream.add_element(t2s.Tpm2StreamPrimitive(t2d.TPM_SU_Size, type))
114 | return self.send_data(stream.get_stream())
115 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2Stream.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2Stream.py
2 | # This file contains utility classes to help marshal and unmarshal data to/from the TPM.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 |
29 | import struct
30 |
31 |
32 | class Tpm2StreamElement(object):
33 | def __init__(self):
34 | self.pack_string = ""
35 |
36 | """This get_size refers to the size of this structure when marshalled"""
37 |
38 | def get_size(self):
39 | return struct.calcsize(self.pack_string)
40 |
41 |
42 | class Tpm2StreamPrimitive(Tpm2StreamElement):
43 | def __init__(self, size, value):
44 | super(Tpm2StreamPrimitive, self).__init__()
45 |
46 | if size not in (1, 2, 4, 8):
47 | raise ValueError("Size must be 1, 2, 4, or 8 bytes!")
48 |
49 | self.pack_string = {
50 | 1: ">B",
51 | 2: ">H",
52 | 4: ">L",
53 | 8: ">Q"
54 | }[size]
55 | self.value = value
56 |
57 | def marshal(self):
58 | return struct.pack(self.pack_string, self.value)
59 |
60 |
61 | class TPM2_COMMAND_HEADER(Tpm2StreamElement):
62 | def __init__(self, tag, size, code):
63 | super(TPM2_COMMAND_HEADER, self).__init__()
64 | self.tag = tag
65 | self.code = code
66 | self.size = size
67 | self.pack_string = ">HLL"
68 |
69 | """This update_size refers to the size of the whole command"""
70 |
71 | def update_size(self, size):
72 | self.size = size
73 |
74 | def marshal(self):
75 | return struct.pack(self.pack_string, self.tag, self.size, self.code)
76 |
77 |
78 | class TPM2B(Tpm2StreamElement):
79 | def __init__(self, data):
80 | super(TPM2B, self).__init__()
81 | self.data = data
82 | self.size = len(data)
83 | self.pack_string = ">H%ds" % self.size
84 |
85 | def update_data(self, data):
86 | self.data = data
87 | self.size = len(data)
88 | self.pack_string = ">H%ds" % self.size
89 |
90 | def marshal(self):
91 | return struct.pack(self.pack_string, self.size, self.data)
92 |
93 |
94 | class Tpm2CommandStream(object):
95 | def __init__(self, tag, size, code):
96 | super(Tpm2CommandStream, self).__init__()
97 | self.header = TPM2_COMMAND_HEADER(tag, size, code)
98 | self.stream_size = self.header.get_size()
99 | self.header.update_size(self.stream_size)
100 | self.stream_elements = []
101 |
102 | def get_size(self):
103 | return self.stream_size
104 |
105 | def add_element(self, element):
106 | self.stream_elements.append(element)
107 | self.stream_size += element.get_size()
108 | self.header.update_size(self.stream_size)
109 |
110 | def get_stream(self):
111 | return self.header.marshal() + b''.join(element.marshal() for element in self.stream_elements)
112 |
--------------------------------------------------------------------------------
/MuPythonLibrary/TPM/Tpm2Stream_Test.py:
--------------------------------------------------------------------------------
1 | # @file Tpm2Stream_Test.py
2 | # This file contains utility classes to help marshal and unmarshal data to/from the TPM.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 |
29 | import unittest
30 | import struct
31 | from MuPythonLibrary.TPM import Tpm2Defs
32 | from MuPythonLibrary.TPM import Tpm2Stream
33 |
34 |
35 | class Tpm2StreamElement(unittest.TestCase):
36 |
37 | def test_object_has_zero_size_by_default(self):
38 | so = Tpm2Stream.Tpm2StreamElement()
39 | self.assertEqual(so.get_size(), 0)
40 |
41 |
42 | class Tpm2CommandHeader(unittest.TestCase):
43 |
44 | def test_ch_marshals_correctly(self):
45 | ch1 = Tpm2Stream.TPM2_COMMAND_HEADER(0x4321, 0x00000000, 0xDEADBEEF)
46 | ch2 = Tpm2Stream.TPM2_COMMAND_HEADER(0x8001, 0x0000000A, Tpm2Defs.TPM_CC_Clear)
47 |
48 | self.assertEqual(ch1.marshal(), bytearray.fromhex('432100000000DEADBEEF'))
49 | self.assertEqual(ch2.marshal(), bytearray.fromhex('80010000000A') + struct.pack(">L", Tpm2Defs.TPM_CC_Clear))
50 |
51 | def test_ch_has_correct_size(self):
52 | ch1 = Tpm2Stream.TPM2_COMMAND_HEADER(0x4321, 0x00000000, 0xDEADBEEF)
53 | self.assertEqual(ch1.get_size(), 0x0A)
54 |
55 | def test_ch_size_can_be_updated(self):
56 | ch1 = Tpm2Stream.TPM2_COMMAND_HEADER(0x4321, 0x00000000, 0xDEADBEEF)
57 | self.assertEqual(ch1.marshal(), bytearray.fromhex('432100000000DEADBEEF'))
58 | ch1.update_size(0x1234)
59 | self.assertEqual(ch1.marshal(), bytearray.fromhex('432100001234DEADBEEF'))
60 |
61 |
62 | if __name__ == '__main__':
63 | unittest.main()
64 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/BmpObject_test.py:
--------------------------------------------------------------------------------
1 | # @file VariableFormat_Test.py
2 | # Unit test harness for the VariableFormat module/classes.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import unittest
29 | import MuPythonLibrary.Uefi.BmpObject as BmpObject
30 | import io
31 |
32 | '''
33 | # to import an image into hex use this
34 | import binascii
35 | filename = image_path
36 | with open(filename, 'rb') as f:
37 | content = f.read()
38 | print(binascii.hexlify(content))
39 | '''
40 |
41 | hamburger = bytes.fromhex(('424d3603000000000000360000002800000010000000100000'
42 | '00010018000000000000000000c30e0000c30e00000000000000000000fffffff7f7f7669ccc43'
43 | '87c3307abd186ab12474b92877ba2477be1e70bc2473b83c81be337cbd4789c3fffffffffffff6'
44 | 'f6f6608fb92d70a11c666a0b57670d6e7f226fb54280b03a8f82228d661d886d0c6e990955a22e'
45 | '78b986b1d8fffffff0f0f03b77ae257e57219b6920a87316a3661ba56a167c4e2b938d2cac8b19'
46 | 'ac7920b18110799f004f9d2f79baffffffffffff146a60157e4c21907026928e289daf33b3d23d'
47 | 'bee43fb8dd48b1d036a1a71e976824a47129997b11589cffffff78c0ec249bc9469bc5877cad5d'
48 | '68854053721a335e182f571b2e592e4466515f799192ac9c82ad75c4c040a89edde6e826b0e54b'
49 | 'b4db9394b0223554233a58364c6a334c6d3451762f4d75304e742b4569273d5a435271c3bcd661'
50 | 'cbc15cbb9df1fafd7995a721395b173862113b692c4f7938557e3f5c7e4365893454812e4b7f32'
51 | '4b7f34496f41506b9db2d0eaf6f5f5f5f54961791f42934a6fdc3e89c42aa1a6297e8e3a5cb534'
52 | 'b79a279183324bdd2945d52a38b5333c8c516890abddf4f8f8f82649703e61ca41a5a053a1a25d'
53 | '9db15c9cbd599ac3568fb5588ead5a93aa468e9133867a2c3eb7384089f7f7f7fdfdfd38889369'
54 | 'a6b176a8cf5297d32b7fcd267bc92377c42e78b92777bf2975b93785cd4892d3589cba338281f7'
55 | 'f7f7ffffff7ab8d2589cd62f79be367cbb3381c61870bb1169b61c71b80d68b73177b3286da92a'
56 | '7cc5297ecc5197cbf4f6f7fcfdfd559ad53e89cf2e7fc32674b6a9c2db2272b61c6eb4b0cbe914'
57 | '68b00b5b9e9db6d0377cb72b7cc4277ccbe8f1f8fdfdff2b81cb3e89ccb7d0ec1c6fb4206dac2e'
58 | '6ea51b68a90f60a51b69aa0e63a91461a31764a52470b22579c2eff4fbffffffb2d0eb3f89ca36'
59 | '81c13c82bd4086c091b0ce3a74a5115c990b599bafcae6055ba3085ba17fa5ca8bacbefbfbfbff'
60 | 'fffff5f9fdaac7e05394cbb3cdea7faad06c9cc43f75a1a4b9cf125b98226ca7065ca40e65ae84'
61 | 'a8becad3d5fbfbfbfffffffffffffafbfcc0d1e0619bcd4e8fc8468ac43d80bb3576aa256fad20'
62 | '6cab1565a8aac8e2e7e9ebf8f8f8ffffff'))
63 |
64 | hamburger_lores = bytes.fromhex('424df60000000000000076000000280000001000000010'
65 | '000000010004000000000080000000c30e0000c30e000000000000000000000000000000008000'
66 | '008000000080800080000000800080008080000080808000c0c0c0000000ff0000ff000000ffff'
67 | '00ff000000ff00ff00ffff0000ffffff00ff733333333333fff73333333333338ff33333323333'
68 | '313ff32333bbbb33333f833733111138783fbb71111311113883f71111333311138ff319333333'
69 | '999138f13333333333391ff377b3333333b33ff8b333333333333ffb3338338338333ff3383333'
70 | '3333333ff83333833383377fff8388738333378ffff8733333338fff')
71 |
72 | bad_header_burger = bytes.fromhex('434df600000000000000760000002800000010000000'
73 | '10000000010004000000000080000000c30e0000c30e0000000000000000000000000000000080'
74 | '00008000000080800080000000800080008080000080808000c0c0c0000000ff0000ff000000ff'
75 | 'ff00ff000000ff00ff00ffff0000ffffff00ff733333333333fff73333333333338ff333333233'
76 | '33313ff32333bbbb33333f833733111138783fbb71111311113883f71111333311138ff3193333'
77 | '33999138f13333333333391ff377b3333333b33ff8b333333333333ffb3338338338333ff33833'
78 | '333333333ff83333833383377fff8388738333378ffff8733333338fff')
79 |
80 | bad_size_burger = bytes.fromhex('424df60000000000000076000000280000001000000010'
81 | '000000010004000000000080000000c30e0000c30e0000'
82 | '0000000000000000000000000000800000800000008080'
83 | '0080000000800080008080000080808000c0c0c0000000'
84 | 'ff0000ff000000ffff00ff000000ff00ff00ffff0000ff'
85 | 'ffff00ff733333333333fff73333333333338ff3333332'
86 | '3333313ff32333bbbb33333f833733111138783fbb7111'
87 | '1311113883f71111333311138ff319333333999138f133'
88 | '33333333391ff377b3333333b33ff8b333333333333ffb'
89 | '3338338338333ff33833333333333ff83333833383377f'
90 | 'ff8388738333378ffff873333333')
91 |
92 |
93 | class TestBmpObject(unittest.TestCase):
94 |
95 | def test_good_header(self):
96 | file = io.BytesIO(hamburger)
97 | bmp = BmpObject.BmpObject(file)
98 | self.assertEqual(bmp.CharB, b'B', "B header should be accurate")
99 | self.assertEqual(bmp.CharM, b'M', "M header should be accurate")
100 |
101 | def test_lores_good_header(self):
102 | file = io.BytesIO(hamburger_lores)
103 | bmp = BmpObject.BmpObject(file)
104 | self.assertEqual(bmp.CharB, b'B', "B header should be accurate")
105 | self.assertEqual(bmp.CharM, b'M', "M header should be accurate")
106 |
107 | def test_get_width_height(self):
108 | file = io.BytesIO(hamburger)
109 | bmp = BmpObject.BmpObject(file)
110 | self.assertEqual(bmp.PixelWidth, 16, "This is a 16 by 16")
111 | self.assertEqual(bmp.PixelHeight, 16, "This is 16 by 16")
112 |
113 | def test_lores_get_width_height(self):
114 | file = io.BytesIO(hamburger_lores)
115 | bmp = BmpObject.BmpObject(file)
116 | self.assertEqual(bmp.PixelWidth, 16, "This is a 16 by 16")
117 | self.assertEqual(bmp.PixelHeight, 16, "This is 16 by 16")
118 |
119 | def test_get_bits(self):
120 | file = io.BytesIO(hamburger_lores)
121 | bmp = BmpObject.BmpObject(file)
122 | self.assertEqual(bmp.BitPerPixel, 4, "should be 4 bit aren't accurate")
123 |
124 | def test_get_24_bits(self):
125 | file = io.BytesIO(hamburger)
126 | bmp = BmpObject.BmpObject(file)
127 | self.assertEqual(bmp.BitPerPixel, 24, "24 bits aren't accurate")
128 |
129 | def test_bad_header(self):
130 | file = io.BytesIO(bad_header_burger)
131 | bmp = BmpObject.BmpObject(file)
132 | self.assertNotEqual(bmp.CharB, b'B', "B header should be accurate")
133 | self.assertEqual(bmp.BitPerPixel, 4, "24 bits aren't accurate")
134 |
135 | def test_bad_image(self):
136 | file = io.BytesIO(bad_size_burger)
137 | with self.assertRaises(Exception):
138 | BmpObject.BmpObject(file) # we should keep reading pass the data
139 |
140 |
141 | if __name__ == '__main__':
142 | unittest.main()
143 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/Capsule/CatGenerator.py:
--------------------------------------------------------------------------------
1 | ## @file
2 | # Script to generate Cat files for capsule update based on supplied inf file
3 | #
4 | # Copyright (c) 2019, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 |
27 | import os
28 | import logging
29 | from MuPythonLibrary.UtilityFunctions import RunCmd
30 | from MuPythonLibrary.Windows.VsWhereUtilities import FindToolInWinSdk
31 |
32 |
33 | class CatGenerator(object):
34 | SUPPORTED_OS = {'win10': '10',
35 | '10': '10',
36 | '10_au': '10_AU',
37 | '10_rs2': '10_RS2',
38 | '10_rs3': '10_RS3',
39 | '10_rs4': '10_RS4',
40 | 'server10': 'Server10',
41 | 'server2016': 'Server2016',
42 | 'serverrs2': 'ServerRS2',
43 | 'serverrs3': 'ServerRS3',
44 | 'serverrs4': 'ServerRS4'
45 | }
46 |
47 | def __init__(self, arch, os):
48 | self.Arch = arch
49 | self.OperatingSystem = os
50 |
51 | @property
52 | def Arch(self):
53 | return self._arch
54 |
55 | @Arch.setter
56 | def Arch(self, value):
57 | value = value.lower()
58 | if(value == "x64") or (value == "amd64"): # support amd64 value so INF and CAT tools can use same arch value
59 | self._arch = "X64"
60 | elif(value == "arm"):
61 | self._arch = "ARM"
62 | elif(value == "arm64") or (value == "aarch64"): # support UEFI defined aarch64 value as well
63 | self._arch = "ARM64"
64 | else:
65 | logging.critical("Unsupported Architecture: %s", value)
66 | raise ValueError("Unsupported Architecture")
67 |
68 | @property
69 | def OperatingSystem(self):
70 | return self._operatingsystem
71 |
72 | @OperatingSystem.setter
73 | def OperatingSystem(self, value):
74 | key = value.lower()
75 | if(key not in CatGenerator.SUPPORTED_OS.keys()):
76 | logging.critical("Unsupported Operating System: %s", key)
77 | raise ValueError("Unsupported Operating System")
78 | self._operatingsystem = CatGenerator.SUPPORTED_OS[key]
79 |
80 | def MakeCat(self, OutputCatFile, PathToInf2CatTool=None):
81 | # Find Inf2Cat tool
82 | if(PathToInf2CatTool is None):
83 | PathToInf2CatTool = FindToolInWinSdk("Inf2Cat.exe")
84 | # check if exists
85 | if not os.path.exists(PathToInf2CatTool):
86 | raise Exception("Can't find Inf2Cat on this machine. Please install the Windows 10 WDK - "
87 | "https://developer.microsoft.com/en-us/windows/hardware/windows-driver-kit")
88 |
89 | # Adjust for spaces in the path (when calling the command).
90 | if " " in PathToInf2CatTool:
91 | PathToInf2CatTool = '"' + PathToInf2CatTool + '"'
92 |
93 | OutputFolder = os.path.dirname(OutputCatFile)
94 | # Make Cat file
95 | cmd = "/driver:. /os:" + self.OperatingSystem + "_" + self.Arch + " /verbose"
96 | ret = RunCmd(PathToInf2CatTool, cmd, workingdir=OutputFolder)
97 | if(ret != 0):
98 | raise Exception("Creating Cat file Failed with errorcode %d" % ret)
99 | if(not os.path.isfile(OutputCatFile)):
100 | raise Exception("CAT file (%s) not created" % OutputCatFile)
101 |
102 | return 0
103 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/Capsule/CatGenerator_test.py:
--------------------------------------------------------------------------------
1 | ## @file
2 | # Test script for CatGenerator.py based on various architecture/OS
3 | #
4 | # Copyright (c) 2019, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 |
27 | import os
28 | import unittest
29 | from MuPythonLibrary.Uefi.Capsule.CatGenerator import CatGenerator
30 |
31 | # must run from build env or set PYTHONPATH env variable to point to the MuPythonLibrary folder
32 |
33 |
34 | class CatGeneratorTest(unittest.TestCase):
35 |
36 | def test_win10_OS(self):
37 | o = CatGenerator("x64", "win10")
38 | self.assertEqual(o.OperatingSystem, "10")
39 |
40 | def test_10_OS(self):
41 | o = CatGenerator("x64", "10")
42 | self.assertEqual(o.OperatingSystem, "10")
43 |
44 | def test_10_AU_OS(self):
45 | o = CatGenerator("x64", "10_AU")
46 | self.assertEqual(o.OperatingSystem, "10_AU")
47 |
48 | def test_10_RS2_OS(self):
49 | o = CatGenerator("x64", "10_RS2")
50 | self.assertEqual(o.OperatingSystem, "10_RS2")
51 |
52 | def test_10_RS3_OS(self):
53 | o = CatGenerator("x64", "10_RS3")
54 | self.assertEqual(o.OperatingSystem, "10_RS3")
55 |
56 | def test_10_RS4_OS(self):
57 | o = CatGenerator("x64", "10_RS4")
58 | self.assertEqual(o.OperatingSystem, "10_RS4")
59 |
60 | def test_win10Server_OS(self):
61 | o = CatGenerator("x64", "Server10")
62 | self.assertEqual(o.OperatingSystem, "Server10")
63 |
64 | def test_Server2016_OS(self):
65 | o = CatGenerator("x64", "Server2016")
66 | self.assertEqual(o.OperatingSystem, "Server2016")
67 |
68 | def test_ServerRS2_OS(self):
69 | o = CatGenerator("x64", "ServerRS2")
70 | self.assertEqual(o.OperatingSystem, "ServerRS2")
71 |
72 | def test_ServerRS3_OS(self):
73 | o = CatGenerator("x64", "ServerRS3")
74 | self.assertEqual(o.OperatingSystem, "ServerRS3")
75 |
76 | def test_ServerRS4_OS(self):
77 | o = CatGenerator("x64", "ServerRS4")
78 | self.assertEqual(o.OperatingSystem, "ServerRS4")
79 |
80 | def test_invalid_OS(self):
81 | with self.assertRaises(ValueError):
82 | CatGenerator("x64", "Invalid Junk")
83 |
84 | def test_x64_arch(self):
85 | o = CatGenerator("x64", "win10")
86 | self.assertEqual(o.Arch, "X64")
87 |
88 | def test_amd64_arch(self):
89 | o = CatGenerator("amd64", "win10")
90 | self.assertEqual(o.Arch, "X64")
91 |
92 | def test_arm_arch(self):
93 | o = CatGenerator("arm", "win10")
94 | self.assertEqual(o.Arch, "ARM")
95 |
96 | def test_arm64_arch(self):
97 | o = CatGenerator("arm64", "win10")
98 | self.assertEqual(o.Arch, "ARM64")
99 |
100 | def test_aarch64_arch(self):
101 | o = CatGenerator("aarch64", "win10")
102 | self.assertEqual(o.Arch, "ARM64")
103 |
104 | def test_invalid_arch(self):
105 | with self.assertRaises(ValueError):
106 | CatGenerator("Invalid Arch", "win10")
107 |
108 | def test_invalid_pathtotool(self):
109 | o = CatGenerator("amd64", "10")
110 | with self.assertRaises(Exception) as cm:
111 | o.MakeCat("garbage", os.path.join("c:", "test", "badpath", "inf2cat.exe"))
112 | self.assertTrue(str(cm.exception).startswith("Can't find Inf2Cat on this machine."))
113 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/Capsule/InfGenerator.py:
--------------------------------------------------------------------------------
1 | ## @file
2 | # Script to generate inf files for capsule update based on INF TEMPLATE and
3 | # supplied information (Name, Version, ESRT Guid, Rollback, etc.)
4 | #
5 | # Copyright (c) 2019, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import os
29 | import logging
30 | import datetime
31 | import re
32 | import uuid
33 |
34 |
35 | #####
36 | #
37 | #####
38 | class InfGenerator(object):
39 |
40 | ### INF Template ###
41 | TEMPLATE = r""";
42 | ; {Name}.inf
43 | ; {DriverVersion}
44 | ; Copyright (C) 2019 Microsoft Corporation. All Rights Reserved.
45 | ;
46 | [Version]
47 | Signature="$WINDOWS NT$"
48 | Class=Firmware
49 | ClassGuid={{f2e7dd72-6468-4e36-b6f1-6488f42c1b52}}
50 | Provider=%Provider%
51 | DriverVer={Date},{DriverVersion}
52 | PnpLockdown=1
53 | CatalogFile={Name}.cat
54 |
55 | [Manufacturer]
56 | %MfgName% = Firmware,NT{Arch}
57 |
58 | [Firmware.NT{Arch}]
59 | %FirmwareDesc% = Firmware_Install,UEFI\RES_{{{EsrtGuid}}}
60 |
61 | [Firmware_Install.NT]
62 | CopyFiles = Firmware_CopyFiles
63 | {Rollback}
64 | [Firmware_CopyFiles]
65 | {FirmwareBinFile}
66 |
67 | [Firmware_Install.NT.Hw]
68 | AddReg = Firmware_AddReg
69 |
70 | [Firmware_AddReg]
71 | HKR,,FirmwareId,,{{{EsrtGuid}}}
72 | HKR,,FirmwareVersion,%REG_DWORD%,{VersionHexString}
73 | HKR,,FirmwareFilename,,{FirmwareBinFile}
74 |
75 | [SourceDisksNames]
76 | 1 = %DiskName%
77 |
78 | [SourceDisksFiles]
79 | {FirmwareBinFile} = 1
80 |
81 | [DestinationDirs]
82 | DefaultDestDir = %DIRID_WINDOWS%,Firmware ; %SystemRoot%\Firmware
83 |
84 | [Strings]
85 | ; localizable
86 | Provider = "{Provider}"
87 | MfgName = "{MfgName}"
88 | FirmwareDesc = "{Description}"
89 | DiskName = "Firmware Update"
90 |
91 | ; non-localizable
92 | DIRID_WINDOWS = 10
93 | REG_DWORD = 0x00010001
94 | """
95 |
96 | ROLLBACKTEMPLATE = r"""AddReg = Firmware_DowngradePolicy_Addreg
97 |
98 | ;override firmware resource update policy to allow downgrade to lower version
99 | [Firmware_DowngradePolicy_Addreg]
100 | HKLM,SYSTEM\CurrentControlSet\Control\FirmwareResources\{{{EsrtGuid}}},Policy,%REG_DWORD%,1
101 | """
102 |
103 | SUPPORTED_ARCH = {'amd64': 'amd64',
104 | 'x64': 'amd64',
105 | 'arm': 'arm',
106 | 'arm64': 'ARM64',
107 | 'aarch64': 'ARM64'
108 | }
109 |
110 | def __init__(self, name_string, provider, esrt_guid, arch, description_string, version_string, version_hex):
111 | self.Name = name_string
112 | self.Provider = provider
113 | self.EsrtGuid = esrt_guid
114 | self.Arch = arch
115 | self.Description = description_string
116 | self.VersionString = version_string
117 | self.VersionHex = version_hex
118 | self._manufacturer = None # default for optional feature
119 | self._date = datetime.date.today()
120 |
121 | @property
122 | def Name(self):
123 | return self._name
124 |
125 | @Name.setter
126 | def Name(self, value):
127 | # test here for invalid chars
128 | if not (re.compile(r'[\w-]*$')).match(value):
129 | logging.critical("Name invalid: '%s'", value)
130 | raise ValueError("Name has invalid chars.")
131 | self._name = value
132 |
133 | @property
134 | def Provider(self):
135 | return self._provider
136 |
137 | @Provider.setter
138 | def Provider(self, value):
139 | self._provider = value
140 |
141 | @property
142 | def Manufacturer(self):
143 | if(self._manufacturer is None):
144 | return self.Provider
145 |
146 | return self._manufacturer
147 |
148 | @Manufacturer.setter
149 | def Manufacturer(self, value):
150 | self._manufacturer = value
151 |
152 | @property
153 | def Description(self):
154 | return self._description
155 |
156 | @Description.setter
157 | def Description(self, value):
158 | self._description = value
159 |
160 | @property
161 | def EsrtGuid(self):
162 | return self._esrtguid
163 |
164 | @EsrtGuid.setter
165 | def EsrtGuid(self, value):
166 | uuid.UUID(value) # if this works it is valid...otherwise throws exception
167 | # todo - make sure it is formatted exactly right
168 | self._esrtguid = value
169 |
170 | @property
171 | def VersionString(self):
172 | return self._versionstring
173 |
174 | @VersionString.setter
175 | def VersionString(self, value):
176 | c = value.count(".")
177 | if(c < 1) or (c > 3):
178 | logging.critical("Version string in invalid format.")
179 | raise ValueError("VersionString must be in format of xx.xx -> xx.xx.xx.xx")
180 | self._versionstring = value
181 |
182 | @property
183 | def VersionHex(self):
184 | return "0x%X" % self._versionhex
185 |
186 | @VersionHex.setter
187 | def VersionHex(self, value):
188 | a = int(value, 0)
189 | if(a > 0xFFFFFFFF):
190 | logging.critical("VersionHex invalid: '%s'", value)
191 | raise ValueError("VersionHex must fit within 32bit value range for unsigned integer")
192 | self._versionhex = a
193 |
194 | @property
195 | def Arch(self):
196 | return self._arch
197 |
198 | @Arch.setter
199 | def Arch(self, value):
200 | key = value.lower()
201 | if(key not in InfGenerator.SUPPORTED_ARCH.keys()):
202 | logging.critical("Arch invalid: '%s'", value)
203 | raise ValueError("Unsupported Architecture")
204 | self._arch = InfGenerator.SUPPORTED_ARCH[key]
205 |
206 | @property
207 | def Date(self):
208 | return self._date.strftime("%m/%d/%Y")
209 |
210 | @Date.setter
211 | def Date(self, value):
212 | if(not isinstance(value, datetime.date)):
213 | raise ValueError("Date must be a datetime.date object")
214 | self._date = value
215 |
216 | def MakeInf(self, OutputInfFilePath, FirmwareBinFileName, Rollback=False):
217 | RollbackString = ""
218 | if(Rollback):
219 | RollbackString = InfGenerator.ROLLBACKTEMPLATE.format(EsrtGuid=self.EsrtGuid)
220 |
221 | binfilename = os.path.basename(FirmwareBinFileName)
222 |
223 | Content = InfGenerator.TEMPLATE.format(
224 | Name=self.Name,
225 | Date=self.Date,
226 | Arch=self.Arch,
227 | DriverVersion=self.VersionString,
228 | EsrtGuid=self.EsrtGuid,
229 | FirmwareBinFile=binfilename,
230 | VersionHexString=self.VersionHex,
231 | Provider=self.Provider,
232 | MfgName=self.Manufacturer,
233 | Description=self.Description,
234 | Rollback=RollbackString)
235 |
236 | with open(OutputInfFilePath, "w") as f:
237 | f.write(Content)
238 |
239 | return 0
240 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/Capsule/InfGenerator_test.py:
--------------------------------------------------------------------------------
1 | ## @file
2 | # Test script for InfGenerator.py
3 | #
4 | # Copyright (c) 2019, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 |
27 | import unittest
28 | from MuPythonLibrary.Uefi.Capsule.InfGenerator import InfGenerator
29 |
30 | # must run from build env or set PYTHONPATH env variable to point to the MuPythonLibrary folder
31 | # To run unit test open cmd prompt in the MuPythonLibrary folder
32 | # and then run 'python -m unittest discover -p "*_test.py"'
33 |
34 |
35 | class InfGeneratorTest(unittest.TestCase):
36 | VALID_GUID_STRING = "3cad7a0c-d35b-4b75-96b1-03a9fb07b7fc"
37 |
38 | def test_valid(self):
39 | o = InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
40 | "description", "aa.bb.cc.dd", "0xaabbccdd")
41 | self.assertIsInstance(o, InfGenerator)
42 | self.assertEqual(o.Name, "test_name")
43 | self.assertEqual(o.Provider, "provider")
44 | self.assertEqual(o.EsrtGuid, InfGeneratorTest.VALID_GUID_STRING)
45 | self.assertEqual(o.Arch, InfGenerator.SUPPORTED_ARCH["x64"])
46 | self.assertEqual(o.Description, "description")
47 | self.assertEqual(int(o.VersionHex, 0), int("0xaabbccdd", 0))
48 | self.assertEqual(o.VersionString, "aa.bb.cc.dd")
49 | self.assertEqual(o.Manufacturer, "provider")
50 |
51 | # loop thru all supported arch and make sure it works
52 | for a in InfGenerator.SUPPORTED_ARCH.keys():
53 | with self.subTest(Arch=a):
54 | o.Arch = a
55 | self.assertEqual(InfGenerator.SUPPORTED_ARCH[a], o.Arch)
56 |
57 | # set manufacturer
58 | o.Manufacturer = "manufacturer"
59 | self.assertEqual("manufacturer", o.Manufacturer)
60 |
61 | def test_invalid_name_symbol(self):
62 |
63 | InvalidChars = ['~', '`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', ' ', '{', '[', '}', ']', '+', '=']
64 | for a in InvalidChars:
65 | with self.subTest(name="test{}name".format(a)):
66 | name = "test{}name".format(a)
67 | with self.assertRaises(ValueError):
68 | InfGenerator(name, "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
69 | "description", "aa.bb", "0xaabbccdd")
70 |
71 | def test_version_string_format(self):
72 | with self.subTest(version_string="zero ."):
73 | with self.assertRaises(ValueError):
74 | InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
75 | "description", "1234", "0x100000000")
76 |
77 | with self.subTest(version_string="> 3 ."):
78 | with self.assertRaises(ValueError):
79 | InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
80 | "description", "1.2.3.4.5", "0x100000000")
81 |
82 | def test_version_hex_too_big(self):
83 | with self.subTest("hex string too big"):
84 | with self.assertRaises(ValueError):
85 | InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
86 | "description", "aa.bb", "0x100000000")
87 |
88 | with self.subTest("decimal too big"):
89 | with self.assertRaises(ValueError):
90 | InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
91 | "description", "aa.bb", "4294967296")
92 |
93 | def test_version_hex_can_support_decimal(self):
94 | o = InfGenerator("test_name", "provider", InfGeneratorTest.VALID_GUID_STRING, "x64",
95 | "description", "aa.bb.cc.dd", "12356")
96 | self.assertEqual(int(o.VersionHex, 0), 12356)
97 |
98 | def test_invalid_guid_format(self):
99 | with self.assertRaises(ValueError):
100 | InfGenerator("test_name", "provider", "NOT A VALID GUID", "x64", "description", "aa.bb", "0x1000000")
101 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/Capsule/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/Uefi/Capsule/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/DecParser.py:
--------------------------------------------------------------------------------
1 | # @file DecParser.py
2 | # Code to help parse DEC file
3 | ##
4 | # Copyright (c) 2018, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 | from MuPythonLibrary.Uefi.EdkII.Parsers.BaseParser import HashFileParser
28 | import os
29 |
30 |
31 | class DecParser(HashFileParser):
32 | def __init__(self):
33 | HashFileParser.__init__(self, 'DecParser')
34 | self.Lines = []
35 | self.Parsed = False
36 | self.Dict = {}
37 | self.LibrariesUsed = []
38 | self.PPIsUsed = []
39 | self.ProtocolsUsed = []
40 | self.GuidsUsed = []
41 | self.PcdsUsed = []
42 | self.IncludesUsed = []
43 | self.Path = ""
44 |
45 | def ParseFile(self, filepath):
46 | self.Logger.debug("Parsing file: %s" % filepath)
47 | if(not os.path.isabs(filepath)):
48 | fp = self.FindPath(filepath)
49 | else:
50 | fp = filepath
51 | self.Path = fp
52 |
53 | f = open(fp, "r")
54 | self.Lines = f.readlines()
55 | f.close()
56 | InDefinesSection = False
57 | InLibraryClassSection = False
58 | InProtocolsSection = False
59 | InGuidsSection = False
60 | InPPISection = False
61 | InPcdSection = False
62 | InIncludesSection = False
63 |
64 | for line in self.Lines:
65 | sline = self.StripComment(line)
66 |
67 | if(sline is None or len(sline) < 1):
68 | continue
69 |
70 | if InDefinesSection:
71 | if sline.strip()[0] == '[':
72 | InDefinesSection = False
73 | else:
74 | if sline.count("=") == 1:
75 | tokens = sline.split('=', 1)
76 | self.Dict[tokens[0].strip()] = tokens[1].strip()
77 | continue
78 |
79 | elif InLibraryClassSection:
80 | if sline.strip()[0] == '[':
81 | InLibraryClassSection = False
82 | else:
83 | t = sline.partition("|")
84 | self.LibrariesUsed.append(t[0].strip())
85 | continue
86 |
87 | elif InProtocolsSection:
88 | if sline.strip()[0] == '[':
89 | InProtocolsSection = False
90 | else:
91 | t = sline.partition("=")
92 | self.ProtocolsUsed.append(t[0].strip())
93 | continue
94 |
95 | elif InGuidsSection:
96 | if sline.strip()[0] == '[':
97 | InGuidsSection = False
98 | else:
99 | t = sline.partition("=")
100 | self.GuidsUsed.append(t[0].strip())
101 | continue
102 |
103 | elif InPcdSection:
104 | if sline.strip()[0] == '[':
105 | InPcdSection = False
106 | else:
107 | t = sline.partition("|")
108 | self.PcdsUsed.append(t[0].strip())
109 | continue
110 |
111 | elif InIncludesSection:
112 | if sline.strip()[0] == '[':
113 | InIncludesSection = False
114 | else:
115 | self.IncludesUsed.append(sline.strip())
116 | continue
117 |
118 | elif InPPISection:
119 | if (sline.strip()[0] == '['):
120 | InPPISection = False
121 | else:
122 | t = sline.partition("=")
123 | self.PPIsUsed.append(t[0].strip())
124 | continue
125 |
126 | # check for different sections
127 | if sline.strip().lower().startswith('[defines'):
128 | InDefinesSection = True
129 |
130 | elif sline.strip().lower().startswith('[libraryclasses'):
131 | InLibraryClassSection = True
132 |
133 | elif sline.strip().lower().startswith('[protocols'):
134 | InProtocolsSection = True
135 |
136 | elif sline.strip().lower().startswith('[guids'):
137 | InGuidsSection = True
138 |
139 | elif sline.strip().lower().startswith('[ppis'):
140 | InPPISection = True
141 |
142 | elif sline.strip().lower().startswith('[pcd'):
143 | InPcdSection = True
144 |
145 | elif sline.strip().lower().startswith('[includes'):
146 | InIncludesSection = True
147 |
148 | self.Parsed = True
149 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/FdfParser.py:
--------------------------------------------------------------------------------
1 | # @file FdfParser.py
2 | # Code to help parse EDK2 Fdf files
3 | ##
4 | # Copyright (c) 2016, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 | from MuPythonLibrary.Uefi.EdkII.Parsers.BaseParser import HashFileParser
28 | import os
29 |
30 |
31 | class FdfParser(HashFileParser):
32 |
33 | def __init__(self):
34 | HashFileParser.__init__(self, 'ModuleFdfParser')
35 | self.Lines = []
36 | self.Parsed = False
37 | self.Dict = {} # defines dictionary
38 | self.FVs = {}
39 | self.FDs = {}
40 | self.CurrentSection = []
41 | self.Path = ""
42 |
43 | def GetNextLine(self):
44 | if len(self.Lines) == 0:
45 | return None
46 |
47 | line = self.Lines.pop()
48 | self.CurrentLine += 1
49 | sline = self.StripComment(line)
50 |
51 | if(sline is None or len(sline) < 1):
52 | return self.GetNextLine()
53 |
54 | sline = self.ReplaceVariables(sline)
55 | if self.ProcessConditional(sline):
56 | # was a conditional so skip
57 | return self.GetNextLine()
58 | if not self.InActiveCode():
59 | return self.GetNextLine()
60 |
61 | self._BracketCount += sline.count("{")
62 | self._BracketCount -= sline.count("}")
63 |
64 | return sline
65 |
66 | def ParseFile(self, filepath):
67 | self.Logger.debug("Parsing file: %s" % filepath)
68 | if(not os.path.isabs(filepath)):
69 | fp = self.FindPath(filepath)
70 | else:
71 | fp = filepath
72 | self.Path = fp
73 | self.CurrentLine = 0
74 | self._f = open(fp, "r")
75 | self.Lines = self._f.readlines()
76 | self.Lines.reverse()
77 | self._f.close()
78 | self._BracketCount = 0
79 | InDefinesSection = False
80 | InFdSection = False
81 | InFvSection = False
82 | InCapsuleSection = False
83 | InFmpPayloadSection = False
84 | InRuleSection = False
85 |
86 | sline = ""
87 | while sline is not None:
88 | sline = self.GetNextLine()
89 |
90 | if sline is None:
91 | break
92 |
93 | if sline.strip().startswith("[") and sline.strip().endswith("]"): # if we're starting a new section
94 | # this basically gets what's after the . or if it doesn't have a period
95 | # the whole thing for every comma seperated item in sline
96 | self.CurrentSection = [
97 | x.split(".", 1)[1] if "." in x else x for x in sline.strip("[] ").strip().split(",")]
98 | InDefinesSection = False
99 | InFdSection = False
100 | InFvSection = False
101 | InCapsuleSection = False
102 | InFmpPayloadSection = False
103 | InRuleSection = False
104 | self.LocalVars = {}
105 | self.LocalVars.update(self.Dict)
106 |
107 | if InDefinesSection:
108 | if sline.count("=") == 1:
109 | tokens = sline.replace("DEFINE", "").split('=', 1)
110 | self.Dict[tokens[0].strip()] = tokens[1].strip()
111 | self.Logger.info("Key,values found: %s = %s" % (tokens[0].strip(), tokens[1].strip()))
112 | continue
113 |
114 | elif InFdSection:
115 | for section in self.CurrentSection:
116 | if section not in self.FVs:
117 | self.FDs[section] = {"Dict": {}}
118 | # TODO finish the FD section
119 | continue
120 |
121 | elif InFvSection:
122 | for section in self.CurrentSection:
123 | if section not in self.FVs:
124 | self.FVs[section] = {"Dict": {}, "Infs": [], "Files": {}}
125 | # ex: INF MdeModulePkg/Core/RuntimeDxe/RuntimeDxe.inf
126 | if sline.upper().startswith("INF "):
127 | InfValue = sline[3:].strip()
128 | self.FVs[section]["Infs"].append(InfValue)
129 | # ex: FILE FREEFORM = 7E175642-F3AD-490A-9F8A-2E9FC6933DDD {
130 | elif sline.upper().startswith("FILE"):
131 | sline = sline.strip("}").strip("{").strip() # make sure we take off the { and }
132 | file_def = sline[4:].strip().split("=", 1) # split by =
133 | if len(file_def) != 2: # check to make sure we can parse this file
134 | raise RuntimeError("Unable to properly parse " + sline)
135 |
136 | currentType = file_def[0].strip() # get the type FILE
137 | currentName = file_def[1].strip() # get the name (guid or otherwise)
138 | if currentType not in self.FVs[section]:
139 | self.FVs[section]["Files"][currentName] = {}
140 | self.FVs[section]["Files"][currentName]["type"] = currentType
141 |
142 | while self._BracketCount > 0: # go until we get our bracket back
143 | sline = self.GetNextLine().strip("}{ ")
144 | # SECTION GUIDED EE4E5898-3914-4259-9D6E-DC7BD79403CF PROCESSING_REQUIRED = TRUE
145 | if sline.upper().startswith("SECTION GUIDED"): # get the guided section
146 | section_def = sline[14:].strip().split("=", 1)
147 | sectionType = section_def[0].strip() # UI in this example
148 | sectionValue = section_def[1].strip()
149 | if sectionType not in self.FVs[section]["Files"][currentName]:
150 | self.FVs[section]["Files"][currentName][sectionType] = {}
151 | # TODO support guided sections
152 | # ex: SECTION UI = "GenericGopDriver"
153 | elif sline.upper().startswith("SECTION"): # get the section
154 | section_def = sline[7:].strip().split("=", 1)
155 | sectionType = section_def[0].strip() # UI in this example
156 | sectionValue = section_def[1].strip()
157 |
158 | if sectionType not in self.FVs[section]["Files"][currentName]:
159 | self.FVs[section]["Files"][currentName][sectionType] = []
160 | self.FVs[section]["Files"][currentName][sectionType].append(sectionValue)
161 | else:
162 | self.Logger.info("Unknown line: {}".format(sline))
163 |
164 | continue
165 |
166 | elif InCapsuleSection:
167 | # TODO: finish capsule section
168 | continue
169 |
170 | elif InFmpPayloadSection:
171 | # TODO finish FMP payload section
172 | continue
173 |
174 | elif InRuleSection:
175 | # TODO finish rule section
176 | continue
177 |
178 | # check for different sections
179 | if sline.strip().lower().startswith('[defines'):
180 | InDefinesSection = True
181 |
182 | elif sline.strip().lower().startswith('[fd.'):
183 | InFdSection = True
184 |
185 | elif sline.strip().lower().startswith('[fv.'):
186 | InFvSection = True
187 |
188 | elif sline.strip().lower().startswith('[capsule.'):
189 | InCapsuleSection = True
190 |
191 | elif sline.strip().lower().startswith('[fmpPayload.'):
192 | InFmpPayloadSection = True
193 |
194 | elif sline.strip().lower().startswith('[rule.'):
195 | InRuleSection = True
196 |
197 | self.Parsed = True
198 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/InfParser.py:
--------------------------------------------------------------------------------
1 | # @file InfParser.py
2 | # Code to help parse EDK2 INF files
3 | ##
4 | # Copyright (c) 2016, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 | from MuPythonLibrary.Uefi.EdkII.Parsers.BaseParser import HashFileParser
28 | import os
29 |
30 |
31 | AllPhases = ["SEC", "PEIM", "PEI_CORE", "DXE_DRIVER", "DXE_CORE", "DXE_RUNTIME_DRIVER", "UEFI_DRIVER",
32 | "SMM_CORE", "DXE_SMM_DRIVER", "UEFI_APPLICATION"]
33 |
34 |
35 | class InfParser(HashFileParser):
36 |
37 | def __init__(self):
38 | HashFileParser.__init__(self, 'ModuleInfParser')
39 | self.Lines = []
40 | self.Parsed = False
41 | self.Dict = {}
42 | self.LibraryClass = ""
43 | self.SupportedPhases = []
44 | self.PackagesUsed = []
45 | self.LibrariesUsed = []
46 | self.ProtocolsUsed = []
47 | self.GuidsUsed = []
48 | self.PpisUsed = []
49 | self.PcdsUsed = []
50 | self.Sources = []
51 | self.Binaries = []
52 | self.Path = ""
53 |
54 | def ParseFile(self, filepath):
55 | self.Logger.debug("Parsing file: %s" % filepath)
56 | if(not os.path.isabs(filepath)):
57 | fp = self.FindPath(filepath)
58 | else:
59 | fp = filepath
60 | self.Path = fp
61 | f = open(fp, "r")
62 | self.Lines = f.readlines()
63 | f.close()
64 | InDefinesSection = False
65 | InPackagesSection = False
66 | InLibraryClassSection = False
67 | InProtocolsSection = False
68 | InGuidsSection = False
69 | InPpiSection = False
70 | InPcdSection = False
71 | InSourcesSection = False
72 | InBinariesSection = False
73 |
74 | for line in self.Lines:
75 | sline = self.StripComment(line)
76 |
77 | if(sline is None or len(sline) < 1):
78 | continue
79 |
80 | if InDefinesSection:
81 | if sline.strip()[0] == '[':
82 | InDefinesSection = False
83 | else:
84 | if sline.count("=") == 1:
85 | tokens = sline.split('=', 1)
86 | self.Dict[tokens[0].strip()] = tokens[1].strip()
87 | #
88 | # Parse Library class and phases in special manor
89 | #
90 | if(tokens[0].strip().lower() == "library_class"):
91 | self.LibraryClass = tokens[1].partition("|")[0].strip()
92 | self.Logger.debug("Library class found")
93 | if(len(tokens[1].partition("|")[2].strip()) < 1):
94 | self.SupportedPhases = AllPhases
95 | elif(tokens[1].partition("|")[2].strip().lower() == "base"):
96 | self.SupportedPhases = AllPhases
97 | else:
98 | self.SupportedPhases = tokens[1].partition("|")[2].strip().split()
99 |
100 | self.Logger.debug("Key,values found: %s = %s" % (tokens[0].strip(), tokens[1].strip()))
101 |
102 | continue
103 |
104 | elif InPackagesSection:
105 | if sline.strip()[0] == '[':
106 | InPackagesSection = False
107 | else:
108 | self.PackagesUsed.append(sline.partition("|")[0].strip())
109 | continue
110 |
111 | elif InLibraryClassSection:
112 | if sline.strip()[0] == '[':
113 | InLibraryClassSection = False
114 | else:
115 | self.LibrariesUsed.append(sline.partition("|")[0].strip())
116 | continue
117 |
118 | elif InProtocolsSection:
119 | if sline.strip()[0] == '[':
120 | InProtocolsSection = False
121 | else:
122 | self.ProtocolsUsed.append(sline.partition("|")[0].strip())
123 | continue
124 |
125 | elif InGuidsSection:
126 | if sline.strip()[0] == '[':
127 | InGuidsSection = False
128 | else:
129 | self.GuidsUsed.append(sline.partition("|")[0].strip())
130 | continue
131 |
132 | elif InPcdSection:
133 | if sline.strip()[0] == '[':
134 | InPcdSection = False
135 | else:
136 | self.PcdsUsed.append(sline.partition("|")[0].strip())
137 | continue
138 |
139 | elif InPpiSection:
140 | if sline.strip()[0] == '[':
141 | InPpiSection = False
142 | else:
143 | self.PpisUsed.append(sline.partition("|")[0].strip())
144 | continue
145 |
146 | elif InSourcesSection:
147 | if sline.strip()[0] == '[':
148 | InSourcesSection = False
149 | else:
150 | self.Sources.append(sline.partition("|")[0].strip())
151 | continue
152 |
153 | elif InBinariesSection:
154 | if sline.strip()[0] == '[':
155 | InBinariesSection = False
156 | else:
157 | self.Binaries.append(sline.partition("|")[0].strip())
158 | continue
159 |
160 | # check for different sections
161 | if sline.strip().lower().startswith('[defines'):
162 | InDefinesSection = True
163 |
164 | elif sline.strip().lower().startswith('[packages'):
165 | InPackagesSection = True
166 |
167 | elif sline.strip().lower().startswith('[libraryclasses'):
168 | InLibraryClassSection = True
169 |
170 | elif sline.strip().lower().startswith('[protocols'):
171 | InProtocolsSection = True
172 |
173 | elif sline.strip().lower().startswith('[ppis'):
174 | InPpiSection = True
175 |
176 | elif sline.strip().lower().startswith('[guids'):
177 | InGuidsSection = True
178 |
179 | elif sline.strip().lower().startswith('[pcd') or \
180 | sline.strip().lower().startswith('[patchpcd') or \
181 | sline.strip().lower().startswith('[fixedpcd') or \
182 | sline.strip().lower().startswith('[featurepcd'):
183 | InPcdSection = True
184 |
185 | elif sline.strip().lower().startswith('[sources'):
186 | InSourcesSection = True
187 |
188 | elif sline.strip().lower().startswith('[binaries'):
189 | InBinariesSection = True
190 |
191 | self.Parsed = True
192 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/OverrideParser.py:
--------------------------------------------------------------------------------
1 | ## @file OverrideParser.py
2 | # Contains classes to help with the parsing of INF files that
3 | # may contain OVERRIDE information.
4 | #
5 | ##
6 | # Copyright (c) 2018, Microsoft Corporation
7 | #
8 | # All rights reserved.
9 | # Redistribution and use in source and binary forms, with or without
10 | # modification, are permitted provided that the following conditions are met:
11 | # 1. Redistributions of source code must retain the above copyright notice,
12 | # this list of conditions and the following disclaimer.
13 | # 2. Redistributions in binary form must reproduce the above copyright notice,
14 | # this list of conditions and the following disclaimer in the documentation
15 | # and/or other materials provided with the distribution.
16 | #
17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
26 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | ##
28 |
29 | import os
30 | import datetime
31 |
32 | FORMAT_VERSION_1 = (1, 4) # Version 1: #OVERRIDE : VERSION | PATH_TO_MODULE | HASH | YYYY-MM-DDThh-mm-ss
33 |
34 |
35 | class OpParseError(Exception):
36 | PE_VER = 'VERSION'
37 | PE_PATH = 'PATH'
38 | PE_HASH = 'HASH'
39 | PE_DATE = 'DATE'
40 |
41 | def __init__(self, my_type):
42 | if my_type not in (OpParseError.PE_VER, OpParseError.PE_PATH,
43 | OpParseError.PE_HASH, OpParseError.PE_DATE):
44 | raise ValueError("Unknown type '%s'" % my_type)
45 | self.type = my_type
46 |
47 | def __str__(self):
48 | return repr(self.type)
49 |
50 |
51 | class OverrideParser(object):
52 | """
53 | OverrideParser is a simple file parser for .inf files that
54 | contain OVERRIDE data (i.e. overriding other .infs).
55 | Creating the object can be done by passing either a valid file path
56 | or a string containing the contents of an .inf file.
57 |
58 | Will raise an exception if the file doesn't exist or if the contents
59 | do not contain any OVERRIDE data.
60 |
61 | NOTE: There is an argument to be made that this class should actually be
62 | a subclass of InfParser, however, the InfParser is looking for far
63 | more details and has a much higher overhead. During a parser refactor,
64 | this should be considered.
65 |
66 | ALSO NOTE: There is a pattern used here where the object parses during
67 | instantiation. This pattern does not necessarily match the other
68 | parsers. The pros and cons of this should also be weighed during
69 | any parser refactor.
70 | """
71 | def __init__(self, file_path=None, inf_contents=None):
72 | super(OverrideParser, self).__init__()
73 |
74 | # Make sure that at least some data is provided.
75 | if file_path is None and inf_contents is None:
76 | raise ValueError("file_path or inf_contents is required.")
77 | # Make sure not too much data is provided.
78 | if file_path is not None and inf_contents is not None:
79 | raise ValueError("Only provide file_path or inf_contents. (%s, %s)" % (file_path, inf_contents))
80 | # If a file path was provided, make sure it exists.
81 | if file_path is not None:
82 | if not os.path.isfile(file_path):
83 | raise ValueError("File path '%s' does not exist." % file_path)
84 |
85 | self.file_path = os.path.abspath(file_path) if file_path is not None else 'String Buffer'
86 |
87 | # Set up the contents for parsing.
88 | parse_contents = inf_contents
89 | if file_path is not None:
90 | with open(file_path, 'r') as file:
91 | parse_contents = file.read()
92 | if not parse_contents:
93 | raise ValueError("Failed to read contents of file '%s'." % self.file_path)
94 |
95 | self.override_lines = self.get_override_lines(parse_contents)
96 |
97 | # If no override lines were found, we're basically done here.
98 | if not self.override_lines:
99 | raise ValueError("File '%s' did not contain any override lines." % self.file_path)
100 |
101 | self.overrides = []
102 | for override_line in self.override_lines:
103 | try:
104 | self.overrides.append(self.parse_override_line(override_line['line']))
105 | except OpParseError as pe:
106 | raise ValueError("Parse error '%s' occurred while processing line %d of '%s'." %
107 | (pe, override_line['lineno'], override_line['line']))
108 |
109 | @staticmethod
110 | def get_override_lines(parse_contents):
111 | parse_lines = parse_contents.split('\n')
112 | result = []
113 |
114 | for i in range(0, len(parse_lines)):
115 | if parse_lines[i].strip().upper().startswith("#OVERRIDE"):
116 | result.append({'lineno': i + 1, 'line': parse_lines[i].strip()})
117 |
118 | return result
119 |
120 | @staticmethod
121 | def parse_override_line(line_contents):
122 | result = {}
123 |
124 | # Split the override string into pieces.
125 | # First the #OVERRIDE, which is separated by a :.
126 | # Then everything else by |.
127 | line_parts = line_contents.split(":")
128 | line_parts = [part.strip() for part in line_parts[1].split("|")]
129 |
130 | # Step 1: Check version and number of blocks in this entry
131 | try:
132 | result['version'] = int(line_parts[0])
133 | except ValueError:
134 | raise OpParseError(OpParseError.PE_VER)
135 |
136 | # Verify this is a known version and has valid number of entries
137 | if not ((result['version'] == FORMAT_VERSION_1[0]) and (len(line_parts) == FORMAT_VERSION_1[1])):
138 | raise OpParseError(OpParseError.PE_VER)
139 |
140 | # Step 2: Process the path to overridden module
141 | # Normalize the path to support different slashes.
142 | result['original_path'] = os.path.normpath(line_parts[1])
143 |
144 | # Step 3: Grep hash entry
145 | result['current_hash'] = line_parts[2]
146 |
147 | # Step 4: Parse the time of hash generation
148 | try:
149 | result['datetime'] = datetime.datetime.strptime(line_parts[3], "%Y-%m-%dT%H-%M-%S")
150 | except ValueError:
151 | raise OpParseError(OpParseError.PE_DATE)
152 |
153 | return result
154 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/OverrideParser_Test.py:
--------------------------------------------------------------------------------
1 | ## @file OverrideParser_Test.py
2 | # Contains unit test routines for the OverrideParser class.
3 | #
4 | ##
5 | # Copyright (c) 2018, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import unittest
29 | import os
30 |
31 | from MuPythonLibrary.Uefi.EdkII.Parsers import OverrideParser as op
32 |
33 | SAMPLE_DATA_SINGLE_OVERRIDE = """#Override : 00000001 | My/Path/1 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30""" # noqa: E501
34 |
35 | SAMPLE_DATA_BAD_VERSION = """#Override : 0000000X | My/Path/1 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30""" # noqa: E501
36 | SAMPLE_DATA_BAD_DATE = """#Override : 00000001 | My/Path/1 | 4e367990b327501d1ea6fbee4002f9c8 | NOTADATE"""
37 |
38 | SAMPLE_DATA_TRIPLE_OVERRIDE = """#Override : 00000001 | My/Path/1 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30
39 | #Override : 00000001 | My/Path/2 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30
40 | #Override : 00000001 | My/Path/3 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30
41 | """
42 |
43 | SAMPLE_DATA_MIXED_CASE_OVERRIDE = """#Override : 00000001 | My/Path/1 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27T22-36-30
44 | #OVERRIDE : 00000001 | MY/PATH/2 | 4E367990B327501D1EA6FBEE4002F9C8 | 2018-11-27T22-36-30
45 | #override : 00000001 | my/path/3 | 4e367990b327501d1ea6fbee4002f9c8 | 2018-11-27t22-36-30
46 | """
47 |
48 | SAMPLE_DATA_NO_OVERRIDE = """This are just some lines of text.
49 |
50 | Nothing to see here.
51 |
52 | #OVER... not really, bet you thought it was an override.
53 | """
54 |
55 | SAMPLE_DATA_REAL_WORLD = """## @file
56 | # The DXE driver produces FORM DISPLAY ENGINE protocol.
57 | #
58 | # Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
59 | # Copyright (c) 2015 - 2018, Microsoft Corporation.
60 | #
61 | # This program and the accompanying materials
62 | # are licensed and made available under the terms and conditions of the BSD License
63 | # which accompanies this distribution. The full text of the license may be found at
64 | # http://opensource.org/licenses/bsd-license.php
65 | #
66 | # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
67 | # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
68 | #
69 | #
70 | ##
71 |
72 | #Override : 00000001 | MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf | 2eba0bc48b8ab3b1399c26800d057102 | 2018-10-05T21-43-51
73 |
74 | [Defines]
75 | INF_VERSION = 0x00010005
76 | BASE_NAME = DisplayEngine
77 | FILE_GUID = E660EA85-058E-4b55-A54B-F02F83A24707
78 | MODULE_TYPE = DXE_DRIVER
79 | VERSION_STRING = 1.0
80 | ENTRY_POINT = InitializeDisplayEngine
81 | UNLOAD_IMAGE = UnloadDisplayEngine
82 |
83 | # MORE TEXT BELOW HERE...
84 | """ # noqa: E501
85 |
86 |
87 | class TestOverrideParser(unittest.TestCase):
88 |
89 | def test_no_inputs_raises_error(self):
90 | with self.assertRaises(ValueError):
91 | op.OverrideParser()
92 |
93 | def test_no_overrides_raises_error(self):
94 | with self.assertRaises(ValueError):
95 | op.OverrideParser(inf_contents=SAMPLE_DATA_NO_OVERRIDE)
96 |
97 | def test_bad_version_or_bad_date_raises_error(self):
98 | with self.assertRaises(ValueError):
99 | op.OverrideParser(inf_contents=SAMPLE_DATA_BAD_VERSION)
100 | with self.assertRaises(ValueError):
101 | op.OverrideParser(inf_contents=SAMPLE_DATA_BAD_DATE)
102 |
103 | def test_single_override_is_parsed(self):
104 | parser = op.OverrideParser(inf_contents=SAMPLE_DATA_SINGLE_OVERRIDE)
105 |
106 | self.assertEqual(len(parser.override_lines), 1)
107 | self.assertEqual(len(parser.overrides), 1)
108 |
109 | self.assertEqual(parser.overrides[0]['version'], 1)
110 | self.assertEqual(parser.overrides[0]['original_path'].upper(), os.path.normpath('MY/PATH/1'))
111 | self.assertEqual(parser.overrides[0]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
112 | self.assertEqual(parser.overrides[0]['datetime'].year, 2018)
113 | self.assertEqual(parser.overrides[0]['datetime'].month, 11)
114 | self.assertEqual(parser.overrides[0]['datetime'].day, 27)
115 |
116 | def test_real_world_override_is_parsed(self):
117 | parser = op.OverrideParser(inf_contents=SAMPLE_DATA_REAL_WORLD)
118 |
119 | self.assertEqual(len(parser.override_lines), 1)
120 | self.assertEqual(len(parser.overrides), 1)
121 |
122 | self.assertEqual(parser.overrides[0]['version'], 1)
123 | self.assertEqual(parser.overrides[0]['original_path'],
124 | os.path.normpath('MdeModulePkg/Universal/DisplayEngineDxe/DisplayEngineDxe.inf'))
125 | self.assertEqual(parser.overrides[0]['current_hash'].upper(), '2EBA0BC48B8AB3B1399C26800D057102')
126 | self.assertEqual(parser.overrides[0]['datetime'].year, 2018)
127 | self.assertEqual(parser.overrides[0]['datetime'].month, 10)
128 | self.assertEqual(parser.overrides[0]['datetime'].day, 5)
129 |
130 | def test_triple_override_is_parsed(self):
131 | parser = op.OverrideParser(inf_contents=SAMPLE_DATA_TRIPLE_OVERRIDE)
132 |
133 | self.assertEqual(len(parser.override_lines), 3)
134 | self.assertEqual(len(parser.overrides), 3)
135 |
136 | self.assertEqual(parser.overrides[0]['version'], 1)
137 | self.assertEqual(parser.overrides[0]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
138 | self.assertEqual(parser.overrides[1]['version'], 1)
139 | self.assertEqual(parser.overrides[1]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
140 | self.assertEqual(parser.overrides[2]['version'], 1)
141 | self.assertEqual(parser.overrides[2]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
142 |
143 | def test_mixed_case_override_is_parsed(self):
144 | parser = op.OverrideParser(inf_contents=SAMPLE_DATA_MIXED_CASE_OVERRIDE)
145 |
146 | self.assertEqual(len(parser.override_lines), 3)
147 | self.assertEqual(len(parser.overrides), 3)
148 |
149 | self.assertEqual(parser.overrides[0]['version'], 1)
150 | self.assertEqual(parser.overrides[0]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
151 | self.assertEqual(parser.overrides[1]['version'], 1)
152 | self.assertEqual(parser.overrides[1]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
153 | self.assertEqual(parser.overrides[2]['version'], 1)
154 | self.assertEqual(parser.overrides[2]['current_hash'].upper(), '4E367990B327501D1EA6FBEE4002F9C8')
155 |
156 | def test_parses_all_versions(self):
157 | # TODO: Fill out this test if it's ever needed.
158 | pass
159 |
160 |
161 | if __name__ == '__main__':
162 | unittest.main()
163 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/TargetTxtParser.py:
--------------------------------------------------------------------------------
1 | # @file TargetTxtParser.py
2 | # Code to help parse Edk2 Conf/Target.txt file
3 | ##
4 | # Copyright (c) 2016, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 | from MuPythonLibrary.Uefi.EdkII.Parsers.BaseParser import HashFileParser
28 | import os
29 |
30 |
31 | class TargetTxtParser(HashFileParser):
32 |
33 | def __init__(self):
34 | HashFileParser.__init__(self, 'TargetTxtParser')
35 | self.Lines = []
36 | self.Parsed = False
37 | self.Dict = {}
38 | self.Path = ""
39 |
40 | def ParseFile(self, filepath):
41 | self.Logger.debug("Parsing file: %s" % filepath)
42 | if(not os.path.isabs(filepath)):
43 | fp = self.FindPath(filepath)
44 | else:
45 | fp = filepath
46 | self.Path = fp
47 | f = open(fp, "r")
48 | self.Lines = f.readlines()
49 | f.close()
50 |
51 | for line in self.Lines:
52 | sline = self.StripComment(line)
53 |
54 | if(sline is None or len(sline) < 1):
55 | continue
56 |
57 | if sline.count("=") == 1:
58 | tokens = sline.split('=', 1)
59 | self.Dict[tokens[0].strip()] = tokens[1].strip()
60 | self.Logger.debug("Key,values found: %s = %s" % (tokens[0].strip(), tokens[1].strip()))
61 | continue
62 |
63 | self.Parsed = True
64 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/Parsers/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/Uefi/EdkII/Parsers/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/PathUtilities.py:
--------------------------------------------------------------------------------
1 | # @file PathUtilities.py
2 | # Code to help convert Edk2, absolute, and relative file paths
3 | ##
4 | # Copyright (c) 2018, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 | import os
28 | import logging
29 | import fnmatch
30 |
31 | #
32 | # Class to help convert from absolute path to EDK2 build path
33 | # using workspace and packagepath variables
34 | #
35 |
36 |
37 | class Edk2Path(object):
38 |
39 | #
40 | # ws - absolute path or cwd relative to workspace
41 | # packagepathlist - list of packages path. Absolute path list or workspace relative path
42 | #
43 | def __init__(self, ws, packagepathlist):
44 | self.WorkspacePath = ws
45 | self.logger = logging.getLogger("Edk2Path")
46 | if(not os.path.isabs(ws)):
47 | self.WorkspacePath = os.path.abspath(os.path.join(os.getcwd(), ws))
48 |
49 | if(not os.path.isdir(self.WorkspacePath)):
50 | self.logger.error("Workspace path invalid. {0}".format(ws))
51 | raise Exception("Workspace path invalid. {0}".format(ws))
52 |
53 | # Set PackagePath
54 | self.PackagePathList = list()
55 | for a in packagepathlist:
56 | if(os.path.isabs(a)):
57 | self.PackagePathList.append(a)
58 | else:
59 | # see if workspace relative
60 | wsr = os.path.join(ws, a)
61 | if(os.path.isdir(wsr)):
62 | self.PackagePathList.append(wsr)
63 | else:
64 | # assume current working dir relative. Will catch invalid dir when checking whole list
65 | self.PackagePathList.append(os.path.abspath(os.path.join(os.getcwd(), a)))
66 |
67 | error = False
68 | for a in self.PackagePathList:
69 | if(not os.path.isdir(a)):
70 | self.logger.error("Invalid package path entry {0}".format(a))
71 | error = True
72 |
73 | # report error
74 | if(error):
75 | raise Exception("Invalid package path directory(s)")
76 |
77 | def GetEdk2RelativePathFromAbsolutePath(self, abspath):
78 | relpath = None
79 | found = False
80 | if abspath is None:
81 | return None
82 | for a in self.PackagePathList:
83 | stripped = abspath.lower().partition(a.lower())[2]
84 | if stripped:
85 | # found our path...now lets correct for case
86 | relpath = abspath[len(a):]
87 | found = True
88 | self.logger.debug("Successfully converted AbsPath to Edk2Relative Path using PackagePath")
89 | self.logger.debug("AbsolutePath: %s found in PackagePath: %s" % (abspath, a))
90 | break
91 |
92 | if(not found):
93 | # try to strip the workspace
94 | stripped = abspath.lower().partition(self.WorkspacePath.lower())[2]
95 | if stripped:
96 | # found our path...now lets correct for case
97 | relpath = abspath[len(self.WorkspacePath):]
98 | found = True
99 | self.logger.debug("Successfully converted AbsPath to Edk2Relative Path using WorkspacePath")
100 | self.logger.debug("AbsolutePath: %s found in Workspace: %s" % (abspath, self.WorkspacePath))
101 |
102 | if(found):
103 | relpath = relpath.replace(os.sep, "/")
104 | return relpath.lstrip("/")
105 |
106 | # didn't find the path for conversion.
107 | self.logger.error("Failed to convert AbsPath to Edk2Relative Path")
108 | self.logger.error("AbsolutePath: %s" % abspath)
109 | return None
110 |
111 | def GetAbsolutePathOnThisSytemFromEdk2RelativePath(self, relpath):
112 | if relpath is None:
113 | return None
114 | relpath = relpath.replace("/", os.sep)
115 | abspath = os.path.join(self.WorkspacePath, relpath)
116 | if os.path.exists(abspath):
117 | return abspath
118 |
119 | for a in self.PackagePathList:
120 | abspath = os.path.join(a, relpath)
121 | if(os.path.exists(abspath)):
122 | return abspath
123 | self.logger.error("Failed to convert Edk2Relative Path to an Absolute Path on this system.")
124 | self.logger.error("Relative Path: %s" % relpath)
125 |
126 | return None
127 |
128 | # Find the package this path belongs to using
129 | # some Heuristic. This isn't perfect but at least
130 | # identifies the directory consistently
131 | #
132 | # @param InputPath: absolute path to module
133 | #
134 | # @ret Name of Package that the module is in.
135 | def GetContainingPackage(self, InputPath):
136 | self.logger.debug("GetContainingPackage: %s" % InputPath)
137 |
138 | dirpathprevious = os.path.dirname(InputPath)
139 | dirpath = os.path.dirname(InputPath)
140 | while(dirpath is not None):
141 | #
142 | # if at the root of a packagepath return the previous dir.
143 | # this catches cases where a package has no DEC
144 | #
145 | if(dirpath in self.PackagePathList):
146 | a = os.path.basename(dirpathprevious)
147 | self.logger.debug("Reached Package Path. Using previous directory: %s" % a)
148 | return a
149 | #
150 | # if at the root of the workspace return the previous dir.
151 | # this catches cases where a package has no DEC
152 | #
153 | if(dirpath == self.WorkspacePath):
154 | a = os.path.basename(dirpathprevious)
155 | self.logger.debug("Reached Workspace Path. Using previous directory: %s" % a)
156 | return a
157 | #
158 | # Check for a DEC file in this folder
159 | # if here then return the directory name as the "package"
160 | #
161 | for f in os.listdir(dirpath):
162 | if fnmatch.fnmatch(f, '*.dec'):
163 | a = os.path.basename(dirpath)
164 | self.logger.debug("Found DEC file at %s. Pkg is: %s", dirpath, a)
165 | return a
166 |
167 | dirpathprevious = dirpath
168 | dirpath = os.path.dirname(dirpath)
169 |
170 | self.logger.error("Failed to find containing package for %s" % InputPath)
171 | self.logger.info("PackagePath is: %s" % os.pathsep.join(self.PackagePathList))
172 | self.logger.info("Workspace path is : %s" % self.WorkspacePath)
173 | return None
174 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/PiFirmwareFile.py:
--------------------------------------------------------------------------------
1 | # @file PiFirmwareFile.py
2 | # Module contains helper classes and functions to work with UEFI FFs.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import uuid
29 | import struct
30 | import sys
31 |
32 | #
33 | # EFI_FFS_FILE_HEADER
34 | #
35 | # typedef struct {
36 | # EFI_GUID Name;
37 | # EFI_FFS_INTEGRITY_CHECK IntegrityCheck;
38 | # EFI_FV_FILETYPE Type;
39 | # EFI_FFS_FILE_ATTRIBUTES Attributes;
40 | # UINT8 Size[3];
41 | # EFI_FFS_FILE_STATE State;
42 | # } EFI_FFS_FILE_HEADER;
43 |
44 |
45 | class EfiFirmwareFileSystemHeader(object):
46 | def __init__(self):
47 | self.StructString = "=16sHBBBBBB"
48 | self.FileSystemGuid = None
49 | self.Size0 = None
50 | self.Size1 = None
51 | self.Size2 = None
52 | self.Attributes = None
53 | self.Type = None
54 | self.State = None
55 |
56 | def get_size(self):
57 | return self.Size0 + (self.Size1 << 8) + (self.Size2 << 16)
58 |
59 | def load_from_file(self, file):
60 | orig_seek = file.tell()
61 | struct_bytes = file.read(struct.calcsize(self.StructString))
62 | file.seek(orig_seek)
63 |
64 | # Load this object with the contents of the data.
65 | (self.FileSystemGuid, self.Checksum, self.Type, self.Attributes, self.Size0, self.Size1,
66 | self.Size2, self.State) = struct.unpack(self.StructString, struct_bytes)
67 |
68 | # Update the GUID to be a UUID object.
69 | if sys.byteorder == 'big':
70 | self.FileSystemGuid = uuid.UUID(bytes=self.FileSystemGuid)
71 | else:
72 | self.FileSystemGuid = uuid.UUID(bytes_le=self.FileSystemGuid)
73 |
74 | return self
75 |
76 | def serialize(self):
77 | file_system_guid_bin = self.FileSystemGuid.bytes if sys.byteorder == 'big' else self.FileSystemGuid.bytes_le
78 | return struct.pack(self.StructString, file_system_guid_bin, self.Checksum,
79 | self.Type, self.Attributes, self.Size0, self.Size1, self.Size2, self.State)
80 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/PiFirmwareVolume.py:
--------------------------------------------------------------------------------
1 | # @file PiFirmwareVolume.py
2 | # Module contains helper classes and functions to work with UEFI FVs.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import uuid
29 | import struct
30 | import sys
31 |
32 |
33 | #
34 | # UEFI GUIDs
35 | #
36 | EfiSystemNvDataFvGuid = uuid.UUID(fields=(0xFFF12B8D, 0x7696, 0x4C8B, 0xA9, 0x85, 0x2747075B4F50))
37 |
38 |
39 | #
40 | # UEFI #Defines
41 | #
42 | EFI_FVH_SIGNATURE = b"_FVH"
43 |
44 |
45 | #
46 | # EFI_FIRMWARE_VOLUME_HEADER
47 | # Can parse or produce an EFI_FIRMWARE_VOLUME_HEADER structure/byte buffer.
48 | #
49 | # typedef struct {
50 | # UINT8 ZeroVector[16];
51 | # EFI_GUID FileSystemGuid;
52 | # UINT64 FvLength;
53 | # UINT32 Signature;
54 | # EFI_FVB_ATTRIBUTES_2 Attributes;
55 | # UINT16 HeaderLength;
56 | # UINT16 Checksum;
57 | # UINT16 ExtHeaderOffset;
58 | # UINT8 Reserved[1];
59 | # UINT8 Revision;
60 | # EFI_FV_BLOCK_MAP_ENTRY BlockMap[1];
61 | # } EFI_FIRMWARE_VOLUME_HEADER;
62 | class EfiFirmwareVolumeHeader(object):
63 | def __init__(self):
64 | self.StructString = "=16s16sQ4sLHHHBBQQ"
65 | self.ZeroVector = None
66 | self.FileSystemGuid = None
67 | self.FvLength = None
68 | self.Attributes = None
69 | self.HeaderLength = None
70 | self.Checksum = None
71 | self.ExtHeaderOffset = None
72 | self.Reserved = None
73 | self.Revision = None
74 | self.Blockmap0 = None
75 | self.Blockmap1 = None
76 |
77 | def load_from_file(self, file):
78 | # This function assumes that the file has been seeked
79 | # to the correct starting location.
80 | orig_seek = file.tell()
81 | struct_bytes = file.read(struct.calcsize(self.StructString))
82 | file.seek(orig_seek)
83 |
84 | # Load this object with the contents of the data.
85 | (self.ZeroVector, file_system_guid_bin, self.FvLength, self.Signature, self.Attributes,
86 | self.HeaderLength, self.Checksum, self.ExtHeaderOffset, self.Reserved, self.Revision,
87 | self.Blockmap0, self.Blockmap1) = struct.unpack(self.StructString, struct_bytes)
88 |
89 | # Make sure that this structure is what we think it is.
90 | if self.Signature != EFI_FVH_SIGNATURE:
91 | raise Exception("File does not appear to point to a valid EfiFirmwareVolumeHeader!")
92 |
93 | # Update the GUID to be a UUID object.
94 | if sys.byteorder == 'big':
95 | self.FileSystemGuid = uuid.UUID(bytes=file_system_guid_bin)
96 | else:
97 | self.FileSystemGuid = uuid.UUID(bytes_le=file_system_guid_bin)
98 |
99 | return self
100 |
101 | def serialize(self):
102 | file_system_guid_bin = self.FileSystemGuid.bytes if sys.byteorder == 'big' else self.FileSystemGuid.bytes_le
103 | return struct.pack(self.StructString, self.ZeroVector, file_system_guid_bin, self.FvLength, self.Signature,
104 | self.Attributes, self.HeaderLength, self.Checksum, self.ExtHeaderOffset, self.Reserved,
105 | self.Revision, self.Blockmap0, self.Blockmap1)
106 | #
107 | # EFI_FIRMWARE_VOLUME_EXT_HEADER
108 | # Can parse or produce an EFI_FIRMWARE_VOLUME_EXT_HEADER structure/byte buffer.
109 | #
110 | # typedef struct {
111 | # EFI_GUID FileSystemGuid;
112 | # UINT32 ExtHeaderSize;
113 | # } EFI_FIRMWARE_VOLUME_EXT_HEADER;
114 |
115 |
116 | class EfiFirmwareVolumeExtHeader(object):
117 | def __init__(self):
118 | self.StructString = "=16sL"
119 | self.FileSystemGuid = None
120 | self.ExtHeaderSize = None
121 |
122 | def load_from_file(self, file):
123 | # This function assumes that the file has been seeked
124 | # to the correct starting location.
125 | orig_seek = file.tell()
126 | struct_bytes = file.read(struct.calcsize(self.StructString))
127 | file.seek(orig_seek)
128 |
129 | # Load this object with the contents of the data.
130 | (self.FileSystemGuid, self.ExtHeaderSize) = struct.unpack(self.StructString, struct_bytes)
131 |
132 | return self
133 |
134 |
135 | if __name__ == '__main__':
136 | pass
137 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/VariableFormat.py:
--------------------------------------------------------------------------------
1 | # @file VariableFormat.py
2 | # Module contains helper classes and functions to work with UEFI Variables.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import uuid
29 | import struct
30 | import sys
31 | import MuPythonLibrary.Uefi.UefiMultiPhase as ump
32 |
33 | #
34 | # UEFI GUIDs
35 | #
36 | EfiVariableGuid = uuid.UUID(fields=(0xDDCF3616, 0x3275, 0x4164, 0x98, 0xB6, 0xFE85707FFE7D))
37 | EfiAuthenticatedVariableGuid = uuid.UUID(fields=(0xAAF32C78, 0x947B, 0x439A, 0xA1, 0x80, 0x2E144EC37792))
38 |
39 | #
40 | # UEFI #Defines
41 | #
42 | HEADER_ALIGNMENT = 4
43 | VARIABLE_STORE_FORMATTED = 0x5A
44 | VARIABLE_STORE_HEALTHY = 0xFE
45 | VARIABLE_DATA = 0x55AA
46 | VAR_IN_DELETED_TRANSITION = 0xFE # Variable is in obsolete transition.
47 | VAR_DELETED = 0xFD # Variable is obsolete.
48 | VAR_HEADER_VALID_ONLY = 0x7F # Variable header has been valid.
49 | VAR_ADDED = 0x3F # Variable has been completely added.
50 |
51 | #
52 | # VARIABLE_STORE_HEADER
53 | # Can parse or produce an VARIABLE_STORE_HEADER structure/byte buffer.
54 | #
55 | # typedef struct {
56 | # EFI_GUID Signature;
57 | # UINT32 Size;
58 | # UINT8 Format;
59 | # UINT8 State;
60 | # UINT16 Reserved;
61 | # UINT32 Reserved1;
62 | # } VARIABLE_STORE_HEADER;
63 |
64 |
65 | class VariableStoreHeader(object):
66 | def __init__(self):
67 | self.StructString = "=16sLBBHL"
68 | self.StructSize = struct.calcsize(self.StructString)
69 | self.Signature = None
70 | self.Size = None
71 | self.Format = None
72 | self.State = None
73 | self.Reserved0 = None
74 | self.Reserved1 = None
75 | self.Type = 'Var'
76 |
77 | def load_from_file(self, file):
78 | # This function assumes that the file has been seeked
79 | # to the correct starting location.
80 | orig_seek = file.tell()
81 | struct_bytes = file.read(struct.calcsize(self.StructString))
82 | file.seek(orig_seek)
83 |
84 | # Load this object with the contents of the data.
85 | (signature_bin, self.Size, self.Format, self.State, self.Reserved0,
86 | self.Reserved1) = struct.unpack(self.StructString, struct_bytes)
87 |
88 | # Update the GUID to be a UUID object.
89 | if sys.byteorder == 'big':
90 | self.Signature = uuid.UUID(bytes=signature_bin)
91 | else:
92 | self.Signature = uuid.UUID(bytes_le=signature_bin)
93 |
94 | # Check one last thing.
95 | if self.Signature != EfiVariableGuid and self.Signature != EfiAuthenticatedVariableGuid:
96 | raise Exception("VarStore is of unknown type! %s" % self.Signature)
97 | if self.Signature == EfiAuthenticatedVariableGuid:
98 | self.Type = 'AuthVar'
99 |
100 | return self
101 |
102 | def serialize(self):
103 | signature_bin = self.Signature.bytes if sys.byteorder == 'big' else self.Signature.bytes_le
104 | return struct.pack(self.StructString, signature_bin, self.Size, self.Format,
105 | self.State, self.Reserved0, self.Reserved1)
106 |
107 | #
108 | # TODO: VariableHeader and AuthenticatedVariableHeader are not truly
109 | # header structures. They're entire variables. This code should be
110 | # cleaned up.
111 | #
112 |
113 | #
114 | # VARIABLE_HEADER
115 | # Can parse or produce an VARIABLE_HEADER structure/byte buffer.
116 | #
117 | # typedef struct {
118 | # UINT16 StartId;
119 | # UINT8 State;
120 | # UINT8 Reserved;
121 | # UINT32 Attributes;
122 | # UINT32 NameSize;
123 | # UINT32 DataSize;
124 | # EFI_GUID VendorGuid;
125 | # } VARIABLE_HEADER;
126 |
127 |
128 | class VariableHeader(object):
129 | def __init__(self):
130 | self.StructString = "=HBBLLL16s"
131 | self.StructSize = struct.calcsize(self.StructString)
132 | self.StartId = VARIABLE_DATA
133 | self.State = VAR_ADDED
134 | self.Attributes = (ump.EFI_VARIABLE_NON_VOLATILE | ump.EFI_VARIABLE_BOOTSERVICE_ACCESS)
135 | self.NameSize = 0
136 | self.DataSize = 0
137 | self.VendorGuid = uuid.uuid4()
138 | self.Name = None
139 | self.Data = None
140 |
141 | def populate_structure_fields(self, in_bytes):
142 | (self.StartId, self.State, reserved, self.Attributes, self.NameSize,
143 | self.DataSize, self.VendorGuid) = struct.unpack(self.StructString, in_bytes)
144 |
145 | def load_from_bytes(self, in_bytes):
146 | # Load this object with the contents of the data.
147 | self.populate_structure_fields(in_bytes[0:self.StructSize])
148 |
149 | # Update the GUID to be a UUID object.
150 | if sys.byteorder == 'big':
151 | self.VendorGuid = uuid.UUID(bytes=self.VendorGuid)
152 | else:
153 | self.VendorGuid = uuid.UUID(bytes_le=self.VendorGuid)
154 |
155 | # Before loading data, make sure that this is a valid variable.
156 | if self.StartId != VARIABLE_DATA:
157 | raise EOFError("No variable data!")
158 |
159 | # Finally, load the data.
160 | data_offset = self.StructSize
161 | self.Name = in_bytes[data_offset:(data_offset + self.NameSize)].decode('utf-16')
162 | self.Name = self.Name[:-1] # Strip the terminating char.
163 | data_offset += self.NameSize
164 | self.Data = in_bytes[data_offset:(data_offset + self.DataSize)]
165 |
166 | return self
167 |
168 | def load_from_file(self, file):
169 | # This function assumes that the file has been seeked
170 | # to the correct starting location.
171 | orig_seek = file.tell()
172 | struct_bytes = file.read(struct.calcsize(self.StructString))
173 |
174 | # Load this object with the contents of the data.
175 | self.populate_structure_fields(struct_bytes)
176 |
177 | # Update the GUID to be a UUID object.
178 | if sys.byteorder == 'big':
179 | self.VendorGuid = uuid.UUID(bytes=self.VendorGuid)
180 | else:
181 | self.VendorGuid = uuid.UUID(bytes_le=self.VendorGuid)
182 |
183 | # Before loading data, make sure that this is a valid variable.
184 | if self.StartId != VARIABLE_DATA:
185 | file.seek(orig_seek)
186 | raise EOFError("No variable data!")
187 |
188 | # Finally, load the data.
189 | self.Name = file.read(self.NameSize).decode('utf-16')[:-1] # Strip the terminating char.
190 | self.Data = file.read(self.DataSize)
191 |
192 | file.seek(orig_seek)
193 | return self
194 |
195 | def get_buffer_data_size(self):
196 | return self.StructSize + self.NameSize + self.DataSize
197 |
198 | def get_buffer_padding_size(self):
199 | buffer_data_size = self.get_buffer_data_size()
200 | padding_size = 0
201 | if buffer_data_size % HEADER_ALIGNMENT != 0:
202 | padding_size += HEADER_ALIGNMENT - (buffer_data_size % HEADER_ALIGNMENT)
203 | return padding_size
204 |
205 | def get_buffer_size(self):
206 | return self.get_buffer_data_size() + self.get_buffer_padding_size()
207 |
208 | def get_packed_name(self):
209 | # Make sure to replace the terminating char.
210 | # name_bytes = b"\x00".join([char for char in (self.Name + b'\x00')])
211 | name_bytes = self.Name.encode('utf-16')
212 |
213 | # Python encode will leave an "0xFFFE" on the front
214 | # to declare the encoding type. UEFI does not use this.
215 | name_bytes = name_bytes[2:]
216 |
217 | # Python encode skips the terminating character, so let's add that.
218 | name_bytes += b"\x00\x00"
219 |
220 | return name_bytes
221 |
222 | def set_name(self, new_name):
223 | self.Name = new_name
224 | self.NameSize = len(self.get_packed_name())
225 |
226 | def set_data(self, new_data):
227 | self.Data = new_data
228 | self.DataSize = len(new_data)
229 |
230 | def pack_struct(self):
231 | vendor_guid = self.VendorGuid.bytes if sys.byteorder == 'big' else self.VendorGuid.bytes_le
232 | return struct.pack(self.StructString, self.StartId, self.State, 0, self.Attributes,
233 | self.NameSize, self.DataSize, vendor_guid)
234 |
235 | def serialize(self, with_padding=False):
236 | bytes = self.pack_struct()
237 |
238 | # Now add the name and data.
239 | bytes += self.get_packed_name()
240 | bytes += self.Data
241 |
242 | # Add padding if necessary.
243 | if with_padding:
244 | bytes += b"\xFF" * self.get_buffer_padding_size()
245 |
246 | return bytes
247 |
248 | #
249 | # AUTHENTICATED_VARIABLE_HEADER
250 | # Can parse or produce an AUTHENTICATED_VARIABLE_HEADER structure/byte buffer.
251 | #
252 | # typedef struct {
253 | # UINT16 StartId;
254 | # UINT8 State;
255 | # UINT8 Reserved;
256 | # UINT32 Attributes;
257 | # UINT64 MonotonicCount;
258 | # EFI_TIME TimeStamp;
259 | # UINT32 PubKeyIndex;
260 | # UINT32 NameSize;
261 | # UINT32 DataSize;
262 | # EFI_GUID VendorGuid;
263 | # } AUTHENTICATED_VARIABLE_HEADER;
264 |
265 |
266 | class AuthenticatedVariableHeader(VariableHeader):
267 | def __init__(self):
268 | super(AuthenticatedVariableHeader, self).__init__()
269 | self.StructString = "=HBBLQ16sLLL16s"
270 | self.StructSize = struct.calcsize(self.StructString)
271 | self.MonotonicCount = 0
272 | self.TimeStamp = b''
273 | self.PubKeyIndex = 0
274 |
275 | def populate_structure_fields(self, in_bytes):
276 | (self.StartId, self.State, reserved, self.Attributes, self.MonotonicCount, self.TimeStamp, self.PubKeyIndex,
277 | self.NameSize, self.DataSize, self.VendorGuid) = struct.unpack(self.StructString, in_bytes)
278 |
279 | def pack_struct(self, with_padding=False):
280 | vendor_guid = self.VendorGuid.bytes if sys.byteorder == 'big' else self.VendorGuid.bytes_le
281 | return struct.pack(self.StructString, self.StartId, self.State, 0, self.Attributes, self.MonotonicCount,
282 | self.TimeStamp, self.PubKeyIndex, self.NameSize, self.DataSize, vendor_guid)
283 |
284 |
285 | if __name__ == '__main__':
286 | pass
287 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/VariableFormat_Test.py:
--------------------------------------------------------------------------------
1 | # @file VariableFormat_Test.py
2 | # Unit test harness for the VariableFormat module/classes.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import unittest
29 | import MuPythonLibrary.Uefi.EdkII.VariableFormat as VF
30 |
31 |
32 | class TestVariableHeader(unittest.TestCase):
33 |
34 | def test_set_name(self):
35 | var = VF.VariableHeader()
36 |
37 | test_name = "MyNewName"
38 | var.set_name(test_name)
39 |
40 | self.assertEqual(var.Name, test_name)
41 |
42 | def test_get_packed_name(self):
43 | var = VF.VariableHeader()
44 |
45 | test_name = "MyNewName"
46 | var.set_name(test_name)
47 |
48 | test_name_packed = bytes.fromhex('4D0079004E00650077004E0061006D0065000000')
49 | self.assertEqual(var.get_packed_name(), test_name_packed)
50 |
51 |
52 | if __name__ == '__main__':
53 | unittest.main()
54 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/EdkII/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/Uefi/EdkII/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/FtwWorkingBlockFormat.py:
--------------------------------------------------------------------------------
1 | # @file FtwWorkingBlockFormat.py
2 | # Module contains helper classes and functions to work with UEFI Variables.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import struct
29 | import uuid
30 | import sys
31 |
32 | #
33 | # UEFI GUIDs
34 | #
35 | EdkiiWorkingBlockSignatureGuid = uuid.UUID(fields=(0x9E58292B, 0x7C68, 0x497D, 0xA0, 0xCE, 0x6500FD9F1B95))
36 |
37 | #
38 | # The EDKII Fault tolerant working block header.
39 | # The header is immediately followed by the write queue data.
40 | #
41 | # typedef struct {
42 | # EFI_GUID Signature;
43 | # UINT32 Crc;
44 | # UINT8 WorkingBlockValid : 1;
45 | # UINT8 WorkingBlockInvalid : 1;
46 | # UINT8 Reserved : 6;
47 | # UINT8 Reserved3[3];
48 | # UINT64 WriteQueueSize;
49 | # } EFI_FAULT_TOLERANT_WORKING_BLOCK_HEADER;
50 |
51 |
52 | class EfiFtwWorkingBlockHeader(object):
53 | def __init__(self):
54 | self.StructString = "=16sLBBBBQ"
55 | self.Signature = None
56 | self.Crc = None
57 | self.WorkingBlockValidFields = None
58 | self.Reserved1 = None
59 | self.Reserved2 = None
60 | self.Reserved3 = None
61 | self.WriteQueueSize = None
62 |
63 | def load_from_file(self, file):
64 | # This function assumes that the file has been seeked
65 | # to the correct starting location.
66 | orig_seek = file.tell()
67 | struct_bytes = file.read(struct.calcsize(self.StructString))
68 | file.seek(orig_seek)
69 |
70 | # Load this object with the contents of the data.
71 | (signature_bin, self.Crc, self.WorkingBlockValidFields, self.Reserved1, self.Reserved2, self.Reserved3,
72 | self.WriteQueueSize) = struct.unpack(self.StructString, struct_bytes)
73 |
74 | # Update the GUID to be a UUID object.
75 | if sys.byteorder == 'big':
76 | self.Signature = uuid.UUID(bytes=signature_bin)
77 | else:
78 | self.Signature = uuid.UUID(bytes_le=signature_bin)
79 |
80 | # Check that signature is valid
81 | if self.Signature != EdkiiWorkingBlockSignatureGuid:
82 | raise Exception("FTW Working Block Header has unknown signature: %s" % self.Signature)
83 |
84 | return self
85 |
86 | def serialize(self):
87 | signature_bin = self.Signature.bytes if sys.byteorder == 'big' else self.Signature.bytes_le
88 | return struct.pack(self.StructString, signature_bin, self.Crc, self.WorkingBlockValidFields,
89 | self.Reserved1, self.Reserved2, self.Reserved3, self.WriteQueueSize)
90 |
91 | #
92 | # EFI Fault tolerant block update write queue entry.
93 | #
94 | # typedef struct {
95 | # UINT8 HeaderAllocated : 1;
96 | # UINT8 WritesAllocated : 1;
97 | # UINT8 Complete : 1;
98 | # UINT8 Reserved : 5;
99 | # EFI_GUID CallerId;
100 | # UINT64 NumberOfWrites;
101 | # UINT64 PrivateDataSize;
102 | # } EFI_FAULT_TOLERANT_WRITE_HEADER;
103 |
104 |
105 | class EfiFtwWriteHeader(object):
106 | def __init__(self):
107 | self.StructString = "=BBBB16sLQQ"
108 | self.StatusBits = None
109 | self.ReservedByte1 = None
110 | self.ReservedByte2 = None
111 | self.ReservedByte3 = None
112 | self.CallerId = None
113 | self.ReservedUint32 = None
114 | self.NumberOfWrites = None
115 | self.PrivateDataSize = None
116 |
117 | def load_from_file(self, file):
118 | # This function assumes that the file has been seeked
119 | # to the correct starting location.
120 | orig_seek = file.tell()
121 | struct_bytes = file.read(struct.calcsize(self.StructString))
122 | file.seek(orig_seek)
123 |
124 | # Load this object with the contents of the data.
125 | (self.StatusBits, self.ReservedByte1, self.ReservedByte2, self.ReservedByte3, self.CallerId,
126 | self.ReservedUint32, self.NumberOfWrites, self.PrivateDataSize) = struct.unpack(self.StructString,
127 | struct_bytes)
128 |
129 | return self
130 |
131 | def serialize(self):
132 | return struct.pack(self.StructString, self.StatusBits, self.ReservedByte1, self.ReservedByte2,
133 | self.ReservedByte3, self.CallerId, self.ReservedUint32, self.NumberOfWrites,
134 | self.PrivateDataSize)
135 |
136 | #
137 | # EFI Fault tolerant block update write queue record.
138 | #
139 | # typedef struct {
140 | # UINT8 BootBlockUpdate : 1;
141 | # UINT8 SpareComplete : 1;
142 | # UINT8 DestinationComplete : 1;
143 | # UINT8 Reserved : 5;
144 | # EFI_LBA Lba;
145 | # UINT64 Offset;
146 | # UINT64 Length;
147 | # INT64 RelativeOffset;
148 | # } EFI_FAULT_TOLERANT_WRITE_RECORD;
149 |
150 |
151 | class EfiFtwWriteRecord(object):
152 | def __init__(self):
153 | self.StructString = "=BBBBLQQQQ"
154 | self.StatusBits = None
155 | self.ReservedByte1 = None
156 | self.ReservedByte2 = None
157 | self.ReservedByte3 = None
158 | self.ReservedUint32 = None
159 | self.Lba = None
160 | self.Offset = None
161 | self.Length = None
162 | self.RelativeOffset = None
163 |
164 | def load_from_file(self, file):
165 | # This function assumes that the file has been seeked
166 | # to the correct starting location.
167 | orig_seek = file.tell()
168 | struct_bytes = file.read(struct.calcsize(self.StructString))
169 | file.seek(orig_seek)
170 |
171 | # Load this object with the contents of the data.
172 | (self.StatusBits, self.ReservedByte1, self.ReservedByte2, self.ReservedByte3, self.ReservedUint32, self.Lba,
173 | self.Offset, self.Length, self.RelativeOffset) = struct.unpack(self.StructString, struct_bytes)
174 |
175 | return self
176 |
177 | def serialize(self):
178 | return struct.pack(self.StructString, self.StatusBits, self.ReservedByte1, self.ReservedByte2,
179 | self.ReservedByte3, self.ReservedUint32, self.Lba, self.Offset, self.Length,
180 | self.RelativeOffset)
181 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/UefiMultiPhase.py:
--------------------------------------------------------------------------------
1 | # @file UefiMultiPhase.py
2 | # Module contains defintions and structures from the UefiMultiPhase header file.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | #
29 | # UEFI #Defines
30 | #
31 | EFI_VARIABLE_NON_VOLATILE = 0x00000001
32 | EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x00000002
33 | EFI_VARIABLE_RUNTIME_ACCESS = 0x00000004
34 | EFI_VARIABLE_HARDWARE_ERROR_RECORD = 0x00000008
35 | EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x00000010
36 | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x00000020
37 | EFI_VARIABLE_APPEND_WRITE = 0x00000040
38 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/UefiStatusCode.py:
--------------------------------------------------------------------------------
1 | # @file UefiStatusCode.py
2 | # Code to help convert an Int to StatusCode string
3 | ##
4 | # Copyright (c) 2016, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 |
28 |
29 | ##
30 | # UefiStatusCode
31 | ##
32 | class UefiStatusCode(object):
33 | # string Array
34 | StatusCodeStrings = ["Success", "Load Error", "Invalid Parameter", "Unsupported", "Bad BufferSize",
35 | "Buffer Too Small", "Not Ready", "Device Error", "Write Protected", "Out of Resources",
36 | "Volume Corrupt", "Volume Full", "No Media", "Media Changed", "Not Found", "Access Denied",
37 | "No Response", "No Mapping", "Time Out", "Not Started", "Already Started", "Aborted",
38 | "ICMP Error", "TFTP Error", "Protocol Error", "Incompatible Error", "Security Violation",
39 | "CRC Error", "End of Media", "Reserved(29)", "Reserved(30)", "End of File",
40 | "Invalid Language", "Compromised Data"]
41 |
42 | def Convert32BitToString(self, i):
43 | # convert a 32bit value to string
44 | return UefiStatusCode.StatusCodeStrings[(i & 0xFFF)]
45 |
46 | def Convert64BitToString(self, l):
47 | if(l > len(UefiStatusCode.StatusCodeStrings)):
48 | return ""
49 | return UefiStatusCode.StatusCodeStrings[(l & 0xFFF)]
50 |
51 | def ConvertHexString64ToString(self, hexstring):
52 | value = int(hexstring, 16)
53 | return self.Convert64BitToString(value)
54 |
55 | def ConvertHexString32ToString(self, hexstring):
56 | value = int(hexstring, 16)
57 | return self.Convert32BitToString(value)
58 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/VariableStoreManipulation.py:
--------------------------------------------------------------------------------
1 | # @file VariableStoreManipulation.py
2 | # Contains classes and helper functions to modify variables in a UEFI ROM image.
3 | #
4 | ##
5 | # Copyright (c) 2017, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import MuPythonLibrary.Uefi.EdkII.PiFirmwareVolume as PiFV
29 | import MuPythonLibrary.Uefi.EdkII.VariableFormat as VF
30 |
31 | import os
32 | import mmap
33 |
34 |
35 | class VariableStore(object):
36 | def __init__(self, romfile, store_base=None, store_size=None):
37 | self.rom_file_path = romfile
38 | self.store_base = store_base
39 | self.store_size = store_size
40 | self.rom_file = None
41 | self.rom_file_map = None
42 |
43 | if not os.path.isfile(self.rom_file_path):
44 | raise Exception("'%s' is not the path to a file!" % self.rom_file_path)
45 |
46 | self.rom_file = open(self.rom_file_path, 'r+b')
47 | self.rom_file_map = mmap.mmap(self.rom_file.fileno(), 0)
48 |
49 | # Sanity check some things.
50 | file_size = self.rom_file_map.size()
51 | if (store_base is not None and store_size is not None and (store_base + store_size) > file_size):
52 | raise Exception("ROM file is %d bytes. Cannot seek to %d+%d bytes!" % (file_size, store_base, store_size))
53 |
54 | # Go ahead and advance the file cursor and load the FV header.
55 | self.rom_file.seek(self.store_base)
56 | self.fv_header = PiFV.EfiFirmwareVolumeHeader().load_from_file(self.rom_file)
57 | if self.fv_header.FileSystemGuid != PiFV.EfiSystemNvDataFvGuid:
58 | raise Exception("Store_base is not pointing at a valid SystemNvData FV!")
59 | if self.fv_header.FvLength != self.store_size:
60 | raise Exception("Store_size %d does not match FV size %d!" % (self.store_size, self.fv_header.FvLength))
61 |
62 | # Advance the file cursor and load the VarStore header.
63 | self.rom_file.seek(self.fv_header.HeaderLength, os.SEEK_CUR)
64 | self.var_store_header = VF.VariableStoreHeader().load_from_file(self.rom_file)
65 | if self.var_store_header.Format != VF.VARIABLE_STORE_FORMATTED or \
66 | self.var_store_header.State != VF.VARIABLE_STORE_HEALTHY:
67 | raise Exception("VarStore is invalid or cannot be processed with this helper!")
68 |
69 | # Now we're finally ready to read some variables.
70 | self.variables = []
71 | self.rom_file.seek(self.var_store_header.StructSize, os.SEEK_CUR)
72 | try:
73 | while True:
74 | new_var = self.get_new_var_class().load_from_file(self.rom_file)
75 |
76 | # Seek past the current variable in the store.
77 | self.rom_file.seek(new_var.get_buffer_size(), os.SEEK_CUR)
78 |
79 | # Add the variable to the array.
80 | self.variables.append(new_var)
81 | except EOFError:
82 | pass
83 | except:
84 | raise
85 |
86 | # Finally, reset the file cursor to the beginning of the VarStore FV.
87 | self.rom_file.seek(self.store_base)
88 |
89 | def __del__(self):
90 | if self.rom_file_map is not None:
91 | self.rom_file_map.flush()
92 | self.rom_file_map.close()
93 |
94 | if self.rom_file is not None:
95 | self.rom_file.close()
96 |
97 | def get_new_var_class(self):
98 | if self.var_store_header.Type == 'Var':
99 | new_var = VF.VariableHeader()
100 | else:
101 | new_var = VF.AuthenticatedVariableHeader()
102 |
103 | return new_var
104 |
105 | def add_variable(self, new_var):
106 | self.variables.append(new_var)
107 |
108 | def flush_to_file(self):
109 | # First, we need to make sure that our variables will fit in the VarStore.
110 | var_size = sum([var.get_buffer_size() for var in self.variables])
111 | # Add the terminating var header.
112 | dummy_var = self.get_new_var_class()
113 | var_size += dummy_var.StructSize
114 | if var_size > self.var_store_header.Size:
115 | raise Exception("Total variable size %d is too large to fit in VarStore %d!" %
116 | (var_size, self.var_store_header.Size))
117 |
118 | # Now, we just have to serialize each variable in turn and write them to the mmap buffer.
119 | var_offset = self.store_base + self.fv_header.HeaderLength + self.var_store_header.StructSize
120 | for var in self.variables:
121 | var_buffer_size = var.get_buffer_size()
122 | self.rom_file_map[var_offset:(var_offset + var_buffer_size)] = var.serialize(True)
123 | var_offset += var_buffer_size
124 |
125 | # Add a terminating Variable Header.
126 | self.rom_file_map[var_offset:(var_offset + dummy_var.StructSize)] = b'\xFF' * dummy_var.StructSize
127 |
128 | # Now we have to flush the mmap to the file.
129 | self.rom_file_map.flush()
130 |
131 |
132 | if __name__ == '__main__':
133 | pass
134 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/WinCert.py:
--------------------------------------------------------------------------------
1 | # @file WinCert.py
2 | # Code to work with UEFI WinCert data
3 | ##
4 | # Copyright (c) 2015, Microsoft Corporation
5 | #
6 | # All rights reserved.
7 | # Redistribution and use in source and binary forms, with or without
8 | # modification, are permitted provided that the following conditions are met:
9 | # 1. Redistributions of source code must retain the above copyright notice,
10 | # this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright notice,
12 | # this list of conditions and the following disclaimer in the documentation
13 | # and/or other materials provided with the distribution.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
19 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
20 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 | ##
26 | ###
27 |
28 |
29 | import struct
30 | import uuid
31 | from MuPythonLibrary.UtilityFunctions import PrintByteList
32 |
33 |
34 | class WinCertPkcs1(object):
35 | STATIC_STRUCT_SIZE = (4 + 2 + 2 + 16)
36 | EFI_HASH_SHA256 = uuid.UUID("{51AA59DE-FDF2-4EA3-BC63-875FB7842EE9}") # EFI_HASH_SHA256 guid defined by UEFI spec
37 |
38 | def __init__(self, filestream=None):
39 | if(filestream is None):
40 | self.Hdr_dwLength = WinCertPkcs1.STATIC_STRUCT_SIZE
41 | self.Hdr_wRevision = WinCert.REVISION
42 | self.Hdr_wCertificateType = WinCert.WIN_CERT_TYPE_EFI_PKCS115
43 | self.HashAlgorithm = None
44 | self.CertData = None
45 | else:
46 | self.PopulateFromFileStream(filestream)
47 |
48 | def AddCertData(self, fs):
49 | if(self.CertData is not None):
50 | raise Exception("Cert Data not 0")
51 | if(self.HashAlgorithm is None):
52 | raise Exception("You must set the Hash Algorithm first")
53 | self.CertData = fs.read()
54 | self.Hdr_dwLength = self.Hdr_dwLength + len(self.CertData)
55 | #
56 | # Method to un-serialize from a filestream
57 | #
58 |
59 | def PopulateFromFileStream(self, fs):
60 | if(fs is None):
61 | raise Exception("Invalid File stream")
62 |
63 | # only populate from file stream those parts that are complete in the file stream
64 | offset = fs.tell()
65 | fs.seek(0, 2)
66 | end = fs.tell()
67 | fs.seek(offset)
68 |
69 | if((end - offset) < WinCertPkcs1.STATIC_STRUCT_SIZE): # size of the static header data
70 | raise Exception("Invalid file stream size")
71 |
72 | self.Hdr_dwLength = struct.unpack("=I", fs.read(4))[0]
73 | self.Hdr_wRevision = struct.unpack("=H", fs.read(2))[0]
74 | self.Hdr_wCertificateType = struct.unpack("=H", fs.read(2))[0]
75 | self.HashAlgorithm = uuid.UUID(bytes_le=fs.read(16))
76 | self.CertData = None
77 |
78 | if((end - fs.tell()) < 1):
79 | raise Exception("Invalid File stream. No data for signature cert data")
80 |
81 | if((end - fs.tell()) < (self.Hdr_dwLength - WinCertPkcs1.STATIC_STRUCT_SIZE)):
82 | raise Exception("Invalid file stream size")
83 |
84 | self.CertData = memoryview(fs.read(self.Hdr_dwLength - WinCertPkcs1.STATIC_STRUCT_SIZE))
85 |
86 | def Print(self):
87 | print("WinCertPKCS115")
88 | print(" Hdr_dwLength: 0x%X" % self.Hdr_dwLength)
89 | print(" Hdr_wRevision: 0x%X" % self.Hdr_wRevision)
90 | print(" Hdr_wCertificateType: 0x%X" % self.Hdr_wCertificateType)
91 | print(" Hash Guid: %s" % str(self.HashAlgorithm))
92 | print(" CertData: ")
93 | cdl = self.CertData.tolist()
94 | PrintByteList(cdl)
95 |
96 | def Write(self, fs):
97 | fs.write(struct.pack("=I", self.Hdr_dwLength))
98 | fs.write(struct.pack("=H", self.Hdr_wRevision))
99 | fs.write(struct.pack("=H", self.Hdr_wCertificateType))
100 | fs.write(self.HashAlgorithm.bytes_le)
101 | fs.write(self.CertData)
102 |
103 | ##
104 | # WIN_CERT_UEFI_GUID
105 | ##
106 |
107 |
108 | class WinCertUefiGuid(object):
109 | STATIC_STRUCT_SIZE = (4 + 2 + 2 + 16)
110 | PKCS7Guid = uuid.UUID("{4aafd29d-68df-49ee-8aa9-347d375665a7}") # PKCS7 guid defined by UEFI spec
111 |
112 | def __init__(self, filestream=None):
113 | if(filestream is None):
114 | self.Hdr_dwLength = WinCertUefiGuid.STATIC_STRUCT_SIZE
115 | self.Hdr_wRevision = WinCert.REVISION
116 | self.Hdr_wCertificateType = WinCert.WIN_CERT_TYPE_EFI_GUID
117 | self.CertType = WinCertUefiGuid.PKCS7Guid
118 | self.CertData = None
119 | else:
120 | self.PopulateFromFileStream(filestream)
121 |
122 | def AddCertData(self, fs):
123 | if(self.CertData is not None):
124 | raise Exception("Cert Data not 0")
125 | self.CertData = memoryview(fs.read())
126 | self.Hdr_dwLength = self.Hdr_dwLength + len(self.CertData)
127 | #
128 | # Method to un-serialize from a filestream
129 | #
130 |
131 | def PopulateFromFileStream(self, fs):
132 | if(fs is None):
133 | raise Exception("Invalid File stream")
134 |
135 | # only populate from file stream those parts that are complete in the file stream
136 | offset = fs.tell()
137 | fs.seek(0, 2)
138 | end = fs.tell()
139 | fs.seek(offset)
140 |
141 | if((end - offset) < WinCertUefiGuid.STATIC_STRUCT_SIZE): # size of the static header data
142 | raise Exception("Invalid file stream size")
143 |
144 | self.Hdr_dwLength = struct.unpack("=I", fs.read(4))[0]
145 | self.Hdr_wRevision = struct.unpack("=H", fs.read(2))[0]
146 | self.Hdr_wCertificateType = struct.unpack("=H", fs.read(2))[0]
147 | self.CertType = uuid.UUID(bytes_le=fs.read(16))
148 | self.CertData = None
149 |
150 | if((end - fs.tell()) < 1):
151 | raise Exception("Invalid File stream. No data for signature cert data")
152 |
153 | if((end - fs.tell()) < (self.Hdr_dwLength - WinCertUefiGuid.STATIC_STRUCT_SIZE)):
154 | raise Exception("Invalid file stream size ")
155 |
156 | self.CertData = memoryview(fs.read(self.Hdr_dwLength - WinCertUefiGuid.STATIC_STRUCT_SIZE))
157 |
158 | def Print(self):
159 | print("WinCertUefiGuid")
160 | print(" Hdr_dwLength: 0x%X" % self.Hdr_dwLength)
161 | print(" Hdr_wRevision: 0x%X" % self.Hdr_wRevision)
162 | print(" Hdr_wCertificateType: 0x%X" % self.Hdr_wCertificateType)
163 | print(" CertType: %s" % str(self.CertType))
164 | print(" CertData: ")
165 | cdl = self.CertData.tolist()
166 | PrintByteList(cdl)
167 |
168 | def Write(self, fs):
169 | fs.write(struct.pack("=I", self.Hdr_dwLength))
170 | fs.write(struct.pack("=H", self.Hdr_wRevision))
171 | fs.write(struct.pack("=H", self.Hdr_wCertificateType))
172 | fs.write(self.CertType.bytes_le)
173 | fs.write(self.CertData)
174 |
175 |
176 | class WinCert(object):
177 | STATIC_STRUCT_SIZE = 8
178 | # WIN_CERTIFICATE.wCertificateTypes UEFI Spec defined
179 | WIN_CERT_TYPE_NONE = 0x0000
180 | WIN_CERT_TYPE_PKCS_SIGNED_DATA = 0x0002
181 | WIN_CERT_TYPE_EFI_PKCS115 = 0x0EF0
182 | WIN_CERT_TYPE_EFI_GUID = 0x0EF1
183 | # Revision
184 | REVISION = 0x200
185 |
186 | #
187 | # this method is a factory
188 | #
189 | @staticmethod
190 | def Factory(fs):
191 | if(fs is None):
192 | raise Exception("Invalid File stream")
193 |
194 | # only populate from file stream those parts that are complete in the file stream
195 | offset = fs.tell()
196 | fs.seek(0, 2)
197 | end = fs.tell()
198 | fs.seek(offset)
199 |
200 | if((end - offset) < WinCert.STATIC_STRUCT_SIZE): # size of the static header data
201 | raise Exception("Invalid file stream size")
202 | # 1 read len
203 | # 2 read revision
204 | # 3 read cert type
205 | fs.seek(4, 1) # seeking past Hdr_dwLength
206 | fs.seek(2, 1) # seeking past Hdr_wRevision
207 | Hdr_wCertificateType = struct.unpack("=H", fs.read(2))[0]
208 |
209 | fs.seek(offset)
210 |
211 | if(Hdr_wCertificateType == WinCert.WIN_CERT_TYPE_EFI_GUID):
212 | return WinCertUefiGuid(fs)
213 | elif(Hdr_wCertificateType == WinCert.WIN_CERT_TYPE_EFI_PKCS115):
214 | return WinCertPkcs1(fs)
215 | else:
216 | return None
217 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Uefi/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/Uefi/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/Windows/VsWhereUtilities.py:
--------------------------------------------------------------------------------
1 | # @file VsWhereUtilities.py
2 | # This module contains code that knows how to find VsWhere
3 | # as well as grab variables from the VS environment
4 | #
5 | # Copyright (c), Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import pkg_resources
29 | import os
30 | import logging
31 | import glob
32 | import subprocess
33 | from MuPythonLibrary.UtilityFunctions import RunCmd
34 | from MuPythonLibrary.UtilityFunctions import GetHostInfo
35 | import re
36 | try:
37 | from StringIO import StringIO
38 | except ImportError:
39 | from io import StringIO
40 | import urllib.error
41 | import urllib.request
42 |
43 | # Update this when you want a new version of VsWhere
44 | __VERSION = "2.6.7"
45 | __URL = "https://github.com/microsoft/vswhere/releases/download/{}/vswhere.exe".format(__VERSION)
46 | __SHA256 = "10abd21aeb5003d87c01f033fd7c170360e362be397f23b0b730324abbd92612"
47 |
48 |
49 | # Downloads VSWhere
50 | def _DownloadVsWhere(unpack_folder=None):
51 | if unpack_folder is None:
52 | unpack_folder = os.path.dirname(__VsWherePath())
53 |
54 | out_file_name = os.path.join(unpack_folder, "vswhere.exe")
55 | logging.info("Attempting to download vswhere to: {}. This may take a second.".format(unpack_folder))
56 | # check if we have the vswhere file already downloaded
57 | if not os.path.isfile(out_file_name):
58 | try:
59 | # Download the file and save it locally under `temp_file_name`
60 | with urllib.request.urlopen(__URL) as response, open(out_file_name, 'wb') as out_file:
61 | out_file.write(response.read())
62 | except urllib.error.HTTPError as e:
63 | logging.error(f"We ran into an issue when getting VsWhere")
64 | raise e
65 |
66 | # do the hash to make sure the file is good
67 | with open(out_file_name, "rb") as file:
68 | import hashlib
69 | temp_file_sha256 = hashlib.sha256(file.read()).hexdigest()
70 | if temp_file_sha256 != __SHA256:
71 | # delete the file since it's not what we're expecting
72 | os.remove(out_file_name)
73 | raise RuntimeError(f"VsWhere - sha256 does not match\n\tdownloaded:\t{temp_file_sha256}\n\t")
74 |
75 |
76 | def __VsWherePath():
77 | file = "vswhere.exe"
78 | requirement = pkg_resources.Requirement.parse("mu-python-library")
79 | file_path = os.path.join("MuPythonLibrary", "bin", file)
80 | vswhere_path = pkg_resources.resource_filename(requirement, file_path)
81 | return vswhere_path
82 |
83 |
84 | ####
85 | # Used to add vswhere support on posix platforms.
86 | # https://docs.microsoft.com/en-us/vswhere/install-vswhere-client-tools
87 | #
88 | # @return string "/PATH/TO/vswhere.exe" or None
89 | ####
90 | def GetVsWherePath(fail_on_not_found=True):
91 | vswhere_path = __VsWherePath()
92 | # check if we can't find it, look for vswhere in the path
93 | if not os.path.isfile(vswhere_path):
94 | for env_var in os.getenv("PATH").split(os.pathsep):
95 | env_var = os.path.join(os.path.normpath(env_var), "vswhere.exe")
96 | if os.path.isfile(env_var):
97 | vswhere_path = env_var
98 | break
99 |
100 | # if we still can't find it, download it
101 | if not os.path.isfile(vswhere_path):
102 | vswhere_dir = os.path.dirname(vswhere_path)
103 | try: # try to download
104 | _DownloadVsWhere(vswhere_dir)
105 | except:
106 | logging.warning("Tried to download VsWhere and failed")
107 | pass
108 |
109 | # if we're still hosed
110 | if not os.path.isfile(vswhere_path) and fail_on_not_found:
111 | logging.error("We weren't able to find vswhere!")
112 | return None
113 |
114 | return vswhere_path
115 |
116 |
117 | ####
118 | # Finds a product with VS Where
119 | ####
120 | def FindWithVsWhere(products: str = "*"):
121 | cmd = "-latest -nologo -all -property installationPath"
122 | vs_where_path = GetVsWherePath()
123 | if vs_where_path is None:
124 | logging.warning("We weren't able to find VSWhere")
125 | return (1, None)
126 | if(products is not None):
127 | cmd += " -products " + products
128 | a = StringIO()
129 | ret = RunCmd(vs_where_path, cmd, outstream=a)
130 | if(ret != 0):
131 | a.close()
132 | return (ret, None)
133 | p1 = a.getvalue().strip()
134 | a.close()
135 | if(len(p1.strip()) > 0):
136 | return (0, p1)
137 | return (ret, None)
138 |
139 |
140 | # Run visual studio batch file and collect the
141 | # interesting environment values
142 | #
143 | # Inspiration taken from cpython for this method of env collection
144 | #
145 | # keys: enumerable list with names of env variables to collect after bat run
146 | # arch: arch to run. amd64, x86, ??
147 | # returns a dictionary of the interesting environment variables
148 | def QueryVcVariables(keys: dict, arch: str = None, product: str = None):
149 | """Launch vcvarsall.bat and read the settings from its environment"""
150 | if product is None:
151 | product = "*"
152 | if arch is None:
153 | # TODO: look up host architecture?
154 | arch = "amd64"
155 | interesting = set(keys)
156 | result = {}
157 | ret, vs_path = FindWithVsWhere(product)
158 | if ret != 0:
159 | logging.warning("We didn't find VS path or otherwise failed to invoke vsWhere")
160 | raise ValueError("Bad VC")
161 | vcvarsall_path = os.path.join(vs_path, "VC", "Auxiliary", "Build", "vcvarsall.bat")
162 | logging.debug("Calling '%s %s'", vcvarsall_path, arch)
163 | popen = subprocess.Popen('"%s" %s & set' % (vcvarsall_path, arch), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
164 | try:
165 | stdout, stderr = popen.communicate()
166 | if popen.wait() != 0:
167 | raise Exception(stderr.decode("mbcs"))
168 | stdout = stdout.decode("mbcs")
169 | for line in stdout.split("\n"):
170 | if '=' not in line:
171 | continue
172 | line = line.strip()
173 | key, value = line.split('=', 1)
174 | if key in interesting:
175 | if value.endswith(os.pathsep):
176 | value = value[:-1]
177 | result[key] = value
178 | finally:
179 | popen.stdout.close()
180 | popen.stderr.close()
181 |
182 | if len(result) != len(interesting):
183 | logging.debug("Input: " + str(sorted(interesting)))
184 | logging.debug("Result: " + str(sorted(list(result.keys()))))
185 | raise ValueError(str(list(result.keys())))
186 | return result
187 |
188 |
189 | # return 1 if a > b
190 | # return 0 if b == a
191 | # return -1 if a < b
192 | def _CompareWindowVersions(a, b):
193 | a_periods = str(a).count(".")
194 | b_periods = str(b).count(".")
195 | if a_periods == 3 and b_periods != 3:
196 | return 1
197 | if b_periods == 3 and a_periods != 3:
198 | return -1
199 | if a_periods != 3 and b_periods != 3:
200 | return 0
201 | a_parts = str(a).split(".")
202 | b_parts = str(b).split(".")
203 | for i in range(3):
204 | a_p = int(a_parts[i])
205 | b_p = int(b_parts[i])
206 | if a_p > b_p:
207 | return 1
208 | if a_p < b_p:
209 | return -1
210 | return 0
211 |
212 |
213 | def _CheckArchOfMatch(match):
214 | '''
215 | Returns if this binary matches our host
216 | returns true or false
217 | if no arch is in the match, then we return true
218 | '''
219 | match = str(match).lower()
220 | isx86 = "x86" in match
221 | isx64 = "x64" in match or "amd64" in match
222 | isArm64 = "aarch" in match or "aarch64" in match or "arm64" in match
223 | isi386 = "i386" in match
224 | isArm = not isArm64 and ("arm" in match)
225 | count = 0
226 | count += 1 if isx64 else 0
227 | count += 1 if isx86 else 0
228 | count += 1 if isArm else 0
229 | count += 1 if isArm64 else 0
230 | count += 1 if isi386 else 0
231 | if count == 0: # we don't know what arch this is?
232 | return True
233 | if count > 1: # there are more than one arch for this binary
234 | logging.warning("We found more than one architecture for {}. Results maybe inconsistent".format(match))
235 | return True
236 |
237 | _, arch, bits = GetHostInfo()
238 | bits = int(bits)
239 | if isx86 and (bits < 32 or arch != "x86"):
240 | return False
241 | if isx64 and (bits < 64 or arch != "x86"):
242 | return False
243 | if isi386:
244 | # TODO add i386 to GetHostInfo
245 | return False
246 | if isArm64 and (bits < 64 or arch != "ARM"):
247 | return False
248 | if isArm and (bits < 32 or arch != "ARM"):
249 | return False
250 | return True
251 |
252 |
253 | # does a glob in the folder that your sdk is
254 | # uses the environmental variable WindowsSdkDir and tries to use WindowsSDKVersion
255 | def FindToolInWinSdk(tool, product=None, arch=None):
256 | variables = ["WindowsSdkDir", "WindowsSDKVersion"]
257 | # get the value with QueryVcVariables
258 | try:
259 | results = QueryVcVariables(variables, product, arch)
260 | # Get the variables we care about
261 | sdk_dir = results["WindowsSdkDir"]
262 | sdk_ver = results["WindowsSDKVersion"]
263 | except ValueError:
264 | sdk_dir = os.path.join(os.getenv("ProgramFiles(x86)"), "Windows Kits", "10", "bin")
265 | sdk_ver = "0.0.0.0"
266 |
267 | sdk_dir = os.path.realpath(sdk_dir)
268 | search_pattern = os.path.join(sdk_dir, "**", tool)
269 |
270 | match_offset = len(sdk_dir)
271 | # look for something like 10.0.12323.0123
272 | windows_ver_regex = re.compile(r'\d+\.\d+\.\d+\.\d+')
273 | top_version_so_far = -1
274 | top_match = None
275 | # Look at in match in the tree
276 | for match in glob.iglob(search_pattern, recursive=True):
277 | match_file = match[match_offset:] # strip off the root
278 | match_front, match_end = os.path.split(match_file) # split off the filename
279 | versions = windows_ver_regex.findall(match_front) # look for windows versions
280 | top_ver_match = 0
281 | if not _CheckArchOfMatch(match_front): # make sure it's a good arch for us
282 | continue
283 | for version in versions: # find the highest version if there are multiple in this?
284 | is_current_sdk_version = _CompareWindowVersions(version, sdk_ver) == 0
285 | if _CompareWindowVersions(version, top_ver_match) > 0 or is_current_sdk_version:
286 | top_ver_match = version
287 | # if we have a highest version or one that matches our current from environment variables?
288 | is_current_sdk_version = _CompareWindowVersions(top_ver_match, sdk_ver) == 0
289 | if _CompareWindowVersions(top_ver_match, top_version_so_far) > 0 or is_current_sdk_version:
290 | top_version_so_far = top_ver_match
291 | top_match = match
292 | return top_match
293 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Windows/VsWhereUtilities_test.py:
--------------------------------------------------------------------------------
1 | # @file VsWhereUtilities_test.py
2 | # Unit test harness for the VsWhereUtilities module/classes.
3 | #
4 | ##
5 | # Copyright (c), Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import unittest
29 | import sys
30 | import os
31 | import MuPythonLibrary.Windows.VsWhereUtilities as VWU
32 |
33 |
34 | class TestVsWhere(unittest.TestCase):
35 |
36 | @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
37 | def test_GetVsWherePath(self):
38 | # Gets VSWhere
39 | old_vs_path = VWU.GetVsWherePath()
40 | os.remove(old_vs_path)
41 | self.assertFalse(os.path.isfile(old_vs_path), "This should be deleted")
42 | vs_path = VWU.GetVsWherePath()
43 | self.assertTrue(os.path.isfile(vs_path), "This should be back")
44 |
45 | @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
46 | def test_FindWithVsWhere(self):
47 | # Finds something with VSWhere
48 | ret, star_prod = VWU.FindWithVsWhere()
49 | self.assertEqual(ret, 0, "Return code should be zero")
50 | self.assertNotEqual(star_prod, None, "We should have found this product")
51 | ret, bad_prod = VWU.FindWithVsWhere("bad_prod")
52 | self.assertEqual(ret, 0, "Return code should be zero")
53 | self.assertEqual(bad_prod, None, "We should not have found this product")
54 |
55 | @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
56 | def test_QueryVcVariables(self):
57 | keys = ["VCINSTALLDIR", "WindowsSDKVersion"]
58 | results = VWU.QueryVcVariables(keys)
59 |
60 | self.assertIsNotNone(results["VCINSTALLDIR"])
61 | self.assertIsNotNone(results["WindowsSDKVersion"])
62 |
63 | @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
64 | def test_FindToolInWinSdk(self):
65 | results = VWU.FindToolInWinSdk("signtool.exe")
66 | self.assertIsNotNone(results)
67 | self.assertTrue(os.path.isfile(results))
68 | results = VWU.FindToolInWinSdk("this_tool_should_never_exist.exe")
69 | self.assertIsNone(results)
70 |
71 |
72 | if __name__ == '__main__':
73 | unittest.main()
74 |
--------------------------------------------------------------------------------
/MuPythonLibrary/Windows/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/Windows/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/bin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/mu_pip_python_library/c65ba472344ebe154b409c7c30dc9149d45675a6/MuPythonLibrary/bin/__init__.py
--------------------------------------------------------------------------------
/MuPythonLibrary/bin/vswhere.md:
--------------------------------------------------------------------------------
1 | # This is where VSwhere will go?
--------------------------------------------------------------------------------
/MuPythonLibrary/feature_GetHostInfo.md:
--------------------------------------------------------------------------------
1 | # GetHostInfo
2 |
3 | This document details the utility function called GetHostInfo. This function was written because NuGet needed a way to determine attributes about the host system to determine what parts of a dependency to use.
4 |
5 | ## How to Use
6 | ```python
7 | from MuPythonLibrary.UtilityFunctions import GetHostInfo
8 |
9 | host_info = GetHostInfo()
10 | ```
11 |
12 | ## Usage info
13 |
14 | GetHostInfo() will return a namedtuple with 3 attributes describing the host machine. Below for each is the name of the field, description of the field and possible contents therein.
15 |
16 | ### 1. os - OS Name
17 |
18 | Windows, Linux, or Java
19 |
20 | ### 2. arch - Processor architecture
21 |
22 | ARM or x86
23 |
24 | ### 3. bit - Highest order bit
25 |
26 | 32 or 64
27 |
28 | ## Purpose
29 |
30 | Since there are multiple different ways one could derive these values, it is necessary provide a common implementation of that logic to ensure it is uniform.
--------------------------------------------------------------------------------
/MuPythonLibrary/feature_MuAnsiHandler.md:
--------------------------------------------------------------------------------
1 | # GetHostInfo
2 |
3 | This document details the Ansi Handler
4 |
5 | ## How to Use
6 | ```python
7 | from MuPythonLibrary.MuAnsiHandler import ColoredStreamHandler
8 |
9 | handler = ColoredStreamHandler(stream, strip=True, convert=False)
10 | formatter = ColoredFormatter()
11 | ```
12 |
13 | ## Usage info
14 |
15 | ColoredStreamHandler() will create a handler from the logging package. It accepts a stream (such as a file) and will display the colors in that particular stream as needed to the console. There are two options, strip and convert.
16 |
17 | ColoredFormatter() will create a formater from the logging package that will insert ANSI codes according to the logging level into the output stream.
18 |
19 | ### ColoredStreamHandler Arguments
20 |
21 | ### 1. strip
22 |
23 | Strip will strip ANSI codes if the terminal does not support them (such as windows).
24 |
25 |
26 | ### 2. convert
27 |
28 | Convert will convert ANSI codes on windows platforms into windows platform calls.
29 |
30 | ### ColoredFormatter Arguments
31 |
32 | ### 1. msg
33 |
34 | The best documentation for this is from Python itself. It's the same message that's passed into the Formatted baseclass.
35 |
36 | ### 2. use_azure
37 |
38 | Azure Dev ops can support colors with certain keywords. This turns that on instead of using ANSI.
39 |
40 | ## Purpose
41 |
42 | To put color into your life and your terminal, we needed to support coloring based on logging levels. ANSI seemed like a universal choice. The StreamHandler is just a workaround for windows based systems that don't support ANSI natively.
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | =================
2 | MU Python Library
3 | =================
4 |
5 | **THIS PROJECT IS NO LONGER ACTIVE** - ACTIVE WORK HAS MOVED TO https://github.com/tianocore/edk2-pytool-library
6 |
7 |
8 |
9 | About
10 | =====
11 |
12 | Python files describing various miscellaneous components from the TPM and EDKII specs.
13 | Please see Project Mu for details https://microsoft.github.io/mu
14 |
15 | Version History
16 | ===============
17 |
18 | 0.4.7
19 | -----
20 |
21 | Main Changes:
22 |
23 | - Added fallback for finding Vs tools when Visual Studio is not installed.
24 |
25 | Bug Fixes:
26 |
27 | - Fix error in VsWhereUtilities that prevented capsules from being generated
28 |
29 | 0.4.6
30 | -----
31 |
32 | Bug Fixes:
33 |
34 | - Fix broken download/publish of vswhere.exe in 0.4.5 due to wheel usage.
35 |
36 |
37 | 0.4.5
38 | -----
39 |
40 | .. note:: This release is broken for install from WHL file. Release has been deleted.
41 |
42 | Main Changes:
43 |
44 | - Add version_compare to UtilityFunctions, used to compare version strings
45 | - Adding functionality to import Modules from File and to import Class from Module
46 | - Add support for parsing FDF's via FdfParser
47 | - Added VsWhere embedded in the pip module itself
48 |
49 | 0.4.4
50 | -----
51 |
52 | Main Changes:
53 |
54 | - Add support for newer windows 10 operating systems in CatGenerator script for capsule generation.
55 | - Change the color for 'critical' events in the ANSI logging handler to be white (more compatible with PowerShell).
56 |
57 | 0.4.3
58 | -----
59 |
60 | Main Changes:
61 |
62 | - Added GetHostInfo to UtilityFunctions. This function will parse the platform module to provide information about the host.
63 | - Added colors for progress and section labels.
64 |
65 | 0.4.2
66 | -----
67 |
68 | Bug fix around quoted paths for Nuget
69 |
70 | 0.4.1
71 | -----
72 |
73 | Main changes:
74 |
75 | - Keep track of errors that occur during the build process and display the list at the very end to make errors easier to locate in the log.
76 | - Added a filter, which gets evaluated before level, that allows specific modules to either be raised or lowered in level before being output to the log.
77 |
78 | Bug fixes:
79 |
80 | - Change FileHandler mode to avoid appending a new log to an existing log.
81 | - Change MuMarkdownHanlder close routine to avoid writing the table of contents twice.
82 | - Change NuGet.exe case to match the executable exactly.
83 | - On Posix systems, throw exception if NuGet.exe is not found on the path instead of failing silently.
84 |
85 | 0.4.0
86 | -----
87 |
88 | Main changes:
89 |
90 | - Add the OverrideParser class and tests.
91 | - Update DscParser to include the enhanced provenance.
92 |
93 | Bug fixes:
94 |
95 | - Clean up the README.rst file.
96 | - Update CI pipeline to report flake results more conveniently.
97 |
98 | 0.3.1
99 | -----
100 |
101 | Bug fixes to enable module to pass both sets of CI gates (Windows and Linux).
102 |
103 | 0.3.0
104 | -----
105 |
106 | Updated documentation and release process. Transition to Beta.
107 |
108 | < 0.3.0
109 | -------
110 |
111 | Alpha development
112 |
--------------------------------------------------------------------------------
/RepoDetails.md:
--------------------------------------------------------------------------------
1 | # Project Mu Pip Python Library
2 |
3 | ??? info "Git Details"
4 | Repository Url: {{mu_pip_python_library.url}}
5 | Branch: {{mu_pip_python_library.branch}}
6 | Commit: [{{mu_pip_python_library.commit}}]({{mu_pip_python_library.commitlink}})
7 | Commit Date: {{mu_pip_python_library.date}}
8 |
9 |
10 | **THIS PROJECT IS NO LONGER ACTIVE** - ACTIVE WORK HAS MOVED TO https://github.com/tianocore/edk2-pytool-library
11 | ----
12 |
13 | Python files describing various miscellaneous components from the TPM and EDKII specs.
14 |
15 | ## More Info
16 |
17 | Please see the Project Mu docs (https://github.com/Microsoft/mu) for more information.
18 |
19 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
20 |
21 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
22 |
23 | ## Issues
24 |
25 | Please open any issues in the Project Mu GitHub tracker. [More Details](https://microsoft.github.io/mu/How/contributing/)
26 |
27 | ## Contributing Code or Docs
28 |
29 | Please follow the general Project Mu Pull Request process. [More Details](https://microsoft.github.io/mu/How/contributing/)
30 | Additionally make sure all testing described in the "Development" section passes.
31 |
32 | ## Using
33 |
34 | [Usage Details](using.md)
35 |
36 | ## Development
37 |
38 | [Development Details](developing.md)
39 |
40 | ## Publish
41 |
42 | [Publish Details](publishing.md)
43 |
44 | ## Copyright & License
45 |
46 | Copyright (c) 2016-2018, Microsoft Corporation
47 |
48 | All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
49 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
50 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
51 |
52 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
53 |
--------------------------------------------------------------------------------
/azure-pipelines-pr-gate.yml:
--------------------------------------------------------------------------------
1 | workspace:
2 | clean: all
3 |
4 | steps:
5 | - checkout: self
6 | clean: true
7 |
8 | - task: UsePythonVersion@0
9 | inputs:
10 | versionSpec: '3.7.x'
11 | architecture: 'x64'
12 |
13 | - script: python -m pip install --upgrade pip
14 | displayName: 'Install/Upgrade pip'
15 |
16 | - script: pip uninstall -y mu_python_library
17 | displayName: 'Remove existing version of self'
18 |
19 | - script: pip install --upgrade -r requirements.txt
20 | displayName: 'Install requirements'
21 |
22 | - script: pip install -e .
23 | displayName: 'Install from Source'
24 |
25 | - script: pytest -v --junitxml=test.junit.xml --html=pytest_MuPythonLibrary_report.html --self-contained-html --cov=MuPythonLibrary --cov-report html:cov_html --cov-report xml:cov.xml --cov-config .coveragerc
26 | displayName: 'Run UnitTests'
27 |
28 | # Publish Test Results to Azure Pipelines/TFS
29 | - task: PublishTestResults@2
30 | displayName: 'Publish junit test results'
31 | continueOnError: true
32 | condition: succeededOrFailed()
33 | inputs:
34 | testResultsFormat: 'JUnit' # Options: JUnit, NUnit, VSTest, xUnit
35 | testResultsFiles: 'test.junit.xml'
36 | mergeTestResults: true # Optional
37 | publishRunAttachments: true # Optional
38 |
39 | # Publish Build Artifacts
40 | # Publish build artifacts to Azure Pipelines/TFS or a file share
41 | - task: PublishBuildArtifacts@1
42 | inputs:
43 | pathtoPublish: 'pytest_MuPythonLibrary_report.html'
44 | artifactName: 'MuPythonLibrary unit test report'
45 | continueOnError: true
46 | condition: succeededOrFailed()
47 |
48 | # Publish Code Coverage Results
49 | # Publish Cobertura code coverage results
50 | - task: PublishCodeCoverageResults@1
51 | inputs:
52 | codeCoverageTool: 'cobertura' # Options: cobertura, jaCoCo
53 | summaryFileLocation: $(System.DefaultWorkingDirectory)/cov.xml
54 | reportDirectory: $(System.DefaultWorkingDirectory)/cov_html
55 | condition: succeededOrFailed()
56 |
57 | - script: flake8 .
58 | displayName: 'Run flake8'
59 | condition: succeededOrFailed()
60 |
61 | # Only capture and archive the lint log on failures.
62 | - script: flake8 . > flake8.err.log
63 | displayName: 'Capture flake8 failures'
64 | condition: Failed()
65 |
66 | - task: PublishBuildArtifacts@1
67 | inputs:
68 | pathtoPublish: 'flake8.err.log'
69 | artifactName: 'Flake8 Error log file'
70 | continueOnError: true
71 | condition: Failed()
72 |
73 | #- script: pip install --upgrade -r requirements.publisher.txt
74 | # displayName: 'Install PyPI publishing requirements'
75 |
76 | #- script: python setup.py sdist bdist_wheel
77 | # displayName: 'Build a wheel'
--------------------------------------------------------------------------------
/azure-pipelines-release.yml:
--------------------------------------------------------------------------------
1 | workspace:
2 | clean: all
3 |
4 | trigger: none # will disable CI builds entirely
5 |
6 | steps:
7 | - checkout: self
8 | clean: true
9 |
10 | - task: UsePythonVersion@0
11 | inputs:
12 | versionSpec: '3.7.x'
13 | architecture: 'x64'
14 |
15 | - script: python -m pip install --upgrade pip
16 | displayName: 'Install/Upgrade pip'
17 |
18 | - script: pip uninstall -y mu_python_library
19 | displayName: 'Remove existing version of self'
20 |
21 | - script: pip install --upgrade -r requirements.txt
22 | displayName: 'Install requirements'
23 |
24 | - script: pip install -e .
25 | displayName: 'Install from Source'
26 |
27 | - script: pytest -v --junitxml=test.junit.xml --html=pytest_MuPythonLibrary_report.html --self-contained-html --cov=MuPythonLibrary --cov-report html:cov_html --cov-report xml:cov.xml --cov-config .coveragerc
28 | displayName: 'Run UnitTests'
29 |
30 | # Publish Test Results to Azure Pipelines/TFS
31 | - task: PublishTestResults@2
32 | displayName: 'Publish junit test results'
33 | continueOnError: true
34 | condition: succeededOrFailed()
35 | inputs:
36 | testResultsFormat: 'JUnit' # Options: JUnit, NUnit, VSTest, xUnit
37 | testResultsFiles: 'test.junit.xml'
38 | mergeTestResults: true # Optional
39 | publishRunAttachments: true # Optional
40 |
41 | # Publish Build Artifacts
42 | # Publish build artifacts to Azure Pipelines/TFS or a file share
43 | - task: PublishBuildArtifacts@1
44 | inputs:
45 | pathtoPublish: 'pytest_MuPythonLibrary_report.html'
46 | artifactName: 'MuPythonLibrary unit test report'
47 | continueOnError: true
48 | condition: succeededOrFailed()
49 |
50 | # Publish Code Coverage Results
51 | # Publish Cobertura code coverage results
52 | - task: PublishCodeCoverageResults@1
53 | inputs:
54 | codeCoverageTool: 'cobertura' # Options: cobertura, jaCoCo
55 | summaryFileLocation: $(System.DefaultWorkingDirectory)/cov.xml
56 | reportDirectory: $(System.DefaultWorkingDirectory)/cov_html
57 | condition: succeededOrFailed()
58 |
59 | - script: flake8 .
60 | displayName: 'Run flake8'
61 | condition: succeededOrFailed()
62 |
63 | # Only capture and archive the lint log on failures.
64 | - script: flake8 . > flake8.err.log
65 | displayName: 'Capture flake8 failures'
66 | condition: Failed()
67 |
68 | - task: PublishBuildArtifacts@1
69 | inputs:
70 | pathtoPublish: 'flake8.err.log'
71 | artifactName: 'Flake8 Error log file'
72 | continueOnError: true
73 | condition: Failed()
74 |
75 | - script: pip install --upgrade -r requirements.publisher.txt
76 | displayName: 'Install PyPI publishing requirements'
77 |
78 | - script: python setup.py sdist bdist_wheel
79 | displayName: 'Build a wheel'
80 |
81 | # Python Script
82 | # Run a Python script.
83 | - task: PythonScript@0
84 | displayName: 'Confirm Version and Tag'
85 | inputs:
86 | scriptSource: 'filePath' # Options: filePath, inline
87 | scriptPath: ConfirmVersionAndTag.py
88 | #arguments: # Optional
89 | #pythonInterpreter: # Optional
90 | #workingDirectory: # Optional
91 | failOnStderr: true # Optional
92 |
93 | - task: TwineAuthenticate@0
94 | inputs:
95 | externalFeeds: 'Pypi-MuPip'
96 |
97 | - script: 'twine upload -r Pypi-MuPip --config-file $(PYPIRC_PATH) dist/*'
98 | displayName: 'Publish to pypi'
--------------------------------------------------------------------------------
/developing.md:
--------------------------------------------------------------------------------
1 | # Developing Project Mu Pip Python Library
2 |
3 | ## Pre-Requisites
4 |
5 | 1. Get the code
6 |
7 | ``` cmd
8 | git clone https://github.com/Microsoft/mu_pip_python_library.git
9 | ```
10 |
11 | 2. Install development dependencies
12 |
13 | ``` cmd
14 | pip install --upgrade -r requirements.txt
15 | ```
16 |
17 | 3. Uninstall any copy of mu_python_library
18 |
19 | ``` cmd
20 | pip uninstall mu_python_library
21 | ```
22 |
23 | 4. Install from local source (run command from root of repo)
24 |
25 | ``` cmd
26 | pip install -e .
27 | ```
28 |
29 | ## Testing
30 |
31 | 1. Run a Basic Syntax/Lint Check (using flake8) and resolve any issues
32 |
33 | ``` cmd
34 | flake8 MuPythonLibrary
35 | ```
36 |
37 | !!! info
38 | Newer editors are very helpful in resolving source formatting errors (whitespace, indentation, etc).
39 | In VSCode open the py file and use ++alt+shift+f++ to auto format.
40 |
41 | 2. Run pytest with coverage data collected
42 |
43 | ``` cmd
44 | pytest -v --junitxml=test.junit.xml --html=pytest_MuPythonLibrary_report.html --self-contained-html --cov=MuPythonLibrary --cov-report html:cov_html --cov-report xml:cov.xml --cov-config .coveragerc
45 | ```
46 |
47 | 3. Look at the reports
48 |
49 | * pytest_MuPythonLibrary_report.html
50 | * cov_html/index.html
51 |
--------------------------------------------------------------------------------
/publishing.md:
--------------------------------------------------------------------------------
1 | # Publishing Project Mu Pip Python Library
2 |
3 | The MuPythonLibrary is published as a pypi (pip) module. The pip module is named __mu_python_library__. Pypi allows for easy version management, dependency management, and sharing.
4 |
5 | Publishing/releasing a new version is generally handled thru a server based build process but for completeness the process is documented here.
6 |
7 | ## Steps
8 |
9 | !!! Info
10 | These directions assume you have already configured your workspace for developing. If not please first do that. Directions on the [developing](developing.md) page.
11 |
12 | 1. Pass all development tests and check. Update the readme with info on changes for this version.
13 | 2. Get your changes into master branch (official releases should only be done from the master branch)
14 | 3. Make a git tag for the version that will be released. Tag format is v..
15 | 4. Do the release process
16 |
17 | 1. Install tools
18 | ``` cmd
19 | pip install --upgrade -r requirements.publisher.txt
20 | ```
21 | 2. Build a wheel
22 | ``` cmd
23 | python setup.py sdist bdist_wheel
24 | ```
25 | 3. Confirm wheel version is aligned with git tag
26 | ``` cmd
27 | ConfirmVersionAndTag.py
28 | ```
29 | 4. Publish the wheel/distribution to pypi
30 | ``` cmd
31 | twine upload dist/*
32 | ```
33 |
--------------------------------------------------------------------------------
/requirements.publisher.txt:
--------------------------------------------------------------------------------
1 | setuptools
2 | wheel
3 | twine
4 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pytest
2 | pytest-html
3 | pytest-cov
4 | flake8
5 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | ## @file setup.py
2 | # This contains setup info for mu_python_library pip module
3 | #
4 | ##
5 | # Copyright (c) 2018, Microsoft Corporation
6 | #
7 | # All rights reserved.
8 | # Redistribution and use in source and binary forms, with or without
9 | # modification, are permitted provided that the following conditions are met:
10 | # 1. Redistributions of source code must retain the above copyright notice,
11 | # this list of conditions and the following disclaimer.
12 | # 2. Redistributions in binary form must reproduce the above copyright notice,
13 | # this list of conditions and the following disclaimer in the documentation
14 | # and/or other materials provided with the distribution.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 | # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 | # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 | # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24 | # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | ##
27 |
28 | import setuptools
29 | from setuptools.command.sdist import sdist
30 | from setuptools.command.install import install
31 | from setuptools.command.develop import develop
32 | from MuPythonLibrary.Windows.VsWhereUtilities import _DownloadVsWhere
33 |
34 | with open("README.rst", "r") as fh:
35 | long_description = fh.read()
36 |
37 |
38 | class PostSdistCommand(sdist):
39 | """Post-sdist."""
40 | def run(self):
41 | # we need to download vswhere so throw the exception if we don't get it
42 | _DownloadVsWhere()
43 | sdist.run(self)
44 |
45 |
46 | class PostInstallCommand(install):
47 | """Post-install."""
48 | def run(self):
49 | install.run(self)
50 | _DownloadVsWhere()
51 |
52 |
53 | class PostDevCommand(develop):
54 | """Post-develop."""
55 | def run(self):
56 | develop.run(self)
57 | try:
58 | _DownloadVsWhere()
59 | except:
60 | pass
61 |
62 |
63 | setuptools.setup(
64 | name="mu_python_library",
65 | author="Project Mu Team",
66 | author_email="maknutse@microsoft.com",
67 | description="Python library supporting Project Mu components (EDKII, TPM, Capsules, etc.)",
68 | long_description=long_description,
69 | url="https://github.com/microsoft/mu_pip_python_library",
70 | license='BSD2',
71 | packages=setuptools.find_packages(),
72 | cmdclass={
73 | 'sdist': PostSdistCommand,
74 | 'install': PostInstallCommand,
75 | 'develop': PostDevCommand,
76 | },
77 | include_package_data=True,
78 | use_scm_version=True,
79 | setup_requires=['setuptools_scm'],
80 | classifiers=[
81 | "Programming Language :: Python :: 3",
82 | "License :: OSI Approved :: BSD License",
83 | "Operating System :: OS Independent",
84 | "Development Status :: 4 - Beta"
85 | ]
86 | )
87 |
--------------------------------------------------------------------------------
/using.md:
--------------------------------------------------------------------------------
1 | # Using Project Mu Pip Python Library
2 |
3 | Install from pip
4 |
5 | ```cmd
6 | pip install mu_python_library
7 | ```
8 | ## Usage Docs
9 |
10 | __TBD__
11 |
--------------------------------------------------------------------------------