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.  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.

Summary
Acacian Application ArchitectureThis 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 PlatformThis code has currently only been built using GCC and GNU Make on Linux platforms - both desktop and small embedded.
Component HandlingAcacian makes a fundamental distinction between Local components and Remote components.
UUID trackingConnecting source CIDs as received in incoming packets, dicovery etc.
DiscoveryAcacian does not implement discovery itself – this is a separate problem which is well addressed by openSLP [SLP] or other SLP implementations.
Memory AllocationMemory management is currently sub-optimal.
Timers, Threads, Queues and PollingThere is an inner poll loop and event queue in <event.c>.
Layering, Initialization, Registration and CallbacksAcacian 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.

Layers and modules

Acacian implements the following layers and modules, roughly top down

DDLCompile-time (for devices) and dynamic run-time parsing and generation of tables for DMP.
DMPRegistration of devices and descriptions, handling of connections, resolution of property addresses to internal functions and vice versa.
SDTSession management, reliable and unreliable PDU transmit and receive, buffering and re-sending, notification of online status.
E1.31Receiver 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 layerWrapping outgoing packets, de-multiplexing incoming packets to SDT and E1.31.
Socket layerHandles the OS and stack dependencies associated with Root layer traffic, including multicast subscribe and unsubscribe.
Timers and eventsMaintains a queue of future events and interfaces to the OS timing services to ensure that these events are handled in timely and ordered manner.

Compiler and Platform

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.

Component Handling

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.

UUID tracking

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.

Discovery

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 Allocation

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.

Timers, Threads, Queues and Polling

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.

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.  SDT and DMP require that the application registers callbacks for handling incoming data, status messages etc.

Tailoring and Optimizing for specific applications.

Acacian provides many compile-time switches to customize your build – see documentation in acncfg.h.

The demo device and controller use OpenSLP which is available as an option on most Linux distributions.
This tree uses the technique described in Sedgewick: “Algorithms” for folding the external nodes back into internal nodes - the value within the node is ignored when descending the tree (as the node is being treated as an internal one) and only the testbit is considered.
DMP address and property handling.
All code uses functions or macros defined here to allocate memory.
When an sdt wrapper is correctly received it is placed in an ordered queue.
void readrxqueue()
Read the received wrapper queue (properly received wrappers only, there may be others awaiting sequencing) and dispatch to client protocol handlers.
Close