// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

/**
 * @file grpcweb.h
 * @brief gRPC-Web message framing (DETWS_ENABLE_GRPC_WEB) - zero-heap length-prefixed frame
 *        builder + parser, the HTTP/1.1-reachable subset of gRPC that wraps the Protobuf
 *        codec (services/protobuf). gRPC proper needs HTTP/2; gRPC-Web rides the shipped
 *        HTTP/1.1 server/client.
 *
 * Each gRPC / gRPC-Web message is a 5-octet prefix then the body:
 * @code
 *   [flags(1)][length(4, big-endian)][body...]
 * @endcode
 *  - flags bit 0 = compressed (per the Message-Encoding header); bit 7 (0x80) marks a
 *    gRPC-Web trailers frame whose body is an HTTP/1.1-style trailer block
 *    (`grpc-status:0\r\ngrpc-message:...\r\n`) instead of a Protobuf message.
 *  - A response is zero or more message frames followed by exactly one trailers frame.
 *
 * The message body is an encoded Protobuf message (build it with the protobuf codec, then
 * frame it here). This is the framing layer only.
 *
 * @author  Douglas Quigg (dstroy0)
 * @date    2026
 */

#ifndef DETERMINISTICESPASYNCWEBSERVER_GRPCWEB_H
#define DETERMINISTICESPASYNCWEBSERVER_GRPCWEB_H

#include "ServerConfig.h"

#if DETWS_ENABLE_GRPC_WEB

#include <stddef.h>
#include <stdint.h>

#define GRPCWEB_FLAG_COMPRESSED 0x01
#define GRPCWEB_FLAG_TRAILER 0x80
#define GRPCWEB_PREFIX_LEN 5

/** @brief Frame a body: `[flags][len BE32][body]`. Returns total octets, or 0 on overflow. */
size_t grpcweb_frame(uint8_t *buf, size_t cap, uint8_t flags, const uint8_t *body, size_t body_len);

/** @brief Frame a (Protobuf) message; @p compressed sets the compressed flag. */
size_t grpcweb_frame_message(uint8_t *buf, size_t cap, const uint8_t *msg, size_t msg_len, bool compressed);

/**
 * @brief Build a trailers frame: `grpc-status:<status>\r\n` plus, when @p message is given,
 *        `grpc-message:<message>\r\n`, wrapped with the 0x80 trailer flag.
 * @return total octets written, or 0 on overflow.
 */
size_t grpcweb_frame_trailer(uint8_t *buf, size_t cap, int status, const char *message);

/** @brief One parsed frame; @ref body points INTO the source buffer. */
struct GrpcWebFrame
{
    uint8_t flags;
    bool compressed; ///< flags & 0x01
    bool trailer;    ///< flags & 0x80
    const uint8_t *body;
    size_t body_len;
};

/**
 * @brief Parse one frame at the head of [buf, buf+len).
 * @param consumed receives the frame's total length so the caller can advance.
 * @return true on a complete frame; false if fewer than the prefix + body octets are buffered.
 */
bool grpcweb_parse(const uint8_t *buf, size_t len, GrpcWebFrame *out, size_t *consumed);

/** @brief Extract `grpc-status` (an integer) from a trailers-frame body. */
bool grpcweb_trailer_status(const uint8_t *body, size_t len, int *status);

#endif // DETWS_ENABLE_GRPC_WEB

#endif // DETERMINISTICESPASYNCWEBSERVER_GRPCWEB_H
