From 5dff20342340843115c9b1ea92f7f068cf47daa8 Mon Sep 17 00:00:00 2001
From: Maximilian Fridrich <m.fridrich@commend.com>
Date: Thu, 23 Nov 2023 14:46:33 +0100
Subject: [PATCH] sipsess: refactor and simplify SDP negotiation state

The SDP negotiation state is now tracked in a single enum which is used
as a state machine to keep track of the current state of the SDP
negotiation.
---
 include/re_sipsess.h  | 10 ++++++
 src/sipsess/accept.c  |  3 ++
 src/sipsess/connect.c | 70 ++++++++++++++++++++++++++++-----------
 src/sipsess/listen.c  | 53 +++++++++++++++++------------
 src/sipsess/modify.c  | 77 ++++++++++++++++++++++++++++---------------
 src/sipsess/prack.c   |  9 ++---
 src/sipsess/reply.c   | 72 +++++++++++++++++++++++++---------------
 src/sipsess/sess.c    | 18 ++++++++--
 src/sipsess/sipsess.h | 11 +++----
 src/sipsess/update.c  | 17 +++++++---
 test/sipsess.c        | 22 ++++++++-----
 11 files changed, 237 insertions(+), 125 deletions(-)

diff --git a/include/re_sipsess.h b/include/re_sipsess.h
index 87006197b..d80e429b3 100644
--- a/include/re_sipsess.h
+++ b/include/re_sipsess.h
@@ -7,6 +7,15 @@
 struct sipsess_sock;
 struct sipsess;
 
+/* SDP Negotiation state */
+enum sdp_neg_state {
+	SDP_NEG_NONE = 0,
+	SDP_NEG_LOCAL_OFFER = (1 << 0),		/** SDP offer sent */
+	SDP_NEG_REMOTE_OFFER = (1 << 1),	/** SDP offer received */
+	SDP_NEG_PREVIEW_ANSWER = (1 << 2),	/** SDP preview answer sent */
+	SDP_NEG_DONE = (1 << 3)			/** SDP negotiation done */
+};
+
 
 typedef void (sipsess_conn_h)(const struct sip_msg *msg, void *arg);
 typedef int  (sipsess_desc_h)(struct mbuf **descp, const struct sa *src,
@@ -74,3 +83,4 @@ void sipsess_close_all(struct sipsess_sock *sock);
 struct sip_dialog *sipsess_dialog(const struct sipsess *sess);
 void sipsess_abort(struct sipsess *sess);
 bool sipsess_ack_pending(const struct sipsess *sess);
+enum sdp_neg_state sipsess_sdp_neg_state(const struct sipsess *sess);
diff --git a/src/sipsess/accept.c b/src/sipsess/accept.c
index ab50daeff..1f506dc7e 100644
--- a/src/sipsess/accept.c
+++ b/src/sipsess/accept.c
@@ -99,6 +99,9 @@ int sipsess_accept(struct sipsess **sessp, struct sipsess_sock *sock,
 	if (err)
 		goto out;
 
+	if (mbuf_get_left(msg->mb))
+		sess->neg_state = SDP_NEG_REMOTE_OFFER;
+
 	va_start(ap, fmt);
 
 	if (scode > 100 && scode < 200) {
diff --git a/src/sipsess/connect.c b/src/sipsess/connect.c
index 7a67d1e13..feebb7d6f 100644
--- a/src/sipsess/connect.c
+++ b/src/sipsess/connect.c
@@ -68,7 +68,9 @@ static int send_handler(enum sip_transp tp, struct sa *src,
 		*contp = cont;
 
 out:
-	sess->sent_offer = desc != NULL;
+	if (desc)
+		sess->neg_state = SDP_NEG_LOCAL_OFFER;
+
 	mem_deref(desc);
 	return err;
 }
@@ -89,20 +91,14 @@ static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg)
 	if (!msg || err || sip_request_loops(&sess->ls, msg->scode))
 		goto out;
 
+	sdp = mbuf_get_left(msg->mb) > 0;
+
 	if (msg->scode < 200) {
 		sess->progrh(msg, sess->arg);
-		sdp = mbuf_get_left(msg->mb) > 0;
 
 		if (msg->scode == 100)
 			return;
 
-		if (sdp && sess->sent_offer) {
-			sess->awaiting_answer = false;
-			err = sess->answerh(msg, sess->arg);
-			if (err)
-				goto out;
-		}
-
 		if (pl_isset(&msg->to.tag)) {
 			err = sip_dialog_established(sess->dlg) ?
 					sip_dialog_update(sess->dlg, msg) :
@@ -111,19 +107,40 @@ static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg)
 				goto out;
 		}
 
+		if (sdp && sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+			err = sess->answerh(msg, sess->arg);
+			if (err)
+				goto out;
+		}
+
 		if (sip_msg_hdr_has_value(msg, SIP_HDR_REQUIRE, "100rel")
 				&& sess->rel100_supported) {
-			if (sdp && !sess->sent_offer) {
-				sess->modify_pending = false;
-				err = sess->offerh(&desc, msg, sess->arg);
+
+			if (sess->neg_state == SDP_NEG_NONE && !sdp)
+				goto out;
+
+			if (sdp) {
+				if (sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+					sess->neg_state = SDP_NEG_DONE;
+				}
+				else if (sess->neg_state == SDP_NEG_NONE) {
+					sess->neg_state = SDP_NEG_REMOTE_OFFER;
+					err = sess->offerh(&desc, msg,
+							   sess->arg);
+				}
 			}
 
 			err |= sipsess_prack(sess, msg->cseq.num, msg->rel_seq,
 					     &msg->cseq.met, desc);
-			mem_deref(desc);
-			sess->desc = mem_deref(sess->desc);
 			if (err)
 				goto out;
+
+			if (sess->neg_state == SDP_NEG_REMOTE_OFFER
+			    && mbuf_get_left(desc))
+				sess->neg_state = SDP_NEG_DONE;
+
+			mem_deref(desc);
+			sess->desc = mem_deref(sess->desc);
 		}
 
 		return;
@@ -138,15 +155,28 @@ static void invite_resp_handler(int err, const struct sip_msg *msg, void *arg)
 		if (err)
 			goto out;
 
-		if (sess->sent_offer)
-			err = sess->answerh(msg, sess->arg);
-		else {
-			sess->modify_pending = false;
-			err = sess->offerh(&desc, msg, sess->arg);
+		if (sdp) {
+			if (sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+				sess->neg_state = SDP_NEG_DONE;
+				err = sess->answerh(msg, sess->arg);
+			}
+			else if (sess->neg_state == SDP_NEG_NONE) {
+				sess->neg_state = SDP_NEG_REMOTE_OFFER;
+				err = sess->offerh(&desc, msg, sess->arg);
+			}
 		}
 
 		err |= sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
-				   sess->auth, sess->ctype, desc);
+				  sess->auth, sess->ctype, desc);
+		if (err)
+			goto out;
+
+		if (sess->neg_state == SDP_NEG_NONE && !sdp)
+			goto out;
+
+		if (sess->neg_state == SDP_NEG_REMOTE_OFFER
+		    && mbuf_get_left(desc))
+			sess->neg_state = SDP_NEG_DONE;
 
 		sess->established = true;
 		mem_deref(desc);
diff --git a/src/sipsess/listen.c b/src/sipsess/listen.c
index a47fda866..d81a7dfe2 100644
--- a/src/sipsess/listen.c
+++ b/src/sipsess/listen.c
@@ -128,14 +128,13 @@ static void bye_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 {
 	struct sipsess *sess;
-	bool awaiting_answer;
 	int err = 0;
 
 	sess = sipsess_find(sock, msg);
 	if (!sess)
 		return;
 
-	if (sipsess_reply_ack(sess, msg, &awaiting_answer))
+	if (sipsess_reply_ack(sess, msg))
 		return;
 
 	if (sess->terminated) {
@@ -146,8 +145,13 @@ static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 		return;
 	}
 
-	if (awaiting_answer) {
-		sess->awaiting_answer = false;
+	if (sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+		if (!mbuf_get_left(msg->mb)) {
+			sipsess_terminate(sess, EPROTO, NULL);
+			return;
+		}
+
+		sess->neg_state = SDP_NEG_DONE;
 		err = sess->answerh(msg, sess->arg);
 	}
 
@@ -169,15 +173,14 @@ static void ack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 
 static void prack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 {
+	bool sdp;
 	struct sipsess *sess;
 	struct mbuf *desc = NULL;
-	bool awaiting_answer = false;
 	bool awaiting_prack = false;
 
 	sess = sipsess_find(sock, msg);
 
-	if (!sess || sipsess_reply_prack(sess, msg, &awaiting_answer,
-					 &awaiting_prack)) {
+	if (!sess || sipsess_reply_prack(sess, msg, &awaiting_prack)) {
 		(void)sip_reply(sock->sip, msg, 481,
 				"Transaction Does Not Exist");
 		return;
@@ -192,22 +195,28 @@ static void prack_handler(struct sipsess_sock *sock, const struct sip_msg *msg)
 		return;
 	}
 
-	if (awaiting_prack) {
-		sess->awaiting_prack = false;
-		sess->refresh_allowed = true;
-	}
+	sdp = mbuf_get_left(msg->mb);
 
-	if (sess->prackh)
-		sess->prackh(msg, sess->arg);
+	if (awaiting_prack)
+		--sess->prack_waiting_cnt;
 
-	if (awaiting_answer) {
-		sess->awaiting_answer = false;
+	if (sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+		if (!sdp) {
+			sipsess_terminate(sess, EPROTO, NULL);
+			return;
+		}
+
+		sess->neg_state = SDP_NEG_DONE;
 		(void)sess->answerh(msg, sess->arg);
 	}
-	else if (msg && mbuf_get_left(msg->mb)) {
+	else if (sess->neg_state == SDP_NEG_DONE && sdp) {
+		sess->neg_state = SDP_NEG_REMOTE_OFFER;
 		(void)sess->offerh(&desc, msg, sess->arg);
 	}
 
+	if (sess->prackh)
+		sess->prackh(msg, sess->arg);
+
 	(void)sipsess_reply_2xx(sess, msg, 200, "OK", desc, NULL, NULL);
 
 	mem_deref(desc);
@@ -219,7 +228,7 @@ static void target_refresh_handler(struct sipsess_sock *sock,
 {
 	struct sip *sip = sock->sip;
 	bool is_invite;
-	bool got_offer;
+	bool sdp;
 	struct sipsess *sess;
 	struct mbuf *desc = NULL;
 	char m[256];
@@ -232,14 +241,15 @@ static void target_refresh_handler(struct sipsess_sock *sock,
 	}
 
 	is_invite = !pl_strcmp(&msg->met, "INVITE");
-	got_offer = (mbuf_get_left(msg->mb) > 0);
+	sdp = (mbuf_get_left(msg->mb) > 0);
 
 	if (!sip_dialog_rseq_valid(sess->dlg, msg)) {
 		(void)sip_treply(NULL, sip, msg, 500, "Server Internal Error");
 		return;
 	}
 
-	if ((is_invite && sess->st) || sess->awaiting_answer) {
+	if ((is_invite && sess->st)
+	    || (sdp && sess->neg_state == SDP_NEG_LOCAL_OFFER)) {
 		(void)sip_treplyf(NULL, NULL, sip, msg, false,
 				  500, "Server Internal Error",
 				  "Retry-After: 5\r\n"
@@ -253,12 +263,13 @@ static void target_refresh_handler(struct sipsess_sock *sock,
 		return;
 	}
 
-	if (got_offer && !sipsess_refresh_allowed(sess)) {
+	if (sdp && !sipsess_refresh_allowed(sess)) {
 		(void)sip_reply(sip, msg, 488, "Not Acceptable Here");
 		return;
 	}
 
-	if (is_invite || got_offer) {
+	if (sdp) {
+		sess->neg_state = SDP_NEG_REMOTE_OFFER;
 		err = sess->offerh(&desc, msg, sess->arg);
 		if (err) {
 			(void)sip_reply(sip, msg, 488,
diff --git a/src/sipsess/modify.c b/src/sipsess/modify.c
index 3f7872be8..6bb0e1fbc 100644
--- a/src/sipsess/modify.c
+++ b/src/sipsess/modify.c
@@ -32,10 +32,13 @@ static void reinvite_resp_handler(int err, const struct sip_msg *msg,
 	struct sipsess *sess = arg;
 	const struct sip_hdr *hdr;
 	struct mbuf *desc = NULL;
+	bool sdp;
 
 	if (!msg || err || sip_request_loops(&sess->ls, msg->scode))
 		goto out;
 
+	sdp = mbuf_get_left(msg->mb) > 0;
+
 	if (msg->scode < 200) {
 		return;
 	}
@@ -43,20 +46,34 @@ static void reinvite_resp_handler(int err, const struct sip_msg *msg,
 
 		(void)sip_dialog_update(sess->dlg, msg);
 
-		if (sess->sent_offer) {
-			(void)sess->answerh(msg, sess->arg);
-		}
-		else {
-			sess->modify_pending = false;
-			(void)sess->offerh(&desc, msg, sess->arg);
+		if (sdp) {
+			if (sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+				sess->neg_state = SDP_NEG_DONE;
+				err = sess->answerh(msg, sess->arg);
+			}
+			else if (sess->neg_state == SDP_NEG_NONE) {
+				sess->neg_state = SDP_NEG_REMOTE_OFFER;
+				err = sess->offerh(&desc, msg, sess->arg);
+			}
+
+			if (err)
+				goto out;
 		}
 
-		(void)sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
+		err = sipsess_ack(sess->sock, sess->dlg, msg->cseq.num,
 				  sess->auth, sess->ctype, desc);
+		if (err)
+			goto out;
+
+		if (sess->neg_state == SDP_NEG_REMOTE_OFFER
+		    && mbuf_get_left(desc))
+		    	sess->neg_state = SDP_NEG_DONE;
 
 		mem_deref(desc);
 	}
 	else {
+		sess->neg_state = SDP_NEG_DONE;
+
 		if (sess->terminated)
 			goto out;
 
@@ -126,28 +143,35 @@ static int send_handler(enum sip_transp tp, struct sa *src,
 
 int sipsess_reinvite(struct sipsess *sess, bool reset_ls)
 {
+	int err;
+
 	if (sess->req)
 		return EPROTO;
 
-	sess->sent_offer = sess->desc ? true : false;
-	sess->modify_pending = false;
-
 	if (reset_ls)
 		sip_loopstate_reset(&sess->ls);
 
-	return sip_drequestf(&sess->req, sess->sip, true, "INVITE",
-			     sess->dlg, 0, sess->auth,
-			     send_handler, reinvite_resp_handler, sess,
-			     "%s%s%s"
-			     "Content-Length: %zu\r\n"
-			     "\r\n"
-			     "%b",
-			     sess->desc ? "Content-Type: " : "",
-			     sess->desc ? sess->ctype : "",
-			     sess->desc ? "\r\n" : "",
-			     sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
-			     sess->desc ? mbuf_buf(sess->desc) : NULL,
-			     sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+	err = sip_drequestf(&sess->req, sess->sip, true, "INVITE",
+			    sess->dlg, 0, sess->auth,
+			    send_handler, reinvite_resp_handler, sess,
+			    "%s%s%s"
+			    "Content-Length: %zu\r\n"
+			    "\r\n"
+			    "%b",
+			    sess->desc ? "Content-Type: " : "",
+			    sess->desc ? sess->ctype : "",
+			    sess->desc ? "\r\n" : "",
+			    sess->desc ? mbuf_get_left(sess->desc) :(size_t)0,
+			    sess->desc ? mbuf_buf(sess->desc) : NULL,
+			    sess->desc ? mbuf_get_left(sess->desc):(size_t)0);
+
+	if (!err) {
+		sess->modify_pending = false;
+		if (sess->desc)
+			sess->neg_state = SDP_NEG_LOCAL_OFFER;
+	}
+
+	return err;
 }
 
 
@@ -161,12 +185,11 @@ int sipsess_reinvite(struct sipsess *sess, bool reset_ls)
  */
 int sipsess_modify(struct sipsess *sess, struct mbuf *desc)
 {
-	if (!sess || sess->terminated || sess->awaiting_answer
-	    || !sip_dialog_established(sess->dlg))
+	if (!sess || sess->terminated || !sip_dialog_established(sess->dlg))
 		return EINVAL;
 
-	if (!sess->established && !sess->refresh_allowed
-	    && mbuf_get_left(desc))
+	if (mbuf_get_left(desc) && (sess->neg_state != SDP_NEG_DONE
+	    && sess->neg_state != SDP_NEG_NONE))
 		return EPROTO;
 
 	mem_deref(sess->desc);
diff --git a/src/sipsess/prack.c b/src/sipsess/prack.c
index cb23d6f8f..81a59712e 100644
--- a/src/sipsess/prack.c
+++ b/src/sipsess/prack.c
@@ -68,9 +68,8 @@ static void prack_resp_handler(int err, const struct sip_msg *msg, void *arg)
 		(void)sip_dialog_update(req->sess->dlg, msg);
 
 		if (mbuf_get_left(msg->mb)) {
-			if (req->sess->sent_offer) {
-				req->sess->awaiting_answer = false;
-				req->sess->refresh_allowed = true;
+			if (req->sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+				req->sess->neg_state = SDP_NEG_DONE;
 				(void)req->sess->answerh(msg, req->sess->arg);
 			}
 
@@ -136,10 +135,6 @@ static int prack_request(struct sipsess_prack *prack)
 	if (err == -1)
 		return err;
 
-	if (req->sess->sent_offer && !req->sess->awaiting_answer
-		&& (!req->body || !mbuf_get_left(req->body)))
-		req->sess->refresh_allowed = true;
-
 	return sip_drequestf(&req->req, req->sess->sip, true, "PRACK",
 			     req->sess->dlg, 0, req->sess->auth, NULL,
 			     prack_resp_handler, prack,
diff --git a/src/sipsess/reply.c b/src/sipsess/reply.c
index efb89089b..3f1d47104 100644
--- a/src/sipsess/reply.c
+++ b/src/sipsess/reply.c
@@ -26,7 +26,6 @@ struct sipsess_reply {
 	const struct sip_msg *msg;
 	struct mbuf *mb;
 	struct sipsess *sess;
-	bool awaiting_answer;
 	bool awaiting_prack;
 	uint16_t scode;
 	uint32_t seq;
@@ -115,11 +114,17 @@ int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
 	struct sipsess_reply *reply = NULL;
 	struct sip_contact contact;
 	int err = ENOMEM;
+	bool sdp = mbuf_get_left(msg->mb) > 0;
 	bool non_invite = !pl_strcmp(&msg->met, "PRACK")
 			  || !pl_strcmp(&msg->met, "UPDATE");
 
 	if (!non_invite) {
-		if (sess->awaiting_prack)
+		if (sess->neg_state == SDP_NEG_NONE && !mbuf_get_left(desc))
+			return EINVAL;
+		else if (sess->neg_state == SDP_NEG_DONE)
+			desc = NULL;
+
+		if (sess->prack_waiting_cnt > 0)
 			return EINVAL;
 
 		reply = mem_zalloc(sizeof(*reply), destructor);
@@ -134,6 +139,9 @@ int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
 		reply->sess = sess;
 	}
 
+	if (non_invite && sess->neg_state != SDP_NEG_REMOTE_OFFER)
+		desc = NULL;
+
 	sip_contact_set(&contact, sess->cuser, &msg->dst, msg->tp);
 	err = sip_treplyf(non_invite ? NULL : &sess->st,
 			  reply ? &reply->mb : NULL, sess->sip,
@@ -160,14 +168,16 @@ int sipsess_reply_2xx(struct sipsess *sess, const struct sip_msg *msg,
 		(void)list_ledata(list_apply(&sess->replyl, false,
 				  cancel_1xx_timers, NULL));
 
+	if (mbuf_get_left(desc)) {
+		if (sdp)
+			sess->neg_state = SDP_NEG_DONE;
+		else if (!non_invite)
+			sess->neg_state = SDP_NEG_LOCAL_OFFER;
+	}
+
 	if (reply) {
 		tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply);
 		tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply);
-
-		if (!mbuf_get_left(msg->mb) && desc) {
-			reply->awaiting_answer = true;
-			sess->awaiting_answer = true;
-		}
 	}
 
  out:
@@ -205,16 +215,32 @@ int sipsess_reply_1xx(struct sipsess *sess, const struct sip_msg *msg,
 				  421, "Extension required",
 				  "Require: 100rel\r\n"
 				  "Content-Length: 0\r\n\r\n");
-		return -1;
+		return EPROTO;
 	}
 	else if (rel100_peer == REL100_REQUIRED && !rel100) {
 		(void)sip_treplyf(&sess->st, NULL, sess->sip, msg, false, 420,
 				  "Bad Extension", "Unsupported: 100rel\r\n"
 				  "Content-Length: 0\r\n\r\n");
-		return -1;
+		return EPROTO;
 	}
 
 	reliably = rel100 && rel100_peer && scode != 100;
+
+	if (reliably && sess->neg_state == SDP_NEG_NONE
+	    && !mbuf_get_left(desc))
+		return EINVAL;
+
+	if (sess->neg_state == SDP_NEG_NONE) {
+		if (reliably && !mbuf_get_left(desc))
+			return EINVAL;
+		else if (!reliably)
+			desc = NULL;
+	}
+	else if (sess->neg_state == SDP_NEG_DONE
+		 || sess->neg_state == SDP_NEG_LOCAL_OFFER) {
+		desc = NULL;
+	}
+
 	if (rel100 != REL100_REQUIRED && reliably) {
 		pl_set_str(&require_header, "Require: 100rel\r\n");
 	}
@@ -264,21 +290,19 @@ int sipsess_reply_1xx(struct sipsess *sess, const struct sip_msg *msg,
 	if (reliably) {
 		tmr_start(&reply->tmr, 64 * SIP_T1, tmr_handler, reply);
 		tmr_start(&reply->tmrg, SIP_T1, retransmit_handler, reply);
-	}
-	else {
-		mem_deref(reply);
-	}
 
-	if (desc) {
-		if (!mbuf_get_left(msg->mb)) {
-			reply->awaiting_answer = true;
-			sess->awaiting_answer = true;
-		}
-		if (reliably) {
-			sess->awaiting_prack = true;
+		if (desc) {
+			++sess->prack_waiting_cnt;
 			reply->awaiting_prack = true;
+			sess->neg_state = mbuf_get_left(msg->mb) ?
+				SDP_NEG_DONE : SDP_NEG_LOCAL_OFFER;
 		}
+	}
+	else {
+		if (desc && sess->neg_state == SDP_NEG_REMOTE_OFFER)
+			sess->neg_state = SDP_NEG_PREVIEW_ANSWER;
 
+		mem_deref(reply);
 	}
 
  out:
@@ -312,7 +336,7 @@ static bool cmp_handler(struct le *le, void *arg)
 
 
 int sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg,
-			bool *awaiting_answer, bool *awaiting_prack)
+			bool *awaiting_prack)
 {
 	struct sipsess_reply *reply;
 
@@ -321,7 +345,6 @@ int sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg,
 	if (!reply)
 		return ENOENT;
 
-	*awaiting_answer = reply->awaiting_answer;
 	*awaiting_prack = reply->awaiting_prack;
 
 	mem_deref(reply);
@@ -330,8 +353,7 @@ int sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg,
 }
 
 
-int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
-		      bool *awaiting_answer)
+int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg)
 {
 	struct sipsess_reply *reply;
 
@@ -340,8 +362,6 @@ int sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
 	if (!reply)
 		return ENOENT;
 
-	*awaiting_answer = reply->awaiting_answer;
-
 	mem_deref(reply);
 
 	return 0;
diff --git a/src/sipsess/sess.c b/src/sipsess/sess.c
index bb62db33c..f394ec92c 100644
--- a/src/sipsess/sess.c
+++ b/src/sipsess/sess.c
@@ -333,7 +333,7 @@ void sipsess_abort(struct sipsess *sess)
  */
 bool sipsess_awaiting_prack(const struct sipsess *sess)
 {
-	return sess ? sess->awaiting_prack : false;
+	return sess ? sess->prack_waiting_cnt > 0 : false;
 }
 
 
@@ -349,8 +349,7 @@ bool sipsess_refresh_allowed(const struct sipsess *sess)
 	if (!sess)
 		return false;
 
-	return ((sess->established || sess->refresh_allowed)
-		&& !sess->terminated && !sess->awaiting_answer);
+	return !sess->terminated && sess->neg_state == SDP_NEG_DONE;
 }
 
 
@@ -366,3 +365,16 @@ bool sipsess_ack_pending(const struct sipsess *sess)
 {
 	return sess && sess->replyl.head ? true : false;
 }
+
+
+/**
+ * Get the SDP negotiation state of a SIP Session
+ *
+ * @param sess  SIP Session
+ *
+ * @return SDP negotiation state
+ */
+enum sdp_neg_state sipsess_sdp_neg_state(const struct sipsess *sess)
+{
+	return sess ? sess->neg_state : SDP_NEG_NONE;
+}
diff --git a/src/sipsess/sipsess.h b/src/sipsess/sipsess.h
index 884d03705..591bf0329 100644
--- a/src/sipsess/sipsess.h
+++ b/src/sipsess/sipsess.h
@@ -36,15 +36,13 @@ struct sipsess {
 	void *arg;
 	uint32_t rel_seq;
 	bool owner;
-	bool sent_offer;
-	bool awaiting_answer;
 	bool modify_pending;
 	bool established;
 	bool peerterm;
 	bool rel100_supported;
-	bool awaiting_prack;
-	bool refresh_allowed;
+	int prack_waiting_cnt;
 	int terminated;
+	enum sdp_neg_state neg_state;
 };
 
 
@@ -95,10 +93,9 @@ int  sipsess_reply_1xx(struct sipsess *sess, const struct sip_msg *msg,
 		       uint16_t scode, const char *reason,
 		       enum rel100_mode rel100, struct mbuf *desc,
 		       const char *fmt, va_list *ap);
-int  sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg,
-		       bool *awaiting_answer);
+int  sipsess_reply_ack(struct sipsess *sess, const struct sip_msg *msg);
 int  sipsess_reply_prack(struct sipsess *sess, const struct sip_msg *msg,
-			 bool *awaiting_answer, bool *awaiting_prack);
+			 bool *awaiting_prack);
 int  sipsess_reinvite(struct sipsess *sess, bool reset_ls);
 int  sipsess_update(struct sipsess *sess);
 int  sipsess_bye(struct sipsess *sess, bool reset_ls);
diff --git a/src/sipsess/update.c b/src/sipsess/update.c
index fbbf25eea..1e982f858 100644
--- a/src/sipsess/update.c
+++ b/src/sipsess/update.c
@@ -46,15 +46,17 @@ static void update_resp_handler(int err, const struct sip_msg *msg, void *arg)
 	else if (msg->scode < 300) {
 		(void)sip_dialog_update(req->sess->dlg, msg);
 
-		if (req->sess->sent_offer) {
+		if (req->sess->neg_state == SDP_NEG_LOCAL_OFFER) {
 			(void)req->sess->answerh(msg, req->sess->arg);
-			req->sess->awaiting_answer = false;
+			req->sess->neg_state = SDP_NEG_DONE;
 		}
 	}
 	else {
 		if (req->sess->terminated)
 			goto out;
 
+		req->sess->neg_state = SDP_NEG_DONE;
+
 		switch (msg->scode) {
 
 		case 401:
@@ -120,10 +122,12 @@ static int send_handler(enum sip_transp tp, struct sa *src,
 
 static int update_request(struct sipsess_request *req)
 {
+	int err;
+
 	if (!req || req->tmr.th)
 		return -1;
 
-	return sip_drequestf(&req->req, req->sess->sip, true, "UPDATE",
+	err = sip_drequestf(&req->req, req->sess->sip, true, "UPDATE",
 			    req->sess->dlg, 0, req->sess->auth, send_handler,
 			    update_resp_handler, req,
 			    "%s%s%s"
@@ -136,6 +140,11 @@ static int update_request(struct sipsess_request *req)
 			    req->body ? mbuf_get_left(req->body) :(size_t)0,
 			    req->body ? mbuf_buf(req->body) : NULL,
 			    req->body ? mbuf_get_left(req->body):(size_t)0);
+
+	if (!err && req->sess->desc)
+		req->sess->neg_state = SDP_NEG_LOCAL_OFFER;
+
+	return err;
 }
 
 
@@ -165,8 +174,6 @@ int sipsess_update(struct sipsess *sess)
 		return err;
 	}
 
-	sess->sent_offer = sess->desc ? true : false;
-	sess->awaiting_answer = sess->sent_offer;
 	sess->modify_pending = false;
 
 	return err;
diff --git a/test/sipsess.c b/test/sipsess.c
index c0051fb0b..fdd2dd068 100644
--- a/test/sipsess.c
+++ b/test/sipsess.c
@@ -16,7 +16,7 @@
 typedef void (prack_func)(void *arg);
 
 
-enum sdp_neg_state {
+enum neg_state {
 	INITIAL = 0,
 	OFFER_RECEIVED,
 	ANSWER_RECEIVED,
@@ -56,7 +56,7 @@ struct test {
 	bool offer_b;
 	enum rel100_mode rel100_a;
 	enum rel100_mode rel100_b;
-	enum sdp_neg_state sdp_state;
+	enum neg_state sdp_state;
 	enum rel100_state rel100_state_a;
 	enum rel100_state rel100_state_b;
 	enum connect_action conn_action;
@@ -454,7 +454,7 @@ static void conn_handler(const struct sip_msg *msg, void *arg)
 	else if (test->conn_action & CONN_ANSWER) {
 		err = sipsess_accept(&test->b, test->sock, msg, 200, "OK",
 				test->rel100_b, "b", "application/sdp",
-				NULL, NULL, NULL, false, offer_handler_b,
+				desc, NULL, NULL, false, offer_handler_b,
 				answer_handler_b, estab_handler_b, NULL, NULL,
 				close_handler, test, hdrs);
 		if (err != test->answ_ret_code) {
@@ -556,7 +556,7 @@ int test_sipsess(void)
 	err = sipsess_connect(&test.a, test.sock, to_uri, NULL,
 			      "sip:a@127.0.0.1", "a", NULL, 0,
 			      "application/sdp", NULL, NULL, false,
-			      callid, desc_handler,
+			      callid, desc_handler_a,
 			      offer_handler_a, answer_handler_a, NULL,
 			      estab_handler_a, NULL, NULL,
 			      close_handler, &test, NULL);
@@ -578,7 +578,9 @@ int test_sipsess(void)
 	ASSERT_TRUE(test.estab_b);
 	ASSERT_TRUE(test.desc);
 	ASSERT_TRUE(test.answr_a);
-	ASSERT_TRUE(!test.offer_b);
+	ASSERT_TRUE(test.offer_b);
+	ASSERT_TRUE(!test.offer_a);
+	ASSERT_TRUE(!test.answr_b);
 
  out:
 	test.a = mem_deref(test.a);
@@ -715,7 +717,7 @@ int test_sipsess_blind_transfer(void)
 	err = sipsess_connect(&test.a, test.sock, to_uri, NULL,
 			      "sip:a@127.0.0.1", "a", NULL, 0,
 			      "application/sdp", NULL, NULL, false,
-			      callid, desc_handler,
+			      callid, desc_handler_a,
 			      offer_handler_a, answer_handler_a, NULL,
 			      estab_handler_a, NULL, NULL,
 			      close_handler, &test, NULL);
@@ -738,7 +740,9 @@ int test_sipsess_blind_transfer(void)
 	ASSERT_TRUE(test.estab_b);
 	ASSERT_TRUE(test.desc);
 	ASSERT_TRUE(test.answr_a);
-	ASSERT_TRUE(!test.offer_b);
+	ASSERT_TRUE(test.offer_b);
+	ASSERT_TRUE(!test.offer_a);
+	ASSERT_TRUE(!test.answr_b);
 
  out:
 	test.a = mem_deref(test.a);
@@ -1054,7 +1058,7 @@ int test_sipsess_100rel_420(void)
 
 	err = re_main_timeout(200);
 	TEST_ERR(err);
-	ASSERT_TRUE(test.err == EINVAL);
+	ASSERT_TRUE(test.err == EPROTO);
 
 	/* okay here -- verify */
 	ASSERT_TRUE(!test.b);
@@ -1127,7 +1131,7 @@ int test_sipsess_100rel_421(void)
 
 	err = re_main_timeout(200);
 	TEST_ERR(err);
-	ASSERT_TRUE(test.err == EINVAL);
+	ASSERT_TRUE(test.err == EPROTO);
 
 	/* okay here -- verify */
 	ASSERT_TRUE(!test.b);