# Copyright (c) 2021 by the Zeek Project. See LICENSE for details.
#
# Trivial File Transfer Protocol
#
# Specs from https://tools.ietf.org/html/rfc1350

module TFTP;

import spicy;

# Common header for all messages:
#
#      2 bytes
# ---------------
# |  TFTP Opcode  |
#  ---------------

public type Packet = unit {
    # public top-level entry point for parsing
    op: uint16 &convert=Opcode($$);
    switch (self.op) {
        Opcode::RRQ -> rrq: Request(True);
        Opcode::WRQ -> wrq: Request(False);
        Opcode::DATA -> data: Data;
        Opcode::ACK -> ack: Acknowledgement;
        Opcode::ERROR -> error: Error;
        # In the case of TFTP we can continue parsing when seeing
        # an unsupported opcode, so simply ignore such packets.
        * -> : void; # Ignored.
    };
};

# TFTP supports five types of packets [...]:
#
# opcode  operation
#   1     Read request (RRQ)
#   2     Write request (WRQ)
#   3     Data (DATA)
#   4     Acknowledgment (ACK)
#   5     Error (ERROR)
#
#
# https://tools.ietf.org/html/rfc2347 adds an additional opcode:
#
# opcode  operation
#   6     Option acknowledgment (OACK)
#
type Opcode = enum {
    RRQ = 0x01,
    WRQ = 0x02,
    DATA = 0x03,
    ACK = 0x04,
    ERROR = 0x05,
};

# Figure 5-1: RRQ/WRQ packet
#
#  2 bytes     string    1 byte     string   1 byte
#  ------------------------------------------------
# | Opcode |  Filename  |   0  |    Mode    |   0  |
#  ------------------------------------------------

type Request = unit(is_read: bool) {
    filename: bytes &until=b"\x00";
    mode: bytes &until=b"\x00";

    on %done {
        spicy::accept_input();
    }
};

# Figure 5-2: DATA packet
#
#  2 bytes     2 bytes      n bytes
#   ----------------------------------
#  | Opcode |   Block #  |   Data     |
#   ----------------------------------

type Data = unit {
    num: uint16;
    data: bytes &eod;
};

# Figure 5-3: ACK packet
#
#  2 bytes     2 bytes
#  ---------------------
# | Opcode |   Block #  |
#  ---------------------

type Acknowledgement = unit {
    num: uint16;
};

#  Figure 5-4: ERROR packet
#
#  2 bytes     2 bytes      string    1 byte
#  -----------------------------------------
# | Opcode |  ErrorCode |   ErrMsg   |   0  |
#  -----------------------------------------

type Error = unit {
    code: uint16;
    msg: bytes &until=b"\x00";
};
