This commit is contained in:
jorgenkraghjakobsen
2020-04-24 12:12:58 +02:00
Unverified
parent 963736cb0a
commit a79197c183
3 changed files with 419 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
COMPONENT_SRCDIRS := .
# CFLAGS +=

View File

@@ -0,0 +1,108 @@
#ifndef __SNAPCAST_H__
#define __SNAPCAST_H__
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
enum message_type {
SNAPCAST_MESSAGE_BASE = 0,
SNAPCAST_MESSAGE_CODEC_HEADER = 1,
SNAPCAST_MESSAGE_WIRE_CHUNK = 2,
SNAPCAST_MESSAGE_SERVER_SETTINGS = 3,
SNAPCAST_MESSAGE_TIME = 4,
SNAPCAST_MESSAGE_HELLO = 5,
SNAPCAST_MESSAGE_STREAM_TAGS = 6,
SNAPCAST_MESSAGE_FIRST = SNAPCAST_MESSAGE_BASE,
SNAPCAST_MESSAGE_LAST = SNAPCAST_MESSAGE_STREAM_TAGS
};
typedef struct tv {
int32_t sec;
int32_t usec;
} tv_t;
typedef struct base_message {
uint16_t type;
uint16_t id;
uint16_t refersTo;
tv_t sent;
tv_t received;
uint32_t size;
} base_message_t;
extern const int BASE_MESSAGE_SIZE;
extern const int TIME_MESSAGE_SIZE;
int base_message_serialize(base_message_t *msg, char *data, uint32_t size);
int base_message_deserialize(base_message_t *msg, const char *data, uint32_t size);
/* Sample Hello message
{
"Arch": "x86_64",
"ClientName": "Snapclient",
"HostName": "my_hostname",
"ID": "00:11:22:33:44:55",
"Instance": 1,
"MAC": "00:11:22:33:44:55",
"OS": "Arch Linux",
"SnapStreamProtocolVersion": 2,
"Version": "0.17.1"
}
*/
typedef struct hello_message {
char *mac;
char *hostname;
char *version;
char *client_name;
char *os;
char *arch;
int instance;
char *id;
int protocol_version;
} hello_message_t;
char* hello_message_serialize(hello_message_t* msg, size_t *size);
typedef struct server_settings_message {
int32_t buffer_ms;
int32_t latency;
uint32_t volume;
bool muted;
} server_settings_message_t;
int server_settings_message_deserialize(server_settings_message_t *msg, const char *json_str);
typedef struct codec_header_message {
char *codec;
uint32_t size;
char *payload;
} codec_header_message_t;
int codec_header_message_deserialize(codec_header_message_t *msg, const char *data, uint32_t size);
void codec_header_message_free(codec_header_message_t *msg);
typedef struct wire_chunk_message {
tv_t timestamp;
uint32_t size;
char *payload;
} wire_chunk_message_t;
// TODO currently copies, could be made to not copy probably
int wire_chunk_message_deserialize(wire_chunk_message_t *msg, const char *data, uint32_t size);
void wire_chunk_message_free(wire_chunk_message_t *msg);
typedef struct time_message {
tv_t latency;
} time_message_t;
int time_message_serialize(time_message_t *msg, char *data, uint32_t size);
int time_message_deserialize(time_message_t *msg, const char *data, uint32_t size);
#endif // __SNAPCAST_H__

View File

@@ -0,0 +1,309 @@
#include "snapcast.h"
#ifdef ESP_PLATFORM
// The ESP-IDF changes the include directory for cJSON
#include <cJSON.h>
#else
#include <cjson/cJSON.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <buffer.h>
const int BASE_MESSAGE_SIZE = 26;
const int TIME_MESSAGE_SIZE = 8;
int base_message_serialize(base_message_t *msg, char *data, uint32_t size) {
write_buffer_t buffer;
int result = 0;
buffer_write_init(&buffer, data, size);
result |= buffer_write_uint16(&buffer, msg->type);
result |= buffer_write_uint16(&buffer, msg->id);
result |= buffer_write_uint16(&buffer, msg->refersTo);
result |= buffer_write_int32(&buffer, msg->sent.sec);
result |= buffer_write_int32(&buffer, msg->sent.usec);
result |= buffer_write_int32(&buffer, msg->received.sec);
result |= buffer_write_int32(&buffer, msg->received.usec);
result |= buffer_write_uint32(&buffer, msg->size);
return result;
}
int base_message_deserialize(base_message_t *msg, const char *data, uint32_t size) {
read_buffer_t buffer;
int result = 0;
buffer_read_init(&buffer, data, size);
result |= buffer_read_uint16(&buffer, &(msg->type));
result |= buffer_read_uint16(&buffer, &(msg->id));
result |= buffer_read_uint16(&buffer, &(msg->refersTo));
result |= buffer_read_int32(&buffer, &(msg->sent.sec));
result |= buffer_read_int32(&buffer, &(msg->sent.usec));
result |= buffer_read_int32(&buffer, &(msg->received.sec));
result |= buffer_read_int32(&buffer, &(msg->received.usec));
result |= buffer_read_uint32(&buffer, &(msg->size));
return result;
}
static cJSON* hello_message_to_json(hello_message_t *msg) {
cJSON *mac;
cJSON *hostname;
cJSON *version;
cJSON *client_name;
cJSON *os;
cJSON *arch;
cJSON *instance;
cJSON *id;
cJSON *protocol_version;
cJSON *json = NULL;
json = cJSON_CreateObject();
if (!json) {
goto error;
}
mac = cJSON_CreateString(msg->mac);
if (!mac) {
goto error;
}
cJSON_AddItemToObject(json, "MAC", mac);
hostname = cJSON_CreateString(msg->hostname);
if (!hostname) {
goto error;
}
cJSON_AddItemToObject(json, "HostName", hostname);
version = cJSON_CreateString(msg->version);
if (!version) {
goto error;
}
cJSON_AddItemToObject(json, "Version", version);
client_name = cJSON_CreateString(msg->client_name);
if (!client_name) {
goto error;
}
cJSON_AddItemToObject(json, "ClientName", client_name);
os = cJSON_CreateString(msg->os);
if (!os) {
goto error;
}
cJSON_AddItemToObject(json, "OS", os);
arch = cJSON_CreateString(msg->arch);
if (!arch) {
goto error;
}
cJSON_AddItemToObject(json, "Arch", arch);
instance = cJSON_CreateNumber(msg->instance);
if (!instance) {
goto error;
}
cJSON_AddItemToObject(json, "Instance", instance);
id = cJSON_CreateString(msg->id);
if (!id) {
goto error;
}
cJSON_AddItemToObject(json, "ID", id);
protocol_version = cJSON_CreateNumber(msg->protocol_version);
if (!protocol_version) {
goto error;
}
cJSON_AddItemToObject(json, "SnapStreamProtocolVersion", protocol_version);
goto end;
error:
cJSON_Delete(json);
end:
return json;
}
char* hello_message_serialize(hello_message_t* msg, size_t *size) {
int str_length, prefixed_length;
cJSON *json;
char *str = NULL;
char *prefixed_str = NULL;
json = hello_message_to_json(msg);
if (!json) {
return NULL;
}
str = cJSON_PrintUnformatted(json);
if (!str) {
return NULL;
}
cJSON_Delete(json);
str_length = strlen(str);
prefixed_length = str_length + 4;
prefixed_str = malloc(prefixed_length);
if (!prefixed_str) {
return NULL;
}
prefixed_str[0] = str_length & 0xff;
prefixed_str[1] = (str_length >> 8) & 0xff;
prefixed_str[2] = (str_length >> 16) & 0xff;
prefixed_str[3] = (str_length >> 24) & 0xff;
memcpy(&(prefixed_str[4]), str, str_length);
free(str);
*size = prefixed_length;
return prefixed_str;
}
int server_settings_message_deserialize(server_settings_message_t *msg, const char *json_str) {
int status = 1;
cJSON *value = NULL;
cJSON *json = cJSON_Parse(json_str);
if (!json) {
const char *error_ptr = cJSON_GetErrorPtr();
if (error_ptr) {
// TODO change to a macro that can be diabled
fprintf(stderr, "Error before: %s\n", error_ptr);
goto end;
}
}
if (msg == NULL) {
status = 2;
goto end;
}
value = cJSON_GetObjectItemCaseSensitive(json, "bufferMs");
if (cJSON_IsNumber(value)) {
msg->buffer_ms = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive(json, "latency");
if (cJSON_IsNumber(value)) {
msg->latency = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive(json, "volume");
if (cJSON_IsNumber(value)) {
msg->volume = value->valueint;
}
value = cJSON_GetObjectItemCaseSensitive(json, "muted");
msg->muted = cJSON_IsTrue(value);
status = 0;
end:
cJSON_Delete(json);
return status;
}
int codec_header_message_deserialize(codec_header_message_t *msg, const char *data, uint32_t size) {
read_buffer_t buffer;
uint32_t string_size;
int result = 0;
buffer_read_init(&buffer, data, size);
result |= buffer_read_uint32(&buffer, &string_size);
if (result) {
// Can't allocate the proper size string if we didn't read the size, so fail early
return 1;
}
msg->codec = malloc(string_size + 1);
if (!msg->codec) {
return 2;
}
result |= buffer_read_buffer(&buffer, msg->codec, string_size);
// Make sure the codec is a proper C string by terminating it with a null character
msg->codec[string_size] = '\0';
result |= buffer_read_uint32(&buffer, &(msg->size));
if (result) {
// Can't allocate the proper size string if we didn't read the size, so fail early
return 1;
}
msg->payload = malloc(msg->size);
if (!msg->payload) {
return 2;
}
result |= buffer_read_buffer(&buffer, msg->payload, msg->size);
return result;
}
int wire_chunk_message_deserialize(wire_chunk_message_t *msg, const char *data, uint32_t size) {
read_buffer_t buffer;
int result = 0;
buffer_read_init(&buffer, data, size);
result |= buffer_read_int32(&buffer, &(msg->timestamp.sec));
result |= buffer_read_int32(&buffer, &(msg->timestamp.usec));
result |= buffer_read_uint32(&buffer, &(msg->size));
// If there's been an error already (especially for the size bit) return early
if (result) {
return result;
}
// TODO maybe should check to see if need to free memory?
msg->payload = malloc(msg->size * sizeof(char));
// Failed to allocate the memory
if (!msg->payload) {
return 2;
}
result |= buffer_read_buffer(&buffer, msg->payload, msg->size);
return result;
}
void codec_header_message_free(codec_header_message_t *msg) {
free(msg->codec);
msg->codec = NULL;
free(msg->payload);
msg->payload = NULL;
}
void wire_chunk_message_free(wire_chunk_message_t *msg) {
if (msg->payload) {
free(msg->payload);
msg->payload = NULL;
}
}
int time_message_serialize(time_message_t *msg, char *data, uint32_t size) {
write_buffer_t buffer;
int result = 0;
buffer_write_init(&buffer, data, size);
result |= buffer_write_int32(&buffer, msg->latency.sec);
result |= buffer_write_int32(&buffer, msg->latency.usec);
return result;
}
int time_message_deserialize(time_message_t *msg, const char *data, uint32_t size) {
read_buffer_t buffer;
int result = 0;
buffer_read_init(&buffer, data, size);
result |= buffer_read_int32(&buffer, &(msg->latency.sec));
result |= buffer_read_int32(&buffer, &(msg->latency.usec));
return result;
}