#!/usr/bin/env python3 # # Copyright (c) 2019, The OpenThread Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 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. # 3. Neither the name of the copyright holder nor the # names of its contributors may be used to endorse or promote products # derived from this software without specific prior written permission. # # 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. # import sys from typing import Union, Any class Bytes(bytearray): """Bytes represents a byte array which is able to handle strings of flexible formats""" def __init__(self, s: Union[str, bytearray, 'Bytes', Any]): if isinstance(s, str): try: s = Bytes._parse_compact(s) except ValueError: try: s = Bytes._parse_octets(s) except ValueError: s = Bytes._parse_hextets(s) super().__init__(s) def __hash__(self): return hash(bytes(self)) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.format_compact()) def format_compact(self) -> str: """ Converts the Bytes to a compact string (without ":"). """ return ''.join('%02x' % b for b in self) def format_octets(self) -> str: """ Converts the Bytes to a string of octets separated by ":". """ return ':'.join('%02x' % b for b in self) def format_hextets(self) -> str: """ Converts the Bytes to a string of hextets separated by ":" """ assert len(self) % 2 == 0, self.format_octets() return ':'.join('%04x' % (self[i] * 256 + self[i + 1]) for i in range(0, len(self), 2)) __str__ = format_octets @staticmethod def _parse_compact(s: str) -> bytearray: try: assert len(s) % 2 == 0 return bytearray(int(s[i:i + 2], 16) for i in range(0, len(s), 2)) except Exception: raise ValueError(s) @staticmethod def _parse_octets(s: str) -> bytearray: try: assert len(s) % 3 == 2 or not s if not s: return bytearray(b"") return bytearray(int(x, 16) for x in s.split(':')) except Exception: raise ValueError(s) @staticmethod def _parse_hextets(s) -> bytearray: try: assert len(s) % 5 == 4 or not s if not s: return bytearray(b"") return bytearray(int(x[i:i + 2], 16) for x in s.split(':') for i in (0, 2)) except Exception: raise ValueError(s) def __getitem__(self, item) -> Union['Bytes', int]: """ Get self[item]. :param item: index or slice to retrieve :return: the byte value at specified index or sub `Bytes` if item is slice """ x = super().__getitem__(item) if isinstance(x, bytearray): return Bytes(x) else: return x def __eq__(self, other: Union[str, 'Bytes']): """ Check if bytes is equal to other. """ if other is None: return False elif not isinstance(other, Bytes): other = self.__class__(other) eq = super().__eq__(other) print("[%r %s %r]" % (self, "==" if eq else "!=", other), file=sys.stderr) return eq if __name__ == '__main__': # some simple tests x = Bytes(b"\x01\x02\x03\x04") assert eval(repr(x)) == x, repr(x) # representation of Bytes should be able to be evaluated back assert x == str(x), (x, str(x)) assert x.format_compact() == "01020304", x.format_compact() assert x.format_octets() == "01:02:03:04", x.format_octets() assert x.format_hextets() == "0102:0304", x.format_hextets() assert Bytes._parse_compact("") == Bytes(b"") assert Bytes._parse_compact('01020304') == x assert Bytes._parse_octets("") == Bytes(b"") assert Bytes._parse_octets('01:02:03:04') == x assert Bytes._parse_hextets("") == Bytes(b"") assert Bytes._parse_hextets('0102:0304') == x assert isinstance(x[:2], Bytes) assert isinstance(x[-2:], Bytes) assert x[:2] == Bytes(b'\x01\x02') assert x[-2:] == Bytes(b'\x03\x04') # should also parse string formats assert Bytes("01020304") == Bytes(b"\x01\x02\x03\x04") assert Bytes("01:02:03:04") == Bytes(b"\x01\x02\x03\x04") assert Bytes("0102:0304") == Bytes(b"\x01\x02\x03\x04")