Skip to content

Using libHexabus (0.6.0 or later)

myeisha edited this page Mar 21, 2013 · 7 revisions

Packet handling (libhexabus/packet.hpp)

Since every interaction in and with a Hexabus network hinges on the packets, packet handling will be discussed first.

Valid Hexabus packets are represented in a class hierarchy. libhexabus Packet hierarchy (UML)

Every valid Hexabus packet on the wire has a representation in this class hierarchy, with the obvious equivalences of hexabus packet type identifiers and classes in this hierarchy. Packets are visitable, and a visitor interface for all valid packet types is provided, as will be explained in greater detail lateron. All classes live in the namespace hexabus, which will be elided for brevity.

class Packet

Base class for all packets in libhexabus. Provides two accessors: uint8_t type(), which returns the type code of the packet on the network, and uint8_t flags(), which contains the flags of the packet. Provides the abstract bast void accept(PacketVisitor&), making all packets visitable.

class ErrorPacket : Packet

Represents an error packet (packet type HXB_PTYPE_ERROR). Provides one extra accessor: uint8_t code(), which contains the error code received with the packet.

Provides a constructor ErrorPacket(uint8_t code, uint8_t flags = 0).

class EIDPacket : Packet

Base class for all packets with EID information. Provides one extra accessor: uint32_t eid(), which contains the EID sent with the packet.

class QueryPacket : EIDPacket

Represents a direct query (packet type HXB_PTYPE_QUERY).

Provides a constructor QueryPacket(uint32_t eid, uint8_t flags = 0).

class EndpointQueryPacket : EIDPacket

Represents an endpoint query (packet type HXB_PTYPE_EPQUERY).

Provides a constructor EndpointQueryPacket(uint32_t eid, uint8_t flags = 0).

class TypedPacket : EIDPacket

Represents a packet with contents of some specified type. Provides one extra accessor: uint8_t datatype(), which contains the hexabus datatype identifier. This value can be any of the valid HXB_DTYPE_* values.

template class ValuePacket<TValue> : TypedPacket

Represents a packet that contains a value. Provides one extra accessor: TValue value(), which contains the value of the packet. Valid template parameters for TValue, with the equivalent hexabus datatype identifiers, are:

Type Hexabus type code Notes
bool HXB_DTYPE_BOOL
uint8_t HXB_DTYPE_UINT8
uint32_t HXB_DTYPE_UINT32
float HXB_DTYPE_FLOAT
boost::posix_time::ptime HXB_DTYPE_DATETIME
boost::posix_time::time_duration HXB_DTYPE_TIMESTAMP
std::string HXB_DTYPE_128STRING Strings longer than 127 characters will be rejected
std::vector<char> HXB_DTYPE_66BYTES Vectors longer than 66 bytes will be rejected, shorter vector will be zero-padded

Please note that this class cannot be instantiated with parameters of TValue not listed above. Attempting to instantiate anyway will result in a compile-time assertion failure.

template class InfoPacket<TValue> : ValuePacket<TValue>

Represents an info packet (packet type HXB_PTYPE_INFO).

Valid instantiatons (as defined by ValuePacket) provide a constructor InfoPacket(uint32_t eid, const TValue& value, uint8_t flags = 0).

template class WritePacket<TValue> : ValuePacket<TValue>

Represents a write packet (packet type HXB_PTYPE_WRITE).

Valid instantiatons (as defined by ValuePacket) provide a constructor WritePacket(uint32_t eid, const TValue& value, uint8_t flags = 0).

class EndpointInfoPacket : ValuePacket<std::string>

Represents an endpoint info packet (packet type HXB_PTYPE_EPINFO). Note that, according to the hexabus packet format, the datatype member of this class need not correlate with the actual value. In fact, the datatype member contains the data type of the endpoint, while value contains a textual description of the endpoint.

Provides a constructor EndpointInfoPacket(uint32_t eid, uint8_t datatype, const std::string& value, uint8_t flags = 0).

class PacketVisitor

As mentioned earlier, all packet classes are visitable. The PacketVisitor class provides the visitor interface for these purposes. One abstract visit method is provided for each valid packet type, where valid packet types are the leaves of the inheritance tree described above. Additionally, a method visitPacket(const Packet&) is provided, the default implementation simply defers to packet.accept.

Network handling (libhexabus/socket.hpp)

You should now have a rough idea of how packets are represented and what you can do with their representations. Packets alone are not very useful, though, with a method to send and receive them, usually to and from the hexabus network. The hexabus::Socket class takes care of this, converting Packet instances to packets on the network and vice versa.

class Socket

Two constructors are provided:

  • Socket(boost::asio::io_service& io): intialize a socket and bind it to a given boost::asio::io_service.
  • Socket(boost::asio::io_service& io, const std::string& interface): intialize a socket and bind it to a given boost::asio::io_service. Additionally, for all packets sent to a multicast address, use the network interface named interface for egress.

Both constructors may throw instances of NetworkException. The second constructor will usually only be useful if (a) your computer has more than one network interface, (b) at least one of these interfaces has no unicast path to the target hexabus network, and (c) you want to send hexabus broadcasts.

Sending packets

Packets can be sent with the send() overloads, all of which receive a const Packet& as first parameter:

  • send(const Packet& packet) send the packet to the hexabus multicast address and port
  • send(const Packet& packet, const boost::asio::ip::address_v6& dest): send the packet to the given IP address on the default port
  • send(const Packet& packet, const boost::asio::ip::udp::endpoint& dest): send the packet to the given endpoint. An endpoint holds both an IP address and a port.

The send methods may throw instances of NetworkException.

Sending packets may bind the socket to a random unused port, if the socket was not previously bound.

Receiving packets

By default, a socket is bound to the unspecified address and unspecified port, so no packets can be received. The socket must first be bound to a valid address before packets can be received, the bind and listen methods are provided to do this:

  • bind(const boost::asio::ip::address_v6& addr): bind the socket to the given IP address and a random unused port
  • bind(const boost::asio::ip::udp::endpoint& ep): binds the socket to the given endpoint.
  • listen(const boost::asio::ip::address_v6& addr): bind the socket to the given IP address and the hexabus default port. This is a convenience method, the same effect can be achieved with bind and the correct port number.

These methods may throw instances of NetworkException.

Receiving packets can now be done in two ways, synchronously and asynchronously.

For synchronous receive, the method receive(const filter_t& filter = filtering::any()) is provided. The filter parameter will be explained later. Calling receive() on a socket will listen on the bound address and return a std::pair<Packet::Ptr, boost::asio::ip::udp::endpoint> for the first packet received. The first member of the pair behaves like a pointer to a Packet instance, the second member holds the IP address and port the packet was sent from.

This method may throw instances of GenericException. It is merely a synchronous wrapper around the asynchronous receive functionality, providid for convenience. As such, running this method may have side effects on other users of the underlying io_service, such as (but not excluding) timer callback firing or asynchronous receives completing.

For asynchronous receive, use the method onPacketReceived(const on_packet_received_slot_t& callback, const filter_t& filter = filtering::any()). callback accepts any argument that can be called with a const Packet& as first parameter, and a boost::asio::ip::udp::endpoint as second parameter. Again, please ignore the second parameter for now. This method registers a receive handler and returns a boost::signals2::connection, which may be used to cancel the receive handler. As long as the handler is active, and the underlying IO service is running, packets received by the socket will be passed to all registered handlers.

Receiving a packet may still yield an error, as in the synchronous case, but throwing exceptions is not an option to pass these errors. If you are interested in these errors, use the method onAsyncError(const on_async_error_slot_t& callback) to register an error handler. When async packet receive sees an error, all active error handlers will be called with the appropriate exception as it would be thrown by the synchronous receive method.

Receive handlers and error handlers may be registered and cancelled seperately.

Receive filtering (libhexabus/filtering.hpp)

Recall the filter parameter to the receive methods mentioned above. A filter is anything that returns a boolean value when called as f(packet, source) with a const Packet& packet and a const boost::asio::ip::udp::endpoint source. Returning a true value indicates a packet match, meaning that the packet should be passed to you. Accordingly, returning false indicates a mismatch, meaning that the packet will not be returned to you. Since any function with a valid signature is acceptable here, you could write a filter that matches only packets with EID 42 as follows:

bool eidMatch(const Packet& packet, const boost::asio::ip::udp::endpoint& from)
{
  hexabus::EIDPacket* p = dynamic_cast<hexabus::EIDPacket*>(&packet);
  return p && p->eid() == 42;
}

Writing filters like this is rather cumbersome, so libhexabus provides a nice way to write simple filters: filter expressions. Filter expressions live in the namespace hexabus::filtering. For brevity, this section will assume that namespace hf = hexabus::filtering holds.

Filter expressions have two distinct properties: match and value. When evaluated as a filter, a non-matching filter expression is equivalent to filter returning false. A matching filter expression is equivalent to a filter returning the value of the filter expression. Note that filter expression values are not restricted to boolean type, but more on this later.

There are a number of atomic filter expressions:

name Matches when Value
hf::isError() always bool (packet is an error packet)
hf::isQuery() always bool (packet is a query packet)
hf::isEndpointQuery() always bool (packet is an endpoint query packet)
hf::isEndpointInfo() always bool (packet is an endpoint info packet)
hf::isInfo<TValue>() always bool (packet is an info packet with value type TValue)
hf::isWrite<TValue>() always bool (packet is a write packet with value type TValue)
hf::eid() packet is an EIDPacket instance uint32_t (EID of the packet)
hf::value<TValue>() packet is ValuePacket<TValue> instance TValue (value member of the packet)
hf::sourceIP() always boost::asio::ip::address_v6 (sender address of the packet)
hf::sourcePort() always uint16_t (sender port of the packet)
hf::constant<TValue>(const TValue& value) always TValue (value given in function call)
hf::any() always true

When evaluating a filter expression as a filter, non-bool values are coerced to bool using the usual C++ semantics for this operation.

Filter expression may be composited to form larger filter expressions. Available operators are all relational operators, all arithmetic operators, the boolean negation operator !, the binary bitwise operators (& | ^) and the boolean shortcut operators (&& ||). When using operators, values used in filter expressions that are not filter expressions themselves need not be wrapped in hf::constant calls.

Additionally to the atomic expressions and the operators, another operation is provided: the match-to-value coercion operator has. Applying this operator to a filter expression discard the value of the expression, matches always, is boolean valued, and has true as it's value if and only if the enclosed filter expression matches. As such, has(hf::eid()) will be true-valued for EID packets and false for everything else.

Coming back to our example from earlier, a filter expression to match packets with EID 42 could be written as has(hf::eid()) && hf::eid() == 42. Using the property of filter expressions that non-matches are equivalent to a false value, we may also write this as hf::eid() == 42. Another filter expression, matching on UINT32 values that are not zero (and nothing else), may thus be written als hf::value<uint32_t>().

Node liveness (libhexabus/liveness.hpp)

Occasionally it is useful to periodically report that the current node in the network is still alive. A special hexabus endpoint has been reserved for this purpose, and libhexabus provides the LivenessReporter class to handle such reports.

The class provides one constructor: LivenessReporter(Socket& socket, boost::posix_time::time_duration interval = boost::posix_time::minutes(60)). The first parameter indicates the socket the reporter should bind to; liveness reports are sent for that socket. The second parameter, defaulting to one hour, indicates the default report interval.

The report interval may be changed using the interval property of the class. The default should be sufficient, but applications that require shorter reporting intervals could be conceived.

The current node liveness state can be explicitly reported by calling reportAlive(bool alive = true) with an appropriate argument value. This will broadcast one bool-valued information packet on the reserved liveness endpoint through the socket.

Periodic liveness reporting can be initiated with start() and stopped with stop(). Periodic reporting requires the IO service used by the socket to be running and is equivalent to calling reportAlive() once for each interval() of time passed.

When first bringing up a node that has not communicated with the network earlier, multicast routers along the way to the hexabus network will not know about this new node. To reliably communicate with the network, state must be created along the path; the establishPaths(unsigned int hopCount) method is provided for this. Calling this method is equivalent to calling reportAlive() a total of hopCount + 1 times, waiting one second between each invocation. Note that this call blocks the current thread of execution during this time period. If you wish to asynchronously establish paths, construct another LivenessReporter with an interval of one second (or your preferred interval for establishing Paths), start it and stop it again at the appropriate time, or call reportAlive() yourself.

Clone this wiki locally