BOTA Recipient tutorial

Table of contents Table of contents

This short tutorial shows how to use the BOTA Recipient API in order to accept BOTA transfers.

The general usage of the BOTA Recipient

BOTA API follows an object-oriented style of programming. In order to use the BOTA Recipient service we need its instance: BotaRecipient. First, the instance needs to be initialized through a call to BOTA_RECIPIENT_Init. Next, the service is handled through a repeating call to BOTA_RECIPIENT_Proc. Once the service is not needed, it can be deinitialized by a call to BOTA_RECIPIENT_Deinit.

In general the BOTA Recipient thread will look like this:

// BOTA Recipient instance
// The BOTA Recipient thread
void BotaThread(void* arg) {
// Initialize BOTA Recipient service
BOTA_RECIPIENT_Init(&botaRecipient, BOTA_DEFAULT_PORT, botaTimeFunc, onTransferStarted, onTransferReceived, onTransferAborted);
while (1) {
// Process the BOTA Recipient service
// Add some delay - in general anywhere near 10ms-100ms is sufficient
Sleep(10);
}
// Deinitialize the BOTA Recipient service
}
void BotaThread(void *arg)
Definition: bota_example_01_sender_service.c:15
BotaRecipient botaRecipient
[bota_example_11_callbacks]
Definition: bota_example_11_recipient_service.c:53
#define BOTA_DEFAULT_PORT
Default UDP port number for the BOTA service.
Definition: bota_recipient/include/bota_defs.h:96
void BOTA_RECIPIENT_Proc(BotaRecipient *botaRecipient)
BotaResult BOTA_RECIPIENT_Init(BotaRecipient *botaRecipient, uint16_t port, BotaTimeFunc timeFunc, BotaOnTransferStartedCallback onTransferStarted, BotaOnTransferReceivedCallback onTransferReceived, BotaOnTransferAbortedCallback onTransferAborted, BotaOnTransferDataMissingCallback onTransferDataMissing)
void BOTA_RECIPIENT_Deinit(BotaRecipient *botaRecipient)
Definition: bota_recipient.h:209

The above code initializes and runs the service, but we miss the definition of callbacks that are passed to the BOTA_RECIPIENT_Init function. These are described in the next section.

Callbacks

The BOTA_RECIPIENT_Init function requires two mandatory callbacks to be provided:

  • timeFunc is time provider function, that should return a 64-bit monotonically increasing value representing passing time in milliseconds
  • onTransferStarted is a callback function that will be called when new transfer is detected. The job of this callback is to decide whether the transfer should be accepted and if so - provide a description of the destination memory, where the transferred data will be stored.

In addition the function allows to hook up two other optional callbacks:

  • onTransferReceived is a callback function that will be called when the whole transfer is successfully received
  • onTransferAborted is a callback function that will be called when the transfer is aborted Though the last two callbacks are considered optional, the users are highly encouraged to implement these.

The following code includes exemplary implementation of these callbacks.

// Time provider function needed for BOTA. This function should return the number of milliseconds passing.
static uint64_t botaTimeFunc(void) {
return GetTickCount();
}
// Function that writes destination memory
static void writeFunc(BotaMemoryAddr addr, const void* src, size_t size, BotaTransferId transferId) {
// TODO: write 'size' bytes from the contents of 'src' to some memory under address 'addr'
}
// Function that reads destination memory
static void readFunc(BotaMemoryAddr addr, void* dst, size_t size, BotaTransferId transferId) {
// TODO: read 'size' bytes from some memory under address 'addr' and write it to 'dst'
}
// Function that will be called when a new transfer is started
static BotaTransferReaction onTransferStarted(BotaTransferId transferId, const EMBENET_IPV6* senderAddr, size_t bulkSize, const void* transferInfo, size_t transferInfoSize, BotaDestinationMemory* memory) {
// fill the structure that describes the destination memory
memory->read = readFunc; // this will be our function for reading the destination memory
memory->write = writeFunc; // this will be our function for writing the destination memory
memory->capacity = 10 * 1024; // this should describe the capacity of the destination memory
memory->startAddr = 0; // this can be used to add offset in the destination memory
// always accept the transfer
}
// Function that will be called when a transfer is received
static void onTransferReceived(BotaTransferId transferId, const BotaDestinationMemory* memory, size_t size, const void* transferInfo, size_t transferInfoSize) {
// the whole transfer was received - memory holds the received bulk data
puts("Transfer was received");
}
// Function that will be called when a transfer is aborted
static void onTransferAborted(BotaTransferId transferId, const BotaDestinationMemory* memory, size_t size, const void* transferInfo, size_t transferInfoSize, BotaAbortReason abortReason) {
// the transfer was aborted - abortReason informs about the actual cause
puts("Transfer was aborted");
}
uint32_t BotaMemoryAddr
Address in the Bulk memory.
Definition: bota_recipient/include/bota_defs.h:106
uint16_t BotaTransferId
BOTA transfer identifier used to identify and distinguish the transfers.
Definition: bota_recipient/include/bota_defs.h:78
BotaTransferReaction
Possible recipient reactions to a new BOTA transfer.
Definition: bota_recipient/include/bota_defs.h:145
@ BOTA_TRANSFER_REACTION_ACCEPT
The BOTA transfer should be accepted.
Definition: bota_recipient/include/bota_defs.h:147
BotaAbortReason
Possible reasons for aborting the transfer.
Definition: bota_recipient.h:43
Definition: bota_recipient.h:31
BotaMemoryAddr startAddr
Start address in the destination bulk memory where the bulk data will be stored.
Definition: bota_recipient.h:37
BotaReadFunc read
Function to read the destination bulk memory.
Definition: bota_recipient.h:33
BotaWriteFunc write
Function to write to the destination bulk memory.
Definition: bota_recipient.h:35
size_t capacity
Capacity of the the destination bulk memory (number of bytes). This is the number of available bytes ...
Definition: bota_recipient.h:39

Accepting or rejecting the transfer

When a new transfer is initiated by the sender and it reaches the recipient, the node decides whether it accepts the transfer or not. This is done in the onTransferStarted callback and the decision is signaled through the returned BotaDestinationMemory structure. If the BotaDestinationMemory::capacity is larger or equal the requested transfer size then the transfer is accepted. Otherwise the transfer is rejected. Thus, to reject the transfer explicitly one can set this field to zero.

A common practice is to accept transfers only from a pre-defined node (such as the Border Router) and reject others.

A transfer may optionally include additional user-defined information describing it. This information is given to the BOTA sender when the transfer is started and is later passed to the recipients. This mechanism may also be used to help to decide whether to accept or reject the transfer.

End of the transfer

The transfer will always end with a call to either onTransferReceived or onTransferAborted callback. If onTransferReceived is called this means that the transfer was successfully received and bulk data is stored in the destination memory. If onTransferAborted is called then the transfer was aborted. The callback includes information about the cause of the abort decision.

Aborting the transfer

The user can decide to abort the ongoing transfer using BOTA_RECIPIENT_AbortTransfer call. This function can also be called from within the BotaDestinationMemory::write and BotaDestinationMemory::read callbacks. This function can be used from other callbacks such as onTransferStarted as long as it aborts some other transfer, and not the one the given callback refers to. Aborting the transfer from its own onTransferStarted, onTransferReceived or onTransferAborted callback makes no sense and is not supported. Calling this function aborts the transfer and invokes the onTransferAborted callback (if it was provided).

Working example

The working example is provided below.

#include "bota_recipient.h" // the one and only header file needed to use the BOTA Recipient service
#include <stdio.h>
// Time provider function needed for BOTA. This function should return the number of milliseconds passing.
static uint64_t botaTimeFunc(void) {
return GetTickCount();
}
// Function that writes destination memory
static void writeFunc(BotaMemoryAddr addr, const void* src, size_t size, BotaTransferId transferId) {
// TODO: write 'size' bytes from the contents of 'src' to some memory under address 'addr'
}
// Function that reads destination memory
static void readFunc(BotaMemoryAddr addr, void* dst, size_t size, BotaTransferId transferId) {
// TODO: read 'size' bytes from some memory under address 'addr' and write it to 'dst'
}
// Function that will be called when a new transfer is started
static BotaTransferReaction onTransferStarted(BotaTransferId transferId, const EMBENET_IPV6* senderAddr, size_t bulkSize, const void* transferInfo, size_t transferInfoSize, BotaDestinationMemory* memory) {
// fill the structure that describes the destination memory
memory->read = readFunc; // this will be our function for reading the destination memory
memory->write = writeFunc; // this will be our function for writing the destination memory
memory->capacity = 10 * 1024; // this should describe the capacity of the destination memory
memory->startAddr = 0; // this can be used to add offset in the destination memory
// always accept the transfer
}
// Function that will be called when a transfer is received
static void onTransferReceived(BotaTransferId transferId, const BotaDestinationMemory* memory, size_t size, const void* transferInfo, size_t transferInfoSize) {
// the whole transfer was received - memory holds the received bulk data
puts("Transfer was received");
}
// Function that will be called when a transfer is aborted
static void onTransferAborted(BotaTransferId transferId, const BotaDestinationMemory* memory, size_t size, const void* transferInfo, size_t transferInfoSize, BotaAbortReason abortReason) {
// the transfer was aborted - abortReason informs about the actual cause
puts("Transfer was aborted");
}
// BOTA Recipient instance
// The BOTA Recipient thread
void BotaThread(void* arg) {
// Initialize BOTA Recipient service
BOTA_RECIPIENT_Init(&botaRecipient, BOTA_DEFAULT_PORT, botaTimeFunc, onTransferStarted, onTransferReceived, onTransferAborted);
while (1) {
// Process the BOTA Recipient service
// Add some delay - in general anywhere near 10ms-100ms is sufficient
Sleep(10);
}
// Deinitialize the BOTA Recipient service
}
BOTA service for Node.

The flow of the transfer process

Finally let's summarize the flow of the transfer reception.

  1. The BOTA Recipient service is initiated by a call to BOTA_RECIPIENT_Init
  2. The service is then processed through periodic calls to BOTA_RECIPIENT_Proc
  3. The BOTA Sender sends request to start a transfer. This invokes the onTransferStarted callback in the recipient.
  4. The Recipient decides if it accepts the transfer or not. If it does, it prepares the descriptor of destination memory telling how to use the destination memory.
  5. If the transfer is accepted, the sender starts to send bulk data in portions. Each received data portion is written to the destination memory in the recipient.
  6. At the end of the transfer the sender initiates a validation procedure, where the CRC of the received data is compared with the expected CRC generated by the sender. If the CRCs math then both parties know that the whole data transfer was successful. The recipient invokes the onTranferFinished callback which ends the transmission in the recipient.
  7. If at any time during the transfer the recipient service decides that the transfer should be aborted, the onTransferAborted callback is called with the description of the transfer and the reason for the abort decision.