shithub: opus

Download patch

ref: 4b9c620a8dbee1c2d164310ac8ace44fff97b162
parent: df2f98f809a40cb380bf4570aa6522d12beac18c
author: Michael Klingbeil <klingm@amazon.com>
date: Wed May 17 08:46:09 EDT 2023

Fixup extensions implementation and add unit tests

Add unit tests for opus_packet_extensions_count, opus_packet_extensions_parse,
and opus_packet_extensions_generate. Add various assertions for NULL buffers or
negative length arguments. Add explicit check for invalid length in
opus_packet_extensions_count. Check for extension id >127 in
opus_packet_extensions_generate. Check for invalid length input in
opus_packet_extensions_generate.

Signed-off-by: Jean-Marc Valin <jmvalin@amazon.com>

--- a/.gitignore
+++ b/.gitignore
@@ -49,6 +49,7 @@
 tests/test_opus_api
 tests/test_opus_decode
 tests/test_opus_encode
+tests/test_opus_extensions
 tests/test_opus_padding
 tests/test_opus_projection
 celt/arm/armopts.s
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -81,6 +81,7 @@
       apt-get install -y cmake ninja-build git
     - !reference [.snippets, git_prep]
   script:
+    - ./autogen.sh
     - mkdir build
     - cmake -S . -B build -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DOPUS_BUILD_TESTING=ON -DOPUS_BUILD_PROGRAMS=ON
     - cmake --build build
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -274,6 +274,7 @@
          $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/opus>
   PRIVATE ${CMAKE_CURRENT_BINARY_DIR}
           ${CMAKE_CURRENT_SOURCE_DIR}
+          ${CMAKE_CURRENT_SOURCE_DIR}/lpcnet/include
           celt
           silk)
 
@@ -351,6 +352,7 @@
 
 add_sources_group(opus silk ${silk_headers} ${silk_sources})
 add_sources_group(opus celt ${celt_headers} ${celt_sources})
+add_sources_group(opus lpcnet ${lpcnet_headers} ${lpcnet_sources})
 
 if(OPUS_FIXED_POINT)
   add_sources_group(opus silk ${silk_sources_fixed})
@@ -641,6 +643,16 @@
   target_compile_definitions(test_opus_encode PRIVATE OPUS_BUILD)
   add_test(NAME test_opus_encode COMMAND ${CMAKE_COMMAND}
         -DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_encode>
+        -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
+        -P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
+
+  add_executable(test_opus_extensions ${test_opus_extensions_sources})
+  target_include_directories(test_opus_extensions
+                            PRIVATE ${CMAKE_CURRENT_BINARY_DIR} celt)
+  target_link_libraries(test_opus_extensions PRIVATE opus)
+  target_compile_definitions(test_opus_extensions PRIVATE OPUS_BUILD)
+  add_test(NAME test_opus_extensions COMMAND ${CMAKE_COMMAND}
+        -DTEST_EXECUTABLE=$<TARGET_FILE:test_opus_extensions>
         -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
         -P "${PROJECT_SOURCE_DIR}/cmake/RunTest.cmake")
 endif()
--- a/Makefile.am
+++ b/Makefile.am
@@ -113,6 +113,7 @@
                   tests/test_opus_api \
                   tests/test_opus_decode \
                   tests/test_opus_encode \
+                  tests/test_opus_extensions \
                   tests/test_opus_padding \
                   tests/test_opus_projection \
                   trivial_example
@@ -129,6 +130,7 @@
         tests/test_opus_api \
         tests/test_opus_decode \
         tests/test_opus_encode \
+        tests/test_opus_extensions \
         tests/test_opus_padding \
         tests/test_opus_projection
 
@@ -151,6 +153,9 @@
 
 tests_test_opus_encode_SOURCES = tests/test_opus_encode.c tests/opus_encode_regressions.c tests/test_opus_common.h
 tests_test_opus_encode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
+
+tests_test_opus_extensions_SOURCES = tests/test_opus_extensions.c tests/test_opus_common.h
+tests_test_opus_extensions_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
 
 tests_test_opus_decode_SOURCES = tests/test_opus_decode.c tests/test_opus_common.h
 tests_test_opus_decode_LDADD = libopus.la $(NE10_LIBS) $(LIBM)
--- a/Makefile.mips
+++ b/Makefile.mips
@@ -102,6 +102,9 @@
 TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
 TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
 
+TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
+TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
+
 TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
 TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
 
@@ -108,7 +111,7 @@
 OPUSCOMPARE_SRCS_C = src/opus_compare.c
 OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
 
-TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_padding
+TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
 
 # Rules
 all: lib opus_demo opus_compare $(TESTS)
@@ -133,6 +136,9 @@
 test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
 	$(LINK.o.cmdline)
 
+test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
+	$(LINK.o.cmdline)
+
 test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
 	$(LINK.o.cmdline)
 
@@ -154,8 +160,10 @@
 clean:
 	rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
                 test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
-                test_opus_encode$(EXESUFFIX) test_opus_padding$(EXESUFFIX) \
+                test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
+                test_opus_padding$(EXESUFFIX)
 		$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
-                $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) $(TESTOPUSPADDING_OBJS)
+                $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
+                $(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
 
 .PHONY: all lib clean force check
--- a/Makefile.unix
+++ b/Makefile.unix
@@ -100,6 +100,9 @@
 TESTOPUSENCODE_SRCS_C = tests/test_opus_encode.c tests/opus_encode_regressions.c
 TESTOPUSENCODE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSENCODE_SRCS_C))
 
+TESTOPUSEXTENSIONS_SRCS_C = tests/test_opus_extensions.c
+TESTOPUSEXTENSIONS_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSEXTENSIONS_SRCS_C))
+
 TESTOPUSPADDING_SRCS_C = tests/test_opus_padding.c
 TESTOPUSPADDING_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(TESTOPUSPADDING_SRCS_C))
 
@@ -106,7 +109,7 @@
 OPUSCOMPARE_SRCS_C = src/opus_compare.c
 OPUSCOMPARE_OBJS := $(patsubst %.c,%$(OBJSUFFIX),$(OPUSCOMPARE_SRCS_C))
 
-TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_padding
+TESTS := test_opus_api test_opus_decode test_opus_encode test_opus_extensions test_opus_padding
 
 # Rules
 all: lib opus_demo opus_compare $(TESTS)
@@ -131,6 +134,9 @@
 test_opus_encode$(EXESUFFIX): $(TESTOPUSENCODE_OBJS) $(TARGET)
 	$(LINK.o.cmdline)
 
+test_opus_extensions$(EXESUFFIX): $(TESTOPUSEXTENSIONS_OBJS) $(TARGET)
+	$(LINK.o.cmdline)
+
 test_opus_padding$(EXESUFFIX): $(TESTOPUSPADDING_OBJS) $(TARGET)
 	$(LINK.o.cmdline)
 
@@ -152,8 +158,10 @@
 clean:
 	rm -f opus_demo$(EXESUFFIX) opus_compare$(EXESUFFIX) $(TARGET) \
                 test_opus_api$(EXESUFFIX) test_opus_decode$(EXESUFFIX) \
-                test_opus_encode$(EXESUFFIX) test_opus_padding$(EXESUFFIX) \
+                test_opus_encode$(EXESUFFIX) test_opus_extensions$(EXESUFFIX) \
+                test_opus_padding$(EXESUFFIX)
 		$(OBJS) $(OPUSDEMO_OBJS) $(OPUSCOMPARE_OBJS) $(TESTOPUSAPI_OBJS) \
-                $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) $(TESTOPUSPADDING_OBJS)
+                $(TESTOPUSDECODE_OBJS) $(TESTOPUSENCODE_OBJS) \
+                $(TESTOPUSEXTENSIONS_OBJS) $(TESTOPUSPADDING_OBJS)
 
 .PHONY: all lib clean force check
--- a/autogen.sh
+++ b/autogen.sh
@@ -9,6 +9,7 @@
 srcdir=`dirname $0`
 test -n "$srcdir" && cd "$srcdir"
 
+git submodule update --init
 (cd lpcnet; ./download_model.sh 9daefbb)
 
 echo "Updating build configuration files, please wait...."
--- a/cmake/OpusSources.cmake
+++ b/cmake/OpusSources.cmake
@@ -37,6 +37,9 @@
                  celt_sources_arm_neon_intr)
 get_opus_sources(CELT_SOURCES_ARM_NE10 celt_sources.mk celt_sources_arm_ne10)
 
+get_opus_sources(LPCNET_HEAD lpcnet_headers.mk lpcnet_headers)
+get_opus_sources(LPCNET_SOURCES lpcnet_sources.mk lpcnet_sources)
+
 get_opus_sources(opus_demo_SOURCES Makefile.am opus_demo_sources)
 get_opus_sources(opus_custom_demo_SOURCES Makefile.am opus_custom_demo_sources)
 get_opus_sources(opus_compare_SOURCES Makefile.am opus_compare_sources)
@@ -43,6 +46,8 @@
 get_opus_sources(tests_test_opus_api_SOURCES Makefile.am test_opus_api_sources)
 get_opus_sources(tests_test_opus_encode_SOURCES Makefile.am
                  test_opus_encode_sources)
+get_opus_sources(tests_test_opus_extensions_SOURCES Makefile.am
+                 test_opus_extensions_sources)
 get_opus_sources(tests_test_opus_decode_SOURCES Makefile.am
                  test_opus_decode_sources)
 get_opus_sources(tests_test_opus_padding_SOURCES Makefile.am
--- a/src/extensions.c
+++ b/src/extensions.c
@@ -99,6 +99,10 @@
    opus_int32 curr_len;
    opus_int32 count=0;
    const unsigned char *curr_data = data;
+
+   celt_assert(len >= 0);
+   celt_assert(data != NULL || len == 0);
+
    curr_len = len;
    while (curr_len > 0)
    {
@@ -105,9 +109,11 @@
       int id;
       opus_int32 header_size;
       id = *curr_data>>1;
+      curr_len = skip_extension(&curr_data, curr_len, &header_size);
+      if (curr_len < 0)
+         return OPUS_INVALID_PACKET;
       if (id > 1)
          count++;
-      curr_len = skip_extension(&curr_data, curr_len, &header_size);
    }
    return count;
 }
@@ -120,6 +126,11 @@
    int curr_frame=0;
    opus_int32 count=0;
 
+   celt_assert(len >= 0);
+   celt_assert(data != NULL || len == 0);
+   celt_assert(nb_extensions != NULL);
+   celt_assert(extensions != NULL || *nb_extensions == 0);
+
    curr_data = data;
    curr_len = len;
    while (curr_len > 0)
@@ -181,10 +192,14 @@
    int curr_frame = 0;
    opus_int32 pos = 0;
    opus_int32 written = 0;
+
+   celt_assert(len >= 0);
+   celt_assert(data != NULL || len == 0);
+
    for (i=0;i<nb_extensions;i++)
    {
       max_frame = IMAX(max_frame, extensions[i].frame);
-      if (extensions[i].id < 2)
+      if (extensions[i].id < 2 || extensions[i].id > 127)
          return OPUS_BAD_ARG;
    }
    if (max_frame >= 48) return OPUS_BAD_ARG;
@@ -225,6 +240,8 @@
             } else {
                int last;
                opus_int32 length_bytes;
+               if (extensions[i].len < 0)
+                  return OPUS_BAD_ARG;
                last = (written == nb_extensions - 1);
                length_bytes = 1 + extensions[i].len/255;
                if (last)
--- a/src/opus_private.h
+++ b/src/opus_private.h
@@ -214,6 +214,8 @@
 
 opus_int32 opus_packet_extensions_generate(unsigned char *data, opus_int32 len, const opus_extension_data  *extensions, int nb_extensions, int pad);
 
+opus_int32 opus_packet_extensions_count(const unsigned char *data, opus_int32 len);
+
 opus_int32 opus_packet_pad_impl(unsigned char *data, opus_int32 len, opus_int32 new_len, int pad, const opus_extension_data  *extensions, int nb_extensions);
 
 #endif /* OPUS_PRIVATE_H */
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -3,6 +3,7 @@
   ['test_opus_api'],
   ['test_opus_decode', [], 60],
   ['test_opus_encode', 'opus_encode_regressions.c', 120],
+  ['test_opus_extensions'],
   ['test_opus_padding'],
   ['test_opus_projection'],
 ]
--- a/tests/test_opus_common.h
+++ b/tests/test_opus_common.h
@@ -82,4 +82,5 @@
 }
 #define test_failed() _test_failed(__FILE__, __LINE__);
 #define opus_test_assert(cond) {if (!(cond)) {test_failed();}}
+#define expect_true(cond, msg) {if (!(cond)) {fprintf(stderr, "FAIL - %s\n", msg); test_failed();}}
 void regression_test(void);
--- /dev/null
+++ b/tests/test_opus_extensions.c
@@ -1,0 +1,313 @@
+/* Copyright (c) 2023 Amazon
+   Written by Michael Klingbeil */
+/*
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+   - Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+   - Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+   OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+/* including sources directly to test internal APIs */
+#define CELT_C /* to make celt_assert work */
+#include "../src/extensions.c"
+#include "test_opus_common.h"
+
+void test_extensions_generate_success(void)
+{
+   static const opus_extension_data ext[] = {
+      {2, 0, (const unsigned char *)"a", 1},
+      {32, 10, (const unsigned char *)"DRED", 4},
+      {33, 1, (const unsigned char *)"NOT DRED", 8},
+      {3, 4, (const unsigned char *)NULL, 0}
+   };
+
+   int result;
+   unsigned char packet[32];
+   const unsigned char *p = packet;
+   result = opus_packet_extensions_generate(packet, 23+4, ext, 4, 1);
+   expect_true(result == 23+4, "expected length 23+4");
+
+   /* expect padding */
+   expect_true(p[0] == 1 && p[1] == 1 && p[2] == 1 && p[3] == 1, "expected padding");
+   p += 4;
+
+   /* extension ID=2 */
+   expect_true((p[0] >> 1) == 2, "expected extension id 2");
+   /* For extension IDs 1 through 31, L=0 means that no data follows the
+      extension, whereas L=1 means that exactly one byte of extension data follows. */
+   expect_true((p[0] & 0x01) == 1, "expected L-bit set");
+   /* content */
+   expect_true(p[1] == 'a', "expected extension content");
+   p += 2;
+
+   /* next byte should increment the frame count, ID=1, L=0 */
+   expect_true(p[0] == 0x02, "bad frame separator");
+   p += 1;
+   /* extension ID=33 */
+   expect_true((p[0] >> 1) == 33, "expected extension id 33");
+   /* For IDs 32 to 127, L=0 signals that the extension data takes up the
+      rest of the padding, and L=1 signals that a length indicator follows. */
+   expect_true((p[0] & 0x01) == 1, "expected L-bit set");
+   /* content */
+   expect_true(p[1] == ext[2].len, "expected length");
+   p += 2;
+   expect_true(0 == memcmp(p, ext[2].data, ext[2].len), "expected extension content");
+   p += ext[2].len;
+
+   /* advance to frame 4, increment by 3 */
+   /* next byte should increment the frame count, ID=1, L=1 */
+   expect_true(p[0] == 0x03, "bad frame separator");
+   expect_true(p[1] == 0x03, "bad frame increment");
+   p += 2;
+   /* extension ID=3 */
+   expect_true((p[0] >> 1) == 3, "expected extension id 3");
+   /* For extension IDs 1 through 31, L=0 means that no data follows the
+      extension, whereas L=1 means that exactly one byte of extension data follows. */
+   expect_true((p[0] & 0x01) == 0, "expected L-bit unset");
+   p += 1;
+
+   /* advance to frame 10, increment by 6 */
+   /* next byte should increment the frame count, ID=1, L=1 */
+   expect_true(p[0] == 0x03, "bad frame separator");
+   expect_true(p[1] == 0x06, "bad frame increment");
+   p += 2;
+   /* extension ID=32 */
+   expect_true((p[0] >> 1) == 32, "expected extension id 32");
+   /* For IDs 32 to 127, L=0 signals that the extension data takes up the
+      rest of the padding */
+   expect_true((p[0] & 0x01) == 0, "expected L-bit unset");
+   p += 1;
+   expect_true(0 == memcmp(p, ext[3].data, ext[3].len), "expected extension content");
+}
+
+void test_extensions_generate_zero(void)
+{
+   int result;
+   unsigned char packet[32];
+
+   /* zero length packet, zero extensions */
+   result = opus_packet_extensions_generate(packet, 0, NULL, 0, 1);
+   expect_true(result == 0, "expected length 0");
+}
+
+void test_extensions_generate_no_padding(void)
+{
+   static const opus_extension_data ext[] = {
+      {2, 0, (const unsigned char *)"a", 1},
+      {32, 10, (const unsigned char *)"DRED", 4},
+      {33, 1, (const unsigned char *)"NOT DRED", 8},
+      {3, 4, (const unsigned char *)NULL, 0}
+   };
+
+   int result;
+   unsigned char packet[32];
+   result = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   expect_true(result == 23, "expected length 23");
+}
+
+void test_extensions_generate_fail(void)
+{
+   static const opus_extension_data ext[] = {
+      {2, 0, (const unsigned char *)"a", 1},
+      {32, 10, (const unsigned char *)"DRED", 4},
+      {33, 1, (const unsigned char *)"NOT DRED", 8},
+      {3, 4, (const unsigned char *)NULL, 0}
+   };
+
+   int result;
+   unsigned char packet[100];
+
+   /* buffer too small */
+   result = opus_packet_extensions_generate(packet, 4, ext, 4, 1);
+   expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+
+   /* invalid id */
+   {
+      static const opus_extension_data id_too_big[] = {
+         {256, 0, (const unsigned char *)"a", 1},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_big, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* invalid id */
+   {
+      static const opus_extension_data id_too_small[] = {
+         {1, 0, (const unsigned char *)"a", 1},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), id_too_small, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* frame index too big */
+   {
+      static const opus_extension_data frame_too_big[] = {
+         {33, 48, (const unsigned char *)"a", 1},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), frame_too_big, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* size too big for extension IDs 1 through 31 */
+   {
+      static const opus_extension_data size_too_big[] = {
+         {2, 0, (const unsigned char *)"abcd", 4},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), size_too_big, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* negative size for extension IDs 1 through 31 */
+   {
+      static const opus_extension_data neg_size[] = {
+         {2, 0, NULL, -4},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+
+   /* negative size for extension IDs 32 through 127 */
+   {
+      static const opus_extension_data neg_size_33[] = {
+         {33, 0, NULL, -4},
+      };
+      result = opus_packet_extensions_generate(packet, sizeof(packet), neg_size_33, 1, 1);
+      expect_true(result == OPUS_BAD_ARG, "expected OPUS_BAD_ARG");
+   }
+}
+
+void test_extensions_parse_success(void)
+{
+   static const opus_extension_data ext[] = {
+      {2, 0, (const unsigned char *)"a", 1},
+      {32, 10, (const unsigned char *)"DRED", 4},
+      {33, 1, (const unsigned char *)"NOT DRED", 8},
+      {3, 4, (const unsigned char *)NULL, 0}
+   };
+   opus_extension_data ext_out[10];
+   int nb_ext;
+   int len, result;
+   unsigned char packet[32];
+
+   nb_ext = 10;
+   len = opus_packet_extensions_generate(packet, 32, ext, 4, 1);
+   expect_true(len == 32, "expected length 32");
+   result = opus_packet_extensions_count(packet, len);
+   expect_true(result == 4, "expected opus_packet_extensions_count 4");
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   expect_true(nb_ext == 4, "expected 4 extensions");
+
+   expect_true(ext_out[0].id == 2, "expected id 2");
+   expect_true(ext_out[0].frame == 0, "expected frame 0");
+   expect_true(ext_out[0].len == 1, "expected len 1");
+   expect_true(0 == memcmp(ext_out[0].data, ext[0].data, 1), "expected data");
+
+   expect_true(ext_out[1].id == 33, "expected id 33");
+   expect_true(ext_out[1].frame == 1, "expected frame 1");
+   expect_true(ext_out[1].len == 8, "expected len 8");
+   expect_true(0 == memcmp(ext_out[1].data, ext[2].data, 8), "expected data");
+
+   expect_true(ext_out[2].id == 3, "expected id 3");
+   expect_true(ext_out[2].frame == 4, "expected frame 4");
+   expect_true(ext_out[2].len == 0, "expected len 0");
+
+   expect_true(ext_out[3].id == 32, "expected id 32");
+   expect_true(ext_out[3].frame == 10, "expected frame 10");
+   expect_true(ext_out[3].len == 4, "expected len 4");
+   expect_true(0 == memcmp(ext_out[3].data, ext[1].data, 4), "expected data");
+}
+
+void test_extensions_parse_zero(void)
+{
+   static const opus_extension_data ext[] = {
+      {32, 1, (const unsigned char *)"DRED", 4},
+   };
+   int nb_ext;
+   int len, result;
+   unsigned char packet[32];
+
+   len = opus_packet_extensions_generate(packet, 32, ext, 1, 1);
+   expect_true(len == 32, "expected length 32");
+
+   nb_ext = 0;
+   result = opus_packet_extensions_parse(packet, len, NULL, &nb_ext);
+   expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+}
+
+void test_extensions_parse_fail(void)
+{
+   static const opus_extension_data ext[] = {
+      {2, 0, (const unsigned char *)"a", 1},
+      {33, 1, (const unsigned char *)"NOT DRED", 8},
+      {3, 4, (const unsigned char *)NULL, 0},
+      {32, 10, (const unsigned char *)"DRED", 4}
+   };
+   opus_extension_data ext_out[10];
+   int nb_ext;
+   int len, result;
+   unsigned char packet[32];
+
+   /* create invalid length */
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   packet[4] = 255;
+   nb_ext = 10;
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+   result = opus_packet_extensions_count(packet, len);
+   expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+
+   /* create invalid frame increment */
+   nb_ext = 10;
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   packet[14] = 255;
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   expect_true(result == OPUS_INVALID_PACKET, "expected OPUS_INVALID_PACKET");
+   /* note, opus_packet_extensions_count does not read the invalid frame increment
+      and tells us that we have 4 extensions */
+   result = opus_packet_extensions_count(packet, len);
+   expect_true(result == 4, "expected opus_packet_extensions_count to return 4");
+
+   /* not enough space */
+   nb_ext = 1;
+   len = opus_packet_extensions_generate(packet, sizeof(packet), ext, 4, 0);
+   result = opus_packet_extensions_parse(packet, len, ext_out, &nb_ext);
+   expect_true(result == OPUS_BUFFER_TOO_SMALL, "expected OPUS_BUFFER_TOO_SMALL");
+}
+
+int main(void)
+{
+   test_extensions_generate_success();
+   test_extensions_generate_zero();
+   test_extensions_generate_no_padding();
+   test_extensions_generate_fail();
+   test_extensions_parse_success();
+   test_extensions_parse_zero();
+   test_extensions_parse_fail();
+   fprintf(stderr,"Tests completed successfully.\n");
+   return 0;
+}
--