Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package chromaprint for openSUSE:Factory checked in at 2026-10-16 20:07:06 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/chromaprint (Old) and /work/SRC/openSUSE:Factory/.chromaprint.new.3698736 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "chromaprint" Fri Oct 16 20:07:06 2026 rev:30 rq:1385138 version:1.6.2 Changes: -------- --- /work/SRC/openSUSE:Factory/chromaprint/chromaprint.changes 2026-10-15 18:20:22.366014150 +0200 +++ /work/SRC/openSUSE:Factory/.chromaprint.new.3698736/chromaprint.changes 2026-10-16 20:07:23.464380668 +0200 @@ -1,0 +2,13 @@ +Fri Oct 16 06:30:22 UTC 2026 - Bjørn Lie <[email protected]> + +- Update to version 1.6.2: + * Fixed a crash in fpcalc when the channel layout changes mid-stream + (e.g. AC-3 files switching between 5.1 and stereo) + * Fixed decoding of audio with more than 8 planar channels in fpcalc + * Fingerprint decoding now rejects invalid input that previously caused + undefined behaviour (shifting past 32 bits) + * The C API encoding/decoding functions now return an error for a negative + size, a NULL buffer with a nonzero size, and, when encoding, more items + than the fingerprint header can hold (previously silently truncated) + +------------------------------------------------------------------- Old: ---- chromaprint-1.6.1.tar.gz New: ---- chromaprint-1.6.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ chromaprint.spec ++++++ --- /var/tmp/diff_new_pack.u2r3rR/_old 2026-10-16 20:07:24.279414724 +0200 +++ /var/tmp/diff_new_pack.u2r3rR/_new 2026-10-16 20:07:24.282414849 +0200 @@ -20,7 +20,7 @@ %define rev aa67c95b9e486884a6d3ee8b0c91207d8c2b0551 %define sover 1 Name: chromaprint -Version: 1.6.1 +Version: 1.6.2 Release: 0 Summary: Audio Fingerprinting Library License: LGPL-2.1-only AND MIT ++++++ chromaprint-1.6.1.tar.gz -> chromaprint-1.6.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/CMakeLists.txt new/chromaprint-1.6.2/CMakeLists.txt --- old/chromaprint-1.6.1/CMakeLists.txt 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/CMakeLists.txt 2026-10-15 11:56:52.000000000 +0200 @@ -2,7 +2,7 @@ set(chromaprint_VERSION_MAJOR 1) set(chromaprint_VERSION_MINOR 6) -set(chromaprint_VERSION_PATCH 1) +set(chromaprint_VERSION_PATCH 2) set(chromaprint_VERSION "${chromaprint_VERSION_MAJOR}.${chromaprint_VERSION_MINOR}.${chromaprint_VERSION_PATCH}") project(chromaprint LANGUAGES C CXX VERSION "${chromaprint_VERSION}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/NEWS.txt new/chromaprint-1.6.2/NEWS.txt --- old/chromaprint-1.6.1/NEWS.txt 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/NEWS.txt 2026-10-15 11:56:52.000000000 +0200 @@ -1,3 +1,15 @@ +Version 1.6.2 -- October 15, 2026 +================================= + + - Fixed a crash in fpcalc when the channel layout changes mid-stream + (e.g. AC-3 files switching between 5.1 and stereo) + - Fixed decoding of audio with more than 8 planar channels in fpcalc + - Fingerprint decoding now rejects invalid input that previously caused + undefined behaviour (shifting past 32 bits) + - The C API encoding/decoding functions now return an error for a negative + size, a NULL buffer with a nonzero size, and, when encoding, more items + than the fingerprint header can hold (previously silently truncated) + Version 1.6.1 -- July 28, 2026 ============================== diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/audio/ffmpeg_audio_reader.h new/chromaprint-1.6.2/src/audio/ffmpeg_audio_reader.h --- old/chromaprint-1.6.1/src/audio/ffmpeg_audio_reader.h 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/audio/ffmpeg_audio_reader.h 2026-10-15 11:56:52.000000000 +0200 @@ -70,8 +70,18 @@ private: inline void SetError(const char *format, int errnum = 0); + inline bool ConverterMatchesFrame(const AVFrame *frame) const; + inline int ResetConverter(const AVFrame *frame); + inline bool EnsureConvertBuffer(int nb_samples); std::unique_ptr<FFmpegAudioProcessor> m_converter; +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + AVChannelLayout m_converter_channel_layout = {}; +#else + uint64_t m_converter_channel_layout = 0; +#endif + int m_converter_sample_format = AV_SAMPLE_FMT_NONE; + int m_converter_sample_rate = 0; uint8_t *m_convert_buffer[1] = { nullptr }; int m_convert_buffer_nb_samples = 0; @@ -104,6 +114,9 @@ Close(); av_dict_free(&m_input_opts); av_freep(&m_convert_buffer[0]); +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + av_channel_layout_uninit(&m_converter_channel_layout); +#endif } inline bool FFmpegAudioReader::SetInputFormat(const char *name) { @@ -191,48 +204,13 @@ m_output_sample_rate = m_codec_ctx->sample_rate; } -#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT - AVChannelLayout output_channel_layout; if (!m_output_channels) { m_output_channels = CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx); } - av_channel_layout_default(&output_channel_layout, m_output_channels); -#else - if (!m_output_channels) { - m_output_channels = CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx); - } - uint64_t output_channel_layout = av_get_default_channel_layout(m_output_channels); -#endif - if (m_codec_ctx->sample_fmt != AV_SAMPLE_FMT_S16 || CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx) != m_output_channels || m_codec_ctx->sample_rate != m_output_sample_rate) { - m_converter.reset(new FFmpegAudioProcessor()); - m_converter->SetCompatibleMode(); - m_converter->SetInputSampleFormat(m_codec_ctx->sample_fmt); - m_converter->SetInputSampleRate(m_codec_ctx->sample_rate); -#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT - m_converter->SetInputChannelLayout(&(m_codec_ctx->ch_layout)); -#else - m_converter->SetInputChannelLayout(m_codec_ctx->channel_layout - ? m_codec_ctx->channel_layout - : av_get_default_channel_layout(m_codec_ctx->channels)); -#endif - m_converter->SetOutputSampleFormat(AV_SAMPLE_FMT_S16); - m_converter->SetOutputSampleRate(m_output_sample_rate); -#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT - m_converter->SetOutputChannelLayout(&output_channel_layout); -#else - m_converter->SetOutputChannelLayout(output_channel_layout); -#endif - auto ret = m_converter->Init(); - if (ret != 0) { - SetError("Could not create an audio converter instance", ret); - return false; - } - } - -#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT - av_channel_layout_uninit(&output_channel_layout); -#endif + // The converter is set up in Read() from the decoded frames, because + // some decoders (e.g. AC-3) change the channel layout mid-stream. + m_converter.reset(); m_opened = true; m_has_more_packets = true; @@ -334,24 +312,38 @@ } if (m_frame->nb_samples > 0) { - if (m_converter) { - if (m_frame->nb_samples > m_convert_buffer_nb_samples) { - int linsize; - av_freep(&m_convert_buffer[0]); - m_convert_buffer_nb_samples = std::max(1024 * 8, m_frame->nb_samples); - ret = av_samples_alloc(m_convert_buffer, &linsize, CHROMAPRINT_CODEC_CHANNELS(m_codec_ctx), m_convert_buffer_nb_samples, AV_SAMPLE_FMT_S16, 1); - if (ret < 0) { - SetError("Couldn't allocate audio converter buffer", ret); + int nb_flushed = 0; + if (!ConverterMatchesFrame(m_frame)) { + if (!EnsureConvertBuffer(m_frame->nb_samples)) { + return false; + } + // The input format changed, drain what the old converter still + // holds before replacing it. + if (m_converter) { + nb_flushed = m_converter->Flush(m_convert_buffer, m_convert_buffer_nb_samples); + if (nb_flushed < 0) { + SetError("Couldn't convert audio", nb_flushed); return false; } } - auto nb_samples = m_converter->Convert(m_convert_buffer, m_convert_buffer_nb_samples, (const uint8_t **) m_frame->data, m_frame->nb_samples); + ret = ResetConverter(m_frame); + if (ret < 0) { + SetError("Could not create an audio converter instance", ret); + return false; + } + } + if (m_converter) { + if (!EnsureConvertBuffer(m_frame->nb_samples)) { + return false; + } + uint8_t *out[1] = { m_convert_buffer[0] + nb_flushed * m_output_channels * sizeof(int16_t) }; + auto nb_samples = m_converter->Convert(out, m_convert_buffer_nb_samples - nb_flushed, (const uint8_t **) m_frame->extended_data, m_frame->nb_samples); if (nb_samples < 0) { - SetError("Couldn't convert audio", ret); + SetError("Couldn't convert audio", nb_samples); return false; } *data = (const int16_t *) m_convert_buffer[0]; - *size = nb_samples; + *size = nb_flushed + nb_samples; } else { *data = (const int16_t *) m_frame->data[0]; *size = m_frame->nb_samples; @@ -375,6 +367,84 @@ } } +inline bool FFmpegAudioReader::EnsureConvertBuffer(int nb_samples) { + // Leave room for samples flushed from a previous converter and for upsampling. + nb_samples = std::max(1024 * 8, nb_samples * 4); + if (nb_samples > m_convert_buffer_nb_samples) { + int linesize; + av_freep(&m_convert_buffer[0]); + m_convert_buffer_nb_samples = 0; + int ret = av_samples_alloc(m_convert_buffer, &linesize, m_output_channels, nb_samples, AV_SAMPLE_FMT_S16, 1); + if (ret < 0) { + SetError("Couldn't allocate audio converter buffer", ret); + return false; + } + m_convert_buffer_nb_samples = nb_samples; + } + return true; +} + +inline bool FFmpegAudioReader::ConverterMatchesFrame(const AVFrame *frame) const { + if (!m_converter) { + return frame->format == AV_SAMPLE_FMT_S16 && + frame->sample_rate == m_output_sample_rate && + CHROMAPRINT_FRAME_CHANNELS(frame) == m_output_channels; + } + if (frame->format != m_converter_sample_format || frame->sample_rate != m_converter_sample_rate) { + return false; + } +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + return av_channel_layout_compare(&frame->ch_layout, &m_converter_channel_layout) == 0; +#else + return (frame->channel_layout ? frame->channel_layout : av_get_default_channel_layout(frame->channels)) == m_converter_channel_layout; +#endif +} + +inline int FFmpegAudioReader::ResetConverter(const AVFrame *frame) { + int ret; + + m_converter.reset(); + m_converter_sample_format = frame->format; + m_converter_sample_rate = frame->sample_rate; +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + ret = av_channel_layout_copy(&m_converter_channel_layout, &frame->ch_layout); + if (ret < 0) { + return ret; + } + AVChannelLayout output_channel_layout; + av_channel_layout_default(&output_channel_layout, m_output_channels); +#else + m_converter_channel_layout = frame->channel_layout + ? frame->channel_layout + : av_get_default_channel_layout(frame->channels); + uint64_t output_channel_layout = av_get_default_channel_layout(m_output_channels); +#endif + + m_converter.reset(new FFmpegAudioProcessor()); + m_converter->SetCompatibleMode(); + m_converter->SetInputSampleFormat((AVSampleFormat) frame->format); + m_converter->SetInputSampleRate(frame->sample_rate); +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + m_converter->SetInputChannelLayout(&m_converter_channel_layout); + m_converter->SetOutputChannelLayout(&output_channel_layout); +#else + m_converter->SetInputChannelLayout(m_converter_channel_layout); + m_converter->SetOutputChannelLayout(output_channel_layout); +#endif + m_converter->SetOutputSampleFormat(AV_SAMPLE_FMT_S16); + m_converter->SetOutputSampleRate(m_output_sample_rate); + +#if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT + av_channel_layout_uninit(&output_channel_layout); +#endif + + ret = m_converter->Init(); + if (ret < 0) { + m_converter.reset(); + } + return ret; +} + inline void FFmpegAudioReader::SetError(const char *message, int errnum) { m_error = message; if (errnum < 0) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/audio/ffmpeg_audio_reader_test.cpp new/chromaprint-1.6.2/src/audio/ffmpeg_audio_reader_test.cpp --- old/chromaprint-1.6.1/src/audio/ffmpeg_audio_reader_test.cpp 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/audio/ffmpeg_audio_reader_test.cpp 2026-10-15 11:56:52.000000000 +0200 @@ -25,8 +25,33 @@ } /* ASSERT_TRUE(reader.ReadAll([&](const int16_t *data, size_t size) { - + }));*/ } +TEST(FFmpegAudioReaderTest, ReadChannelLayoutChange) { + FFmpegAudioReader reader; + + reader.SetOutputChannels(2); + reader.SetOutputSampleRate(11025); + + // 5.1 AC-3 stream with a few stereo frames in the middle + ASSERT_TRUE(reader.Open(TESTS_DIR "/data/test_51_stereo_switch.ac3")) << reader.GetError(); + ASSERT_TRUE(reader.IsOpen()); + + ASSERT_EQ(2, reader.GetChannels()); + ASSERT_EQ(11025, reader.GetSampleRate()); + + const int16_t *data; + size_t size; + size_t total_size = 0; + while (reader.Read(&data, &size)) { + total_size += size; + } + ASSERT_TRUE(reader.IsFinished()) << reader.GetError(); + + // 15 frames of 1536 samples at 44100 Hz + ASSERT_NEAR(15 * 1536 * 11025 / 44100, total_size, 100); +} + }; // namespace chromaprint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/audio/ffmpeg_compat.h new/chromaprint-1.6.2/src/audio/ffmpeg_compat.h --- old/chromaprint-1.6.1/src/audio/ffmpeg_compat.h 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/audio/ffmpeg_compat.h 2026-10-15 11:56:52.000000000 +0200 @@ -21,8 +21,10 @@ #if CHROMAPRINT_HAVE_AV_CHANNEL_LAYOUT #define CHROMAPRINT_CODEC_CHANNELS(ctx) ((ctx)->ch_layout.nb_channels) +#define CHROMAPRINT_FRAME_CHANNELS(frame) ((frame)->ch_layout.nb_channels) #else #define CHROMAPRINT_CODEC_CHANNELS(ctx) ((ctx)->channels) +#define CHROMAPRINT_FRAME_CHANNELS(frame) ((frame)->channels) #endif // avformat_open_input() and av_find_best_stream() took non-const AVInputFormat diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/chromaprint.cpp new/chromaprint-1.6.2/src/chromaprint.cpp --- old/chromaprint-1.6.1/src/chromaprint.cpp 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/chromaprint.cpp 2026-10-15 11:56:52.000000000 +0200 @@ -172,6 +172,10 @@ int chromaprint_encode_fingerprint(const uint32_t *fp, int size, int algorithm, char **encoded_fp, int *encoded_size, int base64) { + // The encoded header stores the size in 24 bits. + if ((fp == NULL && size != 0) || size < 0 || size > 0xFFFFFF || encoded_fp == NULL || encoded_size == NULL) { + return 0; + } std::vector<uint32_t> uncompressed(fp, fp + size); std::string encoded = CompressFingerprint(uncompressed, algorithm); if (base64) { @@ -185,13 +189,19 @@ int chromaprint_decode_fingerprint(const char *encoded_fp, int encoded_size, uint32_t **fp, int *size, int *algorithm, int base64) { - std::string encoded(encoded_fp, encoded_size); - if (base64) { - encoded = Base64Decode(encoded); + if (fp == NULL || size == NULL) { + return 0; } std::vector<uint32_t> uncompressed; int algo; - auto ok = DecompressFingerprint(encoded, uncompressed, algo); + bool ok = false; + if (encoded_size >= 0 && (encoded_fp != NULL || encoded_size == 0)) { + std::string encoded(encoded_fp, encoded_size); + if (base64) { + encoded = Base64Decode(encoded); + } + ok = DecompressFingerprint(encoded, uncompressed, algo); + } if (!ok) { *fp = nullptr; *size = 0; @@ -211,13 +221,16 @@ int chromaprint_decode_fingerprint_header(const char *encoded_fp, int encoded_size, int *size, int *algorithm, int base64) { - std::string encoded(encoded_fp, std::min(6, encoded_size)); - if (base64) { - encoded = Base64Decode(encoded); - } size_t size_out; int algorithm_out; - auto ok = DecompressFingerprintHeader(encoded, size_out, algorithm_out); + bool ok = false; + if (encoded_size >= 0 && (encoded_fp != NULL || encoded_size == 0)) { + std::string encoded(encoded_fp, std::min(6, encoded_size)); + if (base64) { + encoded = Base64Decode(encoded); + } + ok = DecompressFingerprintHeader(encoded, size_out, algorithm_out); + } if (!ok) { if (size) { *size = 0; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/chromaprint.h new/chromaprint-1.6.2/src/chromaprint.h --- old/chromaprint-1.6.1/src/chromaprint.h 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/chromaprint.h 2026-10-15 11:56:52.000000000 +0200 @@ -84,7 +84,7 @@ #define CHROMAPRINT_VERSION_MAJOR 1 #define CHROMAPRINT_VERSION_MINOR 6 -#define CHROMAPRINT_VERSION_PATCH 1 +#define CHROMAPRINT_VERSION_PATCH 2 enum ChromaprintAlgorithm { CHROMAPRINT_ALGORITHM_TEST1 = 0, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/fingerprint_decompressor.cpp new/chromaprint-1.6.2/src/fingerprint_decompressor.cpp --- old/chromaprint-1.6.1/src/fingerprint_decompressor.cpp 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/fingerprint_decompressor.cpp 2026-10-15 11:56:52.000000000 +0200 @@ -19,7 +19,7 @@ { } -void FingerprintDecompressor::UnpackBits() +bool FingerprintDecompressor::UnpackBits() { int i = 0, last_bit = 0; uint32_t value = 0; @@ -31,9 +31,14 @@ i++; } else { last_bit += bit; - value ^= 1 << (last_bit - 1); + if (last_bit > 32) { + DEBUG("FingerprintDecompressor::Decompress() -- Invalid fingerprint (bit position past 32)"); + return false; + } + value ^= uint32_t(1) << (last_bit - 1); } } + return true; } bool FingerprintDecompressor::DecompressHeader(const std::string &input) @@ -60,6 +65,13 @@ return false; } + if (m_size == 0) { + // An empty fingerprint has no body, so anything after the header is + // trailing data, ignored like trailing data after any fingerprint. + m_output.clear(); + return true; + } + size_t offset = 4; m_bits.resize(GetUnpackedInt3ArraySize(input.size() - offset)); UnpackInt3Array(input.begin() + offset, input.end(), m_bits.begin()); @@ -102,8 +114,7 @@ m_output.assign(m_size, -1); - UnpackBits(); - return true; + return UnpackBits(); } }; // namespace chromaprint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/src/fingerprint_decompressor.h new/chromaprint-1.6.2/src/fingerprint_decompressor.h --- old/chromaprint-1.6.1/src/fingerprint_decompressor.h 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/src/fingerprint_decompressor.h 2026-10-15 11:56:52.000000000 +0200 @@ -22,7 +22,7 @@ int GetAlgorithm() const { return m_algorithm; } private: - void UnpackBits(); + bool UnpackBits(); std::vector<uint32_t> m_output; size_t m_size { 0 }; int m_algorithm { -1 }; Binary files old/chromaprint-1.6.1/tests/data/test_51_stereo_switch.ac3 and new/chromaprint-1.6.2/tests/data/test_51_stereo_switch.ac3 differ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/tests/test_api.cpp new/chromaprint-1.6.2/tests/test_api.cpp --- old/chromaprint-1.6.1/tests/test_api.cpp 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/tests/test_api.cpp 2026-10-15 11:56:52.000000000 +0200 @@ -237,4 +237,45 @@ ASSERT_EQ(0, algorithm); } +TEST(API, TestEncodeFingerprintInvalidSize) +{ + uint32_t fingerprint[] = { 1, 0 }; + char *encoded; + int encoded_size; + + ASSERT_EQ(0, chromaprint_encode_fingerprint(fingerprint, -1, 1, &encoded, &encoded_size, 0)); + // The header has room for a 24-bit size only. + ASSERT_EQ(0, chromaprint_encode_fingerprint(fingerprint, 0x1000000, 1, &encoded, &encoded_size, 0)); + ASSERT_EQ(0, chromaprint_encode_fingerprint(NULL, 2, 1, &encoded, &encoded_size, 0)); +} + +TEST(API, TestDecodeFingerprintInvalidSize) +{ + uint32_t *fp; + int length, algorithm; + char data[] = { 0, 0, 0, 2, 65, 0 }; + + ASSERT_EQ(0, chromaprint_decode_fingerprint(data, -1, &fp, &length, &algorithm, 0)); + ASSERT_EQ(0, length); + ASSERT_EQ(0, chromaprint_decode_fingerprint(NULL, 6, &fp, &length, &algorithm, 0)); + ASSERT_EQ(0, length); + + ASSERT_EQ(0, chromaprint_decode_fingerprint_header(data, -1, &length, &algorithm, 0)); + ASSERT_EQ(0, length); + ASSERT_EQ(0, chromaprint_decode_fingerprint_header(NULL, 6, &length, &algorithm, 0)); + ASSERT_EQ(0, length); +} + +TEST(API, TestDecodeFingerprintBitPastPosition32) +{ + uint32_t *fp; + int length, algorithm; + // Six distances of 6 reach bit 36. + char data[] = { 0, 0, 0, 1, char(182), 109, 3 }; + + ASSERT_EQ(0, chromaprint_decode_fingerprint(data, NELEMS(data), &fp, &length, &algorithm, 0)); + ASSERT_EQ(nullptr, fp); + ASSERT_EQ(0, length); +} + }; // namespace chromaprint diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/chromaprint-1.6.1/tests/test_fingerprint_decompressor.cpp new/chromaprint-1.6.2/tests/test_fingerprint_decompressor.cpp --- old/chromaprint-1.6.1/tests/test_fingerprint_decompressor.cpp 2026-07-28 08:12:40.000000000 +0200 +++ new/chromaprint-1.6.2/tests/test_fingerprint_decompressor.cpp 2026-10-15 11:56:52.000000000 +0200 @@ -127,3 +127,98 @@ CheckFingerprints(value, (uint32_t *) expected, NELEMS(expected)); ASSERT_EQ(1, algorithm); } + +TEST(FingerprintDecompressor, BitAtPosition32) +{ + // Five distances of 6 and one of 2 reach bit 32 exactly. + uint32_t expected[] = { 0xA0820820 }; + char data[] = { 0, 0, 0, 1, char(182), 109, 1 }; + + int algorithm = 1; + std::vector<uint32_t> value; + ASSERT_EQ(true, DecompressFingerprint(std::string(data, NELEMS(data)), value, algorithm)); + CheckFingerprints(value, expected, NELEMS(expected)); +} + +TEST(FingerprintDecompressor, BitPastPosition32) +{ + // Six distances of 6 reach bit 36. + char data[] = { 0, 0, 0, 1, char(182), 109, 3 }; + + int algorithm = 1; + std::vector<uint32_t> value; + ASSERT_EQ(false, DecompressFingerprint(std::string(data, NELEMS(data)), value, algorithm)); +} + +TEST(FingerprintDecompressor, ExceptionPastPosition32) +{ + // A distance of 7 + 26 reaches bit 33. + char data[] = { 0, 0, 0, 1, 7, 26 }; + + int algorithm = 1; + std::vector<uint32_t> value; + ASSERT_EQ(false, DecompressFingerprint(std::string(data, NELEMS(data)), value, algorithm)); +} + +TEST(FingerprintDecompressor, SecondItemPastPosition32) +{ + // The first item is valid, the second reaches bit 36. + char data[] = { 0, 0, 0, 2, char(129), 109, char(219), 0 }; + + int algorithm = 1; + std::vector<uint32_t> value; + ASSERT_EQ(false, DecompressFingerprint(std::string(data, NELEMS(data)), value, algorithm)); +} + +TEST(FingerprintDecompressor, SecondItemExceptionPastPosition32) +{ + // The first item takes an exception of 3, the second 7 + 26. + char data[] = { 0, 0, 0, 2, char(199), 1, 67, 3 }; + + int algorithm = 1; + std::vector<uint32_t> value; + ASSERT_EQ(false, DecompressFingerprint(std::string(data, NELEMS(data)), value, algorithm)); +} + +TEST(FingerprintDecompressor, ZeroItemsWithTrailingData) +{ + char data[][8] = { + { 1, 0, 0, 0, 0, 0, 0, 0 }, + { 1, 0, 0, 0, char(255), char(255), char(255), char(255) }, + { 1, 0, 0, 0, char(182), 109, 3, 0 }, + }; + + for (size_t i = 0; i < NELEMS(data); i++) { + int algorithm = 0; + std::vector<uint32_t> value; + ASSERT_EQ(true, DecompressFingerprint(std::string(data[i], NELEMS(data[i])), value, algorithm)); + ASSERT_EQ(0, value.size()); + ASSERT_EQ(1, algorithm); + } +} + +TEST(FingerprintDecompressor, Corrupted) +{ + // Every truncation and every single-bit flip of a valid fingerprint + // either fails or decodes to as many items as the header says. + const std::string data = Base64Decode("AQAAEwkjrUmSJQpUHflR9mjSJMdZpcO_Imdw9dCO9Clu4_wQPvhCB01w6xAtXNcAp5RASgDBhDSCGGIAcwA"); + + auto check = [](const std::string &input) { + size_t size = 0; + int algorithm = 0; + std::vector<uint32_t> value; + if (DecompressFingerprint(input, value, algorithm)) { + ASSERT_EQ(true, DecompressFingerprintHeader(input, size, algorithm)); + ASSERT_EQ(size, value.size()); + } + }; + + for (size_t len = 0; len < data.size(); len++) { + check(data.substr(0, len)); + } + for (size_t bit = 0; bit < data.size() * 8; bit++) { + std::string corrupted = data; + corrupted[bit / 8] ^= char(1 << (bit % 8)); + check(corrupted); + } +}
