KISS File Transfer Protocol: Detailed Documentation

Introduction

The KISS File Transfer Protocol is a robust suite of applications designed for reliable file transfer over RF with any KISS compatible TNC and uses standard AX.25 UI frames (i.e. does not require 'connected mode'). The system consists of a sender, receiver and optional fileserver applications, as well as dedicated applications for repeating and bridging. A pure HTML/JS implementation is provided with support for Chrome's Serial API as well as an optional websockets application. The protocol divides a file into a header packet containing metadata and multiple data packets carrying file chunks. It also supports control packets including ACKs (acknowledgments) and command (CMD) and response (RSP) messages.

The protocol supports both raw binary and Base64-encoded payloads, optional zlib compression, and a dynamic sliding window with adaptive timeouts and retransmissions.

Core Protocol Components

The protocol defines several key packet types:

Additionally, each packet includes explicit fields such as a unique FILEID, encoding method (0 for binary, 1 for Base64), and a compression flag.

KISS Framing and AX.25 Header

KISS Framing

All packets are encapsulated in a KISS frame to preserve data boundaries. The process is as follows:

function buildKISSFrame(packet):
    FLAG = 0xC0
    CMD  = 0x00
    escaped_packet = escapeSpecialBytes(packet)
    return FLAG + CMD + escaped_packet + FLAG
    

AX.25 Header

Each packet begins with a fixed 16-byte header modeled after AX.25 addressing. This header includes:

function buildAX25Header(source, destination):
    dest = encodeAddress(destination, is_last = False)
    src  = encodeAddress(source, is_last = True)
    CONTROL = 0x03
    PID     = 0xF0
    return dest + src + CONTROL + PID
    

Packet Formats and Fields

Common Structure

Every packet consists of:

DATA Packets

DATA packets are used to transmit file data. They come in two types:

Header Packet (Sequence 1)

The header packet initializes a file transfer and carries vital metadata. Its info field is formatted as:

"FILEID:0001{burst_to_hex}/{total_hex}:"
    

Where:

Following this, the header payload is a UTF‑8 pipe‑delimited string containing:

Data Packets (Sequence ≥ 2)

Data packets carry sequential chunks of file data. Their info field is formatted as:

"FILEID:{seq_hex}{burst_to_hex}:"
    

Where:

The payload following this info field is a chunk of file data (up to a configured CHUNK_SIZE in bytes). If Base64 encoding is enabled, each chunk is individually encoded.

ACK Packets

ACK packets acknowledge received data. Their info field contains fields separated by colons. A cumulative ACK, for example "FILEID:ACK:0001-XXXX", indicates the highest contiguous packet received.

CMD and RSP Packets

CMD Packets: Issued by remote clients, CMD packets enable file operations (such as GET, LIST, or PUT). Their info field is constructed as follows:

"cmdID:CMD:"
    

In this format:

This colon-separated format permits variable-length command messages without padding.

RSP Packets: Sent in response to CMD packets, RSP packets provide the result of the requested operation. Their info field is formatted as:

"cmdID:RSP::"
    

Here:

The colon delimiters clearly separate each field, facilitating straightforward parsing.

Receiver Operation and File Reassembly

The receiver processes incoming KISS frames via a FrameReader, parses packets, and reassembles file transfers. Key steps include:

Pseudocode: Receiver Main Loop

// Initialize connection and frame reader
conn = openConnection()         // TCP or serial
frameChan = newChannel()
reader = new FrameReader(conn, frameChan)
start(reader.Run())

transfers = {}   // Map FILEID -> Transfer state

while true:
    if frame received from frameChan:
        packet = parsePacket(frame)
        if packet is ACK:
            continue
        if no transfer exists for packet.FILEID:
            if packet.seq != 1:
                continue
            metadata = split(packet.payload, "|")
            transfers[packet.FILEID] = new Transfer(
                filename = metadata[2],
                origSize = metadata[3],
                compSize = metadata[4],
                md5 = metadata[5],
                encodingMethod = metadata[7],
                compress = (metadata[8] == "1"),
                totalPackets = metadata[9]
            )
            sendAck(conn, localCallsign, packet.sender, packet.FILEID, "0001")
        else:
            transfer = transfers[packet.FILEID]
            if packet.seq == 1:
                continue
            if packet.seq not in transfer.Packets:
                transfer.Packets[packet.seq] = packet.payload
            if packet.seq equals transfer.BurstTo:
                ackValue = computeCumulativeAck(transfer)
                sendAck(conn, localCallsign, packet.sender, packet.FILEID, ackValue)
    else if timeout:
        for each transfer in transfers:
            if inactivity exceeds retry interval:
                if retries not exceeded:
                    resend cumulative ACK for transfer
                else:
                    drop transfer
    for each transfer in transfers:
        if all expected packets received:
            fileData = concatenate packets in order
            if encodingMethod == 1:
                fileData = base64Decode(fileData)
            if compress:
                fileData = decompress(fileData)
            if md5(fileData) matches transfer.md5:
                processFile(fileData, transfer.filename)
            send final ACK (FIN-ACK)
            remove transfer from transfers
    

TCP Inactivity and Reconnection (Receiver)

To ensure robustness, the receiver monitors TCP inactivity. If no data is received within a configured deadline, the receiver reconnects.

Pseudocode: TCP Inactivity Monitor

tcp_timeout = configured deadline (e.g., 600 seconds)
while true:
    sleep(1 second)
    if currentTime - lastDataTime > tcp_timeout:
        log "Inactivity detected, reconnecting..."
        reader.Stop()
        conn.Close()
        loop until new connection is established:
            wait 5 seconds
            attempt new connection
        update lastDataTime
        restart FrameReader with new connection
    

Dynamic Sliding Window and ACK Retransmission

The sender implements a dynamic sliding window, while the receiver computes a cumulative ACK based on the highest contiguous packet received and retransmits ACKs if necessary.

function computeCumulativeAck(transfer):
    max_seq = 1
    for seq = 2 to transfer.totalPackets:
        if transfer.Packets contains seq:
            max_seq = seq
        else:
            break
    if max_seq == 1:
        return "0001"
    else:
        return "0001-" + toHex(max_seq, 4)
    

The ACK is then sent encapsulated within a KISS frame.

Example Command Line Usage

The receiver application can be invoked as follows:

Receiver Example

# Receiver that saves files to disk
./receiver --my-callsign MM3NDH-11 --host 0.0.0.0 --port 9001 --one-file --callsigns "MM5NDH-*,*-15"

# Receiver that outputs the received file to stdout (for piping)
./receiver --my-callsign MM3NDH-11 --host 0.0.0.0 --port 9001 --one-file --stdout

# Receiver that executes a file if its name matches the --execute parameter
./receiver --my-callsign MM3NDH-11 --host 0.0.0.0 --port 9001 --one-file --execute "update.sh"
      

Network Diagram

The diagram below shows a typical deployment scenario:

           +------------------------------------------+
           |                Sender                    |
           | (Serial TNC on 144 MHz, e.g., COM3)       |
           +----------------------+-------------------+
                                  | RF Link (144 MHz)
                                  |
                                  v
                      +------------------------------+
                      |  Proxy / Fileserver (CMD/RSP)|
                      | - Processes CMD packets      |
                      | - Forwards DATA and ACK      |
                      | - Monitors TCP activity      |
                      +--------------+---------------+
                                     | RF Link (433 MHz)
                                     |
                                     v
           +------------------------------------------+
           |               Receiver                   |
           | (TCP/Serial TNC on 433 MHz)              |
           | - Reassembles file from DATA packets     |
           | - Sends cumulative ACKs & FIN-ACK         |
           +------------------------------------------+
    

Summary and Final Remarks

The KISS File Transfer Protocol combines robust framing (using KISS and AX.25), explicit metadata, and dynamic control mechanisms to enable reliable file transfers in challenging RF environments.

With support for DATA, ACK, CMD, and RSP packets, optional zlib compression, and Base64 encoding on a per-chunk basis, the sender and receiver collaborate using dynamic sliding windows, adaptive timeouts, and reconnection logic to maximize throughput while ensuring data integrity.

CMD packets follow the format "<cmdID>:CMD:<command text>", and the corresponding RSP packets use the format "<cmdID>:RSP:<status>:<message>", where the cmdID links the request and its response.