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
19from .other import classproperty
20
21
22def snake_to_camel(string, lower=True) -> str:
23    """Converts snake_case string to camelcase"""
24    pref, *other = string.split("_")
25    return (pref if lower else pref.capitalize()) + "".join(
26        x.capitalize() or "_" for x in other
27    )
28
29
30# Time conversion
31def ns_to_ms(t):
32    """Converts nanoseconds (10^−9) to milliseconds (10^−3)"""
33    return round(t / 1000000)
34
35
36def ns_to_us(t):
37    """Converts nanoseconds (10^−9) to microseconds (10^−6)"""
38    return round(t / 1000)
39
40
41def us_to_ms(t):
42    """Converts microseconds (10^−6) to milliseconds (10^−3)"""
43    return round(t / 1000)
44
45
46def s_to_us(t):
47    """Converts seconds (10^0) to microseconds (10^−6)"""
48    return round(t * 1000000)
49
50
51class ByteStruct(int):
52    """This class enables an ability to assign attribute names to specific bit
53    offsets in a byte, making access more approachable
54    """
55
56    def __new__(cls, *args, **kwargs):
57        fields: dict = {**cls.fields}
58
59        for kwarg, _ in kwargs.items():
60            if kwarg not in fields:
61                raise ValueError(f"{cls.__name__} does not have field {kwarg}")
62
63        value = 0
64        if len(args) == 1 and isinstance(args[0], int):
65            # Initialize from bitmask
66            value = args[0]
67        else:
68            for key, bit_position in fields.items():
69                start, end = bit_position
70                bit_value = int(kwargs.get(key, 0))
71                bit_size = start - end + 1
72                if bit_value > 2**bit_size - 1:
73                    raise ValueError(f"{key} size in bits exceeds {bit_size}")
74                value |= (bit_value & ((1 << bit_size) - 1)) << end
75
76        values = {}
77        for name in fields.keys():
78            start, end = fields[name]
79            values[name] = (value >> end) & ((1 << (start - end + 1)) - 1)
80
81        instance = super().__new__(cls, value)
82        instance._values = values
83
84        return instance
85
86    def replace(self, **kwargs):
87        """Return a new instance with specific values replaced by name."""
88        return self.__class__(**{**self.values, **kwargs})
89
90    def __getattribute__(self, item):
91        values = super().__getattribute__("_values")
92        if item == "values":
93            return {**values}
94        if item not in values:
95            return super().__getattribute__(item)
96        return values[item]
97
98    @classmethod
99    def of(cls, name=None, **kwargs):
100        """Create a subclass with the specified name and parameters"""
101        if name is None:
102            name = "ByteStructOf" + ''.join(k.upper() for k in kwargs)
103        subclass = type(name, (cls,), kwargs)
104        return subclass
105
106    @classproperty
107    def fields(cls) -> dict:  # pylint: disable=no-self-argument
108        return {
109            k: sorted((v, v) if isinstance(v, int) else v)[::-1]
110            for k, v in cls.__dict__.items()
111            if not k.startswith("_")
112        }
113
114    def __repr__(self):
115        fields = self.fields
116        result = []
117        for name, value in self.values.items():
118            start, end = fields[name]
119            length = start - end + 1
120            result.append(f"{name}={bin(value)[2:].zfill(length)}")
121        return f"{self.__class__.__name__}({', '.join(result)})"
122
123
124# CRC Calculation
125def crc16a(data):
126    w_crc = 0x6363
127    for byte in data:
128        byte = byte ^ (w_crc & 0x00FF)
129        byte = (byte ^ (byte << 4)) & 0xFF
130        w_crc = (
131            (w_crc >> 8) ^ (byte << 8) ^ (byte << 3) ^ (byte >> 4)
132        ) & 0xFFFF
133    return bytes([w_crc & 0xFF, (w_crc >> 8) & 0xFF])
134
135
136def with_crc16a(data):
137    return bytes(data) + crc16a(data)
138