text: //
  // Copyright (c) 2013-2021 Winlin
  //
  // SPDX-License-Identifier: MIT
  //
  #include <srs_utest_rtc.hpp>
  
  #include <srs_kernel_error.hpp>
  #include <srs_core_autofree.hpp>
  #include <srs_app_rtc_queue.hpp>
  #include <srs_app_utility.hpp>
  #include <srs_kernel_rtc_rtp.hpp>
  #include <srs_app_rtc_source.hpp>
  #include <srs_app_rtc_conn.hpp>
  #include <srs_kernel_codec.hpp>
  #include <srs_app_conn.hpp>
  
  #include <srs_utest_service.hpp>
  
  #include <vector>
  using namespace std;
  
  VOID TEST(KernelRTCTest, RtpSTAPPayloadException)
  {
      srs_error_t err = srs_success;
  
      unsigned char rtp_pkt[328] = {
          0x90, 0xe0, 0x65, 0x8d, 0x37, 0xbc, 0x20, 0xb7, 0xd8, 0xf7, 0xae, 0x77, 0xbe, 0xde, 0x00, 0x03,
          0x51, 0x06, 0x4f, 0xd5, 0x2f, 0x0e, 0xe1, 0x90, 0x75, 0xc3, 0x00, 0x00, 0xd8, 0x01, 0x00, 0x03,
          0xef, 0x93, 0xc7, 0x6a, 0x23, 0x45, 0xdc, 0xb0, 0xce, 0x2b, 0x51, 0x1a, 0x8a, 0xd1, 0x35, 0xab,
          0x11, 0xa7, 0x15, 0xc4, 0xd6, 0xe4, 0x5d, 0x12, 0x6c, 0x04, 0x86, 0x25, 0xd3, 0x88, 0x76, 0xa2,
          0xb8, 0x58, 0x47, 0x0d, 0x0a, 0xd6, 0x2b, 0x85, 0x04, 0x6a, 0x09, 0x2a, 0x4a, 0xce, 0x22, 0xa2,
          0x05, 0x78, 0x8e, 0x71, 0x5c, 0x22, 0x23, 0x58, 0x9e, 0x16, 0x15, 0xe1, 0x5f, 0xff, 0xfd, 0x32,
          0x0a, 0xe2, 0xb8, 0xea, 0xd6, 0xba, 0xd5, 0x7e, 0x5a, 0xd6, 0x61, 0x1c, 0x82, 0x38, 0xce, 0x4a,
          0xd7, 0xe2, 0xea, 0xaa, 0xab, 0xa8, 0x83, 0xf6, 0x7f, 0x10, 0xf1, 0x7c, 0x55, 0x4d, 0xeb, 0xaa,
          0xf8, 0xfd, 0x35, 0xaa, 0xeb, 0x59, 0x8e, 0xf8, 0x8f, 0x12, 0xb9, 0xdd, 0x39, 0xfa, 0x3f, 0x62,
          0x9e, 0x23, 0x96, 0xab, 0x5e, 0xc4, 0xce, 0x97, 0x55, 0x43, 0x65, 0x29, 0xde, 0x8f, 0xe2, 0xb9,
          0x0f, 0xb8, 0xd0, 0xee, 0x00, 0x31, 0x35, 0xdb, 0x5a, 0xff, 0xff, 0xf8, 0x10, 0xa9, 0x3c, 0xf7,
          0x90, 0x8c, 0xf7, 0x3f, 0x5f, 0xd7, 0x15, 0xac, 0xee, 0xa8, 0xfe, 0x23, 0x84, 0x8b, 0xe6, 0x97,
          0x2a, 0x61, 0x38, 0xba, 0xd3, 0xee, 0x7b, 0x49, 0xfa, 0x81, 0xcb, 0x3f, 0x72, 0xd5, 0x56, 0x8f,
          0xe7, 0x7b, 0x1d, 0xda, 0x85, 0x71, 0xbc, 0x45, 0x75, 0x5d, 0x55, 0x47, 0xc5, 0xf5, 0x36, 0xe4,
          0xa9, 0x17, 0x4a, 0x84, 0xf9, 0xdd, 0xd0, 0xa5, 0xb1, 0xcf, 0x69, 0xcf, 0xcd, 0x1d, 0xac, 0xe4,
          0xc6, 0x3d, 0xd0, 0x95, 0xa3, 0xbd, 0x0a, 0xd4, 0xa2, 0xb9, 0x05, 0x78, 0xae, 0x5a, 0x92, 0xb5,
          0x90, 0x4b, 0xa6, 0x85, 0x3c, 0x27, 0xb3, 0x4d, 0xd2, 0x5c, 0xfa, 0x61, 0x01, 0x4a, 0xa6, 0xd9,
          0x26, 0xf3, 0x78, 0x44, 0x57, 0x2e, 0x79, 0xc5, 0x71, 0x42, 0xb5, 0x34, 0x87, 0x94, 0x57, 0x8a,
          0xe1, 0x09, 0xb3, 0x8a, 0xe7, 0x0b, 0x7f, 0xfc, 0xff, 0xec, 0x28, 0xe3, 0x4c, 0xff, 0xff, 0xa6,
          0x6a, 0xca, 0x2b, 0x84, 0xab, 0x0a, 0xd7, 0xf1, 0xf5, 0x9a, 0x47, 0x08, 0x54, 0xd5, 0xac, 0x9a,
          0xf5, 0x09, 0x5a, 0x29, 0x35, 0x52, 0x79, 0xe0,
      };
  
      int nb_buf = sizeof(rtp_pkt);
      SrsBuffer buf((char*)rtp_pkt, nb_buf);
  
      SrsRtpHeader header;
      EXPECT_TRUE((err = header.decode(&buf)) == srs_success);
  
      // We must skip the padding bytes before parsing payload.
      uint8_t padding = header.get_padding();
      EXPECT_TRUE(buf.require(padding));
      buf.set_size(buf.size() - padding);
  
      SrsAvcNaluType nalu_type = SrsAvcNaluTypeReserved;
      // Try to parse the NALU type for video decoder.
      if (!buf.empty()) {
          nalu_type = SrsAvcNaluType((uint8_t)(buf.head()[0] & kNalTypeMask));
      }
  
      EXPECT_TRUE(nalu_type == kStapA);
      ISrsRtpPayloader* payload = new SrsRtpSTAPPayload();
  
      EXPECT_TRUE((err = payload->decode(&buf)) != srs_success);
      srs_freep(payload);
  }
  
  class MockResource : public ISrsDisposingHandler, public ISrsResource
  {
  public:
      SrsResourceManager* manager_;
      MockResource(SrsResourceManager* manager) {
          manager_ = manager;
          if (manager_) {
              manager_->subscribe(this);
          }
      }
      virtual ~MockResource() {
          if (manager_) {
              manager_->unsubscribe(this);
          }
      }
      virtual const SrsContextId& get_id() {
          return _srs_context->get_id();
      }
      virtual std::string desc() {
          return "";
      }
  };
  
  class MockResourceHookOwner : public MockResource
  {
  public:
      ISrsResource* owner_;
      MockResourceHookOwner(SrsResourceManager* manager) : MockResource(manager) {
          owner_ = NULL;
      }
      virtual ~MockResourceHookOwner() {
      }
      virtual void on_before_dispose(ISrsResource* c) {
          if (c == owner_) { // Remove self if its owner is disposing.
              manager_->remove(this);
          }
      }
      virtual void on_disposing(ISrsResource* c) {
      }
  };
  
  class MockResourceSelf : public MockResource
  {
  public:
      bool remove_in_before_dispose;
      bool remove_in_disposing;
      MockResourceSelf(SrsResourceManager* manager) : MockResource(manager) {
          remove_in_before_dispose = remove_in_disposing = false;
      }
      virtual ~MockResourceSelf() {
      }
      virtual void on_before_dispose(ISrsResource* c) {
          if (remove_in_before_dispose) {
              manager_->remove(this);
          }
      }
      virtual void on_disposing(ISrsResource* c) {
          if (remove_in_disposing) {
              manager_->remove(this);
          }
      }
  };
  
  class MockResourceUnsubscribe : public MockResource
  {
  public:
      int nn_before_dispose;
      int nn_disposing;
      bool unsubscribe_in_before_dispose;
      bool unsubscribe_in_disposing;
      MockResourceUnsubscribe* result;
      MockResourceUnsubscribe(SrsResourceManager* manager) : MockResource(manager) {
          unsubscribe_in_before_dispose = unsubscribe_in_disposing = false;
          nn_before_dispose = nn_disposing = 0;
          result = NULL;
      }
      virtual ~MockResourceUnsubscribe() {
          if (result) { // Copy result before disposing it.
              *result = *this;
          }
      }
      virtual void on_before_dispose(ISrsResource* c) {
          nn_before_dispose++;
          if (unsubscribe_in_before_dispose) {
              manager_->unsubscribe(this);
          }
      }
      virtual void on_disposing(ISrsResource* c) {
          nn_disposing++;
          if (unsubscribe_in_disposing) {
              manager_->unsubscribe(this);
          }
      }
  };
  
  VOID TEST(KernelRTCTest, ConnectionManagerTest)
  {
      srs_error_t err = srs_success;
  
      // When notifying, the handlers changed, disposing event may lost.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceUnsubscribe* conn0 = new MockResourceUnsubscribe(&manager);
          conn0->unsubscribe_in_disposing = true;
          manager.add(conn0);
  
          MockResourceUnsubscribe* conn1 = new MockResourceUnsubscribe(&manager);
          manager.add(conn1);
  
          MockResourceUnsubscribe* conn2 = new MockResourceUnsubscribe(&manager);
          manager.add(conn2);
  
          // When removing conn0, it will unsubscribe and change the handlers,
          // which should not cause the conn1 lost event.
          manager.remove(conn0);
          srs_usleep(0);
          ASSERT_EQ(2, (int)manager.size());
  
          EXPECT_EQ(1, conn1->nn_before_dispose);
          EXPECT_EQ(1, conn1->nn_disposing); // Should get event.
  
          EXPECT_EQ(1, conn2->nn_before_dispose);
          EXPECT_EQ(1, conn2->nn_disposing);
      }
  
      // When notifying, the handlers changed, before-dispose event may lost.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceUnsubscribe* conn0 = new MockResourceUnsubscribe(&manager);
          conn0->unsubscribe_in_before_dispose = true;
          manager.add(conn0);
  
          MockResourceUnsubscribe* conn1 = new MockResourceUnsubscribe(&manager);
          manager.add(conn1);
  
          MockResourceUnsubscribe* conn2 = new MockResourceUnsubscribe(&manager);
          manager.add(conn2);
  
          // When removing conn0, it will unsubscribe and change the handlers,
          // which should not cause the conn1 lost event.
          manager.remove(conn0);
          srs_usleep(0);
          ASSERT_EQ(2, (int)manager.size());
  
          EXPECT_EQ(1, conn1->nn_before_dispose); // Should get event.
          EXPECT_EQ(1, conn1->nn_disposing);
  
          EXPECT_EQ(1, conn2->nn_before_dispose);
          EXPECT_EQ(1, conn2->nn_disposing);
      }
  
      // Subscribe or unsubscribe for multiple times.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceUnsubscribe* resource = new MockResourceUnsubscribe(&manager);
          resource->unsubscribe_in_before_dispose = true;
          manager.add(resource);
  
          MockResourceUnsubscribe result(NULL); // No manager for result.
          resource->result = &result;
  
          manager.remove(resource);
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
  
          EXPECT_EQ(1, result.nn_before_dispose);
          EXPECT_EQ(0, result.nn_disposing); // No disposing event, because we unsubscribe in before-dispose.
      }
  
      // Count the event for disposing.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceUnsubscribe* resource = new MockResourceUnsubscribe(&manager);
          manager.add(resource);
  
          MockResourceUnsubscribe result(NULL); // No manager for result.
          resource->result = &result;
  
          manager.remove(resource);
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
  
          EXPECT_EQ(1, result.nn_before_dispose);
          EXPECT_EQ(1, result.nn_disposing);
      }
  
      // When hooks disposing, remove itself again.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceSelf* resource = new MockResourceSelf(&manager);
          resource->remove_in_disposing = true;
          manager.add(resource);
          EXPECT_EQ(1, (int)manager.size());
  
          manager.remove(resource);
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
      }
  
      // When hooks before-dispose, remove itself again.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceSelf* resource = new MockResourceSelf(&manager);
          resource->remove_in_before_dispose = true;
          manager.add(resource);
          EXPECT_EQ(1, (int)manager.size());
  
          manager.remove(resource);
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
      }
  
      // Cover all normal scenarios.
      if (true) {
          SrsResourceManager manager("mgr", true);
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          // Resource without id or name.
          manager.add_with_id("100", new MockSrsConnection());
          manager.add_with_id("101", new MockSrsConnection());
          manager.add_with_name("srs", new MockSrsConnection());
          manager.add_with_name("av", new MockSrsConnection());
          ASSERT_EQ(4, (int)manager.size());
  
          manager.remove(manager.at(3));
          manager.remove(manager.at(2));
          manager.remove(manager.at(1));
          manager.remove(manager.at(0));
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
      }
  
      // Callback: Remove worker when its master is disposing.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockResourceHookOwner* master = new MockResourceHookOwner(&manager);
          manager.add(master);
          EXPECT_EQ(1, (int)manager.size());
  
          MockResourceHookOwner* worker = new MockResourceHookOwner(&manager);
          worker->owner_ = master; // When disposing master, worker will hook the event and remove itself.
          manager.add(worker);
          EXPECT_EQ(2, (int)manager.size());
  
          manager.remove(master);
          srs_usleep(0); // Trigger the disposing.
  
          // Both master and worker should be disposed.
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
      }
  
      // Normal scenario, free object by manager.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          MockSrsConnection* conn = new MockSrsConnection();
          manager.add(conn);
          EXPECT_EQ(1, (int)manager.size()); EXPECT_FALSE(manager.empty());
  
          manager.remove(conn);
          srs_usleep(0); // Switch context for manager to dispose connections.
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
      }
  
      // Resource with id or name.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          // Resource without id or name.
          MockSrsConnection* conn = new MockSrsConnection();
          manager.add(conn);
          ASSERT_EQ(1, (int)manager.size());
          EXPECT_TRUE(manager.at(0));
          EXPECT_TRUE(!manager.at(1));
          EXPECT_TRUE(!manager.find_by_id("100"));
          EXPECT_TRUE(!manager.find_by_name("srs"));
  
          manager.remove(conn);
          srs_usleep(0);
          ASSERT_EQ(0, (int)manager.size());
  
          // Resource with id.
          if (true) {
              MockSrsConnection* id = new MockSrsConnection();
              manager.add_with_id("100", id);
              EXPECT_EQ(1, (int)manager.size());
              EXPECT_TRUE(manager.find_by_id("100"));
              EXPECT_TRUE(!manager.find_by_id("101"));
              EXPECT_TRUE(!manager.find_by_name("100"));
  
              manager.remove(id);
              srs_usleep(0);
              ASSERT_EQ(0, (int)manager.size());
          }
  
          // Resource with name.
          if (true) {
              MockSrsConnection* name = new MockSrsConnection();
              manager.add_with_name("srs", name);
              EXPECT_EQ(1, (int)manager.size());
              EXPECT_TRUE(manager.find_by_name("srs"));
              EXPECT_TRUE(!manager.find_by_name("srs0"));
              EXPECT_TRUE(!manager.find_by_id("srs"));
  
              manager.remove(name);
              srs_usleep(0);
              ASSERT_EQ(0, (int)manager.size());
          }
  
          // Resource with id and name.
          if (true) {
              MockSrsConnection* id_name = new MockSrsConnection();
              manager.add_with_id("100", id_name);
              manager.add_with_id("200", id_name);
              manager.add_with_name("srs", id_name);
              manager.add_with_name("av", id_name);
              EXPECT_EQ(1, (int)manager.size());
              EXPECT_TRUE(manager.find_by_name("srs"));
              EXPECT_TRUE(manager.find_by_name("av"));
              EXPECT_TRUE(manager.find_by_id("100"));
              EXPECT_TRUE(manager.find_by_id("200"));
              EXPECT_TRUE(!manager.find_by_name("srs0"));
              EXPECT_TRUE(!manager.find_by_id("101"));
  
              manager.remove(id_name);
              srs_usleep(0);
              ASSERT_EQ(0, (int)manager.size());
          }
  
          // Resource with same id or name.
          if (true) {
              MockSrsConnection* conn0 = new MockSrsConnection();
              MockSrsConnection* conn1 = new MockSrsConnection();
              manager.add_with_id("100", conn0);
              manager.add_with_id("100", conn1);
  
              EXPECT_TRUE(conn0 != manager.find_by_id("100"));
              EXPECT_TRUE(conn1 == manager.find_by_id("100"));
  
              manager.remove(conn0);
              srs_usleep(0);
              ASSERT_EQ(1, (int)manager.size());
  
              manager.remove(conn1);
              srs_usleep(0);
              ASSERT_EQ(0, (int)manager.size());
          }
      }
  
      // Coroutine switch context, signal is lost.
      if (true) {
          SrsResourceManager manager("mgr");
          HELPER_EXPECT_SUCCESS(manager.start());
          EXPECT_EQ(0, (int)manager.size()); EXPECT_TRUE(manager.empty());
  
          if (true) { // First connection, which will switch context when deleting.
              MockSrsConnection* conn = new MockSrsConnection();
              conn->do_switch = true;
              manager.add(conn);
              EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size());
  
              manager.remove(conn); // Remove conn to zombies.
              EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(1, (int)manager.zombies_.size());
  
              srs_usleep(0); // Switch to manager coroutine to try to free zombies.
              EXPECT_EQ(0, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size());
          }
  
          if (true) { // Now the previous conn switch back to here, and lost the signal.
              MockSrsConnection* conn = new MockSrsConnection();
              manager.add(conn);
              EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size());
  
              manager.remove(conn); // Remove conn to zombies, signal is lost.
              EXPECT_EQ(1, (int)manager.size()); EXPECT_EQ(1, (int)manager.zombies_.size());
  
              srs_usleep(0); // Switch to manager, but no signal is triggered before, so conn will be freed by loop.
              EXPECT_EQ(0, (int)manager.size()); EXPECT_EQ(0, (int)manager.zombies_.size());
          }
      }
  }
  
  VOID TEST(KernelRTCTest, StringDumpHexTest)
  {
      // Typical normal case.
      if (false) {
          char data[8];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
          EXPECT_EQ(16, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1));
          EXPECT_EQ('6', r.at(r.length() - 4)); EXPECT_EQ('7', r.at(r.length() - 3));
      }
  
      // Fill all buffer.
      if (false) {
          char data[8 * 1024];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
          EXPECT_EQ(16 * 1024, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1));
          EXPECT_EQ('6', r.at(r.length() - 4)); EXPECT_EQ('7', r.at(r.length() - 3));
      }
  
      // Overflow 1 byte.
      if (true) {
          char data[8 * 1024 + 1];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, 0, 0, 0);
          EXPECT_EQ(16 * 1024, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1));
      }
  
      // Fill all buffer, with seperator.
      if (true) {
          char data[5461];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0);
          EXPECT_EQ(16383 - 1, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('c', r.at(r.length() - 2)); EXPECT_EQ('3', r.at(r.length() - 1));
          EXPECT_EQ('6', r.at(r.length() - 5)); EXPECT_EQ('7', r.at(r.length() - 4));
      }
  
      // Overflow 1 byte, with seperator.
      if (true) {
          char data[5461 + 1];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 0, 0);
          EXPECT_EQ(16383 - 1, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1));
      }
  
      // Overflow 1 byte, with seperator and newline.
      if (true) {
          char data[5461 + 1];
          data[0] = (char)0x3c; data[sizeof(data) - 2] = (char)0x67; data[sizeof(data) - 1] = (char)0xc3;
          string r = srs_string_dumps_hex(data, sizeof(data), INT_MAX, ',', 5461, '\n');
          EXPECT_EQ(16383 - 1, (int)r.length());
          EXPECT_EQ('3', r.at(0)); EXPECT_EQ('c', r.at(1));
          EXPECT_EQ('6', r.at(r.length() - 2)); EXPECT_EQ('7', r.at(r.length() - 1));
      }
  }
  
  VOID TEST(KernelRTCTest, SequenceCompare)
  {
      if (true) {
          EXPECT_EQ(0, srs_rtp_seq_distance(0, 0));
          EXPECT_EQ(0, srs_rtp_seq_distance(1, 1));
          EXPECT_EQ(0, srs_rtp_seq_distance(3, 3));
  
          EXPECT_EQ(1, srs_rtp_seq_distance(0, 1));
          EXPECT_EQ(-1, srs_rtp_seq_distance(1, 0));
          EXPECT_EQ(1, srs_rtp_seq_distance(65535, 0));
      }
  
      if (true) {
          EXPECT_FALSE(srs_rtp_seq_distance(1, 1) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(65534, 65535) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(0, 1) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(255, 256) > 0);
  
          EXPECT_TRUE(srs_rtp_seq_distance(65535, 0) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(65280, 0) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(65535, 255) > 0);
          EXPECT_TRUE(srs_rtp_seq_distance(65280, 255) > 0);
  
          EXPECT_FALSE(srs_rtp_seq_distance(0, 65535) > 0);
          EXPECT_FALSE(srs_rtp_seq_distance(0, 65280) > 0);
          EXPECT_FALSE(srs_rtp_seq_distance(255, 65535) > 0);
          EXPECT_FALSE(srs_rtp_seq_distance(255, 65280) > 0);
  
          // Note that srs_rtp_seq_distance(0, 32768)>0 is TRUE by https://mp.weixin.qq.com/s/JZTInmlB9FUWXBQw_7NYqg
          //      but for WebRTC jitter buffer it's FALSE and we follow it.
          EXPECT_FALSE(srs_rtp_seq_distance(0, 32768) > 0);
          // It's FALSE definitely.
          EXPECT_FALSE(srs_rtp_seq_distance(32768, 0) > 0);
      }
  
      if (true) {
          EXPECT_FALSE(srs_seq_is_newer(1, 1));
          EXPECT_TRUE(srs_seq_is_newer(65535, 65534));
          EXPECT_TRUE(srs_seq_is_newer(1, 0));
          EXPECT_TRUE(srs_seq_is_newer(256, 255));
  
          EXPECT_TRUE(srs_seq_is_newer(0, 65535));
          EXPECT_TRUE(srs_seq_is_newer(0, 65280));
          EXPECT_TRUE(srs_seq_is_newer(255, 65535));
          EXPECT_TRUE(srs_seq_is_newer(255, 65280));
  
          EXPECT_FALSE(srs_seq_is_newer(65535, 0));
          EXPECT_FALSE(srs_seq_is_newer(65280, 0));
          EXPECT_FALSE(srs_seq_is_newer(65535, 255));
          EXPECT_FALSE(srs_seq_is_newer(65280, 255));
  
          EXPECT_FALSE(srs_seq_is_newer(32768, 0));
          EXPECT_FALSE(srs_seq_is_newer(0, 32768));
      }
  
      if (true) {
          EXPECT_FALSE(srs_seq_distance(1, 1) > 0);
          EXPECT_TRUE(srs_seq_distance(65535, 65534) > 0);
          EXPECT_TRUE(srs_seq_distance(1, 0) > 0);
          EXPECT_TRUE(srs_seq_distance(256, 255) > 0);
  
          EXPECT_TRUE(srs_seq_distance(0, 65535) > 0);
          EXPECT_TRUE(srs_seq_distance(0, 65280) > 0);
          EXPECT_TRUE(srs_seq_distance(255, 65535) > 0);
          EXPECT_TRUE(srs_seq_distance(255, 65280) > 0);
  
          EXPECT_FALSE(srs_seq_distance(65535, 0) > 0);
          EXPECT_FALSE(srs_seq_distance(65280, 0) > 0);
          EXPECT_FALSE(srs_seq_distance(65535, 255) > 0);
          EXPECT_FALSE(srs_seq_distance(65280, 255) > 0);
  
          EXPECT_FALSE(srs_seq_distance(32768, 0) > 0);
          EXPECT_FALSE(srs_seq_distance(0, 32768) > 0);
      }
  
      if (true) {
          EXPECT_FALSE(srs_seq_is_rollback(1, 1));
          EXPECT_FALSE(srs_seq_is_rollback(65535, 65534));
          EXPECT_FALSE(srs_seq_is_rollback(1, 0));
          EXPECT_FALSE(srs_seq_is_rollback(256, 255));
  
          EXPECT_TRUE(srs_seq_is_rollback(0, 65535));
          EXPECT_TRUE(srs_seq_is_rollback(0, 65280));
          EXPECT_TRUE(srs_seq_is_rollback(255, 65535));
          EXPECT_TRUE(srs_seq_is_rollback(255, 65280));
  
          EXPECT_FALSE(srs_seq_is_rollback(65535, 0));
          EXPECT_FALSE(srs_seq_is_rollback(65280, 0));
          EXPECT_FALSE(srs_seq_is_rollback(65535, 255));
          EXPECT_FALSE(srs_seq_is_rollback(65280, 255));
  
          EXPECT_FALSE(srs_seq_is_rollback(32768, 0));
          EXPECT_FALSE(srs_seq_is_rollback(0, 32768));
      }
  }
  
  VOID TEST(KernelRTCTest, DecodeHeaderWithPadding)
  {
      srs_error_t err = srs_success;
  
      // RTP packet cipher with padding.
      uint8_t data[] = {
          0xb0, 0x66, 0x0a, 0x97, 0x7e, 0x32, 0x10, 0xee, 0x7d, 0xe6, 0xd0, 0xe6, 0xbe, 0xde, 0x00, 0x01, 0x31, 0x00, 0x16, 0x00, 0x25, 0xcd, 0xef, 0xce, 0xd7, 0x24, 0x57, 0xd9, 0x3c, 0xfd, 0x0f, 0x77, 0xea, 0x89, 0x61, 0xcb, 0x67, 0xa1, 0x65, 0x4a, 0x7d, 0x1f, 0x10, 0x4e, 0xed, 0x5e, 0x74, 0xe8, 0x7e, 0xce, 0x4d, 0xcf, 0xd5, 0x58, 0xd1, 0x2c, 0x30, 0xf1, 0x26, 0x62, 0xd3, 0x0c, 0x6a, 0x48,
          0x29, 0x83, 0xd2, 0x3d, 0x30, 0xa1, 0x7c, 0x6f, 0xa1, 0x5c, 0x9f, 0x08, 0x43, 0x50, 0x34, 0x2b, 0x3c, 0xa1, 0xf0, 0xb0, 0xe2, 0x0e, 0xc8, 0xf9, 0x79, 0x06, 0x51, 0xfe, 0xbb, 0x13, 0x54, 0x3e, 0xb4, 0x37, 0x91, 0x96, 0x94, 0xb7, 0x61, 0x2e, 0x97, 0x09, 0xb8, 0x27, 0x10, 0x6a, 0x2e, 0xe0, 0x62, 0xe4, 0x37, 0x41, 0xab, 0x4f, 0xbf, 0x06, 0x0a, 0x89, 0x80, 0x18, 0x0d, 0x6e, 0x0a, 0xd1,
          0x9f, 0xf1, 0xdd, 0x12, 0xbd, 0x1a, 0x70, 0x72, 0x33, 0xcc, 0xaa, 0x82, 0xdf, 0x92, 0x90, 0x45, 0xda, 0x3e, 0x88, 0x1c, 0x63, 0x83, 0xbc, 0xc8, 0xff, 0xfd, 0x64, 0xe3, 0xd4, 0x68, 0xe6, 0xc8, 0xdc, 0x81, 0x72, 0x5f, 0x38, 0x5b, 0xab, 0x63, 0x7b, 0x96, 0x03, 0x03, 0x54, 0xc5, 0xe6, 0x35, 0xf6, 0x86, 0xcc, 0xac, 0x74, 0xb0, 0xf4, 0x07, 0x9e, 0x19, 0x30, 0x4f, 0x90, 0xd6, 0xdb, 0x8b,
          0x0d, 0xcb, 0x76, 0x71, 0x55, 0xc7, 0x4a, 0x6e, 0x1b, 0xb4, 0x42, 0xf4, 0xae, 0x81, 0x17, 0x08, 0xb7, 0x50, 0x61, 0x5a, 0x42, 0xde, 0x1f, 0xf3, 0xfd, 0xe2, 0x30, 0xff, 0xb7, 0x07, 0xdd, 0x4b, 0xb1, 0x00, 0xd9, 0x6c, 0x43, 0xa0, 0x9a, 0xfa, 0xbb, 0xec, 0xdf, 0x51, 0xce, 0x33, 0x79, 0x4b, 0xa7, 0x02, 0xf3, 0x96, 0x62, 0x42, 0x25, 0x28, 0x85, 0xa7, 0xe7, 0xd1, 0xd3, 0xf3,
      };
  
      // If not plaintext, the padding in body is invalid,
      // so it will fail if decoding the header with padding.
      if (true) {
          SrsBuffer b((char*)data, sizeof(data)); SrsRtpHeader h;
          HELPER_EXPECT_FAILED(h.decode(&b));
      }
  
      // Should ok if ignore padding.
      if (true) {
          SrsBuffer b((char*)data, sizeof(data)); SrsRtpHeader h;
          h.ignore_padding(true);
          HELPER_EXPECT_SUCCESS(h.decode(&b));
      }
  }
  
  VOID TEST(KernelRTCTest, DumpsHexToString)
  {
      if (true) {
          EXPECT_STREQ("", srs_string_dumps_hex(NULL, 0).c_str());
      }
  
      if (true) {
          uint8_t data[] = {0, 0, 0, 0};
          EXPECT_STREQ("00 00 00 00", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str());
      }
  
      if (true) {
          uint8_t data[] = {0, 1, 2, 3};
          EXPECT_STREQ("00 01 02 03", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str());
      }
  
      if (true) {
          uint8_t data[] = {0xa, 3, 0xf, 3};
          EXPECT_STREQ("0a 03 0f 03", srs_string_dumps_hex((const char*)data, sizeof(data)).c_str());
      }
  
      if (true) {
          uint8_t data[] = {0xa, 3, 0xf, 3};
          EXPECT_STREQ("0a,03,0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 0, 0).c_str());
          EXPECT_STREQ("0a030f03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, '\0', 0, 0).c_str());
          EXPECT_STREQ("0a,03,\n0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 2, '\n').c_str());
          EXPECT_STREQ("0a,03,0f,03", srs_string_dumps_hex((const char*)data, sizeof(data), INT_MAX, ',', 2,'\0').c_str());
      }
  
      if (true) {
          uint8_t data[] = {0xa, 3, 0xf};
          EXPECT_STREQ("0a 03", srs_string_dumps_hex((const char*)data, sizeof(data), 2).c_str());
      }
  }
  
  VOID TEST(KernelRTCTest, NACKFetchRTPPacket)
  {
      SrsRtcConnection s(NULL, SrsContextId());
      SrsRtcPlayStream play(&s, SrsContextId());
  
      SrsRtcTrackDescription ds;
      SrsRtcVideoSendTrack *track = new SrsRtcVideoSendTrack(&s, &ds);
  
      // The RTP queue will free the packet.
      if (true) {
          SrsRtpPacket* pkt = new SrsRtpPacket();
          pkt->header.set_sequence(100);
          track->rtp_queue_->set(pkt->header.get_sequence(), pkt);
      }
  
      // If sequence not match, packet not found.
      if (true) {
          SrsRtpPacket* pkt = track->fetch_rtp_packet(10);
          EXPECT_TRUE(pkt == NULL);
      }
  
      // The sequence matched, we got the packet.
      if (true) {
          SrsRtpPacket* pkt = track->fetch_rtp_packet(100);
          EXPECT_TRUE(pkt != NULL);
      }
  
      // NACK special case.
      if (true) {
          // The sequence is the "same", 1100%1000 is 100,
          // so we can also get it from the RTP queue.
          SrsRtpPacket* pkt = track->rtp_queue_->at(1100);
          EXPECT_TRUE(pkt != NULL);
  
          // But the track requires exactly match, so it returns NULL.
          pkt = track->fetch_rtp_packet(1100);
          EXPECT_TRUE(pkt == NULL);
      }
  }
  
  extern bool srs_is_stun(const uint8_t* data, size_t size);
  extern bool srs_is_dtls(const uint8_t* data, size_t len);
  extern bool srs_is_rtp_or_rtcp(const uint8_t* data, size_t len);
  extern bool srs_is_rtcp(const uint8_t* data, size_t len);
  
  #define mock_arr_push(arr, elem) arr.push_back(vector<uint8_t>(elem, elem + sizeof(elem)))
  
  VOID TEST(KernelRTCTest, TestPacketType)
  {
      // DTLS packet.
      vector< vector<uint8_t> > dtlss;
      if (true) { uint8_t data[13] = {20}; mock_arr_push(dtlss, data); } // change_cipher_spec(20)
      if (true) { uint8_t data[13] = {21}; mock_arr_push(dtlss, data); } // alert(21)
      if (true) { uint8_t data[13] = {22}; mock_arr_push(dtlss, data); } // handshake(22)
      if (true) { uint8_t data[13] = {23}; mock_arr_push(dtlss, data); } // application_data(23)
      for (int i = 0; i < (int)dtlss.size(); i++) {
          vector<uint8_t> elem = dtlss.at(i);
          EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size()));
      }
  
      for (int i = 0; i < (int)dtlss.size(); i++) {
          vector<uint8_t> elem = dtlss.at(i);
          EXPECT_FALSE(srs_is_dtls(&elem[0], 1));
  
          // All DTLS should not be other packets.
          EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
          EXPECT_TRUE(srs_is_dtls(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  
      // STUN packet.
      vector< vector<uint8_t> > stuns;
      if (true) { uint8_t data[1] = {0}; mock_arr_push(stuns, data); } // binding request.
      if (true) { uint8_t data[1] = {1}; mock_arr_push(stuns, data); } // binding success response.
      for (int i = 0; i < (int)stuns.size(); i++) {
          vector<uint8_t> elem = stuns.at(i);
          EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size()));
      }
  
      for (int i = 0; i < (int)stuns.size(); i++) {
          vector<uint8_t> elem = stuns.at(i);
          EXPECT_FALSE(srs_is_stun(&elem[0], 0));
  
          // All STUN should not be other packets.
          EXPECT_TRUE(srs_is_stun(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  
      // RTCP packet.
      vector< vector<uint8_t> > rtcps;
      if (true) { uint8_t data[12] = {0x80, 192}; mock_arr_push(rtcps, data); }
      if (true) { uint8_t data[12] = {0x80, 200}; mock_arr_push(rtcps, data); } // SR
      if (true) { uint8_t data[12] = {0x80, 201}; mock_arr_push(rtcps, data); } // RR
      if (true) { uint8_t data[12] = {0x80, 202}; mock_arr_push(rtcps, data); } // SDES
      if (true) { uint8_t data[12] = {0x80, 203}; mock_arr_push(rtcps, data); } // BYE
      if (true) { uint8_t data[12] = {0x80, 204}; mock_arr_push(rtcps, data); } // APP
      if (true) { uint8_t data[12] = {0x80, 223}; mock_arr_push(rtcps, data); }
      for (int i = 0; i < (int)rtcps.size(); i++) {
          vector<uint8_t> elem = rtcps.at(i);
          EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  
      for (int i = 0; i < (int)rtcps.size(); i++) {
          vector<uint8_t> elem = rtcps.at(i);
          EXPECT_FALSE(srs_is_rtcp(&elem[0], 2));
  
          // All RTCP should not be other packets.
          EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
          EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
          EXPECT_TRUE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  
      // RTP packet.
      vector< vector<uint8_t> > rtps;
      if (true) { uint8_t data[12] = {0x80, 96}; mock_arr_push(rtps, data); }
      if (true) { uint8_t data[12] = {0x80, 127}; mock_arr_push(rtps, data); }
      if (true) { uint8_t data[12] = {0x80, 224}; mock_arr_push(rtps, data); }
      if (true) { uint8_t data[12] = {0x80, 255}; mock_arr_push(rtps, data); }
      for (int i = 0; i < (int)rtps.size(); i++) {
          vector<uint8_t> elem = rtps.at(i);
          EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  
      for (int i = 0; i < (int)rtps.size(); i++) {
          vector<uint8_t> elem = rtps.at(i);
          EXPECT_FALSE(srs_is_rtp_or_rtcp(&elem[0], 2));
  
          // All RTP should not be other packets.
          EXPECT_FALSE(srs_is_stun(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_dtls(&elem[0], (size_t)elem.size()));
          EXPECT_TRUE(srs_is_rtp_or_rtcp(&elem[0], (size_t)elem.size()));
          EXPECT_FALSE(srs_is_rtcp(&elem[0], (size_t)elem.size()));
      }
  }
  
  VOID TEST(KernelRTCTest, DefaultTrackStatus)
  {
      // By default, track is disabled.
      if (true) {
          SrsRtcTrackDescription td;
  
          // The track must default to disable, that is, the active is false.
          EXPECT_FALSE(td.is_active_);
      }
  
      // Enable it by player.
      if (true) {
          SrsRtcConnection s(NULL, SrsContextId()); SrsRtcPlayStream play(&s, SrsContextId());
          SrsRtcAudioSendTrack* audio; SrsRtcVideoSendTrack *video;
  
          if (true) {
              SrsRtcTrackDescription ds; ds.type_ = "audio"; ds.id_ = "NSNWOn19NDn12o8nNeji2"; ds.ssrc_ = 100;
              play.audio_tracks_[ds.ssrc_] = audio = new SrsRtcAudioSendTrack(&s, &ds);
          }
          if (true) {
              SrsRtcTrackDescription ds; ds.type_ = "video"; ds.id_ = "VMo22nfLDn122nfnDNL2"; ds.ssrc_ = 200;
              play.video_tracks_[ds.ssrc_] = video = new SrsRtcVideoSendTrack(&s, &ds);
          }
          EXPECT_FALSE(audio->get_track_status());
          EXPECT_FALSE(video->get_track_status());
  
          play.set_all_tracks_status(true);
          EXPECT_TRUE(audio->get_track_status());
          EXPECT_TRUE(video->get_track_status());
      }
  
      // Enable it by publisher.
      if (true) {
          SrsRtcConnection s(NULL, SrsContextId()); SrsRtcPublishStream publish(&s, SrsContextId());
          SrsRtcAudioRecvTrack* audio; SrsRtcVideoRecvTrack *video;
  
          if (true) {
              SrsRtcTrackDescription ds; ds.type_ = "audio"; ds.id_ = "NSNWOn19NDn12o8nNeji2"; ds.ssrc_ = 100;
              audio = new SrsRtcAudioRecvTrack(&s, &ds); publish.audio_tracks_.push_back(audio);
          }
          if (true) {
              SrsRtcTrackDescription ds; ds.type_ = "video"; ds.id_ = "VMo22nfLDn122nfnDNL2"; ds.ssrc_ = 200;
              video = new SrsRtcVideoRecvTrack(&s, &ds); publish.video_tracks_.push_back(video);
          }
          EXPECT_FALSE(audio->get_track_status());
          EXPECT_FALSE(video->get_track_status());
  
          publish.set_all_tracks_status(true);
          EXPECT_TRUE(audio->get_track_status());
          EXPECT_TRUE(video->get_track_status());
      }
  }
  
  