/* 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 #include #include #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, ""); #else snprintf(buf, K2IPC_MAX_STRING_LENGTH_FROM_CLIENTS + 1, ""); #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 */ 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 */ 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, 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

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; }