From cc9a4c8ccafc035f185afc5df496041bfe05a6d0 Mon Sep 17 00:00:00 2001 From: Christian Glombek Date: Wed, 21 Jun 2023 06:03:04 +0200 Subject: [PATCH] module-raop: Add TLV utils Adds MIT-licensed TLV helper utilities, taken from https://github.com/maximkulkin/esp-homekit and adapted for usage in PipeWire. --- src/modules/meson.build | 3 +- src/modules/module-raop/tlv.c | 183 ++++++++++++++++++++++++++++++++++ src/modules/module-raop/tlv.h | 69 +++++++++++++ 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 src/modules/module-raop/tlv.c create mode 100644 src/modules/module-raop/tlv.h diff --git a/src/modules/meson.build b/src/modules/meson.build index 9e38742cd..22f1cce85 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -605,7 +605,8 @@ build_module_raop = openssl_lib.found() if build_module_raop pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', [ 'module-raop-sink.c', - 'module-raop/rtsp-client.c' ], + 'module-raop/rtsp-client.c', + 'module-raop/tlv.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, diff --git a/src/modules/module-raop/tlv.c b/src/modules/module-raop/tlv.c new file mode 100644 index 000000000..9d3990d5b --- /dev/null +++ b/src/modules/module-raop/tlv.c @@ -0,0 +1,183 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2018 Maxim Kulkin */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include + +#include "tlv.h" + +tlv_values_t * tlv_new(void) +{ + tlv_values_t *values = malloc(sizeof(tlv_values_t)); + if (!values) + return NULL; + + values->head = NULL; + return values; +} + +void tlv_free(tlv_values_t *values) +{ + if (!values) + return; + + tlv_t *t = values->head; + while (t) { + tlv_t *t2 = t; + t = t->next; + if (t2->value) + free(t2->value); + free(t2); + } + free(values); +} + +static int tlv_add_value_(tlv_values_t *values, uint8_t type, uint8_t *value, size_t size) +{ + tlv_t *tlv = malloc(sizeof(tlv_t)); + if (!tlv) { + return TLV_ERROR_MEMORY; + } + tlv->type = type; + tlv->size = size; + tlv->value = value; + tlv->next = NULL; + + if (!values->head) { + values->head = tlv; + } else { + tlv_t *t = values->head; + while (t->next) { + t = t->next; + } + t->next = tlv; + } + + return 0; +} + +int tlv_add_value(tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size) +{ + uint8_t *data = NULL; + int ret; + if (size) { + data = malloc(size); + if (!data) { + return TLV_ERROR_MEMORY; + } + memcpy(data, value, size); + } + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) + free(data); + return ret; +} + +tlv_t * tlv_get_value(const tlv_values_t *values, uint8_t type) +{ + tlv_t *t = values->head; + while (t) { + if (t->type == type) + return t; + t = t->next; + } + return NULL; +} + +int tlv_format(const tlv_values_t *values, uint8_t *buffer, size_t *size) +{ + size_t required_size = 0; + tlv_t *t = values->head; + while (t) { + required_size += t->size + 2 * ((t->size + 254) / 255); + t = t->next; + } + + if (*size < required_size) { + *size = required_size; + return TLV_ERROR_INSUFFICIENT_SIZE; + } + + *size = required_size; + + t = values->head; + while (t) { + uint8_t *data = t->value; + if (!t->size) { + buffer[0] = t->type; + buffer[1] = 0; + buffer += 2; + t = t->next; + continue; + } + + size_t remaining = t->size; + + while (remaining) { + buffer[0] = t->type; + size_t chunk_size = (remaining > 255) ? 255 : remaining; + buffer[1] = chunk_size; + memcpy(&buffer[2], data, chunk_size); + remaining -= chunk_size; + buffer += chunk_size + 2; + data += chunk_size; + } + + t = t->next; + } + + return 0; +} + +int tlv_parse(const uint8_t *buffer, size_t length, tlv_values_t *values) +{ + size_t i = 0; + int ret; + while (i < length) { + uint8_t type = buffer[i]; + size_t size = 0; + uint8_t *data = NULL; + + // scan TLVs to accumulate total size of subsequent TLVs with same type (chunked data) + size_t j = i; + while (j < length && buffer[j] == type && buffer[j+1] == 255) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + j += chunk_size + 2; + } + if (j < length && buffer[j] == type) { + size_t chunk_size = buffer[j+1]; + size += chunk_size; + } + + // allocate memory to hold all pieces of chunked data and copy data there + if (size == 0) + return -2; + + data = malloc(size); + if (!data) + return TLV_ERROR_MEMORY; + + uint8_t *p = data; + + size_t remaining = size; + while (remaining) { + size_t chunk_size = buffer[i+1]; + memcpy(p, &buffer[i+2], chunk_size); + p += chunk_size; + i += chunk_size + 2; + remaining -= chunk_size; + } + + ret = tlv_add_value_(values, type, data, size); + if (ret < 0) { + free(data); + return ret; + } + } + + return 0; +} diff --git a/src/modules/module-raop/tlv.h b/src/modules/module-raop/tlv.h new file mode 100644 index 000000000..0d7129e11 --- /dev/null +++ b/src/modules/module-raop/tlv.h @@ -0,0 +1,69 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2018 Maxim Kulkin */ +/* SPDX-License-Identifier: MIT */ + +#include + +#define TLV_ERROR_MEMORY -1 +#define TLV_ERROR_INSUFFICIENT_SIZE -2 + +typedef enum { + AP2_TLVType_Method = 0, // (integer) Method to use for pairing. See PairMethod + AP2_TLVType_Identifier = 1, // (UTF-8) Identifier for authentication + AP2_TLVType_Salt = 2, // (bytes) 16+ bytes of random salt + AP2_TLVType_PublicKey = 3, // (bytes) Curve25519, SRP public key or signed Ed25519 key + AP2_TLVType_Proof = 4, // (bytes) Ed25519 or SRP proof + AP2_TLVType_EncryptedData = 5, // (bytes) Encrypted data with auth tag at end + AP2_TLVType_State = 6, // (integer) State of the pairing process. 1=M1, 2=M2, etc. + AP2_TLVType_Error = 7, // (integer) Error code. Must only be present if error code is + // not 0. See AP2_TLVError + AP2_TLVType_RetryDelay = 8, // (integer) Seconds to delay until retrying a setup code + AP2_TLVType_Certificate = 9, // (bytes) X.509 Certificate + AP2_TLVType_Signature = 10, // (bytes) Ed25519 + AP2_TLVType_Permissions = 11, // (integer) Bit value describing permissions of the controller + // being added. + // None (0x00): Regular user + // Bit 1 (0x01): Admin that is able to add and remove + // pairings against the accessory + AP2_TLVType_FragmentData = 13, // (bytes) Non-last fragment of data. If length is 0, + // it's an ACK. + AP2_TLVType_FragmentLast = 14, // (bytes) Last fragment of data + AP2_TLVType_Flags = 19, // Added from airplay2_receiver + AP2_TLVType_Separator = 0xff, +} AP2_TLVType; + +typedef enum { + AP2_TLVError_Unknown = 1, // Generic error to handle unexpected errors + AP2_TLVError_Authentication = 2, // Setup code or signature verification failed + AP2_TLVError_Backoff = 3, // Client must look at the retry delay TLV item and + // wait that many seconds before retrying + AP2_TLVError_MaxPeers = 4, // Server cannot accept any more pairings + AP2_TLVError_MaxTries = 5, // Server reached its maximum number of + // authentication attempts + AP2_TLVError_Unavailable = 6, // Server pairing method is unavailable + AP2_TLVError_Busy = 7, // Server is busy and cannot accept a pairing + // request at this time +} AP2_TLVError; + +typedef struct _tlv { + struct _tlv *next; + uint8_t type; + uint8_t *value; + size_t size; +} tlv_t; + +typedef struct { + tlv_t *head; +} tlv_values_t; + +tlv_values_t *tlv_new(void); + +void tlv_free(tlv_values_t *values); + +int tlv_add_value(tlv_values_t *values, uint8_t type, const uint8_t *value, size_t size); + +tlv_t *tlv_get_value(const tlv_values_t *values, uint8_t type); + +int tlv_format(const tlv_values_t *values, uint8_t *buffer, size_t *size); + +int tlv_parse(const uint8_t *buffer, size_t length, tlv_values_t *values);