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.
@@ -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})"