sdt.h

Implementation of SDT (Session Data Transport) protocol

Summary
sdt.hImplementation of SDT (Session Data Transport) protocol
SDT API OverviewTo use SDT a component must first register using sdt_register.
Discovery in SDTSDT messages Join and Channel Parameters include ad-hoc address and expiry information that has belongs in Discovery and has no place in SDT.
SDT Structures and associated macros.
struct chanParams_sChannel Parameters – applied to each local channel and separately to each local member of a remote channel.
SDT callback functions
clientRx_fnCallback function (registered in<sdt_addClient>) to handle received client protocol data.
chanOpen_fnCallback function (registered in sdt_register as joinRx) to handle unsolicited Join requests.
memberevent_fnCallback function (registered in sdt_register as membevent) to handle out-of-band session or channel events.
sdt_client_s
sdt_Lcomp_sLocal component structure for SDT.
sdt_Rcomp_sRemote component structure for SDT.
Channel and member structuresEach local component may have multiple local channels and each channel may contain many members.
Lchannel_sLocal channel owned by one of our components
Rchannel_sRemote channel owned by a remote copmponent
member_sMember structures
Moving between components, channels and membersUse these macros rather than accessing structures directly because they hide differences due to CF_MULTI_COMPONENT and implementation changes.
txwrap_sOutgoing wrappers.
rxwrap_sIncoming wrappers.
SDT initialization and session management
sdt_register
sdt_deregisterDe-register a component from SDT.
openChannelCreate and initialize a new channel.
closeChannelClose a channel removing all members if necessary.
mkRecipCreate a reciprocal channel.
Ad hoc Join handlersThese are special values for the joinRx argument to sdt_register.
addMemberCreate a new member and add it to a channel and send a cold Join.
drop_memberDrop a member from both its channels.
sdt_addClientRegister a client protocol handler with SDT.
sdt_dropClientDe-register a client protocol from the SDT layer.
SDT transmit functions
startWrapperCreate and initialize a txwrap.
startMemberWrapper
cancelWrapperCancel a txwrap
startProtoMsgInitialize the next message block in the wrapper
endProtoMsgClose a message after adding data
rptProtoMsgRepeat the previous message to another member of the same group.
addProtoMsg
_flushWrapper
flushWrapper
sendWrap
wrapper flagsWhen accumulating messages in a wrapper, some require reliable transmission.
Channel Flags
membevent_eOut of band SDT events as passed to membevent callback [sdt_register].

SDT API Overview

Setting up

To use SDT a component must first register using sdt_register.  This initializes SDT if necessary, provides or generates a local address for handling ad-hoc messages and tells SDT how to handle ad-hoc Join requests.  Finally it provides a callback which SDT calls for out-of-band events to manage the status of sessions [membevent_e].

Next, the component must register one or more client protocols with the SDT layer using sdt_addClient.  This provides SDT with a callback function to handle incoming wrapped messages for that protocol.  For DMP the callback should be dmp_sdtRx.

Locally Initiated Sessions

To create its own sessions a component must first call openChannel which sets the session parameters and returns a Lchannel_s.  It can then call addMember using information from discovery to add members to that channel.

Once a channel is open and has members, the component can transmit messages either to individual members or to all members together.

Remotely Initiated Sessions

Behavior on receipt of a remote iniated Join depends on the joinRx argument passed to sdt_register.  If ADHOCJOIN_NONE was passed such joins are simply refused while ADHOCJOIN_ANY asks SDT to accept all requests.

The joinRx function also determines the strategy for assigning reciprocal channels to remote initiated sessions.  The default behavior if ADHOCJOIN_ANY is used is to create a new unicast reciprocal channel for each remote-led session.  This is by far the most network-efficient strategy, probably reduces processing burden on the remote component and may also do so locally.  It is good for most components.  However, components designed for very large numbers of incoming connections may run into socket limitations or find more memory resources are required using this strategy, in which case a strategy which allocates one or more large multicast channels to act as reciprocals may work better.

Most DMP devices will want to use ADHOCJOIN_ANY since this is how they listen for connections from controllers.  Limited DMP controllers may use ADHOCJOIN_NONE, but most controllers will want to receive events from devices for which those devices need to open event sessions back to them so they will either use ADHOCJOIN_ANY or supply a more discriminating function of their own.  (ADHOCJOIN_ANY translates to a call to the SDT internal function mkRecip which provides a template for writing such a function or may be called directly after making the necessary tests.)

Connection Handling

Once SDT is connected to a remote component via a reciprocal pair of channels the usual action is to send connect messages for the desired client protocol(s).

For incoming connect messages, SDT will accept the connection for any protocol for which it has handlers registered [sdt_addClient].

For locally led sessions the default is to automatically send connect for all protocols that are registered as soon as the channels are established.  This behavior is determined by the CF_SDT_MAX_CLIENT_PROTOCOLS configuration option and by the CHF_NOAUTOCON flag passed to openChannel but support for both these is only partially implemented at present (Feb 2014).

Note: the SDT spec is not clear about whether the connect, connect accept exchange to establish a connection needs to be initiated at one particular end or at both.  Acacian will accept connect requests from remote members irrespective of which end initiated the connections or whether a connection has already been established from either end.

Receiving

Once a connection is open, received wrappers are processed and queued in sequence then when the wrapper is processed, SDT unpacks it and passes any PDUs destined for local components and for registered protocols to the rxfn handler passed to sdt_addClient.

Note: Unpacking and processing the queued wrappers may be an asynchronous process.  See readrxqueue and CF_SDTRX_AUTOCALL.

Transmitting

A number of functions are provided to allow aggregation of messages into wrappers and packets and transmission of them.

Discovery in SDT

SDT messages Join and Channel Parameters include ad-hoc address and expiry information that has belongs in Discovery and has no place in SDT.  It is not at all clear how this is intended to be used.  This is a design flaw and the information is unnecessary.

Acacian does not use this information but does pass the packet data to the application using a member-event.  The demo applications ignore it.

SDT Structures and associated macros.

struct chanParams_s

Channel Parameters – applied to each local channel and separately to each local member of a remote channel.

SDT callback functions

clientRx_fn

typedef void clientRx_fn(struct member_s *memb,
const uint8_t *data,
int length,
void *cookie)

Callback function (registered in<sdt_addClient>) to handle received client protocol data.

membthe member which sent the message.
datapointer to the data (do not modify the data, modify a copy if necessary).
lengththe length of the data block.
cookiethe user data pointer that was passed to sdt_addClient.

chanOpen_fn

typedef struct Lchannel_s *chanOpen_fn(
   ifMC(struct Lcomponent_s *Lcomp,) struct chanParams_s *params
)

Callback function (registered in sdt_register as joinRx) to handle unsolicited Join requests.

LcompThe component being requested to join (only if CF_MULTI_COMPONENT).
paramsThe requested parameters for the incoming channel (the reciprocal channel will want to be compatible).

Return: The callback must return an open channel to use as a reciprocal for the Join, or NULL to refuse the join.

memberevent_fn

typedef void memberevent_fn(int event,
void *object,
void *info)

Callback function (registered in sdt_register as membevent) to handle out-of-band session or channel events.

eventThe event that has occurred (see membevent_e.
objectthe object (channel, member etc.) that causes the event.
infoextra data relating to the event as required.

sdt_client_s

Client protocol handlerper local component.  If there is just one client protocol (CF_SDT_MAX_CLIENT_PROTOCOLS == 1) we just have one of these which slots into the sdt_Lcomp_s, otherwise there is a list of them.

sdt_Lcomp_s

Local component structure for SDT.  This slots into the global <Lcomponent_s> and holds the SDT relevant information.

sdt_Rcomp_s

Remote component structure for SDT.  This slots into the global <Rcomponent_s> and holds the SDT relevant information.

Channel and member structures

Each local component may have multiple local channels and each channel may contain many members.  A single remote component may be a member of multiple local channels and may have different state in each.

Each remote component (there may be many) can have multiple channels and our local component(s) may be members of several.  However, one component cannot be a member of the same channel multiple times.

Lchannel_s

Local channel owned by one of our components

Rchannel_s

Remote channel owned by a remote copmponent

member_s

Member structures

Conceptually a local member of a remote channel is a completely separate entity from a remote member of a local channel and we define two separate structures.  However because of the requirement to bond them in reciprocal pairs, we never have one without the other and it is vital to reference from one to the other easily – we therefore keep both in the same enclosing structure and save ourselves lots of links otherwise needed to track reciprocals.

Furthermore, if we have only one local component (not CF_MULTI_COMPONENT), then it must be the sole member of each remote channel that we track, so we can unite the member and Rchannel structures.  The detail of this is masked by macros to give a consistent API.

Moving between components, channels and members

Use these macros rather than accessing structures directly because they hide differences due to CF_MULTI_COMPONENT and implementation changes.

ctxtLcomp(rcxt)get the local component from receive context.
LchanOwner(Lchan)get the owner (local component) of a local channel.
membLcomp(memb)get the local component from a member
get_Rchan(memb)get the remote channel from a member
forEachMemb(memb, Rchan)iterate over the members of a remote channel
firstMemb(Rchan)get the first member of a remote channel
membRcomp(memb)get the remote component from a member

txwrap_s

Outgoing wrappers.  Sent reliable wrappers must be kept until verified.  They are stored in a doubly linked list with newest added at the head and oldest removed from the tail.

rxwrap_s

Incoming wrappers.  One packet (<rxbuf>) may contain multiple wrappers so for each wrapper we create a tracking structure rxwrap_s.  These are stored before initial processing (if they arrive out of order) and when queued for application, in a doubly linked list in sequence order with newest at the head and oldest at the tail.

SDT initialization and session management

sdt_register

int sdt_register(ifMC(struct Lcomponent_s *Lcomp,) memberevent_fn *membevent,
netx_addr_t *adhocip,
chanOpen_fn *joinRx)
Lcompthe component registering (only if CF_MULTI_COMPONENT).
membeventcall back [memberevent_fn] for handling session and membership events.
adhocipin/out parameter specifying local address and/or port.  If NULL RLP defaults to ephemeral port and any local interface.  If adhocip is explicitly specified as ephemeral port (netx_PORT_EPHEM) and any local interface (INADDR_ANY) then adhocip gets filled in with the actual port used which can then be advertised for discovery [.
joinRxApplication supplied callback function [chanOpen_fn] to handle ad-hoc join requests.  Alternatively specify ADHOCJOIN_NONE (refuse all requests) or ADHOCJOIN_ANY (accept all requests).

See also: rlpSubscribe

sdt_deregister

void sdt_deregister(ifMC(struct Lcomponent_s *Lcomp))

De-register a component from SDT.  Lcomp - the component to de-register (only if CF_MULTI_COMPONENT).

openChannel

struct Lchannel_s *openChannel(
   ifMC(struct Lcomponent_s *Lcomp,) uint16_t flags,
   struct chanParams_s *params
)

Create and initialize a new channel.

Lcompthe component registering (only if CF_MULTI_COMPONENT).
flagssee Channel Flags.
paramsif params is NULL then defaults are used.  If params is provided then these are used for the new channel unless the RECIPROCAL flag is set, in which case the parameters supplied are assumed to be from an incoming channel and the new channel’s parameters are matched to these.

Returns

struct Lchannel_s * on success, on failure NULL is returned and errno set accordingly.

Errors

EINVAL supplied parameters are invalid ENOMEM couldn’t allocate a new struct Lchannel_s

closeChannel

void closeChannel(struct Lchannel_s *Lchan)

Close a channel removing all members if necessary.

Lchanthe channel to close

mkRecip

extern struct Lchannel_s *mkRecip(
   ifMC(struct Lcomponent_s *Lcomp,) struct chanParams_s *params
)

Create a reciprocal channel.  This function is normally invoked automatically when ADHOCJOIN_ANY is passed to sdt_register, however it may be called after vetting incoming Join requests to implement Join policy.

Lcompthe local component (only if CF_MULTI_COMPONENT).
paramsthe channel parameters from the incoming Join request.

Ad hoc Join handlers

These are special values for the joinRx argument to sdt_register.  They only affect ‘cold’ Joins received unsolicited from remote components.  ‘warm’ or reciprocal Join requests creating reciprocal channels for locally initiated sessions are always accepted.

ADHOCJOIN_NONERefuse all ‘cold’ Join requests.
ADHOCJOIN_ANYAccept all ‘cold’ Join requests.

addMember

extern int addMember(struct Lchannel_s *Lchan,
struct Rcomponent_s *Rcomp)

Create a new member and add it to a channel and send a cold Join.

Note: Completion of the add-member process is asynchronous.  A successful return status from addMember indicates only that the member structure was successfully created and the Join request sent.  The status of the complete process is received by callbacks to membevent which was passed to sdt_register.  On success a EV_JOINSUCCESS message is passed (with the new member structure).  On failure EV_JOINFAIL will be sent.

drop_member

void drop_member(struct member_s *memb,
uint8_t reason)

Drop a member from both its channels.  Disconnects all protocols as necessary then sends Leave in the Local channel, Leaaving in the remote channel. reason is sent as the reason code in the disconnecting and leaving messages.

sdt_addClient

int sdt_addClient(
   ifMC(struct Lcomponent_s *Lcomp,) ifSDT_MP(protocolID_t protocol,) clientRx_fn *rxfn,
   void *cookie
)

Register a client protocol handler with SDT.  If acacian has been compiled with CF_SDT_MAX_CLIENT_PROTOCOLS == 1 (the default) then the protocol is not passed in the call but the call is still necessary to pass the receive function and initialize the structures.

Lcompthe local component (only if CF_MULTI_COMPONENT).
protocolthe protocol ID being registered (only if CF_SDT_MAX_CLIENT_PROTOCOLS > 1)
rxfncallback function to handle successfully received data for
cookieApplication data pointer which gets passed to rxfn.

sdt_dropClient

void sdt_dropClient(ifMC(struct Lcomponent_s *Lcomp))

De-register a client protocol from the SDT layer.

SDT transmit functions

startWrapper

struct txwrap_s *startWrapper(int size)

Create and initialize a txwrap.

Returns

New txwrap on success, NULL on error.

Errors

EMSGSIZEsize is too big

startMemberWrapper

struct txwrap_s *startMemberWrapper(struct member_s *memb,
int size,
uint16_t wflags)

cancelWrapper

void cancelWrapper(struct txwrap_s *txwrap)

Cancel a txwrap

startProtoMsg

uint8_t *startProtoMsg(struct txwrap_s **txwrapp,
void *dest,
protocolID_t proto,
uint16_t wflags,
int *sizep)

Initialize the next message block in the wrapper

returns

Pointer to place to put the data

endProtoMsg

int endProtoMsg(struct txwrap_s *txwrap,
uint8_t *endp)

Close a message after adding data

endppoints to the end of the PDUs you have added

rptProtoMsg

int rptProtoMsg(struct txwrap_s **txwrapp,
struct member_s *memb,
uint16_t wflags)

Repeat the previous message to another member of the same group.

Returns

The remaining space (positive) on success, or -1 on error.

addProtoMsg

int addProtoMsg(struct txwrap_s **txwrapp,
struct member_s *memb,
protocolID_t proto,
uint16_t wflags,
const uint8_t *data,
int size)

_flushWrapper

int _flushWrapper(struct txwrap_s *txwrap,
int32_t *Rseqp)

flushWrapper

static inline int flushWrapper(struct txwrap_s **txwrapp)

sendWrap

int sendWrap(struct member_s *memb,
protocolID_t proto,
uint16_t wflags,
const uint8_t *data,
int size)

wrapper flags

When accumulating messages in a wrapper, some require reliable transmission.  Of those that do not most don’t care whether the wrapper is reliable or not.  However, there are a few messages that may require that the wrapper be sent unreliably and these cannot be mixed in the same wrapper as messages that require reliability.

WRAP_REL_DONTCAREMessage doesn’t care whether wrapper is reliable or not.
WRAP_REL_ONMessage requires reliable wrapper.
WRAP_REL_OFFMessage requires unreliable wrapper.
WRAP_REPLYMessage is a reply (SDT sets the association field appropriately).
WRAP_ALL_MEMBERSMessage is to all members of the channel.
WRAP_NOAUTOACKDo not add background ACKs to the wrapper.
WRAP_NOAUTOFLUSHDo not automatically flush full or incompatible wrappers.

Channel Flags

CHF_UNICASTunicast channel
CHF_RECIPROCALis reciprocal for remote initiated channel
CHF_NOAUTOCONdo not automatically issue connect requests
CHF_NOCLOSEdo not automatically close channel when last member is removed

membevent_e

Out of band SDT events as passed to membevent callback [sdt_register].  The event callback is passed two arguments in addition to the event: object and info [memberevent_fn].  The values of these depend on the message.

EV_RCONNECTRemote initiated connect succeeded. object=Local channel, info=member.
EV_LCONNECTLocal initiated connect succeeded. object=Local channel, info=member.
EV_DISCOVERDiscovery information [Discovery in SDT]. object = Remote component, info=pointer to packet data.
EV_JOINFAILAn attempt to join a remote component to a local channel has failed. object=Local channel, info=Remote component.
EV_JOINSUCCESSJoin succeded. object=Local channel, info= member.  This event occurs whoever initiated the join, once both local and remote channel are Joined.
EV_LOCCLOSEused internally, never passed to membevent
EV_LOCDISCONNECTA connection that was locally initiated has disconnected. object=Local channel, info=member.
EV_LOCDISCONNECTINGA connection that was remotely initiated has disconnected. object=Local channel, info=member.
EV_LOCLEAVEmember is being asked to leave the channel. object = Local channel, info=member.
EV_LOSTSEQChannel pair is being closed because we’ve lost sequence in the remote channel. object=Local channel, info= member.
EV_MAKTIMEOUTChannel pair is being closed because the remote did not Ack in the local channel when requested. object=Local channel, info=member.
EV_NAKTIMEOUTChannel pair is being closed because NAK for lost wrappers failed. object=Local channel, info=member.
EV_REMDISCONNECTRemote has sent a disconnect message. object= Local channel, info=member.
EV_REMDISCONNECTINGRemote has sent a disconnecting message. object=Local channel, info=member.
EV_REMLEAVEused internally, never passed to membevent
EV_CONNECTFAILA locally initiated connect request was refused. object=Local channel, info=member.
int sdt_register(ifMC(struct Lcomponent_s *Lcomp,) memberevent_fn *membevent,
netx_addr_t *adhocip,
chanOpen_fn *joinRx)
typedef void clientRx_fn(struct member_s *memb,
const uint8_t *data,
int length,
void *cookie)
Callback function (registered in<sdt_addClient>) to handle received client protocol data.
typedef struct Lchannel_s *chanOpen_fn(
   ifMC(struct Lcomponent_s *Lcomp,) struct chanParams_s *params
)
Callback function (registered in sdt_register as joinRx) to handle unsolicited Join requests.
typedef void memberevent_fn(int event,
void *object,
void *info)
Callback function (registered in sdt_register as membevent) to handle out-of-band session or channel events.
One or many components?
void sdt_deregister(ifMC(struct Lcomponent_s *Lcomp))
De-register a component from SDT.
struct Lchannel_s *openChannel(
   ifMC(struct Lcomponent_s *Lcomp,) uint16_t flags,
   struct chanParams_s *params
)
Create and initialize a new channel.
void closeChannel(struct Lchannel_s *Lchan)
Close a channel removing all members if necessary.
extern struct Lchannel_s *mkRecip(
   ifMC(struct Lcomponent_s *Lcomp,) struct chanParams_s *params
)
Create a reciprocal channel.
extern int addMember(struct Lchannel_s *Lchan,
struct Rcomponent_s *Rcomp)
Create a new member and add it to a channel and send a cold Join.
void drop_member(struct member_s *memb,
uint8_t reason)
Drop a member from both its channels.
int sdt_addClient(
   ifMC(struct Lcomponent_s *Lcomp,) ifSDT_MP(protocolID_t protocol,) clientRx_fn *rxfn,
   void *cookie
)
Register a client protocol handler with SDT.
void sdt_dropClient(ifMC(struct Lcomponent_s *Lcomp))
De-register a client protocol from the SDT layer.
struct txwrap_s *startWrapper(int size)
Create and initialize a txwrap.
struct txwrap_s *startMemberWrapper(struct member_s *memb,
int size,
uint16_t wflags)
void cancelWrapper(struct txwrap_s *txwrap)
Cancel a txwrap
uint8_t *startProtoMsg(struct txwrap_s **txwrapp,
void *dest,
protocolID_t proto,
uint16_t wflags,
int *sizep)
Initialize the next message block in the wrapper
int endProtoMsg(struct txwrap_s *txwrap,
uint8_t *endp)
Close a message after adding data
int rptProtoMsg(struct txwrap_s **txwrapp,
struct member_s *memb,
uint16_t wflags)
Repeat the previous message to another member of the same group.
int addProtoMsg(struct txwrap_s **txwrapp,
struct member_s *memb,
protocolID_t proto,
uint16_t wflags,
const uint8_t *data,
int size)
int _flushWrapper(struct txwrap_s *txwrap,
int32_t *Rseqp)
static inline int flushWrapper(struct txwrap_s **txwrapp)
int sendWrap(struct member_s *memb,
protocolID_t proto,
uint16_t wflags,
const uint8_t *data,
int size)
Out of band SDT events as passed to membevent callback [sdt_register].
void dmp_sdtRx(struct member_s *memb,
const uint8_t *pdus,
int blocksize,
void *ref)
This is a callback function which must be passed to SDT to have Acacian’s DMP code parse and handle incoming DMP.
Refuse all ‘cold’ Join requests.
Accept all ‘cold’ Join requests.
Number of client protocols to allocate space for.
do not automatically issue connect requests
void readrxqueue()
Read the received wrapper queue (properly received wrappers only, there may be others awaiting sequencing) and dispatch to client protocol handlers.
When an sdt wrapper is correctly received it is placed in an ordered queue.
Local component structure for SDT.
struct rlpsocket_s * rlpSubscribe(netx_addr_t *lclad,
protocolID_t protocol,
rlpcallback_fn *callback,
void *ref)
Returns an rlpsocket_s suitable for sending and registers a callback function for receiving packets.
SDT messages Join and Channel Parameters include ad-hoc address and expiry information that has belongs in Discovery and has no place in SDT.
Close