litenetlib-0952 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- litenetlib/__init__.py +66 -0
- litenetlib/channels/__init__.py +16 -0
- litenetlib/channels/base_channel.py +115 -0
- litenetlib/channels/reliable_channel.py +451 -0
- litenetlib/channels/sequenced_channel.py +239 -0
- litenetlib/core/__init__.py +53 -0
- litenetlib/core/connection_request.py +240 -0
- litenetlib/core/constants.py +186 -0
- litenetlib/core/events.py +336 -0
- litenetlib/core/internal_packets.py +358 -0
- litenetlib/core/manager.py +355 -0
- litenetlib/core/packet.py +457 -0
- litenetlib/core/peer.py +334 -0
- litenetlib/utils/__init__.py +21 -0
- litenetlib/utils/data_reader.py +447 -0
- litenetlib/utils/data_writer.py +402 -0
- litenetlib/utils/fast_bit_converter.py +199 -0
- litenetlib/utils/net_utils.py +165 -0
- litenetlib_0952-1.0.0.dist-info/METADATA +448 -0
- litenetlib_0952-1.0.0.dist-info/RECORD +23 -0
- litenetlib_0952-1.0.0.dist-info/WHEEL +5 -0
- litenetlib_0952-1.0.0.dist-info/licenses/LICENSE +21 -0
- litenetlib_0952-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Network data reader for deserializing binary data.
|
|
3
|
+
|
|
4
|
+
Provides methods to read various data types from a binary buffer
|
|
5
|
+
that was written with NetDataWriter.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import struct
|
|
9
|
+
import uuid
|
|
10
|
+
import socket
|
|
11
|
+
from typing import Optional, List, Tuple, Union
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NetDataReader:
|
|
15
|
+
"""
|
|
16
|
+
Binary data reader with position tracking.
|
|
17
|
+
|
|
18
|
+
Uses little-endian byte order for multi-byte values.
|
|
19
|
+
All data reads must be within the buffer bounds.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__slots__ = ('_data', '_position', '_data_size', '_offset')
|
|
23
|
+
|
|
24
|
+
def __init__(self, source: Optional[bytes] = None, offset: int = 0, max_size: Optional[int] = None):
|
|
25
|
+
"""
|
|
26
|
+
Create a new NetDataReader.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
source: Source bytes to read from
|
|
30
|
+
offset: Starting offset in source
|
|
31
|
+
max_size: Maximum size to read (None = to end of source)
|
|
32
|
+
"""
|
|
33
|
+
if source is not None:
|
|
34
|
+
self._data = memoryview(source)
|
|
35
|
+
self._data_size = len(source) if max_size is None else min(max_size, len(source))
|
|
36
|
+
self._offset = offset
|
|
37
|
+
self._position = offset
|
|
38
|
+
else:
|
|
39
|
+
self._data = memoryview(b'')
|
|
40
|
+
self._data_size = 0
|
|
41
|
+
self._offset = 0
|
|
42
|
+
self._position = 0
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_writer(cls, writer: 'NetDataWriter') -> 'NetDataReader':
|
|
46
|
+
"""Create reader from a NetDataWriter."""
|
|
47
|
+
# Import here to avoid circular dependency
|
|
48
|
+
from litenetlib.utils.data_writer import NetDataWriter
|
|
49
|
+
return cls(writer.to_bytes())
|
|
50
|
+
|
|
51
|
+
def set_source(self, source: bytes, offset: int = 0, max_size: Optional[int] = None) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Set new data source.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
source: New source bytes
|
|
57
|
+
offset: Starting offset
|
|
58
|
+
max_size: Maximum size to read
|
|
59
|
+
"""
|
|
60
|
+
self._data = memoryview(source)
|
|
61
|
+
self._data_size = len(source) if max_size is None else min(max_size, len(source))
|
|
62
|
+
self._offset = offset
|
|
63
|
+
self._position = offset
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def position(self) -> int:
|
|
67
|
+
"""Get current read position."""
|
|
68
|
+
return self._position
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def available_bytes(self) -> int:
|
|
72
|
+
"""Get remaining bytes available to read."""
|
|
73
|
+
return self._data_size - self._position
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def end_of_data(self) -> bool:
|
|
77
|
+
"""Check if at end of data."""
|
|
78
|
+
return self._position >= self._data_size
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def is_null(self) -> bool:
|
|
82
|
+
"""Check if has no data."""
|
|
83
|
+
return self._data_size == 0
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def user_data_offset(self) -> int:
|
|
87
|
+
"""Get user data offset (if applicable)."""
|
|
88
|
+
return self._offset
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def user_data_size(self) -> int:
|
|
92
|
+
"""Get user data size."""
|
|
93
|
+
return self._data_size - self._offset
|
|
94
|
+
|
|
95
|
+
def skip_bytes(self, count: int) -> None:
|
|
96
|
+
"""Skip ahead by specified number of bytes."""
|
|
97
|
+
self._position = min(self._position + count, self._data_size)
|
|
98
|
+
|
|
99
|
+
def set_position(self, position: int) -> None:
|
|
100
|
+
"""Set read position."""
|
|
101
|
+
self._position = max(0, min(position, self._data_size))
|
|
102
|
+
|
|
103
|
+
def clear(self) -> None:
|
|
104
|
+
"""Clear reader."""
|
|
105
|
+
self._data = memoryview(b'')
|
|
106
|
+
self._data_size = 0
|
|
107
|
+
self._offset = 0
|
|
108
|
+
self._position = 0
|
|
109
|
+
|
|
110
|
+
# Basic types
|
|
111
|
+
|
|
112
|
+
def get_byte(self) -> int:
|
|
113
|
+
"""Read a single byte."""
|
|
114
|
+
if self.available_bytes < 1:
|
|
115
|
+
raise EOFError("Not enough bytes to read byte")
|
|
116
|
+
value = self._data[self._position]
|
|
117
|
+
self._position += 1
|
|
118
|
+
return value
|
|
119
|
+
|
|
120
|
+
def get_sbyte(self) -> int:
|
|
121
|
+
"""Read a signed byte."""
|
|
122
|
+
b = self.get_byte()
|
|
123
|
+
return b if b < 128 else b - 256
|
|
124
|
+
|
|
125
|
+
def get_bool(self) -> bool:
|
|
126
|
+
"""Read a boolean (0 = False, 1 = True)."""
|
|
127
|
+
return self.get_byte() == 1
|
|
128
|
+
|
|
129
|
+
def get_short(self) -> int:
|
|
130
|
+
"""Read a 16-bit signed integer."""
|
|
131
|
+
if self.available_bytes < 2:
|
|
132
|
+
raise EOFError("Not enough bytes to read short")
|
|
133
|
+
value = struct.unpack_from('<h', self._data, self._position)[0]
|
|
134
|
+
self._position += 2
|
|
135
|
+
return value
|
|
136
|
+
|
|
137
|
+
def get_ushort(self) -> int:
|
|
138
|
+
"""Read a 16-bit unsigned integer."""
|
|
139
|
+
if self.available_bytes < 2:
|
|
140
|
+
raise EOFError("Not enough bytes to read ushort")
|
|
141
|
+
value = struct.unpack_from('<H', self._data, self._position)[0]
|
|
142
|
+
self._position += 2
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
def get_int(self) -> int:
|
|
146
|
+
"""Read a 32-bit signed integer."""
|
|
147
|
+
if self.available_bytes < 4:
|
|
148
|
+
raise EOFError("Not enough bytes to read int")
|
|
149
|
+
value = struct.unpack_from('<i', self._data, self._position)[0]
|
|
150
|
+
self._position += 4
|
|
151
|
+
return value
|
|
152
|
+
|
|
153
|
+
def get_uint(self) -> int:
|
|
154
|
+
"""Read a 32-bit unsigned integer."""
|
|
155
|
+
if self.available_bytes < 4:
|
|
156
|
+
raise EOFError("Not enough bytes to read uint")
|
|
157
|
+
value = struct.unpack_from('<I', self._data, self._position)[0]
|
|
158
|
+
self._position += 4
|
|
159
|
+
return value
|
|
160
|
+
|
|
161
|
+
def get_long(self) -> int:
|
|
162
|
+
"""Read a 64-bit signed integer."""
|
|
163
|
+
if self.available_bytes < 8:
|
|
164
|
+
raise EOFError("Not enough bytes to read long")
|
|
165
|
+
value = struct.unpack_from('<q', self._data, self._position)[0]
|
|
166
|
+
self._position += 8
|
|
167
|
+
return value
|
|
168
|
+
|
|
169
|
+
def get_ulong(self) -> int:
|
|
170
|
+
"""Read a 64-bit unsigned integer."""
|
|
171
|
+
if self.available_bytes < 8:
|
|
172
|
+
raise EOFError("Not enough bytes to read ulong")
|
|
173
|
+
value = struct.unpack_from('<Q', self._data, self._position)[0]
|
|
174
|
+
self._position += 8
|
|
175
|
+
return value
|
|
176
|
+
|
|
177
|
+
def get_float(self) -> float:
|
|
178
|
+
"""Read a 32-bit float."""
|
|
179
|
+
if self.available_bytes < 4:
|
|
180
|
+
raise EOFError("Not enough bytes to read float")
|
|
181
|
+
value = struct.unpack_from('<f', self._data, self._position)[0]
|
|
182
|
+
self._position += 4
|
|
183
|
+
return value
|
|
184
|
+
|
|
185
|
+
def get_double(self) -> float:
|
|
186
|
+
"""Read a 64-bit double."""
|
|
187
|
+
if self.available_bytes < 8:
|
|
188
|
+
raise EOFError("Not enough bytes to read double")
|
|
189
|
+
value = struct.unpack_from('<d', self._data, self._position)[0]
|
|
190
|
+
self._position += 8
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
def get_char(self) -> str:
|
|
194
|
+
"""Read a single character from ushort."""
|
|
195
|
+
return chr(self.get_ushort())
|
|
196
|
+
|
|
197
|
+
# String types
|
|
198
|
+
|
|
199
|
+
def get_string(self, max_length: int = 0) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Read a string with length prefix.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
max_length: Maximum character length (0 = no limit)
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Decoded string (empty if exceeds max_length)
|
|
208
|
+
"""
|
|
209
|
+
size = self.get_ushort()
|
|
210
|
+
if size == 0:
|
|
211
|
+
return ""
|
|
212
|
+
|
|
213
|
+
actual_size = size - 1
|
|
214
|
+
|
|
215
|
+
if self.available_bytes < actual_size:
|
|
216
|
+
raise EOFError(f"Not enough bytes to read string (need {actual_size})")
|
|
217
|
+
|
|
218
|
+
data = bytes(self._data[self._position:self._position + actual_size])
|
|
219
|
+
self._position += actual_size
|
|
220
|
+
|
|
221
|
+
# Decode and check max_length
|
|
222
|
+
try:
|
|
223
|
+
result = data.decode('utf-8')
|
|
224
|
+
if max_length > 0 and len(result) > max_length:
|
|
225
|
+
return ""
|
|
226
|
+
return result
|
|
227
|
+
except UnicodeDecodeError:
|
|
228
|
+
return ""
|
|
229
|
+
|
|
230
|
+
def get_large_string(self) -> str:
|
|
231
|
+
"""
|
|
232
|
+
Read a potentially large string with int length prefix.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Decoded string
|
|
236
|
+
"""
|
|
237
|
+
size = self.get_int()
|
|
238
|
+
if size <= 0:
|
|
239
|
+
return ""
|
|
240
|
+
|
|
241
|
+
if self.available_bytes < size:
|
|
242
|
+
raise EOFError(f"Not enough bytes to read large string (need {size})")
|
|
243
|
+
|
|
244
|
+
data = bytes(self._data[self._position:self._position + size])
|
|
245
|
+
self._position += size
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
return data.decode('utf-8')
|
|
249
|
+
except UnicodeDecodeError:
|
|
250
|
+
return ""
|
|
251
|
+
|
|
252
|
+
# Bytes and arrays
|
|
253
|
+
|
|
254
|
+
def get_bytes(self, count: int) -> bytes:
|
|
255
|
+
"""
|
|
256
|
+
Read specified number of bytes.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
count: Number of bytes to read
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Bytes read
|
|
263
|
+
"""
|
|
264
|
+
if self.available_bytes < count:
|
|
265
|
+
raise EOFError(f"Not enough bytes (need {count}, have {self.available_bytes})")
|
|
266
|
+
|
|
267
|
+
data = bytes(self._data[self._position:self._position + count])
|
|
268
|
+
self._position += count
|
|
269
|
+
return data
|
|
270
|
+
|
|
271
|
+
def get_bytes_with_length(self) -> bytes:
|
|
272
|
+
"""Read bytes with ushort length prefix."""
|
|
273
|
+
length = self.get_ushort()
|
|
274
|
+
return self.get_bytes(length)
|
|
275
|
+
|
|
276
|
+
def get_remaining_bytes(self) -> bytes:
|
|
277
|
+
"""Read all remaining bytes."""
|
|
278
|
+
if self.available_bytes == 0:
|
|
279
|
+
return b''
|
|
280
|
+
data = bytes(self._data[self._position:self._data_size])
|
|
281
|
+
self._position = self._data_size
|
|
282
|
+
return data
|
|
283
|
+
|
|
284
|
+
def get_array(self, element_size: int = 1) -> List:
|
|
285
|
+
"""
|
|
286
|
+
Read an array with length prefix.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
element_size: Size of each element in bytes
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of elements
|
|
293
|
+
"""
|
|
294
|
+
length = self.get_ushort()
|
|
295
|
+
if length == 0:
|
|
296
|
+
return []
|
|
297
|
+
|
|
298
|
+
total_size = length * element_size
|
|
299
|
+
if self.available_bytes < total_size:
|
|
300
|
+
raise EOFError(f"Not enough bytes for array (need {total_size})")
|
|
301
|
+
|
|
302
|
+
result = []
|
|
303
|
+
data_bytes = self._data[self._position:self._position + total_size]
|
|
304
|
+
self._position += total_size
|
|
305
|
+
|
|
306
|
+
for i in range(length):
|
|
307
|
+
offset = i * element_size
|
|
308
|
+
if element_size == 1:
|
|
309
|
+
# byte or bool - preserve byte value as integer 0-255
|
|
310
|
+
result.append(data_bytes[offset])
|
|
311
|
+
elif element_size == 2:
|
|
312
|
+
# short or ushort
|
|
313
|
+
result.append(struct.unpack_from('<H', data_bytes, offset)[0])
|
|
314
|
+
elif element_size == 4:
|
|
315
|
+
# int or uint
|
|
316
|
+
result.append(struct.unpack_from('<I', data_bytes, offset)[0])
|
|
317
|
+
elif element_size == 8:
|
|
318
|
+
# long or ulong
|
|
319
|
+
result.append(struct.unpack_from('<Q', data_bytes, offset)[0])
|
|
320
|
+
|
|
321
|
+
return result
|
|
322
|
+
|
|
323
|
+
def get_string_array(self, max_length: int = 0) -> List[str]:
|
|
324
|
+
"""
|
|
325
|
+
Read an array of strings.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
max_length: Maximum length for each string
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List of strings
|
|
332
|
+
"""
|
|
333
|
+
length = self.get_ushort()
|
|
334
|
+
return [self.get_string(max_length) for _ in range(length)]
|
|
335
|
+
|
|
336
|
+
# Special types
|
|
337
|
+
|
|
338
|
+
def get_uuid(self) -> uuid.UUID:
|
|
339
|
+
"""Read a UUID (16 bytes)."""
|
|
340
|
+
if self.available_bytes < 16:
|
|
341
|
+
raise EOFError("Not enough bytes to read UUID")
|
|
342
|
+
data = bytes(self._data[self._position:self._position + 16])
|
|
343
|
+
self._position += 16
|
|
344
|
+
return uuid.UUID(bytes=data)
|
|
345
|
+
|
|
346
|
+
def get_ip_end_point(self) -> Tuple[str, int, str]:
|
|
347
|
+
"""
|
|
348
|
+
Read an IP endpoint.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
Tuple of (address, port, family) where family is 'IPv4' or 'IPv6'
|
|
352
|
+
"""
|
|
353
|
+
family_byte = self.get_byte()
|
|
354
|
+
|
|
355
|
+
if family_byte == 0:
|
|
356
|
+
# IPv4
|
|
357
|
+
family = 'IPv4'
|
|
358
|
+
addr_bytes = self.get_bytes(4)
|
|
359
|
+
address = socket.inet_ntop(socket.AF_INET, addr_bytes)
|
|
360
|
+
elif family_byte == 1:
|
|
361
|
+
# IPv6
|
|
362
|
+
family = 'IPv6'
|
|
363
|
+
addr_bytes = self.get_bytes(16)
|
|
364
|
+
address = socket.inet_ntop(socket.AF_INET6, addr_bytes)
|
|
365
|
+
else:
|
|
366
|
+
raise ValueError(f"Unknown address family: {family_byte}")
|
|
367
|
+
|
|
368
|
+
port = self.get_ushort()
|
|
369
|
+
return (address, port, family)
|
|
370
|
+
|
|
371
|
+
# Peek methods (read without advancing position)
|
|
372
|
+
|
|
373
|
+
def peek_byte(self) -> int:
|
|
374
|
+
"""Peek at next byte without advancing."""
|
|
375
|
+
if self.available_bytes < 1:
|
|
376
|
+
raise EOFError("Not enough bytes to peek")
|
|
377
|
+
return self._data[self._position]
|
|
378
|
+
|
|
379
|
+
def peek_bool(self) -> bool:
|
|
380
|
+
"""Peek at next boolean without advancing."""
|
|
381
|
+
return self.peek_byte() == 1
|
|
382
|
+
|
|
383
|
+
def peek_ushort(self) -> int:
|
|
384
|
+
"""Peek at next ushort without advancing."""
|
|
385
|
+
if self.available_bytes < 2:
|
|
386
|
+
raise EOFError("Not enough bytes to peek ushort")
|
|
387
|
+
return struct.unpack_from('<H', self._data, self._position)[0]
|
|
388
|
+
|
|
389
|
+
def peek_string(self, max_length: int = 0) -> str:
|
|
390
|
+
"""
|
|
391
|
+
Peek at next string without advancing position.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
max_length: Maximum character length (0 = no limit)
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
String (empty if exceeds max_length)
|
|
398
|
+
"""
|
|
399
|
+
size = struct.unpack_from('<H', self._data, self._position)[0]
|
|
400
|
+
if size == 0:
|
|
401
|
+
return ""
|
|
402
|
+
|
|
403
|
+
actual_size = size - 1
|
|
404
|
+
if self.available_bytes < 2 + actual_size:
|
|
405
|
+
return ""
|
|
406
|
+
|
|
407
|
+
try:
|
|
408
|
+
result = bytes(self._data[self._position + 2:self._position + 2 + actual_size]).decode('utf-8')
|
|
409
|
+
if max_length > 0 and len(result) > max_length:
|
|
410
|
+
return ""
|
|
411
|
+
return result
|
|
412
|
+
except UnicodeDecodeError:
|
|
413
|
+
return ""
|
|
414
|
+
|
|
415
|
+
# Try methods (safe read with default)
|
|
416
|
+
|
|
417
|
+
def try_get_byte(self, default: int = 0) -> Tuple[bool, int]:
|
|
418
|
+
"""Try to read a byte, return (success, value)."""
|
|
419
|
+
if self.available_bytes >= 1:
|
|
420
|
+
return True, self.get_byte()
|
|
421
|
+
return False, default
|
|
422
|
+
|
|
423
|
+
def try_get_bool(self, default: bool = False) -> Tuple[bool, bool]:
|
|
424
|
+
"""Try to read a bool, return (success, value)."""
|
|
425
|
+
if self.available_bytes >= 1:
|
|
426
|
+
return True, self.get_bool()
|
|
427
|
+
return False, default
|
|
428
|
+
|
|
429
|
+
def try_get_int(self, default: int = 0) -> Tuple[bool, int]:
|
|
430
|
+
"""Try to read an int, return (success, value)."""
|
|
431
|
+
if self.available_bytes >= 4:
|
|
432
|
+
return True, self.get_int()
|
|
433
|
+
return False, default
|
|
434
|
+
|
|
435
|
+
def try_get_string(self, default: Optional[str] = None) -> Tuple[bool, Optional[str]]:
|
|
436
|
+
"""Try to read a string, return (success, value)."""
|
|
437
|
+
if self.available_bytes >= 2:
|
|
438
|
+
size = struct.unpack_from('<H', self._data, self._position)[0]
|
|
439
|
+
if self.available_bytes >= 2 + size - 1:
|
|
440
|
+
return True, self.get_string()
|
|
441
|
+
return False, default
|
|
442
|
+
|
|
443
|
+
def __len__(self) -> int:
|
|
444
|
+
return self.available_bytes
|
|
445
|
+
|
|
446
|
+
def __repr__(self) -> str:
|
|
447
|
+
return f"NetDataReader(position={self._position}, available={self.available_bytes})"
|