balena / elixir-sippet

An Elixir library designed to be used as SIP protocol middleware.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is there a way to parse uri in other headers than From, To and Contact?

martinos opened this issue · comments

I need to parse a P-Asserted-Identity header which as the same format as the From header. I am wondering if there's a way to parse it. I am not familiar with nif extensions.

Yes there is. Take a look at c_src/header_list.h; this "macroland" classifies the header types into "categories" for the internal parser, as most follow common structure.

You can even find the P-Asserted-Indentity there (as I copied the header list from IANA) but it's commented. Make sure that the syntax is indeed the same as From, and just set it as SingleContactParams.

It means that on your code you will get the header format parsed as {display_name, uri, parameters} as in the documentation.

Worth sharing your code in a PR; I can integrate these in the main code so others may get the same.

Didn it solve your case @martinos? If so let me close this ticket.

Yes, I will be sending you a pull request, I haven't had time. I was away for Easter, I will be back on Friday.

Thanks for your help.

I am not sure about which parser would be best for the P-Asserted-Indentity header. I am wondering if there's a way to parse that field manually using the the SingleContactParams parser but on the Elixir side. I would rather not touching theheader_list.h for the moment.

I just checked the RFC 3325, and it states that the format is:

  PAssertedID = "P-Asserted-Identity" HCOLON PAssertedID-value
                  *(COMMA PAssertedID-value)
  PAssertedID-value = name-addr / addr-spec

The difference from SingleContactParams is that the the latter accepts semicolon separated parameters, while the former doesn't, it is just name-addr / addr-spec ("John Doe" <john.doe@example.com> or just john.doe@example.com).

The existing parsing rules were not specialized enough to perform just SingleContact (considering the Contact part as being the generic name-addr / addr-spec). But it definitely doesn't hurt using simply SingleContactParams. Actually in real life SIP processing you don't need to be that restrictive, case you get these parameters you may pass them around just as you received them.

On the other hand, if you need to be that restrictive, you can continue using the SingleContactParams form and invalidating parameters from the Elixir side.

The parser was created using nif just because of performance. Using pure Elixir is also possible, but I don't think it's going to be neither much easier to read, neither will perform better. If I had to write a SIP parser entirely in Elixir, I would actually use the same logic you'll find in c_src.

I have some other headers that I want to parse, but I don't want to add it to the header_list since they are a bit custom. I am wondering to parse their values using the c parsers but on the Elixir side of my app?

Example I want to parse a P-Charge-Info header with the SingleContactParams parser. Is there a way for me to do that ?

All headers not specified in the C++ parser are collected by Sippet.Message as generic headers.

Here's an example:

iex> msg = ["OPTIONS sip:foo@bar.com SIP/2.0", "Generic-Header: Value1", "Generic-Header: Value2", ""]
iex> msg |> Enum.join("\n") |> Sippet.Message.parse!()
%Sippet.Message{
  headers: %{"Generic-Header" => ["Value", "Value2"]},  # here you go
  ...
}

So yes, you can parse whatever header you want from Elixir side by modifying Sippet.Message.parse/1.

Maybe I was not clear but I would like to use a C++ parser that parses SingleContactParams for a custom header. Can I do that?
Example, I have this header:

P-Charge-Info: "Alice"<sip:+12155551212@tel.example2.net>;tag=614bdb40

if I parse it using the Message.parse, I will get "Alice"<sip:+12155551212@tel.example2.net>;tag=614bdb40, but I don't have Alice in the first element of 3 elements tuple, as when it is parsed on the C++ side.

Is it possible to call the following function directly from Elixir ?

ERL_NIF_TERM ParseSingleContactParams(ErlNifEnv* env,
    std::string::const_iterator values_begin,
    std::string::const_iterator values_end) {
  Tokenizer tok(values_begin, values_end);
  ERL_NIF_TERM contact = ParseContact(env, &tok);
  if (enif_is_atom(env, contact))
    return contact;
  ERL_NIF_TERM parameters = ParseParameters(env, &tok);
  if (enif_is_atom(env, parameters))
    return parameters;
  int arity;'
  const ERL_NIF_TERM *name_and_address;
  enif_get_tuple(env, contact, &arity, &name_and_address);
  return enif_make_tuple3(env, name_and_address[0], name_and_address[1],
      parameters);
}

I am not familiar using nif module, maybe I can't do that and I need to create my own ParseSingleContactParams Elixir function. I wanted to avoid that because there are so many corner case in the way we should parse sip header values.

If you don't change anything in the code, P-Charge-Info will be handled as a generic header. That is, whenever the parser sees the same header-name it will append the entire header-value as a single string to a list.

This way:

iex> message = "OPTIONS sip:foo@bar.com SIP/2.0\nP-Charge-Info: \"Alice\"<sip:+12155551212@tel.example2.net>;tag=614bdb40\nP-Charge-Info: \"Romeo\"<sip:+123123123123@tel.example1.net>;tag=614bdb40\n\n"
iex> message |> Sippet.Message.parse
{:ok, %Sippet.Message{
   body: nil,
   headers: %{
     "P-Charge-Info" => ["\"Alice\"<sip:+12155551212@tel.example2.net>;tag=614bdb40",
      "\"Romeo\"<sip:+123123123123@tel.example1.net>;tag=614bdb40"]
   },
   start_line: %Sippet.Message.RequestLine{
     method: :options,
     request_uri: %Sippet.URI{
       authority: "foo@bar.com",
       headers: nil,
       host: "bar.com",
       parameters: nil,
       port: 5060,
       scheme: "sip",
       userinfo: "foo"
     },
     version: {2, 0}
   },
   target: nil
 }

But as per https://tools.ietf.org/id/draft-york-sipping-p-charge-info-15.html, it is actually a SingleContactParams (single "Contact" -like header, i.e. single name-addr or addr-spec per message). If you set as SingleContactParams, the presence of a second header-name will make the parser generate a {:error, :multiple_definition} as result. That's what the "Single" part of the parser name does.

OK, then if you set:

X(P-Charge-Info, 0, p_charge_info, SingleContactParams)

In c_src/header_list.h, the library will do what you want. Check:

iex> message = "OPTIONS sip:foo@bar.com SIP/2.0\nP-Charge-Info: \"Alice\"<sip:+12155551212@tel.example2.net>;tag=614bdb40\n\n"
iex> message |> Sippet.Message.parse
{:ok,       
 %Sippet.Message{
   body: nil,
   headers: %{
     p_charge_info: {"Alice",
      %Sippet.URI{
        authority: "+12155551212@tel.example2.net",
        headers: nil,
        host: "tel.example2.net",
        parameters: nil,
        port: 5060,
        scheme: "sip",
        userinfo: "+12155551212"
      }, %{"tag" => "614bdb40"}}
   },
   start_line: %Sippet.Message.RequestLine{
     method: :options,
     request_uri: %Sippet.URI{
       authority: "foo@bar.com",
       headers: nil,
       host: "bar.com",
       parameters: nil,
       port: 5060,
       scheme: "sip",
       userinfo: "foo"
     },
     version: {2, 0}
   },
   target: nil
 }}

So, the Sippet.Message.parse/1 function is returning {"Alice", %Sippet.URI{...}, %{...}} as you expect.

A tricky thing is that if you just change the c_src/header_list.h and just do a mix compile, you will notice that nothing gets compiled (supposing you already compiled the NIF module before). What I do in this case to make sure is:

find . -name *.o -exec rm {} \;
find . -name *.so -exec rm {} \;

From the elixir-sippet source directory (caution here: do not do that from other folder or you might erase important files from your system!), and repeat the mix compile command again.