"""
Lab003. Обнаружение повреждения пакета с помощью CRC-32.

Цель:
1. Сформировать пакет с CRC-32.
2. Проверить исправный пакет.
3. Изменить один бит полезной нагрузки.
4. Убедиться, что CRC обнаруживает повреждение.
"""

import struct
import zlib


# ============================================================
# Константы протокола
# ============================================================

SYNC_WORD = 0xAA55
PROTOCOL_VERSION = 1
MESSAGE_TYPE_TEXT = 1

HEADER_FORMAT = ">HBBHH"
CRC_FORMAT = ">I"

CRC_SIZE = struct.calcsize(CRC_FORMAT)


# ============================================================
# Формирование исходного пакета
# ============================================================

message = "ПРИВЕТ SDR"
payload = message.encode("utf-8")

sequence_number = 1
payload_length = len(payload)

header = struct.pack(
    HEADER_FORMAT,
    SYNC_WORD,
    PROTOCOL_VERSION,
    MESSAGE_TYPE_TEXT,
    sequence_number,
    payload_length,
)

# CRC вычисляется по заголовку и полезной нагрузке.
packet_without_crc = header + payload

crc_value = zlib.crc32(packet_without_crc) & 0xFFFFFFFF

crc_bytes = struct.pack(
    CRC_FORMAT,
    crc_value,
)

packet = packet_without_crc + crc_bytes


print("Исходное сообщение:")
print(message)

print("\nCRC-32 исходного пакета:")
print(f"0x{crc_value:08X}")

print("\nПолный пакет:")
print(packet.hex(" "))

print("\nДлина полного пакета:")
print(len(packet), "байт")


# ============================================================
# Проверка исправного пакета
# ============================================================

received_data = packet[:-CRC_SIZE]
received_crc_bytes = packet[-CRC_SIZE:]

(received_crc,) = struct.unpack(
    CRC_FORMAT,
    received_crc_bytes,
)

calculated_crc = zlib.crc32(received_data) & 0xFFFFFFFF


print("\n--- Проверка исправного пакета ---")

print(f"CRC из пакета:       0x{received_crc:08X}")
print(f"CRC вычисленный:     0x{calculated_crc:08X}")

assert received_crc == calculated_crc

print("Результат: пакет не повреждён.")


# ============================================================
# Имитация повреждения одного бита
# ============================================================

corrupted_packet = bytearray(packet)

header_size = struct.calcsize(HEADER_FORMAT)

# Буква S находится по индексу 13 внутри PAYLOAD.
payload_byte_index = 13
packet_byte_index = header_size + payload_byte_index

original_byte = corrupted_packet[packet_byte_index]

# Меняем младший бит:
# S = 0x53
# R = 0x52
corrupted_packet[packet_byte_index] ^= 0x01

corrupted_byte = corrupted_packet[packet_byte_index]


print("\n--- Повреждение одного бита ---")

print(
    f"До повреждения:    "
    f"0x{original_byte:02X} = {original_byte:08b}"
)

print(
    f"После повреждения: "
    f"0x{corrupted_byte:02X} = {corrupted_byte:08b}"
)


# ============================================================
# Проверка повреждённого пакета
# ============================================================

corrupted_data = bytes(
    corrupted_packet[:-CRC_SIZE]
)

corrupted_crc_bytes = bytes(
    corrupted_packet[-CRC_SIZE:]
)

(received_crc_after_corruption,) = struct.unpack(
    CRC_FORMAT,
    corrupted_crc_bytes,
)

calculated_crc_after_corruption = (
    zlib.crc32(corrupted_data) & 0xFFFFFFFF
)


print("\n--- Проверка повреждённого пакета ---")

print(
    f"CRC из пакета:       "
    f"0x{received_crc_after_corruption:08X}"
)

print(
    f"CRC вычисленный:     "
    f"0x{calculated_crc_after_corruption:08X}"
)


if received_crc_after_corruption != calculated_crc_after_corruption:
    print("\nРезультат: CRC обнаружил повреждение пакета.")
else:
    print("\nРезультат: повреждение не обнаружено.")


# ============================================================
# Показываем повреждённое сообщение
# ============================================================

corrupted_payload = corrupted_data[header_size:]

corrupted_message = corrupted_payload.decode("utf-8")

print("\nСообщение внутри повреждённого пакета:")
print(corrupted_message)


# ============================================================
# Автоматические проверки
# ============================================================

assert corrupted_message == "ПРИВЕТ RDR"

assert (
    received_crc_after_corruption
    != calculated_crc_after_corruption
)

print("\nПроверка пройдена: CRC-32 обнаружил изменение одного бита.")