├── .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 | --------------------------------------------------------------------------------