#include "ipc_proto.h"

#include <stdlib.h>
#include <string.h>

#define ZVFS_REQ_HEADER_WIRE_SIZE  (sizeof(uint32_t) + sizeof(uint32_t))
#define ZVFS_RESP_HEADER_WIRE_SIZE (sizeof(uint32_t) + sizeof(int32_t) + sizeof(uint32_t))

static int write_bytes(uint8_t **p, size_t *remaining, const void *src, size_t n) {
    if (*remaining < n) {
        return -1;
    }
    memcpy(*p, src, n);
    *p += n;
    *remaining -= n;
    return 0;
}

static int read_bytes(const uint8_t **p, size_t *remaining, void *dst, size_t n) {
    if (*remaining < n) {
        return -1;
    }
    memcpy(dst, *p, n);
    *p += n;
    *remaining -= n;
    return 0;
}

static int write_u32(uint8_t **p, size_t *remaining, uint32_t v) {
    return write_bytes(p, remaining, &v, sizeof(v));
}

static int read_u32(const uint8_t **p, size_t *remaining, uint32_t *v) {
    return read_bytes(p, remaining, v, sizeof(*v));
}

static int write_s32(uint8_t **p, size_t *remaining, int32_t v) {
    return write_bytes(p, remaining, &v, sizeof(v));
}

static int read_s32(const uint8_t **p, size_t *remaining, int32_t *v) {
    return read_bytes(p, remaining, v, sizeof(*v));
}

static int write_u64(uint8_t **p, size_t *remaining, uint64_t v) {
    return write_bytes(p, remaining, &v, sizeof(v));
}

static int read_u64(const uint8_t **p, size_t *remaining, uint64_t *v) {
    return read_bytes(p, remaining, v, sizeof(*v));
}

static int valid_opcode(uint32_t opcode) {
    return opcode >= ZVFS_OP_CREATE && opcode <= ZVFS_OP_RESET_BLOBSTORE;
}

/* -------------------- header -------------------- */

size_t zvfs_serialize_req_header(const struct zvfs_req_header *header, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!header || !buf) {
        return 0;
    }
    if (write_u32(&p, &remaining, header->opcode) != 0) {
        return 0;
    }
    if (write_u32(&p, &remaining, header->payload_len) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_header(const uint8_t *buf, size_t buf_len, struct zvfs_req_header *header) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!header || !buf) {
        return 0;
    }
    if (read_u32(&p, &remaining, &header->opcode) != 0) {
        return 0;
    }
    if (read_u32(&p, &remaining, &header->payload_len) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_resp_header(const struct zvfs_resp_header *header, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!header || !buf) {
        return 0;
    }
    if (write_u32(&p, &remaining, header->opcode) != 0) {
        return 0;
    }
    if (write_s32(&p, &remaining, header->status) != 0) {
        return 0;
    }
    if (write_u32(&p, &remaining, header->payload_len) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_resp_header(const uint8_t *buf, size_t buf_len, struct zvfs_resp_header *header) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!header || !buf) {
        return 0;
    }
    if (read_u32(&p, &remaining, &header->opcode) != 0) {
        return 0;
    }
    if (read_s32(&p, &remaining, &header->status) != 0) {
        return 0;
    }
    if (read_u32(&p, &remaining, &header->payload_len) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

/* -------------------- request body -------------------- */

size_t zvfs_serialize_req_create(const struct zvfs_req_create_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->size_hint) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_create(const uint8_t *buf, size_t buf_len, struct zvfs_req_create_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->size_hint) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_open(const struct zvfs_req_open_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->blob_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_open(const uint8_t *buf, size_t buf_len, struct zvfs_req_open_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->blob_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_read(const struct zvfs_req_read_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0 ||
        write_u64(&p, &remaining, body->offset) != 0 ||
        write_u64(&p, &remaining, body->length) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_read(const uint8_t *buf, size_t buf_len, struct zvfs_req_read_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0 ||
        read_u64(&p, &remaining, &body->offset) != 0 ||
        read_u64(&p, &remaining, &body->length) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_write(const struct zvfs_req_write_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (body->length > 0 && !body->data) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0 ||
        write_u64(&p, &remaining, body->offset) != 0 ||
        write_u64(&p, &remaining, body->length) != 0 ||
        write_u32(&p, &remaining, body->flags) != 0) {
        return 0;
    }
    if (body->length > 0 && write_bytes(&p, &remaining, body->data, (size_t)body->length) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_write(const uint8_t *buf, size_t buf_len, struct zvfs_req_write_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    body->data = NULL;
    body->flags = 0;
    if (read_u64(&p, &remaining, &body->handle_id) != 0 ||
        read_u64(&p, &remaining, &body->offset) != 0 ||
        read_u64(&p, &remaining, &body->length) != 0 ||
        read_u32(&p, &remaining, &body->flags) != 0) {
        return 0;
    }

    if (body->length > remaining) {
        return 0;
    }

    if (body->length > 0) {
        body->data = malloc((size_t)body->length);
        if (!body->data) {
            return 0;
        }
        memcpy((void *)body->data, p, (size_t)body->length);
        p += body->length;
        remaining -= (size_t)body->length;
    }

    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_resize(const struct zvfs_req_resize_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0 ||
        write_u64(&p, &remaining, body->new_size) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_resize(const uint8_t *buf, size_t buf_len, struct zvfs_req_resize_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0 ||
        read_u64(&p, &remaining, &body->new_size) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_sync_md(const struct zvfs_req_sync_md_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_sync_md(const uint8_t *buf, size_t buf_len, struct zvfs_req_sync_md_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_close(const struct zvfs_req_close_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_close(const uint8_t *buf, size_t buf_len, struct zvfs_req_close_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_delete(const struct zvfs_req_delete_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->blob_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_delete(const uint8_t *buf, size_t buf_len, struct zvfs_req_delete_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->blob_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_add_ref(const struct zvfs_req_add_ref_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0 ||
        write_u32(&p, &remaining, body->ref_delta) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_add_ref(const uint8_t *buf, size_t buf_len, struct zvfs_req_add_ref_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0 ||
        read_u32(&p, &remaining, &body->ref_delta) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_req_add_ref_batch(const struct zvfs_req_add_ref_batch_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;
    uint32_t i;

    if (!body || !buf) {
        return 0;
    }
    if (body->item_count > 0 && !body->items) {
        return 0;
    }

    if (write_u32(&p, &remaining, body->item_count) != 0) {
        return 0;
    }
    for (i = 0; i < body->item_count; i++) {
        if (write_u64(&p, &remaining, body->items[i].handle_id) != 0 ||
            write_u32(&p, &remaining, body->items[i].ref_delta) != 0) {
            return 0;
        }
    }

    return (size_t)(p - buf);
}

size_t zvfs_deserialize_req_add_ref_batch(const uint8_t *buf, size_t buf_len, struct zvfs_req_add_ref_batch_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;
    uint32_t i = 0;
    struct zvfs_add_ref_item *items = NULL;

    if (!body || !buf) {
        return 0;
    }
    body->items = NULL;
    body->item_count = 0;

    if (read_u32(&p, &remaining, &body->item_count) != 0) {
        return 0;
    }
    if (body->item_count > 0) {
        items = calloc(body->item_count, sizeof(*items));
        if (!items) {
            return 0;
        }
        for (i = 0; i < body->item_count; i++) {
            if (read_u64(&p, &remaining, &items[i].handle_id) != 0 ||
                read_u32(&p, &remaining, &items[i].ref_delta) != 0) {
                free(items);
                return 0;
            }
        }
        body->items = items;
    }

    return (size_t)(p - buf);
}

/* -------------------- response body -------------------- */

size_t zvfs_serialize_resp_create(const struct zvfs_resp_create_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->blob_id) != 0 ||
        write_u64(&p, &remaining, body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_resp_create(const uint8_t *buf, size_t buf_len, struct zvfs_resp_create_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->blob_id) != 0 ||
        read_u64(&p, &remaining, &body->handle_id) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_resp_open(const struct zvfs_resp_open_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->handle_id) != 0 ||
        write_u64(&p, &remaining, body->size) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_resp_open(const uint8_t *buf, size_t buf_len, struct zvfs_resp_open_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->handle_id) != 0 ||
        read_u64(&p, &remaining, &body->size) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_resp_read(const struct zvfs_resp_read_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (body->length > 0 && !body->data) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->length) != 0) {
        return 0;
    }
    if (body->length > 0 && write_bytes(&p, &remaining, body->data, (size_t)body->length) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_resp_read(const uint8_t *buf, size_t buf_len, struct zvfs_resp_read_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    body->data = NULL;

    if (read_u64(&p, &remaining, &body->length) != 0) {
        return 0;
    }
    if (body->length > remaining) {
        return 0;
    }

    if (body->length > 0) {
        body->data = malloc((size_t)body->length);
        if (!body->data) {
            return 0;
        }
        memcpy(body->data, p, (size_t)body->length);
        p += body->length;
        remaining -= (size_t)body->length;
    }

    return (size_t)(p - buf);
}

size_t zvfs_serialize_resp_write(const struct zvfs_resp_write_body *body, uint8_t *buf, size_t buf_len) {
    uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (write_u64(&p, &remaining, body->bytes_written) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_deserialize_resp_write(const uint8_t *buf, size_t buf_len, struct zvfs_resp_write_body *body) {
    const uint8_t *p = buf;
    size_t remaining = buf_len;

    if (!body || !buf) {
        return 0;
    }
    if (read_u64(&p, &remaining, &body->bytes_written) != 0) {
        return 0;
    }
    return (size_t)(p - buf);
}

size_t zvfs_serialize_resp_resize(uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_deserialize_resp_resize(const uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_serialize_resp_sync_md(uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_deserialize_resp_sync_md(const uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_serialize_resp_close(uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_deserialize_resp_close(const uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_serialize_resp_delete(uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

size_t zvfs_deserialize_resp_delete(const uint8_t *buf, size_t buf_len) {
    (void)buf;
    (void)buf_len;
    return 0;
}

/* -------------------- compatibility wrapper: req -------------------- */

size_t zvfs_serialize_req(struct zvfs_req *req, uint8_t *buf, size_t buf_len) {
    struct zvfs_req_header header;
    size_t body_len = 0;

    if (!req || !buf || !valid_opcode(req->opcode)) {
        return 0;
    }
    if (buf_len < ZVFS_REQ_HEADER_WIRE_SIZE) {
        return 0;
    }

    switch (req->opcode) {
    case ZVFS_OP_CREATE: {
        struct zvfs_req_create_body body = { .size_hint = req->size_hint };
        body_len = zvfs_serialize_req_create(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                             buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_OPEN: {
        struct zvfs_req_open_body body = { .blob_id = req->blob_id };
        body_len = zvfs_serialize_req_open(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                           buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_READ: {
        struct zvfs_req_read_body body = {
            .handle_id = req->handle_id,
            .offset = req->offset,
            .length = req->length,
        };
        body_len = zvfs_serialize_req_read(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                           buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_WRITE: {
        struct zvfs_req_write_body body = {
            .handle_id = req->handle_id,
            .offset = req->offset,
            .length = req->length,
            .flags = req->write_flags,
            .data = req->data,
        };
        body_len = zvfs_serialize_req_write(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                            buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_RESIZE: {
        struct zvfs_req_resize_body body = {
            .handle_id = req->handle_id,
            .new_size = req->size_hint,
        };
        body_len = zvfs_serialize_req_resize(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                             buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_SYNC_MD: {
        struct zvfs_req_sync_md_body body = { .handle_id = req->handle_id };
        body_len = zvfs_serialize_req_sync_md(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                              buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_CLOSE: {
        struct zvfs_req_close_body body = { .handle_id = req->handle_id };
        body_len = zvfs_serialize_req_close(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                            buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_DELETE: {
        struct zvfs_req_delete_body body = { .blob_id = req->blob_id };
        body_len = zvfs_serialize_req_delete(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                             buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_ADD_REF: {
        struct zvfs_req_add_ref_body body = {
            .handle_id = req->handle_id,
            .ref_delta = req->ref_delta,
        };
        body_len = zvfs_serialize_req_add_ref(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                              buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_ADD_REF_BATCH: {
        struct zvfs_req_add_ref_batch_body body = {
            .item_count = req->add_ref_count,
            .items = req->add_ref_items,
        };
        body_len = zvfs_serialize_req_add_ref_batch(&body, buf + ZVFS_REQ_HEADER_WIRE_SIZE,
                                                    buf_len - ZVFS_REQ_HEADER_WIRE_SIZE);
        break;
    }
    case ZVFS_OP_RESET_BLOBSTORE:
        body_len = 0;
        break;
    default:
        return 0;
    }

    if (body_len == 0 && req->opcode != ZVFS_OP_RESET_BLOBSTORE) {
        return 0;
    }

    header.opcode = req->opcode;
    header.payload_len = (uint32_t)body_len;
    if (zvfs_serialize_req_header(&header, buf, buf_len) != ZVFS_REQ_HEADER_WIRE_SIZE) {
        return 0;
    }

    return ZVFS_REQ_HEADER_WIRE_SIZE + body_len;
}

size_t zvfs_deserialize_req(uint8_t *buf, size_t buf_len, struct zvfs_req *req) {
    struct zvfs_req_header header;
    size_t header_len;
    size_t total;
    size_t consumed = 0;
    const uint8_t *payload;

    if (!buf || !req) {
        return 0;
    }

    header_len = zvfs_deserialize_req_header(buf, buf_len, &header);
    if (header_len == 0 || !valid_opcode(header.opcode)) {
        return 0;
    }

    total = header_len + header.payload_len;
    if (buf_len < total) {
        return 0;
    }

    memset(req, 0, sizeof(*req));
    req->opcode = header.opcode;

    payload = buf + header_len;

    switch (header.opcode) {
    case ZVFS_OP_CREATE: {
        struct zvfs_req_create_body body;
        consumed = zvfs_deserialize_req_create(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->size_hint = body.size_hint;
        }
        break;
    }
    case ZVFS_OP_OPEN: {
        struct zvfs_req_open_body body;
        consumed = zvfs_deserialize_req_open(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->blob_id = body.blob_id;
        }
        break;
    }
    case ZVFS_OP_READ: {
        struct zvfs_req_read_body body;
        consumed = zvfs_deserialize_req_read(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
            req->offset = body.offset;
            req->length = body.length;
            req->data = malloc(req->length);
        }
        break;
    }
    case ZVFS_OP_WRITE: {
        struct zvfs_req_write_body body;
        consumed = zvfs_deserialize_req_write(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
            req->offset = body.offset;
            req->length = body.length;
            req->write_flags = body.flags;
            req->data = (void *)body.data;
        }
        break;
    }
    case ZVFS_OP_RESIZE: {
        struct zvfs_req_resize_body body;
        consumed = zvfs_deserialize_req_resize(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
            req->size_hint = body.new_size;
        }
        break;
    }
    case ZVFS_OP_SYNC_MD: {
        struct zvfs_req_sync_md_body body;
        consumed = zvfs_deserialize_req_sync_md(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
        }
        break;
    }
    case ZVFS_OP_CLOSE: {
        struct zvfs_req_close_body body;
        consumed = zvfs_deserialize_req_close(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
        }
        break;
    }
    case ZVFS_OP_DELETE: {
        struct zvfs_req_delete_body body;
        consumed = zvfs_deserialize_req_delete(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->blob_id = body.blob_id;
        }
        break;
    }
    case ZVFS_OP_ADD_REF: {
        struct zvfs_req_add_ref_body body;
        consumed = zvfs_deserialize_req_add_ref(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->handle_id = body.handle_id;
            req->ref_delta = body.ref_delta;
        }
        break;
    }
    case ZVFS_OP_ADD_REF_BATCH: {
        struct zvfs_req_add_ref_batch_body body;
        consumed = zvfs_deserialize_req_add_ref_batch(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            req->add_ref_count = body.item_count;
            req->add_ref_items = (struct zvfs_add_ref_item *)body.items;
        }
        break;
    }
    case ZVFS_OP_RESET_BLOBSTORE:
        if (header.payload_len != 0) {
            return 0;
        }
        consumed = 0;
        break;
    default:
        return 0;
    }

    if (consumed != header.payload_len) {
        if (req->data) {
            free(req->data);
            req->data = NULL;
        }
        if (req->add_ref_items) {
            free(req->add_ref_items);
            req->add_ref_items = NULL;
        }
        return 0;
    }

    return total;
}

/* -------------------- compatibility wrapper: resp -------------------- */

size_t zvfs_serialize_resp(struct zvfs_resp *resp, uint8_t *buf, size_t buf_len) {
    struct zvfs_resp_header header;
    size_t body_len = 0;

    if (!resp || !buf || !valid_opcode(resp->opcode)) {
        return 0;
    }
    if (buf_len < ZVFS_RESP_HEADER_WIRE_SIZE) {
        return 0;
    }

    if (resp->status == 0) {
        switch (resp->opcode) {
        case ZVFS_OP_CREATE: {
            struct zvfs_resp_create_body body = {
                .blob_id = resp->blob_id,
                .handle_id = resp->handle_id,
            };
            body_len = zvfs_serialize_resp_create(&body, buf + ZVFS_RESP_HEADER_WIRE_SIZE,
                                                  buf_len - ZVFS_RESP_HEADER_WIRE_SIZE);
            break;
        }
        case ZVFS_OP_OPEN: {
            struct zvfs_resp_open_body body = {
                .handle_id = resp->handle_id,
                .size = resp->size,
            };
            body_len = zvfs_serialize_resp_open(&body, buf + ZVFS_RESP_HEADER_WIRE_SIZE,
                                                buf_len - ZVFS_RESP_HEADER_WIRE_SIZE);
            break;
        }
        case ZVFS_OP_READ: {
            struct zvfs_resp_read_body body = {
                .length = resp->length,
                .data = resp->data,
            };
            body_len = zvfs_serialize_resp_read(&body, buf + ZVFS_RESP_HEADER_WIRE_SIZE,
                                                buf_len - ZVFS_RESP_HEADER_WIRE_SIZE);
            break;
        }
        case ZVFS_OP_WRITE: {
            struct zvfs_resp_write_body body = { .bytes_written = resp->bytes_written };
            body_len = zvfs_serialize_resp_write(&body, buf + ZVFS_RESP_HEADER_WIRE_SIZE,
                                                 buf_len - ZVFS_RESP_HEADER_WIRE_SIZE);
            break;
        }
        case ZVFS_OP_RESIZE:
        case ZVFS_OP_SYNC_MD:
        case ZVFS_OP_CLOSE:
        case ZVFS_OP_DELETE:
        case ZVFS_OP_ADD_REF:
        case ZVFS_OP_ADD_REF_BATCH:
        case ZVFS_OP_RESET_BLOBSTORE:
            body_len = 0;
            break;
        default:
            return 0;
        }

        if (resp->opcode <= ZVFS_OP_WRITE && body_len == 0) {
            return 0;
        }
    }

    header.opcode = resp->opcode;
    header.status = resp->status;
    header.payload_len = (uint32_t)body_len;

    if (zvfs_serialize_resp_header(&header, buf, buf_len) != ZVFS_RESP_HEADER_WIRE_SIZE) {
        return 0;
    }

    return ZVFS_RESP_HEADER_WIRE_SIZE + body_len;
}

size_t zvfs_deserialize_resp(uint8_t *buf, size_t buf_len, struct zvfs_resp *resp) {
    struct zvfs_resp_header header;
    size_t header_len;
    size_t total;
    size_t consumed = 0;
    const uint8_t *payload;

    if (!buf || !resp) {
        return 0;
    }

    header_len = zvfs_deserialize_resp_header(buf, buf_len, &header);
    if (header_len == 0 || !valid_opcode(header.opcode)) {
        return 0;
    }

    total = header_len + header.payload_len;
    if (buf_len < total) {
        return 0;
    }

    memset(resp, 0, sizeof(*resp));
    resp->opcode = header.opcode;
    resp->status = header.status;

    if (header.status != 0) {
        if (header.payload_len != 0) {
            return 0;
        }
        return total;
    }

    payload = buf + header_len;

    switch (header.opcode) {
    case ZVFS_OP_CREATE: {
        struct zvfs_resp_create_body body;
        consumed = zvfs_deserialize_resp_create(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            resp->blob_id = body.blob_id;
            resp->handle_id = body.handle_id;
        }
        break;
    }
    case ZVFS_OP_OPEN: {
        struct zvfs_resp_open_body body;
        consumed = zvfs_deserialize_resp_open(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            resp->handle_id = body.handle_id;
            resp->size = body.size;
        }
        break;
    }
    case ZVFS_OP_READ: {
        struct zvfs_resp_read_body body;
        consumed = zvfs_deserialize_resp_read(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            resp->length = body.length;
            resp->data = body.data;
        }
        break;
    }
    case ZVFS_OP_WRITE: {
        struct zvfs_resp_write_body body;
        consumed = zvfs_deserialize_resp_write(payload, header.payload_len, &body);
        if (consumed == header.payload_len) {
            resp->bytes_written = body.bytes_written;
        }
        break;
    }
    case ZVFS_OP_RESIZE:
    case ZVFS_OP_SYNC_MD:
    case ZVFS_OP_CLOSE:
    case ZVFS_OP_DELETE:
    case ZVFS_OP_ADD_REF:
    case ZVFS_OP_ADD_REF_BATCH:
    case ZVFS_OP_RESET_BLOBSTORE:
        if (header.payload_len != 0) {
            return 0;
        }
        consumed = 0;
        break;
    default:
        return 0;
    }

    if (consumed != header.payload_len) {
        if (resp->data) {
            free(resp->data);
            resp->data = NULL;
        }
        return 0;
    }

    return total;
}
