1007 lines
27 KiB
C++
1007 lines
27 KiB
C++
/*
|
|
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;
|
|
}
|