11'''
2- Copyright (c) 2024 Pyogenics
2+ Copyright (c) 2025 Pyogenics
33
44Permission is hereby granted, free of charge, to any person obtaining a copy
55of this software and associated documentation files (the "Software"), to deal
2020SOFTWARE.
2121'''
2222
23- from io import BytesIO
24- from struct import unpack
25- from array import array
2623from zlib import decompress
24+ from io import BytesIO
2725
28- class OptionalMask :
29- def __init__ (self ):
30- self .optionalMask = []
31-
32- def read (self , stream ):
33- print ("Read optional mask" )
34- # Read "Null-mask" field
35- nullMask = b""
36- nullMaskOffset = 0
37-
38- nullMaskField = int .from_bytes (stream .read (1 ), "little" )
39- nullMaskType = nullMaskField & 0b10000000
40- if nullMaskType == 0 :
41- # Short null-mask: 5-29 bits
42- nullMaskLength = nullMaskField & 0b01100000
43-
44- nullMask += bytes (nullMaskField & 0b00011111 )
45- nullMask += stream .read (nullMaskLength ) # 1,2 or 3 bytes
46- nullMaskOffset = 3
47- else :
48- # Long null-mask: 64 - 4194304 bytes
49- nullMaskLengthSize = nullMaskField & 0b01000000
50- nullMaskLength = nullMaskField & 0b00111111
51- if nullMaskLengthSize > 0 :
52- # Long length: 22 bits
53- nullMaskLength = nullMaskLength << 16
54- nullMaskLength += int .from_bytes (stream .read (2 ), "big" )
55- else :
56- # Short length: 6 bits
57- pass
58-
59- nullMask += stream .read (nullMaskLength )
60- nullMaskOffset = 0
61-
62- nullMask = BytesIO (nullMask )
63- # Process first byte (the first byte is missing some bits on some nullmask configs)
64- maskByte = int .from_bytes (nullMask .read (1 ))
65- for bitI in range (7 - nullMaskOffset , - 1 , - 1 ):
66- self .optionalMask .append (
67- not bool (maskByte & (2 ** bitI ))
68- )
69-
70- # Process the rest of the bytes
71- for maskByte in nullMask .read ():
72- for bitI in range (7 , - 1 , - 1 ):
73- self .optionalMask .append (
74- not bool (maskByte & (2 ** bitI ))
75- )
76-
77- print (f"Optional mask flags: { len (self .optionalMask )} " )
78-
79- def getOptional (self ):
80- optional = self .optionalMask .pop (0 )
81- return optional
82-
83- def getOptionals (self , count ):
84- optionals = ()
85- for _ in range (count ):
86- optionals += (self .optionalMask .pop (0 ),)
87- return optionals
88-
89- def getLength (self ):
90- return len (self .optionalMask )
91-
92- def readPacket (stream ):
93- print ("Reading packet" )
94-
95- # Read "Package Length" field
96- packageLength = 0
97- packageGzip = False
98-
99- packageLengthField = int .from_bytes (stream .read (1 ), "little" )
100- packageLengthSize = packageLengthField & 0b10000000
101- if packageLengthSize == 0 :
102- # Short package: 14 bits
103- packageLength += (packageLengthField & 0b00111111 ) << 8
104- packageLength += int .from_bytes (stream .read (1 ), "little" )
105-
106- packageGzip = packageLengthField & 0b01000000
107- else :
108- # Long package: 31 bits
109- packageLength += (packageLengthField & 0b00111111 ) << 24
110- packageLength += int .from_bytes (stream .read (3 ), "little" )
26+ from .IOTools import unpackStream
11127
112- packageGzip = packageLengthField & 0b01000000
28+ def unwrapPacket (stream ):
29+ print ("Unwrapping packet" )
11330
114- # Decompress gzip data
115- package = stream .read ()
116- if packageGzip :
117- print ("Decompressing packet" )
118- package = decompress (package )
119- package = BytesIO (package )
31+ # Determine size and compression
32+ packetFlags = int .from_bytes (stream .read (1 ))
33+ compressedPacket = (packetFlags & 0b01000000 ) > 0
34+
35+ packetLength = 0
36+ packetLengthType = packetFlags & 0b10000000
37+ if packetLengthType == 0 :
38+ # This is a short packet
39+ packetLength = int .from_bytes (stream .read (1 ))
40+ packetLength += (packetFlags & 0b00111111 ) << 8 # Part of the length is embedded in the flags field
41+ else :
42+ # This is a long packet
43+ packetLength = int .from_bytes (stream .read (3 ), "big" )
44+ packetLength += (packetFlags & 0b00111111 ) << 24
12045
121- return package
46+ # Decompress the packet if needed
47+ packetData = stream .read (packetLength )
48+ if compressedPacket :
49+ print ("Decompressing packet" )
50+ packetData = decompress (packetData )
51+
52+ return BytesIO (packetData )
53+
54+ def readOptionalMask (stream ):
55+ print ("Reading optional mask" )
56+
57+ optionalMask = []
58+
59+ # Determine mask type (there are multiple length types)
60+ maskFlags = int .from_bytes (stream .read (1 ))
61+ maskLengthType = maskFlags & 0b10000000
62+ if maskLengthType == 0 :
63+ # Short mask: 5 optional bits + upto 3 extra bytes
64+ # First read the integrated optional bits
65+ integratedOptionalBits = maskFlags << 3 # Trim flag bits so we're left with the optionals and some padding bits
66+ for bitI in range (7 , 2 , - 1 ): #0b11111000 left to right
67+ optional = (integratedOptionalBits & 2 ** bitI ) == 0
68+ optionalMask .append (optional )
69+
70+ # Now read the external bytes
71+ externalByteCount = (maskFlags & 0b01100000 ) >> 5
72+ externalBytes = stream .read (externalByteCount )
73+ for externalByte in externalBytes :
74+ for bitI in range (7 , - 1 , - 1 ): #0b11111111 left to right
75+ optional = (externalByte & 2 ** bitI ) == 0
76+ optionalMask .append (optional )
77+ else :
78+ # This type of mask encodes an extra length/count field to increase the number of possible optionals significantly
79+ maskLengthType = maskFlags & 0b01000000
80+ externalByteCount = 0
81+ if maskLengthType == 0 :
82+ # Medium mask: stores number of bytes used for the optional mask in the last 6 bits of the flags
83+ externalByteCount = maskFlags & 0b00111111
84+ else :
85+ # Long mask: # Medium mask: stores number of bytes used for the optional mask in the last 6 bits of the flags + 2 extra bytes
86+ externalByteCount = (maskFlags & 0b00111111 ) << 16
87+ externalByteCount += int .from_bytes (stream .read (2 ), "big" )
88+
89+ # Read the external bytes
90+ externalBytes = stream .read (externalByteCount )
91+ for externalByte in externalBytes :
92+ for bitI in range (7 , - 1 , - 1 ): #0b11111111 left to right
93+ optional = (externalByte & 2 ** bitI ) == 0
94+ optionalMask .append (optional )
95+
96+ optionalMask .reverse ()
97+ return optionalMask
12298
12399'''
124- Array
100+ Array type readers
125101'''
126- def readArrayLength (package ):
102+ def readArrayLength (packet ):
127103 arrayLength = 0
128104
129- arrayField = int .from_bytes (package .read (1 ), "little" )
130- arrayLengthType = arrayField & 0b10000000
131- # Short array length
105+ arrayFlags = int .from_bytes (packet .read (1 ))
106+ arrayLengthType = arrayFlags & 0b10000000
132107 if arrayLengthType == 0 :
133- # Length of the array is contained in the last 7 bits of this byte
134- arrayLength = arrayField & 0b01111111
135- else : # Must be large array length
136- longArrayLengthType = arrayField & 0b01000000
137- # Length in last 6 bits + next byte
138- if longArrayLengthType == 0 :
139- lengthByte = int . from_bytes ( package . read ( 1 ), "little" )
140- arrayLength = (arrayField & 0b00111111 ) << 8
141- arrayLength += lengthByte
142- else : # Length in last 6 bits + next 2 bytes
143- lengthBytes = int . from_bytes ( package . read ( 2 ), "big" )
144- arrayLength = (arrayField & 0b00111111 ) << 16
145- arrayLength += lengthBytes
108+ # Short array
109+ arrayLength = arrayFlags & 0b01111111
110+ else :
111+ # Long array
112+ arrayLengthType = arrayFlags & 0b01000000
113+ if arrayLengthType == 0 :
114+ # Length in last 6 bits of flags + next byte
115+ arrayLength = (arrayFlags & 0b00111111 ) << 8
116+ arrayLength += int . from_bytes ( packet . read ( 1 ))
117+ else :
118+ # Length in last 6 bits of flags + next 2 byte
119+ arrayLength = (arrayFlags & 0b00111111 ) << 16
120+ arrayLength += int . from_bytes ( packet . read ( 2 ), "big" )
146121
147122 return arrayLength
148123
149- def readObjectArray (package , objReader , optionalMask ):
150- length = readArrayLength (package )
124+ def readObjectArray (packet , objReader , optionalMask ):
125+ arrayLength = readArrayLength (packet )
151126 objects = []
152- for _ in range (length ):
127+ for _ in range (arrayLength ):
153128 obj = objReader ()
154- obj .read (package , optionalMask )
129+ obj .read (packet , optionalMask )
155130 objects .append (obj )
156131
157132 return objects
158133
159- def readString (package ):
160- stringLength = readArrayLength (package )
161- string = package .read (stringLength )
134+ def readString (packet ):
135+ stringLength = readArrayLength (packet )
136+ string = packet .read (stringLength )
162137 string = string .decode ("utf-8" )
163138
164139 return string
165140
166- def readInt16Array (package ):
167- length = readArrayLength (package )
168- integers = unpack (f"{ length } h" , package .read (length * 2 ))
169- integers = array ("h" , integers )
141+ def readInt16Array (packet ):
142+ arrayLength = readArrayLength (packet )
143+ integers = unpackStream (f"{ arrayLength } h" , packet )
170144
171- return integers
145+ return list ( integers )
172146
173- def readIntArray (package ):
174- length = readArrayLength (package )
175- integers = unpack (f"{ length } i" , package .read (length * 4 ))
176- integers = array ("i" , integers )
147+ def readIntArray (packet ):
148+ arrayLength = readArrayLength (packet )
149+ integers = unpackStream (f"{ arrayLength } i" , packet )
177150
178- return integers
151+ return list ( integers )
179152
180- def readInt64Array (package ):
181- length = readArrayLength (package )
182- integers = unpack (f"{ length } q" , package .read (length * 8 ))
183- integers = array ("q" , integers )
153+ def readInt64Array (packet ):
154+ arrayLength = readArrayLength (packet )
155+ integers = unpackStream (f"{ arrayLength } q" , packet )
184156
185- return integers
157+ return list ( integers )
186158
187- def readFloatArray (package ):
188- length = readArrayLength (package )
189- floats = unpack (f">{ length } f" , package .read (length * 4 ))
190- floats = array ("f" , floats )
159+ def readFloatArray (packet ):
160+ arrayLength = readArrayLength (packet )
161+ floats = unpackStream (f">{ arrayLength } f" , packet )
191162
192- return floats
163+ return list ( floats )
0 commit comments