/*
 * Part of DNS zone file validator `validns`.
 *
 * Copyright 2025-2026 OARC, Inc.
 * Copyright 2011-2025 Anton Berezin <tobez@tobez.org>
 * Modified BSD license.
 * (See LICENSE file in the distribution.)
 */
#include <sys/types.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#include "common.h"
#include "textparse.h"
#include "mempool.h"
#include "carp.h"
#include "rr.h"
#include "hex.h"

static struct rr* zonemd_parse(char* name, long ttl, int type, char* s)
{
    struct rr_zonemd* rr = getmem(sizeof(*rr));
    int               scheme, hash_algorithm;
    long long         i;
    int               expected_digest_length;

    /* Serial */
    i = extract_integer(&s, "serial", NULL);
    if (i < 0)
        return NULL;
    if (i > 4294967295UL)
        return bitch("serial is out of range");
    rr->serial = i;

    scheme = extract_integer(&s, "scheme", NULL);
    if (scheme < 0)
        return NULL;
    if (scheme != 1)
        return bitch("unsupported scheme %u (only scheme 1 is defined)", scheme);
    rr->scheme = scheme;

    hash_algorithm = extract_integer(&s, "hash algorithm", NULL);
    if (hash_algorithm < 0)
        return NULL;
    if (hash_algorithm == 1) {
        expected_digest_length = SHA384_BYTES; /* SHA-384 */
    } else if (hash_algorithm == 2) {
        expected_digest_length = SHA512_BYTES; /* SHA-512 */
    } else {
        return bitch("unsupported hash algorithm %u (only 1=SHA-384 and 2=SHA-512 are defined)", hash_algorithm);
    }
    rr->hash_algorithm = hash_algorithm;

    rr->digest = extract_hex_binary_data(&s, "digest", EXTRACT_EAT_WHITESPACE);
    if (rr->digest.length < 0)
        return NULL;

    if (rr->digest.length != expected_digest_length)
        return bitch("wrong digest length, expected %d bytes for hash algorithm %u", expected_digest_length, (unsigned int)rr->hash_algorithm);

    if (*s) {
        return bitch("garbage after valid ZONEMD data");
    }

    return store_record(type, name, ttl, rr);
}

static char* zonemd_human(struct rr* rrv)
{
    RRCAST(zonemd);
    char  prefix[64];
    char* hex = encode_hex_temp((const unsigned char*)rr->digest.data, rr->digest.length);
    int   pl  = snprintf(prefix, sizeof(prefix), "%u %d %d ",
        rr->serial, rr->scheme, rr->hash_algorithm);
    char* out;

    if (pl < 0 || pl >= (int)sizeof(prefix))
        return NULL;
    out = getmem_temp(pl + rr->digest.length * 2 + 1);
    memcpy(out, prefix, pl);
    memcpy(out + pl, hex, rr->digest.length * 2 + 1);
    return out;
}

static struct binary_data zonemd_wirerdata(struct rr* rrv)
{
    RRCAST(zonemd);

    return compose_binary_data("411d", 1,
        rr->serial, rr->scheme, rr->hash_algorithm, rr->digest);
}

struct rr_methods zonemd_methods = { zonemd_parse, zonemd_human, zonemd_wirerdata, NULL, NULL };
