Skip to content

Commit e234e51

Browse files
authored
Refactor Parser (#163)
* separate dac_control_task * move base message parsing out of main * ensure now is set like before * refactor time sync variables * move time sync message handling in own method * remove unused variables * clarify variable name * add TAG to parser * move parsing of time message to parser file * callback for server settings * server settings parsing moved * callback codec headers * move codec header parsing * moved unknown message parsing * separate wire chunk handling function * move parsing of wire chunk * parser reset function * move typed message callbacks out of parsing functions * move base message handling out of parsing function * move process_data function out of http_get_task * receive_data function * fill buffer function * smaller variable scope for receive_data * move code to connection handler * setup_network function * move setup_network to connection_handler * move resetting of netbuf into receive_data function * prepare transition to state machine for connection * connection handling as state machine * move data processing out of network state machine * move connection_ensure_byte to connection handler * create connection struct * use connection struct in connection_ensure_byte * move connection handling into base message parser * simplify base message parsing * simplify unknown message parsing * simplify time message parsing * simplify server message parsing * simplify codec header parsing * simplify wire chunk parsing * remove base_message and time_message parameters from process_data by using local scope * local scope for wire_chnk parameter * don't pass decoderChunk via parameters * use fixed length for codecString to simplify memory management * clearer parameter and variable names in codec header handling * improve codec header error handling * local scope for serverSettingsString * reorder parse_codec_header_message parameters * local scope for codecPayload
1 parent 98c439d commit e234e51

File tree

7 files changed

+1616
-2088
lines changed

7 files changed

+1616
-2088
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
idf_component_register(SRCS "snapcast.c" "player.c"
1+
idf_component_register(SRCS "snapcast.c" "player.c" "snapcast_protocol_parser.c"
22
INCLUDE_DIRS "include"
33
REQUIRES libbuffer json libmedian timefilter esp_wifi driver esp_timer)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#ifndef __SNAPCAST_PROTOCOL_PARSER_H__
2+
#define __SNAPCAST_PROTOCOL_PARSER_H__
3+
4+
#include "snapcast.h"
5+
#include "player.h" // needed for coded_type_t
6+
7+
8+
typedef struct decoderData_s {
9+
uint32_t type; // should be SNAPCAST_MESSAGE_CODEC_HEADER
10+
// or SNAPCAST_MESSAGE_WIRE_CHUNK
11+
uint8_t *inData;
12+
tv_t timestamp;
13+
uint8_t *outData;
14+
uint32_t bytes;
15+
} decoderData_t;
16+
17+
typedef int (*get_byte_callback_t)(void* connection_data, char* buffer);
18+
19+
typedef struct {
20+
get_byte_callback_t get_byte_function;
21+
void* get_byte_context;
22+
} snapcast_protocol_parser_t;
23+
24+
typedef enum {
25+
PARSER_COMPLETE = 0,
26+
PARSER_INCOMPLETE,
27+
PARSER_CRITICAL_ERROR,
28+
PARSER_CONNECTION_ERROR,
29+
} parser_return_state_t;
30+
31+
parser_return_state_t parse_base_message(snapcast_protocol_parser_t* parser,
32+
base_message_t* base_message_rx);
33+
34+
parser_return_state_t parse_wire_chunk_message(snapcast_protocol_parser_t* parser,
35+
base_message_t* base_message_rx,
36+
bool received_codec_header,
37+
codec_type_t codec,
38+
pcm_chunk_message_t** pcmData,
39+
wire_chunk_message_t* wire_chnk,
40+
decoderData_t* decoderChunk);
41+
42+
parser_return_state_t parse_codec_header_message(
43+
snapcast_protocol_parser_t* parser,
44+
bool* received_codec_header, codec_type_t* codec,
45+
char** codecPayload, uint32_t* codecPayloadLen);
46+
47+
parser_return_state_t parse_sever_settings_message(
48+
snapcast_protocol_parser_t* parser, base_message_t* base_message_rx,
49+
server_settings_message_t* server_settings_message);
50+
51+
parser_return_state_t parse_time_message(snapcast_protocol_parser_t* parser,
52+
base_message_t* base_message_rx,
53+
time_message_t* time_message_rx);
54+
55+
parser_return_state_t parse_unknown_message(snapcast_protocol_parser_t* parser,
56+
base_message_t* base_message_rx);
57+
58+
#endif // __SNAPCAST_PROTOCOL_PARSER_H__
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
#include "snapcast_protocol_parser.h"
2+
3+
#include "esp_log.h"
4+
5+
// MACROS for reading from connection
6+
#define READ_BYTE(parser, dest) \
7+
do { \
8+
char _byte; \
9+
if ((parser)->get_byte_function((parser)->get_byte_context, &_byte) != 0) { \
10+
return PARSER_CONNECTION_ERROR; \
11+
} \
12+
(dest) = _byte; \
13+
} while(0)
14+
15+
#define READ_UINT16_LE(parser, dest) \
16+
do { \
17+
char _bytes[2]; \
18+
if ((parser)->get_byte_function((parser)->get_byte_context, &_bytes[0]) != 0 || \
19+
(parser)->get_byte_function((parser)->get_byte_context, &_bytes[1]) != 0) { \
20+
return PARSER_CONNECTION_ERROR; \
21+
} \
22+
(dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8); \
23+
} while(0)
24+
25+
#define READ_UINT32_LE(parser, dest) \
26+
do { \
27+
char _bytes[4]; \
28+
for (int _i = 0; _i < 4; _i++) { \
29+
if ((parser)->get_byte_function((parser)->get_byte_context, &_bytes[_i]) != 0) { \
30+
return PARSER_CONNECTION_ERROR; \
31+
} \
32+
} \
33+
(dest) = (_bytes[0] & 0xFF) | ((_bytes[1] & 0xFF) << 8) | \
34+
((_bytes[2] & 0xFF) << 16) | ((_bytes[3] & 0xFF) << 24); \
35+
} while(0)
36+
37+
#define READ_TIMESTAMP(parser, ts) \
38+
do { \
39+
READ_UINT32_LE(parser, (ts).sec); \
40+
READ_UINT32_LE(parser, (ts).usec); \
41+
} while(0)
42+
43+
#define READ_DATA(parser, dest, len) \
44+
do { \
45+
for (uint32_t _i = 0; _i < (len); _i++) { \
46+
if ((parser)->get_byte_function((parser)->get_byte_context, &(dest)[_i]) != 0) { \
47+
return PARSER_CONNECTION_ERROR; \
48+
} \
49+
} \
50+
} while(0)
51+
52+
#define READ_DATA_WITH_CLEANUP(parser, dest, len, cleanup) \
53+
do { \
54+
for (uint32_t _i = 0; _i < (len); _i++) { \
55+
if ((parser)->get_byte_function((parser)->get_byte_context, &(dest)[_i]) != 0) { \
56+
cleanup; \
57+
return PARSER_CONNECTION_ERROR; \
58+
} \
59+
} \
60+
} while(0)
61+
62+
static const char* TAG = "SNAPCAST_PROTOCOL_PARSER";
63+
64+
parser_return_state_t parse_base_message(snapcast_protocol_parser_t* parser,
65+
base_message_t* base_message_rx) {
66+
READ_UINT16_LE(parser, base_message_rx->type);
67+
READ_UINT16_LE(parser, base_message_rx->id);
68+
READ_UINT16_LE(parser, base_message_rx->refersTo);
69+
READ_TIMESTAMP(parser, base_message_rx->sent);
70+
READ_TIMESTAMP(parser, base_message_rx->received);
71+
READ_UINT32_LE(parser, base_message_rx->size);
72+
73+
return PARSER_COMPLETE;
74+
}
75+
76+
77+
78+
parser_return_state_t parse_wire_chunk_message(snapcast_protocol_parser_t* parser,
79+
base_message_t* base_message_rx,
80+
bool received_codec_header,
81+
codec_type_t codec,
82+
pcm_chunk_message_t** pcmData,
83+
wire_chunk_message_t* wire_chnk,
84+
decoderData_t* decoderChunk) {
85+
86+
READ_TIMESTAMP(parser, wire_chnk->timestamp);
87+
READ_UINT32_LE(parser, wire_chnk->size);
88+
89+
// TODO: we could use wire chunk directly maybe?
90+
decoderChunk->bytes = wire_chnk->size;
91+
while (!decoderChunk->inData) {
92+
decoderChunk->inData = (uint8_t*)malloc(decoderChunk->bytes);
93+
if (!decoderChunk->inData) {
94+
ESP_LOGW(TAG,
95+
"malloc decoderChunk->inData failed, wait "
96+
"1ms and try again");
97+
98+
vTaskDelay(pdMS_TO_TICKS(1));
99+
}
100+
}
101+
102+
uint32_t payloadOffset = 0;
103+
uint32_t tmpData = 0;
104+
int32_t payloadDataShift = 0;
105+
size_t tmp_size = base_message_rx->size - 12;
106+
107+
if (received_codec_header == true) {
108+
switch (codec) {
109+
case OPUS:
110+
case FLAC: {
111+
READ_DATA(parser, (char*)decoderChunk->inData, tmp_size);
112+
payloadOffset += tmp_size;
113+
decoderChunk->outData = NULL;
114+
decoderChunk->type = SNAPCAST_MESSAGE_WIRE_CHUNK;
115+
116+
break;
117+
}
118+
119+
case PCM: {
120+
size_t _tmp = tmp_size;
121+
122+
if (*pcmData == NULL) {
123+
if (allocate_pcm_chunk_memory(pcmData, wire_chnk->size) < 0) {
124+
*pcmData = NULL;
125+
}
126+
127+
tmpData = 0;
128+
payloadDataShift = 3;
129+
payloadOffset = 0;
130+
}
131+
132+
while (_tmp--) {
133+
char tmp_val;
134+
READ_BYTE(parser, tmp_val);
135+
tmpData |= ((uint32_t)tmp_val << (8 * payloadDataShift));
136+
137+
payloadDataShift--;
138+
if (payloadDataShift < 0) {
139+
payloadDataShift = 3;
140+
141+
if ((*pcmData) && ((*pcmData)->fragment->payload)) {
142+
volatile uint32_t* sample;
143+
uint8_t dummy1;
144+
uint32_t dummy2 = 0;
145+
146+
// TODO: find a more
147+
// clever way to do this,
148+
// best would be to
149+
// actually store it the
150+
// right way in the first
151+
// place
152+
dummy1 = tmpData >> 24;
153+
dummy2 |= (uint32_t)dummy1 << 16;
154+
dummy1 = tmpData >> 16;
155+
dummy2 |= (uint32_t)dummy1 << 24;
156+
dummy1 = tmpData >> 8;
157+
dummy2 |= (uint32_t)dummy1 << 0;
158+
dummy1 = tmpData >> 0;
159+
dummy2 |= (uint32_t)dummy1 << 8;
160+
tmpData = dummy2;
161+
162+
sample = (volatile uint32_t *)(&((*pcmData)->fragment->payload[payloadOffset]));
163+
*sample = (volatile uint32_t)tmpData;
164+
165+
payloadOffset += 4;
166+
}
167+
168+
tmpData = 0;
169+
}
170+
}
171+
172+
break;
173+
}
174+
default: {
175+
ESP_LOGE(TAG, "Decoder (1) not supported");
176+
return PARSER_CRITICAL_ERROR;
177+
}
178+
}
179+
}
180+
181+
if (received_codec_header == true) {
182+
return PARSER_COMPLETE;
183+
} else {
184+
return PARSER_INCOMPLETE; // TODO: right return value?
185+
}
186+
}
187+
188+
parser_return_state_t parse_codec_header_message(
189+
snapcast_protocol_parser_t* parser,
190+
bool* received_codec_header, codec_type_t* codec,
191+
char** codecPayload, uint32_t* codecPayloadLen) {
192+
*received_codec_header = false;
193+
194+
uint32_t codecStringLen = 0;
195+
READ_UINT32_LE(parser, codecStringLen);
196+
197+
char codecString[8]; // longest supported string has 4 + 1 chars
198+
199+
if (codecStringLen + 1 > sizeof(codecString)) {
200+
READ_DATA(parser, codecString, sizeof(codecString)-1);
201+
codecString[sizeof(codecString)-1] = 0; // null terminate
202+
ESP_LOGE(TAG, "Codec : %s... not supported %lu", codecStringLen);
203+
ESP_LOGI(TAG,
204+
"Change encoder codec to "
205+
"opus, flac or pcm in "
206+
"/etc/snapserver.conf on "
207+
"server");
208+
return PARSER_CRITICAL_ERROR;
209+
}
210+
READ_DATA(parser, codecString, codecStringLen);
211+
212+
// NULL terminate string
213+
codecString[codecStringLen] = 0;
214+
215+
// ESP_LOGI (TAG, "got codec string: %s", tmp);
216+
217+
if (strcmp(codecString, "opus") == 0) {
218+
*codec = OPUS;
219+
} else if (strcmp(codecString, "flac") == 0) {
220+
*codec = FLAC;
221+
} else if (strcmp(codecString, "pcm") == 0) {
222+
*codec = PCM;
223+
} else {
224+
*codec = NONE;
225+
226+
ESP_LOGI(TAG, "Codec : %s not supported", codecString);
227+
ESP_LOGI(TAG,
228+
"Change encoder codec to "
229+
"opus, flac or pcm in "
230+
"/etc/snapserver.conf on "
231+
"server");
232+
233+
return PARSER_CRITICAL_ERROR;
234+
}
235+
236+
//
237+
READ_UINT32_LE(parser, *codecPayloadLen);
238+
239+
*codecPayload = malloc(*codecPayloadLen); // allocate memory
240+
// for codec payload
241+
if (*codecPayload == NULL) {
242+
ESP_LOGE(TAG,
243+
"couldn't get memory "
244+
"for codec payload");
245+
246+
return PARSER_CRITICAL_ERROR;
247+
}
248+
249+
READ_DATA(parser, *codecPayload, *codecPayloadLen);
250+
251+
*received_codec_header = true;
252+
253+
return PARSER_COMPLETE;
254+
}
255+
256+
parser_return_state_t parse_sever_settings_message(
257+
snapcast_protocol_parser_t* parser, base_message_t* base_message_rx,
258+
server_settings_message_t* server_settings_message) {
259+
uint32_t typedMsgLen;
260+
char* serverSettingsString = NULL;
261+
READ_UINT32_LE(parser, typedMsgLen);
262+
263+
// ESP_LOGI(TAG,"server settings string is %lu long", typedMsgLen);
264+
265+
// now get some memory for server settings string
266+
serverSettingsString = malloc(typedMsgLen + 1);
267+
if (serverSettingsString == NULL) {
268+
ESP_LOGE(TAG,
269+
"couldn't get memory for "
270+
"server settings string");
271+
return PARSER_CRITICAL_ERROR;
272+
}
273+
274+
size_t tmpSize = base_message_rx->size - 4;
275+
// TODO: should there be an assert that tmpSize <= typedMsgLen?
276+
277+
READ_DATA_WITH_CLEANUP(parser, serverSettingsString, tmpSize,
278+
free(serverSettingsString) // This will be executed on error before returning
279+
);
280+
281+
serverSettingsString[typedMsgLen] = 0;
282+
283+
// ESP_LOGI(TAG, "got string: %s",
284+
// serverSettingsString);
285+
286+
int deserialization_result;
287+
288+
deserialization_result = server_settings_message_deserialize(
289+
server_settings_message, serverSettingsString);
290+
291+
free(serverSettingsString);
292+
293+
if (deserialization_result) {
294+
ESP_LOGE(TAG,
295+
"Failed to read server "
296+
"settings: %d",
297+
deserialization_result);
298+
return PARSER_CRITICAL_ERROR;
299+
}
300+
301+
return PARSER_COMPLETE; // do callback
302+
303+
}
304+
305+
306+
parser_return_state_t parse_time_message(snapcast_protocol_parser_t* parser,
307+
base_message_t* base_message_rx,
308+
time_message_t* time_message_rx) {
309+
READ_TIMESTAMP(parser, time_message_rx->latency);
310+
311+
if (base_message_rx->size < 8) { // TODO: how to handle this case? Do we NEED to check?
312+
ESP_LOGE(TAG,
313+
"error time message, this shouldn't happen! %d %ld",
314+
8, base_message_rx->size);
315+
return PARSER_INCOMPLETE; // use this return value as "ignore"
316+
}
317+
318+
// ESP_LOGI(TAG, "done time message");
319+
return PARSER_COMPLETE; // do callback
320+
}
321+
322+
parser_return_state_t parse_unknown_message(snapcast_protocol_parser_t* parser,
323+
base_message_t* base_message_rx) {
324+
// For unknown messages, we need to consume all remaining bytes
325+
char dummy_byte;
326+
for (uint32_t i = 0; i < base_message_rx->size; i++) {
327+
READ_BYTE(parser, dummy_byte);
328+
}
329+
330+
ESP_LOGI(TAG, "done unknown typed message %d", base_message_rx->type);
331+
332+
return PARSER_COMPLETE;
333+
}

0 commit comments

Comments
 (0)