The Agent daemon supports a local API with REST-like endpoints as well as WebSockets.
All messages passing through the Local API must be in the ioMessage format. Note that the ContextData and ContentData fields of all messages must be base64 encoded when sending messages in a JSON response. All incoming messages will also have these fields base64 encoded and will need to be decoded upon arrival. No other fields should be encoded, because all other fields are capable of being transmitted directly as JSON. This allows containers to quickly examine an incoming message InfoType and InfoFormat to see if performing the base64 decoding is worthwhile.
If a message InfoFormat is actually base64, then it will be encoded again during transmission as JSON. While this is not very efficient, it is sustainable as a practice. Neither the ioFog Local API nor the containers need to examine messages to determine the type of encoding. They both simply decode arriving messages and encode messages before sending (just the ContextData and ContentData fields).
All messages sent and received using the Local API must be in the standard ioMessage format, which can be found in the ioMessage Specification below.
The purpose of a message is move information along a path. No understanding of the contents of the messages should be required in order to help it to its correct destination. The header fields of each message, however, are intended to be read and understood by functional pieces of the iofog system. Because the data contents of the message format are open, that means each recipient will be required to determine for itself if it understands how to read the data. Recipients can check the information type and information format headers to determine this.
The ioMessage versions are integers, not decimals. This is because it is harder to parse a raw binary message with decimals across different computing platforms. So... ioMessage versions will be things like 4, 5, and 12. The version can be used to determine what fields will be present in the message and perhaps how the data will be arranged in those fields.
The ID for each message must be unique across the Earth for 20 years or longer. Depending on the volume of ioMessages across the globe, a 128-bit identifier may reach a 99.9%+ chance of collisions well before that timeframe ends. So a 256-bit identifier has been chosen and should suffice.
The fields listed here do not contain any formatting information except for the ID, which is strictly standardized. Each embodiment of the ioMessage standard will make use of the best features of the embodiment method. For example, when using JSON to create ioMessages, there is no need to include length information about the different fields. And there is no need to put any particular field in any particular position. XML is similar. But when encoding an ioMessage in raw bytes, the order of the information is very crucial for packing and parsing the messages. While JSON and XML offer some advantages, they also have more overhead than raw bytes. And while raw byte formatting requires parsing by the receiver, it also has very low overhead and is excellent for real-time transmission of media such as photos or video.
A listing for JSON, XML, and raw bytes is included in this document after the main field listing.
Data Type | Text |
Key | ID |
Required | Yes |
Description | A 256-bit universally unique identifier per message allows for portability and globe-wide verification of events. The ID string is formatted in base58 for readability, transmission safety between systems, and compactness. |
Data Type | Text |
Key | Tag |
Required | No |
Description | This is an open field for associating a message with a particular device or any other interesting thing. It should be queryable later, making this a high-value field for some applications. |
Data Type | Text |
Key | GroupID |
Required | No |
Description | This is how messages can be allocated to a sequence or stream. |
Data Type | Integer |
Key | SequenceNumber |
Required | No |
Description | What number in the sequence is this current message? |
Data Type | Integer |
Key | SequenceTotal |
Required | No |
Description | How many total messages are in the sequence? Absence of a total count while sequence numbers and a message group ID are present may be used to indicate a stream with indeterminate length. |
Data Type | Integer |
Key | Priority |
Required | No |
Description | The lower the number, the higher the priority. This is a simple quality of service (QoS) indicator. Emergency messages or system error logs might get the highest priority. Self-contained messages (such as a button push or a temperature reading) might get very high priority. Media stream messages (such as one second of audio) might get very low priority ranking in order to allow message slowing or dropping as needed in a busy system. |
Data Type | Integer |
Key | Timestamp |
Required | Yes |
Description | Universal timecode including milliseconds. Milliseconds can be entered as zeroes if needed. |
Data Type | Text |
Key | Publisher |
Required | Yes |
Description | This is the identifier of the element that is sending the message. It can be used to determine routing or guarantee privacy and security. Because each element is assigned a UUID during configuration, even across ioFog instances no message should be received by an unintended entity. |
Data Type | Text |
Key | AuthID |
Required | No |
Description | This is an open field to pass along authentication information about the particular authorized entity generating the message, such as an employee ID number or a user ID in the application. |
Data Type | Text |
Key | AuthGroup |
Required | No |
Description | This is an open field to pass authentication group information. This allows pieces of the application to know they are dealing with a message from an authenticated user of a particular type (such as “employee” or “system admin”) without needing to know the actual identification information. |
Data Type | Integer |
Key | Version |
Required | Yes |
Description | Which version of the ioMessage format does this particular message comply with? |
Data Type | Integer |
Key | ChainPosition |
Required | No |
Description | When using cryptographic message chaining, this field represents the position in the message chain that this paricular message occupies. It is similar to the "block height" value found in blockchain technology. |
Data Type | Text |
Key | Hash |
Required | No |
Description | When using cryptographic message chaining, a hash of this entire message can be included here. |
Data Type | Text |
Key | PreviousHash |
Required | No |
Description | When using cryptographic message chaining, the hash value of the previous message is included here. This forms the cryptographic link from the prior message to this one. |
Data Type | Text |
Key | Nonce |
Required | No |
Description | When using cryptographic message chaining, an open field is needed to achieve the correct hash value. The information in this field will not be meaningful, but will be necessary to produce the final hash of the message. |
Data Type | Integer |
Key | DifficultyTarget |
Required | No |
Description | When using cryptographic message chaining, this field represents the hashing workload required to cryptographically seal the chain. |
Data Type | Text |
Key | InfoType |
Required | Yes |
Description | This is like a MIME type. It describes what type of information is contained in the content data field. |
Data Type | Text |
Key | InfoFormat |
Required | Yes |
Description | This is a sub-field of the Information Type. It defines the format of the data content in this message. If the information type is “Temperature”, for example, then the information format might be “Degrees Kelvin”. |
Data Type | Any (including binary, text, integer, etc.) |
Key | ContextData |
Required | No |
Description | Context data in raw bytes. This field can be used to embed any information desired and will likely be very different from one solution to the next. It is the responsibility of the receiving element(s) to understand the context data format and the meaning of the context information. |
Data Type | Any (including binary, text, integer, etc.) |
Key | ContentData |
Required | Yes |
Description | The actual data content of the message in its raw form. Having a raw format for this field allows for the greatest amount of flexibility in the system. |
The ContextData and ContentData fields of an ioMessage, when embodied in JSON, will always be base64 encoded. This is because these fields contain raw bytes and there is no other way to represent raw bytes in the utf-8 structure that JSON uses. Upon receiving a JSON ioMessage, you must base64 decode those two fields. Before sending a JSON ioMessage, you must base64 encode those two fields.
{
"id": "sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
"tag": "Bosch Camera 16",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"timestamp": 1234567890123,
"publisher": "Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "image/jpeg",
"infoformat": "file/.jpg",
"contextdata": "",
"contentdata": "sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
}
The ContextData and ContentData fields of an ioMessage, when embodied in XML, will always be base64 encoded. This is because these fields contain raw bytes and there is no other way to represent raw bytes in the text formats that XML uses. Upon receiving an XML ioMessage, you must base64 decode those two fields. Before sending an XML ioMessage, you must base64 encode those two fields.
<iomessage> <id>sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf</id> <tag>Bosch Camera 16</tag> <groupid></groupid> <sequencenumber>1</sequencenumber> <sequencetotal>1</sequencetotal> <priority>0</priority> <timestamp>1234567890123</timestamp> <publisher>Ayew98wtosdhFSKdjhsdfkjhkjesdhg</publisher> <authid></authid> <authgroup></authgroup> <version>4</version> <chainposition>0</chainposition> <hash></hash> <previoushash></previoushash> <nonce></nonce> <difficultytarget>0.0</difficultytarget> <infotype>image/jpeg</infotype> <infoformat>file/.jpg</infoformat> <contextdata></contextdata> <contentdata>sDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufha</contentdata> </iomessage>
Bytes are octets here. No funny business. Just good old 8-bit bytes. The sequence of bytes here must be followed strictly so the message can be parsed by the receiver.
[2 bytes] - Version [1 bytes] - Length of ID field [2 bytes] - Length of Tag field [1 bytes] - Length of Group ID field [1 bytes] - Length of Sequence Number field [1 bytes] - Length of Sequence Total field [1 bytes] - Length of Priority field [1 bytes] - Length of Timestamp field [1 bytes] - Length of Publisher field [2 bytes] - Length of Auth ID field [2 bytes] - Length of Auth Group field [1 bytes] - Length of Chain Position field [2 bytes] - Length of Hash field [2 bytes] - Length of Previous Hash field [2 bytes] - Length of Nonce field [1 bytes] - Length of Difficulty Target field [1 bytes] - Length of Info Type field [1 bytes] - Length of Info Format field [4 bytes] - Length of Context Data field [4 bytes] - Length of Content Data field [n bytes] - ID value [n bytes] - Tag value [n bytes] - Group ID value [n bytes] - Sequence Number value [n bytes] - Sequence Total value [n bytes] - Priority value [n bytes] - Timestamp value [n bytes] - Publisher value [n bytes] - Auth ID value [n bytes] - Auth Group value [n bytes] - Chain Position value [n bytes] - Hash value [n bytes] - Previous Hash value [n bytes] - Nonce value [n bytes] - Difficulty Target value [n bytes] - Info Type value [n bytes] - Info Format value [n bytes] - Context Data value [n bytes] - Content Data value
This endpoint provides the current JSON configuration string for the requesting container. Containers identify themselves by their element ID, which is mapped into the container as an environment variable.
http://iofog:54321/v2/config/get
{
"status": "okay",
"config": "{\"property1\":\"value1\",\"property2\":\"value2\"}"
}
None
{
"id": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ"
// Note: The POST value is JSON and must be sent
// with HTTP header set as “Content-Type:application/json”
}
This endpoint returns a JSON array containing all of the unread messages for this container up to this point. Receiving the messages clears them from the queue so a following call to this API endpoint will not contain the same messages.
http://iofog:54321/v2/messages/next
{
"status": "okay",
"count": 2,
"messages": [
{
"id": "ObJ5STY02PMLM4XKXM8oSuPlc7mUh5Ej",
"tag": "",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"timestamp": 1452214777495,
"publisher": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "text",
"infoformat": "utf-8",
"contextdata": "",
"contentdata": "8943asefSDhdkljsafhasldkjhfdlk==wehj23"
},
{
"id": "sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
"tag": "Bosch Camera 16",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"timestamp": 1234567890123,
"publisher": "Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "image/jpeg",
"infoformat": "file/.jpg",
"contextdata": "",
"contentdata": "sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
}
]
}
None
{
"id": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ"
// Note: The POST value is JSON and must be sent with
// HTTP header set as “Content-Type:application/json”
}
This endpoing allows a container to post a message to the system. The message ID is generated inside the ioFog system, so it is not passed by the container when posting the message. The timestamp is not generated by the container, either. Both of these are returned in the repsonse from this endpoint. This prevents time synchronization problems and unique identifier problems from taking place in the container, where the language, frameworks, and code quality are unknown.
http://iofog:54321/v2/messages/new
{ "status":"okay", "timestamp":1234567890123, "id":"f9y43trfdsSDFkjhdso8y4twouhsdfksjhdf2o834wyr4we" }
None
{
"tag": "",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"publisher": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "text",
"infoformat": "utf-8",
"contextdata": "",
"contentdata": "42h3isuhsdlukhfsd==w3efakhsfdkljhafs"
// Note: The POST value is JSON and must be sent with
// HTTP header set as “Content-Type:application/json”
}
This endpoint allows a container to query for messages from any number of publishers within any timeframe. The messages will only be provided for publishers that the container is allowed to access. In other words, if a container doesn't normally receive messages from a particular publisher, then the container can try to query for messages from that publisher but it won't receive any. The message retrieval and security controls are all performed by the Message Bus module and the allowed messages are passed to the Local API to send out.
Beause of memory limitations, the Local API may only send a portion of the requested messages. The Local API will decide how many messages are appropriate to send and will return the adjusted starting and ending timeframe as illustrated in the sample response output below. The Local API will always use the starting timeframe and will adjust the ending timeframe to reflect the timestamp of the actual last message in the list.
http://iofog:54321/v2/messages/query
{
"status": "okay",
"count": 2,
"timeframestart": 1234567890123,
"timeframeend": 9876543210123,
"messages": [
{
"id": "ObJ5STY02PMLM4XKXM8oSuPlc7mUh5Ej",
"tag": "",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"timestamp": 1452214777495,
"publisher": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "text",
"infoformat": "utf-8",
"contextdata": "",
"contentdata": "wei8y43ipouwhefdskhufdslkjahsdf"
},
{
"id": "sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
"tag": "Bosch Camera 16",
"groupid": "",
"sequencenumber": 1,
"sequencetotal": 1,
"priority": 0,
"timestamp": 1234567890123,
"publisher": "Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
"authid": "",
"authgroup": "",
"version": 4,
"chainposition": 0,
"hash": "",
"previoushash": "",
"nonce": "",
"difficultytarget": 0.0,
"infotype": "image/jpeg",
"infoformat": "file/.jpg",
"contextdata": "",
"contentdata": "sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
}
]
}
None
{
"id": "R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
"timeframestart": 1234567890123,
"timeframeend": 1234567890123,
"publishers": [
"sefhuiw4984twefsdoiuhsdf",
"d895y459rwdsifuhSDFKukuewf",
"SESD984wtsdidsiusidsufgsdfkh"
]
// Note: The POST value is JSON and must be sent
// with HTTP header set as “Content-Type:application/json”
}
This endpoint opens a control Websocket connection for the container. The control commands sent over this Websocket are specified here. It is the responsibility of the container to establish this connection and ensure it is always running. If the container loses the Websocket connection, it should establish a new connection. The Local API is responsible for knowing which Websocket connection belongs to which container so that it can pass information to the appropriate recipients.
The container ID must be passed as part of the URL because otherwise it would have to be passed in the Websocket connection itself and that would make associated connections with container IDs rather difficult.
ws://iofog:54321/v2/control/socket/id/34t9whefsdfDFKjhw4tiouhwef
None - the Websocket will simply be opened successfully
id - the container ID of the container requesting the Websocket connection (example shown here as 34t9whefsdfDFKjhw4tiouhwef)
None
Standard "Ping" message (op code 9) Standard "Pong" message (op code 10) Acknowledgement message (op code 11) New container configuration available (op code 12)
Standard "Ping" message (op code 9) Standard "Pong" message (op code 10) Acknowledgement message (op code 11)
This endpoint opens a message Websocket connection for the container. The messages and other commands sent over this Websocket are specified here. It is the responsibility of the container to establish this connection and ensure it is always running. If the container loses the Websocket connection, it should establish a new connection. The Local API is responsible for knowing which Websocket connection belongs to which container so that it can pass information to the appropriate recipients.
The container ID must be passed as part of the URL because otherwise it would have to be passed in the Websocket connection itself and that would make associated connections with container IDs rather difficult.
ws://iofog:54321/v2/message/socket/id/34t9whefsdfDFKjhw4tiouhwef
None - the Websocket will simply be opened successfully
id - the container ID of the container requesting the Websocket connection (example shown here as 34t9whefsdfDFKjhw4tiouhwef)
None
Standard "Ping" message (op code 9) Standard "Pong" message (op code 10) ioMessage transmission (op code 13 followed by 4 bytes indicating the total length of the message followed by the bytes of the actual ioMessage) ioMessage receipt transmission (op code 14 followed by 4 bytes indicating the length of the response followed by the actual bytes of the response message containing the ioMessage ID and Timestamp fields with all other fields empty)
Standard "Ping" message (op code 9) Standard "Pong" message (op code 10) Acknowledgement message (op code 11) ioMessage transmission (op code 13 followed by 4 bytes indicating the total length of the message followed by the bytes of the actual ioMessage)