lightlib
This commit is contained in:
2
components/lightsnapcast/component.mk
Normal file
2
components/lightsnapcast/component.mk
Normal file
@@ -0,0 +1,2 @@
|
||||
COMPONENT_SRCDIRS := .
|
||||
# CFLAGS +=
|
||||
108
components/lightsnapcast/include/snapcast.h
Normal file
108
components/lightsnapcast/include/snapcast.h
Normal 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__
|
||||
309
components/lightsnapcast/snapcast.c
Normal file
309
components/lightsnapcast/snapcast.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user