int main(void) {
	return_status status = return_status_init();

	//generate keys
	buffer_t *header_key = buffer_create_on_heap(crypto_aead_chacha20poly1305_KEYBYTES, crypto_aead_chacha20poly1305_KEYBYTES);
	buffer_t *message_key = buffer_create_on_heap(crypto_secretbox_KEYBYTES, crypto_secretbox_KEYBYTES);
	buffer_t *public_identity_key = buffer_create_on_heap(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE);
	buffer_t *public_ephemeral_key = buffer_create_on_heap(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE);
	buffer_t *public_prekey = buffer_create_on_heap(PUBLIC_KEY_SIZE, PUBLIC_KEY_SIZE);

	buffer_t *header = buffer_create_on_heap(4, 4);
	buffer_create_from_string(message, "Hello world!\n");

	buffer_t *packet = NULL;
	buffer_t *decrypted_header = NULL;


	if(sodium_init() == -1) {
		throw(INIT_ERROR, "Failed to initialize libsodium.");
	}

	//generate message
	header->content[0] = 0x01;
	header->content[1] = 0x02;
	header->content[2] = 0x03;
	header->content[3] = 0x04;
	molch_message_type packet_type = 1;
	printf("Packet type: %02x\n", packet_type);
	putchar('\n');

	//NORMAL MESSAGE
	printf("NORMAL MESSAGE\n");
	int status_int = 0;
	status = create_and_print_message(
			&packet,
			header_key,
			message_key,
			packet_type,
			header,
			message,
			NULL,
			NULL,
			NULL);
	throw_on_error(GENERIC_ERROR, "Failed to create and print message.");

	//now decrypt the header
	status = packet_decrypt_header(
			&decrypted_header,
			packet,
			header_key);
	throw_on_error(DECRYPT_ERROR, "Failed to decrypt the header.");


	if (decrypted_header->content_length != header->content_length) {
		throw(INVALID_VALUE, "Decrypted header isn't of the same length.");
	}
	printf("Decrypted header has the same length.\n\n");

	//compare headers
	if (buffer_compare(header, decrypted_header) != 0) {
		throw(INVALID_VALUE, "Decrypted header doesn't match.");
	}
	printf("Decrypted header matches.\n\n");

	//check if it decrypts manipulated packets (manipulated metadata)
	printf("Manipulating header length.\n");
	packet->content[2]++;
	status = packet_decrypt_header(
			&decrypted_header,
			packet,
			header_key);
	if (status.status == SUCCESS) {
		throw(GENERIC_ERROR, "Manipulated packet was accepted.");
	} else {
		return_status_destroy_errors(&status);
	}

	printf("Header manipulation detected.\n\n");

	//repair manipulation
	packet->content[2]--;
	//check if it decrypts manipulated packets (manipulated header)
	printf("Manipulate header.\n");
	packet->content[3 + crypto_aead_chacha20poly1305_NPUBBYTES + 1] ^= 0x12;
	status = packet_decrypt_header(
			&decrypted_header,
			packet,
			header_key);
	if (status.status == SUCCESS) {
		throw(GENERIC_ERROR, "Manipulated packet was accepted.");
	} else {
		return_status_destroy_errors(&status);
	}

	printf("Header manipulation detected!\n\n");

	//undo header manipulation
	packet->content[3 + crypto_aead_chacha20poly1305_NPUBBYTES + 1] ^= 0x12;

	//PREKEY MESSAGE
	printf("PREKEY_MESSAGE\n");
	//create the public keys
	status_int = buffer_fill_random(public_identity_key, PUBLIC_KEY_SIZE);
	if (status_int != 0) {
		throw(KEYGENERATION_FAILED, "Failed to generate public identity key.");
	}
	status_int = buffer_fill_random(public_ephemeral_key, PUBLIC_KEY_SIZE);
	if (status_int != 0) {
		throw(KEYGENERATION_FAILED, "Failed to generate public ephemeral key.");
	}
	status_int = buffer_fill_random(public_prekey, PUBLIC_KEY_SIZE);
	if (status_int != 0) {
		throw(KEYGENERATION_FAILED, "Failed to generate public prekey.");
	}

	buffer_destroy_from_heap_and_null_if_valid(packet);

	packet_type = PREKEY_MESSAGE;
	status = create_and_print_message(
			&packet,
			header_key,
			message_key,
			packet_type,
			header,
			message,
			public_identity_key,
			public_ephemeral_key,
			public_prekey);
	throw_on_error(GENERIC_ERROR, "Failed to crate and print message.");

	//now decrypt the header
	status = packet_decrypt_header(
			&decrypted_header,
			packet,
			header_key);
	throw_on_error(DECRYPT_ERROR, "Failed to decrypt the header.");

	if (decrypted_header->content_length != header->content_length) {
		throw(INVALID_VALUE, "Decrypted header isn't of the same length.");
	}
	printf("Decrypted header has the same length.\n\n");

	//compare headers
	if (buffer_compare(header, decrypted_header) != 0) {
		throw(INVALID_VALUE, "Decrypted header doesn't match.");
	}
	printf("Decrypted header matches.\n");

cleanup:
	buffer_destroy_from_heap_and_null_if_valid(header_key);
	buffer_destroy_from_heap_and_null_if_valid(message_key);
	buffer_destroy_from_heap_and_null_if_valid(header);
	buffer_destroy_from_heap_and_null_if_valid(public_identity_key);
	buffer_destroy_from_heap_and_null_if_valid(public_ephemeral_key);
	buffer_destroy_from_heap_and_null_if_valid(public_prekey);
	buffer_destroy_from_heap_and_null_if_valid(packet);
	buffer_destroy_from_heap_and_null_if_valid(decrypted_header);

	on_error {
		print_errors(&status);
	}
	return_status_destroy_errors(&status);

	return status.status;
}
int main(void) {
	sodium_init();

	//generate keys and message
	buffer_t *header_key = buffer_create(crypto_aead_chacha20poly1305_KEYBYTES, crypto_aead_chacha20poly1305_KEYBYTES);
	buffer_t *message_key = buffer_create(crypto_secretbox_KEYBYTES, crypto_secretbox_KEYBYTES);
	buffer_t *message = buffer_create_from_string("Hello world!\n");
	buffer_t *header = buffer_create(4, 4);
	header->content[0] = 0x01;
	header->content[1] = 0x02;
	header->content[2] = 0x03;
	header->content[3] = 0x04;
	buffer_t *packet = buffer_create(3 + crypto_aead_chacha20poly1305_NPUBBYTES + crypto_aead_chacha20poly1305_ABYTES + crypto_secretbox_NONCEBYTES + message->content_length + header->content_length + crypto_secretbox_MACBYTES + 255, 3 + crypto_aead_chacha20poly1305_NPUBBYTES + crypto_aead_chacha20poly1305_ABYTES + crypto_secretbox_NONCEBYTES + message->content_length + header->content_length + crypto_secretbox_MACBYTES + 255);
	const unsigned char packet_type = 1;
	printf("Packet type: %02x\n", packet_type);
	const unsigned char current_protocol_version = 2;
	printf("Current protocol version: %02x\n", current_protocol_version);
	const unsigned char highest_supported_protocol_version = 3;
	printf("Highest supported protocol version: %02x\n", highest_supported_protocol_version);
	putchar('\n');
	int status = create_and_print_message(
			packet,
			packet_type,
			current_protocol_version,
			highest_supported_protocol_version,
			message,
			message_key,
			header,
			header_key);
	buffer_clear(message_key);
	buffer_clear(message);
	if (status != 0) {
		buffer_clear(header_key);
		buffer_clear(header);
		return status;
	}

	//now decrypt the header
	buffer_t *decrypted_header = buffer_create(255, 255);
	buffer_t *decrypted_message_nonce = buffer_create(crypto_secretbox_NONCEBYTES, crypto_secretbox_NONCEBYTES);
	status = packet_decrypt_header(
			packet,
			decrypted_header,
			decrypted_message_nonce,
			header_key);
	if (status != 0) {
		fprintf(stderr, "ERROR: Failed to decrypt the header. (%i)\n", status);
		buffer_clear(header);
		buffer_clear(decrypted_header);
		buffer_clear(decrypted_message_nonce);
		buffer_clear(header_key);
		return status;
	}


	if (decrypted_header->content_length != header->content_length) {
		fprintf(stderr, "ERROR: Decrypted header isn't of the same length!\n");
		buffer_clear(header);
		buffer_clear(decrypted_header);
		buffer_clear(decrypted_message_nonce);
		buffer_clear(header_key);
		return EXIT_SUCCESS;
	}
	printf("Decrypted header has the same length.\n\n");

	printf("Decrypted message nonce (%zi Bytes):\n", decrypted_message_nonce->content_length);
	print_hex(decrypted_message_nonce);
	putchar('\n');
	buffer_clear(decrypted_message_nonce);

	//compare headers
	if (buffer_compare(header, decrypted_header) != 0) {
		fprintf(stderr, "ERROR: Decrypted header doesn't match!\n");
		buffer_clear(header);
		buffer_clear(decrypted_header);
		buffer_clear(header_key);
		return EXIT_FAILURE;
	}
	printf("Decrypted header matches.\n\n");

	//check if it decrypts manipulated packets (manipulated metadata)
	printf("Manipulating header length.\n");
	packet->content[2]++;
	status = packet_decrypt_header(
			packet,
			decrypted_header,
			decrypted_message_nonce,
			header_key);
	buffer_clear(decrypted_message_nonce);
	buffer_clear(decrypted_header);
	if (status == 0) { //header was decrypted despite manipulation
		fprintf(stderr, "ERROR: Manipulated packet was accepted!\n");
		buffer_clear(header);
		buffer_clear(header_key);
		return EXIT_FAILURE;
	}
	printf("Header manipulation detected.\n\n");

	//repair manipulation
	packet->content[2]--;
	//check if it decrypts manipulated packets (manipulated header)
	printf("Manipulate header.\n");
	packet->content[3 + crypto_aead_chacha20poly1305_NPUBBYTES + 1] ^= 0x12;
	status = packet_decrypt_header(
			packet,
			decrypted_header,
			decrypted_message_nonce,
			header_key);
	buffer_clear(decrypted_message_nonce);
	buffer_clear(decrypted_header);
	if (status == 0) { //header was decrypted desp
		fprintf(stderr, "ERROR: Manipulated packet was accepted!\n");
		buffer_clear(header);
		buffer_clear(header_key);
		return EXIT_FAILURE;
	}
	printf("Header manipulation detected!\n");

	//undo header manipulation
	packet->content[3 + crypto_aead_chacha20poly1305_NPUBBYTES + 1] ^= 0x12;

	buffer_clear(header);
	buffer_clear(header_key);

	return EXIT_SUCCESS;
}