BOTA Recipient tutorial

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, 5000, 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:54
BotaResult BOTA_RECIPIENT_Init(BotaRecipient *botaRecipient, uint16_t port, BotaTimeFunc timeFunc, BotaOnTransferStartedCallback onTransferStarted, BotaOnTransferReceivedCallback onTransferReceived, BotaOnTransferAbortedCallback onTransferAborted)
void BOTA_RECIPIENT_Proc(BotaRecipient *botaRecipient)
void BOTA_RECIPIENT_Deinit(BotaRecipient *botaRecipient)
Definition: bota_recipient.h:159

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 size_t writeFunc(BotaMemoryAddr addr, const void* src, size_t size) {
// TODO: write 'size' bytes from the contents of 'src' to some memory under address 'addr'
return size;
}
// Function that reads destination memory
static size_t readFunc(BotaMemoryAddr addr, void* dst, size_t size) {
// TODO: read 'size' bytes from some memory under address 'addr' and write it to 'dst'
return size;
}
// Function that will be called when a new transfer is started
static BotaDestinationMemory onTransferStarted(BotaTransferId transferId, const EMBENET_IPV6* senderAddr, size_t bulkSize, const void* transferInfo, size_t transferInfoSize) {
// prepare and return a structure that describes the destination memory
destMemory.read = readFunc; // this will be our function for reading the destination memory
destMemory.write = writeFunc; // this will be our function for writing the destination memory
destMemory.capacity = 10*1024; // this should describe the capacity of the destination memory
destMemory.startAddr = 0; // this can be used to add offset in the destination memory
return destMemory;
}
// 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:51
uint16_t BotaTransferId
BOTA transfer identifier used to identify and distinguish the transfers.
Definition: bota_recipient/include/bota_defs.h:40
BotaAbortReason
Possible reasons for aborting the transfer.
Definition: bota_recipient.h:58
Definition: bota_recipient.h:46
BotaMemoryAddr startAddr
Start address in the destination bulk memory where the bulk data will be stored.
Definition: bota_recipient.h:52
BotaReadFunc read
Function to read the destination bulk memory.
Definition: bota_recipient.h:48
BotaWriteFunc write
Function to write to the destination bulk memory.
Definition: bota_recipient.h:50
size_t capacity
Capacity of the the destination bulk memory (number of bytes). This is the number of available bytes ...
Definition: bota_recipient.h:54

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.

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 size_t writeFunc(BotaMemoryAddr addr, const void* src, size_t size) {
// TODO: write 'size' bytes from the contents of 'src' to some memory under address 'addr'
return size;
}
// Function that reads destination memory
static size_t readFunc(BotaMemoryAddr addr, void* dst, size_t size) {
// TODO: read 'size' bytes from some memory under address 'addr' and write it to 'dst'
return size;
}
// Function that will be called when a new transfer is started
static BotaDestinationMemory onTransferStarted(BotaTransferId transferId, const EMBENET_IPV6* senderAddr, size_t bulkSize, const void* transferInfo, size_t transferInfoSize) {
// prepare and return a structure that describes the destination memory
destMemory.read = readFunc; // this will be our function for reading the destination memory
destMemory.write = writeFunc; // this will be our function for writing the destination memory
destMemory.capacity = 10*1024; // this should describe the capacity of the destination memory
destMemory.startAddr = 0; // this can be used to add offset in the destination memory
return destMemory;
}
// 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, 5000, 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.
Go to Top