Skip to content

Commit 4b2ba7e

Browse files
committed
Refactor Alternativa Protocol implementation
1 parent 8a96286 commit 4b2ba7e

3 files changed

Lines changed: 135 additions & 221 deletions

File tree

Lines changed: 111 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'''
2-
Copyright (c) 2024 Pyogenics
2+
Copyright (c) 2025 Pyogenics
33
44
Permission is hereby granted, free of charge, to any person obtaining a copy
55
of this software and associated documentation files (the "Software"), to deal
@@ -20,173 +20,144 @@
2020
SOFTWARE.
2121
'''
2222

23-
from io import BytesIO
24-
from struct import unpack
25-
from array import array
2623
from 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

Comments
 (0)