# @TEST-EXEC: zeek -b %INPUT
# @TEST-EXEC: TEST_DIFF_CANONIFIER= btest-diff .stdout
# @TEST-EXEC: TEST_DIFF_CANONIFIER=$SCRIPTS/diff-remove-abspath btest-diff .stderr

module A;

type Color: enum {
	Red = 10,
	White = 20,
	Blue = 30
};

type Foo: record {
	hello: string;
	t: bool;
	f: bool;
	n: count &optional;
	m: count &optional;  # not in input
	def: count &default = 123;
	i: int;
	pi: double;
	a: string_vec;
	c1: Color;
	p: port;
	ti: time;
	it: interval;
	ad: addr;
	s: subnet;
	re: pattern;
	su: subnet_set;
	se: set[addr, port];
	tbl: table[addr, port] of string;
};

event zeek_init()
	{
	local json = "{\"hello\":\"world\",\"t\":true,\"f\":false,\"se\":[[\"192.168.0.1\", \"80/tcp\"], [\"2001:db8::1\", \"8080/udp\"]],\"n\":null,\"i\":123,\"pi\":3.1416,\"a\":[\"1\",\"2\",\"3\",\"4\"],\"su\":[\"[aa:bb::0]/32\",\"192.168.0.0/16\"],\"c1\":\"A::Blue\",\"p\":\"1500/tcp\",\"it\":5000,\"ad\":\"127.0.0.1\",\"s\":\"[::1/128]\",\"re\":\"/a/\",\"ti\":1681652265.042767,\"tbl\":{\"[\\\"192.168.0.1\\\",\\\"80/tcp\\\"]\":\"foo\"}}";
	print from_json(json, Foo);
	}

@TEST-START-NEXT
# argument type mismatch
event zeek_init()
	{
	print from_json("[]", 10);
	}

@TEST-START-NEXT
# JSON parse error
event zeek_init()
	{
	print from_json("{\"hel", string_vec);
	}

@TEST-START-NEXT
type bool_t: bool;
type Foo: record {
	a: bool;
};

# type mismatch error
event zeek_init()
	{
	print from_json("[]", bool_t);
	print from_json("{\"a\": \"hello\"}", Foo);
	}

@TEST-START-NEXT
# type unsupport error
event zeek_init()
	{
	print from_json("[]", table_string_of_string);
	}

@TEST-START-NEXT
type port_t: port;
# additional & incorrect port formats
event zeek_init()
	{
	# Ports can also be given as objects:
	print from_json("{\"port\":80,\"proto\":\"tcp\"}", port_t);
	# These are violations:
	print from_json("\"80\"", port_t);
	print from_json("{}", port_t);
	}

@TEST-START-NEXT
type set_t: set[int, bool];
# index type doesn't match
event zeek_init()
	{
	print from_json("[[1, false], [2]]", set_t);
	print from_json("[[1, false], [2, 1]]", set_t);
	}

@TEST-START-NEXT
type pattern_t: pattern;
# pattern compile error
event zeek_init()
	{
	print from_json("\"/([[:print:]]{-}[[:alnum:]]foo)/\"", pattern_t);
	}

@TEST-START-NEXT
type Color: enum {
	Red = 10
};
# enum error
event zeek_init()
	{
	print from_json("\"Yellow\"", Color);
	}

@TEST-START-NEXT
# container null
event zeek_init()
	{
	print from_json("[\"fe80::/64\",null,\"192.168.0.0/16\"]", subnet_set);
	print from_json("[\"1\",null,\"3\",\"4\"]", string_vec);
	}

@TEST-START-NEXT
type Foo: record {
	hello: string;
	t: bool;
};
# record field null or missing
event zeek_init()
	{
	print from_json("{\"t\":null}", Foo);
	print from_json("{\"hello\": null, \"t\": true}", Foo);
	}

@TEST-START-NEXT
type Foo: record {
	hello: string;
};
# extra fields are alright
event zeek_init()
	{
	print from_json("{\"hello\": \"Hello!\", \"t\": true}", Foo);
	}

@TEST-START-NEXT
type Foo: record {
	id_field: string;
};
# test key_fun for n
event zeek_init()
	{
	print from_json("{\"id-field\": \"Hello!\"}", Foo);
	print from_json("{\"id-field\": \"Hello!\"}", Foo, function(s: string): string {
		return gsub(s, /[^a-zA-Z0-9_]/, "_");
	});
	}

@TEST-START-NEXT
# From: https://www.rfc-editor.org/rfc/rfc8520#section-9
global input:string = "{\"ietf-mud:mud\":{\"mud-version\":1,\"mud-url\":\"https://lighting.example.com/lightbulb2000\",\"last-update\":\"2019-01-28T11:20:51+01:00\",\"cache-validity\":48,\"is-supported\":true,\"systeminfo\":\"The BMS Example Light Bulb\",\"from-device-policy\":{\"access-lists\":{\"access-list\":[{\"name\":\"mud-76100-v6fr\"}]}},\"to-device-policy\":{\"access-lists\":{\"access-list\":[{\"name\":\"mud-76100-v6to\"}]}}},\"ietf-access-control-list:acls\":{\"acl\":[{\"name\":\"mud-76100-v6to\",\"type\":\"ipv6-acl-type\",\"aces\":{\"ace\":[{\"name\":\"cl0-todev\",\"matches\":{\"ipv6\":{\"ietf-acldns:src-dnsname\":\"test.example.com\",\"protocol\":6},\"tcp\":{\"ietf-mud:direction-initiated\":\"from-device\",\"source-port\":{\"operator\":\"eq\",\"port\":443}}},\"actions\":{\"forwarding\":\"accept\"}}]}},{\"name\":\"mud-76100-v6fr\",\"type\":\"ipv6-acl-type\",\"aces\":{\"ace\":[{\"name\":\"cl0-frdev\",\"matches\":{\"ipv6\":{\"ietf-acldns:dst-dnsname\":\"test.example.com\",\"protocol\":6},\"tcp\":{\"ietf-mud:direction-initiated\":\"from-device\",\"destination-port\":{\"operator\":\"eq\",\"port\":443}}},\"actions\":{\"forwarding\":\"accept\"}}]}}]}}";

module Mud8520;
type MudAccessList: record {
	name: string;
};

type MudAccessLists: record {
	access_list: vector of MudAccessList;
};

type MudPolicy: record {
	access_lists: MudAccessLists;
};

type MudMatchPort: record {
	operator: string;
	_port: count;
};

type MudMatch: record {
	ietf_acldns_dst_dnsname: string &optional;
	ietf_mud_direction_initiated: string &optional;
	destination_port: MudMatchPort &optional;
};

type MudMatches: record {
	# Might make more sense to have individual types.
	ipv6: MudMatch &optional;
	tcp: MudMatch &optional;
};

type MudAce: record {
	name: string;
	matches: MudMatches;
};

type MudAces: record {
	ace: vector of MudAce;
};

type MudAcl: record {
	name: string;
	_type: string;
	aces: MudAces;
};
type MudAcls: record {
	acl: vector of MudAcl;
};

type Mud: record {
	mud_version: count;
	mud_url: string;
	last_update: string;
	cache_validity: count;
	is_supported: bool;
	systeminfo: string;
	from_device_policy: MudPolicy;
	to_device_policy: MudPolicy;
};

type MudDocument: record {
	ietf_mud_mud: Mud;
	ietf_access_control_list_acls: MudAcls;
};

event zeek_init()
	{
	local mud_mapping_table: table[string] of string = {
		["type"] = "_type",
		["port"] = "_port",
	};
	local mud_json_key_fun = function [mud_mapping_table](s: string): string {
		if ( s in mud_mapping_table )
			return mud_mapping_table[s];
		return gsub(s, /[^a-zA-Z0-9_]/, "_");
	};

	local result = from_json(input, MudDocument, mud_json_key_fun);
	print result$valid;
	print result$v;
	}
