This document outlines the top-level approach to application architecture in Acacian and says what the Acacian implementation handles and what is left to the application. Most of this document assumes a full DMP application although there are notes on simplification and some discussion of E1.31 only implementation.
Whilst there is skeleton of most of the code here, as of March 2011, some of this documentation is aspirational. However anyone extending or building on Acacian is very strongly urged to heed these guidelines so as to ensure an efficient and consistent implementation.
|Acacian Application Architecture||This document outlines the top-level approach to application architecture in Acacian and says what the Acacian implementation handles and what is left to the application.|
|Layers and modules|
|Compiler and Platform||This code has currently only been built using GCC and GNU Make on Linux platforms - both desktop and small embedded.|
|Component Handling||Acacian makes a fundamental distinction between Local components and Remote components.|
|UUID tracking||Connecting source CIDs as received in incoming packets, dicovery etc.|
|Discovery||Acacian does not implement discovery itself – this is a separate problem which is well addressed by openSLP [SLP] or other SLP implementations.|
|Memory Allocation||Memory management is currently sub-optimal.|
|Timers, Threads, Queues and Polling||There is an inner poll loop and event queue in <event.c>.|
|Layering, Initialization, Registration and Callbacks||Acacian ensures that initialization of lower layers is automatically called when they are needed by higher ones, so unless specific options need to be passed low level initialization is largely transparent.|
|Tailoring and Optimizing for specific applications.||Acacian provides many compile-time switches to customize your build – see documentation in acncfg.h.|
|DDL||Compile-time (for devices) and dynamic run-time parsing and generation of tables for DMP.|
|DMP||Registration of devices and descriptions, handling of connections, resolution of property addresses to internal functions and vice versa.|
|SDT||Session management, reliable and unreliable PDU transmit and receive, buffering and re-sending, notification of online status.|
|E1.31||Receiver code, tracks multiple source-universes according to the specification. [note: RLP and other support code has changed considerably since E1.31 was last built and tested].|
|Root layer||Wrapping outgoing packets, de-multiplexing incoming packets to SDT and E1.31.|
|Socket layer||Handles the OS and stack dependencies associated with Root layer traffic, including multicast subscribe and unsubscribe.|
|Timers and events||Maintains a queue of future events and interfaces to the OS timing services to ensure that these events are handled in timely and ordered manner.|
This code has currently only been built using GCC and GNU Make on Linux platforms - both desktop and small embedded. It assumes ISO C99 compliance. Porting to other compilers and platforms is on the TODO list!
No Makefile is provided because in testing it has been found more convenient to integrate the sources into the build process for the main application. Compiling the source with GCC is relatively straightforward.
Acacian makes a fundamental distinction between Local components and Remote components. In particular it needs different information relating to them and uses different structures to deal with them. In many implementations, there is just one local component which is tighly bound to Acacian and may be statically defined. However, some applications may implement multiple local components on the same Acacian instance and the code becomes more complex because one local component may need to talk to another. In this situation, the same component may appear as both a local component and a remote one.
To avoid unnecessary duplication, pointers and small structures, the main structures for both local and remote components contain data for multiple layers. However, much of this data is intended for internal use within the relevant layer only and should not be touched by application code.
Connecting source CIDs as received in incoming packets, dicovery etc. to their internal structures needs to be done very rapidly and efficiently. Searching a linear list is not sensible. Acacian implements a radix search algorithm using a Patricia tree (see Radix search using Patricia tree). This code is contained in uuid.c and is used for storing and tracking almost eny entity identified by UUID including device classes (DCIDs), behaviorsets and languages. There is also an alternative hash based search mechanism but this has not been tested for a long time.
Acacian does not implement discovery itself – this is a separate problem which is well addressed by openSLP [SLP] or other SLP implementations. Acacian does however provide some code to generate and parse SLP URLs and attributes which are conformant with EPI-19.
The generic UUID and component handling code in Acacian can also be used by the application to help manage discovered components. In order to connect to those components SDT only needs to be provided with the CID and adhoc address for the component, whilst DMP needs appropriate property maps. See dmpmap.c.
Unfortunately, the SDT protocol is not clean with respect to discovery. It confuses the issue by declaring expiry times and adhoc addresses within packets, and requires the implementation to support adhoc queries on almost any incoming socket, many of which could otherwise be optimised for a very narrow range of commands and sources. This implementation handles this poor situation notifying the application of this information, but does not attempt to retain or track addresses and expiry times learrned in this way – that is the job of the discovery subsystem.
Memory management is currently sub-optimal. There are the remnants of several earlier schemes within the code. Most allocation and freeing is wrapped in macros which are defined in acnmem.h.
There is an inner poll loop and event queue in <event.c>. This loop is single-threaded and can be used to build entire applications as is seen in the demonstration programs <device_demo> and <controller_demo>.
If built with CF_SDTRX_AUTOCALL true, the code will automatically read and handle each wrapper (by calling the registered higher layer callback functions) once it has completed SDT layer processing. However, depending on the application this may not work well because it holds up processing of any new incoming messages until the last one has been fully handled. If CF_SDTRX_AUTOCALL is false, readrxqueue() must be polled periodically to handle any received packets in the queue.
Acacian ensures that initialization of lower layers is automatically called when they are needed by higher ones, so unless specific options need to be passed low level initialization is largely transparent. SDT and DMP require that the application registers callbacks for handling incoming data, status messages etc.
Acacian provides many compile-time switches to customize your build – see documentation in acncfg.h.
Read the received wrapper queue (properly received wrappers only, there may be others awaiting sequencing) and dispatch to client protocol handlers.