diff --git a/platform/linux-generic/odp_ipsec.c b/platform/linux-generic/odp_ipsec.c
index b17e4cd7b..b0103604e 100644
--- a/platform/linux-generic/odp_ipsec.c
+++ b/platform/linux-generic/odp_ipsec.c
@@ -123,8 +123,8 @@ static inline int _odp_ipv4_csum(odp_packet_t pkt,
 	return 0;
 }
 
-/** @internal Checksum offset in IPv4 header */
-#define _ODP_IPV4HDR_CSUM_OFFSET	10
+#define _ODP_IPV4HDR_CSUM_OFFSET ODP_OFFSETOF(_odp_ipv4hdr_t, chksum)
+#define _ODP_IPV4HDR_PROTO_OFFSET ODP_OFFSETOF(_odp_ipv4hdr_t, proto)
 
 /**
  * Calculate and fill in IPv4 checksum
@@ -158,7 +158,7 @@ static inline int _odp_ipv4_csum_update(odp_packet_t pkt)
 					2, &chksum);
 }
 
-#define ipv4_hdr_len(ip) (_ODP_IPV4HDR_IHL(ip->ver_ihl) * 4)
+#define ipv4_hdr_len(ip) (_ODP_IPV4HDR_IHL((ip)->ver_ihl) * 4)
 static inline
 void ipv4_adjust_len(_odp_ipv4hdr_t *ip, int adj)
 {
@@ -218,200 +218,310 @@ static inline odp_pktio_parser_layer_t parse_layer(odp_ipsec_proto_layer_t l)
 	return ODP_PKTIO_PARSER_LAYER_NONE;
 }
 
-static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
-				   odp_ipsec_sa_t sa,
-				   odp_packet_t *pkt_out,
-				   odp_ipsec_op_status_t *status)
-{
-	ipsec_sa_t *ipsec_sa = NULL;
-	uint32_t ip_offset = odp_packet_l3_offset(pkt);
-	_odp_ipv4hdr_t *ip = odp_packet_l3_ptr(pkt, NULL);
-	uint16_t ip_hdr_len = ipv4_hdr_len(ip);
-	odp_crypto_packet_op_param_t param;
-	int rc;
+typedef struct {
+	_odp_ipv4hdr_t *ip;
 	unsigned stats_length;
-	uint16_t ipsec_offset;   /**< Offset of IPsec header from
-				      buffer start */
-	uint8_t	iv[IPSEC_MAX_IV_LEN];  /**< ESP IV storage */
-	ipsec_aad_t aad;         /**< AAD, note ESN is not fully supported */
-	unsigned hdr_len;        /**< Length of IPsec headers */
-	unsigned trl_len;        /**< Length of IPsec trailers */
-	uint8_t  ip_tos;         /**< Saved IP TOS value */
-	uint8_t  ip_ttl;         /**< Saved IP TTL value */
-	uint16_t ip_frag_offset; /**< Saved IP flags value */
-	odp_crypto_packet_result_t crypto; /**< Crypto operation result */
-	odp_packet_hdr_t *pkt_hdr;
+	uint16_t ip_offset;
+	uint16_t ip_hdr_len;
+	uint16_t ip_tot_len;
+	union {
+		struct {
+			uint16_t ip_df;
+			uint8_t  ip_tos;
+		} out_tunnel;
+		struct {
+			uint16_t hdr_len;
+			uint16_t trl_len;
+		} in;
+	};
+	union {
+		struct {
+			uint8_t  tos;
+			uint8_t  ttl;
+			uint16_t frag_offset;
+		} ah_ipv4;
+	};
+	ipsec_aad_t aad;
+	uint8_t	iv[IPSEC_MAX_IV_LEN];
+} ipsec_state_t;
+
+static int ipsec_parse_ipv4(ipsec_state_t *state)
+{
+	if (_ODP_IPV4HDR_IS_FRAGMENT(odp_be_to_cpu_16(state->ip->frag_offset)))
+		return -1;
 
-	ODP_ASSERT(ODP_PACKET_OFFSET_INVALID != ip_offset);
-	ODP_ASSERT(NULL != ip);
+	state->ip_hdr_len = ipv4_hdr_len(state->ip);
+	state->ip_tot_len = odp_be_to_cpu_16(state->ip->tot_len);
 
-	ip_tos = 0;
-	ip_ttl = 0;
-	ip_frag_offset = 0;
+	return 0;
+}
 
-	/* Initialize parameters block */
-	memset(&param, 0, sizeof(param));
+static inline ipsec_sa_t *ipsec_get_sa(odp_ipsec_sa_t sa,
+				       odp_ipsec_protocol_t proto,
+				       uint32_t spi,
+				       void *dst_addr,
+				       odp_ipsec_op_status_t *status)
+{
+	ipsec_sa_t *ipsec_sa;
+
+	if (ODP_IPSEC_SA_INVALID == sa) {
+		ipsec_sa_lookup_t lookup;
+
+		lookup.proto = proto;
+		lookup.spi = spi;
+		lookup.dst_addr = dst_addr;
+
+		ipsec_sa = _odp_ipsec_sa_lookup(&lookup);
+		if (NULL == ipsec_sa) {
+			status->error.sa_lookup = 1;
+			return NULL;
+		}
+	} else {
+		ipsec_sa = _odp_ipsec_sa_use(sa);
+		ODP_ASSERT(NULL != ipsec_sa);
+		if (ipsec_sa->proto != proto ||
+		    ipsec_sa->spi != spi) {
+			status->error.proto = 1;
+			return ipsec_sa;
+		}
+	}
+
+	return ipsec_sa;
+}
+
+static int ipsec_in_iv(odp_packet_t pkt,
+		       ipsec_state_t *state,
+		       ipsec_sa_t *ipsec_sa,
+		       uint16_t iv_offset)
+{
+	memcpy(state->iv, ipsec_sa->salt, ipsec_sa->salt_length);
+	if (odp_packet_copy_to_mem(pkt,
+				   iv_offset,
+				   ipsec_sa->esp_iv_len,
+				   state->iv + ipsec_sa->salt_length) < 0)
+		return -1;
+
+	if (ipsec_sa->aes_ctr_iv) {
+		state->iv[12] = 0;
+		state->iv[13] = 0;
+		state->iv[14] = 0;
+		state->iv[15] = 1;
+	}
+
+	return 0;
+}
+
+static int ipsec_in_esp(odp_packet_t *pkt,
+			ipsec_state_t *state,
+			ipsec_sa_t **_ipsec_sa,
+			odp_ipsec_sa_t sa,
+			odp_crypto_packet_op_param_t *param,
+			odp_ipsec_op_status_t *status)
+{
+	_odp_esphdr_t esp;
+	uint16_t ipsec_offset;
+	ipsec_sa_t *ipsec_sa;
 
-	ipsec_offset = ip_offset + ip_hdr_len;
+	ipsec_offset = state->ip_offset + state->ip_hdr_len;
 
-	if (odp_be_to_cpu_16(ip->tot_len) + ip_offset > odp_packet_len(pkt)) {
+	if (odp_packet_copy_to_mem(*pkt, ipsec_offset,
+				   sizeof(esp), &esp) < 0) {
 		status->error.alg = 1;
-		goto err;
+		return -1;
 	}
 
-	if (_ODP_IPV4HDR_IS_FRAGMENT(odp_be_to_cpu_16(ip->frag_offset))) {
-		status->error.proto = 1;
-		goto err;
+	ipsec_sa = ipsec_get_sa(sa, ODP_IPSEC_ESP,
+				odp_be_to_cpu_32(esp.spi),
+				&state->ip->dst_addr, status);
+	*_ipsec_sa = ipsec_sa;
+	if (status->error.all)
+		return -1;
+
+	if (ipsec_in_iv(*pkt, state, ipsec_sa,
+			ipsec_offset + _ODP_ESPHDR_LEN) < 0) {
+		status->error.alg = 1;
+		return -1;
 	}
 
-	/* Check IP header for IPSec protocols and look it up */
-	if (_ODP_IPPROTO_ESP == ip->proto) {
-		_odp_esphdr_t esp;
+	state->in.hdr_len = _ODP_ESPHDR_LEN + ipsec_sa->esp_iv_len;
+	state->in.trl_len = _ODP_ESPTRL_LEN + ipsec_sa->icv_len;
 
-		if (odp_packet_copy_to_mem(pkt, ipsec_offset,
-					   sizeof(esp), &esp) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	param->cipher_range.offset = ipsec_offset + state->in.hdr_len;
+	param->cipher_range.length = state->ip_tot_len -
+				    state->ip_hdr_len -
+				    state->in.hdr_len -
+				    ipsec_sa->icv_len;
+	param->override_iv_ptr = state->iv;
 
-		if (ODP_IPSEC_SA_INVALID == sa) {
-			ipsec_sa_lookup_t lookup;
+	state->aad.spi = esp.spi;
+	state->aad.seq_no = esp.seq_no;
 
-			lookup.proto = ODP_IPSEC_ESP;
-			lookup.spi = odp_be_to_cpu_32(esp.spi);
-			lookup.dst_addr = &ip->dst_addr;
+	param->aad.ptr = (uint8_t *)&state->aad;
 
-			ipsec_sa = _odp_ipsec_sa_lookup(&lookup);
-			if (NULL == ipsec_sa) {
-				status->error.sa_lookup = 1;
-				goto err;
-			}
-		} else {
-			ipsec_sa = _odp_ipsec_sa_use(sa);
-			ODP_ASSERT(NULL != ipsec_sa);
-			if (ipsec_sa->proto != ODP_IPSEC_ESP ||
-			    ipsec_sa->spi != odp_be_to_cpu_32(esp.spi)) {
-				status->error.proto = 1;
-				goto err;
-			}
-		}
+	param->auth_range.offset = ipsec_offset;
+	param->auth_range.length = state->ip_tot_len -
+				  state->ip_hdr_len -
+				  ipsec_sa->icv_len;
+	param->hash_result_offset = state->ip_offset +
+				   state->ip_tot_len -
+				   ipsec_sa->icv_len;
 
-		memcpy(iv, ipsec_sa->salt, ipsec_sa->salt_length);
-		if (odp_packet_copy_to_mem(pkt,
-					   ipsec_offset + _ODP_ESPHDR_LEN,
-					   ipsec_sa->esp_iv_len,
-					   iv + ipsec_sa->salt_length) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	state->stats_length = param->cipher_range.length;
 
-		if (ipsec_sa->aes_ctr_iv) {
-			iv[12] = 0;
-			iv[13] = 0;
-			iv[14] = 0;
-			iv[15] = 1;
-		}
+	return 0;
+}
 
-		hdr_len = _ODP_ESPHDR_LEN + ipsec_sa->esp_iv_len;
-		trl_len = _ODP_ESPTRL_LEN + ipsec_sa->icv_len;
+static int ipsec_in_esp_post(odp_packet_t pkt,
+			     ipsec_state_t *state)
+{
+	_odp_esptrl_t esptrl;
+	uint32_t esptrl_offset = state->ip_offset +
+				 state->ip_tot_len -
+				 state->in.trl_len;
+
+	if (odp_packet_copy_to_mem(pkt, esptrl_offset,
+				   sizeof(esptrl), &esptrl) < 0 ||
+	    state->ip_offset + esptrl.pad_len > esptrl_offset ||
+	    _odp_packet_cmp_data(pkt, esptrl_offset - esptrl.pad_len,
+				 ipsec_padding, esptrl.pad_len) != 0)
+		return -1;
 
-		param.cipher_range.offset = ipsec_offset + hdr_len;
-		param.cipher_range.length = odp_be_to_cpu_16(ip->tot_len) -
-					    ip_hdr_len -
-					    hdr_len -
-					    ipsec_sa->icv_len;
-		param.override_iv_ptr = iv;
+	state->ip->proto = esptrl.next_header;
+	state->in.trl_len += esptrl.pad_len;
 
-		aad.spi = esp.spi;
-		aad.seq_no = esp.seq_no;
+	return 0;
+}
 
-		param.aad.ptr = (uint8_t *)&aad;
+static int ipsec_in_ah(odp_packet_t *pkt,
+		       ipsec_state_t *state,
+		       ipsec_sa_t **_ipsec_sa,
+		       odp_ipsec_sa_t sa,
+		       odp_crypto_packet_op_param_t *param,
+		       odp_ipsec_op_status_t *status)
+{
+	_odp_ahhdr_t ah;
+	uint16_t ipsec_offset;
+	ipsec_sa_t *ipsec_sa;
 
-		param.auth_range.offset = ipsec_offset;
-		param.auth_range.length = odp_be_to_cpu_16(ip->tot_len) -
-					  ip_hdr_len -
-					  ipsec_sa->icv_len;
-		param.hash_result_offset = ip_offset +
-					   odp_be_to_cpu_16(ip->tot_len) -
-					   ipsec_sa->icv_len;
+	ipsec_offset = state->ip_offset + state->ip_hdr_len;
 
-		stats_length = param.cipher_range.length;
-	} else if (_ODP_IPPROTO_AH == ip->proto) {
-		_odp_ahhdr_t ah;
+	if (odp_packet_copy_to_mem(*pkt, ipsec_offset,
+				   sizeof(ah), &ah) < 0) {
+		status->error.alg = 1;
+		return -1;
+	}
 
-		if (odp_packet_copy_to_mem(pkt, ipsec_offset,
-					   sizeof(ah), &ah) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	ipsec_sa = ipsec_get_sa(sa, ODP_IPSEC_AH,
+				odp_be_to_cpu_32(ah.spi),
+				&state->ip->dst_addr, status);
+	*_ipsec_sa = ipsec_sa;
+	if (status->error.all)
+		return -1;
 
-		if (ODP_IPSEC_SA_INVALID == sa) {
-			ipsec_sa_lookup_t lookup;
+	if (ipsec_in_iv(*pkt, state, ipsec_sa,
+			ipsec_offset + _ODP_AHHDR_LEN) < 0) {
+		status->error.alg = 1;
+		return -1;
+	}
 
-			lookup.proto = ODP_IPSEC_AH;
-			lookup.spi = odp_be_to_cpu_32(ah.spi);
-			lookup.dst_addr = &ip->dst_addr;
+	param->override_iv_ptr = state->iv;
 
-			ipsec_sa = _odp_ipsec_sa_lookup(&lookup);
-			if (NULL == ipsec_sa) {
-				status->error.sa_lookup = 1;
-				goto err;
-			}
-		} else {
-			ipsec_sa = _odp_ipsec_sa_use(sa);
-			ODP_ASSERT(NULL != ipsec_sa);
-			if (ipsec_sa->proto != ODP_IPSEC_AH ||
-			    ipsec_sa->spi != odp_be_to_cpu_32(ah.spi)) {
-				status->error.proto = 1;
-				goto err;
-			}
-		}
+	state->in.hdr_len = (ah.ah_len + 2) * 4;
+	state->in.trl_len = 0;
 
-		memcpy(iv, ipsec_sa->salt, ipsec_sa->salt_length);
-		if (odp_packet_copy_to_mem(pkt,
-					   ipsec_offset + _ODP_AHHDR_LEN,
-					   ipsec_sa->esp_iv_len,
-					   iv + ipsec_sa->salt_length) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
-		param.override_iv_ptr = iv;
+	/* Save everything to context */
+	state->ah_ipv4.tos = state->ip->tos;
+	state->ah_ipv4.frag_offset = state->ip->frag_offset;
+	state->ah_ipv4.ttl = state->ip->ttl;
 
-		hdr_len = (ah.ah_len + 2) * 4;
-		trl_len = 0;
+	/* FIXME: zero copy of header, passing it to crypto! */
+	/*
+	 * If authenticating, zero the mutable fields build the request
+	 */
+	state->ip->chksum = 0;
+	state->ip->tos = 0;
+	state->ip->frag_offset = 0;
+	state->ip->ttl = 0;
 
-		/* Save everything to context */
-		ip_tos = ip->tos;
-		ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset);
-		ip_ttl = ip->ttl;
+	state->aad.spi = ah.spi;
+	state->aad.seq_no = ah.seq_no;
 
-		/* FIXME: zero copy of header, passing it to crypto! */
-		/*
-		 * If authenticating, zero the mutable fields build the request
-		 */
-		ip->chksum = 0;
-		ip->tos = 0;
-		ip->frag_offset = 0;
-		ip->ttl = 0;
+	param->aad.ptr = (uint8_t *)&state->aad;
 
-		aad.spi = ah.spi;
-		aad.seq_no = ah.seq_no;
+	param->auth_range.offset = state->ip_offset;
+	param->auth_range.length = state->ip_tot_len;
+	param->hash_result_offset = ipsec_offset + _ODP_AHHDR_LEN +
+				ipsec_sa->esp_iv_len;
 
-		param.aad.ptr = (uint8_t *)&aad;
+	state->stats_length = param->auth_range.length;
 
-		param.auth_range.offset = ip_offset;
-		param.auth_range.length = odp_be_to_cpu_16(ip->tot_len);
-		param.hash_result_offset = ipsec_offset + _ODP_AHHDR_LEN +
-					ipsec_sa->esp_iv_len;
+	return 0;
+}
 
-		stats_length = param.auth_range.length;
+static int ipsec_in_ah_post(odp_packet_t pkt,
+			    ipsec_state_t *state)
+{
+	_odp_ahhdr_t ah;
+	uint16_t ipsec_offset;
+
+	ipsec_offset = state->ip_offset + state->ip_hdr_len;
+
+	if (odp_packet_copy_to_mem(pkt, ipsec_offset,
+				   sizeof(ah), &ah) < 0)
+		return -1;
+
+	state->ip->proto = ah.next_header;
+
+	/* Restore mutable fields */
+	state->ip->ttl = state->ah_ipv4.ttl;
+	state->ip->tos = state->ah_ipv4.tos;
+	state->ip->frag_offset = state->ah_ipv4.frag_offset;
+
+	return 0;
+}
+
+static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
+				   odp_ipsec_sa_t sa,
+				   odp_packet_t *pkt_out,
+				   odp_ipsec_op_status_t *status)
+{
+	ipsec_state_t state;
+	ipsec_sa_t *ipsec_sa = NULL;
+	odp_crypto_packet_op_param_t param;
+	int rc;
+	odp_crypto_packet_result_t crypto; /**< Crypto operation result */
+	odp_packet_hdr_t *pkt_hdr;
+
+	state.ip_offset = odp_packet_l3_offset(pkt);
+	ODP_ASSERT(ODP_PACKET_OFFSET_INVALID != state.ip_offset);
+
+	state.ip = odp_packet_l3_ptr(pkt, NULL);
+	ODP_ASSERT(NULL != state.ip);
+
+	/* Initialize parameters block */
+	memset(&param, 0, sizeof(param));
+
+	rc = ipsec_parse_ipv4(&state);
+	if (rc < 0 ||
+	    state.ip_tot_len + state.ip_offset > odp_packet_len(pkt)) {
+		status->error.alg = 1;
+		goto err;
+	}
+
+	/* Check IP header for IPSec protocols and look it up */
+	if (_ODP_IPPROTO_ESP == state.ip->proto) {
+		rc = ipsec_in_esp(&pkt, &state, &ipsec_sa, sa, &param, status);
+	} else if (_ODP_IPPROTO_AH == state.ip->proto) {
+		rc = ipsec_in_ah(&pkt, &state, &ipsec_sa, sa, &param, status);
 	} else {
 		status->error.proto = 1;
 		goto err;
 	}
+	if (rc < 0)
+		goto err;
 
 	if (_odp_ipsec_sa_replay_precheck(ipsec_sa,
-					  odp_be_to_cpu_32(aad.seq_no),
+					  odp_be_to_cpu_32(state.aad.seq_no),
 					  status) < 0)
 		goto err;
 
@@ -450,70 +560,30 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
 		goto err;
 	}
 
-	if (_odp_ipsec_sa_stats_update(ipsec_sa, stats_length, status) < 0)
+	if (_odp_ipsec_sa_stats_update(ipsec_sa,
+				       state.stats_length,
+				       status) < 0)
 		goto err;
 
 	if (_odp_ipsec_sa_replay_update(ipsec_sa,
-					odp_be_to_cpu_32(aad.seq_no),
+					odp_be_to_cpu_32(state.aad.seq_no),
 					status) < 0)
 		goto err;
 
-	ip_offset = odp_packet_l3_offset(pkt);
-	ip = odp_packet_l3_ptr(pkt, NULL);
-	ip_hdr_len = ipv4_hdr_len(ip);
-
-	if (_ODP_IPPROTO_ESP == ip->proto) {
-		/*
-		 * Finish cipher by finding ESP trailer and processing
-		 */
-		_odp_esptrl_t esptrl;
-		uint32_t esptrl_offset = ip_offset +
-					 odp_be_to_cpu_16(ip->tot_len) -
-					 trl_len;
-
-		if (odp_packet_copy_to_mem(pkt, esptrl_offset,
-					   sizeof(esptrl), &esptrl) < 0) {
-			status->error.proto = 1;
-			goto err;
-		}
-
-		if (ip_offset + esptrl.pad_len > esptrl_offset) {
-			status->error.proto = 1;
-			goto err;
-		}
-
-		if (_odp_packet_cmp_data(pkt, esptrl_offset - esptrl.pad_len,
-					 ipsec_padding, esptrl.pad_len) != 0) {
-			status->error.proto = 1;
-			goto err;
-		}
-
-		ip->proto = esptrl.next_header;
-		trl_len += esptrl.pad_len;
-	} else if (_ODP_IPPROTO_AH == ip->proto) {
-		/*
-		 * Finish auth
-		 */
-		_odp_ahhdr_t ah;
-
-		if (odp_packet_copy_to_mem(pkt, ipsec_offset,
-					   sizeof(ah), &ah) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
-
-		ip->proto = ah.next_header;
+	state.ip = odp_packet_l3_ptr(pkt, NULL);
 
-		/* Restore mutable fields */
-		ip->ttl = ip_ttl;
-		ip->tos = ip_tos;
-		ip->frag_offset = odp_cpu_to_be_16(ip_frag_offset);
-	} else {
+	if (_ODP_IPPROTO_ESP == state.ip->proto)
+		rc = ipsec_in_esp_post(pkt, &state);
+	else if (_ODP_IPPROTO_AH == state.ip->proto)
+		rc = ipsec_in_ah_post(pkt, &state);
+	else
+		rc = -1;
+	if (rc < 0) {
 		status->error.proto = 1;
 		goto err;
 	}
 
-	if (odp_packet_trunc_tail(&pkt, trl_len, NULL, NULL) < 0) {
+	if (odp_packet_trunc_tail(&pkt, state.in.trl_len, NULL, NULL) < 0) {
 		status->error.alg = 1;
 		goto err;
 	}
@@ -521,32 +591,36 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
 	if (ODP_IPSEC_MODE_TUNNEL == ipsec_sa->mode) {
 		/* We have a tunneled IPv4 packet, strip outer and IPsec
 		 * headers */
-		odp_packet_move_data(pkt, ip_hdr_len + hdr_len, 0,
-				     ip_offset);
-		if (odp_packet_trunc_head(&pkt, ip_hdr_len + hdr_len,
+		odp_packet_move_data(pkt, state.ip_hdr_len + state.in.hdr_len,
+				     0,
+				     state.ip_offset);
+		if (odp_packet_trunc_head(&pkt, state.ip_hdr_len +
+					  state.in.hdr_len,
 					  NULL, NULL) < 0) {
 			status->error.alg = 1;
 			goto err;
 		}
+
+		if (odp_packet_len(pkt) > sizeof(*state.ip)) {
+			state.ip = odp_packet_l3_ptr(pkt, NULL);
+			state.ip->ttl -= ipsec_sa->dec_ttl;
+			_odp_ipv4_csum_update(pkt);
+		}
 	} else {
-		odp_packet_move_data(pkt, hdr_len, 0,
-				     ip_offset + ip_hdr_len);
-		if (odp_packet_trunc_head(&pkt, hdr_len,
+		odp_packet_move_data(pkt, state.in.hdr_len, 0,
+				     state.ip_offset + state.ip_hdr_len);
+		if (odp_packet_trunc_head(&pkt, state.in.hdr_len,
 					  NULL, NULL) < 0) {
 			status->error.alg = 1;
 			goto err;
 		}
-	}
-
-	/* Finalize the IPv4 header */
-	if (odp_packet_len(pkt) > sizeof(*ip)) {
-		ip = odp_packet_l3_ptr(pkt, NULL);
-
-		if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode)
-			ipv4_adjust_len(ip, -(hdr_len + trl_len));
 
-		ip->ttl -= ipsec_sa->dec_ttl;
-		_odp_ipv4_csum_update(pkt);
+		if (odp_packet_len(pkt) > sizeof(*state.ip)) {
+			state.ip = odp_packet_l3_ptr(pkt, NULL);
+			ipv4_adjust_len(state.ip,
+					-(state.in.hdr_len + state.in.trl_len));
+			_odp_ipv4_csum_update(pkt);
+		}
 	}
 
 	pkt_hdr = odp_packet_hdr(pkt);
@@ -554,7 +628,7 @@ static ipsec_sa_t *ipsec_in_single(odp_packet_t pkt,
 	packet_parse_reset(pkt_hdr);
 
 	packet_parse_l3_l4(pkt_hdr, parse_layer(ipsec_config.inbound.parse),
-			   ip_offset, _ODP_ETHTYPE_IPV4);
+			   state.ip_offset, _ODP_ETHTYPE_IPV4);
 
 	*pkt_out = pkt;
 
@@ -577,317 +651,343 @@ uint32_t ipsec_seq_no(ipsec_sa_t *ipsec_sa)
 }
 
 /* Helper for calculating encode length using data length and block size */
-#define ESP_ENCODE_LEN(x, b) ((((x) + ((b) - 1)) / (b)) * (b))
+#define IPSEC_PAD_LEN(x, b) ((((x) + ((b) - 1)) / (b)) * (b))
 
-static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
-				    odp_ipsec_sa_t sa,
-				    odp_packet_t *pkt_out,
-				    odp_ipsec_out_opt_t *opt ODP_UNUSED,
-				    odp_ipsec_op_status_t *status)
+static int ipsec_out_tunnel_parse_ipv4(ipsec_state_t *state,
+				       ipsec_sa_t *ipsec_sa)
 {
-	ipsec_sa_t *ipsec_sa = NULL;
-	uint32_t ip_offset = odp_packet_l3_offset(pkt);
-	_odp_ipv4hdr_t *ip = odp_packet_l3_ptr(pkt, NULL);
-	uint16_t ip_hdr_len = ipv4_hdr_len(ip);
-	odp_crypto_packet_op_param_t param;
-	unsigned stats_length;
-	int rc;
-	uint16_t ipsec_offset;   /**< Offset of IPsec header from
-				      buffer start */
-	uint8_t	iv[IPSEC_MAX_IV_LEN];  /**< ESP IV storage */
-	ipsec_aad_t aad;         /**< AAD, note ESN is not fully supported */
-	unsigned hdr_len;        /**< Length of IPsec headers */
-	unsigned trl_len;        /**< Length of IPsec trailers */
-	uint8_t  ip_tos;         /**< Saved IP TOS value */
-	uint8_t  ip_ttl;         /**< Saved IP TTL value */
-	uint16_t ip_frag_offset; /**< Saved IP flags value */
-	odp_crypto_packet_result_t crypto; /**< Crypto operation result */
-	odp_packet_hdr_t *pkt_hdr;
+	_odp_ipv4hdr_t *ipv4hdr = state->ip;
+	uint16_t flags = odp_be_to_cpu_16(ipv4hdr->frag_offset);
 
-	ODP_ASSERT(ODP_PACKET_OFFSET_INVALID != ip_offset);
-	ODP_ASSERT(NULL != ip);
+	ipv4hdr->ttl -= ipsec_sa->dec_ttl;
+	state->out_tunnel.ip_tos = ipv4hdr->tos;
+	state->out_tunnel.ip_df = _ODP_IPV4HDR_FLAGS_DONT_FRAG(flags);
 
-	ip_tos = 0;
-	ip_ttl = 0;
-	ip_frag_offset = 0;
+	return 0;
+}
 
-	ipsec_sa = _odp_ipsec_sa_use(sa);
-	ODP_ASSERT(NULL != ipsec_sa);
+static int ipsec_out_tunnel_ipv4(odp_packet_t *pkt,
+				 ipsec_state_t *state,
+				 ipsec_sa_t *ipsec_sa)
+{
+	_odp_ipv4hdr_t out_ip;
+	uint16_t flags;
+
+	out_ip.ver_ihl = 0x45;
+	if (ipsec_sa->copy_dscp)
+		out_ip.tos = state->out_tunnel.ip_tos;
+	else
+		out_ip.tos = (state->out_tunnel.ip_tos &
+			      ~_ODP_IP_TOS_DSCP_MASK) |
+			     (ipsec_sa->out.tun_dscp <<
+			      _ODP_IP_TOS_DSCP_SHIFT);
+	state->ip_tot_len = odp_packet_len(*pkt) - state->ip_offset;
+	state->ip_tot_len += _ODP_IPV4HDR_LEN;
+
+	out_ip.tot_len = odp_cpu_to_be_16(state->ip_tot_len);
+	/* No need to convert to BE: ID just should not be duplicated */
+	out_ip.id = odp_atomic_fetch_add_u32(&ipsec_sa->out.tun_hdr_id,
+					     1);
+	if (ipsec_sa->copy_df)
+		flags = state->out_tunnel.ip_df;
+	else
+		flags = ((uint16_t)ipsec_sa->out.tun_df) << 14;
+	out_ip.frag_offset = odp_cpu_to_be_16(flags);
+	out_ip.ttl = ipsec_sa->out.tun_ttl;
+	out_ip.proto = _ODP_IPPROTO_IPIP;
+	/* Will be filled later by packet checksum update */
+	out_ip.chksum = 0;
+	out_ip.src_addr = ipsec_sa->out.tun_src_ip;
+	out_ip.dst_addr = ipsec_sa->out.tun_dst_ip;
+
+	if (odp_packet_extend_head(pkt, _ODP_IPV4HDR_LEN,
+				   NULL, NULL) < 0)
+		return -1;
 
-	/* Initialize parameters block */
-	memset(&param, 0, sizeof(param));
+	odp_packet_move_data(*pkt, 0, _ODP_IPV4HDR_LEN, state->ip_offset);
 
-	if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode &&
-	    _ODP_IPV4HDR_IS_FRAGMENT(odp_be_to_cpu_16(ip->frag_offset))) {
-		status->error.alg = 1;
-		goto err;
-	}
+	odp_packet_copy_from_mem(*pkt, state->ip_offset,
+				 _ODP_IPV4HDR_LEN, &out_ip);
 
-	if (odp_be_to_cpu_16(ip->tot_len) + ip_offset > odp_packet_len(pkt)) {
-		status->error.alg = 1;
-		goto err;
-	}
+	odp_packet_l4_offset_set(*pkt, state->ip_offset + _ODP_IPV4HDR_LEN);
 
-	if (ODP_IPSEC_MODE_TUNNEL == ipsec_sa->mode) {
-		_odp_ipv4hdr_t out_ip;
-		uint16_t tot_len;
+	state->ip = odp_packet_l3_ptr(*pkt, NULL);
+	state->ip_hdr_len = _ODP_IPV4HDR_LEN;
 
-		ip->ttl -= ipsec_sa->dec_ttl;
+	return 0;
+}
 
-		out_ip.ver_ihl = 0x45;
-		if (ipsec_sa->copy_dscp)
-			out_ip.tos = ip->tos;
-		else
-			out_ip.tos = (ip->tos & ~_ODP_IP_TOS_DSCP_MASK) |
-				     (ipsec_sa->out.tun_dscp <<
-				      _ODP_IP_TOS_DSCP_SHIFT);
-		tot_len = odp_be_to_cpu_16(ip->tot_len) + _ODP_IPV4HDR_LEN;
-		out_ip.tot_len = odp_cpu_to_be_16(tot_len);
-		/* No need to convert to BE: ID just should not be duplicated */
-		out_ip.id = odp_atomic_fetch_add_u32(&ipsec_sa->out.tun_hdr_id,
-						     1);
-		if (ipsec_sa->copy_df)
-			out_ip.frag_offset = ip->frag_offset & 0x4000;
-		else
-			out_ip.frag_offset =
-				((uint16_t)ipsec_sa->out.tun_df) << 14;
-		out_ip.ttl = ipsec_sa->out.tun_ttl;
-		out_ip.proto = _ODP_IPV4;
-		/* Will be filled later by packet checksum update */
-		out_ip.chksum = 0;
-		out_ip.src_addr = ipsec_sa->out.tun_src_ip;
-		out_ip.dst_addr = ipsec_sa->out.tun_dst_ip;
-
-		if (odp_packet_extend_head(&pkt, _ODP_IPV4HDR_LEN,
-					   NULL, NULL) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+static int ipsec_out_iv(ipsec_state_t *state,
+			ipsec_sa_t *ipsec_sa)
+{
+	if (ipsec_sa->use_counter_iv) {
+		uint64_t ctr;
+
+		/* Both GCM and CTR use 8-bit counters */
+		ODP_ASSERT(sizeof(ctr) == ipsec_sa->esp_iv_len);
 
-		odp_packet_move_data(pkt, 0, _ODP_IPV4HDR_LEN, ip_offset);
+		ctr = odp_atomic_fetch_add_u64(&ipsec_sa->out.counter,
+					       1);
+		/* Check for overrun */
+		if (ctr == 0)
+			return -1;
 
-		odp_packet_copy_from_mem(pkt, ip_offset,
-					 _ODP_IPV4HDR_LEN, &out_ip);
+		memcpy(state->iv, ipsec_sa->salt, ipsec_sa->salt_length);
+		memcpy(state->iv + ipsec_sa->salt_length, &ctr,
+		       ipsec_sa->esp_iv_len);
 
-		odp_packet_l4_offset_set(pkt, ip_offset + _ODP_IPV4HDR_LEN);
+		if (ipsec_sa->aes_ctr_iv) {
+			state->iv[12] = 0;
+			state->iv[13] = 0;
+			state->iv[14] = 0;
+			state->iv[15] = 1;
+		}
+	} else if (ipsec_sa->esp_iv_len) {
+		uint32_t len;
 
-		ip = odp_packet_l3_ptr(pkt, NULL);
-		ip_hdr_len = _ODP_IPV4HDR_LEN;
+		len = odp_random_data(state->iv, ipsec_sa->esp_iv_len,
+				      ODP_RANDOM_CRYPTO);
+
+		if (len != ipsec_sa->esp_iv_len)
+			return -1;
 	}
 
-	ipsec_offset = ip_offset + ip_hdr_len;
+	return 0;
+}
 
-	if (ipsec_sa->proto == ODP_IPSEC_ESP) {
-		_odp_esphdr_t esp;
-		_odp_esptrl_t esptrl;
-		uint32_t encrypt_len;
-		uint16_t ip_data_len = odp_be_to_cpu_16(ip->tot_len) -
-				       ip_hdr_len;
-		uint32_t pad_block = ipsec_sa->esp_block_len;
+static int ipsec_out_esp(odp_packet_t *pkt,
+			 ipsec_state_t *state,
+			 ipsec_sa_t *ipsec_sa,
+			 odp_crypto_packet_op_param_t *param)
+{
+	_odp_esphdr_t esp;
+	_odp_esptrl_t esptrl;
+	uint32_t encrypt_len;
+	uint16_t ip_data_len = state->ip_tot_len -
+			       state->ip_hdr_len;
+	uint32_t pad_block = ipsec_sa->esp_block_len;
+	uint16_t ipsec_offset = state->ip_offset + state->ip_hdr_len;
+	unsigned hdr_len;
+	unsigned trl_len;
+
+	/* ESP trailer should be 32-bit right aligned */
+	if (pad_block < 4)
+		pad_block = 4;
+
+	encrypt_len = IPSEC_PAD_LEN(ip_data_len + _ODP_ESPTRL_LEN,
+				    pad_block);
+
+	hdr_len = _ODP_ESPHDR_LEN + ipsec_sa->esp_iv_len;
+	trl_len = encrypt_len -
+		       ip_data_len +
+		       ipsec_sa->icv_len;
+
+	if (ipsec_out_iv(state, ipsec_sa) < 0)
+		return -1;
 
-		/* ESP trailer should be 32-bit right aligned */
-		if (pad_block < 4)
-			pad_block = 4;
+	param->override_iv_ptr = state->iv;
 
-		encrypt_len = ESP_ENCODE_LEN(ip_data_len + _ODP_ESPTRL_LEN,
-					     pad_block);
+	if (odp_packet_extend_tail(pkt, trl_len, NULL, NULL) < 0 ||
+	    odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0)
+		return -1;
 
-		hdr_len = _ODP_ESPHDR_LEN + ipsec_sa->esp_iv_len;
-		trl_len = encrypt_len -
-			       ip_data_len +
-			       ipsec_sa->icv_len;
+	odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
+
+	state->ip = odp_packet_l3_ptr(*pkt, NULL);
+
+	/* Set IPv4 length before authentication */
+	ipv4_adjust_len(state->ip, hdr_len + trl_len);
+	state->ip_tot_len += hdr_len + trl_len;
+
+	uint32_t esptrl_offset = state->ip_offset +
+				 state->ip_hdr_len +
+				 hdr_len +
+				 encrypt_len -
+				 _ODP_ESPTRL_LEN;
+
+	memset(&esp, 0, sizeof(esp));
+	esp.spi = odp_cpu_to_be_32(ipsec_sa->spi);
+	esp.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
+
+	state->aad.spi = esp.spi;
+	state->aad.seq_no = esp.seq_no;
+
+	param->aad.ptr = (uint8_t *)&state->aad;
+
+	memset(&esptrl, 0, sizeof(esptrl));
+	esptrl.pad_len = encrypt_len - ip_data_len - _ODP_ESPTRL_LEN;
+	esptrl.next_header = state->ip->proto;
+	state->ip->proto = _ODP_IPPROTO_ESP;
+
+	odp_packet_copy_from_mem(*pkt,
+				 ipsec_offset, _ODP_ESPHDR_LEN,
+				 &esp);
+	odp_packet_copy_from_mem(*pkt,
+				 ipsec_offset + _ODP_ESPHDR_LEN,
+				 ipsec_sa->esp_iv_len,
+				 state->iv + ipsec_sa->salt_length);
+	odp_packet_copy_from_mem(*pkt,
+				 esptrl_offset - esptrl.pad_len,
+				 esptrl.pad_len, ipsec_padding);
+	odp_packet_copy_from_mem(*pkt,
+				 esptrl_offset, _ODP_ESPTRL_LEN,
+				 &esptrl);
+
+	param->cipher_range.offset = ipsec_offset + hdr_len;
+	param->cipher_range.length = state->ip_tot_len -
+				    state->ip_hdr_len -
+				    hdr_len -
+				    ipsec_sa->icv_len;
+
+	param->auth_range.offset = ipsec_offset;
+	param->auth_range.length = state->ip_tot_len -
+				  state->ip_hdr_len -
+				  ipsec_sa->icv_len;
+	param->hash_result_offset = state->ip_offset +
+				   state->ip_tot_len -
+				   ipsec_sa->icv_len;
+
+	state->stats_length = param->cipher_range.length;
 
-		if (ipsec_sa->use_counter_iv) {
-			uint64_t ctr;
+	return 0;
+}
 
-			/* Both GCM and CTR use 8-bit counters */
-			ODP_ASSERT(sizeof(ctr) == ipsec_sa->esp_iv_len);
+static int ipsec_out_ah(odp_packet_t *pkt,
+			ipsec_state_t *state,
+			ipsec_sa_t *ipsec_sa,
+			odp_crypto_packet_op_param_t *param)
+{
+	_odp_ahhdr_t ah;
+	unsigned hdr_len = _ODP_AHHDR_LEN + ipsec_sa->esp_iv_len +
+		ipsec_sa->icv_len;
+	uint16_t ipsec_offset = state->ip_offset + state->ip_hdr_len;
 
-			ctr = odp_atomic_fetch_add_u64(&ipsec_sa->out.counter,
-						       1);
-			/* Check for overrun */
-			if (ctr == 0)
-				goto err;
+	/* Save IPv4 stuff */
+	state->ah_ipv4.tos = state->ip->tos;
+	state->ah_ipv4.frag_offset = state->ip->frag_offset;
+	state->ah_ipv4.ttl = state->ip->ttl;
 
-			memcpy(iv, ipsec_sa->salt, ipsec_sa->salt_length);
-			memcpy(iv + ipsec_sa->salt_length, &ctr,
-			       ipsec_sa->esp_iv_len);
+	if (odp_packet_extend_head(pkt, hdr_len, NULL, NULL) < 0)
+		return -1;
 
-			if (ipsec_sa->aes_ctr_iv) {
-				iv[12] = 0;
-				iv[13] = 0;
-				iv[14] = 0;
-				iv[15] = 1;
-			}
-		} else if (ipsec_sa->esp_iv_len) {
-			uint32_t len;
+	odp_packet_move_data(*pkt, 0, hdr_len, ipsec_offset);
 
-			len = odp_random_data(iv, ipsec_sa->esp_iv_len,
-					      ODP_RANDOM_CRYPTO);
+	state->ip = odp_packet_l3_ptr(*pkt, NULL);
 
-			if (len != ipsec_sa->esp_iv_len) {
-				status->error.alg = 1;
-				goto err;
-			}
-		}
+	/* Set IPv4 length before authentication */
+	ipv4_adjust_len(state->ip, hdr_len);
+	state->ip_tot_len += hdr_len;
 
-		param.override_iv_ptr = iv;
+	memset(&ah, 0, sizeof(ah));
+	ah.spi = odp_cpu_to_be_32(ipsec_sa->spi);
+	ah.ah_len = 1 + (ipsec_sa->esp_iv_len + ipsec_sa->icv_len) / 4;
+	ah.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
+	ah.next_header = state->ip->proto;
+	state->ip->proto = _ODP_IPPROTO_AH;
 
-		if (odp_packet_extend_tail(&pkt, trl_len, NULL, NULL) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	state->aad.spi = ah.spi;
+	state->aad.seq_no = ah.seq_no;
 
-		if (odp_packet_extend_head(&pkt, hdr_len, NULL, NULL) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	param->aad.ptr = (uint8_t *)&state->aad;
 
-		odp_packet_move_data(pkt, 0, hdr_len, ipsec_offset);
-
-		ip = odp_packet_l3_ptr(pkt, NULL);
-
-		/* Set IPv4 length before authentication */
-		ipv4_adjust_len(ip, hdr_len + trl_len);
-
-		uint32_t esptrl_offset = ip_offset +
-					 ip_hdr_len +
-					 hdr_len +
-					 encrypt_len -
-					 _ODP_ESPTRL_LEN;
-
-		memset(&esp, 0, sizeof(esp));
-		esp.spi = odp_cpu_to_be_32(ipsec_sa->spi);
-		esp.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
-
-		aad.spi = esp.spi;
-		aad.seq_no = esp.seq_no;
-
-		param.aad.ptr = (uint8_t *)&aad;
-
-		memset(&esptrl, 0, sizeof(esptrl));
-		esptrl.pad_len = encrypt_len - ip_data_len - _ODP_ESPTRL_LEN;
-		esptrl.next_header = ip->proto;
-		ip->proto = _ODP_IPPROTO_ESP;
-
-		odp_packet_copy_from_mem(pkt,
-					 ipsec_offset, _ODP_ESPHDR_LEN,
-					 &esp);
-		odp_packet_copy_from_mem(pkt,
-					 ipsec_offset + _ODP_ESPHDR_LEN,
-					 ipsec_sa->esp_iv_len,
-					 iv + ipsec_sa->salt_length);
-		odp_packet_copy_from_mem(pkt,
-					 esptrl_offset - esptrl.pad_len,
-					 esptrl.pad_len, ipsec_padding);
-		odp_packet_copy_from_mem(pkt,
-					 esptrl_offset, _ODP_ESPTRL_LEN,
-					 &esptrl);
-
-		param.cipher_range.offset = ipsec_offset + hdr_len;
-		param.cipher_range.length = odp_be_to_cpu_16(ip->tot_len) -
-					    ip_hdr_len -
-					    hdr_len -
-					    ipsec_sa->icv_len;
-
-		param.auth_range.offset = ipsec_offset;
-		param.auth_range.length = odp_be_to_cpu_16(ip->tot_len) -
-					  ip_hdr_len -
-					  ipsec_sa->icv_len;
-		param.hash_result_offset = ip_offset +
-					   odp_be_to_cpu_16(ip->tot_len) -
-					   ipsec_sa->icv_len;
-
-		stats_length = param.cipher_range.length;
-	} else if (ipsec_sa->proto == ODP_IPSEC_AH) {
-		_odp_ahhdr_t ah;
-
-		hdr_len = _ODP_AHHDR_LEN + ipsec_sa->esp_iv_len +
-			ipsec_sa->icv_len;
-		trl_len = 0;
-
-		/* Save IPv4 stuff */
-		ip_tos = ip->tos;
-		ip_frag_offset = odp_be_to_cpu_16(ip->frag_offset);
-		ip_ttl = ip->ttl;
-
-		if (odp_packet_extend_tail(&pkt, trl_len, NULL, NULL) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	/* For GMAC */
+	if (ipsec_out_iv(state, ipsec_sa) < 0)
+		return -1;
 
-		if (odp_packet_extend_head(&pkt, hdr_len, NULL, NULL) < 0) {
-			status->error.alg = 1;
-			goto err;
-		}
+	param->override_iv_ptr = state->iv;
 
-		odp_packet_move_data(pkt, 0, hdr_len, ipsec_offset);
+	odp_packet_copy_from_mem(*pkt,
+				 ipsec_offset, _ODP_AHHDR_LEN,
+				 &ah);
+	odp_packet_copy_from_mem(*pkt,
+				 ipsec_offset + _ODP_AHHDR_LEN,
+				 ipsec_sa->esp_iv_len,
+				 state->iv + ipsec_sa->salt_length);
+	_odp_packet_set_data(*pkt,
+			     ipsec_offset + _ODP_AHHDR_LEN +
+			       ipsec_sa->esp_iv_len,
+			     0, ipsec_sa->icv_len);
 
-		ip = odp_packet_l3_ptr(pkt, NULL);
+	state->ip->chksum = 0;
+	state->ip->tos = 0;
+	state->ip->frag_offset = 0;
+	state->ip->ttl = 0;
 
-		/* Set IPv4 length before authentication */
-		ipv4_adjust_len(ip, hdr_len + trl_len);
+	param->auth_range.offset = state->ip_offset;
+	param->auth_range.length = state->ip_tot_len;
+	param->hash_result_offset = ipsec_offset + _ODP_AHHDR_LEN +
+				ipsec_sa->esp_iv_len;
 
-		memset(&ah, 0, sizeof(ah));
-		ah.spi = odp_cpu_to_be_32(ipsec_sa->spi);
-		ah.ah_len = 1 + (ipsec_sa->esp_iv_len + ipsec_sa->icv_len) / 4;
-		ah.seq_no = odp_cpu_to_be_32(ipsec_seq_no(ipsec_sa));
-		ah.next_header = ip->proto;
-		ip->proto = _ODP_IPPROTO_AH;
+	state->stats_length = param->auth_range.length;
 
-		aad.spi = ah.spi;
-		aad.seq_no = ah.seq_no;
+	return 0;
+}
 
-		param.aad.ptr = (uint8_t *)&aad;
+static void ipsec_out_ah_post(ipsec_state_t *state)
+{
+	state->ip->ttl = state->ah_ipv4.ttl;
+	state->ip->tos = state->ah_ipv4.tos;
+	state->ip->frag_offset = state->ah_ipv4.frag_offset;
+}
 
-		/* For GMAC */
-		if (ipsec_sa->use_counter_iv) {
-			uint64_t ctr;
+static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
+				    odp_ipsec_sa_t sa,
+				    odp_packet_t *pkt_out,
+				    odp_ipsec_out_opt_t *opt ODP_UNUSED,
+				    odp_ipsec_op_status_t *status)
+{
+	ipsec_state_t state;
+	ipsec_sa_t *ipsec_sa;
+	odp_crypto_packet_op_param_t param;
+	int rc;
+	odp_crypto_packet_result_t crypto; /**< Crypto operation result */
+	odp_packet_hdr_t *pkt_hdr;
 
-			ODP_ASSERT(sizeof(ctr) == ipsec_sa->esp_iv_len);
+	state.ip_offset = odp_packet_l3_offset(pkt);
+	ODP_ASSERT(ODP_PACKET_OFFSET_INVALID != state.ip_offset);
 
-			ctr = odp_atomic_fetch_add_u64(&ipsec_sa->out.counter,
-						       1);
-			/* Check for overrun */
-			if (ctr == 0)
-				goto err;
+	state.ip = odp_packet_l3_ptr(pkt, NULL);
+	ODP_ASSERT(NULL != state.ip);
 
-			memcpy(iv, ipsec_sa->salt, ipsec_sa->salt_length);
-			memcpy(iv + ipsec_sa->salt_length, &ctr,
-			       ipsec_sa->esp_iv_len);
-			param.override_iv_ptr = iv;
-		}
+	ipsec_sa = _odp_ipsec_sa_use(sa);
+	ODP_ASSERT(NULL != ipsec_sa);
 
-		odp_packet_copy_from_mem(pkt,
-					 ipsec_offset, _ODP_AHHDR_LEN,
-					 &ah);
-		odp_packet_copy_from_mem(pkt,
-					 ipsec_offset + _ODP_AHHDR_LEN,
-					 ipsec_sa->esp_iv_len,
-					 iv + ipsec_sa->salt_length);
-		_odp_packet_set_data(pkt,
-				     ipsec_offset + _ODP_AHHDR_LEN +
-				       ipsec_sa->esp_iv_len,
-				     0, ipsec_sa->icv_len);
-
-		ip->chksum = 0;
-		ip->tos = 0;
-		ip->frag_offset = 0;
-		ip->ttl = 0;
-
-		param.auth_range.offset = ip_offset;
-		param.auth_range.length = odp_be_to_cpu_16(ip->tot_len);
-		param.hash_result_offset = ipsec_offset + _ODP_AHHDR_LEN +
-					ipsec_sa->esp_iv_len;
-
-		stats_length = param.auth_range.length;
+	/* Initialize parameters block */
+	memset(&param, 0, sizeof(param));
+
+	if (ODP_IPSEC_MODE_TRANSPORT == ipsec_sa->mode) {
+		rc = ipsec_parse_ipv4(&state);
+		if (state.ip_tot_len + state.ip_offset != odp_packet_len(pkt))
+			rc = -1;
 	} else {
+		rc = ipsec_out_tunnel_parse_ipv4(&state, ipsec_sa);
+		if (rc < 0) {
+			status->error.alg = 1;
+			goto err;
+		}
+
+		rc = ipsec_out_tunnel_ipv4(&pkt, &state, ipsec_sa);
+	}
+	if (rc < 0) {
+		status->error.alg = 1;
+		goto err;
+	}
+
+	if (ipsec_sa->proto == ODP_IPSEC_ESP)
+		rc = ipsec_out_esp(&pkt, &state, ipsec_sa, &param);
+	else if (ipsec_sa->proto == ODP_IPSEC_AH)
+		rc = ipsec_out_ah(&pkt, &state, ipsec_sa, &param);
+	else
+		rc = -1;
+	if (rc < 0) {
 		status->error.alg = 1;
 		goto err;
 	}
 
 	/* No need to run precheck here, we know that packet is authentic */
-	if (_odp_ipsec_sa_stats_update(ipsec_sa, stats_length, status) < 0)
+	if (_odp_ipsec_sa_stats_update(ipsec_sa,
+				       state.stats_length,
+				       status) < 0)
 		goto err;
 
 	param.session = ipsec_sa->session;
@@ -922,14 +1022,9 @@ static ipsec_sa_t *ipsec_out_single(odp_packet_t pkt,
 		goto err;
 	}
 
-	ip = odp_packet_l3_ptr(pkt, NULL);
-
 	/* Finalize the IPv4 header */
-	if (ip->proto == _ODP_IPPROTO_AH) {
-		ip->ttl = ip_ttl;
-		ip->tos = ip_tos;
-		ip->frag_offset = odp_cpu_to_be_16(ip_frag_offset);
-	}
+	if (ipsec_sa->proto == ODP_IPSEC_AH)
+		ipsec_out_ah_post(&state);
 
 	_odp_ipv4_csum_update(pkt);
 
