ftth_rsip/
lib.rs

1//! A general purpose library of common SIP types
2//!
3//! Like [http](https://docs.rs/http/) crate, this crate is a general purpose
4//! library for common types found when working
5//! with the SIP protocol. You'll find [SipMessage](SipMessage)
6//! and its [Request](Request)
7//! / [Response](Response)
8//! variant types for working as either a client or a server as well
9//! as all of their components, like [Method](Method),
10//! a very flexible [Uri](Uri),
11//! [Version](Version),
12//! [StatusCode](StatusCode) etc.
13//!
14//! Rsip is capable of parsing messages from `bytes`, `&str` or `String` using
15//! [nom](https://github.com/Geal/nom) parser and can also generate SIP messages
16//! using helpful struct builders.
17//!
18//! You will notably not find an implementation of sending requests or spinning up
19//! a SIP server in this crate. SIP servers, by nature of SIP protocol, are very
20//! complex usually and will sit at different crates/libs. Rsip is intended to be
21//! the de-facto SIP base library for Rust. It was built to be used inside
22//! [viska](https://github.com/vasilakisfil/viska) initially but then was split
23//! to a different crate. It was inspired by [libsip](https://github.com/ByteHeathen/libsip)
24//! but has taken a bit different path regarding parsing, flexibility & safety.
25//!
26//! For locating SIP servers ([RFC3263](https://datatracker.ietf.org/doc/html/rfc3263)) take a
27//! look on [rsip-dns](https://github.com/vasilakisfil/rsip-dns) library.
28//!
29//! ## SIP: It's all about headers
30//! In case you haven't worked with SIP before let me tell you one thing: headers in SIP play
31//! a crucial role, basically most of your logic resides around the headers (and there are plenty
32//! of them :) ). `Status` and `Method` of course have their role, but there decisions are more
33//! straightforward on what you need to do.
34//!
35//! Rsip takes a unique role regarding headers mixing together safety and flexibility: by default
36//! rsip will parse all headers and convert them to distinct
37//! [untyped header](headers::untyped) types,
38//! as part of the big fat [Header enum](Header).
39//! Each untyped header is basically a NewType around String.
40//! This means that, apart from non-UTF8 compliant headers, any header is supported even if your
41//! client has a bug or rsip itself has a bug somewhere in a typed header.
42//! Also, any parsing on complex headers takes place only when needed, on demand.
43//!
44//! [Untyped headers](headers::untyped) have their use, but in practice when you want to
45//! interact with a header, you need to convert it to its [typed form](headers::typed).
46//! Not all headers have a typed form, for instance, some
47//! headers are just opaque strings (like [CallID](headers::CallId)), or some headers just need helpful methods
48//! instead of a whole new typed struct (like
49//! [Expires](headers::Expires), which provides the [seconds](headers::Expires::seconds) method).
50//!
51//! For instance, creating a new `Call-ID` header can easily be done like (examples taken from
52//! [RFC3665](https://datatracker.ietf.org/doc/html/rfc3665)):
53//!
54//! ```
55//! use ftth_rsip::headers::UntypedHeader;
56//! ftth_rsip::headers::CallId::new("1j9FpLxk3uxtm8tn@biloxi.example.com");
57//! ```
58//!
59//! Similarly, generating a `From` header:
60//! ```
61//! use ftth_rsip::headers::{From, UntypedHeader};
62//! let from = ftth_rsip::headers::From::new("Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl");
63//! ```
64//!
65//! If you want to automatically include all defined traits in Rsip, you can include the prelude:
66//! ```
67//! use ftth_rsip::prelude::*;
68//! let from = ftth_rsip::headers::From::new("Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl");
69//! ```
70//!
71//! While for `Call-Id`, it's fine to pass in an str (which probably will be generated & saved
72//! somewhere else in your application), for many headers working with plain strings doesn't make
73//! much sense, like in the `From` header. As mentioned, the main reason untyped headers exist is
74//! performance (parsing takes place only when needed) and flexibility (you can throw in there
75//! whatever you like ^_^).
76//!
77//! In order to generate a From header, you will probably need to use its [typed](typed::From) version:
78//! ```
79//! use ftth_rsip::headers::typed::TypedHeader;
80//!
81//! let typed_from: ftth_rsip::typed::From = ftth_rsip::typed::From {
82//!     display_name: Some("Bob".into()),
83//!     uri: ftth_rsip::Uri {
84//!         scheme: Some(ftth_rsip::Scheme::Sips),
85//!         auth: Some(ftth_rsip::Auth {
86//!             user: "Bob".into(),
87//!             password: None,
88//!         }),
89//!         host_with_port: ftth_rsip::Domain::from("biloxi.example.com").into(),
90//!         ..Default::default()
91//!     },
92//!     params: vec![ftth_rsip::Param::Tag(ftth_rsip::param::Tag::new("a73kszlfl"))],
93//! };
94//! ```
95//!
96//! ## Uri: a flexible uri struct
97//!
98//! As you can see in the typed `From` example, a very crucial part is the [Uri](Uri). The uri we want to generate is
99//! `sips:bob@biloxi.example.com` as in the untyped header above. Hence:
100//! * we specify hat the [scheme](Scheme) of the Uri is `Scheme::Sips`
101//! * we specify the auth part of the Uri, but only the user part which is `Bob`. We do that
102//! using the helpful [Auth](Auth) struct,
103//! * we specify the host part of the Uri, which is defined in the Uri as
104//! [HostWithPort](HostWithPort) struct.
105//! In our case, it is a simple domain without a port specified.
106//! For that we can use the [Domain](Domain) struct that has an automatic convertion to [HostWithPort](HostWithPort)
107//! struct. In general you will find a lot of helpful `(Try)From/(Try)Into` convertions to remove
108//! boilerplate. According to SIP spec, the URI can hold params (like `method`) and headers,
109//! although in practice none really puts those to a SIP URI. That's why we have defaults in there.
110//!
111//! It is important to note that in order to set the `tag` param, we use the [Tag](param::Tag) struct, found
112//! inside the [param](param) module. In the same module you will find various other params that can be
113//! used in headers like [From](headers::From), [To](headers::To), [Contact](headers::Contact),
114//! [Via](headers::Via) etc.
115//!
116//! In general, there are tons of helpful `(Try)From/(Try)Into` convertions to remove boilerplate.
117//! So when you have a typed header, it's easy to take its untyped form:
118//!
119//! ```
120//! # let typed_from: ftth_rsip::typed::From = ftth_rsip::typed::From {
121//! #     display_name: Some("Bob".into()),
122//! #     uri: ftth_rsip::Uri {
123//! #         scheme: Some(ftth_rsip::Scheme::Sips),
124//! #         auth: Some(ftth_rsip::Auth {
125//! #             user: "Bob".into(),
126//! #             password: None,
127//! #         }),
128//! #         host_with_port: ftth_rsip::Domain::from("biloxi.example.com").into(),
129//! #         ..Default::default()
130//! #     },
131//! #     params: vec![ftth_rsip::Param::Tag(ftth_rsip::param::Tag::new("a73kszlfl"))],
132//! # };
133//! let untyped_from: ftth_rsip::headers::From = typed_from.into();
134//! ```
135//!
136//! ## Header: the matching enum
137//! [Header](Header) is the enum that holds all variants of the headers. It is there so it's easy to do
138//! matches to find relevant headers.
139//!
140//! Continuing from the previous section, if you want to get the typed or untyped headers as part of
141//! the `Header` enum you can do the following:
142//! ```
143//! # let typed_from: ftth_rsip::typed::From = ftth_rsip::typed::From {
144//! #     display_name: Some("Bob".into()),
145//! #     uri: ftth_rsip::Uri {
146//! #         scheme: Some(ftth_rsip::Scheme::Sips),
147//! #         auth: Some(ftth_rsip::Auth {
148//! #             user: "Bob".into(),
149//! #             password: None,
150//! #         }),
151//! #         host_with_port: ftth_rsip::Domain::from("biloxi.example.com").into(),
152//! #         ..Default::default()
153//! #     },
154//! #     params: vec![ftth_rsip::Param::Tag(ftth_rsip::param::Tag::new("a73kszlfl"))],
155//! # };
156//! let from: ftth_rsip::Header = typed_from.into();
157//! //or
158//! # use ftth_rsip::prelude::*;
159//! # let untyped_from = ftth_rsip::headers::From::new("Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl");
160//! let from: ftth_rsip::Header = untyped_from.into();
161//! ```
162//!
163//! In case a header is not defined in Rsip, parsing will store it in `Header` enum in the `Other`
164//! variant. For instance, constructing the `X-Fs-Sending-Message` header (related to SMS in SIP),
165//! you can do:
166//! ```
167//! let x_fs_sending_message = ftth_rsip::Header::Other("X-FS-Sending-Message".into(), "f9c4adc8-9c2a-47d5-a7f1-63d20784685e".into());
168//! ```
169//! ## Headers: a vec of headers
170//! [Headers](Headers) is a newtype around `Vec<Header>`, but it's there to give you better safety along
171//! with some helpful methods. In SIP, many headers are allowed to appear more than once time, so
172//! if we want to provide something else, maybe we should go with something similar to what the
173//! http crate does (a multimap HashMap to take advantage of the characteristics of HTTP headers).
174//! But this shouldn't worry you as the external API will remain the same even if the internal
175//! implementation is moved from a `Vec<Header>` to a multimap HashMap.
176//!
177//! In order to push some headers in the [Headers](Headers) you can simple do:
178//! ```
179//! # use ftth_rsip::prelude::*;
180//! # let from = ftth_rsip::headers::From::new("Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl");
181//! # let to = ftth_rsip::headers::To::new("Bob <sips:bob@biloxi.example.com>;tag=a73kszlfl");
182//! let mut request_headers: ftth_rsip::Headers = Default::default();
183//! request_headers.push(from.into());
184//! request_headers.push(to.into());
185//! //.
186//! //.
187//! //.
188//! ```
189//!
190//! ## The SIP Messages: Request & Response
191//! [Request](Request) & [Response](Response) are the main structs that you will use just before you finish creating
192//! a SIP message. Creating those are very intuitive as you will only need to "fill-in" their
193//! fields. Creating a SIP request while having already a `Headers` vec:
194//! ```
195//! # let request_headers = Default::default();
196//! let request = ftth_rsip::Request {
197//!     method: ftth_rsip::Method::Register,
198//!     uri: ftth_rsip::Uri {
199//!         scheme: Some(ftth_rsip::Scheme::Sips),
200//!         host_with_port: ftth_rsip::Domain::from("ss2.biloxi.example.com").into(),
201//!         ..Default::default()
202//!     },
203//!     headers: request_headers,
204//!     version: ftth_rsip::Version::V2,
205//!     body: vec![],
206//! };
207//! ```
208//!
209//! Now you have an [ftth_rsip::Request](Request), but sometimes it is helpful to work with the
210//! [ftth_rsip::SipMessage](SipMessage) enum, especially when you are sitting on the receiver's side.
211//! In order to convert a [Request](Request) (or a [Response](Response)) to a
212//! [SipMessage](SipMessage) it's simple:
213//!
214//! ```
215//! # let request_headers = Default::default();
216//! # let request = ftth_rsip::Request {
217//! #     method: ftth_rsip::Method::Register,
218//! #     uri: ftth_rsip::Uri {
219//! #         scheme: Some(ftth_rsip::Scheme::Sips),
220//! #         host_with_port: ftth_rsip::Domain::from("ss2.biloxi.example.com").into(),
221//! #         ..Default::default()
222//! #     },
223//! #     headers: request_headers,
224//! #     version: ftth_rsip::Version::V2,
225//! #     body: vec![],
226//! # };
227//! let sip_message: ftth_rsip::SipMessage = request.into();
228//! //similar
229//! # let request_headers = Default::default();
230//! # let request = ftth_rsip::Request {
231//! #     method: ftth_rsip::Method::Register,
232//! #     uri: ftth_rsip::Uri {
233//! #         scheme: Some(ftth_rsip::Scheme::Sips),
234//! #         host_with_port: ftth_rsip::Domain::from("ss2.biloxi.example.com").into(),
235//! #         ..Default::default()
236//! #     },
237//! #     headers: request_headers,
238//! #     version: ftth_rsip::Version::V2,
239//! #     body: vec![],
240//! # };
241//! let sip_message = ftth_rsip::SipMessage::Request(request);
242//! ```
243//!
244//! Similarly, creating a response when having already defined the headers in a `response_headers`
245//! variable:
246//!
247//! ```
248//! # let response_headers = Default::default();
249//! let response = ftth_rsip::Response {
250//!     status_code: 401.into(),
251//!     headers: response_headers,
252//!     version: ftth_rsip::Version::V2,
253//!     body: vec![],
254//! };
255//!
256//! let sip_message: ftth_rsip::SipMessage = response.into();
257//! ```
258//!
259//!
260
261pub mod common;
262mod error;
263
264pub mod headers;
265pub mod message;
266pub mod services;
267
268pub use error::{Error, TokenizerError};
269
270pub use headers::{Header, Headers};
271pub use message::{Request, Response, SipMessage};
272
273pub use crate::common::uri::*;
274pub use crate::common::*;
275
276pub use crate::message::header_macros::*;
277
278pub mod typed {
279    pub use crate::headers::typed::*;
280}
281
282pub mod prelude {
283    pub use crate::{
284        headers::{typed::TypedHeader, ToTypedHeader, UntypedHeader},
285        message::{HasHeaders, HeadersExt},
286    };
287}
288
289pub(crate) type NomError<'a> = nom::Err<nom::error::VerboseError<&'a [u8]>>;
290pub(crate) type NomStrError<'a> = nom::Err<nom::error::VerboseError<&'a str>>;
291pub(crate) type GenericNomError<'a, T> = nom::Err<nom::error::VerboseError<T>>;
292pub(crate) type IResult<'a, T> = Result<(&'a [u8], T), nom::Err<TokenizerError>>;
293//pub(crate) type SResult<'a, T> = Result<(&'a str, T), nom::Err<TokenizerError>>;
294pub(crate) type GResult<I, T> = Result<(I, T), nom::Err<TokenizerError>>;
295
296//need to include &str or &[u8] in definition
297pub trait AbstractInput<'a, I>:
298    nom::InputTakeAtPosition<Item = I>
299    + nom::InputTake
300    + Clone
301    + Copy
302    + nom::FindSubstring<&'a str>
303    + nom::Slice<nom::lib::std::ops::RangeFrom<usize>>
304    + nom::InputLength
305    + nom::InputIter
306    + nom::Compare<&'a str>
307    + nom::Offset
308    + nom::Slice<core::ops::RangeTo<usize>>
309    + std::fmt::Debug
310    + Into<&'a bstr::BStr>
311    + Default
312{
313    fn is_empty(&self) -> bool;
314}
315
316pub trait AbstractInputItem<I>: nom::AsChar + std::cmp::PartialEq<I> + From<u8> + Clone {
317    fn is_alphabetic(c: I) -> bool;
318    fn is_alphanumeric(c: I) -> bool;
319    fn is_token(c: I) -> bool;
320}
321
322impl<'a> AbstractInput<'a, char> for &'a str {
323    fn is_empty(&self) -> bool {
324        <str>::is_empty(self)
325    }
326}
327
328impl AbstractInputItem<char> for char {
329    fn is_alphabetic(c: char) -> bool {
330        c.is_ascii_alphabetic()
331    }
332
333    fn is_alphanumeric(c: char) -> bool {
334        c.is_ascii_alphanumeric()
335    }
336
337    fn is_token(c: char) -> bool {
338        Self::is_alphanumeric(c) || "-.!%*_+`'~".contains(c)
339    }
340}
341
342impl<'a> AbstractInput<'a, u8> for &'a [u8] {
343    fn is_empty(&self) -> bool {
344        <[u8]>::is_empty(self)
345    }
346}
347impl AbstractInputItem<u8> for u8 {
348    fn is_alphabetic(c: u8) -> bool {
349        nom::character::is_alphabetic(c)
350    }
351
352    fn is_alphanumeric(c: u8) -> bool {
353        nom::character::is_alphanumeric(c)
354    }
355
356    fn is_token(c: u8) -> bool {
357        use nom::character::is_alphanumeric;
358
359        is_alphanumeric(c) || "-.!%*_+`'~".contains(char::from(c))
360    }
361}
362
363pub(crate) mod utils {
364    pub fn opt_trim(input: &str) -> Option<&str> {
365        let input = input.trim();
366
367        match input.is_empty() {
368            true => None,
369            false => Some(input),
370        }
371    }
372}
373
374pub(crate) mod parser_utils {
375    use crate::TokenizerError;
376
377    pub fn is_token(c: u8) -> bool {
378        use nom::character::is_alphanumeric;
379
380        is_alphanumeric(c) || "-.!%*_+`'~".contains(char::from(c))
381    }
382
383    pub fn is_empty_or_fail_with<'a, I, T: crate::AbstractInput<'a, I>, S: Into<&'a bstr::BStr>>(
384        rem: T,
385        tuple: (&'static str, S),
386    ) -> Result<(), nom::Err<crate::TokenizerError>> {
387        if !rem.is_empty() {
388            //TODO: specify that this is trailing input
389            //use a comma in params tests to test
390            Err(TokenizerError::from(tuple).into())
391        } else {
392            Ok(())
393        }
394    }
395
396    /*
397    pub fn create_error_for<'a>(rem: &'a [u8], error: &'static str) -> super::NomError<'a> {
398        nom::Err::Error(nom::error::VerboseError {
399            errors: vec![(rem, nom::error::VerboseErrorKind::Context(error))],
400        })
401    }
402    */
403
404    /*
405        pub fn is_unreserved(chr: u8) -> bool {
406            use nom::character::is_alphanumeric;
407
408            is_alphanumeric(chr) || "-_.!~*'()".contains(char::from(chr))
409        }
410
411        pub fn is_reserved(chr: u8) -> bool {
412            ";/?:@&=+$,".contains(char::from(chr))
413        }
414    */
415
416    /*
417        pub fn opt_sc<'a>(
418            input: &'a [u8],
419        ) -> IResult<&'a [u8], Option<&'a [u8]>, VerboseError<&'a [u8]>> {
420            use nom::{bytes::complete::tag, combinator::opt};
421
422            opt(tag(";"))(input)
423        }
424
425        pub fn opt_amp<'a>(
426            input: &'a [u8],
427        ) -> IResult<&'a [u8], Option<&'a [u8]>, VerboseError<&'a [u8]>> {
428            use nom::{bytes::complete::tag, combinator::opt};
429
430            opt(tag("&"))(input)
431        }
432    */
433}