diff --git a/src/a2dp-aac.c b/src/a2dp-aac.c index 550e3fffe..a9f12e871 100644 --- a/src/a2dp-aac.c +++ b/src/a2dp-aac.c @@ -552,6 +552,43 @@ static int a2dp_aac_configuration_select( return 0; } +static int a2dp_aac_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_aac_t *conf = configuration; + a2dp_aac_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + switch (conf_v.object_type) { + case AAC_OBJECT_TYPE_MPEG2_AAC_LC: + case AAC_OBJECT_TYPE_MPEG4_AAC_LC: + case AAC_OBJECT_TYPE_MPEG4_AAC_LTP: + case AAC_OBJECT_TYPE_MPEG4_AAC_SCA: + break; + default: + debug("AAC: Invalid object type: %#x", conf->object_type); + return A2DP_CHECK_ERR_OBJECT_TYPE; + } + + const uint16_t conf_frequency = AAC_GET_FREQUENCY(conf_v); + if (a2dp_sampling_lookup(a2dp_aac_samplings, conf_frequency) == NULL) { + debug("AAC: Invalid sampling frequency: %#x", AAC_GET_FREQUENCY(*conf)); + return A2DP_CHECK_ERR_SAMPLING; + } + + if (a2dp_channel_mode_lookup(a2dp_aac_channels, conf_v.channels) == NULL) { + debug("AAC: Invalid channel mode: %#x", conf->channels); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + return A2DP_CHECK_OK; +} + static int a2dp_aac_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -630,11 +667,10 @@ struct a2dp_codec a2dp_aac_source = { AAC_INIT_BITRATE(320000) }, .capabilities_size = sizeof(a2dp_aac_t), - .channels[0] = a2dp_aac_channels, - .samplings[0] = a2dp_aac_samplings, .init = a2dp_aac_source_init, .capabilities_filter = a2dp_aac_capabilities_filter, .configuration_select = a2dp_aac_configuration_select, + .configuration_check = a2dp_aac_configuration_check, .transport_init = a2dp_aac_transport_init, .transport_start = a2dp_aac_source_transport_start, .enabled = true, @@ -691,11 +727,10 @@ struct a2dp_codec a2dp_aac_sink = { AAC_INIT_BITRATE(320000) }, .capabilities_size = sizeof(a2dp_aac_t), - .channels[0] = a2dp_aac_channels, - .samplings[0] = a2dp_aac_samplings, .init = a2dp_aac_sink_init, .capabilities_filter = a2dp_aac_capabilities_filter, .configuration_select = a2dp_aac_configuration_select, + .configuration_check = a2dp_aac_configuration_check, .transport_init = a2dp_aac_transport_init, .transport_start = a2dp_aac_sink_transport_start, .enabled = true, diff --git a/src/a2dp-aptx-hd.c b/src/a2dp-aptx-hd.c index 5ed7e6f61..091aaf15e 100644 --- a/src/a2dp-aptx-hd.c +++ b/src/a2dp-aptx-hd.c @@ -313,6 +313,31 @@ static int a2dp_aptx_hd_configuration_select( return 0; } +static int a2dp_aptx_hd_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_aptx_hd_t *conf = configuration; + a2dp_aptx_hd_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + if (a2dp_sampling_lookup(a2dp_aptx_hd_samplings, conf_v.aptx.frequency) == NULL) { + debug("apt-X HD: Invalid sampling frequency: %#x", conf->aptx.frequency); + return A2DP_CHECK_ERR_SAMPLING; + } + + if (a2dp_channel_mode_lookup(a2dp_aptx_hd_channels, conf_v.aptx.channel_mode) == NULL) { + debug("apt-X HD: Invalid channel mode: %#x", conf->aptx.channel_mode); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + return A2DP_CHECK_OK; +} + static int a2dp_aptx_hd_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -361,10 +386,9 @@ struct a2dp_codec a2dp_aptx_hd_source = { APTX_SAMPLING_FREQ_48000, }, .capabilities_size = sizeof(a2dp_aptx_hd_t), - .channels[0] = a2dp_aptx_hd_channels, - .samplings[0] = a2dp_aptx_hd_samplings, .init = a2dp_aptx_hd_source_init, .configuration_select = a2dp_aptx_hd_configuration_select, + .configuration_check = a2dp_aptx_hd_configuration_check, .transport_init = a2dp_aptx_hd_transport_init, .transport_start = a2dp_aptx_hd_source_transport_start, }; @@ -392,9 +416,8 @@ struct a2dp_codec a2dp_aptx_hd_sink = { APTX_SAMPLING_FREQ_48000, }, .capabilities_size = sizeof(a2dp_aptx_hd_t), - .channels[0] = a2dp_aptx_hd_channels, - .samplings[0] = a2dp_aptx_hd_samplings, .configuration_select = a2dp_aptx_hd_configuration_select, + .configuration_check = a2dp_aptx_hd_configuration_check, .transport_init = a2dp_aptx_hd_transport_init, .transport_start = a2dp_aptx_hd_sink_transport_start, }; diff --git a/src/a2dp-aptx.c b/src/a2dp-aptx.c index 28684c0f3..ebc3fda50 100644 --- a/src/a2dp-aptx.c +++ b/src/a2dp-aptx.c @@ -276,6 +276,31 @@ static int a2dp_aptx_configuration_select( return 0; } +static int a2dp_aptx_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_aptx_t *conf = configuration; + a2dp_aptx_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + if (a2dp_sampling_lookup(a2dp_aptx_samplings, conf_v.frequency) == NULL) { + debug("apt-X: Invalid sampling frequency: %#x", conf->frequency); + return A2DP_CHECK_ERR_SAMPLING; + } + + if (a2dp_channel_mode_lookup(a2dp_aptx_channels, conf_v.channel_mode) == NULL) { + debug("apt-X: Invalid channel mode: %#x", conf->channel_mode); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + return A2DP_CHECK_OK; +} + static int a2dp_aptx_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -324,10 +349,9 @@ struct a2dp_codec a2dp_aptx_source = { APTX_SAMPLING_FREQ_48000, }, .capabilities_size = sizeof(a2dp_aptx_t), - .channels[0] = a2dp_aptx_channels, - .samplings[0] = a2dp_aptx_samplings, .init = a2dp_aptx_source_init, .configuration_select = a2dp_aptx_configuration_select, + .configuration_check = a2dp_aptx_configuration_check, .transport_init = a2dp_aptx_transport_init, .transport_start = a2dp_aptx_source_transport_start, }; @@ -355,9 +379,8 @@ struct a2dp_codec a2dp_aptx_sink = { APTX_SAMPLING_FREQ_48000, }, .capabilities_size = sizeof(a2dp_aptx_t), - .channels[0] = a2dp_aptx_channels, - .samplings[0] = a2dp_aptx_samplings, .configuration_select = a2dp_aptx_configuration_select, + .configuration_check = a2dp_aptx_configuration_check, .transport_init = a2dp_aptx_transport_init, .transport_start = a2dp_aptx_sink_transport_start, }; diff --git a/src/a2dp-faststream.c b/src/a2dp-faststream.c index 3535b6232..ac812b556 100644 --- a/src/a2dp-faststream.c +++ b/src/a2dp-faststream.c @@ -289,6 +289,40 @@ static int a2dp_faststream_configuration_select( return 0; } +static int a2dp_faststream_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_faststream_t *conf = configuration; + a2dp_faststream_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + switch (conf_v.direction) { + case FASTSTREAM_DIRECTION_MUSIC: + case FASTSTREAM_DIRECTION_VOICE: + break; + default: + debug("FastStream: Invalid direction: %#x", conf->direction); + return A2DP_CHECK_ERR_DIRECTIONS; + } + + if (a2dp_sampling_lookup(a2dp_faststream_samplings_voice, conf_v.frequency_voice) == NULL) { + debug("FastStream: Invalid voice sampling frequency: %#x", conf->frequency_voice); + return A2DP_CHECK_ERR_SAMPLING_VOICE; + } + + if (a2dp_sampling_lookup(a2dp_faststream_samplings_music, conf_v.frequency_music) == NULL) { + debug("FastStream: Invalid music sampling frequency: %#x", conf->frequency_music); + return A2DP_CHECK_ERR_SAMPLING_MUSIC; + } + + return A2DP_CHECK_OK; +} + static int a2dp_faststream_transport_init(struct ba_transport *t) { if (t->a2dp.configuration.faststream.direction & FASTSTREAM_DIRECTION_MUSIC) { @@ -356,10 +390,9 @@ struct a2dp_codec a2dp_faststream_source = { FASTSTREAM_SAMPLING_FREQ_VOICE_16000, }, .capabilities_size = sizeof(a2dp_faststream_t), - .samplings[0] = a2dp_faststream_samplings_music, - .samplings[1] = a2dp_faststream_samplings_voice, .init = a2dp_faststream_source_init, .configuration_select = a2dp_faststream_configuration_select, + .configuration_check = a2dp_faststream_configuration_check, .transport_init = a2dp_faststream_transport_init, .transport_start = a2dp_faststream_source_transport_start, }; @@ -392,9 +425,8 @@ struct a2dp_codec a2dp_faststream_sink = { FASTSTREAM_SAMPLING_FREQ_VOICE_16000, }, .capabilities_size = sizeof(a2dp_faststream_t), - .samplings[0] = a2dp_faststream_samplings_music, - .samplings[1] = a2dp_faststream_samplings_voice, .configuration_select = a2dp_faststream_configuration_select, + .configuration_check = a2dp_faststream_configuration_check, .transport_init = a2dp_faststream_transport_init, .transport_start = a2dp_faststream_sink_transport_start, }; diff --git a/src/a2dp-lc3plus.c b/src/a2dp-lc3plus.c index b9bfb8303..37be6edf4 100644 --- a/src/a2dp-lc3plus.c +++ b/src/a2dp-lc3plus.c @@ -590,6 +590,42 @@ static int a2dp_lc3plus_configuration_select( return 0; } +static int a2dp_lc3plus_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_lc3plus_t *conf = configuration; + a2dp_lc3plus_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + switch (conf_v.frame_duration) { + case LC3PLUS_FRAME_DURATION_025: + case LC3PLUS_FRAME_DURATION_050: + case LC3PLUS_FRAME_DURATION_100: + break; + default: + debug("LC3plus: Invalid frame duration: %#x", conf->frame_duration); + return A2DP_CHECK_ERR_FRAME_DURATION; + } + + if (a2dp_channel_mode_lookup(a2dp_lc3plus_channels, conf_v.channels) == NULL) { + debug("LC3plus: Invalid channel mode: %#x", conf->channels); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + uint16_t conf_frequency = LC3PLUS_GET_FREQUENCY(conf_v); + if (a2dp_sampling_lookup(a2dp_lc3plus_samplings, conf_frequency) == NULL) { + debug("LC3plus: Invalid sampling frequency: %#x", LC3PLUS_GET_FREQUENCY(*conf)); + return A2DP_CHECK_ERR_SAMPLING; + } + + return A2DP_CHECK_OK; +} + static int a2dp_lc3plus_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -639,10 +675,9 @@ struct a2dp_codec a2dp_lc3plus_source = { LC3PLUS_SAMPLING_FREQ_96000) }, .capabilities_size = sizeof(a2dp_lc3plus_t), - .channels[0] = a2dp_lc3plus_channels, - .samplings[0] = a2dp_lc3plus_samplings, .init = a2dp_lc3plus_source_init, .configuration_select = a2dp_lc3plus_configuration_select, + .configuration_check = a2dp_lc3plus_configuration_check, .transport_init = a2dp_lc3plus_transport_init, .transport_start = a2dp_lc3plus_source_transport_start, }; @@ -669,9 +704,8 @@ struct a2dp_codec a2dp_lc3plus_sink = { LC3PLUS_SAMPLING_FREQ_96000) }, .capabilities_size = sizeof(a2dp_lc3plus_t), - .channels[0] = a2dp_lc3plus_channels, - .samplings[0] = a2dp_lc3plus_samplings, .configuration_select = a2dp_lc3plus_configuration_select, + .configuration_check = a2dp_lc3plus_configuration_check, .transport_init = a2dp_lc3plus_transport_init, .transport_start = a2dp_lc3plus_sink_transport_start, }; diff --git a/src/a2dp-ldac.c b/src/a2dp-ldac.c index 63c2f996f..b6d23f862 100644 --- a/src/a2dp-ldac.c +++ b/src/a2dp-ldac.c @@ -368,6 +368,31 @@ static int a2dp_ldac_configuration_select( return 0; } +static int a2dp_ldac_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_ldac_t *conf = configuration; + a2dp_ldac_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + if (a2dp_sampling_lookup(a2dp_ldac_samplings, conf_v.frequency) == NULL) { + debug("LDAC: Invalid sampling frequency: %#x", conf->frequency); + return A2DP_CHECK_ERR_SAMPLING; + } + + if (a2dp_channel_mode_lookup(a2dp_ldac_channels, conf_v.channel_mode) == NULL) { + debug("LDAC: Invalid channel mode: %#x", conf->channel_mode); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + return A2DP_CHECK_OK; +} + static int a2dp_ldac_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -420,10 +445,9 @@ struct a2dp_codec a2dp_ldac_source = { LDAC_SAMPLING_FREQ_96000, }, .capabilities_size = sizeof(a2dp_ldac_t), - .channels[0] = a2dp_ldac_channels, - .samplings[0] = a2dp_ldac_samplings, .init = a2dp_ldac_source_init, .configuration_select = a2dp_ldac_configuration_select, + .configuration_check = a2dp_ldac_configuration_check, .transport_init = a2dp_ldac_transport_init, .transport_start = a2dp_ldac_source_transport_start, }; @@ -453,9 +477,8 @@ struct a2dp_codec a2dp_ldac_sink = { LDAC_SAMPLING_FREQ_96000, }, .capabilities_size = sizeof(a2dp_ldac_t), - .channels[0] = a2dp_ldac_channels, - .samplings[0] = a2dp_ldac_samplings, .configuration_select = a2dp_ldac_configuration_select, + .configuration_check = a2dp_ldac_configuration_check, .transport_init = a2dp_ldac_transport_init, .transport_start = a2dp_ldac_sink_transport_start, }; diff --git a/src/a2dp-mpeg.c b/src/a2dp-mpeg.c index 173550bf2..09864a6fc 100644 --- a/src/a2dp-mpeg.c +++ b/src/a2dp-mpeg.c @@ -522,6 +522,41 @@ static int a2dp_mpeg_configuration_select( return 0; } +static int a2dp_mpeg_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_mpeg_t *conf = configuration; + a2dp_mpeg_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + switch (conf_v.layer) { + case MPEG_LAYER_MP1: + case MPEG_LAYER_MP2: + case MPEG_LAYER_MP3: + break; + default: + debug("MPEG: Invalid layer: %#x", conf->layer); + return A2DP_CHECK_ERR_MPEG_LAYER; + } + + if (a2dp_channel_mode_lookup(a2dp_mpeg_channels, conf_v.channel_mode) == NULL) { + debug("MPEG: Invalid channel mode: %#x", conf->channel_mode); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + if (a2dp_sampling_lookup(a2dp_mpeg_samplings, conf_v.frequency) == NULL) { + debug("MPEG: Invalid sampling frequency: %#x", conf->frequency); + return A2DP_CHECK_ERR_SAMPLING; + } + + return A2DP_CHECK_OK; +} + static int a2dp_mpeg_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -601,10 +636,9 @@ struct a2dp_codec a2dp_mpeg_source = { ) }, .capabilities_size = sizeof(a2dp_mpeg_t), - .channels[0] = a2dp_mpeg_channels, - .samplings[0] = a2dp_mpeg_samplings, .init = a2dp_mpeg_source_init, .configuration_select = a2dp_mpeg_configuration_select, + .configuration_check = a2dp_mpeg_configuration_check, .transport_init = a2dp_mpeg_transport_init, .transport_start = a2dp_mpeg_source_transport_start, /* TODO: This is an optional but covered by the A2DP spec codec, @@ -679,9 +713,8 @@ struct a2dp_codec a2dp_mpeg_sink = { ) }, .capabilities_size = sizeof(a2dp_mpeg_t), - .channels[0] = a2dp_mpeg_channels, - .samplings[0] = a2dp_mpeg_samplings, .configuration_select = a2dp_mpeg_configuration_select, + .configuration_check = a2dp_mpeg_configuration_check, .transport_init = a2dp_mpeg_transport_init, .transport_start = a2dp_mpeg_sink_transport_start, .enabled = false, diff --git a/src/a2dp-sbc.c b/src/a2dp-sbc.c index bd0cb7346..e169c9fee 100644 --- a/src/a2dp-sbc.c +++ b/src/a2dp-sbc.c @@ -429,9 +429,78 @@ static int a2dp_sbc_configuration_select( return errno = ENOTSUP, -1; } + if (caps->min_bitpool > caps->max_bitpool) { + error("SBC: No supported bit-pool range: [%u, %u]", + saved.min_bitpool, saved.max_bitpool); + return errno = ENOTSUP, -1; + } + return 0; } +static int a2dp_sbc_configuration_check( + const struct a2dp_codec *codec, + const void *configuration) { + + const a2dp_sbc_t *conf = configuration; + a2dp_sbc_t conf_v = *conf; + + /* validate configuration against BlueALSA capabilities */ + if (a2dp_filter_capabilities(codec, &codec->capabilities, + &conf_v, sizeof(conf_v)) != 0) + return A2DP_CHECK_ERR_SIZE; + + if (a2dp_sampling_lookup(a2dp_sbc_samplings, conf_v.frequency) == NULL) { + debug("SBC: Invalid sampling frequency: %#x", conf->frequency); + return A2DP_CHECK_ERR_SAMPLING; + } + + if (a2dp_channel_mode_lookup(a2dp_sbc_channels, conf_v.channel_mode) == NULL) { + debug("SBC: Invalid channel mode: %#x", conf->channel_mode); + return A2DP_CHECK_ERR_CHANNEL_MODE; + } + + switch (conf_v.block_length) { + case SBC_BLOCK_LENGTH_4: + case SBC_BLOCK_LENGTH_8: + case SBC_BLOCK_LENGTH_12: + case SBC_BLOCK_LENGTH_16: + break; + default: + debug("SBC: Invalid block length: %#x", conf->block_length); + return A2DP_CHECK_ERR_BLOCK_LENGTH; + } + + switch (conf_v.subbands) { + case SBC_SUBBANDS_4: + case SBC_SUBBANDS_8: + break; + default: + debug("SBC: Invalid sub-bands: %#x", conf->subbands); + return A2DP_CHECK_ERR_SUB_BANDS; + } + + switch (conf_v.allocation_method) { + case SBC_ALLOCATION_SNR: + case SBC_ALLOCATION_LOUDNESS: + break; + default: + debug("SBC: Invalid allocation method: %#x", conf->allocation_method); + return A2DP_CHECK_ERR_ALLOCATION_METHOD; + } + + if (conf_v.min_bitpool > conf_v.max_bitpool) { + error("SBC: Invalid bit-pool range: [%u, %u]", + conf->min_bitpool, conf->max_bitpool); + return A2DP_CHECK_ERR_BIT_POOL_RANGE; + } + + debug("SBC: Selected bit-pool range: [%u, %u]", + conf->min_bitpool, conf->max_bitpool); + + return A2DP_CHECK_OK; +} + static int a2dp_sbc_transport_init(struct ba_transport *t) { const struct a2dp_channel_mode *chm; @@ -508,11 +577,10 @@ struct a2dp_codec a2dp_sbc_source = { .max_bitpool = SBC_MAX_BITPOOL, }, .capabilities_size = sizeof(a2dp_sbc_t), - .channels[0] = a2dp_sbc_channels, - .samplings[0] = a2dp_sbc_samplings, .init = a2dp_sbc_source_init, .capabilities_filter = a2dp_sbc_capabilities_filter, .configuration_select = a2dp_sbc_configuration_select, + .configuration_check = a2dp_sbc_configuration_check, .transport_init = a2dp_sbc_transport_init, .transport_start = a2dp_sbc_source_transport_start, .enabled = true, @@ -552,10 +620,9 @@ struct a2dp_codec a2dp_sbc_sink = { .max_bitpool = SBC_MAX_BITPOOL, }, .capabilities_size = sizeof(a2dp_sbc_t), - .channels[0] = a2dp_sbc_channels, - .samplings[0] = a2dp_sbc_samplings, .capabilities_filter = a2dp_sbc_capabilities_filter, .configuration_select = a2dp_sbc_configuration_select, + .configuration_check = a2dp_sbc_configuration_check, .transport_init = a2dp_sbc_transport_init, .transport_start = a2dp_sbc_sink_transport_start, .enabled = true, diff --git a/src/a2dp.c b/src/a2dp.c index d6f2fc712..2af4aff35 100644 --- a/src/a2dp.c +++ b/src/a2dp.c @@ -18,8 +18,6 @@ #include #include -#include - #if ENABLE_AAC # include "a2dp-aac.h" #endif @@ -330,181 +328,6 @@ uint16_t a2dp_get_vendor_codec_id(const void *capabilities, size_t size) { return 0xFFFF; } -/** - * Check whether A2DP configuration is valid. - * - * @param codec A2DP codec setup. - * @param configuration A2DP codec configuration blob. - * @param size The size of the A2DP codec configuration blob. - * @return On success this function returns A2DP_CHECK_OK. Otherwise, - * A2DP_CHECK_ERR_* error bit-mask is returned to indicate invalid - * A2DP configuration blob. */ -uint32_t a2dp_check_configuration( - const struct a2dp_codec *codec, - const void *configuration, - size_t size) { - - unsigned int cap_chm = 0; - unsigned int cap_freq = 0; - uint32_t ret = A2DP_CHECK_OK; - - /* prevent out-of-bounds memory access */ - if (size != codec->capabilities_size) - return A2DP_CHECK_ERR_SIZE; - - switch (codec->codec_id) { - case A2DP_CODEC_SBC: { - - const a2dp_sbc_t *cap = configuration; - cap_chm = cap->channel_mode; - cap_freq = cap->frequency; - - if (cap->allocation_method != SBC_ALLOCATION_SNR && - cap->allocation_method != SBC_ALLOCATION_LOUDNESS) { - debug("Invalid SBC allocation method: %#x", cap->allocation_method); - ret |= A2DP_CHECK_ERR_SBC_ALLOCATION; - } - - if (cap->subbands != SBC_SUBBANDS_4 && - cap->subbands != SBC_SUBBANDS_8) { - debug("Invalid SBC sub-bands: %#x", cap->subbands); - ret |= A2DP_CHECK_ERR_SBC_SUB_BANDS; - } - - if (cap->block_length != SBC_BLOCK_LENGTH_4 && - cap->block_length != SBC_BLOCK_LENGTH_8 && - cap->block_length != SBC_BLOCK_LENGTH_12 && - cap->block_length != SBC_BLOCK_LENGTH_16) { - debug("Invalid SBC block length: %#x", cap->block_length); - ret |= A2DP_CHECK_ERR_SBC_BLOCK_LENGTH; - } - - debug("Selected A2DP SBC bit-pool range: [%u, %u]", - cap->min_bitpool, cap->max_bitpool); - - break; - } - -#if ENABLE_MPEG - case A2DP_CODEC_MPEG12: { - - const a2dp_mpeg_t *cap = configuration; - cap_chm = cap->channel_mode; - cap_freq = cap->frequency; - - if (cap->layer != MPEG_LAYER_MP1 && - cap->layer != MPEG_LAYER_MP2 && - cap->layer != MPEG_LAYER_MP3) { - debug("Invalid MPEG layer: %#x", cap->layer); - ret |= A2DP_CHECK_ERR_MPEG_LAYER; - } - - break; - } -#endif - -#if ENABLE_AAC - case A2DP_CODEC_MPEG24: { - - const a2dp_aac_t *cap = configuration; - cap_chm = cap->channels; - cap_freq = AAC_GET_FREQUENCY(*cap); - - if (cap->object_type != AAC_OBJECT_TYPE_MPEG2_AAC_LC && - cap->object_type != AAC_OBJECT_TYPE_MPEG4_AAC_LC && - cap->object_type != AAC_OBJECT_TYPE_MPEG4_AAC_LTP && - cap->object_type != AAC_OBJECT_TYPE_MPEG4_AAC_SCA) { - debug("Invalid AAC object type: %#x", cap->object_type); - ret |= A2DP_CHECK_ERR_AAC_OBJ_TYPE; - } - - break; - } -#endif - -#if ENABLE_APTX - case A2DP_CODEC_VENDOR_APTX: { - const a2dp_aptx_t *cap = configuration; - cap_chm = cap->channel_mode; - cap_freq = cap->frequency; - break; - } -#endif - -#if ENABLE_APTX_HD - case A2DP_CODEC_VENDOR_APTX_HD: { - const a2dp_aptx_hd_t *cap = configuration; - cap_chm = cap->aptx.channel_mode; - cap_freq = cap->aptx.frequency; - break; - } -#endif - -#if ENABLE_FASTSTREAM - case A2DP_CODEC_VENDOR_FASTSTREAM: { - - const a2dp_faststream_t *cap = configuration; - cap_freq = cap->frequency_music; - - if ((cap->direction & (FASTSTREAM_DIRECTION_MUSIC | FASTSTREAM_DIRECTION_VOICE)) == 0) { - debug("Invalid FastStream directions: %#x", cap->direction); - ret |= A2DP_CHECK_ERR_FASTSTREAM_DIR; - } - - unsigned int cap_freq_bc = cap->frequency_voice; - if (a2dp_sampling_lookup(codec->samplings[1], cap_freq_bc) == NULL) { - debug("Invalid back-channel sampling frequency: %#x", cap_freq_bc); - ret |= A2DP_CHECK_ERR_SAMPLING_BC; - } - - break; - } -#endif - -#if ENABLE_LC3PLUS - case A2DP_CODEC_VENDOR_LC3PLUS: { - - const a2dp_lc3plus_t *cap = configuration; - cap_chm = cap->channels; - cap_freq = LC3PLUS_GET_FREQUENCY(*cap); - - if (cap->frame_duration != LC3PLUS_FRAME_DURATION_025 && - cap->frame_duration != LC3PLUS_FRAME_DURATION_050 && - cap->frame_duration != LC3PLUS_FRAME_DURATION_100) { - debug("Invalid LC3plus frame duration: %#x", cap->frame_duration); - ret |= A2DP_CHECK_ERR_LC3PLUS_DURATION; - } - - break; - } -#endif - -#if ENABLE_LDAC - case A2DP_CODEC_VENDOR_LDAC: { - const a2dp_ldac_t *cap = configuration; - cap_chm = cap->channel_mode; - cap_freq = cap->frequency; - break; - } -#endif - - default: - g_assert_not_reached(); - } - - if (a2dp_channel_mode_lookup(codec->channels[0], cap_chm) == NULL) { - debug("Invalid channel mode: %#x", cap_chm); - ret |= A2DP_CHECK_ERR_CHANNELS; - } - - if (a2dp_sampling_lookup(codec->samplings[0], cap_freq) == NULL) { - debug("Invalid sampling frequency: %#x", cap_freq); - ret |= A2DP_CHECK_ERR_SAMPLING; - } - - return ret; -} - /** * Filter A2DP codec capabilities with given capabilities mask. */ int a2dp_filter_capabilities( @@ -545,6 +368,64 @@ int a2dp_select_configuration( return errno = EINVAL, -1; } +/** + * Check whether A2DP configuration is valid. + * + * @param codec A2DP codec setup. + * @param configuration A2DP codec configuration blob. + * @param size The size of the A2DP codec configuration blob. + * @return On success this function returns A2DP_CHECK_OK. Otherwise, + * one of the A2DP_CHECK_ERR_* values is returned. */ +enum a2dp_check_err a2dp_check_configuration( + const struct a2dp_codec *codec, + const void *configuration, + size_t size) { + + if (size == codec->capabilities_size) + return codec->configuration_check(codec, configuration); + + error("Invalid configuration size: %zu != %zu", size, codec->capabilities_size); + return A2DP_CHECK_ERR_SIZE; +} + +/** + * Get string representation of A2DP configuration check error. */ +const char *a2dp_check_strerror( + enum a2dp_check_err err) { + switch (err) { + case A2DP_CHECK_OK: + return "Success"; + case A2DP_CHECK_ERR_SIZE: + return "Invalid size"; + case A2DP_CHECK_ERR_CHANNEL_MODE: + return "Invalid channel mode"; + case A2DP_CHECK_ERR_SAMPLING: + return "Invalid sampling frequency"; + case A2DP_CHECK_ERR_ALLOCATION_METHOD: + return "Invalid allocation method"; + case A2DP_CHECK_ERR_BIT_POOL_RANGE: + return "Invalid bit-pool range"; + case A2DP_CHECK_ERR_SUB_BANDS: + return "Invalid sub-bands"; + case A2DP_CHECK_ERR_BLOCK_LENGTH: + return "Invalid block length"; + case A2DP_CHECK_ERR_MPEG_LAYER: + return "Invalid MPEG layer"; + case A2DP_CHECK_ERR_OBJECT_TYPE: + return "Invalid object type"; + case A2DP_CHECK_ERR_DIRECTIONS: + return "Invalid directions"; + case A2DP_CHECK_ERR_SAMPLING_VOICE: + return "Invalid voice sampling frequency"; + case A2DP_CHECK_ERR_SAMPLING_MUSIC: + return "Invalid music sampling frequency"; + case A2DP_CHECK_ERR_FRAME_DURATION: + return "Invalid frame duration"; + } + debug("Unknown error code: %#x", err); + return "Check error"; +} + int a2dp_transport_init( struct ba_transport *t) { return t->a2dp.codec->transport_init(t); diff --git a/src/a2dp.h b/src/a2dp.h index 082261faa..34aa026c4 100644 --- a/src/a2dp.h +++ b/src/a2dp.h @@ -61,11 +61,6 @@ struct a2dp_codec { a2dp_t capabilities; size_t capabilities_size; - /* list of supported channel modes */ - const struct a2dp_channel_mode *channels[2]; - /* list of supported sampling frequencies */ - const struct a2dp_sampling *samplings[2]; - /* callback function for codec initialization */ int (*init)(struct a2dp_codec *codec); @@ -82,6 +77,11 @@ struct a2dp_codec { const struct a2dp_codec *codec, void *capabilities); + /* callback function for checking configuration correctness */ + int (*configuration_check)( + const struct a2dp_codec *codec, + const void *configuration); + int (*transport_init)(struct ba_transport *t); int (*transport_start)(struct ba_transport *t); @@ -137,25 +137,6 @@ uint16_t a2dp_get_vendor_codec_id( const void *capabilities, size_t size); -#define A2DP_CHECK_OK 0 -#define A2DP_CHECK_ERR_SIZE (1 << 0) -#define A2DP_CHECK_ERR_CHANNELS (1 << 1) -#define A2DP_CHECK_ERR_CHANNELS_BC (1 << 2) -#define A2DP_CHECK_ERR_SAMPLING (1 << 3) -#define A2DP_CHECK_ERR_SAMPLING_BC (1 << 4) -#define A2DP_CHECK_ERR_SBC_ALLOCATION (1 << 5) -#define A2DP_CHECK_ERR_SBC_SUB_BANDS (1 << 6) -#define A2DP_CHECK_ERR_SBC_BLOCK_LENGTH (1 << 7) -#define A2DP_CHECK_ERR_MPEG_LAYER (1 << 8) -#define A2DP_CHECK_ERR_AAC_OBJ_TYPE (1 << 9) -#define A2DP_CHECK_ERR_FASTSTREAM_DIR (1 << 10) -#define A2DP_CHECK_ERR_LC3PLUS_DURATION (1 << 11) - -uint32_t a2dp_check_configuration( - const struct a2dp_codec *codec, - const void *configuration, - size_t size); - int a2dp_filter_capabilities( const struct a2dp_codec *codec, const void *capabilities_mask, @@ -167,6 +148,31 @@ int a2dp_select_configuration( void *capabilities, size_t size); +enum a2dp_check_err { + A2DP_CHECK_OK = 0, + A2DP_CHECK_ERR_SIZE, + A2DP_CHECK_ERR_CHANNEL_MODE, + A2DP_CHECK_ERR_SAMPLING, + A2DP_CHECK_ERR_ALLOCATION_METHOD, + A2DP_CHECK_ERR_BIT_POOL_RANGE, + A2DP_CHECK_ERR_SUB_BANDS, + A2DP_CHECK_ERR_BLOCK_LENGTH, + A2DP_CHECK_ERR_MPEG_LAYER, + A2DP_CHECK_ERR_OBJECT_TYPE, + A2DP_CHECK_ERR_DIRECTIONS, + A2DP_CHECK_ERR_SAMPLING_VOICE, + A2DP_CHECK_ERR_SAMPLING_MUSIC, + A2DP_CHECK_ERR_FRAME_DURATION, +}; + +enum a2dp_check_err a2dp_check_configuration( + const struct a2dp_codec *codec, + const void *configuration, + size_t size); + +const char *a2dp_check_strerror( + enum a2dp_check_err err); + int a2dp_transport_init( struct ba_transport *t); int a2dp_transport_start( diff --git a/src/bluealsa-dbus.c b/src/bluealsa-dbus.c index 4db1fec2d..335b241b0 100644 --- a/src/bluealsa-dbus.c +++ b/src/bluealsa-dbus.c @@ -680,14 +680,11 @@ static void bluealsa_pcm_select_codec(GDBusMethodInvocation *inv, void *userdata a2dp_filter_capabilities(codec, &sep->capabilities, &a2dp_configuration, a2dp_configuration_size); - a2dp_filter_capabilities(codec, &codec->capabilities, - &a2dp_configuration, a2dp_configuration_size); - uint32_t rv; + enum a2dp_check_err rv; if ((rv = a2dp_check_configuration(codec, &a2dp_configuration, a2dp_configuration_size)) != A2DP_CHECK_OK) { - error("Invalid configuration: %s: %#x", "Invalid configuration blob", rv); - errmsg = "Invalid configuration blob"; + errmsg = a2dp_check_strerror(rv); goto fail; } diff --git a/src/bluez.c b/src/bluez.c index 1df3f835f..b462e2d81 100644 --- a/src/bluez.c +++ b/src/bluez.c @@ -474,9 +474,10 @@ static void bluez_endpoint_set_configuration(GDBusMethodInvocation *inv, void *u const void *data = g_variant_get_fixed_array(value, &size, sizeof(char)); memcpy(&configuration, data, MIN(size, sizeof(configuration))); - uint32_t rv; + enum a2dp_check_err rv; if ((rv = a2dp_check_configuration(codec, data, size)) != A2DP_CHECK_OK) { - error("Invalid configuration: %s: %#x", "Invalid configuration blob", rv); + error("Invalid configuration: %s: %s", + "Invalid configuration blob", a2dp_check_strerror(rv)); goto fail; } diff --git a/test/test-a2dp.c b/test/test-a2dp.c index f582f63c3..f1f6c33d1 100644 --- a/test/test-a2dp.c +++ b/test/test-a2dp.c @@ -160,8 +160,17 @@ CK_START_TEST(test_a2dp_check_configuration) { }; ck_assert_int_eq(a2dp_check_configuration(&a2dp_sbc_source, - &cfg_invalid, sizeof(cfg_invalid)), - A2DP_CHECK_ERR_SAMPLING | A2DP_CHECK_ERR_CHANNELS | A2DP_CHECK_ERR_SBC_SUB_BANDS); + &cfg_invalid, sizeof(cfg_invalid)), A2DP_CHECK_ERR_SAMPLING); + +#if ENABLE_AAC + a2dp_aac_t cfg_aac_invalid = { + /* FDK-AAC encoder does not support AAC Long Term Prediction */ + .object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LTP, + AAC_INIT_FREQUENCY(AAC_SAMPLING_FREQ_44100) + .channels = AAC_CHANNELS_1 }; + ck_assert_int_eq(a2dp_check_configuration(&a2dp_aac_source, + &cfg_aac_invalid, sizeof(cfg_aac_invalid)), A2DP_CHECK_ERR_OBJECT_TYPE); +#endif } CK_END_TEST