Engee documentation
Notebook

Principles of MAC-level operation in communication systems

In this article, we will look at the basic principles of the MAC (Media Access Control) level in the Engee language, including the formation of request and response frames between stations. The MAC layer is responsible for managing access to the data transmission medium and provides:

  • Personnel formation
  • Error detection and correction
  • Environment access control
  • Identification of stations

We will start the implementation of the example by declaring the package structure. MacHeader and MacFrame These structures represent the standard frame format in 802.11 (Wi-Fi) wireless networks. In real implementations, there may be additional fields or variations depending on the specific type of frame (control, control, or data frames). Our code is correct for an ad-hoc network where addr3 it can be anything, for example, "00:00:00:00:00:00" but FCS in the example is counted only by the body of the frame.

MacHeader structure (MAC Frame header)

Field Type Purpose
frame_control UInt16 Control field (frame type, flags, protocol version)
duration UInt16 Channel Redundancy Time (µs) for NAV (Virtual Carrier Sense)
addr1 String Receiver Address (RA) is the recipient's MAC.
addr2 String Transmitter Address (TA) is the sender's MAC.
addr3 String BSSID (in infrastructure networks, the MAC of the access point, in ad—hoc— the group identifier).
seq_ctrl UInt16 Sequence control

Briefly about the assignment of addresses

  • addr1 (RA) — To whom the frame is directed (recipient).
  • addr2 (TA) — Who sent the frame **.
  • addr3 (BSSID) is the network identifier (for infrastructure networks, the MAC of the access point).

MacFrame structure (The entire MAC frame)

Field Type Purpose
header MacHeader Frame title (see above).
body Vector{UInt8} Payload (data).
fcs UInt32 Frame Check Sequence (CRC-32) — checksum for integrity verification.
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

Now let's move on to forming the request frame. (Request Frame) , it consists of the function crc32 and create_request_frame.

The first function simulates the calculation of CRC-32 (checksum) for data, but actually uses a simplified algorithm: it sums all the bytes in the array and limits the result to a 32-bit number (as in the real CRC-32). The present CRC-32 uses polynomial division (for example, 0xEDB88320).

The
second function creates a MAC request frame (for example, to transfer data over a Wi-Fi network), let's take a step-by-step look at how this function works.

  1. **frame_control = 0x00b4 **

    Indicates that it is a data frame (type 0b10) with default flags. In real networks, there may be protection flags, fragmentation, etc.

  2. duration = UInt16(0)

    Channel Backup time (0 — the frame is short, NAV is not used).

  3. seq_num = rand(UInt16)

    A random 16-bit sequence number (in reality, it should be incremented).

  4. Creating a header (MacHeader)

    • addr1 — Recipient's MAC (receiver_addr).
    • addr2 — Sender's MAC (sender_addr).
    • addr3 — BSSID (in this example 00:00:00:00:00:00, which means an ad-hoc network).
  5. frame_body = Vector{UInt8}(payload)

    Converts a string payload to an array of bytes.

  6. fcs = crc32(frame_body)

    Calculates a "checksum" (simplified) for the frame body.

  7. Returns MacFrame - finished frame with title, data and 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)

Next, we will create a response frame. (Response Frame), the frame itself includes:

  1. Frame Header (MAC Header)

  2. Payload, converts the string payload in bytes (Vector{UInt8}).

  3. Checksum (FCS), calculates the CRC32 from the frame body.

  4. Returns the finished product MacFrame with header, body and 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)

After that, you can proceed to the implementation of the algorithm for interaction between stations. This test simulates the exchange of MAC frames between two stations (A and B) on the network.

  • A - sends a request

  • B - sends a response

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

Implementation of the simple CSMA/CA protocol

CSMA/CA is a data access control protocol used in wireless networks (Wi-Fi, IEEE 802.11). Unlike CSMA/CD (Ethernet), where collisions are detected, CSMA/CA prevents them by listening to the channel before transmission (Carrier Sense), accidental delays (Backoff) upon detection of employment and confirmations (ACK) to verify delivery, this example is implemented based on an earlier disassembled implementation.

The algorithm of operation

  1. Channel Verification (CCA — Clear Channel Assessment)

    • If the channel is available → transfer.
    • If busy → waiting for a random time (Backoff).
  2. Backoff Mechanism

    • After each failed attempt, the delay increases (exponentially).
  3. Confirmation (ACK)

    • The recipient sends an ACK in response to a successful reception.
  4. Repeated attempts

    • If the ACK has not arrived → repeat transmission (before max_attempts).

Next, let's move on to the implementation and the first function is csma_ca_send, it simulates channel occupancy (rand() < 0.3 — 30% chance of employment), includes a Backoff timer (rand(1:10) * 10 ms) when the channel is busy and the attempt limit (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

The second function is create_request_frame it includes a simplified header (without the real FCS, instead the payload length) and fixed fields (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

And the last function receive_frame performs recipient verification (addr1 must match the MAC address of the station or be broadcast), and also checks the checksum (FCS) — the length of the data is checked (simplified).

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)

How is it different from the previous version?

  1. CSMA/CA has been added (previously, frames were sent immediately).
  2. Backoff timer when the channel is busy.
  3. Simplified FCS (instead of CRC32, payload length).
  4. There is no ACK (it is required in real Wi-Fi).

Next, we will analyze the errors.

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

Conclusion

In this example, we looked at the basic principles of the MAC layer, implementing request and response framing in the Engee language, basic MAC header structures, simplified implementation of the CSMA/CA protocol and error handling mechanisms, and made sure that our algorithms work correctly.

The examples presented demonstrate the key concepts of the MAC layer in communication systems.