1# Copyright (C) 2024 The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15# Lint as: python3 16 17# String transformations 18 19import math 20 21from .other import classproperty 22 23 24def snake_to_camel(string, lower=True) -> str: 25 """Converts snake_case string to camelcase""" 26 pref, *other = string.split("_") 27 return (pref if lower else pref.capitalize()) + "".join( 28 x.capitalize() or "_" for x in other 29 ) 30 31 32# Time conversion 33def ns_to_ms(t): 34 """Converts nanoseconds (10^−9) to milliseconds (10^−3)""" 35 return round(t / 1000000) 36 37 38def ns_to_us(t): 39 """Converts nanoseconds (10^−9) to microseconds (10^−6)""" 40 return round(t / 1000) 41 42 43def us_to_ms(t): 44 """Converts microseconds (10^−6) to milliseconds (10^−3)""" 45 return round(t / 1000) 46 47 48def s_to_us(t, *, method=None): 49 """Converts seconds (10^0) to microseconds (10^−6)""" 50 return math.ceil(t * 1000000) if method == "ceil" else round(t * 1000000) 51 52 53def s_to_ms(t, *, method=None): 54 """Converts seconds (10^0) to milliseconds (10^−3)""" 55 return math.ceil(t * 1000) if method == "ceil" else round(t * 1000) 56 57 58class ByteStruct(int): 59 """This class enables an ability to assign attribute names to specific bit 60 offsets in a byte, making access more approachable 61 """ 62 63 def __new__(cls, *args, **kwargs): 64 fields: dict = {**cls.fields} 65 66 for kwarg, _ in kwargs.items(): 67 if kwarg not in fields: 68 raise ValueError(f"{cls.__name__} does not have field {kwarg}") 69 70 value = 0 71 if len(args) == 1 and isinstance(args[0], int): 72 # Initialize from bitmask 73 value = args[0] 74 else: 75 for key, bit_position in fields.items(): 76 start, end = bit_position 77 bit_value = int(kwargs.get(key, 0)) 78 bit_size = start - end + 1 79 if bit_value > 2**bit_size - 1: 80 raise ValueError(f"{key} size in bits exceeds {bit_size}") 81 value |= (bit_value & ((1 << bit_size) - 1)) << end 82 83 values = {} 84 for name in fields.keys(): 85 start, end = fields[name] 86 values[name] = (value >> end) & ((1 << (start - end + 1)) - 1) 87 88 instance = super().__new__(cls, value) 89 instance._values = values 90 91 return instance 92 93 def replace(self, **kwargs): 94 """Return a new instance with specific values replaced by name.""" 95 return self.__class__(**{**self.values, **kwargs}) 96 97 def __getattribute__(self, item): 98 values = super().__getattribute__("_values") 99 if item == "values": 100 return {**values} 101 if item not in values: 102 return super().__getattribute__(item) 103 return values[item] 104 105 @classmethod 106 def of(cls, name=None, **kwargs): 107 """Create a subclass with the specified name and parameters""" 108 if name is None: 109 name = "ByteStructOf" + ''.join(k.upper() for k in kwargs) 110 subclass = type(name, (cls,), kwargs) 111 return subclass 112 113 @classproperty 114 def fields(cls) -> dict: # pylint: disable=no-self-argument 115 return { 116 k: sorted((v, v) if isinstance(v, int) else v)[::-1] 117 for k, v in cls.__dict__.items() 118 if not k.startswith("_") 119 } 120 121 def __repr__(self): 122 fields = self.fields 123 result = [] 124 for name, value in self.values.items(): 125 start, end = fields[name] 126 length = start - end + 1 127 result.append(f"{name}={bin(value)[2:].zfill(length)}") 128 return f"{self.__class__.__name__}({', '.join(result)})" 129 130 131# CRC Calculation 132def crc16a(data): 133 w_crc = 0x6363 134 for byte in data: 135 byte = byte ^ (w_crc & 0x00FF) 136 byte = (byte ^ (byte << 4)) & 0xFF 137 w_crc = ( 138 (w_crc >> 8) ^ (byte << 8) ^ (byte << 3) ^ (byte >> 4) 139 ) & 0xFFFF 140 return bytes([w_crc & 0xFF, (w_crc >> 8) & 0xFF]) 141 142 143def with_crc16a(data): 144 return bytes(data) + crc16a(data) 145