|
| 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