/* Creates test vector bolt04/blinded-onion-message-onion-test.json.  Run output through jq! */
static void maybe_print(const char *fmt, ...);
#define SUPERVERBOSE maybe_print
#include "config.h"
#include "../../wire/fromwire.c"
#include "../../wire/tlvstream.c"
#include "../../wire/towire.c"
#include "../amount.c"
#include "../bigsize.c"
#include "../blindedpath.c"
#include "../blindedpay.c"
#include "../blinding.c"
#include "../features.c"
#include "../hmac.c"
#include "../onion_encode.c"
#include "../onion_message_parse.c"
#include "../sciddir_or_pubkey.c"
#include "../sphinx.c"
#include "../../wire/onion_wiregen.c"
#include "../../wire/peer_wiregen.c"
#include <common/ecdh.h>
#include <common/setup.h>
#include <stdio.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for fromwire_channel_id */
bool fromwire_channel_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
			 struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "fromwire_channel_id called!\n"); abort(); }
/* Generated stub for fromwire_node_id */
void fromwire_node_id(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct node_id *id UNNEEDED)
{ fprintf(stderr, "fromwire_node_id called!\n"); abort(); }
/* Generated stub for new_onionreply */
struct onionreply *new_onionreply(const tal_t *ctx UNNEEDED, const u8 *contents TAKES UNNEEDED)
{ fprintf(stderr, "new_onionreply called!\n"); abort(); }
/* Generated stub for pubkey_from_node_id */
bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id UNNEEDED)
{ fprintf(stderr, "pubkey_from_node_id called!\n"); abort(); }
/* Generated stub for towire_channel_id */
void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "towire_channel_id called!\n"); abort(); }
/* Generated stub for towire_node_id */
void towire_node_id(u8 **pptr UNNEEDED, const struct node_id *id UNNEEDED)
{ fprintf(stderr, "towire_node_id called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

static bool comma;

static void flush_comma(void)
{
	if (comma)
		printf(",\n");
	comma = false;
}

static void maybe_comma(void)
{
	comma = true;
}

static void json_start(const char *name, const char what)
{
	flush_comma();
	if (name)
		printf("\"%s\":", name);
	printf("%c\n", what);
}

static void json_end(const char what)
{
	comma = false;
	printf("%c\n", what);
}

static void json_strfield(const char *name, const char *val)
{
	flush_comma();
	printf("\t\"%s\": \"%s\"", name, val);
	maybe_comma();
}

static void json_hexfield(const char *name, const u8 *val, size_t len)
{
	json_strfield(name, tal_hexstr(tmpctx, val, len));
}

static void json_hex_talfield(const char *name, const u8 *val)
{
	json_strfield(name, tal_hexstr(tmpctx, val, tal_bytelen(val)));
}

static void json_pubkey(const char *name, const struct pubkey *key)
{
	json_strfield(name, fmt_pubkey(tmpctx, key));
}

static bool enable_superverbose;
static void maybe_print(const char *fmt, ...)
{
	if (enable_superverbose) {
		va_list ap;
		va_start(ap, fmt);
		vfprintf(stdout, fmt, ap);
		va_end(ap);
	}
}

/* Updated each time, as we pretend to be Alice, Bob, Carol */
static const struct privkey *mykey;

void ecdh(const struct pubkey *point, struct secret *ss)
{
	if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey,
			   mykey->secret.data, NULL, NULL) != 1)
		abort();
}

/* This established by trial and error. */
#define LARGEST_DAVE_TLV_SIZE 42

/* Generic, ugly, function to calc encrypted_recipient_data,
   alias and next path_key, and print out JSON */
static u8 *add_hop(const char *name,
		   const char *comment,
		   const struct pubkey *me,
		   const struct pubkey *next,
		   const struct privkey *override_path_key,
		   const char *additional_field_hexstr,
		   const char *path_id_hexstr,
		   struct privkey *path_key, /* in and out */
		   struct pubkey *alias /* out */)
{
	struct tlv_encrypted_data_tlv *tlv = tlv_encrypted_data_tlv_new(tmpctx);
	u8 *enctlv, *encrypted_recipient_data;
	const u8 *additional;

	json_start(NULL, '{');
	json_strfield("alias", name);
	json_strfield("comment", comment);
	json_hexfield("path_key_secret", path_key->secret.data, sizeof(*path_key));

	/* We have to calc first, to make padding correct */
	tlv->next_node_id = tal_dup_or_null(tlv, struct pubkey, next);
	if (path_id_hexstr)
		tlv->path_id = tal_hexdata(tlv, path_id_hexstr,
					   strlen(path_id_hexstr));

	/* Normally we wouldn't know path_key privkey, and we'd just
	 * paste in the rest of the path as given, but here we're actually
	 * generating the lot. */
	if (override_path_key) {
		tlv->next_path_key_override = tal(tlv, struct pubkey);
		assert(pubkey_from_privkey(override_path_key, tlv->next_path_key_override));
	}

	/* This is assumed to be a valid TLV tuple, and greater than
	 * any previous, so we simply append. */
	if (additional_field_hexstr)
		additional = tal_hexdata(tlv, additional_field_hexstr,
					 strlen(additional_field_hexstr));
	else
		additional = NULL;

	enctlv = tal_arr(tmpctx, u8, 0);
	towire_tlv_encrypted_data_tlv(&enctlv, tlv);

	/* Now create padding (in Dave's path) */
	if (tal_bytelen(enctlv) + tal_bytelen(additional)
	    < LARGEST_DAVE_TLV_SIZE) {
		/* Add padding: T and L take 2 bytes, even before V */
		assert(tal_bytelen(enctlv) + tal_bytelen(additional) + 2
		       <= LARGEST_DAVE_TLV_SIZE);
		tlv->padding = tal_arrz(tlv, u8,
					LARGEST_DAVE_TLV_SIZE
					- tal_bytelen(enctlv)
					- tal_bytelen(additional) - 2);
	}
	enctlv = tal_arr(tmpctx, u8, 0);
	towire_tlv_encrypted_data_tlv(&enctlv, tlv);
	towire(&enctlv, additional, tal_bytelen(additional));
	if (!override_path_key)
		assert(tal_bytelen(enctlv) == LARGEST_DAVE_TLV_SIZE);

	json_start("tlvs", '{');
	if (tlv->padding)
		json_hex_talfield("padding", tlv->padding);
	if (tlv->next_node_id)
		json_pubkey("next_node_id", tlv->next_node_id);
	if (tlv->path_id)
		json_hex_talfield("path_id", tlv->path_id);
	if (tlv->next_path_key_override) {
		json_pubkey("next_path_key_override",
			    tlv->next_path_key_override);
		json_hexfield("path_key_override_secret",
			      override_path_key->secret.data,
			      sizeof(*override_path_key));
	}
	if (additional) {
		/* Deconstruct into type, len, value */
		size_t totlen = tal_bytelen(additional);
		u64 type = fromwire_bigsize(&additional, &totlen);
		u64 len = fromwire_bigsize(&additional, &totlen);
		assert(len == totlen);
		json_hexfield(tal_fmt(tmpctx, "unknown_tag_%"PRIu64, type),
			      additional, len);
	}
	json_end('}');

	maybe_comma();
	json_hex_talfield("encrypted_data_tlv", enctlv);
	flush_comma();
	enable_superverbose = true;
	encrypted_recipient_data = enctlv_from_encmsg_raw(tmpctx,
							  path_key,
							  me,
							  enctlv,
							  path_key,
							  alias);
	enable_superverbose = false;
	json_hex_talfield("encrypted_recipient_data",
			  encrypted_recipient_data);
	if (override_path_key)
		*path_key = *override_path_key;

	json_end('}');
	maybe_comma();
	return encrypted_recipient_data;
}

int main(int argc, char *argv[])
{
	const char *alias[] = {"Alice", "Bob", "Carol", "Dave"};
	struct privkey privkey[ARRAY_SIZE(alias)], path_key, override_path_key;
	struct pubkey id[ARRAY_SIZE(alias)], path_key_pub;
	struct secret session_key;
	u8 *erd[ARRAY_SIZE(alias)]; /* encrypted_recipient_data */
	u8 *onion_message_packet, *onion_message;
	struct pubkey blinded[ARRAY_SIZE(alias)];
	struct sphinx_path *sphinx_path;
	struct onionpacket *op;
	struct secret *path_secrets;

	common_setup(argv[0]);

	/* Alice is AAA... Bob is BBB... */
	for (size_t i = 0; i < ARRAY_SIZE(alias); i++) {
		memset(&privkey[i], alias[i][0], sizeof(privkey[i]));
		assert(pubkey_from_privkey(&privkey[i], &id[i]));
	}

	memset(&session_key, 3, sizeof(session_key));

	json_start(NULL, '{');
	json_strfield("comment", "Test vector creating an onionmessage, including joining an existing one");
	json_start("generate", '{');
        json_strfield("comment",  "This sections contains test data for Dave's blinded path Bob->Dave; sender has to prepend a hop to Alice to reach Bob");
	json_hexfield("session_key", session_key.data, sizeof(session_key));
	json_start("hops", '[');

	memset(&path_key, 99, sizeof(path_key));
	memset(&override_path_key, 1, sizeof(override_path_key));
	erd[0] = add_hop("Alice", "Alice->Bob: note next_path_key_override to match that give by Dave for Bob",
			 &id[0], &id[1],
			 &override_path_key, NULL, NULL,
			 &path_key, &blinded[0]);
	erd[1] = add_hop("Bob", "Bob->Carol",
			 &id[1], &id[2],
			 NULL, "fd023103123456", NULL,
			 &path_key, &blinded[1]);
	erd[2] = add_hop("Carol", "Carol->Dave",
			 &id[2], &id[3],
			 NULL, NULL, NULL,
			 &path_key, &blinded[2]);
	erd[3] = add_hop("Dave", "Dave is final node, hence path_id",
			 &id[3], NULL,
			 NULL, "fdffff0206c1",
			 "deadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0ffeedeadbeefbadc0",
			 &path_key, &blinded[3]);

	json_end(']');
	json_end('}');
	maybe_comma();

	memset(&path_key, 99, sizeof(path_key));
	assert(pubkey_from_privkey(&path_key, &path_key_pub));
	json_start("route", '{');
	json_strfield("comment", "The resulting blinded route Alice to Dave.");
	json_pubkey("introduction_node_id", &id[0]);
	json_pubkey("path_key", &path_key_pub);

	json_start("hops", '[');
	for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
		json_start(NULL, '{');
		if (i != 0)
			json_pubkey("blinded_node_id", &blinded[i]);
		json_hex_talfield("encrypted_recipient_data", erd[i]);
		json_end('}');
		maybe_comma();
	}
	json_end(']');
	json_end('}');
	maybe_comma();

	json_start("onionmessage", '{');
	json_strfield("comment", "An onion message which sends a 'hello' to Dave");
	json_strfield("unknown_tag_1", "68656c6c6f");

	/* Create the onionmessage */
	sphinx_path = sphinx_path_new(tmpctx, NULL, 0);
	for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
		struct tlv_onionmsg_tlv *tlv = tlv_onionmsg_tlv_new(tmpctx);
		u8 *onionmsg_tlv;
		tlv->encrypted_recipient_data = erd[i];
		onionmsg_tlv = tal_arr(tmpctx, u8, 0);
		/* For final hop, add unknown 'hello' field */
		if (i == ARRAY_SIZE(erd) - 1) {
			towire_bigsize(&onionmsg_tlv, 1); /* type */
			towire_bigsize(&onionmsg_tlv, strlen("hello")); /* length */
			towire(&onionmsg_tlv, "hello", strlen("hello"));
		}
		towire_tlv_onionmsg_tlv(&onionmsg_tlv, tlv);
		sphinx_add_hop(sphinx_path, &blinded[i], onionmsg_tlv);
	}

	/* Make it use our session key! */
	sphinx_path->session_key = &session_key;

	/* BOLT #4:
	 * - SHOULD set `onion_message_packet` `len` to 1366 or 32834.
	 */
	op = create_onionpacket(tmpctx, sphinx_path, ROUTING_INFO_SIZE,
				&path_secrets);
	onion_message_packet = serialize_onionpacket(tmpctx, op);
	json_hex_talfield("onion_message_packet", onion_message_packet);
	json_end('}');
	maybe_comma();

	json_start("decrypt", '{');
	json_strfield("comment", "This section contains the internal values generated by intermediate nodes when decrypting the onion.");

	onion_message = towire_onion_message(tmpctx, &path_key_pub, onion_message_packet);

	json_start("hops", '[');
	for (size_t i = 0; i < ARRAY_SIZE(erd); i++) {
		struct sciddir_or_pubkey next_node;
		struct tlv_onionmsg_tlv *final_om;
		struct pubkey final_alias;
		struct secret *final_path_id;

		json_start(NULL, '{');
		json_strfield("alias", alias[i]);
		json_hexfield("privkey", privkey[i].secret.data, sizeof(privkey[i]));
		json_hex_talfield("onion_message", onion_message);

		/* Now, do full decrypt code, to check */
		assert(fromwire_onion_message(tmpctx, onion_message,
					      &path_key_pub, &onion_message_packet));

		/* For test_ecdh */
		mykey = &privkey[i];
		assert(onion_message_parse(tmpctx, onion_message_packet, &path_key_pub,
					   &id[i],
					   &onion_message, &next_node,
					   &final_om,
					   &final_alias,
					   &final_path_id) == NULL);
		if (onion_message) {
			assert(next_node.is_pubkey);
			json_pubkey("next_node_id", &next_node.pubkey);
		} else {
			const struct tlv_field *hello;
			json_start("tlvs", '{');
			json_hexfield("path_id", final_path_id->data, sizeof(*final_path_id));
			hello = &final_om->fields[0];
			json_hexfield(tal_fmt(tmpctx,
					      "unknown_tag_%"PRIu64,
					      hello->numtype),
				      hello->value,
				      hello->length);
			json_end('}');
		}
		json_end('}');
		maybe_comma();
	}
	json_end(']');
	json_end('}');
	json_end('}');
	common_shutdown();
}
