1# Copyright (c) 2019, Stefan Grönke
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted providing that the following conditions
6# are met:
7# 1. Redistributions of source code must retain the above copyright
8#    notice, this list of conditions and the following disclaimer.
9# 2. Redistributions in binary form must reproduce the above copyright
10#    notice, this list of conditions and the following disclaimer in the
11#    documentation and/or other materials provided with the distribution.
12#
13# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
17# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
21# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
22# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
23# POSSIBILITY OF SUCH DAMAGE.
24import ctypes
25import typing
26import struct
27
28
29class CtlType:
30    min_size = 0
31    data: bytes
32    ctype: typing.Optional[type] = None
33    unpack_format: typing.Optional[str] = None
34
35    def __init__(self, data: bytes, size: int) -> None:
36        self.data = data
37        self.size = size
38
39    @property
40    def amount(self):
41        if self.min_size == 0:
42            return 1
43        return int(self.size / self.min_size)
44
45    @property
46    def value(self) -> typing.Any:
47        if self.unpack_format is None:
48            return self.data.value
49        values = list(struct.unpack(
50            f"<{self.unpack_format * self.amount}",
51            self.data
52        ))
53        if len(values) == 1:
54            return values[0]
55        return values
56
57    def __str__(self) -> str:
58        if self.amount == 1:
59            return self.__tostring(self.value)
60        return " ".join([self.__tostring(x) for x in self.value])
61
62    @staticmethod
63    def __tostring(value: typing.Any) -> str:
64        if isinstance(value, bytes) is True:
65            return value.decode()
66        return str(value)
67
68
69class NODE(CtlType):
70    ctype = ctypes.c_uint
71    min_size = ctypes.sizeof(ctypes.c_uint)
72    unpack_format = "I"
73
74
75class INT(CtlType):
76    ctype = ctypes.c_int
77    min_size = ctypes.sizeof(ctypes.c_int)
78    unpack_format = "i"
79
80
81class STRING(CtlType):
82
83    @property
84    def value(self) -> str:
85        return self.data.value.decode()
86
87
88class S64(CtlType):
89    ctype = ctypes.c_int64
90    min_size = ctypes.sizeof(ctypes.c_int64)
91    unpack_format = "q"
92
93
94class STRUCT(CtlType):
95    pass
96
97
98class OPAQUE(CtlType):
99    pass
100
101
102class UINT(CtlType):
103    ctype = ctypes.c_uint
104    min_size = ctypes.sizeof(ctypes.c_uint)
105    unpack_format = "I"
106
107
108class LONG(CtlType):
109    ctype = ctypes.c_long
110    min_size = ctypes.sizeof(ctypes.c_long)
111    unpack_format = "q"
112
113
114class ULONG(CtlType):
115    ctype = ctypes.c_ulong
116    min_size = ctypes.sizeof(ctypes.c_ulong)
117    unpack_format = "Q"
118
119
120class U64(CtlType):
121    ctype = ctypes.c_uint64
122    min_size = ctypes.sizeof(ctypes.c_uint64)
123    unpack_format = "Q"
124
125
126class U8(CtlType):
127    ctype = ctypes.c_uint8
128    min_size = ctypes.sizeof(ctypes.c_uint8)
129    unpack_format = "B"
130
131
132class U16(CtlType):
133    ctype = ctypes.c_uint16
134    min_size = ctypes.sizeof(ctypes.c_uint16)
135    unpack_format = "H"
136
137
138class S8(CtlType):
139    ctype = ctypes.c_int8
140    min_size = ctypes.sizeof(ctypes.c_int8)
141    unpack_format = "b"
142
143
144class S16(CtlType):
145    ctype = ctypes.c_int16
146    min_size = ctypes.sizeof(ctypes.c_int16)
147    unpack_format = "h"
148
149
150class S32(CtlType):
151    ctype = ctypes.c_int32
152    min_size = ctypes.sizeof(ctypes.c_int32)
153    unpack_format = "i"
154
155
156class U32(CtlType):
157    ctype = ctypes.c_uint32
158    min_size = ctypes.sizeof(ctypes.c_uint32)
159    unpack_format = "I"
160
161
162def identify_type(kind: int, fmt: bytes) -> CtlType:
163    ctl_type = kind & 0xF
164    if ctl_type == 1:
165        return NODE
166    elif ctl_type == 2:
167        return INT
168    elif ctl_type == 3:
169        return STRING
170    elif ctl_type == 4:
171        return S64
172    elif ctl_type == 5:
173        # return STRUCT if (fmt[0:1] == b"S") else OPAQUE
174        return OPAQUE
175    elif ctl_type == 6:
176        return UINT
177    elif ctl_type == 7:
178        return LONG
179    elif ctl_type == 8:
180        return ULONG
181    elif ctl_type == 9:
182        return U64
183    elif ctl_type == 10:
184        return U8
185    elif ctl_type == 11:
186        return U16
187    elif ctl_type == 12:
188        return S8
189    elif ctl_type == 13:
190        return S16
191    elif ctl_type == 14:
192        return S32
193    elif ctl_type == 15:
194        return U32
195    else:
196        raise Exception(f"Invalid ctl_type: {ctl_type}")
197