/**
 * @copyright Utilitech AS 2023-2026
 * License: Fair Source
 * 
 */

#include "HdlcParser.h"
#include "lwip/def.h"
#include "crc.h"

int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
    int len;

    uint8_t* ptr;
    if(ctx.length < 3)
        return DATA_PARSE_INCOMPLETE;

    HDLCHeader* h = (HDLCHeader*) d;
    ptr = (uint8_t*) &h[1];

    // Frame format type 3
    if((h->format & 0xF0) == 0xA0) {
        // Length field (11 lsb of format)
        len = (ntohs(h->format) & 0x7FF) + 2;
        if(len > ctx.length)
            return DATA_PARSE_INCOMPLETE;

        HDLCFooter* f = (HDLCFooter*) (d + len - sizeof *f);

        // First and last byte should be HDLC_FLAG
        if(h->flag != HDLC_FLAG || f->flag != HDLC_FLAG)
            return DATA_PARSE_BOUNDARY_FLAG_MISSING;

        // Verify FCS
        if(f->fcs > 0 && ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
            return DATA_PARSE_FOOTER_CHECKSUM_ERROR;

        // Skip destination address, LSB marks last byte
        while(((*ptr) & 0x01) == 0x00) {
            ptr++;
        }
        ptr++;

        // Skip source address, LSB marks last byte
        while(((*ptr) & 0x01) == 0x00) {
            ptr++;
        }
        ptr++;

        HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);

        // Verify HCS
        if(t3->hcs > 0 && ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
            return DATA_PARSE_HEADER_CHECKSUM_ERROR;
        ptr += 3;

        // Exclude all of header and 3 byte footer
        ctx.length -= ptr-d;
        if(ctx.length > 1) {
            ctx.length -= 3;
        }

        // Payload incomplete
        if((h->format & 0x08) == 0x08) {
            if(lastSequenceNumber == 0) {
                if(buf == NULL) buf = (uint8_t *)malloc((size_t)1024);
                pos = 0;
            }

            if(buf == NULL) return DATA_PARSE_FAIL;

            if((*ptr) == DATA_TAG_LLC) {
                ptr += 3; // Skip LLC
                ctx.length -= 3;
            }

            memcpy(buf + pos, ptr, ctx.length);
            pos += ctx.length;

            lastSequenceNumber++;
            return DATA_PARSE_INTERMEDIATE_SEGMENT;
        } else if(lastSequenceNumber > 0) {
            lastSequenceNumber = 0;
            if(buf == NULL) return DATA_PARSE_FAIL;

            if((*ptr) == DATA_TAG_LLC) {
                ptr += 3; // Skip LLC
                ctx.length -= 3;
            }

            memcpy(buf + pos, ptr, ctx.length);
            pos += ctx.length;

            memcpy((uint8_t *) d, buf, pos);
            free(buf);
            buf = NULL;
            ctx.length = pos;
            pos = 0;
            return DATA_PARSE_OK;
        } else {
            return ptr-d;
        }
    }
    return DATA_PARSE_UNKNOWN_DATA;
}