1# Copyright (c) 2015-2022 Intel Corporation.
2#
3# Redistribution and use in source and binary forms, with or without
4# modification, are permitted provided that the following conditions are met:
5#
6#     * Redistributions of source code must retain the above copyright notice,
7#       this list of conditions and the following disclaimer.
8#     * 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#     * Neither the name of Intel Corporation nor the names of its contributors
12#       may be used to endorse or promote products derived from this software
13#       without specific prior written permission.
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
18# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
19# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
22# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26"""bits.cdata module."""
27
28from __future__ import print_function
29import binascii
30import ctypes
31import textwrap
32import uuid
33
34def print_fields(cls):
35    with ttypager.page():
36        print("{}".format(cls.__name__))
37        print("{:20s} {:6} {:6}".format('field', 'length', 'offset'))
38        for f in cls._fields_:
39            a = getattr(cls, f[0])
40            print("{:20s} {:6} {:6}".format(f[0], a.size, a.offset))
41
42def to_bytes(var):
43    return (ctypes.c_char * ctypes.sizeof(var)).from_buffer(var).raw
44
45_CTYPES_HEX_TYPES = (
46    ctypes.c_void_p,
47    ctypes.c_uint8, ctypes.c_uint16, ctypes.c_uint32, ctypes.c_uint64,
48    ctypes.c_ubyte, ctypes.c_ushort, ctypes.c_uint, ctypes.c_ulong, ctypes.c_ulonglong,
49)
50
51class c_base(object):
52    """Base class for ctypes structures and unions."""
53    @staticmethod
54    def _formatval(t, val):
55        if val is not None and t in _CTYPES_HEX_TYPES:
56            return "{:#x}".format(val)
57        if issubclass(t, ctypes.Array):
58            if issubclass(t._type_, (ctypes.c_char, ctypes.c_wchar)):
59                return "'{}'".format(val)
60            else:
61                return "[{}]".format(", ".join(Struct._formatval(t._type_, item) for item in val))
62        return "{}".format(val)
63
64
65    def _formatter(self, field):
66        name = field[0]
67        t = field[1]
68        val = getattr(self, name)
69        if hasattr(self, '_formats'):
70            f = self._formats.get(name, None)
71            if f:
72                return f(val)
73        if issubclass(t, (Struct, Union)):
74            val._indent = self._indent
75            return str(val)
76        if issubclass(t, ctypes.Array):
77            if issubclass(t._type_, (Struct, Union)):
78                s = "["
79                for item in val:
80                    item._indent = self._indent + "  "
81                    s += "\n" + str(item)
82                s += "]"
83                return s
84        return self._formatval(t, val)
85
86    _indent = ""
87
88    def _wrap(self, str, indent=True):
89        line_len = 77 - len(self._indent + '  ')
90        _wrapper = textwrap.TextWrapper(width=line_len, initial_indent=self._indent, subsequent_indent=self._indent + '  ')
91        _wrapper_indentall = textwrap.TextWrapper(width=line_len, initial_indent=self._indent + '  ', subsequent_indent=self._indent + '  ')
92        def __wrap():
93            wrapper = _wrapper
94            for line in str.split("\n"):
95                # Preserve blank lines, for which wrapper emits an empty list
96                if not line:
97                    yield ""
98                for wrapped_line in wrapper.wrap(line):
99                    yield wrapped_line
100                if indent:
101                    wrapper = _wrapper_indentall
102        return '\n'.join(__wrap())
103
104    def preface_field(self, field):
105        a = getattr(self.__class__, field[0])
106        return "ofs={} ".format(a.offset)
107
108    def bitfield_info(self, field):
109        a = getattr(self.__class__, field[0])
110        bit_count = a.size >> 16
111        lo_bit = a.size & 0xFFFF
112        hi_bit = lo_bit + bit_count - 1
113        return bit_count, hi_bit, lo_bit
114
115    def preface_bitfield(self, field):
116        bit_count, hi_bit, lo_bit = self.bitfield_info(field)
117        if bit_count > 1:
118            return "bits[{}:{}]=".format(hi_bit, lo_bit)
119        if bit_count == 1:
120            return "bit[{}]=".format(lo_bit)
121        return ""
122
123    def __str__(self):
124        self._indent += "  "
125        s = "{}({})".format(self.__class__.__name__, "".join("\n{}{}={}{}".format(self.preface_field(field), field[0], self.preface_bitfield(field), self._formatter(field)) for field in self._fields_))
126        self._indent = ""
127        return self._wrap(s)
128
129class Struct(ctypes.Structure, c_base):
130    """Base class for ctypes structures."""
131    def __hash__(self):
132        buf = (ctypes.c_uint8 * ctypes.sizeof(self)).from_buffer(self)
133        return binascii.crc32(buf)
134
135    def __cmp__(self, other):
136        return cmp(hash(self), hash(other))
137
138class Union(ctypes.Union, c_base):
139    """Base class for ctypes unions."""
140    def __hash__(self):
141        buf = (ctypes.c_uint8 * ctypes.sizeof(self)).from_buffer(self)
142        return binascii.crc32(buf)
143
144    def __cmp__(self, other):
145        return cmp(hash(self), hash(other))
146
147class GUID(Struct):
148    _fields_ = [
149        ('Data', ctypes.c_ubyte * 16),
150    ]
151
152    def __init__(self, *args, **kwargs):
153        """Create a GUID.  Accepts any arguments the uuid.UUID constructor
154        would accept.  Also accepts an instance of uuid.UUID, either as the
155        first argument or as a keyword argument "uuid".  As with other
156        ctypes structures, passing no parameters yields a zero-initialized
157        structure."""
158        u = kwargs.get("uuid")
159        if u is not None:
160            self.uuid = u
161        elif not(args) and not(kwargs):
162            self.uuid = uuid.UUID(int=0)
163        elif args and isinstance(args[0], uuid.UUID):
164            self.uuid = args[0]
165        else:
166            self.uuid = uuid.UUID(*args, **kwargs)
167
168    def _get_uuid(self):
169        return uuid.UUID(bytes_le=to_bytes(self))
170
171    def _set_uuid(self, u):
172        ctypes.memmove(ctypes.addressof(self), ctypes.c_char_p(u.bytes_le), ctypes.sizeof(self))
173
174    uuid = property(_get_uuid, _set_uuid)
175
176    def __cmp__(self, other):
177        if isinstance(other, GUID):
178            return cmp(self.uuid, other.uuid)
179        if isinstance(other, uuid.UUID):
180            return cmp(self.uuid, other)
181        return NotImplemented
182
183    def __hash__(self):
184        return hash(self.uuid)
185
186    def __repr__(self):
187        return "GUID({})".format(self.uuid)
188
189    def __str__(self):
190        return "{}".format(self.uuid)
191
192def _format_guid(val):
193    try:
194        import efi
195        guid_str = efi.known_uuids.get(val.uuid, None)
196    except:
197        guid_str = None
198    if guid_str:
199        return '{} ({})'.format(val, guid_str)
200    return '{}'.format(val)
201