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:
void BotaThread(void *arg) {
onTransferDataMissingCallback);
while(1) {
DO_SLEEP(10);
}
}
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.
static uint64_t botaTimeFunc(void) { return GetTickCount(); }
}
}
memory->
write = writeFunc;
}
size_t transferInfoSize) {
puts("Transfer was received");
}
puts("Transfer was aborted");
}
static void onTransferDataMissingCallback(
BotaTransferId transferId,
size_t receivedSize,
size_t totalSize,
void const *transferInfo,
size_t transferInfoSize) {
puts("Transfer missing too much data");
}
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 <stdio.h>
static uint64_t botaTimeFunc(void) { return GetTickCount(); }
}
}
memory->
write = writeFunc;
}
size_t transferInfoSize) {
puts("Transfer was received");
}
puts("Transfer was aborted");
}
static void onTransferDataMissingCallback(
BotaTransferId transferId,
size_t receivedSize,
size_t totalSize,
void const *transferInfo,
size_t transferInfoSize) {
puts("Transfer missing too much data");
}
void BotaThread(void *arg) {
onTransferDataMissingCallback);
while(1) {
DO_SLEEP(10);
}
}
#define BOTA_DEFAULT_PORT
Default UDP port number for the BOTA service.
Definition bota_defs.h:109
uint32_t BotaMemoryAddr
Address in the Bulk memory.
Definition bota_defs.h:119
uint16_t BotaTransferId
BOTA transfer identifier used to identify and distinguish the transfers.
Definition bota_defs.h:91
BotaTransferReaction
Possible recipient reactions to a new BOTA transfer.
Definition bota_defs.h:158
@ BOTA_TRANSFER_REACTION_ACCEPT
The BOTA transfer should be accepted.
Definition bota_defs.h:160
BotaAbortReason
Possible reasons for aborting the transfer.
Definition bota_recipient.h:41
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:28
BotaMemoryAddr startAddr
Start address in the destination bulk memory where the bulk data will be stored.
Definition bota_recipient.h:34
BotaReadFunc read
Function to read the destination bulk memory.
Definition bota_recipient.h:30
BotaWriteFunc write
Function to write to the destination bulk memory.
Definition bota_recipient.h:32
size_t capacity
Definition bota_recipient.h:37
Definition bota_recipient.h:205
The flow of the transfer process
Finally let's summarize the flow of the transfer reception.
- The BOTA Recipient service is initiated by a call to BOTA_RECIPIENT_Init
- The service is then processed through periodic calls to BOTA_RECIPIENT_Proc
- The BOTA Sender sends request to start a transfer. This invokes the onTransferStarted callback in the recipient.
- 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.
- 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.
- 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.
- 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.