MCPcopy Index your code
hub / github.com/Televiska/rsip

github.com/Televiska/rsip @0.4.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release 0.4.0 ↗ · + Follow
792 symbols 1,225 edges 216 files 16 documented · 2%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Rsip

License: MIT Build status Crates.io Version Minimum rustc version

A common general purpose library for SIP. It can parse and generate all SIP structures.

Like HTTP, this crate is a general purpose library for common types found when working with the SIP protocol. You’ll find the SipMessage and its Request and Response variant types for working as either a client or a server as well as all of their components, like Method, Version, a very flexible Uri, StatusCode etc.

Rsip is capable of parsing messages from bytes, &str or String using nom parser and can also generate SIP messages using helpful structs.

You will notably not find an implementation of sending requests or spinning up a SIP server in this crate. SIP servers, by nature of SIP protocol, are very complex usually and will sit at different crates/libs. Rsip is intended to be the de-facto SIP base library for Rust. It was built to be used inside viska initially but then was split to a different crate.

It was inspired by libsip but has taken a bit different path regarding parsing, flexibility & safety.

For locating SIP servers (RFC3263) take a look on rsip-dns library.

Features

  • This thing is fast, uses nom for basic message parsing and headers are parsed only when needed, on-demand. Intentions are to make it even faster by providing non-owning variants (&str and &[u8])
  • Strong (new)types everywhere. Even if underlying type is String, everything is a NewType for better type safety.
  • Provides typed headers on demand, like From, To, Contact, Via etc The reasoning behind on demand strongly typed headers is 2 fold:
  • perfromance & memory reasons: headers are parsed only when needed
  • it enables you to still have a working Rust SIP parser in case a typed header has a bug, the peer has a bug or there is an edge/new case never seen before.
  • While performance is always a goal, user friendliness and usability is the main goal. A lot of helpful functions and convertions to make things easy :)
  • Very simple code structure make it super easy to extend and add new typed headers As long as you can do nom stuff, it's straightforward. The goal is to add many typed headers of latest RFCs like PASSporT, SHAKEN, push notifications etc
  • Provides some extra services like Digest auth generator/validator etc Intention is to add many helper services.

Architecture

Each type in rsip has a tokenizer attached. This is not enforced by the type system yet, however very soon this will be the case. In brief, for every rsip type we have: * Tokenizing: in the lowest level we have the Tokenizer which is capable of tokenizing the input. All common tokenizers accept abstract input, either &str or &[u8] so it can be reused when the input is plain bytes, or when the input has already been parsed and it's a String/&str, like the headers. * Parsing: once the input has been tokenized, then there are TryFrom impls from the relevant type tokenizer to the actual type. This is the parsing step where tokens (in the form of &str or &[u8]) are transformed to integers, strings and rsip types. * each rsip type implements the Display trait and hence has a representation.

Examples

For instance, generating the Register request found in section 2.1 of RFC3665

REGISTER sips:ss2.biloxi.example.com SIP/2.0
Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
Max-Forwards: 70
From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
To: Bob <sips:bob@biloxi.example.com>
Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
CSeq: 1 REGISTER
Contact: <sips:bob@client.biloxi.example.com>
Content-Length: 0

can be done like that:

fn generate_register_request() -> rsip::SipMessage {
    let mut headers: rsip::Headers = Default::default();

    let base_uri = rsip::Uri {
        scheme: Some(rsip::Scheme::Sips),
        auth: Some(("bob", Option::<String>::None).into()),
        host_with_port: rsip::Domain::from("biloxi.example.com").into(),
        ..Default::default()
    };

    headers.push(
        rsip::typed::Via {
            version: rsip::Version::V2,
            transport: rsip::Transport::Tls,
            uri: rsip::Uri {
                host_with_port: (rsip::Domain::from("client.biloxi.example.com"), 5060).into(),
                ..Default::default()
            },
            params: vec![rsip::Param::Branch(rsip::param::Branch::new(
                "z9hG4bKnashds7",
            ))],
        }
        .into(),
    );
    headers.push(rsip::headers::MaxForwards::default().into());
    headers.push(
        rsip::typed::From {
            display_name: Some("Bob".into()),
            uri: base_uri.clone(),
            params: vec![rsip::Param::Tag(rsip::param::Tag::new("a73kszlfl"))],
        }
        .into(),
    );
    headers.push(
        rsip::typed::To {
            display_name: Some("Bob".into()),
            uri: base_uri.clone(),
            params: Default::default(),
        }
        .into(),
    );
    headers.push(rsip::headers::CallId::default().into());
    headers.push(
        rsip::typed::CSeq {
            seq: 1,
            method: rsip::Method::Register,
        }
        .into(),
    );
    headers.push(
        rsip::typed::Contact {
            display_name: None,
            uri: base_uri,
            params: Default::default(),
        }
        .into(),
    );
    headers.push(rsip::headers::ContentLength::default().into());

    rsip::Request {
        method: rsip::Method::Register,
        uri: rsip::Uri {
            scheme: Some(rsip::Scheme::Sips),
            host_with_port: rsip::Domain::from("ss2.biloxi.example.com").into(),
            ..Default::default()
        },
        version: rsip::Version::V2,
        headers: headers,
        body: Default::default(),
    }
    .into()
}

And the response similarly can be generated:

pub fn create_unauthorized_from(request: rsip::Request) -> Result<rsip::SipMessage, crate::Error> {
    //imports helpful header traits
    use rsip::prelude::*;

    let mut headers: rsip::Headers = Default::default();
    headers.push(request.via_header()?.clone().into());
    headers.push(request.from_header()?.clone().into());
    let mut to = request.to_header()?.typed()?;
    to.with_tag("1410948204".into());
    headers.push(to.into());
    headers.push(request.call_id_header()?.clone().into());
    headers.push(request.cseq_header()?.clone().into());
    headers.push(rsip::Header::ContentLength(Default::default()));
    headers.push(rsip::Header::Server(Default::default()));

    headers.push(
        rsip::typed::WwwAuthenticate {
            realm: "atlanta.example.com".into(),
            nonce: "ea9c8e88df84f1cec4341ae6cbe5a359".into(),
            algorithm: Some(rsip::headers::auth::Algorithm::Md5),
            qop: Some(rsip::headers::auth::Qop::Auth),
            stale: Some("FALSE".into()),
            opaque: Some("".into()),
            ..Default::default()
        }
        .into(),
    );

    Ok(rsip::Response {
        status_code: 401.into(),
        headers,
        version: rsip::Version::V2,
        body: Default::default()
    }
    .into())
}

which generates the following:

SIP/2.0 401 Unauthorized
Via: SIP/2.0/TLS client.biloxi.example.com:5061;branch=z9hG4bKnashds7
 ;received=192.0.2.201
From: Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl
To: Bob <sips:bob@biloxi.example.com>;tag=1410948204
Call-ID: 1j9FpLxk3uxtm8tn@biloxi.example.com
CSeq: 1 REGISTER
WWW-Authenticate: Digest realm="atlanta.example.com", qop="auth",
 nonce="ea9c8e88df84f1cec4341ae6cbe5a359",
 opaque="", stale=FALSE, algorithm=MD5
Content-Length: 0

To Do

  • improve errors
  • write more tests, especially around edge cases
  • Make tokenizer an associated generic type on each type defined in this lib
  • implement more common traits like Hash etc

Extension points exported contracts — how you extend this code

Tokenize (Interface)
(no doc) [13 implementers]
src/headers/typed/mod.rs
HasHeaders (Interface)
Simple trait to signify that the underlying type has headers so that the [HeadersExt](crate::message::HeadersExt) trait [4 …
src/message/mod.rs
AbstractInput (Interface)
need to include &str or &[u8] in definition [2 implementers]
src/lib.rs
UntypedHeader (Interface)
(no doc)
src/headers/untyped/mod.rs
HeadersExt (Interface)
Helpful trait to access most common headers from a [Request](crate::Request), [Response](crate::Response) and [SipMessag [4 …
src/message/headers_ext.rs
AbstractInputItem (Interface)
(no doc) [2 implementers]
src/lib.rs
ToTypedHeader (Interface)
(no doc)
src/headers/untyped/mod.rs
TypedHeader (Interface)
(no doc)
src/headers/typed/mod.rs

Core symbols most depended-on inside this repo

into
called by 190
src/error/tokenizer_error.rs
typed
called by 36
src/headers/untyped/mod.rs
into_iter
called by 25
src/headers/mod.rs
push
called by 23
src/headers/mod.rs
try_into
called by 15
src/headers/header.rs
iter
called by 14
src/headers/mod.rs
find_param
called by 12
src/headers/typed/authorization.rs
try_into
called by 10
src/common/uri/host_with_port/mod.rs

Shape

Method 360
Function 284
Class 122
Enum 18
Interface 8

Languages

Rust100%

Modules by API surface

tests/common/uri/mod.rs25 symbols
src/message/headers_ext.rs24 symbols
src/common/transport.rs21 symbols
rsip-derives/src/lib.rs15 symbols
tests/message/request.rs14 symbols
src/message/sip_message.rs14 symbols
src/message/request.rs14 symbols
src/message/response.rs13 symbols
src/common/uri/mod.rs13 symbols
tests/message/response.rs11 symbols
tests/message/message.rs11 symbols
src/headers/mod.rs11 symbols

For agents

$ claude mcp add rsip \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact