Сообщество Engee

MAC-уровень

Автор
avatar-yurevyurev
Notebook

Принципы работы MAC-уровня в системах связи

В этой статье мы рассмотрим основные принципы работы MAC (Media Access Control) уровня на языке Engee, включая формирование кадров запроса и ответа между станциями. MAC-уровень отвечает за управление доступом к среде передачи данных и обеспечивает:

  • Формирование кадров
  • Обнаружение и коррекцию ошибок
  • Управление доступом к среде
  • Идентификацию станций

Реализацию примера мы начнём с объявления структуры пакетов MacHeader и MacFrame, эти структуры представляют стандартный формат кадров в беспроводных сетях 802.11 (Wi-Fi). В реальных реализациях могут быть дополнительные поля или вариации в зависимости от конкретного типа кадра (управляющие, контрольные или кадры данных). Наш код корректен для ad-hoc сети, где addr3 может быть любым, например, "00:00:00:00:00:00", но FCS в примере считается только по телу кадра.

Структура MacHeader (Заголовок MAC-кадра)

Поле Тип Назначение
frame_control UInt16 Управляющее поле (тип кадра, флаги, версия протокола)
duration UInt16 Время резервирования канала (μs) для NAV (Virtual Carrier Sense)
addr1 String Receiver Address (RA) — MAC получателя.
addr2 String Transmitter Address (TA) — MAC отправителя.
addr3 String BSSID (в инфраструктурных сетях — MAC точки доступа, в ad-hoc — идентификатор группы).
seq_ctrl UInt16 Управление последовательностью

Кратко о назначении адресов

  • addr1 (RA) — Кому кадр направлен (получатель).
  • addr2 (TA) — Кто кадр отправил.
  • addr3 (BSSID) — Идентификатор сети (для инфраструктурных сетей — MAC точки доступа).

Структура MacFrame (Весь MAC-кадр)

Поле Тип Назначение
header MacHeader Заголовок кадра (см. выше).
body Vector{UInt8} Полезная нагрузка (данные).
fcs UInt32 Frame Check Sequence (CRC-32) — контрольная сумма для проверки целостности.
In [ ]:
struct MacHeader
    frame_control::UInt16
    duration::UInt16
    addr1::String
    addr2::String
    addr3::String
    seq_ctrl::UInt16
end

struct MacFrame
    header::MacHeader
    body::Vector{UInt8}
    fcs::UInt32
end

Теперь перейдём к формированию кадра запроса. (Request Frame) , он состоит из функции crc32 и create_request_frame.

Первая функция имитирует расчёт CRC-32 (контрольной суммы) для данных, но на самом деле использует упрощённый алгоритм: суммирует все байты в массиве и ограничивает результат 32-битным числом (как в настоящем CRC-32). Настоящий CRC-32 использует полиномиальное деление (например, 0xEDB88320).


Вторая функция создаёт MAC-кадр запроса (например, для передачи данных в сети Wi-Fi), давайте по шагам разберём работу данной функции.

  1. **frame_control = 0x00b4 **

    Указывает, что это кадр данных (тип 0b10) с флагами по умолчанию. В реальных сетях здесь могут быть флаги защиты, фрагментации и др.

  2. duration = UInt16(0)

    Время резервирования канала (0 — кадр короткий, NAV не используется).

  3. seq_num = rand(UInt16)

    Случайный 16-битный номер последовательности (в реальности должен инкрементироваться).

  4. Создание заголовка (MacHeader)

    • addr1 — MAC получателя (receiver_addr).
    • addr2 — MAC отправителя (sender_addr).
    • addr3 — BSSID (в этом примере 00:00:00:00:00:00, что означает ad-hoc сеть).
  5. frame_body = Vector{UInt8}(payload)

    Преобразует строку payload в массив байт.

  6. fcs = crc32(frame_body)

    Вычисляет "контрольную сумму" (упрощённую) для тела кадра.

  7. Возвращает MacFrame - готовый кадр с заголовком, данными и FCS.

In [ ]:
function crc32(data::Vector{UInt8})
    return UInt32(sum(data) % UInt32(0xFFFFFFFF))
end

function create_request_frame(sender_addr, receiver_addr, payload)
    frame_control = 0x00b4
    duration = UInt16(0)
    seq_num = rand(UInt16)
    header = MacHeader(frame_control, duration, receiver_addr, sender_addr, "00:00:00:00:00:00", seq_num)
    frame_body = Vector{UInt8}(payload)
    fcs = crc32(frame_body)
    MacFrame(header, frame_body, fcs)
end
Out[0]:
create_request_frame (generic function with 1 method)

Далее сформируем кадр ответа. (Response Frame), сам кадр включает в себя:

  1. Заголовок кадра (MAC Header)

  2. Полезная нагрузка (Payload), преобразует строку payload в байты (Vector{UInt8}).

  3. Контрольная сумма (FCS), вычисляет CRC32 от тела кадра.

  4. Возвращает готовый MacFrame с заголовком, телом и FCS.

In [ ]:
function create_response_frame(sender_addr, receiver_addr, request_frame, payload)
    frame_control = 0x00c4
    new_duration = max(0, Int(request_frame.header.duration) - 100)
    header = MacHeader(frame_control, UInt16(new_duration), receiver_addr, sender_addr, "00:00:00:00:00:00", request_frame.header.seq_ctrl + UInt16(1))
    frame_body = Vector{UInt8}(payload)
    fcs = crc32(frame_body)
    MacFrame(header, frame_body, fcs)
end
Out[0]:
create_response_frame (generic function with 1 method)

После этого можно переходить к реализации алгоритма взаимодействия между станциями, этот тест имитирует обмен MAC-кадрами между двумя станциями (A и B) в сети.

  • А - отправляет запрос

  • B - отправляет ответ

In [ ]:
station_a = "AA:BB:CC:DD:EE:FF"
station_b = "11:22:33:44:55:66"
request_payload = "Запрос данных от A"
request_frame = create_request_frame(station_a, station_b, request_payload)
println("Станция A отправляет кадр запроса:")
println("Кому: ", request_frame.header.addr1)
println("От: ", request_frame.header.addr2)
println("Полезная нагрузка: ", String(request_frame.body))
println("Контрольная сумма FCS: ", request_frame.fcs)
println()
response_payload = "Ответ с данными от B"
response_frame = create_response_frame(station_b, station_a, request_frame, response_payload)
println("Станция B отправляет кадр ответа:")
println("Кому: ", response_frame.header.addr1)
println("От: ", response_frame.header.addr2)
println("Полезная нагрузка: ", String(response_frame.body))
println("Контрольная сумма FCS: ", response_frame.fcs)
Станция A отправляет кадр запроса:
Кому: 11:22:33:44:55:66
От: AA:BB:CC:DD:EE:FF
Полезная нагрузка: Запрос данных от A
Контрольная сумма FCS: 5369

Станция B отправляет кадр ответа:
Кому: AA:BB:CC:DD:EE:FF
От: 11:22:33:44:55:66
Полезная нагрузка: Ответ с данными от B
Контрольная сумма FCS: 5790

Реализация простого протокола CSMA/CA

CSMA/CA — это протокол управления доступом к среде передачи данных, используемый в беспроводных сетях (Wi-Fi, IEEE 802.11). В отличие от CSMA/CD (Ethernet), где коллизии обнаруживаются, CSMA/CA предотвращает их за счёт прослушивания канала перед передачей (Carrier Sense), случайных задержек (Backoff) при обнаружении занятости и подтверждений (ACK) для проверки доставки, реализован данный пример на основе раннее разобранной реализации.

Алгоритм работы

  1. Проверка канала (CCA — Clear Channel Assessment)

    • Если канал свободен → передача.
    • Если занят → ждёт случайное время (Backoff).
  2. Механизм Backoff

    • После каждой неудачной попытки задержка увеличивается (экспоненциальный рост).
  3. Подтверждение (ACK)

    • Получатель отправляет ACK в ответ на успешный приём.
  4. Повторные попытки

    • Если ACK не пришёл → повтор передачи (до max_attempts).

Далее перейдём к реализация и первая функция - это csma_ca_send, она имитирует занятость канала (rand() < 0.3 — 30% вероятность занятости), включает в себя Backoff-таймер (rand(1:10) * 10 мс) при занятом канале и лимит попыток (max_attempts=3).

In [ ]:
function csma_ca_send(sender, receiver, payload, max_attempts=3)
    attempt = 1
    while attempt <= max_attempts
        channel_busy = rand() < 0.3
        
        if !channel_busy
            frame = create_request_frame(sender, receiver, payload)
            println("Попытка $attempt: Кадр отправлен от $sender к $receiver")
            return frame
        else
            backoff_time = rand(1:10) * 10
            println("Попытка $attempt: Канал занят, ожидание $backoff_time мс")
            sleep(backoff_time / 1000)
            attempt += 1
        end
    end
    
    println("Не удалось отправить после $max_attempts попыток")
    return nothing
end

Вторая функция - это create_request_frame она включает в себя упрощённый заголовок (без реального FCS, вместо него — длина payload) и фиксированные поля (seq_ctrl = 0xf198, addr3 = 00:00:00:00:00:00).

In [ ]:
function create_request_frame(sender, receiver, payload)
    body_data = Vector{UInt8}(payload)
    fcs_value = length(body_data)
    return (
        header = (
            frame_control = 0x00b4,
            duration = 0x0000,
            addr1 = receiver,
            addr2 = sender,
            addr3 = "00:00:00:00:00:00",
            seq_ctrl = 0xf198
        ),
        body = body_data,
        fcs = fcs_value
    )
end

И последняя функция receive_frame выполняет проверку адресата (addr1 должен совпадать с MAC-адресом станции или быть broadcast), а так же проверяет контрольную сумму (FCS) — проверяется длина данных (упрощённо).

In [ ]:
function receive_frame(receiver_addr, frame)
    if frame.header.addr1 != receiver_addr && frame.header.addr1 != "FF:FF:FF:FF:FF:FF"
        println("Кадр не для этой станции")
        return false
    end
    
    calculated_fcs = length(frame.body)
    if calculated_fcs != frame.fcs
        println("Кадр повреждён (несовпадение FCS)")
        return false
    end
    
    println("Кадр успешно получен:")
    println("От: ", frame.header.addr2)
    println("Данные: ", String(frame.body))
    return true
end
Out[0]:
receive_frame (generic function with 1 method)

Чем отличается от прошлой версии?

  1. Добавлен CSMA/CA (раньше кадры отправлялись сразу).
  2. Backoff-таймер при занятости канала.
  3. Упрощённый FCS (вместо CRC32 — длина payload).
  4. Нет ACK (в реальном Wi-Fi он обязателен).

Далее проведём анализ ошибок.

In [ ]:
station_x = "AA:11:BB:22:CC:33"
station_y = "DD:44:EE:55:FF:66"
sent_frame = csma_ca_send(station_x, station_y, "Test data with CSMA/CA")

station_c = "CC:CC:CC:CC:CC:CC"
station_d = "DD:DD:DD:DD:DD:DD"

request = create_request_frame(station_c, station_d, "Test data")
receive_frame(station_d, request)
Попытка 1: Кадр отправлен от AA:11:BB:22:CC:33 к DD:44:EE:55:FF:66
Кадр успешно получен:
От: CC:CC:CC:CC:CC:CC
Данные: Test data
Out[0]:
true

Заключение

В этом примере мы рассмотрели основные принципы работы MAC-уровня, реализовав на языке Engee формирование кадров запроса и ответа, базовые структуры MAC-заголовков, упрощённую реализацию протокола CSMA/CA и механизмы обработки ошибок, и убедились что наши алгоритмы работают корректно.

Представленные примеры демонстрируют ключевые концепции MAC-уровня в системах связи.