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

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:

```rust fn generateregisterrequest() -> 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:

```rust pub fn createunauthorizedfrom(request: rsip::Request) -> Result { //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