• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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