/*
 * Copyright (C) 2006-2020 Tobias Brunner
 * Copyright (C) 2005-2010 Martin Willi
 * Copyright (C) 2006 Daniel Roethlisberger
 * Copyright (C) 2005 Jan Hutter
 *
 * Copyright (C) secunet Security Networks AG
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * This program 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 General Public License
 * for more details.
 */

#include <stdlib.h>
#include <string.h>

#include "message.h"

#include <library.h>
#include <bio/bio_writer.h>
#include <collections/array.h>
#include <daemon.h>
#include <sa/ikev1/keymat_v1.h>
#include <encoding/generator.h>
#include <encoding/parser.h>
#include <encoding/payloads/encodings.h>
#include <encoding/payloads/payload.h>
#include <encoding/payloads/hash_payload.h>
#include <encoding/payloads/encrypted_payload.h>
#include <encoding/payloads/encrypted_fragment_payload.h>
#include <encoding/payloads/unknown_payload.h>
#include <encoding/payloads/cp_payload.h>
#include <encoding/payloads/fragment_payload.h>

/**
 * Max number of notify payloads per IKEv2 message
 */
#define MAX_NOTIFY_PAYLOADS 20

/**
 * Max number of delete payloads per IKEv2 message
 */
#define MAX_DELETE_PAYLOADS 20

/**
 * Max number of certificate payloads per IKEv2 message
 */
#define MAX_CERT_PAYLOADS 8

/**
 * Max number of vendor ID payloads per IKEv2 message
 */
#define MAX_VID_PAYLOADS 20

/**
 * Max number of certificate request payloads per IKEv1 message
 */
#define MAX_CERTREQ_PAYLOADS 20

/**
 * Max number of NAT-D payloads per IKEv1 message
 */
#define MAX_NAT_D_PAYLOADS 10

/**
 * Max number of unknown payloads per message
 */
#define MAX_UNKNOWN_PAYLOADS 5

/**
 * A payload rule defines the rules for a payload
 * in a specific message rule. It defines if and how
 * many times a payload must/can occur in a message
 * and if it must be encrypted.
 */
typedef struct {
	/* Payload type */
	 payload_type_t type;
	/* Minimal occurrence of this payload. */
	size_t min_occurrence;
	/* Max occurrence of this payload. */
	size_t max_occurrence;
	/* TRUE if payload must be encrypted */
	bool encrypted;
	/* If payload occurs, the message rule is fulfilled */
	bool sufficient;
} payload_rule_t;

/**
 * payload ordering structure allows us to reorder payloads according to RFC.
 */
typedef struct {
	/** payload type */
	payload_type_t type;
	/** notify type, if payload == PLV2_NOTIFY */
	notify_type_t notify;
} payload_order_t;

/**
 * A message rule defines the kind of a message,
 * if it has encrypted contents and a list
 * of payload ordering rules and payload parsing rules.
 */
typedef struct {
	/** Type of message. */
	exchange_type_t exchange_type;
	/** Is message a request or response. */
	bool is_request;
	/** Message contains encrypted payloads. */
	bool encrypted;
	/** Number of payload rules which will follow */
	int rule_count;
	/** Pointer to first payload rule */
	payload_rule_t *rules;
	/** Number of payload order rules */
	int order_count;
	/** payload ordering rules */
	payload_order_t *order;
} message_rule_t;

/**
 * Message rule for IKE_SA_INIT from initiator.
 */
static payload_rule_t ike_sa_init_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PLV2_SECURITY_ASSOCIATION,		1,	1,						FALSE,	FALSE},
	{PLV2_KEY_EXCHANGE,				1,	1,						FALSE,	FALSE},
	{PLV2_NONCE,					1,	1,						FALSE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
};

/**
 * payload order for IKE_SA_INIT initiator
 */
static payload_order_t ike_sa_init_i_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					COOKIE},
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NONCE,					0},
	{PLV2_NOTIFY,					NAT_DETECTION_SOURCE_IP},
	{PLV2_NOTIFY,					NAT_DETECTION_DESTINATION_IP},
	{PLV2_NOTIFY,					0},
	{PLV2_VENDOR_ID,				0},
};

/**
 * Message rule for IKE_SA_INIT from responder.
 */
static payload_rule_t ike_sa_init_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	TRUE},
	{PLV2_SECURITY_ASSOCIATION,		1,	1,						FALSE,	FALSE},
	{PLV2_KEY_EXCHANGE,				1,	1,						FALSE,	FALSE},
	{PLV2_NONCE,					1,	1,						FALSE,	FALSE},
	{PLV2_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	FALSE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
};

/**
 * payload order for IKE_SA_INIT responder
 */
static payload_order_t ike_sa_init_r_order[] = {
/*	payload type					notify type */
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NONCE,					0},
	{PLV2_NOTIFY,					NAT_DETECTION_SOURCE_IP},
	{PLV2_NOTIFY,					NAT_DETECTION_DESTINATION_IP},
	{PLV2_NOTIFY,					HTTP_CERT_LOOKUP_SUPPORTED},
	{PLV2_CERTREQ,					0},
	{PLV2_NOTIFY,					0},
	{PLV2_VENDOR_ID,				0},
};

/**
 * Message rule for IKE_AUTH from initiator.
 */
static payload_rule_t ike_auth_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_EAP,						0,	1,						TRUE,	TRUE},
	{PLV2_AUTH,						0,	1,						TRUE,	TRUE},
	{PLV2_ID_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		TRUE,	FALSE},
	{PLV2_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	TRUE,	FALSE},
	{PLV2_ID_RESPONDER,				0,	1,						TRUE,	FALSE},
#ifdef ME
	{PLV2_SECURITY_ASSOCIATION,		0,	1,						TRUE,	FALSE},
	{PLV2_TS_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_RESPONDER,				0,	1,						TRUE,	FALSE},
#else
	{PLV2_SECURITY_ASSOCIATION,		0,	1,						TRUE,	FALSE},
	{PLV2_TS_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_RESPONDER,				0,	1,						TRUE,	FALSE},
#endif /* ME */
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_AUTH initiator
 */
static payload_order_t ike_auth_i_order[] = {
/*	payload type					notify type */
	{PLV2_ID_INITIATOR,				0},
	{PLV2_CERTIFICATE,				0},
	{PLV2_NOTIFY,					INITIAL_CONTACT},
	{PLV2_NOTIFY,					HTTP_CERT_LOOKUP_SUPPORTED},
	{PLV2_CERTREQ,					0},
	{PLV2_ID_RESPONDER,				0},
	{PLV2_AUTH,						0},
	{PLV2_EAP,						0},
	{PLV2_CONFIGURATION,			0},
	{PLV2_NOTIFY,					IPCOMP_SUPPORTED},
	{PLV2_NOTIFY,					USE_TRANSPORT_MODE},
	{PLV2_NOTIFY,					ESP_TFC_PADDING_NOT_SUPPORTED},
	{PLV2_NOTIFY,					NON_FIRST_FRAGMENTS_ALSO},
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_TS_INITIATOR,				0},
	{PLV2_TS_RESPONDER,				0},
	{PLV2_NOTIFY,					MOBIKE_SUPPORTED},
	{PLV2_NOTIFY,					ADDITIONAL_IP4_ADDRESS},
	{PLV2_NOTIFY,					ADDITIONAL_IP6_ADDRESS},
	{PLV2_NOTIFY,					NO_ADDITIONAL_ADDRESSES},
	{PLV2_NOTIFY,					0},
	{PLV2_VENDOR_ID,				0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for IKE_AUTH from responder.
 */
static payload_rule_t ike_auth_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_EAP,						0,	1,						TRUE,	TRUE},
	{PLV2_AUTH,						0,	1,						TRUE,	TRUE},
	{PLV2_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		TRUE,	FALSE},
	{PLV2_ID_RESPONDER,				0,	1,						TRUE,	FALSE},
	{PLV2_SECURITY_ASSOCIATION,		0,	1,						TRUE,	FALSE},
	{PLV2_TS_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_RESPONDER,				0,	1,						TRUE,	FALSE},
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_AUTH responder
 */
static payload_order_t ike_auth_r_order[] = {
/*	payload type					notify type */
	{PLV2_ID_RESPONDER,				0},
	{PLV2_CERTIFICATE,				0},
	{PLV2_AUTH,						0},
	{PLV2_EAP,						0},
	{PLV2_CONFIGURATION,			0},
	{PLV2_NOTIFY,					IPCOMP_SUPPORTED},
	{PLV2_NOTIFY,					USE_TRANSPORT_MODE},
	{PLV2_NOTIFY,					ESP_TFC_PADDING_NOT_SUPPORTED},
	{PLV2_NOTIFY,					NON_FIRST_FRAGMENTS_ALSO},
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_TS_INITIATOR,				0},
	{PLV2_TS_RESPONDER,				0},
	{PLV2_NOTIFY,					AUTH_LIFETIME},
	{PLV2_NOTIFY,					MOBIKE_SUPPORTED},
	{PLV2_NOTIFY,					ADDITIONAL_IP4_ADDRESS},
	{PLV2_NOTIFY,					ADDITIONAL_IP6_ADDRESS},
	{PLV2_NOTIFY,					NO_ADDITIONAL_ADDRESSES},
	{PLV2_NOTIFY,					0},
	{PLV2_VENDOR_ID,				0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for INFORMATIONAL from initiator.
 */
static payload_rule_t informational_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_DELETE,					0,	MAX_DELETE_PAYLOADS,	TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for INFORMATIONAL initiator
 */
static payload_order_t informational_i_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					UPDATE_SA_ADDRESSES},
	{PLV2_NOTIFY,					NAT_DETECTION_SOURCE_IP},
	{PLV2_NOTIFY,					NAT_DETECTION_DESTINATION_IP},
	{PLV2_NOTIFY,					COOKIE2},
	{PLV2_NOTIFY,					0},
	{PLV2_DELETE,					0},
	{PLV2_CONFIGURATION,			0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for INFORMATIONAL from responder.
 */
static payload_rule_t informational_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_DELETE,					0,	MAX_DELETE_PAYLOADS,	TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for INFORMATIONAL responder
 */
static payload_order_t informational_r_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					UPDATE_SA_ADDRESSES},
	{PLV2_NOTIFY,					NAT_DETECTION_SOURCE_IP},
	{PLV2_NOTIFY,					NAT_DETECTION_DESTINATION_IP},
	{PLV2_NOTIFY,					COOKIE2},
	{PLV2_NOTIFY,					0},
	{PLV2_DELETE,					0},
	{PLV2_CONFIGURATION,			0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for CREATE_CHILD_SA from initiator.
 */
static payload_rule_t create_child_sa_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_SECURITY_ASSOCIATION,		1,	1,						TRUE,	FALSE},
	{PLV2_NONCE,					1,	1,						TRUE,	FALSE},
	{PLV2_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_RESPONDER,				0,	1,						TRUE,	FALSE},
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for CREATE_CHILD_SA from initiator.
 */
static payload_order_t create_child_sa_i_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					REKEY_SA},
	{PLV2_NOTIFY,					IPCOMP_SUPPORTED},
	{PLV2_NOTIFY,					USE_TRANSPORT_MODE},
	{PLV2_NOTIFY,					ESP_TFC_PADDING_NOT_SUPPORTED},
	{PLV2_NOTIFY,					NON_FIRST_FRAGMENTS_ALSO},
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_NONCE,					0},
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_TS_INITIATOR,				0},
	{PLV2_TS_RESPONDER,				0},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for CREATE_CHILD_SA from responder.
 */
static payload_rule_t create_child_sa_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_SECURITY_ASSOCIATION,		1,	1,						TRUE,	FALSE},
	{PLV2_NONCE,					1,	1,						TRUE,	FALSE},
	{PLV2_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_INITIATOR,				0,	1,						TRUE,	FALSE},
	{PLV2_TS_RESPONDER,				0,	1,						TRUE,	FALSE},
	{PLV2_CONFIGURATION,			0,	1,						TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for CREATE_CHILD_SA from responder.
 */
static payload_order_t create_child_sa_r_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					IPCOMP_SUPPORTED},
	{PLV2_NOTIFY,					USE_TRANSPORT_MODE},
	{PLV2_NOTIFY,					ESP_TFC_PADDING_NOT_SUPPORTED},
	{PLV2_NOTIFY,					NON_FIRST_FRAGMENTS_ALSO},
	{PLV2_SECURITY_ASSOCIATION,		0},
	{PLV2_NONCE,					0},
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_TS_INITIATOR,				0},
	{PLV2_TS_RESPONDER,				0},
	{PLV2_NOTIFY,					ADDITIONAL_TS_POSSIBLE},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for IKE_INTERMEDIATE from initiator.
 */
static payload_rule_t ike_intermediate_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_INTERMEDIATE initiator
 */
static payload_order_t ike_intermediate_i_order[] = {
/*	payload type					notify type */
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for IKE_INTERMEDIATE from responder.
 */
static payload_rule_t ike_intermediate_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_INTERMEDIATE responder
 */
static payload_order_t ike_intermediate_r_order[] = {
/*	payload type					notify type */
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for IKE_FOLLOWUP_KE from initiator.
 */
static payload_rule_t ike_followup_ke_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV2_KEY_EXCHANGE,				1,	1,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_FOLLOWUP_KE initiator
 */
static payload_order_t ike_followup_ke_i_order[] = {
/*	payload type					notify type */
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

/**
 * Message rule for IKE_FOLLOWUP_KE from responder.
 */
static payload_rule_t ike_followup_ke_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_FRAGMENT,					0,	1,						TRUE,	TRUE},
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_KEY_EXCHANGE,				1,	1,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for IKE_FOLLOWUP_KE responder
 */
static payload_order_t ike_followup_ke_r_order[] = {
/*	payload type					notify type */
	{PLV2_KEY_EXCHANGE,				0},
	{PLV2_NOTIFY,					0},
	{PLV2_FRAGMENT,					0},
};

#ifdef ME
/**
 * Message rule for ME_CONNECT from initiator.
 */
static payload_rule_t me_connect_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_ID_PEER,					1,	1,						TRUE,	FALSE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for ME_CONNECT from initiator.
 */
static payload_order_t me_connect_i_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					0},
	{PLV2_ID_PEER,					0},
	{PLV2_VENDOR_ID,				0},
};

/**
 * Message rule for ME_CONNECT from responder.
 */
static payload_rule_t me_connect_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV2_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	TRUE},
	{PLV2_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
};

/**
 * payload order for ME_CONNECT from responder.
 */
static payload_order_t me_connect_r_order[] = {
/*	payload type					notify type */
	{PLV2_NOTIFY,					0},
	{PLV2_VENDOR_ID,				0},
};
#endif /* ME */

#ifdef USE_IKEV1
/**
 * Message rule for ID_PROT from initiator.
 */
static payload_rule_t id_prot_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	1,						FALSE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						FALSE,	FALSE},
	{PLV1_NONCE,					0,	1,						FALSE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PLV1_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	FALSE,	FALSE},
	{PLV1_NAT_D,					0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_NAT_D_DRAFT_00_03,		0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_ID,						0,	1,						TRUE,	FALSE},
	{PLV1_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		TRUE,	FALSE},
	{PLV1_SIGNATURE,				0,	1,						TRUE,	FALSE},
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for ID_PROT from initiator.
 */
static payload_order_t id_prot_i_order[] = {
/*	payload type					notify type */
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_NONCE,					0},
	{PLV1_ID,						0},
	{PLV1_CERTIFICATE,				0},
	{PLV1_SIGNATURE,				0},
	{PLV1_HASH,						0},
	{PLV1_CERTREQ,					0},
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_NAT_D,					0},
	{PLV1_NAT_D_DRAFT_00_03,		0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for ID_PROT from responder.
 */
static payload_rule_t id_prot_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	1,						FALSE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						FALSE,	FALSE},
	{PLV1_NONCE,					0,	1,						FALSE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PLV1_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	FALSE,	FALSE},
	{PLV1_NAT_D,					0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_NAT_D_DRAFT_00_03,		0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_ID,						0,	1,						TRUE,	FALSE},
	{PLV1_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		TRUE,	FALSE},
	{PLV1_SIGNATURE,				0,	1,						TRUE,	FALSE},
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for ID_PROT from responder.
 */
static payload_order_t id_prot_r_order[] = {
/*	payload type					notify type */
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_NONCE,					0},
	{PLV1_ID,						0},
	{PLV1_CERTIFICATE,				0},
	{PLV1_SIGNATURE,				0},
	{PLV1_HASH,						0},
	{PLV1_CERTREQ,					0},
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_NAT_D,					0},
	{PLV1_NAT_D_DRAFT_00_03,		0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for AGGRESSIVE from initiator.
 */
static payload_rule_t aggressive_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	1,						FALSE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						FALSE,	FALSE},
	{PLV1_NONCE,					0,	1,						FALSE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PLV1_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	FALSE,	FALSE},
	{PLV1_NAT_D,					0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_NAT_D_DRAFT_00_03,		0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_ID,						0,	1,						FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		TRUE,	FALSE},
	{PLV1_SIGNATURE,				0,	1,						TRUE,	FALSE},
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for AGGRESSIVE from initiator.
 */
static payload_order_t aggressive_i_order[] = {
/*	payload type					notify type */
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_NONCE,					0},
	{PLV1_ID,						0},
	{PLV1_CERTIFICATE,				0},
	{PLV1_CERTREQ,					0},
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_HASH,						0},
	{PLV1_NAT_D,					0},
	{PLV1_NAT_D_DRAFT_00_03,		0},
	{PLV1_SIGNATURE,				0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for AGGRESSIVE from responder.
 */
static payload_rule_t aggressive_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	1,						FALSE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						FALSE,	FALSE},
	{PLV1_NONCE,					0,	1,						FALSE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		FALSE,	FALSE},
	{PLV1_CERTREQ,					0,	MAX_CERTREQ_PAYLOADS,	FALSE,	FALSE},
	{PLV1_NAT_D,					0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_NAT_D_DRAFT_00_03,		0,	MAX_NAT_D_PAYLOADS,		FALSE,	FALSE},
	{PLV1_ID,						0,	1,						FALSE,	FALSE},
	{PLV1_CERTIFICATE,				0,	MAX_CERT_PAYLOADS,		FALSE,	FALSE},
	{PLV1_SIGNATURE,				0,	1,						FALSE,	FALSE},
	{PLV1_HASH,						0,	1,						FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for AGGRESSIVE from responder.
 */
static payload_order_t aggressive_r_order[] = {
/*	payload type					notify type */
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_NONCE,					0},
	{PLV1_ID,						0},
	{PLV1_CERTIFICATE,				0},
	{PLV1_CERTREQ,					0},
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_NAT_D,					0},
	{PLV1_NAT_D_DRAFT_00_03,		0},
	{PLV1_SIGNATURE,				0},
	{PLV1_HASH,						0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for INFORMATIONAL_V1 from initiator.
 */
static payload_rule_t informational_i_rules_v1[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV1_DELETE,					0,	MAX_DELETE_PAYLOADS,	TRUE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
};

/**
 * payload order for INFORMATIONAL_V1 from initiator.
 */
static payload_order_t informational_i_order_v1[] = {
/*	payload type					notify type */
	{PLV1_NOTIFY,					0},
	{PLV1_DELETE,					0},
	{PLV1_VENDOR_ID,				0},
};

/**
 * Message rule for INFORMATIONAL_V1 from responder.
 */
static payload_rule_t informational_r_rules_v1[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV1_DELETE,					0,	MAX_DELETE_PAYLOADS,	TRUE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
};

/**
 * payload order for INFORMATIONAL_V1 from responder.
 */
static payload_order_t informational_r_order_v1[] = {
/*	payload type					notify type */
	{PLV1_NOTIFY,					0},
	{PLV1_DELETE,					0},
	{PLV1_VENDOR_ID,				0},
};

/**
 * Message rule for QUICK_MODE from initiator.
 */
static payload_rule_t quick_mode_i_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	2,						TRUE,	FALSE},
	{PLV1_NONCE,					0,	1,						TRUE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PLV1_ID,						0,	2,						TRUE,	FALSE},
	{PLV1_NAT_OA,					0,	2,						TRUE,	FALSE},
	{PLV1_NAT_OA_DRAFT_00_03,		0,	2,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for QUICK_MODE from initiator.
 */
static payload_order_t quick_mode_i_order[] = {
/*	payload type					notify type */
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_HASH,						0},
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_NONCE,					0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_ID,						0},
	{PLV1_NAT_OA,					0},
	{PLV1_NAT_OA_DRAFT_00_03,		0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for QUICK_MODE from responder.
 */
static payload_rule_t quick_mode_r_rules[] = {
/*	payload type					min	max						encr	suff */
	{PLV1_NOTIFY,					0,	MAX_NOTIFY_PAYLOADS,	TRUE,	FALSE},
	{PLV1_VENDOR_ID,				0,	MAX_VID_PAYLOADS,		TRUE,	FALSE},
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_SECURITY_ASSOCIATION,		0,	2,						TRUE,	FALSE},
	{PLV1_NONCE,					0,	1,						TRUE,	FALSE},
	{PLV1_KEY_EXCHANGE,				0,	1,						TRUE,	FALSE},
	{PLV1_ID,						0,	2,						TRUE,	FALSE},
	{PLV1_NAT_OA,					0,	2,						TRUE,	FALSE},
	{PLV1_NAT_OA_DRAFT_00_03,		0,	2,						TRUE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   TRUE,   FALSE},
	{PLV1_FRAGMENT,					0,	1,						FALSE,	TRUE},
};

/**
 * payload order for QUICK_MODE from responder.
 */
static payload_order_t quick_mode_r_order[] = {
/*	payload type					notify type */
	{PLV1_NOTIFY,					0},
	{PLV1_VENDOR_ID,				0},
	{PLV1_HASH,						0},
	{PLV1_SECURITY_ASSOCIATION,		0},
	{PLV1_NONCE,					0},
	{PLV1_KEY_EXCHANGE,				0},
	{PLV1_ID,						0},
	{PLV1_NAT_OA,					0},
	{PLV1_NAT_OA_DRAFT_00_03,		0},
	{PLV1_FRAGMENT,					0},
};

/**
 * Message rule for TRANSACTION.
 */
static payload_rule_t transaction_payload_rules_v1[] = {
/*	payload type					min	max	encr	suff */
	{PLV1_HASH,						0,	1,						TRUE,	FALSE},
	{PLV1_CONFIGURATION,			1,	1,						FALSE,	FALSE},
	{PL_UNKNOWN,					0,  MAX_UNKNOWN_PAYLOADS,   FALSE,  FALSE},
};

/**
 * Payload order for TRANSACTION.
 */
static payload_order_t transaction_payload_order_v1[] = {
/*	payload type					notify type */
	{PLV1_HASH,						0},
	{PLV1_CONFIGURATION,			0},
};

#endif /* USE_IKEV1 */

/**
 * Message rules, defines allowed payloads.
 */
static message_rule_t message_rules[] = {
	{IKE_SA_INIT,		TRUE,	FALSE,
		countof(ike_sa_init_i_rules), ike_sa_init_i_rules,
		countof(ike_sa_init_i_order), ike_sa_init_i_order,
	},
	{IKE_SA_INIT,		FALSE,	FALSE,
		countof(ike_sa_init_r_rules), ike_sa_init_r_rules,
		countof(ike_sa_init_r_order), ike_sa_init_r_order,
	},
	{IKE_AUTH,			TRUE,	TRUE,
		countof(ike_auth_i_rules), ike_auth_i_rules,
		countof(ike_auth_i_order), ike_auth_i_order,
	},
	{IKE_AUTH,			FALSE,	TRUE,
		countof(ike_auth_r_rules), ike_auth_r_rules,
		countof(ike_auth_r_order), ike_auth_r_order,
	},
	{INFORMATIONAL,		TRUE,	TRUE,
		countof(informational_i_rules), informational_i_rules,
		countof(informational_i_order), informational_i_order,
	},
	{INFORMATIONAL,		FALSE,	TRUE,
		countof(informational_r_rules), informational_r_rules,
		countof(informational_r_order), informational_r_order,
	},
	{CREATE_CHILD_SA,	TRUE,	TRUE,
		countof(create_child_sa_i_rules), create_child_sa_i_rules,
		countof(create_child_sa_i_order), create_child_sa_i_order,
	},
	{CREATE_CHILD_SA,	FALSE,	TRUE,
		countof(create_child_sa_r_rules), create_child_sa_r_rules,
		countof(create_child_sa_r_order), create_child_sa_r_order,
	},
	{IKE_INTERMEDIATE,	TRUE,	TRUE,
		countof(ike_intermediate_i_rules), ike_intermediate_i_rules,
		countof(ike_intermediate_i_order), ike_intermediate_i_order,
	},
	{IKE_INTERMEDIATE,			FALSE,	TRUE,
		countof(ike_intermediate_r_rules), ike_intermediate_r_rules,
		countof(ike_intermediate_r_order), ike_intermediate_r_order,
	},
	{IKE_FOLLOWUP_KE,	TRUE,	TRUE,
		countof(ike_followup_ke_i_rules), ike_followup_ke_i_rules,
		countof(ike_followup_ke_i_order), ike_followup_ke_i_order,
	},
	{IKE_FOLLOWUP_KE,	FALSE,	TRUE,
		countof(ike_followup_ke_r_rules), ike_followup_ke_r_rules,
		countof(ike_followup_ke_r_order), ike_followup_ke_r_order,
	},
#ifdef ME
	{ME_CONNECT,		TRUE,	TRUE,
		countof(me_connect_i_rules), me_connect_i_rules,
		countof(me_connect_i_order), me_connect_i_order,
	},
	{ME_CONNECT,		FALSE,	TRUE,
		countof(me_connect_r_rules), me_connect_r_rules,
		countof(me_connect_r_order), me_connect_r_order,
	},
#endif /* ME */
#ifdef USE_IKEV1
	{ID_PROT,			TRUE,	FALSE,
		countof(id_prot_i_rules), id_prot_i_rules,
		countof(id_prot_i_order), id_prot_i_order,
	},
	{ID_PROT,			FALSE,	FALSE,
		countof(id_prot_r_rules), id_prot_r_rules,
		countof(id_prot_r_order), id_prot_r_order,
	},
	{AGGRESSIVE,		TRUE,	FALSE,
		countof(aggressive_i_rules), aggressive_i_rules,
		countof(aggressive_i_order), aggressive_i_order,
	},
	{AGGRESSIVE,		FALSE,	FALSE,
		countof(aggressive_r_rules), aggressive_r_rules,
		countof(aggressive_r_order), aggressive_r_order,
	},
	{INFORMATIONAL_V1,	TRUE,	TRUE,
		countof(informational_i_rules_v1), informational_i_rules_v1,
		countof(informational_i_order_v1), informational_i_order_v1,
	},
	{INFORMATIONAL_V1,	FALSE,	TRUE,
		countof(informational_r_rules_v1), informational_r_rules_v1,
		countof(informational_r_order_v1), informational_r_order_v1,
	},
	{QUICK_MODE,		TRUE,	TRUE,
		countof(quick_mode_i_rules), quick_mode_i_rules,
		countof(quick_mode_i_order), quick_mode_i_order,
	},
	{QUICK_MODE,		FALSE,	TRUE,
		countof(quick_mode_r_rules), quick_mode_r_rules,
		countof(quick_mode_r_order), quick_mode_r_order,
	},
	{TRANSACTION,		TRUE,	TRUE,
		countof(transaction_payload_rules_v1), transaction_payload_rules_v1,
		countof(transaction_payload_order_v1), transaction_payload_order_v1,
	},
	{TRANSACTION,		FALSE,	TRUE,
		countof(transaction_payload_rules_v1), transaction_payload_rules_v1,
		countof(transaction_payload_order_v1), transaction_payload_order_v1,
	},
	/* TODO-IKEv1: define rules for other exchanges */
#endif /* USE_IKEV1 */
};

/**
 * Data for fragment reassembly.
 */
typedef struct {

	/**
	 * For IKEv1 the number of the last fragment (in case we receive them out
	 * of order), since the first one starts with 1 this defines the number of
	 * fragments we expect.
	 * For IKEv2 we store the total number of fragment we received last.
	 */
	uint16_t last;

	/**
	 * Length of all currently received fragments.
	 */
	size_t len;

	/**
	 * Maximum length of a fragmented packet.
	 */
	size_t max_packet;

} fragment_data_t;

typedef struct private_message_t private_message_t;

/**
 * Private data of an message_t object.
 */
struct private_message_t {

	/**
	 * Public part of a message_t object.
	 */
	message_t public;

	/**
	 * Minor version of message.
	 */
	uint8_t major_version;

	/**
	 * Major version of message.
	 */
	uint8_t minor_version;

	/**
	 * First Payload in message.
	 */
	payload_type_t first_payload;

	/**
	 * Assigned exchange type.
	 */
	exchange_type_t exchange_type;

	/**
	 * TRUE if message is a request, FALSE if a reply.
	 */
	bool is_request;

	/**
	 * The message is encrypted (IKEv1)
	 */
	bool is_encrypted;

	/**
	 * Higher version supported?
	 */
	bool version_flag;

	/**
	 * Reserved bits in IKE header
	 */
	bool reserved[2];

	/**
	 * Sorting of message disabled?
	 */
	bool sort_disabled;

	/**
	 * Message ID of this message.
	 */
	uint32_t message_id;

	/**
	 * ID of assigned IKE_SA.
	 */
	ike_sa_id_t *ike_sa_id;

	/**
	 * Assigned UDP packet, stores incoming packet or last generated one.
	 */
	packet_t *packet;

	/**
	 * Array of generated fragments (if any), as packet_t*.
	 * If defragmenting (i.e. frag != NULL) this contains fragment_t*
	 */
	array_t *fragments;

	/**
	 * Linked List where payload data are stored in.
	 */
	linked_list_t *payloads;

	 /**
	  * Assigned parser to parse Header and Body of this message.
	  */
	parser_t *parser;

	/**
	 * The message rule for this message instance
	 */
	message_rule_t *rule;

	/**
	 * Data used to reassemble a fragmented message
	 */
	fragment_data_t *frag;
};

/**
 * Maximum number of fragments we will handle
 */
#define MAX_FRAGMENTS 255

/**
 * A single fragment within a fragmented message
 */
typedef struct {

	/** fragment number */
	uint8_t num;

	/** fragment data */
	chunk_t data;

} fragment_t;

static void fragment_destroy(fragment_t *this)
{
	chunk_free(&this->data);
	free(this);
}

static void reset_defrag(private_message_t *this)
{
	array_destroy_function(this->fragments, (void*)fragment_destroy, NULL);
	this->fragments = NULL;
	this->frag->last = 0;
	this->frag->len = 0;
}

/**
 * Get the message rule that applies to this message
 */
static message_rule_t* get_message_rule(private_message_t *this)
{
	int i;

	for (i = 0; i < countof(message_rules); i++)
	{
		if ((this->exchange_type == message_rules[i].exchange_type) &&
			(this->is_request == message_rules[i].is_request))
		{
			return &message_rules[i];
		}
	}
	return NULL;
}

/**
 * Look up a payload rule
 */
static payload_rule_t* get_payload_rule(private_message_t *this,
										payload_type_t type)
{
	int i;

	for (i = 0; i < this->rule->rule_count;i++)
	{
		if (this->rule->rules[i].type == type)
		{
			return &this->rule->rules[i];
		}
	}
	return NULL;
}

METHOD(message_t, set_ike_sa_id, void,
	private_message_t *this,ike_sa_id_t *ike_sa_id)
{
	DESTROY_IF(this->ike_sa_id);
	this->ike_sa_id = ike_sa_id->clone(ike_sa_id);
}

METHOD(message_t, get_ike_sa_id, ike_sa_id_t*,
	private_message_t *this)
{
	return this->ike_sa_id;
}

METHOD(message_t, set_message_id, void,
	private_message_t *this,uint32_t message_id)
{
	this->message_id = message_id;
}

METHOD(message_t, get_message_id, uint32_t,
	private_message_t *this)
{
	return this->message_id;
}

METHOD(message_t, get_initiator_spi, uint64_t,
	private_message_t *this)
{
	return (this->ike_sa_id->get_initiator_spi(this->ike_sa_id));
}

METHOD(message_t, get_responder_spi, uint64_t,
	private_message_t *this)
{
	return (this->ike_sa_id->get_responder_spi(this->ike_sa_id));
}

METHOD(message_t, set_major_version, void,
	private_message_t *this, uint8_t major_version)
{
	this->major_version = major_version;
}

METHOD(message_t, get_major_version, uint8_t,
	private_message_t *this)
{
	return this->major_version;
}

METHOD(message_t, set_minor_version, void,
	private_message_t *this,uint8_t minor_version)
{
	this->minor_version = minor_version;
}

METHOD(message_t, get_minor_version, uint8_t,
	private_message_t *this)
{
	return this->minor_version;
}

METHOD(message_t, set_exchange_type, void,
	private_message_t *this, exchange_type_t exchange_type)
{
	this->exchange_type = exchange_type;
}

METHOD(message_t, get_exchange_type, exchange_type_t,
	private_message_t *this)
{
	return this->exchange_type;
}

METHOD(message_t, get_first_payload_type, payload_type_t,
	private_message_t *this)
{
	return this->first_payload;
}

METHOD(message_t, set_request, void,
	private_message_t *this, bool request)
{
	this->is_request = request;
}

METHOD(message_t, get_request, bool,
	private_message_t *this)
{
	return this->is_request;
}

METHOD(message_t, set_version_flag, void,
	private_message_t *this)
{
	this->version_flag = TRUE;
}

METHOD(message_t, get_reserved_header_bit, bool,
	private_message_t *this, u_int nr)
{
	if (nr < countof(this->reserved))
	{
		return this->reserved[nr];
	}
	return FALSE;
}

METHOD(message_t, set_reserved_header_bit, void,
	private_message_t *this, u_int nr)
{
	if (nr < countof(this->reserved))
	{
		this->reserved[nr] = TRUE;
	}
}

METHOD(message_t, is_encoded, bool,
	private_message_t *this)
{
	return this->packet->get_data(this->packet).ptr != NULL;
}

METHOD(message_t, is_fragmented, bool,
	private_message_t *this)
{
	return array_count(this->fragments) > 0;
}

METHOD(message_t, add_payload, void,
	private_message_t *this, payload_t *payload)
{
	payload_t *last_payload;

	if (this->payloads->get_count(this->payloads) > 0)
	{
		this->payloads->get_last(this->payloads, (void **)&last_payload);
		last_payload->set_next_type(last_payload, payload->get_type(payload));
	}
	else
	{
		this->first_payload = payload->get_type(payload);
	}
	payload->set_next_type(payload, PL_NONE);
	this->payloads->insert_last(this->payloads, payload);

	DBG2(DBG_ENC ,"added payload of type %N to message",
		 payload_type_names, payload->get_type(payload));
}

METHOD(message_t, add_notify, void,
	private_message_t *this, bool flush, notify_type_t type, chunk_t data)
{
	notify_payload_t *notify;
	payload_t *payload;

	if (flush)
	{
		while (this->payloads->remove_last(this->payloads,
												(void**)&payload) == SUCCESS)
		{
			payload->destroy(payload);
		}
	}
	if (this->major_version == IKEV2_MAJOR_VERSION)
	{
		notify = notify_payload_create(PLV2_NOTIFY);
	}
	else
	{
		notify = notify_payload_create(PLV1_NOTIFY);
	}
	notify->set_notify_type(notify, type);
	notify->set_notification_data(notify, data);
	add_payload(this, (payload_t*)notify);
}

METHOD(message_t, set_source, void,
	private_message_t *this, host_t *host)
{
	this->packet->set_source(this->packet, host);
}

METHOD(message_t, set_destination, void,
	private_message_t *this, host_t *host)
{
	this->packet->set_destination(this->packet, host);
}

METHOD(message_t, get_source, host_t*,
	private_message_t *this)
{
	return this->packet->get_source(this->packet);
}

METHOD(message_t, get_destination, host_t*,
	private_message_t *this)
{
	return this->packet->get_destination(this->packet);
}

METHOD(message_t, create_payload_enumerator, enumerator_t*,
	private_message_t *this)
{
	return this->payloads->create_enumerator(this->payloads);
}

METHOD(message_t, remove_payload_at, void,
	private_message_t *this, enumerator_t *enumerator)
{
	this->payloads->remove_at(this->payloads, enumerator);
}

METHOD(message_t, get_payload, payload_t*,
	private_message_t *this, payload_type_t type)
{
	payload_t *current, *found = NULL;
	enumerator_t *enumerator;

	enumerator = create_payload_enumerator(this);
	while (enumerator->enumerate(enumerator, &current))
	{
		if (current->get_type(current) == type)
		{
			found = current;
			break;
		}
	}
	enumerator->destroy(enumerator);
	return found;
}

METHOD(message_t, get_notify, notify_payload_t*,
	private_message_t *this, notify_type_t type)
{
	enumerator_t *enumerator;
	notify_payload_t *notify = NULL;
	payload_t *payload;

	enumerator = create_payload_enumerator(this);
	while (enumerator->enumerate(enumerator, &payload))
	{
		if (payload->get_type(payload) == PLV2_NOTIFY ||
			payload->get_type(payload) == PLV1_NOTIFY)
		{
			notify = (notify_payload_t*)payload;
			if (notify->get_notify_type(notify) == type)
			{
				break;
			}
			notify = NULL;
		}
	}
	enumerator->destroy(enumerator);
	return notify;
}

#if DEBUG_LEVEL >= 1
/**
 * get a string representation of the message
 */
static char* get_string(private_message_t *this, char *buf, int len)
{
	enumerator_t *enumerator;
	payload_t *payload;
	int written;
	char *pos = buf;

	memset(buf, 0, len);
	len--;

	written = snprintf(pos, len, "%N %s %u [",
					   exchange_type_names, this->exchange_type,
					   this->is_request ? "request" : "response",
					   this->message_id);
	if (written >= len || written < 0)
	{
		return "";
	}
	pos += written;
	len -= written;

	enumerator = create_payload_enumerator(this);
	while (enumerator->enumerate(enumerator, &payload))
	{
		written = snprintf(pos, len, " %N", payload_type_short_names,
						   payload->get_type(payload));
		if (written >= len || written < 0)
		{
			goto err;
		}
		pos += written;
		len -= written;
		if (payload->get_type(payload) == PLV2_NOTIFY ||
			payload->get_type(payload) == PLV1_NOTIFY)
		{
			notify_payload_t *notify;
			notify_type_t type;
			chunk_t data;

			notify = (notify_payload_t*)payload;
			type = notify->get_notify_type(notify);
			data = notify->get_notification_data(notify);
			if (type == MS_NOTIFY_STATUS && data.len == 4)
			{
				written = snprintf(pos, len, "(%N(%d))", notify_type_short_names,
								   type, untoh32(data.ptr));
			}
			else
			{
				written = snprintf(pos, len, "(%N)", notify_type_short_names,
								   type);
			}
			if (written >= len || written < 0)
			{
				goto err;
			}
			pos += written;
			len -= written;
		}
		if (payload->get_type(payload) == PLV2_EAP)
		{
			eap_payload_t *eap = (eap_payload_t*)payload;
			eap_type_t type;
			pen_t vendor;
			char method[64] = "";

			type = eap->get_type(eap, &vendor);
			if (type)
			{
				if (vendor)
				{
					snprintf(method, sizeof(method), "/%d-%N", type,
							 pen_short_names, vendor);
				}
				else
				{
					snprintf(method, sizeof(method), "/%N",
							 eap_type_short_names, type);
				}
			}
			written = snprintf(pos, len, "/%N%s", eap_code_short_names,
							   eap->get_code(eap), method);
			if (written >= len || written < 0)
			{
				goto err;
			}
			pos += written;
			len -= written;
		}
		if (payload->get_type(payload) == PLV2_CONFIGURATION ||
			payload->get_type(payload) == PLV1_CONFIGURATION)
		{
			cp_payload_t *cp = (cp_payload_t*)payload;
			enumerator_t *attributes;
			configuration_attribute_t *attribute;
			bool first = TRUE;
			char *pfx;

			switch (cp->get_type(cp))
			{
				case CFG_REQUEST:
					pfx = "RQ(";
					break;
				case CFG_REPLY:
					pfx = "RP(";
					break;
				case CFG_SET:
					pfx = "S(";
					break;
				case CFG_ACK:
					pfx = "A(";
					break;
				default:
					pfx = "(";
					break;
			}

			attributes = cp->create_attribute_enumerator(cp);
			while (attributes->enumerate(attributes, &attribute))
			{
				written = snprintf(pos, len, "%s%N", first ? pfx : " ",
								   configuration_attribute_type_short_names,
								   attribute->get_type(attribute));
				if (written >= len || written < 0)
				{
					attributes->destroy(attributes);
					goto err;
				}
				pos += written;
				len -= written;
				first = FALSE;
			}
			attributes->destroy(attributes);
			if (!first)
			{
				written = snprintf(pos, len, ")");
				if (written >= len || written < 0)
				{
					goto err;
				}
				pos += written;
				len -= written;
			}
		}
		if (payload->get_type(payload) == PLV1_FRAGMENT)
		{
			fragment_payload_t *frag;

			frag = (fragment_payload_t*)payload;
			if (frag->is_last(frag))
			{
				written = snprintf(pos, len, "(%u/%u)",
							frag->get_number(frag), frag->get_number(frag));
			}
			else
			{
				written = snprintf(pos, len, "(%u)", frag->get_number(frag));
			}
			if (written >= len || written < 0)
			{
				goto err;
			}
			pos += written;
			len -= written;
		}
		if (payload->get_type(payload) == PLV2_FRAGMENT)
		{
			encrypted_fragment_payload_t *frag;

			frag = (encrypted_fragment_payload_t*)payload;
			written = snprintf(pos, len, "(%u/%u)",
							   frag->get_fragment_number(frag),
							   frag->get_total_fragments(frag));
			if (written >= len || written < 0)
			{
				goto err;
			}
			pos += written;
			len -= written;
		}
		if (payload->get_type(payload) == PL_UNKNOWN)
		{
			unknown_payload_t *unknown;

			unknown = (unknown_payload_t*)payload;
			written = snprintf(pos, len, "(%d)", unknown->get_type(unknown));
			if (written >= len || written < 0)
			{
				goto err;
			}
			pos += written;
			len -= written;
		}
	}
	snprintf(pos, len, " ]");

err:
	enumerator->destroy(enumerator);
	return buf;
}
#endif

METHOD(message_t, disable_sort, void,
	private_message_t *this)
{
	this->sort_disabled = TRUE;
}

/**
 * reorder payloads depending on reordering rules
 */
static void order_payloads(private_message_t *this)
{
	linked_list_t *list;
	payload_t *payload;
	int i;

	DBG2(DBG_ENC, "order payloads in message");

	/* move to temp list */
	list = linked_list_create();
	while (this->payloads->remove_last(this->payloads,
									   (void**)&payload) == SUCCESS)
	{
		list->insert_first(list, payload);
	}
	/* for each rule, ... */
	for (i = 0; i < this->rule->order_count; i++)
	{
		enumerator_t *enumerator;
		notify_payload_t *notify;
		payload_order_t order;

		order = this->rule->order[i];

		/* ... find all payload ... */
		enumerator = list->create_enumerator(list);
		while (enumerator->enumerate(enumerator, &payload))
		{
			/* ... with that type ... */
			if (payload->get_type(payload) == order.type)
			{
				notify = (notify_payload_t*)payload;

				/**... and check notify for type. */
				if (order.type != PLV2_NOTIFY || order.notify == 0 ||
					order.notify == notify->get_notify_type(notify))
				{
					list->remove_at(list, enumerator);
					add_payload(this, payload);
				}
			}
		}
		enumerator->destroy(enumerator);
	}
	/* append all payloads without a rule to the end */
	while (list->remove_last(list, (void**)&payload) == SUCCESS)
	{
		/* do not complain about payloads in private use space */
		if (payload->get_type(payload) < 128)
		{
			DBG1(DBG_ENC, "payload %N has no ordering rule in %N %s",
				 payload_type_names, payload->get_type(payload),
				 exchange_type_names, this->rule->exchange_type,
				 this->rule->is_request ? "request" : "response");
		}
		add_payload(this, payload);
	}
	list->destroy(list);
}

/**
 * Wrap payloads in an encrypted payload
 */
static encrypted_payload_t* wrap_payloads(private_message_t *this)
{
	encrypted_payload_t *encrypted = NULL;
	linked_list_t *payloads;
	payload_t *current;

	/* move all payloads to a temporary list */
	payloads = linked_list_create();
	while (this->payloads->remove_first(this->payloads,
										(void**)&current) == SUCCESS)
	{
		if (current->get_type(current) == PLV2_FRAGMENT)
		{	/* treat encrypted fragment payload as encrypted payload */
			encrypted = (encrypted_payload_t*)current;
		}
		else
		{
			payloads->insert_last(payloads, current);
		}
	}
	if (encrypted)
	{	/* simply adopt all the unprotected payloads */
		this->payloads->destroy(this->payloads);
		this->payloads = payloads;
		return encrypted;
	}

	if (this->is_encrypted)
	{
		encrypted = encrypted_payload_create(PLV1_ENCRYPTED);
	}
	else
	{
		encrypted = encrypted_payload_create(PLV2_ENCRYPTED);
	}
	while (payloads->remove_first(payloads, (void**)&current) == SUCCESS)
	{
		payload_rule_t *rule;
		payload_type_t type;
		bool encrypt = TRUE;

		type = current->get_type(current);
		rule = get_payload_rule(this, type);
		if (rule)
		{
			encrypt = rule->encrypted;
		}
		if (encrypt || this->is_encrypted)
		{	/* encryption is forced for IKEv1 */
			DBG2(DBG_ENC, "insert payload %N into encrypted payload",
				 payload_type_names, type);
			encrypted->add_payload(encrypted, current);
		}
		else
		{
			DBG2(DBG_ENC, "insert payload %N unencrypted",
				 payload_type_names, type);
			add_payload(this, current);
		}
	}
	payloads->destroy(payloads);

	return encrypted;
}

/**
 * Creates the IKE header for this message
 */
static ike_header_t *create_header(private_message_t *this)
{
	ike_header_t *ike_header;
	bool *reserved;
	int i;

	ike_header = ike_header_create_version(this->major_version,
										   this->minor_version);
	ike_header->set_exchange_type(ike_header, this->exchange_type);
	ike_header->set_message_id(ike_header, this->message_id);
	if (this->major_version == IKEV2_MAJOR_VERSION)
	{
		ike_header->set_response_flag(ike_header, !this->is_request);
		ike_header->set_version_flag(ike_header, this->version_flag);
		ike_header->set_initiator_flag(ike_header,
						this->ike_sa_id->is_initiator(this->ike_sa_id));
	}
	else
	{
		ike_header->set_encryption_flag(ike_header, this->is_encrypted);
	}
	ike_header->set_initiator_spi(ike_header,
						this->ike_sa_id->get_initiator_spi(this->ike_sa_id));
	ike_header->set_responder_spi(ike_header,
						this->ike_sa_id->get_responder_spi(this->ike_sa_id));

	for (i = 0; i < countof(this->reserved); i++)
	{
		reserved = payload_get_field(&ike_header->payload_interface,
									 RESERVED_BIT, i);
		if (reserved)
		{
			*reserved = this->reserved[i];
		}
	}
	return ike_header;
}

/**
 * Generates the message, if needed, wraps the payloads in an encrypted payload.
 *
 * The generator and the possible encrypted payload are returned.  The latter
 * is not yet encrypted (but the transform is set).  It is also not added to
 * the payload list (so unless there are unprotected payloads that list will
 * be empty afterwards).
 */
static status_t generate_message(private_message_t *this, keymat_t *keymat,
				generator_t **out_generator, encrypted_payload_t **encrypted)
{
	keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
	generator_t *generator;
	payload_type_t next_type;
	enumerator_t *enumerator;
	aead_t *aead = NULL;
	chunk_t hash = chunk_empty;
	ike_header_t *ike_header;
	payload_t *payload, *next;
	bool encrypting = FALSE;

	if (this->exchange_type == EXCHANGE_TYPE_UNDEFINED)
	{
		DBG1(DBG_ENC, "exchange type is not defined");
		return INVALID_STATE;
	}

	if (this->packet->get_source(this->packet) == NULL ||
		this->packet->get_destination(this->packet) == NULL)
	{
		DBG1(DBG_ENC, "source/destination not defined");
		return INVALID_STATE;
	}

	this->rule = get_message_rule(this);
	if (!this->rule)
	{
		DBG1(DBG_ENC, "no message rules specified for this message type");
		return NOT_SUPPORTED;
	}

	if (!this->sort_disabled)
	{
		order_payloads(this);
	}

	if (keymat && keymat->get_version(keymat) == IKEV1)
	{
		/* get a hash for this message, if any is required */
		if (keymat_v1->get_hash_phase2(keymat_v1, &this->public, &hash))
		{	/* insert a HASH payload as first payload */
			hash_payload_t *hash_payload;

			hash_payload = hash_payload_create(PLV1_HASH);
			hash_payload->set_hash(hash_payload, hash);
			this->payloads->insert_first(this->payloads, hash_payload);
			if (this->exchange_type == INFORMATIONAL_V1)
			{
				this->is_encrypted = encrypting = TRUE;
			}
			chunk_free(&hash);
		}
	}

	if (this->major_version == IKEV2_MAJOR_VERSION)
	{
		encrypting = this->rule->encrypted;
	}
	else if (!encrypting)
	{
		/* If at least one payload requires encryption, encrypt the message.
		 * If no key material is available, the flag will be reset below. */
		enumerator = this->payloads->create_enumerator(this->payloads);
		while (enumerator->enumerate(enumerator, (void**)&payload))
		{
			payload_rule_t *rule;

			rule = get_payload_rule(this, payload->get_type(payload));
			if (rule && rule->encrypted)
			{
				this->is_encrypted = encrypting = TRUE;
				break;
			}
		}
		enumerator->destroy(enumerator);
	}

#if DEBUG_LEVEL >= 1
	char str[BUF_LEN];
	DBG1(DBG_ENC, "generating %s", get_string(this, str, sizeof(str)));
#endif

	if (keymat)
	{
		aead = keymat->get_aead(keymat, FALSE);
	}
	if (encrypting)
	{
		if (aead)
		{
			*encrypted = wrap_payloads(this);
			(*encrypted)->set_transform(*encrypted, aead);
		}
		else if (this->exchange_type == INFORMATIONAL ||
				 this->exchange_type == INFORMATIONAL_V1)
		{	/* allow sending unencrypted INFORMATIONALs */
			encrypting = FALSE;
		}
		else
		{
			DBG1(DBG_ENC, "unable to encrypt payloads without AEAD transform");
			return FAILED;
		}
	}
	if (!encrypting)
	{
		DBG2(DBG_ENC, "not encrypting payloads");
		this->is_encrypted = FALSE;
	}

	/* generate all payloads with proper next type */
	*out_generator = generator = generator_create();
	ike_header = create_header(this);
	payload = (payload_t*)ike_header;
	enumerator = create_payload_enumerator(this);
	while (enumerator->enumerate(enumerator, &next))
	{
		payload->set_next_type(payload, next->get_type(next));
		generator->generate_payload(generator, payload);
		payload = next;
	}
	enumerator->destroy(enumerator);

	next_type = PL_NONE;
	if (this->is_encrypted)
	{	/* for encrypted IKEv1 messages */
		next_type = (*encrypted)->payload_interface.get_next_type(
														(payload_t*)*encrypted);
	}
	else if (*encrypted)
	{	/* use proper IKEv2 encrypted (fragment) payload type */
		next_type = (*encrypted)->payload_interface.get_type(
														(payload_t*)*encrypted);
	}
	payload->set_next_type(payload, next_type);
	generator->generate_payload(generator, payload);
	ike_header->destroy(ike_header);
	return SUCCESS;
}

/**
 * Encrypts and adds the encrypted payload (if any) to the payload list and
 * finalizes the message generation.  Destroys the given generator.
 */
static status_t finalize_message(private_message_t *this, keymat_t *keymat,
						generator_t *generator, encrypted_payload_t *encrypted)
{
	keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
	chunk_t chunk;
	uint32_t *lenpos;

	if (encrypted)
	{
		if (this->is_encrypted)
		{	/* for IKEv1 instead of associated data we provide the IV */
			if (!keymat_v1->get_iv(keymat_v1, this->message_id, &chunk))
			{
				generator->destroy(generator);
				encrypted->destroy(encrypted);
				return FAILED;
			}
		}
		else
		{	/* build associated data (without header of encrypted payload) */
			chunk = generator->get_chunk(generator, &lenpos);
			/* fill in length, including encrypted payload */
			htoun32(lenpos, chunk.len + encrypted->get_length(encrypted));
		}
		this->payloads->insert_last(this->payloads, encrypted);
		if (encrypted->encrypt(encrypted, this->message_id, chunk) != SUCCESS)
		{
			generator->destroy(generator);
			return INVALID_STATE;
		}
		generator->generate_payload(generator, &encrypted->payload_interface);
	}
	chunk = generator->get_chunk(generator, &lenpos);
	htoun32(lenpos, chunk.len);
	this->packet->set_data(this->packet, chunk_clone(chunk));
	if (this->is_encrypted && this->exchange_type != INFORMATIONAL_V1)
	{
		/* update the IV for the next IKEv1 message */
		chunk_t last_block;
		aead_t *aead;
		size_t bs;

		aead = keymat->get_aead(keymat, FALSE);
		bs = aead->get_block_size(aead);
		last_block = chunk_create(chunk.ptr + chunk.len - bs, bs);
		if (!keymat_v1->update_iv(keymat_v1, this->message_id, last_block) ||
			!keymat_v1->confirm_iv(keymat_v1, this->message_id))
		{
			generator->destroy(generator);
			return FAILED;
		}
	}
	generator->destroy(generator);
	return SUCCESS;
}

METHOD(message_t, generate, status_t,
	private_message_t *this, keymat_t *keymat, packet_t **packet)
{
	generator_t *generator = NULL;
	encrypted_payload_t *encrypted = NULL;
	status_t status;

	status = generate_message(this, keymat, &generator, &encrypted);
	if (status != SUCCESS)
	{
		DESTROY_IF(generator);
		return status;
	}
	status = finalize_message(this, keymat, generator, encrypted);
	if (status != SUCCESS)
	{
		return status;
	}
	if (packet)
	{
		*packet = this->packet->clone(this->packet);
	}
	return SUCCESS;
}

METHOD(message_t, get_plain, bool,
	private_message_t *this, chunk_t *plain)
{
	generator_t *generator, *enc_generator;
	enumerator_t *enumerator;
	ike_header_t *ike_header;
	payload_t *payload;
	encrypted_payload_t *encrypted;
	chunk_t int_auth_a, enc_header, int_auth_p;
	struct {
		uint8_t next_payload;
		uint8_t flags;
		uint16_t length;
	} __attribute__((packed)) header = {};
	uint32_t *lenpos;

	if (this->major_version == IKEV1_MAJOR_VERSION ||
		this->exchange_type != IKE_INTERMEDIATE)
	{
		return FALSE;
	}

	/* we expect to be called after the message has either been parsed
	 * or already generated once, so we don't modify payload order */
	generator = generator_create_no_dbg();
	ike_header = create_header(this);
	payload = (payload_t*)ike_header;
	/* for parsed messages the payloads were already extracted from the
	 * encrypted payload, if there were any unprotected paylaods we wouldn't
	 * know. lets assume there aren't any (also for sent messages) */
	payload->set_next_type(payload, PLV2_ENCRYPTED);

	generator->generate_payload(generator, payload);
	int_auth_a = generator->get_chunk(generator, &lenpos);

	enc_generator = generator_create_no_dbg();
	this->payloads->get_first(this->payloads, (void**)&payload);
	if (payload && payload->get_type(payload) == PLV2_ENCRYPTED)
	{
		/* we have to generate only the contents of this payload,
		 * not the payload itself, the header is added manually */
		encrypted = (encrypted_payload_t*)payload;

		encrypted->generate_payloads(encrypted, enc_generator);

		header.next_payload = payload->get_next_type(payload);
	}
	else
	{
		/* as mentioned above, assume all received payloads were contained in an
		 * encrypted payload */
		enumerator = create_payload_enumerator(this);
		while (enumerator->enumerate(enumerator, &payload))
		{
			enc_generator->generate_payload(enc_generator, payload);
		}
		enumerator->destroy(enumerator);

		header.next_payload = this->first_payload;
	}
	int_auth_p = enc_generator->get_chunk(enc_generator, NULL);

	/* flags are currently no copied, but the critical bit and the reserved
	 * bits MUST be zero for encrypted payloads, so that's what we assume */
	enc_header = chunk_from_thing(header);
	header.length = htons(enc_header.len + int_auth_p.len);

	htoun32(lenpos, int_auth_a.len + enc_header.len + int_auth_p.len);
	*plain = chunk_cat("ccc", int_auth_a, enc_header, int_auth_p);

	enc_generator->destroy(enc_generator);
	generator->destroy(generator);
	ike_header->destroy(ike_header);
	return TRUE;
}

/**
 * Creates a (basic) clone of the given message
 */
static message_t *clone_message(private_message_t *this)
{
	message_t *message;

	message = message_create_from_packet(packet_clone_no_data(this->packet));
	message->set_major_version(message, this->major_version);
	message->set_minor_version(message, this->minor_version);
	message->set_ike_sa_id(message, this->ike_sa_id);
	message->set_message_id(message, this->message_id);
	message->set_request(message, this->is_request);
	message->set_exchange_type(message, this->exchange_type);
	memcpy(((private_message_t*)message)->reserved, this->reserved,
		   sizeof(this->reserved));
	return message;
}

/**
 * Create a single fragment with the given data
 */
static message_t *create_fragment(private_message_t *this, payload_type_t next,
								  uint16_t num, uint16_t count, chunk_t data)
{
	enumerator_t *enumerator;
	payload_t *fragment, *payload;
	message_t *message;
	peer_cfg_t *peer_cfg;
	ike_sa_t *ike_sa;

	message = clone_message(this);
	if (this->major_version == IKEV1_MAJOR_VERSION)
	{
		/* other implementations seem to just use 0 as message ID, so here we go */
		message->set_message_id(message, 0);
		/* always use the initial message type for fragments, even for quick mode
		 * or transaction messages. */
		ike_sa = charon->bus->get_sa(charon->bus);
		if (ike_sa && (peer_cfg = ike_sa->get_peer_cfg(ike_sa)) &&
			peer_cfg->has_option(peer_cfg, OPT_IKEV1_AGGRESSIVE))
		{
			message->set_exchange_type(message, AGGRESSIVE);
		}
		else
		{
			message->set_exchange_type(message, ID_PROT);
		}
		fragment = (payload_t*)fragment_payload_create_from_data(
													num, num == count, data);
	}
	else
	{
		fragment = (payload_t*)encrypted_fragment_payload_create_from_data(
													num, count, data);
		if (num == 1)
		{
			/* only in the first fragment is this set to the type of the first
			 * payload in the encrypted payload */
			fragment->set_next_type(fragment, next);
			/* move unprotected payloads to the first fragment */
			enumerator = this->payloads->create_enumerator(this->payloads);
			while (enumerator->enumerate(enumerator, &payload))
			{
				if (payload->get_type(payload) != PLV2_ENCRYPTED)
				{
					this->payloads->remove_at(this->payloads, enumerator);
					message->add_payload(message, payload);
				}
			}
			enumerator->destroy(enumerator);
		}
	}
	message->add_payload(message, (payload_t*)fragment);
	return message;
}

/**
 * Destroy all fragments
 */
static void clear_fragments(private_message_t *this)
{
	array_destroy_offset(this->fragments, offsetof(packet_t, destroy));
	this->fragments = NULL;
}

/**
 * Reduce the fragment length but ensure it stays > 0
 */
#define REDUCE_FRAG_LEN(fl, amount) ({ \
	fl = max(1, (ssize_t)fl - (amount)); \
})

METHOD(message_t, fragment, status_t,
	private_message_t *this, keymat_t *keymat, size_t frag_len,
	enumerator_t **fragments)
{
	encrypted_payload_t *encrypted = NULL;
	generator_t *generator = NULL;
	message_t *fragment;
	packet_t *packet;
	payload_type_t next = PL_NONE;
	uint16_t num, count;
	host_t *src, *dst;
	chunk_t data;
	status_t status;
	size_t len;

	src = this->packet->get_source(this->packet);
	dst = this->packet->get_destination(this->packet);
	/* frag_len is the complete IP datagram length, account for overhead (we
	 * assume no IP options/extension headers are used) */
	REDUCE_FRAG_LEN(frag_len, (src->get_family(src) == AF_INET) ? 20 : 40);
	/* 8 (UDP header) */
	REDUCE_FRAG_LEN(frag_len, 8);
	if (dst->get_port(dst) != IKEV2_UDP_PORT &&
		src->get_port(src) != IKEV2_UDP_PORT)
	{	/* reduce length due to non-ESP marker */
		REDUCE_FRAG_LEN(frag_len, 4);
	}

	if (is_encoded(this))
	{
		if (this->major_version == IKEV2_MAJOR_VERSION)
		{
			encrypted = (encrypted_payload_t*)get_payload(this, PLV2_ENCRYPTED);
		}
		data = this->packet->get_data(this->packet);
		len = data.len;
	}
	else
	{
		status = generate_message(this, keymat, &generator, &encrypted);
		if (status != SUCCESS)
		{
			DESTROY_IF(generator);
			return status;
		}
		data = generator->get_chunk(generator, NULL);
		len = data.len + (encrypted ? encrypted->get_length(encrypted) : 0);
	}

	/* check if we actually need to fragment the message and if we have an
	 * encrypted payload for IKEv2 */
	if (len <= frag_len ||
	   (this->major_version == IKEV2_MAJOR_VERSION && !encrypted))
	{
		if (generator)
		{
			status = finalize_message(this, keymat, generator, encrypted);
			if (status != SUCCESS)
			{
				return status;
			}
		}
		*fragments = enumerator_create_single(this->packet, NULL);
		return SUCCESS;
	}

	/* frag_len denoted the maximum IKE message size so far, later on it will
	 * denote the maximum content size of a fragment payload, therefore,
	 * account for IKE header */
	REDUCE_FRAG_LEN(frag_len, 28);

	if (this->major_version == IKEV1_MAJOR_VERSION)
	{
		if (generator)
		{
			status = finalize_message(this, keymat, generator, encrypted);
			if (status != SUCCESS)
			{
				return status;
			}
			data = this->packet->get_data(this->packet);
			generator = NULL;
		}
		/* overhead for the fragmentation payload header */
		REDUCE_FRAG_LEN(frag_len, 8);
	}
	else
	{
		aead_t *aead;

		if (generator)
		{
			generator->destroy(generator);
			generator = generator_create();
		}
		else
		{	/* do not log again if it was generated previously */
			generator = generator_create_no_dbg();
		}
		next = encrypted->payload_interface.get_next_type((payload_t*)encrypted);
		encrypted->generate_payloads(encrypted, generator);
		data = generator->get_chunk(generator, NULL);
		if (!is_encoded(this))
		{
			this->payloads->insert_last(this->payloads, encrypted);
		}
		aead = keymat->get_aead(keymat, FALSE);
		/* overhead for the encrypted fragment payload */
		REDUCE_FRAG_LEN(frag_len, aead->get_iv_size(aead));
		REDUCE_FRAG_LEN(frag_len, aead->get_icv_size(aead));
		/* header */
		REDUCE_FRAG_LEN(frag_len, 8);
		/* padding and padding length */
		frag_len = round_down(frag_len, aead->get_block_size(aead));
		REDUCE_FRAG_LEN(frag_len, 1);
		/* TODO-FRAG: if there are unprotected payloads, should we account for
		 * their length in the first fragment? we still would have to add
		 * an encrypted fragment payload (albeit empty), even so we couldn't
		 * prevent IP fragmentation in every case */
	}

	count = data.len / frag_len + (data.len % frag_len ? 1 : 0);
	this->fragments = array_create(0, count);
	DBG1(DBG_ENC, "splitting IKE message (%zu bytes) into %hu fragments", len,
		 count);
	for (num = 1; num <= count; num++)
	{
		len = min(data.len, frag_len);
		fragment = create_fragment(this, next, num, count,
								   chunk_create(data.ptr, len));
		status = fragment->generate(fragment, keymat, &packet);
		fragment->destroy(fragment);
		if (status != SUCCESS)
		{
			DBG1(DBG_ENC, "failed to generate IKE fragment");
			clear_fragments(this);
			DESTROY_IF(generator);
			return FAILED;
		}
		array_insert(this->fragments, ARRAY_TAIL, packet);
		data = chunk_skip(data, len);
	}
	*fragments = array_create_enumerator(this->fragments);
	DESTROY_IF(generator);
	return SUCCESS;
}

METHOD(message_t, get_packet, packet_t*,
	private_message_t *this)
{
	return this->packet->clone(this->packet);
}

METHOD(message_t, get_packet_data, chunk_t,
	private_message_t *this)
{
	return this->packet->get_data(this->packet);
}

METHOD(message_t, get_fragments, enumerator_t*,
	private_message_t *this)
{
	return array_create_enumerator(this->fragments);
}

METHOD(message_t, parse_header, status_t,
	private_message_t *this)
{
	ike_header_t *ike_header;
	status_t status;
	bool *reserved;
	int i;

	DBG2(DBG_ENC, "parsing header of message");

	if (!this->parser)
	{	/* reassembled IKEv2 message, header is inherited from fragments */
		return SUCCESS;
	}
	this->parser->reset_context(this->parser);
	status = this->parser->parse_payload(this->parser, PL_HEADER,
										 (payload_t**)&ike_header);
	if (status != SUCCESS)
	{
		DBG1(DBG_ENC, "header could not be parsed");
		return status;

	}

	status = ike_header->payload_interface.verify(
										&ike_header->payload_interface);
	if (status != SUCCESS)
	{
		DBG1(DBG_ENC, "header verification failed");
		ike_header->destroy(ike_header);
		return status;
	}

	DESTROY_IF(this->ike_sa_id);
	this->ike_sa_id = ike_sa_id_create(
									ike_header->get_maj_version(ike_header),
									ike_header->get_initiator_spi(ike_header),
									ike_header->get_responder_spi(ike_header),
									ike_header->get_initiator_flag(ike_header));

	this->exchange_type = ike_header->get_exchange_type(ike_header);
	this->message_id = ike_header->get_message_id(ike_header);
	this->major_version = ike_header->get_maj_version(ike_header);
	this->minor_version = ike_header->get_min_version(ike_header);
	if (this->major_version == IKEV2_MAJOR_VERSION)
	{
		this->is_request = !ike_header->get_response_flag(ike_header);
	}
	else
	{
		this->is_encrypted = ike_header->get_encryption_flag(ike_header);
	}
	this->first_payload = ike_header->payload_interface.get_next_type(
												&ike_header->payload_interface);
	if (this->first_payload == PLV1_FRAGMENT && this->is_encrypted)
	{	/* racoon sets the encrypted bit when sending a fragment, but these
		 * messages are really not encrypted */
		this->is_encrypted = FALSE;
	}

	for (i = 0; i < countof(this->reserved); i++)
	{
		reserved = payload_get_field(&ike_header->payload_interface,
									 RESERVED_BIT, i);
		if (reserved)
		{
			this->reserved[i] = *reserved;
		}
	}
	ike_header->destroy(ike_header);

	this->parser->set_major_version(this->parser, this->major_version);

	DBG2(DBG_ENC, "parsed a %N %s header", exchange_type_names,
		 this->exchange_type, this->major_version == IKEV1_MAJOR_VERSION ?
		 "message" : (this->is_request ? "request" : "response"));
	return SUCCESS;
}

/**
 * Check if a payload is for a mediation extension connectivity check
 */
static bool is_connectivity_check(private_message_t *this, payload_t *payload)
{
#ifdef ME
	if (this->exchange_type == INFORMATIONAL &&
		payload->get_type(payload) == PLV2_NOTIFY)
	{
		notify_payload_t *notify = (notify_payload_t*)payload;

		switch (notify->get_notify_type(notify))
		{
			case ME_CONNECTID:
			case ME_ENDPOINT:
			case ME_CONNECTAUTH:
				return TRUE;
			default:
				break;
		}
	}
#endif /* !ME */
	return FALSE;
}

/**
 * Parses and verifies the unencrypted payloads contained in the message
 */
static status_t parse_payloads(private_message_t *this)
{
	payload_type_t type = this->first_payload;
	payload_t *payload;
	status_t status;

	if (this->is_encrypted)
	{	/* wrap the whole encrypted IKEv1 message in a special encrypted
		 * payload which is then handled just like a regular payload */
		encrypted_payload_t *encryption;

		status = this->parser->parse_payload(this->parser, PLV1_ENCRYPTED,
											 (payload_t**)&encryption);
		if (status != SUCCESS)
		{
			DBG1(DBG_ENC, "failed to wrap encrypted IKEv1 message");
			return PARSE_ERROR;
		}
		encryption->payload_interface.set_next_type((payload_t*)encryption,
													this->first_payload);
		this->payloads->insert_last(this->payloads, encryption);
		return SUCCESS;
	}

	while (type != PL_NONE)
	{
		DBG2(DBG_ENC, "starting parsing a %N payload",
			 payload_type_names, type);

		status = this->parser->parse_payload(this->parser, type, &payload);
		if (status != SUCCESS)
		{
			DBG1(DBG_ENC, "payload type %N could not be parsed",
				 payload_type_names, type);
			return PARSE_ERROR;
		}

		DBG2(DBG_ENC, "verifying payload of type %N", payload_type_names, type);
		status = payload->verify(payload);
		if (status != SUCCESS)
		{
			DBG1(DBG_ENC, "%N payload verification failed",
				 payload_type_names, type);
			payload->destroy(payload);
			return VERIFY_ERROR;
		}
		if (payload->get_type(payload) == PL_UNKNOWN)
		{
			DBG2(DBG_ENC, "%N payload unknown or not allowed",
				 payload_type_names, type);
		}
		else
		{
			DBG2(DBG_ENC, "%N payload verified, adding to payload list",
				 payload_type_names, type);
		}
		this->payloads->insert_last(this->payloads, payload);

		/* an encrypted (fragment) payload MUST be the last one, so STOP here.
		 * decryption is done later */
		if (type == PLV2_ENCRYPTED || type == PLV2_FRAGMENT)
		{
			DBG2(DBG_ENC, "%N payload found, stop parsing",
				 payload_type_names, type);
			break;
		}
		type = payload->get_next_type(payload);
	}
	return SUCCESS;
}

/**
 * Decrypt an encrypted payload and extract all contained payloads.
 */
static status_t decrypt_and_extract(private_message_t *this, keymat_t *keymat,
						payload_t *previous, encrypted_payload_t *encryption)
{
	payload_t *encrypted;
	payload_type_t type;
	chunk_t chunk;
	aead_t *aead;
	size_t bs;
	status_t status = SUCCESS;

	if (!keymat)
	{
		DBG1(DBG_ENC, "found encrypted payload, but no keymat");
		return INVALID_ARG;
	}
	aead = keymat->get_aead(keymat, TRUE);
	if (!aead)
	{
		DBG1(DBG_ENC, "found encrypted payload, but no transform set");
		return INVALID_ARG;
	}
	if (!this->parser)
	{
		/* reassembled IKEv2 messages are already decrypted, we still call
		 * decrypt() to parse the contained payloads */
		status = encryption->decrypt(encryption, chunk_empty);
	}
	else
	{
		bs = aead->get_block_size(aead);
		encryption->set_transform(encryption, aead);
		chunk = this->packet->get_data(this->packet);
		if (chunk.len < encryption->get_length(encryption) ||
			chunk.len < bs)
		{
			DBG1(DBG_ENC, "invalid payload length");
			return VERIFY_ERROR;
		}
		if (keymat->get_version(keymat) == IKEV1)
		{	/* instead of associated data we provide the IV, we also update
			 * the IV with the last encrypted block */
			keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
			chunk_t iv;

			if (keymat_v1->get_iv(keymat_v1, this->message_id, &iv))
			{
				status = encryption->decrypt(encryption, iv);
				if (status == SUCCESS)
				{
					if (!keymat_v1->update_iv(keymat_v1, this->message_id,
							chunk_create(chunk.ptr + chunk.len - bs, bs)))
					{
						status = FAILED;
					}
				}
			}
			else
			{
				status = FAILED;
			}
		}
		else
		{
			chunk.len -= encryption->get_length(encryption);
			status = encryption->decrypt(encryption, chunk);
		}
	}
	if (status != SUCCESS)
	{
		return status;
	}

	while ((encrypted = encryption->remove_payload(encryption)))
	{
		type = encrypted->get_type(encrypted);
		if (previous)
		{
			previous->set_next_type(previous, type);
		}
		else
		{
			this->first_payload = type;
		}
		DBG2(DBG_ENC, "insert decrypted payload of type %N at end of list",
			 payload_type_names, type);
		this->payloads->insert_last(this->payloads, encrypted);
		previous = encrypted;
	}
	return SUCCESS;
}

/**
 * Decrypt an encrypted fragment payload.
 */
static status_t decrypt_fragment(private_message_t *this, keymat_t *keymat,
								 encrypted_fragment_payload_t *fragment)
{
	encrypted_payload_t *encrypted = (encrypted_payload_t*)fragment;
	chunk_t chunk;
	aead_t *aead;
	size_t bs;

	if (!keymat)
	{
		DBG1(DBG_ENC, "found encrypted fragment payload, but no keymat");
		return INVALID_ARG;
	}
	aead = keymat->get_aead(keymat, TRUE);
	if (!aead)
	{
		DBG1(DBG_ENC, "found encrypted fragment payload, but no transform set");
		return INVALID_ARG;
	}
	bs = aead->get_block_size(aead);
	encrypted->set_transform(encrypted, aead);
	chunk = this->packet->get_data(this->packet);
	if (chunk.len < encrypted->get_length(encrypted) ||
		chunk.len < bs)
	{
		DBG1(DBG_ENC, "invalid payload length");
		return VERIFY_ERROR;
	}
	chunk.len -= encrypted->get_length(encrypted);
	return encrypted->decrypt(encrypted, chunk);
}

/**
 * Do we accept unencrypted ID/HASH payloads in Main Mode, as seen from
 * some SonicWall boxes?
 */
static bool accept_unencrypted_mm(private_message_t *this, payload_type_t type)
{
	if (this->exchange_type == ID_PROT)
	{
		if (type == PLV1_ID || type == PLV1_HASH)
		{
			return lib->settings->get_bool(lib->settings,
									"%s.accept_unencrypted_mainmode_messages",
									FALSE, lib->ns);
		}
	}
	return FALSE;
}

/**
 * Decrypt payload from the encrypted payload
 */
static status_t decrypt_payloads(private_message_t *this, keymat_t *keymat)
{
	payload_t *payload, *previous = NULL;
	enumerator_t *enumerator;
	payload_rule_t *rule;
	payload_type_t type;
	status_t status = SUCCESS;
	char *was_encrypted = NULL;

	enumerator = this->payloads->create_enumerator(this->payloads);
	while (enumerator->enumerate(enumerator, &payload))
	{
		type = payload->get_type(payload);

		DBG2(DBG_ENC, "process payload of type %N", payload_type_names, type);

		if (type == PLV2_ENCRYPTED || type == PLV1_ENCRYPTED ||
			type == PLV2_FRAGMENT)
		{
			if (was_encrypted)
			{
				DBG1(DBG_ENC, "%s can't contain other payloads of type %N",
					 was_encrypted, payload_type_names, type);
				status = VERIFY_ERROR;
				break;
			}
		}

		if (type == PLV2_ENCRYPTED || type == PLV1_ENCRYPTED)
		{
			encrypted_payload_t *encryption;

			DBG2(DBG_ENC, "found an encrypted payload");
			encryption = (encrypted_payload_t*)payload;
			this->payloads->remove_at(this->payloads, enumerator);

			if (enumerator->enumerate(enumerator, NULL))
			{
				DBG1(DBG_ENC, "encrypted payload is not last payload");
				encryption->destroy(encryption);
				status = VERIFY_ERROR;
				break;
			}
			status = decrypt_and_extract(this, keymat, previous, encryption);
			encryption->destroy(encryption);
			if (status != SUCCESS)
			{
				break;
			}
			was_encrypted = "encrypted payload";
		}
		else if (type == PLV2_FRAGMENT)
		{
			encrypted_fragment_payload_t *fragment;

			DBG2(DBG_ENC, "found an encrypted fragment payload");
			fragment = (encrypted_fragment_payload_t*)payload;

			if (enumerator->enumerate(enumerator, NULL))
			{
				DBG1(DBG_ENC, "encrypted fragment payload is not last payload");
				status = VERIFY_ERROR;
				break;
			}
			status = decrypt_fragment(this, keymat, fragment);
			if (status != SUCCESS)
			{
				break;
			}
			was_encrypted = "encrypted fragment payload";
		}

		if (type != PL_UNKNOWN && !was_encrypted &&
			!is_connectivity_check(this, payload) &&
			this->exchange_type != AGGRESSIVE)
		{
			rule = get_payload_rule(this, type);
			if ((!rule || rule->encrypted) &&
				!accept_unencrypted_mm(this, type))
			{
				DBG1(DBG_ENC, "payload type %N was not encrypted",
					 payload_type_names, type);
				status = FAILED;
				break;
			}
		}
		previous = payload;
	}
	enumerator->destroy(enumerator);
	return status;
}

/**
 * Verify a message and all payload according to message/payload rules
 */
static status_t verify(private_message_t *this)
{
	bool complete = FALSE;
	int i;

	DBG2(DBG_ENC, "verifying message structure");

	/* check for payloads with wrong count */
	for (i = 0; i < this->rule->rule_count; i++)
	{
		enumerator_t *enumerator;
		payload_t *payload;
		payload_rule_t *rule;
		int found = 0;

		rule = &this->rule->rules[i];
		enumerator = create_payload_enumerator(this);
		while (enumerator->enumerate(enumerator, &payload))
		{
			payload_type_t type;

			type = payload->get_type(payload);
			if (type == rule->type)
			{
				found++;
				DBG2(DBG_ENC, "found payload of type %N",
					 payload_type_names, type);
				if (found > rule->max_occurrence)
				{
					DBG1(DBG_ENC, "payload of type %N more than %d times (%d) "
						 "occurred in current message", payload_type_names,
						 type, rule->max_occurrence, found);
					enumerator->destroy(enumerator);
					return VERIFY_ERROR;
				}
			}
		}
		enumerator->destroy(enumerator);

		if (!complete && found < rule->min_occurrence)
		{
			DBG1(DBG_ENC, "payload of type %N not occurred %d times (%d)",
				 payload_type_names, rule->type, rule->min_occurrence, found);
			return VERIFY_ERROR;
		}
		if (found && rule->sufficient)
		{
			complete = TRUE;
		}
	}
	return SUCCESS;
}

METHOD(message_t, parse_body, status_t,
	private_message_t *this, keymat_t *keymat)
{
	status_t status = SUCCESS;

	DBG2(DBG_ENC, "parsing body of message, first payload is %N",
		 payload_type_names, this->first_payload);

	this->rule = get_message_rule(this);
	if (!this->rule)
	{
		DBG1(DBG_ENC, "no message rules specified for a %N %s",
			 exchange_type_names, this->exchange_type,
			 this->is_request ? "request" : "response");
		return NOT_SUPPORTED;
	}

	/* reassembled IKEv2 messages are already parsed (except for the payloads
	 * contained in the encrypted payload, which are handled below) */
	if (this->parser)
	{
		status = parse_payloads(this);
		if (status != SUCCESS)
		{	/* error is already logged */
			return status;
		}
	}

	status = decrypt_payloads(this, keymat);
	if (status != SUCCESS)
	{
		DBG1(DBG_ENC, "could not decrypt payloads");
		return status;
	}

	status = verify(this);
	if (status != SUCCESS)
	{
		return status;
	}

#if DEBUG_LEVEL >= 1
	char str[BUF_LEN];
	DBG1(DBG_ENC, "parsed %s", get_string(this, str, sizeof(str)));
#endif

	if (keymat && keymat->get_version(keymat) == IKEV1)
	{
		keymat_v1_t *keymat_v1 = (keymat_v1_t*)keymat;
		chunk_t hash;

		if (keymat_v1->get_hash_phase2(keymat_v1, &this->public, &hash))
		{
			hash_payload_t *hash_payload;
			chunk_t other_hash;

			if (this->first_payload != PLV1_HASH)
			{
				if (this->exchange_type == INFORMATIONAL_V1)
				{
					DBG1(DBG_ENC, "ignoring unprotected INFORMATIONAL from %H",
						 this->packet->get_source(this->packet));
				}
				else
				{
					DBG1(DBG_ENC, "expected HASH payload as first payload");
				}
				chunk_free(&hash);
				return VERIFY_ERROR;
			}
			hash_payload = (hash_payload_t*)get_payload(this, PLV1_HASH);
			other_hash = hash_payload->get_hash(hash_payload);
			DBG3(DBG_ENC, "HASH received %B\nHASH expected %B",
				 &other_hash, &hash);
			if (!chunk_equals_const(hash, other_hash))
			{
				DBG1(DBG_ENC, "received HASH payload does not match");
				chunk_free(&hash);
				return FAILED;
			}
			chunk_free(&hash);
		}
		if (this->is_encrypted && this->exchange_type != INFORMATIONAL_V1)
		{	/* message verified, confirm IV */
			if (!keymat_v1->confirm_iv(keymat_v1, this->message_id))
			{
				return FAILED;
			}
		}
	}
	return SUCCESS;
}

/**
 * Store the fragment data for the fragment with the given fragment number.
 */
static status_t add_fragment(private_message_t *this, uint16_t num,
							 chunk_t data)
{
	fragment_t *fragment;
	int i, insert_at = -1;

	for (i = 0; i < array_count(this->fragments); i++)
	{
		array_get(this->fragments, i, &fragment);
		if (fragment->num == num)
		{
			/* ignore a duplicate fragment */
			DBG1(DBG_ENC, "received duplicate fragment #%hu", num);
			return NEED_MORE;
		}
		if (fragment->num > num)
		{
			insert_at = i;
			break;
		}
	}
	this->frag->len += data.len;
	if (this->frag->len > this->frag->max_packet)
	{
		DBG1(DBG_ENC, "fragmented IKE message is too large");
		reset_defrag(this);
		return FAILED;
	}
	INIT(fragment,
		.num = num,
		.data = chunk_clone(data),
	);
	array_insert(this->fragments, insert_at, fragment);
	return SUCCESS;
}

/**
 * Merge the cached fragment data and resets the defragmentation state.
 * Also updates the IP addresses to those of the last received fragment.
 */
static chunk_t merge_fragments(private_message_t *this, message_t *last)
{
	fragment_t *fragment;
	bio_writer_t *writer;
	host_t *src, *dst;
	chunk_t data;
	int i;

	writer = bio_writer_create(this->frag->len);
	for (i = 0; i < array_count(this->fragments); i++)
	{
		array_get(this->fragments, i, &fragment);
		writer->write_data(writer, fragment->data);
	}
	data = writer->extract_buf(writer);
	writer->destroy(writer);

	/* set addresses to those of the last fragment we received */
	src = last->get_source(last);
	dst = last->get_destination(last);
	this->packet->set_source(this->packet, src->clone(src));
	this->packet->set_destination(this->packet, dst->clone(dst));

	reset_defrag(this);
	free(this->frag);
	this->frag = NULL;
	return data;
}

METHOD(message_t, add_fragment_v1, status_t,
	private_message_t *this, message_t *message)
{
	fragment_payload_t *payload;
	chunk_t data;
	uint8_t num;
	status_t status;

	if (!this->frag)
	{
		return INVALID_STATE;
	}
	payload = (fragment_payload_t*)message->get_payload(message, PLV1_FRAGMENT);
	if (!payload)
	{
		return INVALID_ARG;
	}
	if (!this->fragments || this->message_id != payload->get_id(payload))
	{
		reset_defrag(this);
		this->message_id = payload->get_id(payload);
		/* we don't know the total number of fragments, assume something */
		this->fragments = array_create(0, 4);
	}

	num = payload->get_number(payload);
	data = payload->get_data(payload);
	if (!this->frag->last && payload->is_last(payload))
	{
		this->frag->last = num;
	}
	status = add_fragment(this, num, data);
	if (status != SUCCESS)
	{
		return status;
	}

	if (array_count(this->fragments) != this->frag->last)
	{
		/* there are some fragments missing */
		DBG1(DBG_ENC, "received fragment #%hhu, waiting for complete IKE "
			 "message", num);
		return NEED_MORE;
	}

	data = merge_fragments(this, message);
	this->packet->set_data(this->packet, data);
	DBG1(DBG_ENC, "received fragment #%hhu, reassembled fragmented IKE "
		 "message (%zu bytes)", num, data.len);

	this->parser = parser_create(data);

	if (parse_header(this) != SUCCESS)
	{
		DBG1(DBG_IKE, "failed to parse header of reassembled IKE message");
		return FAILED;
	}
	return SUCCESS;
}

METHOD(message_t, add_fragment_v2, status_t,
	private_message_t *this, message_t *message)
{
	encrypted_fragment_payload_t *encrypted_fragment;
	encrypted_payload_t *encrypted;
	payload_t *payload;
	aead_t *aead;
	enumerator_t *enumerator;
	chunk_t data;
	uint16_t total, num;
	status_t status;

	if (!this->frag)
	{
		return INVALID_STATE;
	}
	payload = message->get_payload(message, PLV2_FRAGMENT);
	if (!payload || this->message_id != message->get_message_id(message))
	{
		return INVALID_ARG;
	}
	encrypted_fragment = (encrypted_fragment_payload_t*)payload;
	total = encrypted_fragment->get_total_fragments(encrypted_fragment);
	if (total > MAX_FRAGMENTS)
	{
		DBG1(DBG_IKE, "maximum fragment count exceeded");
		reset_defrag(this);
		return FAILED;
	}
	if (!this->fragments || total > this->frag->last)
	{
		reset_defrag(this);
		this->frag->last = total;
		this->fragments = array_create(0, total);
	}
	else if (total < this->frag->last)
	{	/* drop these silently as mandated by RFC 7383, section 2.6 */
		return INVALID_ARG;
	}
	num = encrypted_fragment->get_fragment_number(encrypted_fragment);
	data = encrypted_fragment->get_content(encrypted_fragment);
	status = add_fragment(this, num, data);
	if (status != SUCCESS)
	{
		return status;
	}

	if (num == 1)
	{
		/* the first fragment denotes the payload type of the first payload in
		 * the original encrypted payload, cache that */
		this->first_payload = payload->get_next_type(payload);
		/* move all unprotected payloads contained in the first fragment */
		enumerator = message->create_payload_enumerator(message);
		while (enumerator->enumerate(enumerator, &payload))
		{
			if (payload->get_type(payload) != PLV2_FRAGMENT)
			{
				message->remove_payload_at(message, enumerator);
				this->payloads->insert_last(this->payloads, payload);
			}
		}
		enumerator->destroy(enumerator);
	}

	if (array_count(this->fragments) != this->frag->last)
	{
		/* there are some fragments missing */
		DBG1(DBG_ENC, "received fragment #%hu of %hu, waiting for complete IKE "
			 "message", num, this->frag->last);
		return NEED_MORE;
	}

	encrypted = (encrypted_payload_t*)encrypted_fragment;
	aead = encrypted->get_transform(encrypted);

	data = merge_fragments(this, message);

	/* use the cached next payload type from the SKF of the first fragment */
	encrypted = encrypted_payload_create_from_plain(this->first_payload, data);
	encrypted->set_transform(encrypted, aead);

	if (this->payloads->get_last(this->payloads, (void**)&payload) == SUCCESS)
	{	/* if there are any unprotected payloads, update the last one's next
		 * payload type (it points to an SKF instead of an SK) */
		payload->set_next_type(payload, encrypted->payload_interface.get_type(
												&encrypted->payload_interface));
	}
	this->payloads->insert_last(this->payloads, encrypted);
	/* update payload type in the header (could be an unprotected payload) */
	this->payloads->get_first(this->payloads, (void**)&payload);
	this->first_payload = payload->get_type(payload);

	/* we report the length of the complete IKE message when splitting, do the
	 * same here, so add the IKEv2 header len to the reassembled payload data */
#if DEBUG_LEVEL >= 1
	size_t len = 28;
	enumerator = create_payload_enumerator(this);
	while (enumerator->enumerate(enumerator, &payload))
	{
		len += payload->get_length(payload);
	}
	enumerator->destroy(enumerator);

	DBG1(DBG_ENC, "received fragment #%hu of %hu, reassembled fragmented IKE "
		 "message (%zu bytes)", num, total, len);
#endif /* DEBUG_LEVEL */

	return SUCCESS;
}

METHOD(message_t, get_metadata, metadata_t*,
	private_message_t *this, const char *key)
{
	return this->packet->get_metadata(this->packet, key);
}

METHOD(message_t, set_metadata, void,
	private_message_t *this, const char *key, metadata_t *data)
{
	this->packet->set_metadata(this->packet, key, data);
}

METHOD(message_t, destroy, void,
	private_message_t *this)
{
	DESTROY_IF(this->ike_sa_id);
	DESTROY_IF(this->parser);
	this->payloads->destroy_offset(this->payloads, offsetof(payload_t, destroy));
	this->packet->destroy(this->packet);
	if (this->frag)
	{
		reset_defrag(this);
		free(this->frag);
	}
	else
	{
		array_destroy_offset(this->fragments, offsetof(packet_t, destroy));
	}
	free(this);
}

/*
 * Described in header.
 */
message_t *message_create_from_packet(packet_t *packet)
{
	private_message_t *this;

	INIT(this,
		.public = {
			.set_major_version = _set_major_version,
			.get_major_version = _get_major_version,
			.set_minor_version = _set_minor_version,
			.get_minor_version = _get_minor_version,
			.set_message_id = _set_message_id,
			.get_message_id = _get_message_id,
			.get_initiator_spi = _get_initiator_spi,
			.get_responder_spi = _get_responder_spi,
			.set_ike_sa_id = _set_ike_sa_id,
			.get_ike_sa_id = _get_ike_sa_id,
			.set_exchange_type = _set_exchange_type,
			.get_exchange_type = _get_exchange_type,
			.get_first_payload_type = _get_first_payload_type,
			.set_request = _set_request,
			.get_request = _get_request,
			.set_version_flag = _set_version_flag,
			.get_reserved_header_bit = _get_reserved_header_bit,
			.set_reserved_header_bit = _set_reserved_header_bit,
			.add_payload = _add_payload,
			.add_notify = _add_notify,
			.disable_sort = _disable_sort,
			.generate = _generate,
			.is_encoded = _is_encoded,
			.is_fragmented = _is_fragmented,
			.fragment = _fragment,
			.add_fragment = _add_fragment_v2,
			.set_source = _set_source,
			.get_source = _get_source,
			.set_destination = _set_destination,
			.get_destination = _get_destination,
			.create_payload_enumerator = _create_payload_enumerator,
			.remove_payload_at = _remove_payload_at,
			.get_payload = _get_payload,
			.get_notify = _get_notify,
			.parse_header = _parse_header,
			.parse_body = _parse_body,
			.get_packet = _get_packet,
			.get_packet_data = _get_packet_data,
			.get_fragments = _get_fragments,
			.get_metadata = _get_metadata,
			.set_metadata = _set_metadata,
			.get_plain = _get_plain,
			.destroy = _destroy,
		},
		.exchange_type = EXCHANGE_TYPE_UNDEFINED,
		.is_request = TRUE,
		.first_payload = PL_NONE,
		.packet = packet,
		.payloads = linked_list_create(),
		.parser = parser_create(packet->get_data(packet)),
	);

	return &this->public;
}

/*
 * Described in header.
 */
message_t *message_create(int major, int minor)
{
	message_t *this = message_create_from_packet(packet_create());

	this->set_major_version(this, major);
	this->set_minor_version(this, minor);

	return this;
}

/*
 * Described in header.
 */
message_t *message_create_defrag(message_t *fragment)
{
	private_message_t *this;

	if (!fragment->get_payload(fragment, PLV1_FRAGMENT) &&
		!fragment->get_payload(fragment, PLV2_FRAGMENT))
	{
		return NULL;
	}
	this = (private_message_t*)clone_message((private_message_t*)fragment);
	/* we don't need a parser for IKEv2, the one for IKEv1 is created after
	 * reassembling the original message */
	this->parser->destroy(this->parser);
	this->parser = NULL;
	if (fragment->get_major_version(fragment) == IKEV1_MAJOR_VERSION)
	{
		/* we store the fragment ID in the message ID field, which should be
		 * zero for fragments, but make sure */
		this->message_id = 0;
		this->public.add_fragment = _add_fragment_v1;
	}
	INIT(this->frag,
		.max_packet = lib->settings->get_int(lib->settings,
								"%s.max_packet", PACKET_MAX_DEFAULT, lib->ns),
	);
	return &this->public;
}
