blob: c64271739f89e041c973d4356f44ca7c72cd3ebb [file] [log] [blame]
#!/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")