Sleds/K2Client/K2Client.cpp

1007 lines
27 KiB
C++
Raw Normal View History

2025-03-13 21:28:38 +00:00
/*
Copyright (c) 2013, 2014 IOnU Security Inc. All rights reserved. Copyright (c) 2015, 2016 Sequence Logic, Inc.
Created August 2013 by Kendrick Webster
K2Client.c - implementation of K2Client.h
(see K2Client.h for module description)
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "util.h"
#include "K2IPC.h"
#include "K2Client.h"
int heartbeat_interval_seconds = K2CLI_HEARTBEAT_INTERVAL_SECONDS; /* may be overriden by command-line in test tool */
#define HEARTBEAT_WINDOW_TICKS K2CLI_TIMER_TICKS_PER_SECOND
#define HEARTBEAT_MINIMUM (heartbeat_interval_seconds - K2CLI_HEARTBEAT_RANGE_SECONDS)
#define TICKS_PACKET_RETRY1 ((K2CLI_RETRY1_TENTHS_OF_SECONDS * K2CLI_TIMER_TICKS_PER_SECOND) / 10)
#define TICKS_PACKET_RETRY2 ((K2CLI_RETRY2_TENTHS_OF_SECONDS * K2CLI_TIMER_TICKS_PER_SECOND) / 10)
#define TICKS_PACKET_RETRY3 ((K2CLI_RETRY3_TENTHS_OF_SECONDS * K2CLI_TIMER_TICKS_PER_SECOND) / 10)
#define TICKS_PACKET_RETRY3_TIMEOUT ((K2CLI_RETRY3_TIMEOUT_TENTHS_OF_SECONDS * K2CLI_TIMER_TICKS_PER_SECOND) / 10)
#define TICKS_CONNECT2_DELAY \
((( \
( \
K2DMN_RETRY1_TENTHS_OF_SECONDS \
+ K2DMN_RETRY2_TENTHS_OF_SECONDS \
+ K2DMN_RETRY3_TENTHS_OF_SECONDS \
+ K2DMN_RETRY3_TIMEOUT_TENTHS_OF_SECONDS \
) \
+ K2CLI_RETRY1_TENTHS_OF_SECONDS \
) * K2CLI_TIMER_TICKS_PER_SECOND) / 10)
/* copies of K2Cli_Initialize(...) parameters */
static uint8_t* packet_buffer;
static const char* szURN;
static k2cli_message_callback_t OnMessage;
static k2cli_event_callback_t OnEvent;
static k2cli_device_status_callback_t OnDeviceStatus;
static k2cli_stats_callback_t OnStats;
static k2cli_udp_send_callback_t SendUDP;
static k2cli_stopwatch_callback_t Stopwatch;
/* for supporting multiple clients per library instance (to do in future: move relevant module static vars into struct) */
static uint32_t client_instance;
static int ticks_until_next_packet_send;
enum
{
TIMER_CONNECT,
TIMER_CONNECT_RETRY1,
TIMER_CONNECT_RETRY2,
TIMER_CONNECT_RETRY3,
TIMER_CONNECT_TIMEOUT,
TIMER_CONNECT2,
TIMER_CONNECT2_RETRY1,
TIMER_CONNECT2_RETRY2,
TIMER_CONNECT2_RETRY3,
TIMER_CONNECT2_TIMEOUT,
TIMER_HEARTBEAT,
TIMER_HEARTBEAT_RETRY1,
TIMER_HEARTBEAT_RETRY2,
TIMER_HEARTBEAT_RETRY3,
TIMER_HEARTBEAT_TIMEOUT,
TIMER_POLL_DB,
TIMER_POLL_DB_RETRY1,
TIMER_POLL_DB_RETRY2,
TIMER_POLL_DB_RETRY3,
TIMER_POLL_DB_TIMEOUT,
TIMER_SUBSCRIBE_OFFICE,
TIMER_SUBSCRIBE_OFFICE_RETRY1,
TIMER_SUBSCRIBE_OFFICE_RETRY2,
TIMER_SUBSCRIBE_OFFICE_RETRY3,
TIMER_SUBSCRIBE_OFFICE_TIMEOUT,
TIMER_STATS_QUERY,
TIMER_STATS_QUERY_RETRY1,
TIMER_STATS_QUERY_RETRY2,
TIMER_STATS_QUERY_RETRY3,
TIMER_STATS_QUERY_TIMEOUT,
TIMER_LOGOUT,
TIMER_LOGOUT_RETRY1,
TIMER_LOGOUT_RETRY2,
TIMER_LOGOUT_RETRY3,
TIMER_LOGOUT_TIMEOUT,
TIMER_LOGGED_OUT
};
static int timer_state = TIMER_LOGGED_OUT;
static uint32_t rx_client_instance;
static uint8_t tx_sequence, rx_sequence;
static int tx_repeated;
static char* status_select_offices;
static char* status_select_offices_header;
static char* status_select_offices_fragment;
static size_t status_select_offices_length;
static size_t status_select_offices_fragment_length;
static int trigger_poll_db, trigger_status_select, trigger_stats_query;
/* Packet obfuscation and validation */
static const uint8_t key [K2IPC_KEY_SIZE] = {K2IPC_KEY};
static const uint8_t mic [K2IPC_MIC_SIZE] = {K2IPC_MIC};
/* ----------------------- Upcall wrappers ----------------------- */
/* event notification upcall for K2CLI_EVENTS */
static void reportEvent(unsigned int event)
{
if (NULL != OnEvent)
{
OnEvent(event);
}
}
/*
read the heartbeat stopwatch (via upcall)
returns elapsed seconds
*/
static int stopwatchElapsed(void)
{
if (Stopwatch)
{
return Stopwatch(0);
}
else
{
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
return 0;
}
}
/*
reset the heartbeat stopwatch (via upcall)
also sets random range tick count (for avoiding heartbeat syncrhonization)
*/
static void stopwatchReset(void)
{
if (Stopwatch)
{
Stopwatch(1);
}
else
{
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
}
ticks_until_next_packet_send = (int)get_random(HEARTBEAT_WINDOW_TICKS);
}
/* ----------------------- Timer state machine helpers ----------------------- */
/* Enter the TIMER_HEARTBEAT (idle) state */
static void enterIdle(void)
{
timer_state = TIMER_HEARTBEAT;
}
/* Enter the TIMER_LOGGED_OUT (inactive) state */
static void enterLoggedOut(void)
{
timer_state = TIMER_LOGGED_OUT;
}
/* ----------------------- K2IPC_SUBSCRIBE_OFFICE (K2Cli_StatusSelect) helpers ----------------------- */
static void freeStatusSelect(void)
{
free(status_select_offices);
status_select_offices = NULL;
free(status_select_offices_header);
status_select_offices_header = NULL;
status_select_offices_fragment = NULL;
status_select_offices_length = 0;
status_select_offices_fragment_length = 0;
trigger_status_select = 0;
}
static void initStatusSelectHeader(void)
{
size_t len = strlen(szURN) + 2;
status_select_offices_header = (char *)malloc(len);
#ifdef WIN32
sprintf_s(status_select_offices_header, len, "%s%c", szURN, K2IPC_URN_DELIMITER);
#else
snprintf(status_select_offices_header, len, "%s%c", szURN, K2IPC_URN_DELIMITER);
#endif
status_select_offices_fragment_length = K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS - len; /* intentionally not (len - 1), counts frag_descriptor */
}
static void statusSelect(const char* offices)
{
offices = offices ? offices : "";
freeStatusSelect();
status_select_offices = strdup(offices);
status_select_offices_length = strlen(offices);
status_select_offices_fragment = status_select_offices;
initStatusSelectHeader();
trigger_status_select = 1;
}
/* get string for K2IPC_SUBSCRIBE_OFFICE packet or retry */
static void getStatusSelectFragment(char* buf)
{
size_t len;
char frag_descriptor;
if (status_select_offices && status_select_offices_fragment && status_select_offices_header)
{
if (status_select_offices_fragment == status_select_offices)
{
if (status_select_offices_length > status_select_offices_fragment_length)
{
frag_descriptor = K2IPC_FRAGMENT_FIRST;
}
else
{
frag_descriptor = K2IPC_FRAGMENT_SINGLE;
}
}
else
{
len = status_select_offices_fragment - status_select_offices;
if ((status_select_offices_length - len) > status_select_offices_fragment_length)
{
frag_descriptor = K2IPC_FRAGMENT_MIDDLE;
}
else
{
frag_descriptor = K2IPC_FRAGMENT_LAST;
}
}
#ifdef WIN32
_snprintf_s(buf, K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1, _TRUNCATE, "%s%c%s", status_select_offices_header, frag_descriptor, status_select_offices_fragment);
#else
snprintf(buf, K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1, "%s%c%s", status_select_offices_header, frag_descriptor, status_select_offices_fragment);
#endif
}
else
{
#ifdef WIN32
sprintf_s(buf, K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1, "<ERROR>");
#else
snprintf(buf, K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1, "<ERROR>");
#endif
}
}
/*
update pointers for getStatusSelectFragment() to return the next fragment,
returns non-zero (TRUE) if there are more fragments
*/
static int nextStatusSelectFragment(void)
{
size_t len = status_select_offices_fragment - status_select_offices;
size_t len_remaining = status_select_offices_length - len;
if (status_select_offices && (len_remaining > status_select_offices_fragment_length))
{
status_select_offices_fragment += status_select_offices_fragment_length;
return 1;
}
else
{
return 0;
}
}
/* ----------------------- Sending packets ----------------------- */
/* get/update the sequence number for a transmitted packet */
static uint8_t getTxSequence(uint8_t opcode)
{
switch (opcode)
{
case K2IPC_CONNECT:
return tx_sequence = rx_sequence = 0;
case K2IPC_LOGOUT:
case K2IPC_POLL_DB:
case K2IPC_HEARTBEAT:
case K2IPC_SUBSCRIBE_OFFICE:
case K2IPC_QUERY_STATS:
if (!tx_repeated)
{
++tx_sequence;
}
return tx_sequence;
case K2IPC_ACK_MESSAGE:
case K2IPC_ACK_DEVICE_STATUS:
case K2IPC_ACK_PING:
return rx_sequence;
default:
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
return 0;
}
}
/*
Encode a packet
Returns length of packet in octets
*/
static unsigned int encodePacket(uint8_t opcode, const char* info)
{
sc_hash_state_t h;
uint8_t* p = packet_buffer;
int len = (int)strlen(info);
/* sanity-check string length, truncate (sans NUL to make daemon complain) if necessary */
if ((K2IPC_OVERHEAD_SIZE + len) > K2IPC_MAX_PACKET_SIZE)
{
len = K2IPC_MAX_PACKET_SIZE - K2IPC_OVERHEAD_SIZE;
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
}
/* generate nonce and use it to key the stream cipher */
memset(&h, 0, sizeof(h));
get_random_bytes(p, K2IPC_NONCE_SIZE);
sc_hash_vector(&h, p, K2IPC_NONCE_SIZE);
sc_hash_vector(&h, key, sizeof(key));
sc_hash_mix(&h, K2IPC_KEY_MIX_CYCLES);
/* add other packet fields, encrypt packet, return length */
p += K2IPC_NONCE_SIZE;
*p++ = ((K2IPC_PROTOCOL_VERSION >> 8) & 0xFF);
*p++ = (K2IPC_PROTOCOL_VERSION & 0xFF);
*p++ = ((client_instance >> 16) & 0xFF);
*p++ = ((client_instance >> 8) & 0xFF);
*p++ = (client_instance & 0xFF);
*p++ = getTxSequence(opcode);
*p++ = opcode;
memcpy(p, info, len + 1);
memcpy(p + len + 1, mic, K2IPC_MIC_SIZE);
sc_hash_encrypt(&h, packet_buffer + K2IPC_NONCE_SIZE, len + K2IPC_CRYPT_SIZE);
return (unsigned int)len + K2IPC_OVERHEAD_SIZE;
}
/*
Encode and send a packet
Resets the heartbeat stopwatch
*/
static void sendPacket(uint8_t opcode, const char* info)
{
unsigned int len;
if (packet_buffer && info && SendUDP)
{
len = encodePacket(opcode, info);
SendUDP(len);
stopwatchReset();
}
else
{
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
}
}
static void sendAckMessage(void)
{
log_verbose("Tx ACK_MESSAGE");
sendPacket(K2IPC_ACK_MESSAGE, szURN);
}
static void sendAckDeviceStatus(void)
{
log_verbose("Tx ACK_DEVICE_STATUS");
sendPacket(K2IPC_ACK_DEVICE_STATUS, szURN);
}
static void sendAckPing(void)
{
log_verbose("Tx ACK_PING");
sendPacket(K2IPC_ACK_PING, szURN);
}
static void sendConnect(void)
{
log_verbose("Tx CONNECT");
sendPacket(K2IPC_CONNECT, szURN);
}
static void sendHeartbeat(void)
{
log_verbose("Tx HEARTBEAT");
sendPacket(K2IPC_HEARTBEAT, szURN);
}
static void sendPollDB(void)
{
log_verbose("Tx POLL_DB");
sendPacket(K2IPC_POLL_DB, szURN);
}
static void sendStatsQuery(void)
{
log_verbose("Tx QUERY_STATS");
sendPacket(K2IPC_QUERY_STATS, szURN);
}
static void sendLogout(void)
{
log_verbose("Tx LOGOUT");
sendPacket(K2IPC_LOGOUT, szURN);
}
static void sendSubscribeOffice(void)
{
log_verbose("Tx SUBSCRIBE_OFFICE");
char buf[K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1];
getStatusSelectFragment(buf);
sendPacket(K2IPC_SUBSCRIBE_OFFICE, buf);
}
/* Helper for checkPacket() -- Send an ACK for a packet with opcode <op> */
static void sendAck(uint8_t op)
{
switch (op)
{
case K2IPC_MESSAGE:
sendAckMessage();
break;
case K2IPC_DEVICE_STATUS:
sendAckDeviceStatus();
break;
case K2IPC_PING:
sendAckPing();
break;
}
}
/* Timer-driven sender of next packet or retry */
static void sendNextPacket(void)
{
int state = timer_state;
/* first: update the state, timer, and <tx_repeated> */
switch (state)
{
case TIMER_CONNECT:
case TIMER_CONNECT2:
case TIMER_HEARTBEAT:
case TIMER_POLL_DB:
case TIMER_SUBSCRIBE_OFFICE:
case TIMER_STATS_QUERY:
case TIMER_LOGOUT:
tx_repeated = 0;
ticks_until_next_packet_send = TICKS_PACKET_RETRY1;
++timer_state;
break;
case TIMER_CONNECT_RETRY1:
case TIMER_CONNECT2_RETRY1:
case TIMER_HEARTBEAT_RETRY1:
case TIMER_POLL_DB_RETRY1:
case TIMER_SUBSCRIBE_OFFICE_RETRY1:
case TIMER_STATS_QUERY_RETRY1:
case TIMER_LOGOUT_RETRY1:
tx_repeated = 1;
ticks_until_next_packet_send = TICKS_PACKET_RETRY2;
++timer_state;
break;
case TIMER_CONNECT_RETRY2:
case TIMER_CONNECT2_RETRY2:
case TIMER_HEARTBEAT_RETRY2:
case TIMER_POLL_DB_RETRY2:
case TIMER_SUBSCRIBE_OFFICE_RETRY2:
case TIMER_STATS_QUERY_RETRY2:
case TIMER_LOGOUT_RETRY2:
ticks_until_next_packet_send = TICKS_PACKET_RETRY3;
++timer_state;
break;
case TIMER_CONNECT_RETRY3:
case TIMER_CONNECT2_RETRY3:
case TIMER_HEARTBEAT_RETRY3:
case TIMER_POLL_DB_RETRY3:
case TIMER_SUBSCRIBE_OFFICE_RETRY3:
case TIMER_STATS_QUERY_RETRY3:
case TIMER_LOGOUT_RETRY3:
ticks_until_next_packet_send = TICKS_PACKET_RETRY3_TIMEOUT;
++timer_state;
break;
case TIMER_CONNECT_TIMEOUT:
case TIMER_CONNECT2_TIMEOUT:
case TIMER_HEARTBEAT_TIMEOUT:
case TIMER_POLL_DB_TIMEOUT:
case TIMER_SUBSCRIBE_OFFICE_TIMEOUT:
case TIMER_STATS_QUERY_TIMEOUT:
enterLoggedOut();
reportEvent(K2CLI_ERROR_NOT_CONNECTED);
break;
case TIMER_LOGOUT_TIMEOUT:
enterLoggedOut();
reportEvent(K2CLI_ERROR_LOGOUT_FAILED);
break;
}
/*
next: send a packet (or not) based on state,
<tx_repeated> was set in switch() above
*/
switch (state)
{
case TIMER_CONNECT:
case TIMER_CONNECT_RETRY1:
case TIMER_CONNECT_RETRY2:
case TIMER_CONNECT_RETRY3:
case TIMER_CONNECT2:
case TIMER_CONNECT2_RETRY1:
case TIMER_CONNECT2_RETRY2:
case TIMER_CONNECT2_RETRY3:
sendConnect();
break;
case TIMER_HEARTBEAT:
case TIMER_HEARTBEAT_RETRY1:
case TIMER_HEARTBEAT_RETRY2:
case TIMER_HEARTBEAT_RETRY3:
sendHeartbeat();
break;
case TIMER_POLL_DB:
case TIMER_POLL_DB_RETRY1:
case TIMER_POLL_DB_RETRY2:
case TIMER_POLL_DB_RETRY3:
sendPollDB();
break;
case TIMER_SUBSCRIBE_OFFICE:
case TIMER_SUBSCRIBE_OFFICE_RETRY1:
case TIMER_SUBSCRIBE_OFFICE_RETRY2:
case TIMER_SUBSCRIBE_OFFICE_RETRY3:
sendSubscribeOffice();
break;
case TIMER_STATS_QUERY:
case TIMER_STATS_QUERY_RETRY1:
case TIMER_STATS_QUERY_RETRY2:
case TIMER_STATS_QUERY_RETRY3:
case TIMER_STATS_QUERY_TIMEOUT:
sendStatsQuery();
break;
case TIMER_LOGOUT:
case TIMER_LOGOUT_RETRY1:
case TIMER_LOGOUT_RETRY2:
case TIMER_LOGOUT_RETRY3:
sendLogout();
break;
}
}
/* ----------------------- Receiving packets ----------------------- */
/*
helper for checkPacket
returns 1 (TRUE) if <p> matches this client's URN
*/
static int checkURN(const char* p)
{
if (szURN)
{
if (0 == memcmp(p, szURN, strlen(szURN)))
{
return 1;
}
else
{
reportEvent(K2CLI_ERROR_WRONG_URN);
return 0;
}
}
else
{
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
return 0;
}
}
/*
Helper for decodePacket
Returns 1 (TRUE) if a received packet should be processed
Updates rx_sequence
Sends ACKs for packets that need to be ACK'd
*/
static int checkPacket(const uint8_t* p, unsigned int len)
{
uint16_t protocol_version;
uint8_t seq, op;
/* reject packet if MIC is wrong */
if (0 != memcmp(packet_buffer + (len - K2IPC_MIC_SIZE), mic, K2IPC_MIC_SIZE))
{
reportEvent(K2CLI_INFO_SPURIOUS_PACKET);
return 0;
}
/* reject packet if NUL terminator for body (string) is missing */
if (0 != packet_buffer[len - (K2IPC_MIC_SIZE + 1)])
{
reportEvent(K2CLI_ERROR_MISSING_NUL);
return 0;
}
protocol_version = *p++;
protocol_version <<= 8;
protocol_version |= *p++;
/* reject packet if protocol version is wrong (daemon matches clients) */
if (K2IPC_PROTOCOL_VERSION != protocol_version)
{
reportEvent(K2CLI_ERROR_WRONG_PROTOCOL);
return 0;
}
rx_client_instance = *p++;
rx_client_instance <<= 8;
rx_client_instance |= *p++;
rx_client_instance <<= 8;
rx_client_instance |= *p++;
seq = *p++;
op = *p++;
switch (op)
{
case K2IPC_MESSAGE:
case K2IPC_DEVICE_STATUS:
case K2IPC_PING:
if (seq != ++rx_sequence)
{
if (seq == --rx_sequence)
{
sendAck(op); /* normally the packet handler sends the ACK, but the handler isn't invoked for repeated packets */
reportEvent(K2CLI_INFO_REPEATED_MESSAGE);
}
else
{
reportEvent(K2CLI_ERROR_BAD_MSG_SEQUENCE);
}
return 0;
}
break;
case K2IPC_ACK_CONNECT:
case K2IPC_NAK_CONNECT:
case K2IPC_ACK_LOGOUT:
case K2IPC_ACK_POLL_DB:
case K2IPC_ACK_HEARTBEAT:
case K2IPC_ACK_SUBSCRIBE_OFFICE:
if (!checkURN((const char*)p))
{
return 0;
}
/* FALL THROUGH */
case K2IPC_ACK_QUERY_STATS:
if (seq != tx_sequence)
{
reportEvent((seq == (tx_sequence - 1)) ? K2CLI_INFO_REPEATED_ACK : K2CLI_ERROR_BAD_ACK_SEQUENCE);
return 0;
}
break;
default:
reportEvent(K2CLI_ERROR_BAD_OPCODE);
return 0;
}
return 1;
}
/*
Decode a packet
Returns pointer to opcode if packet should be processed, NULL otherwise
*/
static const uint8_t* decodePacket(unsigned int len)
{
sc_hash_state_t h;
uint8_t* p = packet_buffer;
/* reject short packets */
if (len < K2IPC_OVERHEAD_SIZE)
{
reportEvent(K2CLI_INFO_SPURIOUS_PACKET);
return NULL;
}
/* decrypt packet */
memset(&h, 0, sizeof(h));
sc_hash_vector(&h, p, K2IPC_NONCE_SIZE);
sc_hash_vector(&h, key, sizeof(key));
sc_hash_mix(&h, K2IPC_KEY_MIX_CYCLES);
p += K2IPC_NONCE_SIZE;
sc_hash_decrypt(&h, p, len - K2IPC_NONCE_SIZE);
/* check packet validity and sequence */
return checkPacket(p, len) ? (p + 6) : NULL;
}
/*
Message (non ACK) packet handlers
----------------------------------------------------------
*/
static void onMessage(const char* p)
{
if (OnMessage)
{
OnMessage(p);
}
sendAckMessage();
}
static void onDeviceStatus(const char* p)
{
if (OnDeviceStatus)
{
OnDeviceStatus(p);
}
sendAckDeviceStatus();
}
static void onPing(void)
{
sendAckPing();
}
/*
Ack/Nak packet handlers
----------------------------------------------------------
*/
static void onAckConnect(void)
{
switch (timer_state)
{
case TIMER_CONNECT:
case TIMER_CONNECT_RETRY1:
case TIMER_CONNECT_RETRY2:
case TIMER_CONNECT_RETRY3:
case TIMER_CONNECT_TIMEOUT:
case TIMER_CONNECT2:
case TIMER_CONNECT2_RETRY1:
case TIMER_CONNECT2_RETRY2:
case TIMER_CONNECT2_RETRY3:
case TIMER_CONNECT2_TIMEOUT:
enterIdle();
reportEvent(K2CLI_INFO_LOGGED_IN);
break;
}
}
static void onNakConnect(void)
{
switch (timer_state)
{
case TIMER_CONNECT:
case TIMER_CONNECT_RETRY1:
case TIMER_CONNECT_RETRY2:
case TIMER_CONNECT_RETRY3:
case TIMER_CONNECT_TIMEOUT:
timer_state = TIMER_CONNECT2;
ticks_until_next_packet_send = TICKS_CONNECT2_DELAY;
break;
case TIMER_CONNECT2:
case TIMER_CONNECT2_RETRY1:
case TIMER_CONNECT2_RETRY2:
case TIMER_CONNECT2_RETRY3:
case TIMER_CONNECT2_TIMEOUT:
enterLoggedOut();
reportEvent(K2CLI_ERROR_DUPLICATE_URN);
reportEvent(K2CLI_ERROR_NOT_CONNECTED);
break;
}
}
static void onAckLogout(void)
{
switch (timer_state)
{
case TIMER_LOGOUT:
case TIMER_LOGOUT_RETRY1:
case TIMER_LOGOUT_RETRY2:
case TIMER_LOGOUT_RETRY3:
case TIMER_LOGOUT_TIMEOUT:
enterLoggedOut();
reportEvent(K2CLI_INFO_LOGGED_OUT);
break;
}
}
static void onAckPollDB(void)
{
switch (timer_state)
{
case TIMER_POLL_DB:
case TIMER_POLL_DB_RETRY1:
case TIMER_POLL_DB_RETRY2:
case TIMER_POLL_DB_RETRY3:
case TIMER_POLL_DB_TIMEOUT:
enterIdle();
reportEvent(K2CLI_INFO_POLL_REQUESTED);
break;
}
}
static void onAckHeartbeat(void)
{
switch (timer_state)
{
case TIMER_HEARTBEAT:
case TIMER_HEARTBEAT_RETRY1:
case TIMER_HEARTBEAT_RETRY2:
case TIMER_HEARTBEAT_RETRY3:
case TIMER_HEARTBEAT_TIMEOUT:
enterIdle();
reportEvent(K2CLI_INFO_HEARTBEAT);
break;
}
}
static void onAckSubscribeOffice(void)
{
switch (timer_state)
{
case TIMER_SUBSCRIBE_OFFICE:
case TIMER_SUBSCRIBE_OFFICE_RETRY1:
case TIMER_SUBSCRIBE_OFFICE_RETRY2:
case TIMER_SUBSCRIBE_OFFICE_RETRY3:
case TIMER_SUBSCRIBE_OFFICE_TIMEOUT:
if (nextStatusSelectFragment())
{
timer_state = TIMER_SUBSCRIBE_OFFICE;
sendNextPacket();
}
else
{
enterIdle();
freeStatusSelect();
reportEvent(K2CLI_INFO_OFFICES_SUBSCRIBED);
}
break;
}
}
static void onAckQueryStats(const char* p)
{
switch (timer_state)
{
case TIMER_STATS_QUERY:
case TIMER_STATS_QUERY_RETRY1:
case TIMER_STATS_QUERY_RETRY2:
case TIMER_STATS_QUERY_RETRY3:
case TIMER_STATS_QUERY_TIMEOUT:
enterIdle();
if (OnStats)
{
OnStats(p);
}
break;
}
}
/* ----------------------- Module interface functions ----------------------- */
void K2Cli_Initialize(
uint8_t* buf,
const char* urn,
k2cli_message_callback_t onMessage,
k2cli_event_callback_t onEvent,
k2cli_device_status_callback_t onDeviceStatus,
k2cli_stats_callback_t onStats,
k2cli_udp_send_callback_t sendUDP,
k2cli_stopwatch_callback_t stopwatch)
{
packet_buffer = buf;
szURN = urn;
OnMessage = onMessage;
OnEvent = onEvent;
OnDeviceStatus = onDeviceStatus;
OnStats = onStats;
SendUDP = sendUDP;
Stopwatch = stopwatch;
log_debug("cli init");
}
void K2Cli_HandlePacket(unsigned int len)
{
const char* p = (const char*)decodePacket(len);
if (p)
{
switch (*p++)
{
case K2IPC_MESSAGE: log_verbose("Rx MESSAGE"); onMessage(p); break;
case K2IPC_DEVICE_STATUS: log_verbose("Rx DEVICE_STATUS"); onDeviceStatus(p); break;
case K2IPC_PING: log_verbose("Rx PING"); onPing(); break;
case K2IPC_ACK_CONNECT: log_verbose("Rx ACK_CONNECT"); onAckConnect(); break;
case K2IPC_NAK_CONNECT: log_verbose("Rx NAK_CONNECT"); onNakConnect(); break;
case K2IPC_ACK_LOGOUT: log_verbose("Rx ACK_LOGOUT"); onAckLogout(); break;
case K2IPC_ACK_POLL_DB: log_verbose("Rx ACK_POLL_DB"); onAckPollDB(); break;
case K2IPC_ACK_HEARTBEAT: log_verbose("Rx ACK_HEARTBEAT"); onAckHeartbeat(); break;
case K2IPC_ACK_SUBSCRIBE_OFFICE: log_verbose("Rx ACK_SUBSCRIBE_OFFICE"); onAckSubscribeOffice(); break;
case K2IPC_ACK_QUERY_STATS: log_verbose("Rx ACK_QUERY_STATS"); onAckQueryStats(p); break;
default:
/* decodePacket() should catch bad opcodes */
reportEvent(K2CLI_ERROR_FAILED_ASSERT);
break;
}
}
}
void K2Cli_Timer(void)
{
switch (timer_state)
{
case TIMER_HEARTBEAT:
if (trigger_poll_db)
{
trigger_poll_db = 0;
timer_state = TIMER_POLL_DB;
sendNextPacket();
}
else if (trigger_status_select)
{
trigger_status_select = 0;
timer_state = TIMER_SUBSCRIBE_OFFICE;
sendNextPacket();
}
else if (trigger_stats_query)
{
trigger_stats_query = 0;
timer_state = TIMER_STATS_QUERY;
sendNextPacket();
}
else if (HEARTBEAT_MINIMUM <= stopwatchElapsed())
{
if (0 >= --ticks_until_next_packet_send)
{
sendNextPacket();
}
}
break;
case TIMER_LOGGED_OUT:
break;
default:
if (0 >= --ticks_until_next_packet_send)
{
sendNextPacket();
}
break;
}
}
void K2Cli_Logout(void)
{
timer_state = TIMER_LOGOUT;
ticks_until_next_packet_send = 1;
freeStatusSelect();
log_debug("cli logout");
}
void K2Cli_Login(void)
{
timer_state = TIMER_CONNECT;
ticks_until_next_packet_send = 1;
freeStatusSelect();
log_debug("cli login");
}
void K2Cli_Poll_DB(void)
{
trigger_poll_db = 1;
}
void K2Cli_StatusSelect(const char* offices)
{
statusSelect(offices);
}
void K2Cli_QueryStats(void)
{
trigger_stats_query = 1;
}