820 lines
23 KiB
C
820 lines
23 KiB
C
|
|
/*
|
||
|
|
* Copyright 2015-18 Hillcrest Laboratories, Inc.
|
||
|
|
*
|
||
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
* you may not use this file except in compliance with the License and
|
||
|
|
* any applicable agreements you may have with Hillcrest Laboratories, Inc.
|
||
|
|
* You may obtain a copy of the License at
|
||
|
|
*
|
||
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
*
|
||
|
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
* See the License for the specific language governing permissions and
|
||
|
|
* limitations under the License.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Hillcrest Sensor Hub Transport Protocol (SHTP) API
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "shtp.h"
|
||
|
|
#include "sh2_err.h"
|
||
|
|
#include "sh2_util.h"
|
||
|
|
|
||
|
|
#include <string.h>
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------------
|
||
|
|
// Private types
|
||
|
|
|
||
|
|
#define SH2_MAX_APPS (5)
|
||
|
|
#define SHTP_APP_NAME_LEN (32)
|
||
|
|
#define SH2_MAX_CHANS (8)
|
||
|
|
#define SHTP_CHAN_NAME_LEN (32)
|
||
|
|
|
||
|
|
// Defined Globally Unique Identifiers
|
||
|
|
#define GUID_SHTP (0)
|
||
|
|
|
||
|
|
// Command Channel commands and parameters
|
||
|
|
#define SHTP_CHAN_COMMAND (0)
|
||
|
|
#define CMD_ADVERTISE (0)
|
||
|
|
#define CMD_ADVERTISE_SHTP (0)
|
||
|
|
#define CMD_ADVERTISE_ALL (1)
|
||
|
|
#define RESP_ADVERTISE (0)
|
||
|
|
|
||
|
|
#define SHTP_HDR_LEN (4)
|
||
|
|
|
||
|
|
#define TAG_SHTP_VERSION 0x80
|
||
|
|
|
||
|
|
typedef struct shtp_App_s {
|
||
|
|
uint32_t guid;
|
||
|
|
char appName[SHTP_APP_NAME_LEN];
|
||
|
|
} shtp_App_t;
|
||
|
|
|
||
|
|
typedef struct shtp_AppListener_s {
|
||
|
|
uint16_t guid;
|
||
|
|
shtp_AdvertCallback_t *callback;
|
||
|
|
void *cookie;
|
||
|
|
} shtp_AppListener_t;
|
||
|
|
|
||
|
|
typedef struct shtp_ChanListener_s {
|
||
|
|
uint16_t guid;
|
||
|
|
char chanName[SHTP_CHAN_NAME_LEN];
|
||
|
|
shtp_Callback_t *callback;
|
||
|
|
void *cookie;
|
||
|
|
} shtp_ChanListener_t;
|
||
|
|
|
||
|
|
typedef struct shtp_Channel_s {
|
||
|
|
uint8_t nextOutSeq;
|
||
|
|
uint8_t nextInSeq;
|
||
|
|
uint32_t guid; // app id
|
||
|
|
char chanName[SHTP_CHAN_NAME_LEN];
|
||
|
|
bool wake;
|
||
|
|
shtp_Callback_t *callback;
|
||
|
|
void *cookie;
|
||
|
|
} shtp_Channel_t;
|
||
|
|
|
||
|
|
typedef enum {
|
||
|
|
ADVERT_NEEDED,
|
||
|
|
ADVERT_REQUESTED,
|
||
|
|
ADVERT_IDLE,
|
||
|
|
} advert_phase_t;
|
||
|
|
|
||
|
|
// Per-instance data for SHTP
|
||
|
|
typedef struct shtp_s {
|
||
|
|
// Associated SHTP HAL
|
||
|
|
// If 0, this indicates the SHTP instance is available for new opens
|
||
|
|
sh2_Hal_t *pHal;
|
||
|
|
|
||
|
|
// Asynchronous Event callback and it's cookie
|
||
|
|
shtp_EventCallback_t *eventCallback;
|
||
|
|
void * eventCookie;
|
||
|
|
|
||
|
|
// Data from adverts
|
||
|
|
char shtpVersion[8];
|
||
|
|
uint16_t outMaxPayload;
|
||
|
|
uint16_t outMaxTransfer;
|
||
|
|
|
||
|
|
// Transmit support
|
||
|
|
uint8_t outTransfer[SH2_HAL_MAX_TRANSFER_OUT];
|
||
|
|
|
||
|
|
// Receive support
|
||
|
|
uint16_t inMaxTransfer;
|
||
|
|
uint16_t inRemaining;
|
||
|
|
uint8_t inChan;
|
||
|
|
uint8_t inPayload[SH2_HAL_MAX_PAYLOAD_IN];
|
||
|
|
uint16_t inCursor;
|
||
|
|
uint32_t inTimestamp;
|
||
|
|
uint8_t inTransfer[SH2_HAL_MAX_TRANSFER_IN];
|
||
|
|
|
||
|
|
// What stage of advertisement processing are we in.
|
||
|
|
advert_phase_t advertPhase;
|
||
|
|
|
||
|
|
// Applications
|
||
|
|
shtp_App_t app[SH2_MAX_APPS];
|
||
|
|
uint8_t nextApp;
|
||
|
|
|
||
|
|
// Advert registrations
|
||
|
|
uint8_t nextAppListener;
|
||
|
|
shtp_AppListener_t appListener[SH2_MAX_APPS];
|
||
|
|
|
||
|
|
// SHTP Channels
|
||
|
|
shtp_Channel_t chan[SH2_MAX_CHANS];
|
||
|
|
|
||
|
|
// Channel listeners
|
||
|
|
shtp_ChanListener_t chanListener[SH2_MAX_CHANS];
|
||
|
|
uint8_t nextChanListener;
|
||
|
|
|
||
|
|
// Stats
|
||
|
|
uint32_t txDiscards;
|
||
|
|
uint32_t shortFragments;
|
||
|
|
uint32_t tooLargePayloads;
|
||
|
|
uint32_t badRxChan;
|
||
|
|
uint32_t badTxChan;
|
||
|
|
|
||
|
|
} shtp_t;
|
||
|
|
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------------
|
||
|
|
// Private data
|
||
|
|
|
||
|
|
// Advertisement request
|
||
|
|
static const uint8_t advertise[] = {
|
||
|
|
CMD_ADVERTISE,
|
||
|
|
CMD_ADVERTISE_ALL
|
||
|
|
};
|
||
|
|
|
||
|
|
#define MAX_INSTANCES (1)
|
||
|
|
static shtp_t instances[MAX_INSTANCES];
|
||
|
|
|
||
|
|
static bool shtp_initialized = false;
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------------
|
||
|
|
// Private functions
|
||
|
|
|
||
|
|
static void shtp_init(void)
|
||
|
|
{
|
||
|
|
// clear instance memory.
|
||
|
|
// In particular, this clears the pHal pointers which are used
|
||
|
|
// to determine if an instance is open and in-use.
|
||
|
|
memset(instances, 0, sizeof(instances));
|
||
|
|
|
||
|
|
// Set the initialized flag so this doesn't happen again.
|
||
|
|
shtp_initialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static shtp_t *getInstance(void)
|
||
|
|
{
|
||
|
|
for (int n = 0; n < MAX_INSTANCES; n++) {
|
||
|
|
if (instances[n].pHal == 0) {
|
||
|
|
// This instance is free
|
||
|
|
return &instances[n];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Can't give an instance, none are free
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Register a listener for an app (advertisement listener)
|
||
|
|
static void addAdvertListener(shtp_t *pShtp, uint16_t guid,
|
||
|
|
shtp_AdvertCallback_t *callback, void * cookie)
|
||
|
|
{
|
||
|
|
shtp_AppListener_t *pAppListener = 0;
|
||
|
|
|
||
|
|
// Bail out if no space for more apps
|
||
|
|
if (pShtp->nextAppListener >= SH2_MAX_APPS) return;
|
||
|
|
|
||
|
|
// Register this app
|
||
|
|
pAppListener = &pShtp->appListener[pShtp->nextAppListener];
|
||
|
|
pShtp->nextAppListener++;
|
||
|
|
pAppListener->guid = guid;
|
||
|
|
pAppListener->callback = callback;
|
||
|
|
pAppListener->cookie = cookie;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Try to match registered listeners with their channels.
|
||
|
|
// This is performed every time the underlying Channel, App, Listener data structures are updated.
|
||
|
|
// As a result, channel number to callback association is fast when receiving packets
|
||
|
|
static void updateCallbacks(shtp_t *pShtp)
|
||
|
|
{
|
||
|
|
// Figure out which callback is associated with each channel.
|
||
|
|
// Channel -> (GUID, Chan name).
|
||
|
|
// GUID -> App name.
|
||
|
|
// (App name, Chan name) -> Callback
|
||
|
|
|
||
|
|
uint32_t guid;
|
||
|
|
const char * chanName = 0;
|
||
|
|
|
||
|
|
for (int chanNo = 0; chanNo < SH2_MAX_CHANS; chanNo++) {
|
||
|
|
// Reset callback for this channel until we find the right one.
|
||
|
|
pShtp->chan[chanNo].callback = 0;
|
||
|
|
|
||
|
|
if (pShtp->chan[chanNo].guid == 0xFFFFFFFF) {
|
||
|
|
// This channel entry not used.
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get GUID and Channel Name for this channel
|
||
|
|
guid = pShtp->chan[chanNo].guid;
|
||
|
|
chanName = pShtp->chan[chanNo].chanName;
|
||
|
|
|
||
|
|
// Look for a listener registered with this guid, channel name
|
||
|
|
for (int listenerNo = 0; listenerNo < SH2_MAX_CHANS; listenerNo++)
|
||
|
|
{
|
||
|
|
if ((pShtp->chanListener[listenerNo].callback != 0) &&
|
||
|
|
(pShtp->chanListener[listenerNo].guid == guid) &&
|
||
|
|
(strcmp(chanName, pShtp->chanListener[listenerNo].chanName) == 0))
|
||
|
|
{
|
||
|
|
|
||
|
|
// This listener is the one for this channel
|
||
|
|
pShtp->chan[chanNo].callback = pShtp->chanListener[listenerNo].callback;
|
||
|
|
pShtp->chan[chanNo].cookie = pShtp->chanListener[listenerNo].cookie;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Register a new channel listener
|
||
|
|
static int addChanListener(shtp_t *pShtp,
|
||
|
|
uint16_t guid, const char * chanName,
|
||
|
|
shtp_Callback_t *callback, void *cookie)
|
||
|
|
{
|
||
|
|
shtp_ChanListener_t *pListener = 0;
|
||
|
|
|
||
|
|
// Bail out if there are too many listeners registered
|
||
|
|
if (pShtp->nextChanListener >= SH2_MAX_CHANS) return SH2_ERR;
|
||
|
|
|
||
|
|
// Register channel listener
|
||
|
|
pListener = &pShtp->chanListener[pShtp->nextChanListener];
|
||
|
|
pShtp->nextChanListener++;
|
||
|
|
pListener->guid = guid;
|
||
|
|
strcpy(pListener->chanName, chanName);
|
||
|
|
pListener->callback = callback;
|
||
|
|
pListener->cookie = cookie;
|
||
|
|
|
||
|
|
// re-evaluate channel callbacks
|
||
|
|
updateCallbacks(pShtp);
|
||
|
|
|
||
|
|
return SH2_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline uint16_t min_u16(uint16_t a, uint16_t b)
|
||
|
|
{
|
||
|
|
if (a < b) {
|
||
|
|
return a;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return b;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send a cargo as a sequence of transports
|
||
|
|
static int txProcess(shtp_t *pShtp, uint8_t chan, const uint8_t* pData, uint32_t len)
|
||
|
|
{
|
||
|
|
int status = SH2_OK;
|
||
|
|
|
||
|
|
bool continuation = false;
|
||
|
|
uint16_t cursor = 0;
|
||
|
|
uint16_t remaining;
|
||
|
|
uint16_t transferLen; // length of transfer, minus the header
|
||
|
|
uint16_t lenField;
|
||
|
|
|
||
|
|
cursor = 0;
|
||
|
|
remaining = len;
|
||
|
|
while (remaining > 0) {
|
||
|
|
// How much data (not header) can we send in next transfer
|
||
|
|
transferLen = min_u16(remaining, pShtp->outMaxTransfer-SHTP_HDR_LEN);
|
||
|
|
|
||
|
|
// Length field will be transferLen + SHTP_HDR_LEN
|
||
|
|
lenField = transferLen + SHTP_HDR_LEN;
|
||
|
|
|
||
|
|
// Put the header in the out buffer
|
||
|
|
pShtp->outTransfer[0] = lenField & 0xFF;
|
||
|
|
pShtp->outTransfer[1] = (lenField >> 8) & 0xFF;
|
||
|
|
if (continuation) {
|
||
|
|
pShtp->outTransfer[1] |= 0x80;
|
||
|
|
}
|
||
|
|
pShtp->outTransfer[2] = chan;
|
||
|
|
pShtp->outTransfer[3] = pShtp->chan[chan].nextOutSeq++;
|
||
|
|
|
||
|
|
// Stage one tranfer in the out buffer
|
||
|
|
memcpy(pShtp->outTransfer+SHTP_HDR_LEN, pData+cursor, transferLen);
|
||
|
|
remaining -= transferLen;
|
||
|
|
cursor += transferLen;
|
||
|
|
|
||
|
|
// Transmit (try repeatedly while HAL write returns 0)
|
||
|
|
status = pShtp->pHal->write(pShtp->pHal, pShtp->outTransfer, lenField);
|
||
|
|
while (status == 0)
|
||
|
|
{
|
||
|
|
shtp_service(pShtp);
|
||
|
|
status = pShtp->pHal->write(pShtp->pHal, pShtp->outTransfer, lenField);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (status < 0)
|
||
|
|
{
|
||
|
|
// Error, throw away this cargo
|
||
|
|
pShtp->txDiscards++;
|
||
|
|
return status;
|
||
|
|
}
|
||
|
|
|
||
|
|
// For the rest of this transmission, packets are continuations.
|
||
|
|
continuation = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
return SH2_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Callback for SHTP app-specific advertisement tags
|
||
|
|
static void shtpAdvertHdlr(void *cookie, uint8_t tag, uint8_t len, uint8_t *val)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)cookie;
|
||
|
|
|
||
|
|
switch (tag) {
|
||
|
|
case TAG_SHTP_VERSION:
|
||
|
|
if (strlen((const char *)val) < sizeof(pShtp->shtpVersion)) {
|
||
|
|
strcpy(pShtp->shtpVersion, (const char *)val);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add one to the set of known Apps
|
||
|
|
static void addApp(shtp_t *pShtp, uint32_t guid)
|
||
|
|
{
|
||
|
|
shtp_App_t *pApp = 0;
|
||
|
|
|
||
|
|
// Bail out if this GUID is already registered
|
||
|
|
for (int n = 0; n < pShtp->nextApp; n++) {
|
||
|
|
if (pShtp->app[n].guid == guid) return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bail out if no space for more apps
|
||
|
|
if (pShtp->nextApp >= SH2_MAX_APPS) return;
|
||
|
|
|
||
|
|
// Register this app
|
||
|
|
pApp = &pShtp->app[pShtp->nextApp];
|
||
|
|
pShtp->nextApp++;
|
||
|
|
pApp->guid = guid;
|
||
|
|
strcpy(pApp->appName, "");
|
||
|
|
|
||
|
|
// Re-evaluate channel callbacks
|
||
|
|
updateCallbacks(pShtp);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void setAppName(shtp_t *pShtp, uint32_t guid, const char * appName)
|
||
|
|
{
|
||
|
|
shtp_App_t *pApp = 0;
|
||
|
|
|
||
|
|
// Find the app entry with this GUID
|
||
|
|
for (unsigned n = 0; n < pShtp->nextApp; n++) {
|
||
|
|
if (pShtp->app[n].guid == guid) {
|
||
|
|
pApp = &pShtp->app[n];
|
||
|
|
strcpy(pApp->appName, appName);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add one to the set of known channels
|
||
|
|
static void addChannel(shtp_t *pShtp, uint8_t chanNo, uint32_t guid, const char * chanName, bool wake)
|
||
|
|
{
|
||
|
|
if (chanNo >= SH2_MAX_CHANS) return;
|
||
|
|
|
||
|
|
shtp_Channel_t * pChan = &pShtp->chan[chanNo];
|
||
|
|
|
||
|
|
// Store channel definition
|
||
|
|
pChan->guid = guid;
|
||
|
|
strcpy(pChan->chanName, chanName);
|
||
|
|
pChan->wake = wake;
|
||
|
|
|
||
|
|
// Init channel-associated data
|
||
|
|
pChan->nextOutSeq = 0;
|
||
|
|
pChan->nextInSeq = 0;
|
||
|
|
pChan->callback = 0;
|
||
|
|
pChan->cookie = 0;
|
||
|
|
|
||
|
|
// Re-evaluate channel callbacks
|
||
|
|
updateCallbacks(pShtp);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void callAdvertHandler(shtp_t *pShtp, uint32_t guid,
|
||
|
|
uint8_t tag, uint8_t len, uint8_t *val)
|
||
|
|
{
|
||
|
|
// Find listener for this app
|
||
|
|
for (int n = 0; n < SH2_MAX_APPS; n++)
|
||
|
|
{
|
||
|
|
if (pShtp->appListener[n].guid == guid) {
|
||
|
|
// Found matching App entry
|
||
|
|
if (pShtp->appListener[n].callback != 0) {
|
||
|
|
pShtp->appListener[n].callback(pShtp->appListener[n].cookie, tag, len, val);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void processAdvertisement(shtp_t *pShtp, uint8_t *payload, uint16_t payloadLen)
|
||
|
|
{
|
||
|
|
uint16_t x;
|
||
|
|
uint8_t tag;
|
||
|
|
uint8_t len;
|
||
|
|
uint8_t *val;
|
||
|
|
uint16_t cursor = 1;
|
||
|
|
uint32_t guid = 0;
|
||
|
|
char appName[SHTP_APP_NAME_LEN];
|
||
|
|
char chanName[SHTP_CHAN_NAME_LEN];
|
||
|
|
uint8_t chanNo = 0;
|
||
|
|
bool wake = false;
|
||
|
|
|
||
|
|
strcpy(appName, "");
|
||
|
|
strcpy(chanName, "");
|
||
|
|
|
||
|
|
pShtp->advertPhase = ADVERT_IDLE;
|
||
|
|
|
||
|
|
while (cursor < payloadLen) {
|
||
|
|
tag = payload[cursor++];
|
||
|
|
len = payload[cursor++];
|
||
|
|
val = payload+cursor;
|
||
|
|
cursor += len;
|
||
|
|
|
||
|
|
// Process tag
|
||
|
|
switch (tag) {
|
||
|
|
case TAG_NULL:
|
||
|
|
// Reserved value, not a valid tag.
|
||
|
|
break;
|
||
|
|
case TAG_GUID:
|
||
|
|
// A new GUID is being established so terminate advertisement process with earlier app, if any.
|
||
|
|
callAdvertHandler(pShtp, guid, TAG_NULL, 0, 0);
|
||
|
|
|
||
|
|
guid = readu32(val);
|
||
|
|
addApp(pShtp, guid);
|
||
|
|
|
||
|
|
strcpy(appName, "");
|
||
|
|
strcpy(chanName, "");
|
||
|
|
break;
|
||
|
|
case TAG_MAX_CARGO_PLUS_HEADER_WRITE:
|
||
|
|
x = readu16(val) - SHTP_HDR_LEN;
|
||
|
|
|
||
|
|
if (x < SH2_HAL_MAX_PAYLOAD_OUT) {
|
||
|
|
pShtp->outMaxPayload = x;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case TAG_MAX_CARGO_PLUS_HEADER_READ:
|
||
|
|
x = readu16(val) - SHTP_HDR_LEN;
|
||
|
|
// No need to store this!
|
||
|
|
break;
|
||
|
|
case TAG_MAX_TRANSFER_WRITE:
|
||
|
|
x = readu16(val) - SHTP_HDR_LEN;
|
||
|
|
if (x < SH2_HAL_MAX_TRANSFER_OUT) {
|
||
|
|
pShtp->outMaxTransfer = x;
|
||
|
|
} else {
|
||
|
|
pShtp->outMaxTransfer = SH2_HAL_MAX_TRANSFER_OUT;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case TAG_MAX_TRANSFER_READ:
|
||
|
|
x = readu16(val) - SHTP_HDR_LEN;
|
||
|
|
if (x < SH2_HAL_MAX_TRANSFER_IN) {
|
||
|
|
pShtp->inMaxTransfer = x;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case TAG_NORMAL_CHANNEL:
|
||
|
|
chanNo = readu8(val);
|
||
|
|
wake = false;
|
||
|
|
break;
|
||
|
|
case TAG_WAKE_CHANNEL:
|
||
|
|
chanNo = readu8(val);
|
||
|
|
wake = true;
|
||
|
|
break;
|
||
|
|
case TAG_APP_NAME:
|
||
|
|
strcpy(appName, (const char *)val);
|
||
|
|
setAppName(pShtp, guid, appName);
|
||
|
|
|
||
|
|
break;
|
||
|
|
case TAG_CHANNEL_NAME:
|
||
|
|
strcpy(chanName, (const char *)val);
|
||
|
|
addChannel(pShtp, chanNo, guid, (const char *)val, wake);
|
||
|
|
|
||
|
|
// Store channel metadata
|
||
|
|
if (chanNo < SH2_MAX_CHANS) {
|
||
|
|
pShtp->chan[chanNo].guid = guid;
|
||
|
|
strcpy(pShtp->chan[chanNo].chanName, chanName);
|
||
|
|
pShtp->chan[chanNo].wake = wake;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case TAG_ADV_COUNT:
|
||
|
|
// Not yet supported.
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
// Nothing special needs to be done with this tag.
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Deliver a TLV entry to the app's handler
|
||
|
|
callAdvertHandler(pShtp, guid, tag, len, val);
|
||
|
|
}
|
||
|
|
|
||
|
|
// terminate advertisement process with last app
|
||
|
|
callAdvertHandler(pShtp, guid, TAG_NULL, 0, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Callback for SHTP command channel
|
||
|
|
static void shtpCmdListener(void *cookie, uint8_t *payload, uint16_t len, uint32_t timestamp)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)cookie;
|
||
|
|
|
||
|
|
if ((payload == 0) || (len == 0)) return;
|
||
|
|
|
||
|
|
uint8_t response = payload[0];
|
||
|
|
|
||
|
|
switch (response) {
|
||
|
|
case RESP_ADVERTISE:
|
||
|
|
processAdvertisement(pShtp, payload, len);
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
// unknown response
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void rxAssemble(shtp_t *pShtp, uint8_t *in, uint16_t len, uint32_t t_us)
|
||
|
|
{
|
||
|
|
uint16_t payloadLen;
|
||
|
|
bool continuation;
|
||
|
|
uint8_t chan = 0;
|
||
|
|
uint8_t seq = 0;
|
||
|
|
|
||
|
|
// discard invalid short fragments
|
||
|
|
if (len < SHTP_HDR_LEN) {
|
||
|
|
pShtp->shortFragments++;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Interpret header fields
|
||
|
|
payloadLen = (in[0] + (in[1] << 8)) & (~0x8000);
|
||
|
|
continuation = ((in[1] & 0x80) != 0);
|
||
|
|
chan = in[2];
|
||
|
|
seq = in[3];
|
||
|
|
|
||
|
|
if (payloadLen < SHTP_HDR_LEN) {
|
||
|
|
pShtp->shortFragments++;
|
||
|
|
|
||
|
|
if (pShtp->eventCallback) {
|
||
|
|
pShtp->eventCallback(pShtp->eventCookie, SHTP_SHORT_FRAGMENT);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((chan >= SH2_MAX_CHANS) ||
|
||
|
|
(chan >= pShtp->nextChanListener)) {
|
||
|
|
// Invalid channel id.
|
||
|
|
pShtp->badRxChan++;
|
||
|
|
|
||
|
|
if (pShtp->eventCallback) {
|
||
|
|
pShtp->eventCallback(pShtp->eventCookie, SHTP_BAD_RX_CHAN);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Discard earlier assembly in progress if the received data doesn't match it.
|
||
|
|
if (pShtp->inRemaining) {
|
||
|
|
// Check this against previously received data.
|
||
|
|
if (!continuation ||
|
||
|
|
(chan != pShtp->inChan) ||
|
||
|
|
(seq != pShtp->chan[chan].nextInSeq)) {
|
||
|
|
// This fragment doesn't fit with previous one, discard earlier data
|
||
|
|
pShtp->inRemaining = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pShtp->inRemaining == 0) {
|
||
|
|
if (payloadLen > sizeof(pShtp->inPayload)) {
|
||
|
|
// Error: This payload won't fit! Discard it.
|
||
|
|
pShtp->tooLargePayloads++;
|
||
|
|
|
||
|
|
if (pShtp->eventCallback) {
|
||
|
|
pShtp->eventCallback(pShtp->eventCookie, SHTP_TOO_LARGE_PAYLOADS);
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// This represents a new payload
|
||
|
|
|
||
|
|
// Store timestamp
|
||
|
|
pShtp->inTimestamp = t_us;
|
||
|
|
|
||
|
|
// Start a new assembly.
|
||
|
|
pShtp->inCursor = 0;
|
||
|
|
pShtp->inChan = chan;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Append the new fragment to the payload under construction.
|
||
|
|
if (len > payloadLen) {
|
||
|
|
// Only use the valid portion of the transfer
|
||
|
|
len = payloadLen;
|
||
|
|
}
|
||
|
|
memcpy(pShtp->inPayload + pShtp->inCursor, in+SHTP_HDR_LEN, len-SHTP_HDR_LEN);
|
||
|
|
pShtp->inCursor += len-SHTP_HDR_LEN;
|
||
|
|
pShtp->inRemaining = payloadLen - len;
|
||
|
|
|
||
|
|
// If whole payload received, deliver it to channel listener.
|
||
|
|
if (pShtp->inRemaining == 0) {
|
||
|
|
|
||
|
|
// Call callback if there is one.
|
||
|
|
if (pShtp->chan[chan].callback != 0) {
|
||
|
|
pShtp->chan[chan].callback(pShtp->chan[chan].cookie,
|
||
|
|
pShtp->inPayload, pShtp->inCursor,
|
||
|
|
pShtp->inTimestamp);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remember next sequence number we expect for this channel.
|
||
|
|
pShtp->chan[chan].nextInSeq = seq + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------------
|
||
|
|
// Public functions
|
||
|
|
|
||
|
|
// Takes HAL pointer, returns shtp ID for use in future calls.
|
||
|
|
// HAL will be opened by this call.
|
||
|
|
void *shtp_open(sh2_Hal_t *pHal)
|
||
|
|
{
|
||
|
|
if (!shtp_initialized) {
|
||
|
|
// Perform one-time module initialization
|
||
|
|
shtp_init();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate params
|
||
|
|
if (pHal == 0) {
|
||
|
|
// Error
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find an available instance for this open
|
||
|
|
shtp_t *pShtp = getInstance();
|
||
|
|
if (pShtp == 0) {
|
||
|
|
// No instances available, return error
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear the SHTP instance as a shortcut to initializing all fields
|
||
|
|
memset(pShtp, 0, sizeof(shtp_t));
|
||
|
|
|
||
|
|
// Store reference to the HAL
|
||
|
|
pShtp->pHal = pHal;
|
||
|
|
|
||
|
|
// Clear the asynchronous event callback point
|
||
|
|
pShtp->eventCallback = 0;
|
||
|
|
pShtp->eventCookie = 0;
|
||
|
|
|
||
|
|
// Initialize state vars (be prepared for adverts)
|
||
|
|
pShtp->outMaxPayload = SH2_HAL_MAX_PAYLOAD_OUT;
|
||
|
|
pShtp->outMaxTransfer = SH2_HAL_MAX_TRANSFER_OUT;
|
||
|
|
|
||
|
|
// Establish SHTP App and command channel a priori
|
||
|
|
addApp(pShtp, GUID_SHTP);
|
||
|
|
addChannel(pShtp, 0, GUID_SHTP, "command", false);
|
||
|
|
|
||
|
|
// Register SHTP advert listener and command channel listener
|
||
|
|
shtp_listenAdvert(pShtp, GUID_SHTP, shtpAdvertHdlr, pShtp);
|
||
|
|
shtp_listenChan(pShtp, GUID_SHTP, "command", shtpCmdListener, pShtp);
|
||
|
|
|
||
|
|
// When we open the HAL, it resets the device and adverts are sent automatically.
|
||
|
|
// So we go to ADVERT_REQUESTED state. They are on the way.
|
||
|
|
pShtp->advertPhase = ADVERT_REQUESTED;
|
||
|
|
|
||
|
|
// Open HAL
|
||
|
|
pHal->open(pHal);
|
||
|
|
|
||
|
|
return pShtp;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Releases resources associated with this SHTP instance.
|
||
|
|
// HAL will not be closed.
|
||
|
|
void shtp_close(void *pInstance)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
|
||
|
|
pShtp->pHal->close(pShtp->pHal);
|
||
|
|
|
||
|
|
// Clear pShtp
|
||
|
|
// (Resetting pShtp->pHal to 0, returns this instance to the free pool)
|
||
|
|
memset(pShtp, 0, sizeof(shtp_t));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Register the pointer of the callback function for reporting asynchronous events
|
||
|
|
void shtp_setEventCallback(void *pInstance,
|
||
|
|
shtp_EventCallback_t * eventCallback,
|
||
|
|
void *eventCookie) {
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
|
||
|
|
pShtp->eventCallback = eventCallback;
|
||
|
|
pShtp->eventCookie = eventCookie;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Register a listener for an SHTP channel
|
||
|
|
int shtp_listenChan(void *pInstance,
|
||
|
|
uint16_t guid, const char * chan,
|
||
|
|
shtp_Callback_t *callback, void * cookie)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
|
||
|
|
// Balk if channel name isn't valid
|
||
|
|
if ((chan == 0) || (strlen(chan) == 0)) return SH2_ERR_BAD_PARAM;
|
||
|
|
|
||
|
|
return addChanListener(pShtp, guid, chan, callback, cookie);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Register a listener for SHTP advertisements
|
||
|
|
int shtp_listenAdvert(void *pInstance,
|
||
|
|
uint16_t guid,
|
||
|
|
shtp_AdvertCallback_t *advertCallback, void * cookie)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
|
||
|
|
// Register the advert listener
|
||
|
|
addAdvertListener(pShtp, guid, advertCallback, cookie);
|
||
|
|
|
||
|
|
// Arrange for a new set of advertisements, for this listener
|
||
|
|
if (pShtp->advertPhase == ADVERT_IDLE) {
|
||
|
|
pShtp->advertPhase = ADVERT_NEEDED;
|
||
|
|
}
|
||
|
|
|
||
|
|
return SH2_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Look up the channel number for a particular app, channel.
|
||
|
|
uint8_t shtp_chanNo(void *pInstance,
|
||
|
|
const char * appName, const char * chanName)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
|
||
|
|
int chan = 0;
|
||
|
|
uint32_t guid = 0xFFFFFFFF;
|
||
|
|
|
||
|
|
// Determine GUID for this appname
|
||
|
|
for (int n = 0; n < SH2_MAX_APPS; n++) {
|
||
|
|
if (strcmp(pShtp->app[n].appName, appName) == 0) {
|
||
|
|
guid = pShtp->app[n].guid;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (guid == 0xFFFFFFFF) return -1;
|
||
|
|
|
||
|
|
for (chan = 0; chan < SH2_MAX_CHANS; chan++) {
|
||
|
|
if ((strcmp(pShtp->chan[chan].chanName, chanName) == 0) &&
|
||
|
|
pShtp->chan[chan].guid == guid) {
|
||
|
|
// Found match
|
||
|
|
return chan;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Not found
|
||
|
|
return 0xFF;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Send an SHTP payload on a particular channel
|
||
|
|
int shtp_send(void *pInstance,
|
||
|
|
uint8_t channel, const uint8_t *payload, uint16_t len)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
int ret = SH2_OK;
|
||
|
|
|
||
|
|
if (len > pShtp->outMaxPayload) {
|
||
|
|
return SH2_ERR_BAD_PARAM;
|
||
|
|
}
|
||
|
|
if (channel >= SH2_MAX_CHANS) {
|
||
|
|
pShtp->badTxChan++;
|
||
|
|
return SH2_ERR_BAD_PARAM;
|
||
|
|
}
|
||
|
|
|
||
|
|
ret = txProcess(pShtp, channel, payload, len);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check for received data and process it.
|
||
|
|
void shtp_service(void *pInstance)
|
||
|
|
{
|
||
|
|
shtp_t *pShtp = (shtp_t *)pInstance;
|
||
|
|
uint32_t t_us = 0;
|
||
|
|
|
||
|
|
if (pShtp->advertPhase == ADVERT_NEEDED) {
|
||
|
|
pShtp->advertPhase = ADVERT_REQUESTED; // do this before send, to avoid recursion.
|
||
|
|
int status = shtp_send(pShtp, SHTP_CHAN_COMMAND, advertise, sizeof(advertise));
|
||
|
|
if (status != SH2_OK) {
|
||
|
|
// Oops, advert request failed. Go back to needing one.
|
||
|
|
pShtp->advertPhase = ADVERT_NEEDED;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
int len = pShtp->pHal->read(pShtp->pHal, pShtp->inTransfer, sizeof(pShtp->inTransfer), &t_us);
|
||
|
|
if (len) {
|
||
|
|
rxAssemble(pShtp, pShtp->inTransfer, len, t_us);
|
||
|
|
}
|
||
|
|
}
|