Первый запуск
This commit is contained in:
36
Infrostructure/ProtocolCoder/BitReader.py
Normal file
36
Infrostructure/ProtocolCoder/BitReader.py
Normal file
@@ -0,0 +1,36 @@
|
||||
class BitReader:
|
||||
"""
|
||||
Класс для чтение битов из байтовой строки (bytes).
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
self.bit_pos = 0
|
||||
self.total_bits = len(data) * 8
|
||||
|
||||
def read_bits(self, length):
|
||||
"""
|
||||
Считывает length бит и возвращает их как целое число.
|
||||
"""
|
||||
if self.bit_pos + length > self.total_bits:
|
||||
raise ValueError(f"Недостаточно данных: запрошено {length}, осталось {self.remaining()}")
|
||||
|
||||
value = 0
|
||||
# Читаем побитово (можно оптимизировать, но так надежнее для понимания)
|
||||
for _ in range(length):
|
||||
byte_index = self.bit_pos // 8
|
||||
# В байте биты идут слева направо (7..0), где 7 - старший
|
||||
bit_offset = 7 - (self.bit_pos % 8)
|
||||
|
||||
bit = (self.data[byte_index] >> bit_offset) & 1
|
||||
value = (value << 1) | bit
|
||||
|
||||
self.bit_pos += 1
|
||||
return value
|
||||
|
||||
def has_bits(self, length):
|
||||
"""Проверяет, осталось ли достаточно бит для чтения."""
|
||||
return self.bit_pos + length <= self.total_bits
|
||||
|
||||
def remaining(self):
|
||||
return self.total_bits - self.bit_pos
|
||||
34
Infrostructure/ProtocolCoder/BitWriter.py
Normal file
34
Infrostructure/ProtocolCoder/BitWriter.py
Normal file
@@ -0,0 +1,34 @@
|
||||
class BitWriter:
|
||||
"""
|
||||
Класс для накопления бит и их конвертации в байтовую строку.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.value = 0
|
||||
self.bit_count = 0
|
||||
|
||||
def add_bits(self, val, length):
|
||||
"""
|
||||
Добавляет length бит из числа val в поток.
|
||||
"""
|
||||
# Сдвигаем текущее накопленное значение влево на length
|
||||
self.value = (self.value << length) | (val & ((1 << length) - 1))
|
||||
self.bit_count += length
|
||||
|
||||
def get_bytes(self):
|
||||
"""
|
||||
Возвращает накопленные биты в виде объекта bytes.
|
||||
Если количество бит не кратно 8, дополняет нулями справа (до полного байта).
|
||||
"""
|
||||
if self.bit_count == 0:
|
||||
return b''
|
||||
|
||||
# Вычисляем количество необходимых байт
|
||||
num_bytes = (self.bit_count + 7) // 8
|
||||
|
||||
# Сдвигаем значение влево, чтобы заполнить последний байт, если он не полон
|
||||
# Например, если есть 4 бита 1010, нам нужно получить байт 10100000 (0xA0)
|
||||
shift_remainder = (num_bytes * 8) - self.bit_count
|
||||
final_value = self.value << shift_remainder
|
||||
|
||||
return final_value.to_bytes(num_bytes, byteorder='big')
|
||||
218
Infrostructure/ProtocolCoder/MessageEncoder.py
Normal file
218
Infrostructure/ProtocolCoder/MessageEncoder.py
Normal file
@@ -0,0 +1,218 @@
|
||||
import time
|
||||
|
||||
from Infrostructure.ProtocolCoder.BitReader import BitReader
|
||||
from Infrostructure.ProtocolCoder.BitWriter import BitWriter
|
||||
|
||||
|
||||
class MessageEncoder:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def encode_protocol(self, template_id, variables, section_power=3):
|
||||
|
||||
# --- 1. Секция заголовков ---
|
||||
writer = BitWriter()
|
||||
|
||||
# Поле 1: Размер секции (1 байт)
|
||||
# Здесь указываем саму степень (например, 3)
|
||||
writer.add_bits(section_power, 8)
|
||||
|
||||
# Вычисляем размер одной секции в битах (S)
|
||||
section_size_bits = 1 << section_power
|
||||
# Максимальное число, которое можно записать в поле, описывающее длину (например, для 8 бит это 255)
|
||||
max_len_per_section = (1 << section_size_bits) - 1
|
||||
|
||||
# Поле 2: Зарезервированная область (4 секции)
|
||||
# 4 секции * section_size_bits
|
||||
writer.add_bits(0, 4 * section_size_bits)
|
||||
|
||||
# --- 2. Секция шаблона ---
|
||||
|
||||
# Определяем битовую длину ID шаблона
|
||||
# Если ID=0, нужно хотя бы 1 бит, но bit_length() вернет 0, обрабатываем это
|
||||
tn = template_id.bit_length() if template_id > 0 else 1
|
||||
|
||||
# Поле 3: Размер следующей секции (tn) в секциях (размер поля = 1 секция)
|
||||
# Внимание: в ТЗ написано "1 секция – размер следующей секции ... в битах".
|
||||
writer.add_bits(tn, section_size_bits)
|
||||
|
||||
# Поле 4: Идентификатор шаблона (tn бит)
|
||||
writer.add_bits(template_id, tn)
|
||||
|
||||
# --- 3. Секции данных ---
|
||||
|
||||
for var_id, var_val in variables:
|
||||
# Подготовка значения переменной
|
||||
if isinstance(var_val, str):
|
||||
# Если строка, берем код первого символа (для примера 'A' -> 65)
|
||||
# Для полноценных строк нужно кодировать в байты, здесь упрощение под "числовые переменные"
|
||||
if len(var_val) == 1:
|
||||
val_int = ord(var_val)
|
||||
else:
|
||||
# Если пришла длинная строка, кодируем как большое число
|
||||
val_bytes = var_val.encode('utf-8')
|
||||
val_int = int.from_bytes(val_bytes, byteorder='big')
|
||||
else:
|
||||
val_int = var_val
|
||||
|
||||
# Определяем необходимые биты для значения и ID
|
||||
# Используем bit_length для максимальной компактности
|
||||
# Однако, в примере ID=1 (1 бит) записан в 4 бита.
|
||||
# Алгоритм: берем минимально необходимый размер, либо выравниваем, если требуется.
|
||||
# ТЗ: "вписываются в максимально компактном виде". Значит, берем реальный bit_length.
|
||||
|
||||
# Биты для значения
|
||||
val_total_bits = val_int.bit_length() if val_int > 0 else 1
|
||||
# Биты для ID
|
||||
id_bits = var_id.bit_length() if var_id > 0 else 1
|
||||
|
||||
# Логика разбиения на секции, если значение не влезает в одну секцию описания размера.
|
||||
# Поле размера (xn) само имеет размер 1 секцию (например, 8 бит).
|
||||
# Значит, максимальная длина блока данных = 255 бит.
|
||||
# Если val_total_bits > 255, нужно разбивать на несколько секций данных.
|
||||
|
||||
bits_left = val_total_bits
|
||||
|
||||
# Для корректной нарезки битов большого числа нам удобно преобразовать его в строку или срезать маской
|
||||
# Но проще математически брать куски от старших бит к младшим или наоборот.
|
||||
# Порядок записи битов: обычно Big Endian.
|
||||
|
||||
while bits_left > 0:
|
||||
# Определяем, сколько бит значения запишем в этот блок
|
||||
# Либо всё что осталось, либо максимум, который можно описать одним числом в поле размера
|
||||
chunk_size = min(bits_left, max_len_per_section)
|
||||
|
||||
# Вырезаем нужный кусок (chunk) из числа val_int
|
||||
# Нам нужны старшие биты из оставшихся.
|
||||
# Пример: всего 10 бит, берем 8. Нужно сдвинуть (10-8)=2 раза вправо.
|
||||
shift = bits_left - chunk_size
|
||||
chunk_val = (val_int >> shift) & ((1 << chunk_size) - 1)
|
||||
|
||||
# Поле 5: Размер ID в битах (n) - занимает 1 секцию
|
||||
writer.add_bits(id_bits, section_size_bits)
|
||||
|
||||
# Поле 6: Размер блока значения в битах (xn) - занимает 1 секцию
|
||||
writer.add_bits(chunk_size, section_size_bits)
|
||||
|
||||
# Поле 7: Идентификатор (n бит)
|
||||
writer.add_bits(var_id, id_bits)
|
||||
|
||||
# Поле 8: Блок значения (xn бит)
|
||||
writer.add_bits(chunk_val, chunk_size)
|
||||
|
||||
bits_left -= chunk_size
|
||||
|
||||
return writer.get_bytes()
|
||||
|
||||
def decode_protocol(self, data):
|
||||
"""
|
||||
Декодирует бинарные данные обратно в ID шаблона и список переменных.
|
||||
|
||||
:param data: bytes объект
|
||||
:return: кортеж (template_id, list_of_variables)
|
||||
где list_of_variables это список кортежей (var_id, value)
|
||||
"""
|
||||
reader = BitReader(data)
|
||||
|
||||
# --- 1. Секция заголовков ---
|
||||
if not reader.has_bits(8):
|
||||
raise ValueError("Пустые данные или некорректный заголовок")
|
||||
|
||||
# 1. Размер секции (степень двойки)
|
||||
section_power = reader.read_bits(8)
|
||||
section_size = 1 << section_power # 2^power
|
||||
|
||||
# 2. Пропускаем зарезервированную область (4 секции)
|
||||
reader.read_bits(4 * section_size)
|
||||
|
||||
# --- 2. Секция шаблона ---
|
||||
|
||||
# 3. Размер ID шаблона (1 секция)
|
||||
tn = reader.read_bits(section_size)
|
||||
|
||||
# 4. Идентификатор шаблона (tn бит)
|
||||
template_id = reader.read_bits(tn)
|
||||
|
||||
# --- 3. Секции данных ---
|
||||
|
||||
variables = []
|
||||
last_var_id = None
|
||||
|
||||
# Читаем, пока есть данные.
|
||||
# Минимальный блок данных требует 2 секции заголовков (размер ID и размер значения)
|
||||
while reader.has_bits(2 * section_size):
|
||||
# 5. Размер ID переменной (1 секция)
|
||||
n = reader.read_bits(section_size)
|
||||
|
||||
# 6. Размер значения переменной (1 секция)
|
||||
xn = reader.read_bits(section_size)
|
||||
|
||||
# Проверяем, хватает ли бит на само тело данных
|
||||
# (Это может случиться, если в конце файла "мусорные" нули для выравнивания байта)
|
||||
if not reader.has_bits(n + xn):
|
||||
break
|
||||
|
||||
# 7. Идентификатор переменной
|
||||
var_id = reader.read_bits(n)
|
||||
|
||||
# 8. Значение переменной (часть значения)
|
||||
chunk_value = reader.read_bits(xn)
|
||||
|
||||
# Логика склеивания (Reassembly):
|
||||
# Если ID текущей переменной совпадает с ID последней добавленной,
|
||||
# значит это продолжение большого числа, которое было разбито на секции.
|
||||
# Энкодер писал старшие части первыми (Big Endian logic в чанках),
|
||||
# поэтому мы сдвигаем старое значение и добавляем новый кусок.
|
||||
if last_var_id is not None and var_id == last_var_id:
|
||||
# Получаем предыдущее значение
|
||||
_, prev_val = variables.pop()
|
||||
# Сдвигаем его влево на размер нового куска и добавляем новый кусок
|
||||
new_val = (prev_val << xn) | chunk_value
|
||||
variables.append((var_id, new_val))
|
||||
else:
|
||||
# Новая переменная
|
||||
variables.append((var_id, chunk_value))
|
||||
last_var_id = var_id
|
||||
|
||||
return template_id, variables
|
||||
|
||||
def get_hex(self, data):
|
||||
return " ".join(f"{b:02X}" for b in data)
|
||||
|
||||
def from_hex(self, hex_str):
|
||||
return bytes.fromhex(hex_str)
|
||||
|
||||
def int_to_str(self, number):
|
||||
if number == 0:
|
||||
return ""
|
||||
# 1. Вычисляем, сколько байт занимает число
|
||||
# (bit_length() + 7) // 8 — это округление вверх до целого байта
|
||||
num_bytes = (number.bit_length() + 7) // 8
|
||||
|
||||
# 2. Превращаем число в байты
|
||||
# Важно использовать byteorder='big', так как энкодер записывал старшие байты первыми
|
||||
bytes_data = number.to_bytes(num_bytes, byteorder='big')
|
||||
|
||||
# 3. Декодируем байты в строку
|
||||
try:
|
||||
return bytes_data.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
# Если число не является валидной utf-8 строкой, возвращаем как есть или hex
|
||||
return f"<Binary: {bytes_data.hex()}>"
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
me = MessageEncoder()
|
||||
hex = "03 00 00 00 00 01 81 27 59 18 19 1A 96 98 19 16 98 19 00 8F F9 37 B7 BA 00"
|
||||
|
||||
# Генерируем
|
||||
binary_data = me.from_hex(hex)
|
||||
|
||||
t = time.time()
|
||||
for i in range(1000):
|
||||
data = me.decode_protocol(binary_data)
|
||||
print((time.time() - t )*1000)
|
||||
|
||||
tmp = [(i[0], me.int_to_str(i[1])) if i[1] > 100000 else i for i in data[1]]
|
||||
|
||||
print(data[0], tmp)
|
||||
Reference in New Issue
Block a user