For programmers to share common conventions about network interfaces, TinyOS documentation describes the choices made to design network and AM interfaces, and how upper layers should be designed to meet these conventions.
The basic interface is Packet
(figure 1.1), which
provides access to the field that is common to every datagram
protocol : the payload. Protocols must provide a specific
interface to give access to the relevant fields of their
format. To avoid copy of data and memory consumption, the
components of the different layers share the same packet buffer, a
message_t
pointer. They rely on underlying packet
interfaces to know where their data begin.
interface Packet { command void clear(message_t* msg); command uint8_t payloadLength(message_t* msg); command void setPayLoadLength(message_t* msg, uint8_t len); command uint8_t maxPayloadLength(); command void* getPayload(message_t* msg, uint8_t* len); } |
interface AMPacket { command am_addr_t address(); command am_addr_t destination(message_t* amsg); command void setDestination(message_t* amsg, am_addr_t addr); command bool isForMe(message_t* amsg); command am_id_t type(message_t* amsg); command void setType(message_t* amsg, am_id_t t); } |
interface AMSend { command error_t send(am_addr_t addr, message_t* msg, uint8_t len); command error_t cancel(message_t* msg); event void sendDone(message_t* msg, error_t error); command uint8_t maxPayloadLength(); command void* getPayload(message_t* msg); } |
The AMPacket
interface provides accessors to two
additional fields : destination and
type(figure 1.2). The type of an active
message is a one-byte integer to identify the data format. Though
it also provides commands to set these fields, this is solely for
specific purposes beyond the scope of this document. The fields
will be set during the send process by the send component,
specified by AMSend
(figure 1.3). We can indeed notice
the addr
parameter of the send
command. There is no way to specify the type though. It is because
AMSend implementors provide a parameterized interface, with the
type as a parameter. This let co-existing protocols share a fair
sending queue, managed by the sender component.
Since all layers share the same packet buffer, and all rely on a
specific interface to manipulate messages, a single interface to
receive packets is needed. It is named Receive
, as shown on
figure 1.4. The commands associated to the payload are
only here for convenience.
One can notice that the receive handler is required to return a
message_t
buffer when a packet is received. This is a
system to avoid memory leaks. Indeed, the receive component has to
know when the packet is processed and the buffer available, so
that it can use the buffer for another received packet. If the
receive handler has to process several packets, it could keep all
the buffers (which are statically allocated). This would let the receive component out of
memory, and would prevent other components from receiving
messages. By returning a buffer, the receive handler provides to
the receive component a buffer that can be used for then next
packet that is received. The receive handler can either process
the packet and return the given buffer, or post a processing task
and return a new buffer.
interface Receive { event message_t* receive(message_t* msg, void* payload, uint8_t len); command void* getPayload(message_t* msg, uint8_t* len); command uint8_t payloadLength(message_t* msg); } |
However, interesting work had been done for TinyOS 1.x. A modular network layer was designed to ease implementation of new protocols and code reuse. Some parts of this layer were implemented and provided with TinyOS distribution. This work gives a good basis for our design, it was therefore adapted to TinyOS 2 and our needs. This is discussed in next section.
![]() |
The goal of the implementation is to provide to an application a
component in order to transparently send and receive data in a multihop
network. We have called this component DymoNetworkC
, which
is a configuration. The wiring provided by this configuration is
illustrated in figure 1.5. Since DYMO is a routing
protocol, the configuration must include a datagram protocol to
transport data on multi-hop routes. It can be any protocol using
the same address format as DYMO, a 16-bit address in this case. In
this document, we refer to the transport protocol as MH (for
Multi-Hop).
To be used, the network layer need to be started with the
SplitControl
interface. This is implemented by a dedicated
module, NetControlM
, which waits for all other components
to start before letting the application using the network layer
(ActiveMessageC
implements the link layer). The application
can then send and receive MHPacket
s, which can be
manipulated with the appropriate module.
Sent and received packets are handled by the DispatcherM
module, and passed to either DymoServiceC
or
MHServiceC
, depending on their AM type. These two
components inspect the packet and decide what to do with it:
forwarding it, passing it to the upper layer (for
MHServiceC
), or dropping it.
Forwarded packets (which include sent packets) are given to
AMSenderC
, which manages a sendinq queue for each AM
type. Service components can also generate packets, primarily to
send control packets (routing and error messages for DYMO, error
messages for MH). They decide what is the next hop of the packet
thanks to the routing table component.
DymoServiceC
(figure 1.6) and
MHServiceC
(figure 1.7) configurations contain
the components that implement the algortihms and packet format of
their respective protocols. Each of them is composed of three
parts: a forwarding engine, a protocol engine, and a packet
implementation.
The ForwardingEngineM
module has a very simple role and is
identical for the two protocol services. Upon receiving a packet from
the dispatcher through the forward
command, it asks the
routing engine for what should be done with this packet. The
routing engine inspects the packet thanks to the packet module,
decides what to do according to the implemented protocol, and
possibly updates the packet if it has to be forwarded. It returns
its decision to the forwarding engine, which performs the
appropriate action.
If processing a packet is too long, the routing engine may
immediately return a ``drop'' action, process the packet and
forward it by itself or send an arror message, since it also has
access to the AMSend
interface.
DymoTableC
component stores known routes, that is
mainly a destination address, a next hop and a hop count. Routing
information is retrieved from the table via RoutingTable
, a
generic interface for routing tables (described in
section 1.3.1). The DymoEngineM
module has
more control thanks to the DymoTable
interface, mainly to
update the table and know when a route is needed, so that a route
request can be issued.
interface RoutingTable { /** * Request for a route toward a destination. * @param Address of the destination node * @return The routing information associated to the destination */ command rt_info_t * getRoute(addr_t address); /** * Signal that a route has been removed from the table. * @param route_info Routing information associated to the evicted entry * @param r reason of the eviction */ event void evicted(rt_info_t * route_info, reason_t r); command uint16_t size(); } |
interface DymoTable { /** * Update the table with fresh information about a destination. * @param route_info The routing information associated to the destination */ command void update(rt_info_t * route_info); /** * Signal that a component asked for an unknown route, a RREQ should * be generated. * @param destination Target node of the needed route. */ event void routeNeeded(addr_t destination); } |