/*
 * 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 <ctype.h>

#include "common.h"
#include "textparse.h"
#include "mempool.h"
#include "carp.h"
#include "rr.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 s[1024];
    char digest_hex[129]; /* Max 64 bytes * 2 + 1 for null terminator */
    int i;

    /* Convert digest to hex string */
    if (rr->digest.length > 64) {
        return "...";
    }
    for (i = 0; i < rr->digest.length; i++) {
        sprintf(digest_hex + i*2, "%02x", (unsigned char)rr->digest.data[i]);
    }
    digest_hex[rr->digest.length * 2] = '\0';

    snprintf(s, 1024, "%u %d %d %s",
             rr->serial, rr->scheme, rr->hash_algorithm, digest_hex);

    return quickstrdup_temp(s);
}

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 };
