diff --git a/src/transaction/endpoint.rs b/src/transaction/endpoint.rs index 7e0440a8..2eaa8f11 100644 --- a/src/transaction/endpoint.rs +++ b/src/transaction/endpoint.rs @@ -49,6 +49,13 @@ pub struct EndpointOption { /// (RFC 7989) needs no switch: dialogs participate only when the /// application supplies a UUID or the peer sends a Session-ID header. pub history_info_enabled: bool, + /// Whether a client INVITE transaction ACKs a 2xx final itself (default `true`). + /// With `false` a 2xx moves the transaction straight to + /// Terminated: no automatic ACK, no stored ACK. + /// RFC 3261 section 17.1.1.2: the 2xx ACK is the TU's, end-to-end; + /// proxies (section 16) must not ACK a 2xx and must see retransmitted 2xx. + /// `true` keeps the UA behavior the dialog layer relies on. + pub auto_ack_2xx: bool, } impl Default for EndpointOption { @@ -61,6 +68,7 @@ impl Default for EndpointOption { callid_suffix: None, callid_format: CallIdFormat::default(), history_info_enabled: false, + auto_ack_2xx: true, } } } diff --git a/src/transaction/transaction.rs b/src/transaction/transaction.rs index 82fcd89f..0abc7607 100644 --- a/src/transaction/transaction.rs +++ b/src/transaction/transaction.rs @@ -746,7 +746,16 @@ impl Transaction { } _ => { if self.transaction_type == TransactionType::ClientInvite { - TransactionState::Completed + if resp.status_code.kind() == StatusCodeKind::Successful + && !self.endpoint_inner.option.auto_ack_2xx + { + // auto_ack_2xx = false (proxy mode): a 2xx terminates + // the client INVITE transaction at once; the ACK is + // the TU's job (RFC 3261 section 17.1.1.2). + TransactionState::Terminated + } else { + TransactionState::Completed + } } else { TransactionState::Terminated } @@ -1105,8 +1114,17 @@ impl Transaction { ) && self.last_ack.is_none() { if let Some(ref resp) = self.last_response { - if let Ok(ack) = self.endpoint_inner.make_ack(&self.original, resp) { - self.last_ack.replace(ack); + // auto_ack_2xx = false (proxy mode): store no ACK + // for a 2xx, so retransmitted 2xx fall through + // to the TU instead of being absorbed or re-ACKed + // here (RFC 3261 section 17.1.1.2). + let auto_ack = self.endpoint_inner.option.auto_ack_2xx + || resp.status_code.kind() != StatusCodeKind::Successful; + if auto_ack { + if let Ok(ack) = self.endpoint_inner.make_ack(&self.original, resp) + { + self.last_ack.replace(ack); + } } } }