Addressing

Most systems in use nowadays use point-to-point addressing. In a point-to-point addressing scheme, the identities (read addresses) of both the sender and the receiver of a message are known and messages are routed in between. Since the identities of sensor nodes within a network are often not known or not interesting, OSAS adds a different addressing scheme: Content Based Addressing.

Within OSAS a content based address is predicate over NContext (node level state & functions). These predicates typically test for capabilities or properties of a node such as "the node is equipped with a temperature sensor", "the node is attached to patient four" or "a storage service has been installed on the node".

Within the WaSCoL programming language a content-based address is specified by the construct:

[<scope>|<HC>|<predicate>]

  • Scope is used to control where the message is transmitted. Currently two scoping mechanisms have been implemented:
    • Network (all nodes)
    • Cluster (a subgroup of nodes, more on this later)
  • The HC (hopcount) field functions as a limit on how far the message is allowed to travel before it is dropped (regardless of if it has reached its destination or not). It is either an integer or *.
  • Finally, the predicate is a boolean expression. If the expression is True, then it may be omitted altogether. The predicate is used to specify where a message is executed.

All the predicates get preinstalled on nodes during the configuration phase, such that later on, a specific content based address can be referenced with a compact ID (essentially, an increasing integer assigned by order of appearance).

Some examples and special forms:

  • [Network|1]
    This address targets all nodes in the network over a single-hop distance. It can be used to simply broadcast a message.
  • [Network|*]
    Use this address to flood a message through the entire network. The message sent will be encapsulated in a flooding handler which ensures that the message is executed everywhere at most once.
  • [Network|*|NodeID()==4]
    Here we see an example of a predicate. The systemcall NodeID is called to ensure that only node nr. four executes this message. Node that this message is flooded to all nodes in the network (due to the [Network|*] part) and then filtered based on the predicate. If any point-to-point routing mechanism is present, it is more efficient to use that to reach an individual node than to use a content based address.
  • [Network|*|HasSysCall(Temp)]
    All nodes in the network which are equipped with a temperature sensor.
  • [Network|*|GetProperty("PatientID", "Metadata")==4]
    All nodes in the network which are attached to patient four. Note: in this form it requires that the property "PatientID" within the "Metadata" namespace is defined on all nodes, which is not a safe assumption.
  • [Network|2|HasService(StorageService)]
    All nodes within the 2-hop neighborhood with the StorageService (assuming the StorageService has actually been defined).

Useful systemcalls which are often used in predicates are:

  • Introspection: HasSysCall, HasService, HasHandler, HasEvent
  • Properties: HasProperty, GetProperty, InitProperty, (SetProperty, DelProperty)
  • Identity: NodeID, NodeType

Nested addresses

In the previous description of content based addresses, the addresses of both the subscriber and the provider were independent of each other. Sometimes it is necessary to subscribe only to those providers that have some kind of relationship to the subscriber. For example, the nodes attached on a single patient, or nodes in the same logical group.

When defining subscriptions, it is possible to evaluate a (part of an) address predicate for the providers on the subscriber nodes. This way, simple relations between nodes can be used. To compute a (sub-) expression on a different node, enclose it: <<like this>> This is not allowed in the for-clause of a deployment statement, but it can be used within the on-clause or within program text e.g. as a parameter to SendMessage.

For example, assuming that each node has a system call PatientID() and we want to subscribe only to services on nodes belonging to the same patient.

for [Network|*|HasService(Forwarder)]
  install ExampleSubscription
       on [Network|*|HasService(Temperature) && PatientID() == <<PatientID()>>]

In the example above, the enclosed PatientID is evaluated on the subscriber nodes and the other PatientID is evaluated on the provider nodes. The value of the enclosed expression is passed as a parameter to the content based address predicate on the provider side.

Scope: Clustering

Previously we have shown the Network scope which floods messages throughout the entire network (within the bounds of the maximum hopcount). This operation can be quite costly (especially in large networks), therefore we sometimes want to limit the reception of messages to a subset of nodes. Here we take a look at such a subset: the cluster. We define a cluster as a set of connected nodes satisfying the same predicate.

The syntax for creating a cluster looks like this:

[Cluster(<cluster predicate>)|<HC>|<address predicate>]

Any node that evaluates the cluster predicate to False will set the hopcount to 1 which stops further forwarding of the message. A message guarded by a cluster predicate is received by all nodes in the cluster as well as the 1-hop neighbours of that cluster. The embedded payload of the message however is only executed within the cluster. Do note however, that the address predicate is also evaluated on the 1-hop neighbours. This is because the current implementation does not short-circuit the evaluation of the address predicate. This can cause subtile errors if the address predicate contains any system calls with side-effects.

As an example, consider a hospital wing. Each hospital room contains a gateway which is part of a routing backbone which runs through the hallway. Suppose we want each room gateway to subscribe to the accelerometer nodes of the patients within that room. We can create the following subscription:

for [Cluster(HasService(RoutingService))|*|NodeType()=="Gateway"]
  install ClusteringExample
       on [Cluster(RoomID()==<<RoomID()>>)|*|HasService(Acceleration)]

In this case, not using the cluster would cause the subscription request which is sent to the gateways to propagate to all the nodes on the patients as well rather than just the backbone cluster.