/*
 * Copyright (C) 2023 Hasl Developers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <https://www.gnu.org/licenses/>.
 */

#include <glib.h>

#include <hasl.h>

/******************************************************************************
 * Tests
 *****************************************************************************/
static void
test_hasl_gs2_header_new(void) {
	HaslGs2Header *header = hasl_gs2_header_new();

	g_assert_true(HASL_IS_GS2_HEADER(header));

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_properties(void) {
	HaslGs2Header *header = NULL;
	HaslGs2HeaderChannelBinding channel_binding;
	char *channel_binding_name = NULL;
	char *authzid = NULL;
	gboolean standard_mechanism = FALSE;

	header = g_object_new(
		HASL_TYPE_GS2_HEADER,
		"standard-mechanism", FALSE,
		"channel-binding", HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
		"channel-binding-name", "weee",
		"authzid", "zerocool",
		NULL);

	g_assert_true(HASL_IS_GS2_HEADER(header));

	g_object_get(
		G_OBJECT(header),
		"standard-mechanism", &standard_mechanism,
		"channel-binding", &channel_binding,
		"channel-binding-name", &channel_binding_name,
		"authzid", &authzid,
		NULL);

	g_assert_false(standard_mechanism);
	g_assert_cmpuint(channel_binding, ==,
	                 HaslGs2HeaderChannelBindingClientSupportedServerUnknown);

	g_assert_cmpstr(channel_binding_name, ==, "weee");
	g_clear_pointer(&channel_binding_name, g_free);

	g_assert_cmpstr(authzid, ==, "zerocool");
	g_clear_pointer(&authzid, g_free);

	g_assert_finalize_object(header);
}

/******************************************************************************
 * to_string tests
 *****************************************************************************/
static void
test_hasl_gs2_header_to_string_default(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_no_error(error);
	g_assert_cmpstr(str, ==, "n,,");

	g_clear_pointer(&str, g_free);
	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_non_standard_mechanism(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_standard_mechanism(header, FALSE);

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_no_error(error);
	g_assert_cmpstr(str, ==, "F,n,,");

	g_clear_pointer(&str, g_free);
	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_channel_binding_client_supported(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header,
	                                    HaslGs2HeaderChannelBindingClientSupported);
	hasl_gs2_header_set_channel_binding_name(header, "weee");

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_no_error(error);
	g_assert_cmpstr(str, ==, "p=weee,,");

	g_clear_pointer(&str, g_free);
	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_channel_binding_client_supported_name_required(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header,
	                                    HaslGs2HeaderChannelBindingClientSupported);

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_error(error,
	               HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_NOT_SPECIFIED);
	g_clear_error(&error);
	g_assert_null(str);

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_channel_binding_client_supported_server_unknown(void)
{
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header,
	                                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown);
	hasl_gs2_header_set_channel_binding_name(header, "weee");

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_no_error(error);
	g_assert_cmpstr(str, ==, "y,,");

	g_clear_pointer(&str, g_free);
	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_channel_binding_unknown(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header, 0xFFFF);

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CB_FLAG_UNKNOWN);
	g_clear_error(&error);
	g_assert_null(str);

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_channel_binding_name_invalid(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;
	gboolean matched = FALSE;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header,
	                                    HaslGs2HeaderChannelBindingClientSupported);
	hasl_gs2_header_set_channel_binding_name(header, "🪦");

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_INVALID);

	matched = g_pattern_match_simple("channel binding name*contains invalid*",
	                                 error->message);
	g_assert_true(matched);

	g_clear_error(&error);
	g_assert_null(str);

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_authzid(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_channel_binding(header, 0xFF);

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_error(error,
	               HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CB_FLAG_UNKNOWN);
	g_clear_error(&error);
	g_assert_null(str);

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_to_string_unknown_channel_binding(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;
	char *str = NULL;

	header = hasl_gs2_header_new();
	hasl_gs2_header_set_authzid(header, "herman");

	str = hasl_gs2_header_to_string(header, &error);
	g_assert_no_error(error);
	g_assert_cmpstr(str, ==, "n,a=herman,");

	g_clear_pointer(&str, g_free);
	g_assert_finalize_object(header);
}

/******************************************************************************
 * Parse tests
 *****************************************************************************/
static inline void
test_hasl_gs2_parse(const char *input,
                    int *length,
                    gboolean expected_standard_mechanism,
                    HaslGs2HeaderChannelBinding expected_binding,
                    const char *expected_cbname,
                    const char *expected_authzid)
{
	HaslGs2Header *header = NULL;
	HaslGs2HeaderChannelBinding binding = 0;
	GError *error = NULL;
	const char *cbname = NULL;
	const char *authzid = NULL;

	header = hasl_gs2_header_parse_full(input, length, &error);
	g_assert_no_error(error);

	g_assert_true(HASL_IS_GS2_HEADER(header));

	if(expected_standard_mechanism) {
		g_assert_true(hasl_gs2_header_get_standard_mechanism(header));
	} else {
		g_assert_false(hasl_gs2_header_get_standard_mechanism(header));
	}

	binding = hasl_gs2_header_get_channel_binding(header);
	g_assert_cmpuint(binding, ==, expected_binding);

	cbname = hasl_gs2_header_get_channel_binding_name(header);
	g_assert_cmpstr(cbname, ==, expected_cbname);

	authzid = hasl_gs2_header_get_authzid(header);
	g_assert_cmpstr(authzid, ==, expected_authzid);

	g_assert_finalize_object(header);
}

static void
test_hasl_gs2_header_parse_rfc5801_no_binding(void) {
	test_hasl_gs2_parse("n,,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);
}

static void
test_hasl_gs2_header_parse_rfc5801_no_binding_authzid(void) {
	test_hasl_gs2_parse("n,a=someuser,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    "someuser");
}

static void
test_hasl_gs2_header_parse_rfc5801_nonstd_no_binding(void) {
	test_hasl_gs2_parse("F,n,,",
	                    NULL,
	                    FALSE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);
}

static void
test_hasl_gs2_header_parse_rfc5801_binding_tls_unique(void) {
	test_hasl_gs2_parse("p=tls-unique,,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    NULL);
}

static void
test_hasl_gs2_header_parse_rfc5801_binding_tls_unique_authzid(void) {
	test_hasl_gs2_parse("p=tls-unique,a=someuser,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    "someuser");
}

static void
test_hasl_gs2_header_parse_rfc5801_binding_tls_server_end_point(void) {
	test_hasl_gs2_parse("p=tls-server-end-point,,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-server-end-point",
	                    NULL);
}

static void
test_hasl_gs2_header_parse_rfc5801_binding_authzid(void) {
	test_hasl_gs2_parse("y,a=someuser,",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
	                    NULL,
	                    "someuser");
}

static void
test_hasl_gs2_header_parse_with_trailing(void) {
	test_hasl_gs2_parse("n,,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);

	test_hasl_gs2_parse("n,a=someuser,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    "someuser");

	test_hasl_gs2_parse("F,n,,a=foo",
	                    NULL,
	                    FALSE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);

	test_hasl_gs2_parse("p=tls-unique,,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    NULL);

	test_hasl_gs2_parse("p=tls-unique,a=someuser,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    "someuser");

	test_hasl_gs2_parse("p=tls-server-end-point,,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-server-end-point",
	                    NULL);

	test_hasl_gs2_parse("y,a=someuser,a=foo",
	                    NULL,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
	                    NULL,
	                    "someuser");
}

static void
test_hasl_gs2_header_parse_missing_cbname(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;

	header = hasl_gs2_header_parse("p,,", &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_NOT_SPECIFIED);
	g_clear_error(&error);
	g_assert_null(header);

	header = hasl_gs2_header_parse("p=,,", &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_NOT_SPECIFIED);
	g_clear_error(&error);
	g_assert_null(header);
}

static void
test_hasl_gs2_header_parse_unexpected_cbname(void) {
	HaslGs2Header *header = NULL;
	GError *error = NULL;

	header = hasl_gs2_header_parse("y=foo,,", &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_UNEXPECTED);
	g_clear_error(&error);
	g_assert_null(header);

	header = hasl_gs2_header_parse("n=bar,,", &error);
	g_assert_error(error, HASL_GS2_HEADER_ERROR,
	               HASL_GS2_HEADER_ERROR_CBNAME_UNEXPECTED);
	g_clear_error(&error);
	g_assert_null(header);
}

static void
test_hasl_gs2_header_parse_full_with_trailing(void) {
	int length = 0;

	test_hasl_gs2_parse("n,,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);
	g_assert_cmpint(length, ==, 3);

	length = 0;
	test_hasl_gs2_parse("n,a=someuser,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    "someuser");
	g_assert_cmpint(length, ==, 13);

	length = 0;
	test_hasl_gs2_parse("F,n,,a=foo",
	                    &length,
	                    FALSE,
	                    HaslGs2HeaderChannelBindingClientNotSupported,
	                    NULL,
	                    NULL);
	g_assert_cmpint(length, ==, 5);

	length = 0;
	test_hasl_gs2_parse("p=tls-unique,,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    NULL);
	g_assert_cmpint(length, ==, 14);

	length = 0;
	test_hasl_gs2_parse("p=tls-unique,a=someuser,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-unique",
	                    "someuser");
	g_assert_cmpint(length, ==, 24);

	length = 0;
	test_hasl_gs2_parse("p=tls-server-end-point,,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupported,
	                    "tls-server-end-point",
	                    NULL);
	g_assert_cmpint(length, ==, 24);

	length = 0;
	test_hasl_gs2_parse("y,a=someuser,a=foo",
	                    &length,
	                    TRUE,
	                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
	                    NULL,
	                    "someuser");
	g_assert_cmpint(length, ==, 13);
}


/******************************************************************************
 * Main
 *****************************************************************************/
int
main(int argc, char *argv[]) {
	g_test_init(&argc, &argv, NULL);

	g_test_add_func("/hasl/gs2-header/new", test_hasl_gs2_header_new);
	g_test_add_func("/hasl/gs2-header/properties",
	                test_hasl_gs2_header_properties);

	g_test_add_func("/hasl/gs2-header/to-string/default",
	                test_hasl_gs2_header_to_string_default);
	g_test_add_func("/hasl/gs2-header/to-string/non-standard-mechanism",
	                test_hasl_gs2_header_to_string_non_standard_mechanism);
	g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported",
	                test_hasl_gs2_header_to_string_channel_binding_client_supported);
	g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported-name-required",
	                test_hasl_gs2_header_to_string_channel_binding_client_supported_name_required);
	g_test_add_func("/hasl/gs2-header/to-string/channel-binding-client-supported-server-unknown",
	                test_hasl_gs2_header_to_string_channel_binding_client_supported_server_unknown);
	g_test_add_func("/hasl/gs2-header/to-string/channel-binding-unknown",
	                test_hasl_gs2_header_to_string_channel_binding_unknown);
	g_test_add_func("/hasl/gs2-header/to-string/channel-binding-name-invalid",
	                test_hasl_gs2_header_to_string_channel_binding_name_invalid);
	g_test_add_func("/hasl/gs2-header/to-string/authzid",
	                test_hasl_gs2_header_to_string_authzid);
	g_test_add_func("/hasl/gs2-header/to-string/unknown-channel-binding",
	                test_hasl_gs2_header_to_string_unknown_channel_binding);

	g_test_add_func("/hasl/gs2-header/parse/rfc5801/no-binding",
	                test_hasl_gs2_header_parse_rfc5801_no_binding);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/no-binding-authzid",
	                test_hasl_gs2_header_parse_rfc5801_no_binding_authzid);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/nonstd-no-binding",
	                test_hasl_gs2_header_parse_rfc5801_nonstd_no_binding);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-unique",
	                test_hasl_gs2_header_parse_rfc5801_binding_tls_unique);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-unique-authzid",
	                test_hasl_gs2_header_parse_rfc5801_binding_tls_unique_authzid);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-server-end-point",
	                test_hasl_gs2_header_parse_rfc5801_binding_tls_server_end_point);
	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-authzid",
	                test_hasl_gs2_header_parse_rfc5801_binding_authzid);

	g_test_add_func("/hasl/gs2-header/parse/with-trailing",
	                test_hasl_gs2_header_parse_with_trailing);
	g_test_add_func("/hasl/gs2-header/parse/missing-cbname",
	                test_hasl_gs2_header_parse_missing_cbname);
	g_test_add_func("/hasl/gs2-header/parse/unexpected-cbname",
	                test_hasl_gs2_header_parse_unexpected_cbname);

	g_test_add_func("/hasl/gs2-header/parse-full/with-trailing",
	                test_hasl_gs2_header_parse_full_with_trailing);

	return g_test_run();
}
