/** * @file include/di/rpc/lowlevel.h * @brief Lowlevel RPC * @date Aug 27, 2015 * @author rheijden * @copyright 2015 Dual Inventive Technology Centre B.V. * * @defgroup rpc_ll DI-Net RPC lowlevel * @{ */ #ifndef INCLUDE_DI_RPC_LL_H_ #define INCLUDE_DI_RPC_LL_H_ #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /**< DI-Net lowlevel message header magic */ #define DI_RPC_LL_MSG_HDR_MAGIC \ "\x44\x4a\x52" #define DI_RPC_LL_MSG_HDR_MAGIC_SIZE \ (sizeof(DI_RPC_LL_MSG_HDR_MAGIC) - 1) #define DI_RPC_LL_MSG_HDR_TYPE_LEN 1U /** Message type field length in bytes */ #define DI_RPC_LL_MSG_HDR_TYPE_OFFSET 3U /** Message type field byte offset, after magic */ #define DI_RPC_LL_MSG_HDR_SIZE_LEN 2U /** Message size field length in bytes */ #define DI_RPC_LL_MSG_HDR_SIZE_OFFSET 4U /** Message size field byte offset, after magic */ #define DI_RPC_LL_MSG_HANDSHAKE_DEVICE_UID_SIZE 32U /** Message handshake device:uid size */ /** DI-Net lowlevel message header size */ #define DI_RPC_LL_MSG_HDR_SIZE \ (DI_RPC_LL_MSG_HDR_MAGIC_SIZE + \ DI_RPC_LL_MSG_HDR_TYPE_LEN + \ DI_RPC_LL_MSG_HDR_SIZE_LEN) #define DI_RPC_LL_MSG_REPLY_MKAY "MKAY" #define DI_RPC_LL_MSG_REPLY_MKAY_SIZE (sizeof(DI_RPC_LL_MSG_REPLY_MKAY) - 1) #define DI_RPC_LL_MSG_REPLY_WTF "WTF" #define DI_RPC_LL_MSG_REPLY_WTF_SIZE (sizeof(DI_RPC_LL_MSG_REPLY_WTF) - 1) /** Message status */ enum di_rpc_ll_msg_status { DI_RPC_LL_MSG_STAT_ERR_INVALID_TYPE = -3, /**< Message has invalid type */ DI_RPC_LL_MSG_STAT_ERR_MORE_DATA_NEEDED = -2, /**< Message incomplete, buffer needs more data */ DI_RPC_LL_MSG_STAT_UNKNOWN = -1, /**< Unknown message, header magic not found */ DI_RPC_LL_MSG_STAT_OK = 0, /**< Message ok */ }; /** DI-Net available lowlevel message types */ enum di_rpc_ll_msg_type { DI_RPC_LL_MSG_TYPE_UNKNOWN = 0x00, /**< Unknown */ DI_RPC_LL_MSG_TYPE_HS_REQUEST = 0x01, /**< Handshake request */ DI_RPC_LL_MSG_TYPE_REPLY = 0x02, /**< Reply @see di_rpc_ll_msg_reply_status */ DI_RPC_LL_MSG_TYPE_REGISTER = 0x03, /**< Register device:uid on existing connection */ DI_RPC_LL_MSG_TYPE_UNREGISTER = 0x04, /**< Unregister device:uid on existing connection */ DI_RPC_LL_MSG_TYPE_PLAIN = 0x10, /**< Plain DI-Net RPC message */ DI_RPC_LL_MSG_TYPE_ENCRYPTED = 0x20, /**< Encrypted DI-Net RPC message */ DI_RPC_LL_MSG_TYPE_TIME = 0x40 /**< DI-Net time request/reply message */ }; /** Reply status when message is of type `DI_RPC_LL_MSG_TYPE_REPLY` */ enum di_rpc_ll_msg_reply { DI_RPC_LL_MSG_REPLY_UNKNOWN, /**< Unknown reply status */ DI_RPC_LL_MSG_REPLY_ERROR, /**< Reply error (`"WTF"`) */ DI_RPC_LL_MSG_REPLY_OK, /**< Reply ok (`"MKAY"`) */ DI_RPC_LL_MSG_REPLY_TIME /**< Reply DI-Net timestamp */ }; /** DI-Net RPC lowlevel message */ struct di_rpc_ll_msg { enum di_rpc_ll_msg_status status; /**< Message status */ enum di_rpc_ll_msg_reply reply; /**< Reply status when status == `DI_RPC_LL_MSG_TYPE_REPLY` */ enum di_rpc_ll_msg_type type; /**< Message type */ uint8_t *data; /**< Message data payload */ uint16_t size; /**< Message data payload size */ size_t remaining; /**< Bytes remaining in buf after message */ struct di_buffer *buf; /**< Buffer of current message */ }; /** * @name Encoding * @{ */ /** * Encode message from src buffer with src->used into dst buffer * @param dst Destination buffer * @param src Source buffer * @param type Lowlevel message type of source buffer */ di_errno_t di_rpc_ll_encode(struct di_buffer *dst, struct di_buffer *src, enum di_rpc_ll_msg_type type); /** * Encode handshake which uses device:uid * @param buf The destination buffer to encode the handshake message to * @param device_uid The device:uid must be 33 bytes which must include the NULL termination * @retval DNOK when device_uid is correct and buf holds the encoded message */ di_errno_t di_rpc_ll_encode_handshake(struct di_buffer *buf, const char device_uid[DI_DEVICE_UID_LEN]); /** * Write register which uses device:uid * @param buf The destination buffer to encode the register message to * @param device_uid The device:uid must be 33 bytes which must include the NULL termination * @retval DNOK when device_uid is correct and buf holds the encoded message */ di_errno_t di_rpc_ll_encode_register(struct di_buffer *buf, const char device_uid[DI_DEVICE_UID_LEN]); /** * Write time request */ di_errno_t di_rpc_ll_encode_time_request(struct di_buffer *buf); /** * Encode time */ di_errno_t di_rpc_ll_encode_time(struct di_buffer *buf, uint64_t time); /** * @} */ /** * @name Decoding * @{ */ /** Decode buffer into rpc lowlevel message * @note DI_RPC_LL_MSG_STAT_OK -> DNOK, set llmsg->status accordingly ... DNOK is more convenient */ enum di_rpc_ll_msg_status di_rpc_ll_decode(struct di_buffer *buf, struct di_rpc_ll_msg *llmsg); /** * Decode the time from the message */ di_errno_t di_rpc_ll_decode_time(struct di_rpc_ll_msg *llmsg, uint64_t *time); /** * @} */ /** * @name RPC reader * @{ */ /** * Prepare RPC reader from buffer. It decodes the message header. */ di_errno_t di_rpc_ll_rpc_reader_start(mpack_writer_t *reader, struct di_buffer *buf); /** * Finish RPC reader. * @todo how to return there is more data in buf, with di_errno? */ di_errno_t di_rpc_ll_rpc_reader_finish(mpack_writer_t *reader, struct di_buffer *buf); /** * @} */ /** * @name RPC writer * @{ */ /** * Prepare RPC writer with buffer. Skips enough bytes to write the lowlevel header. */ di_errno_t di_rpc_ll_rpc_writer_start(mpack_writer_t *writer, struct di_buffer *buf); /** * Finish RPC writer. Writers the lowlevel header. */ di_errno_t di_rpc_ll_rpc_writer_finish(mpack_writer_t *writer, struct di_buffer *buf); /** * @} */ /** * @name Lowlevel operations * @{ */ /** * Write lowlevel message header * @note The buffer size must be minimal size of DI_RPC_LL_MSG_HDR_SIZE * @param buf Target buffer (starts writing at offset 0) * @param type Lowlevel message type * @param data_len Length of data payload * @note The current DI-Net RPC protocol specification has a limit of uint16_t frame length */ di_errno_t di_rpc_ll_write_header(struct di_buffer *buf, enum di_rpc_ll_msg_type type, size_t data_len); /** * Write lowlevel message data payload * @note This only writes the payload, the header needs to be written first with di_rpc_ll_write_header. * @param buf Target buffer (starts writing at offset after the lowlevel header) * @param data Data payload * @param len Data payload length * @note The current DI-Net RPC protocol specification has a limit of uint16_t frame length */ di_errno_t di_rpc_ll_write_data(struct di_buffer *buf, const uint8_t *data, size_t len); /** * Copy remaining data from lowlevel message into a di_buffer * @param dst Destination buffer (starts writing at offset 0) * @param src Decoded lowlevel message which holds remaining data */ di_errno_t di_rpc_ll_memcpy_remaining(struct di_buffer *dst, struct di_rpc_ll_msg *src); /** * @} */ #ifdef __cplusplus } #endif /** @} */ #endif /* INCLUDE_DI_RPC_LL_H_ */