September 23, 2002
head/vocal/sip2/sipstack/README

SipMessage design goals:
- efficient parsing
- reasonable interface for the application writer
- reasonably easy path to adding new headers, methods, parameter names

SipMessage design.

There are two APIs to SipMessage; one from the transport layer 'up' and one from
the application layer 'down'. The transport layers pulls the raw message text
off the wire, parses the text just enough to segment it onto headers and sets
the text of the headers into the SipMessage generically. This minimal header
parse phase is called "pre-parsing".

The pre-parse phase needs to identify the header for generic storage into the
SipMessage. In addition, the pre-parse phase needs to know if unescaped commas
are delimiters in the header text to be parsed. However, the pre-parse phase
does not require static typing of the header or whether the header can appear
multiple times in the message. 

The pre-parse phase allows unknown headers.

The application requests a specific header by static type. If the header does
not exists, an empty header of the appropriate type is created. If the header
has not already been parsed, a parser is created and associated with the
header. The parser is not invoked until the application requests a sub-part of
the header. This permits headers to be moved from one message to another without
parsing within the header.

The parser is statically typed to provide a specific interface to the
application. The specifics of how to parse a message of a given type is
implemented by the parser type. The static header types are available to the
application through global variables named after the headers; e.g. CallId, CSeq,
From, Via.

The interface from each parser is a set of accessors. Each accessor may return a
reference, allowing the application to set the corresponding value directly, or
it may return a const reference, indicating a read only value. Most parsers also
present an interface to a generic parameter accessor. This generic parameter
accessors allows the application to get or set proprietary parameters.

The application retrieves unknown headers by the string name of the unknown
header.

Interface Examples:

SipMessage *msg;
msg->get(From).getAddressOfRecord();
Data& aor = msg->get(From)->host();
msg->get(From).host() = "vovida.org";
msg->exists(Warning);
msg->exists("Proprietary-Header"); // true if present
msg->get("Proprietary-Header"); // returns ParserContainer<String>&

for (Vias::iterator i = msg->get(Vias).begin();
     i != msg->get(Vias).end(); i++)
{
    Data& host = i->host();
    int& port = i->port();
}

Implementation:

There is a fixed set of headers. Each header is assigned an enum value. There is
a type associated with each enum value and a distinctly named global variable of
header type. The enum value is used to store and retrieve the headers at
runtime. The header type is used to retrieve the parsed and statically typed
headers at compile time.

A SipMessage contains an array of pointers to HeaderFieldValueList. This array
is indexed by the header enums.

The pre-parse phase consists of:
0. determine the boundaries of the header string
1. determine the boundaries of the header name string
2. look up the enum value for the header name
3. look up the comma tokenizing property of the header enum
4. determine the boundaries of each header in the header string
5. for each header in the header string, add the header string boundaries by
   enum to the message -- the message stores the header string boundaries in the
   as indexed by the header enum

While processing headers the pre-parse will allocate chunks of memory to hold the
header strings. Each of these buffers is passed opaquely to the message for
deletion when the message is deleted.

After pre-parsing the message header array contains one HeaderFieldValueList for
each header encountered in the pre-parse. Each HeaderFieldValueList contains 
a list of HeaderFieldValue. 

The application requests the contents of a header through a type safe interface;
the returned object is either a particular parser type or a particular parser
container. A parser container is returned if and only if the header is specified
to permit multiple values.

If the header specifies multiple values, the returned value is iterable. Each
iteration provides access to a header value. In addition, there is an interface
on the returned value to determine the number of header values.

The returned value in the single header value case, and within the iteration
over the multi header value case provides access to the components of the header
value specfic to the type of header. For example, CSeq provides getMethod() and
sequence. For sequence, the sequence value is an integer returned by reference
and may be directly modified.

Adding a new header:

Assume the new header name New-Header

Symbols.hxx
1. Add New_Header to the static symbol declarations.
Symbols.chxx
1. Add New_Header = "New-Header" to the static symbol definitions.

HeaderTypes.hxx
1. Add New_Header to the Headers::Type enum. Add this entry anywhere before UNKNOWN.
   Headers are output in the same order they appear in the Headers::Type enum.
2. Determine if the header allows multiple values. Use the MultiHeader template
   if the new header allows multiple values, use the Header template if the new
   header does not allow multiple values.
3. Determine the parser type for the header. This may be StringComponent for
   unstructured headers, may be another existing parser type (see
   ParserCategories.hxx), or may require a new parser type.
4. Add the declaration of the header type and the header type global variable to
   HeaderTypes.hxx
e.g.:
class Header<Headers::New_Header>
{
   public:
      typedef StringComponent Type;
      Header()
      {
         Headers::CommaTokenizing[Headers::New_Header] = Type::isCommaTokenizing;
         HeaderStrings[Headers::New_Header] = Symbols::New_Header;
      }
};
extern Header<Headers::Content_Disposition> Content_Disposition;
5. Add the mapping from the four character hash to the header enum. This is
   error prone. The hash value is platform dependent.

HeaderTypesSetup.cxx
1. Add a line to the hash id generator.

HeaderTypes.cxx
1. Add the definition of the new header type global variable. Be sure to use the
   same template type as in the declaration.
e.g.
Header<Headers::New_Header> Vocal2:: New_Header>;

ParserCategories.hxx
(if the new header requires a new parser)

1. Declare the new parser category as publicly inheriting from
   ParserCategory.
2. Determine if commas can be used to include multiple header values in a single
   line.
3. Declare virtual ParserCategory* clone(HeaderFieldValue*) const;
4. Declare virtual void parse();
5. Declare private members as required to store the parsed header value.
6. Declare public methods as required to access the parsed header value.
7. Typedef the container for the parser. Not required if the header value is
   never used in a header that allows multiple values.

e.g.
typedef ParserContainer<NewComponent> NewComponents;
   
ParserCategories.cxx
(if the new header requires a new parser)
1. Define clone(), parse()
2. Define accessors. Each accessor must call checkParsed() before processing any
   data.


(* 
   It would be nice if getHeaderType(const char *headerName, int len)
   was defined my configure/make. The supported.hxx would be replaced 
   with a configuration file headers.hdr that specified the SIP header name, the
   C++ parser category, and whether the header allows multiple values.

   This file could be compiled into the various declarations and definitions.
   It is even possible to allow the user to specify additional headers in the
   code; have the automatically generated types go into the enum
   HeaderTypesBase, and have HeaderTypes inherit; HeaderTypes::Types could
   continue where the inherited enum leaves off. 
*)



