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}