- I've started work on a new project! The project in question is one that - I have wanted to tackle for a long time but did not have the courage to - do so. However at long last it is time... -
-- time to get funky... -
-- I'm writing a music player, and not one that simply plays music like - spotify. This music player falls more inline with MPD in which it's a - daemon running in the background playing the music and clients may - communicate with it to tell it what to play when and the likes. -
-- Currently I'm at that very important part in which the daemon needs to - communicate to clients via a protocol *fancy*, and to do so I have decided - to go with a socket that way in theory I can control my daemon from - other devices on the network. With the communication method decided I now - have to define what data goes between devices. At the current moment I've - settled on an enum for different signals. -
-- As seen here: -
--enum libmoo_signal { - /* core signals are in the < 100 range */ - LIBMOO_SIGNAL_ESTCON = 1, - LIBMOO_SIGNAL_ESTDCON = 2, - LIBMOO_SIGNAL_INCOMPATABLE_VERSION = 3, - LIBMOO_SIGNAL_OK = 50, /* previous message received was ok */ - LIBMOO_SIGNAL_NOK = 51, /* previous message received was not ok */ - /* setting/adding data happens in the 100 range */ - LIBMOO_SIGNAL_ADD_SONG = 101, - LIBMOO_SIGNAL_SET_PLAY = 103, - LIBMOO_SIGNAL_SET_PAUSE = 104, - LIBMOO_SIGNAL_SET_TOGGLE_PAUSE = 105, - LIBMOO_SIGNAL_SET_SKIP_NEXT = 110, - LIBMOO_SIGNAL_SET_SKIP_PREV = 111, - /* removing data happens in the 200 range */ - LIBMOO_SIGNAL_REM_SONG = 201, - /* getting data happens in the 300 range */ - LIBMOO_SIGNAL_GET_CUR_SONG = 301, - LIBMOO_SIGNAL_GET_PAUSE = 303, - LIBMOO_SIGNAL_GET_PROGRESS = 310, -}; --
- As for how I actually transmit the signals, I've settled on a format which - defaults to 42 bytes of data being sent with the option to send more by - setting the 41st-42nd bytes to a uint16 containing the additional number - of bytes being sent. -
--/* byte - data - * - * 0-7 - libmoo protocol version - * - * 8-31 - reserved for the future - * - * 32-40 - libmoo_signal - * - * 41-42 - uint16 more_data - * - * 43-8191 - char *data - */ -typedef char * libmoo_payload; --
- I'm not sure how this compares to other protocols used to transmit data, - but I'm quite proud of how I've laid this out. -
-- Moving on to actual the point of this article: serialization. For the sake - of useablilty I've defined a datatype which models our payload which you - can see here: -
--typedef struct { - enum libmoo_signal signal; /* the signal */ - uint16_t more_data; /* the number bytes of the additional data */ - char *data; /* additional data */ -} libmoo_data; --
- And a function which allows you to easily create libmoo_data: -
--/** - * @brief create a new libmoo_data - * - * @param signal the signal to set - * @param data the data to set - * @return the data object - */ -libmoo_data *libmoo_create_data(enum libmoo_signal signal, char *data); --
- I've provided this so that you don't have to deal with manually setting - the size of the data that you pass into the object, although (if you so - choose) you may easily override it. -
-- Now that we've gone over the interface for interacting with the payload - let's get to the intersting stage in which we get the data ready for - launch. -
--/** - * @brief convert data into a payload. - * The payload must be allocated prior to calling this function it should be - * LIBMOO_PAYLOAD_SIZE chars long unless you think you know better. - * - * @param payload pointer to the payload - * @param data data which will be serialized - * @return the size of the resulting payload - */ -size_t -libmoo_serialize_data(libmoo_payload payload, libmoo_data *data) -{ - void *ptr; - ptr = payload; - /* add LIBMOO_VERSION */ - mempcpy(ptr, LIBMOO_VERSION, strlen(LIBMOO_VERSION)); - /* skip the reserved space */ - ptr += 24; - /* add the signal type */ - mempcpy(ptr, &data->signal, sizeof(data->signal)); - /* add the size of the more data */ - mempcpy(ptr, &data->more_data, sizeof(data->more_data)); - /* the more data */ - if (data->more_data) { - mempcpy(ptr, data->data, strlen(data->data)); - } - return LIBMOO_HEADER_SIZE + sizeof(data->more_data) + data->more_data; -} --
- As you may have noticed in the code snippet above libmoo_serialize_data is - a function that takes in a pre-allocated payload, and the data to - serialize. I've opted to allow the user to allocate space for the payload - if they determine that they know what they're doing incase my dumb - function is too generous. Moving onto the body of the function you'll see - an unfamiliar function called mempcpy which is a macro for copying data - into the payload. This macro takes in the same arguments as memcpy (dest, - src, size), but in addition it increments dest by size so I can append - data to the payload without making this code horrible to read. -
-- In earlier stages of development I didn't actually return the size of the - resulting payload, thinking that I could just strlen it. This was a rookie - mistake. Because I've included extra space (bytes 8-31) when you run - strlen on any payload it results in 5 (the length of the version string). - After finding this out I decided that I do actually want to send more - than the version information, and so now libmoo_serialize_data returns a - size_t continaing the number of bytes that have been serialized. -
-- Now that we've serialized and presumably sent the data to a client - we need to do the magic part which completes this transaction: - deserialization. -
-- Below is my method of deserializing the data. It's pretty much the - opposite of the serialization function with the key exception that we - need to do some validation. Which I haven't implemented yet. -
--libmoo_data -*libmoo_deserialize_data(libmoo_payload payload, unsigned int payload_size) -{ - libmoo_data *data; - char libmoo_version[LIBMOO_VERSION_LEN + 1]; - /* ensure that there's enough data to comply to spec */ - if (payload_size < LIBMOO_HEADER_SIZE) { - errno = EBADE; - return NULL; - } - /* attempt to allocate space and if we fail return NULL errno contains the - * error - */ - data = calloc(1, sizeof(libmoo_data)); - if (data == NULL) { - errno = ENOMEM; - return NULL; - } - /* get the libmoo_version */ - memscpy(libmoo_version, payload, LIBMOO_VERSION_LEN); - libmoo_version[LIBMOO_VERSION_LEN] = '