Skip to content

lib.rs claims RFC 6026 but transaction layer skips Accepted/Timer L/Timer M (§4, §7.1, §7.2, §8.5) #127

@andrico21

Description

@andrico21

Hey, I'm using rsipstack 0.5.12 for a SIP↔Telegram voice bridge and ran into something while auditing the transaction layer. Wanted to surface it before I assume the worst.

src/lib.rs:211 says:

//! * **RFC 6026** - Correct Transaction Handling for 2xx Responses to INVITE

But reading the transaction code, I don't see RFC 6026 actually implemented — and the gap looks like several explicit MUSTs in the spec. Quick rundown:

1. No Accepted state

RFC 6026 §4 (Summary): "It adds a state to the INVITE server state machine to absorb retransmissions of the INVITE after a 2xx response has been sent." §7.1 introduces this state by name.

src/transaction/mod.rs:84-92:

pub enum TransactionState {
    Nothing, Calling, Trying, Proceeding,
    Completed, Confirmed, Terminated,
}

2. Server INVITE 2xx goes to Completed, not Accepted

RFC 6026 §7.1: "If the SIP element's TU issues a 2xx response for this transaction while the state machine is in the 'Proceeding' state, the state machine MUST transition to the 'Accepted' state and set Timer L to 64*T1…"

§8.5 restates this as the replacement for RFC 3261 §17.2.1 paragraph 4: "…The server transaction MUST then transition to the 'Accepted' state."

src/transaction/transaction.rs:348-357:

let new_state = match response.status_code.kind() {
    StatusCodeKind::Provisional => match response.status_code {
        StatusCode::Trying => TransactionState::Trying,
        _ => TransactionState::Proceeding,
    },
    _ => match self.transaction_type {
        TransactionType::ServerInvite => TransactionState::Completed,
        _ => TransactionState::Terminated,
    },
};

2xx and non-2xx final responses both end up in Completed.

3. Server transaction retransmits 2xx via Timer G

RFC 6026 §7.1: "The server transaction MUST NOT generate 2xx retransmissions on its own." §8.5 reinforces: "It is not retransmitted by the server transaction; retransmissions of 2xx responses are handled by the TU."

src/transaction/transaction.rs:744-770 — when Timer G fires in Completed state, it resends last_response. For a 2xx, this is exactly what §7.1 forbids:

TransactionState::Completed => {
    if let TransactionTimer::TimerG(key, duration) = timer {
        if let Some(last_response) = &self.last_response {
            if let Some(connection) = &self.connection {
                // ...inspector pass-through elided...
                connection.send(last_response, ...).await?;
            }
        }
        // restart Timer G with an upper limit
        let duration = (duration * 2).min(self.endpoint_inner.option.t1x64);
        // ...
    }
}

4. No Timer L; effective ACK window is ~T4 (≈5s), not 64*T1 (≈32s)

RFC 6026 §7.1: "set Timer L to 64*T1, where T1 is the round-trip time estimate" and "Timer L reflects the amount of time the server transaction could receive 2xx responses for retransmission from the TU while it is waiting to receive an ACK."

src/transaction/mod.rs:249-257 — no Timer L variant:

pub enum TransactionTimer {
    TimerA(...), TimerB(...), TimerC(...), TimerD(...),
    TimerK(...), TimerG(...), TimerCleanup(...),
}

src/transaction/transaction.rs:837-877 (entry to Completed for ServerInvite) starts Timer G, Timer K (= T4, default 5s), and Timer D (= 64*T1, default 32s); lines 771-780 transition to Terminated on either Timer K or Timer D. Timer K fires first, so the effective wait for an ACK after sending a 2xx is ~5s rather than RFC 6026's 32s.

5. Client INVITE 2xx skips Accepted + Timer M

RFC 6026 §7.2: "If a 2xx response is received while the client INVITE state machine is in the 'Calling' or 'Proceeding' states, it MUST transition to the 'Accepted' state, pass the 2xx response to the TU, and set Timer M to 64*T1." §8.4 restates this as the replacement for RFC 3261 §17.1.1.2.

src/transaction/transaction.rs:660-666:

_ => {
    if self.transaction_type == TransactionType::ClientInvite {
        TransactionState::Completed
    } else {
        TransactionState::Terminated
    }
}

ClientInvite goes to Completed on any non-provisional response, including 2xx. No Timer M anywhere in the codebase.

6. No tests reference RFC 6026

grep -rn '6026\|Accepted state\|TimerL\|TimerM' src/transaction/tests/ → no hits across mod.rs, test_client.rs, test_endpoint.rs, test_provisional_responses.rs, test_server.rs, test_transaction_states.rs.


Question: is RFC 6026 intended to be supported, with the current state machine being a deliberate design choice (e.g. relying on waiting_ack for 2xx-ACK routing instead of the spec-prescribed flow)? Or is the lib.rs claim aspirational?

For my project (UAS only, direct PBX → bridge over UDP, no proxy chain, no forking, no outbound INVITE), the current behavior is functionally OK — waiting_ack resolves the ACKs and Timer G handles 2xx retransmits within the ~5s Timer K window. So I'm not blocked. I just want my own audit trail to agree with reality.

If you'd like a PR adding Accepted / TimerL / TimerM and a strict-RFC-6026 SIPp scenario, happy to scope it. Otherwise it would help to soften the lib.rs wording.

Thanks for rsipstack — clean dependency to work with otherwise.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions