/**
 * @file WSFrame.h
 * @brief WebSocket frame constants, opcode definitions, and frame-building helpers.
 *
 * RFC 6455 WebSocket framing layer utilities.
 */

#ifndef STREAMHUB_WSFRAME_H_
#define STREAMHUB_WSFRAME_H_

#include "CompilerTypes.h"
#include <string.h>

namespace StreamHub {

using MARTe::uint8;
using MARTe::uint16;
using MARTe::uint32;
using MARTe::uint64;

/*---------------------------------------------------------------------------*/
/* Opcodes                                                                    */
/*---------------------------------------------------------------------------*/

static const uint8 WS_OPCODE_CONTINUATION = 0x00u;
static const uint8 WS_OPCODE_TEXT         = 0x01u;
static const uint8 WS_OPCODE_BINARY       = 0x02u;
static const uint8 WS_OPCODE_CLOSE        = 0x08u;
static const uint8 WS_OPCODE_PING         = 0x09u;
static const uint8 WS_OPCODE_PONG         = 0x0Au;

static const uint8 WS_FIN_BIT             = 0x80u;
static const uint8 WS_MASK_BIT            = 0x80u;

/*---------------------------------------------------------------------------*/
/* Frame encoder (server → client, no masking)                               */
/*---------------------------------------------------------------------------*/

/**
 * @brief Encode a WebSocket frame header into @p buf.
 * @param buf       Output buffer (must have at least 10 bytes available).
 * @param opcode    WS_OPCODE_TEXT or WS_OPCODE_BINARY.
 * @param payloadLen  Number of payload bytes that follow the header.
 * @return Number of header bytes written (2, 4, or 10).
 */
inline uint32 WSEncodeHeader(uint8 *buf, uint8 opcode, uint64 payloadLen) {
    buf[0] = WS_FIN_BIT | opcode;
    if (payloadLen < 126u) {
        buf[1] = static_cast<uint8>(payloadLen);
        return 2u;
    } else if (payloadLen < 65536u) {
        buf[1] = 126u;
        buf[2] = static_cast<uint8>((payloadLen >> 8u) & 0xFFu);
        buf[3] = static_cast<uint8>(payloadLen & 0xFFu);
        return 4u;
    } else {
        buf[1] = 127u;
        for (int i = 7; i >= 0; i--) {
            buf[2 + i] = static_cast<uint8>(payloadLen & 0xFFu);
            payloadLen >>= 8u;
        }
        return 10u;
    }
}

/*---------------------------------------------------------------------------*/
/* Frame decoder (client → server, with masking)                             */
/*---------------------------------------------------------------------------*/

/**
 * @brief Parsed fields from a received WebSocket frame header.
 */
struct WSFrameHeader {
    bool   fin;
    uint8  opcode;
    bool   masked;
    uint64 payloadLen;
    uint8  maskKey[4];
    uint32 headerSize;  ///< Total header byte count (2 + 0|2|8 length + 0|4 mask)
};

/**
 * @brief Try to parse a WebSocket frame header from @p buf (bufLen available bytes).
 * @param buf     Received bytes.
 * @param bufLen  Number of bytes available.
 * @param hdr     Output: parsed header fields.
 * @return true if a complete header was parsed; false if more bytes are needed.
 */
inline bool WSParseHeader(const uint8 *buf, uint32 bufLen, WSFrameHeader &hdr) {
    if (bufLen < 2u) { return false; }

    hdr.fin     = (buf[0] & WS_FIN_BIT) != 0u;
    hdr.opcode  = buf[0] & 0x0Fu;
    hdr.masked  = (buf[1] & WS_MASK_BIT) != 0u;

    uint32 hdrSize = 2u;
    uint64 plen    = static_cast<uint64>(buf[1] & 0x7Fu);

    if (plen == 126u) {
        if (bufLen < 4u) { return false; }
        plen = (static_cast<uint64>(buf[2]) << 8u) | static_cast<uint64>(buf[3]);
        hdrSize += 2u;
    } else if (plen == 127u) {
        if (bufLen < 10u) { return false; }
        plen = 0u;
        for (int i = 0; i < 8; i++) {
            plen = (plen << 8u) | static_cast<uint64>(buf[2 + i]);
        }
        hdrSize += 8u;
    }

    if (hdr.masked) {
        if (bufLen < hdrSize + 4u) { return false; }
        hdr.maskKey[0] = buf[hdrSize];
        hdr.maskKey[1] = buf[hdrSize + 1u];
        hdr.maskKey[2] = buf[hdrSize + 2u];
        hdr.maskKey[3] = buf[hdrSize + 3u];
        hdrSize += 4u;
    } else {
        hdr.maskKey[0] = hdr.maskKey[1] = hdr.maskKey[2] = hdr.maskKey[3] = 0u;
    }

    hdr.payloadLen = plen;
    hdr.headerSize = hdrSize;
    return true;
}

/**
 * @brief XOR-unmask a masked WebSocket payload in place.
 * @param payload  Payload buffer.
 * @param len      Payload length.
 * @param mask     4-byte masking key.
 */
inline void WSUnmask(uint8 *payload, uint32 len, const uint8 mask[4]) {
    for (uint32 i = 0u; i < len; i++) {
        payload[i] ^= mask[i % 4u];
    }
}

} /* namespace StreamHub */

#endif /* STREAMHUB_WSFRAME_H_ */
