text: #define MS_CLASS "DepUsrSCTP"
  // #define MS_LOG_DEV_LEVEL 3
  
  #include "DepUsrSCTP.hpp"
  #include "DepLibUV.hpp"
  #include "Logger.hpp"
  #include <usrsctp.h>
  #include <mutex>
  
  /* Static. */
  
  static constexpr size_t CheckerInterval{ 10u }; // In ms.
  static std::mutex globalSyncMutex;
  static size_t globalInstances = 0;
  
  /* Static methods for usrsctp global callbacks. */
  
  inline static int onSendSctpData(void* addr, void* data, size_t len, uint8_t /*tos*/, uint8_t /*setDf*/)
  {
  	auto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast<uintptr_t>(addr));
  
  	if (!sctpAssociation)
  	{
  		MS_WARN_TAG(sctp, "no SctpAssociation found");
  
  		return -1;
  	}
  
  	sctpAssociation->OnUsrSctpSendSctpData(data, len);
  
  	// NOTE: Must not free data, usrsctp lib does it.
  
  	return 0;
  }
  
  // Static method for printing usrsctp debug.
  inline static void sctpDebug(const char* format, ...)
  {
  	char buffer[10000];
  	va_list ap;
  
  	va_start(ap, format);
  	vsprintf(buffer, format, ap);
  
  	// Remove the artificial carriage return set by usrsctp.
  	buffer[std::strlen(buffer) - 1] = '\0';
  
  	MS_DEBUG_TAG(sctp, "%s", buffer);
  
  	va_end(ap);
  }
  
  /* Static variables. */
  
  thread_local DepUsrSCTP::Checker* DepUsrSCTP::checker{ nullptr };
  uint64_t DepUsrSCTP::numSctpAssociations{ 0u };
  uintptr_t DepUsrSCTP::nextSctpAssociationId{ 0u };
  std::unordered_map<uintptr_t, RTC::SctpAssociation*> DepUsrSCTP::mapIdSctpAssociation;
  
  /* Static methods. */
  
  void DepUsrSCTP::ClassInit()
  {
  	MS_TRACE();
  
  	MS_DEBUG_TAG(info, "usrsctp");
  
  	{
  		std::lock_guard<std::mutex> lock(globalSyncMutex);
  
  		if (globalInstances == 0)
  		{
  			usrsctp_init_nothreads(0, onSendSctpData, sctpDebug);
  
  			// Disable explicit congestion notifications (ecn).
  			usrsctp_sysctl_set_sctp_ecn_enable(0);
  
  #ifdef SCTP_DEBUG
  			usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL);
  #endif
  		}
  
  		++globalInstances;
  	}
  
  	DepUsrSCTP::checker = new DepUsrSCTP::Checker();
  }
  
  void DepUsrSCTP::ClassDestroy()
  {
  	MS_TRACE();
  
  	{
  		std::lock_guard<std::mutex> lock(globalSyncMutex);
  		--globalInstances;
  
  		if (globalInstances == 0)
  		{
  			usrsctp_finish();
  			// TODO: This cleanup currently causes assertion errors in
  			// DepUsrSCTP::DeregisterSctpAssociation()
  
  			// numSctpAssociations = 0u; nextSctpAssociationId = 0u;
  			// DepUsrSCTP::mapIdSctpAssociation.clear();
  		}
  	}
  
  	delete DepUsrSCTP::checker;
  }
  
  uintptr_t DepUsrSCTP::GetNextSctpAssociationId()
  {
  	MS_TRACE();
  
  	std::lock_guard<std::mutex> lock(globalSyncMutex);
  
  	// NOTE: usrsctp_connect() fails with a value of 0.
  	if (DepUsrSCTP::nextSctpAssociationId == 0u)
  		++DepUsrSCTP::nextSctpAssociationId;
  
  	// In case we've wrapped around and need to find an empty spot from a removed
  	// SctpAssociation. Assumes we'll never be full.
  	while (DepUsrSCTP::mapIdSctpAssociation.find(DepUsrSCTP::nextSctpAssociationId) !=
  	       DepUsrSCTP::mapIdSctpAssociation.end())
  	{
  		++DepUsrSCTP::nextSctpAssociationId;
  
  		if (DepUsrSCTP::nextSctpAssociationId == 0u)
  			++DepUsrSCTP::nextSctpAssociationId;
  	}
  
  	return DepUsrSCTP::nextSctpAssociationId++;
  }
  
  void DepUsrSCTP::RegisterSctpAssociation(RTC::SctpAssociation* sctpAssociation)
  {
  	MS_TRACE();
  
  	std::lock_guard<std::mutex> lock(globalSyncMutex);
  
  	auto it = DepUsrSCTP::mapIdSctpAssociation.find(sctpAssociation->id);
  
  	MS_ASSERT(
  	  it == DepUsrSCTP::mapIdSctpAssociation.end(),
  	  "the id of the SctpAssociation is already in the map");
  
  	DepUsrSCTP::mapIdSctpAssociation[sctpAssociation->id] = sctpAssociation;
  
  	if (++DepUsrSCTP::numSctpAssociations == 1u)
  		DepUsrSCTP::checker->Start();
  }
  
  void DepUsrSCTP::DeregisterSctpAssociation(RTC::SctpAssociation* sctpAssociation)
  {
  	MS_TRACE();
  
  	std::lock_guard<std::mutex> lock(globalSyncMutex);
  
  	auto found = DepUsrSCTP::mapIdSctpAssociation.erase(sctpAssociation->id);
  
  	MS_ASSERT(found > 0, "SctpAssociation not found");
  	MS_ASSERT(DepUsrSCTP::numSctpAssociations > 0u, "numSctpAssociations was not higher than 0");
  
  	if (--DepUsrSCTP::numSctpAssociations == 0u)
  		DepUsrSCTP::checker->Stop();
  }
  
  RTC::SctpAssociation* DepUsrSCTP::RetrieveSctpAssociation(uintptr_t id)
  {
  	MS_TRACE();
  
  	std::lock_guard<std::mutex> lock(globalSyncMutex);
  
  	auto it = DepUsrSCTP::mapIdSctpAssociation.find(id);
  
  	if (it == DepUsrSCTP::mapIdSctpAssociation.end())
  		return nullptr;
  
  	return it->second;
  }
  
  /* DepUsrSCTP::Checker instance methods. */
  
  DepUsrSCTP::Checker::Checker()
  {
  	MS_TRACE();
  
  	this->timer = new Timer(this);
  }
  
  DepUsrSCTP::Checker::~Checker()
  {
  	MS_TRACE();
  
  	delete this->timer;
  }
  
  void DepUsrSCTP::Checker::Start()
  {
  	MS_TRACE();
  
  	MS_DEBUG_TAG(sctp, "usrsctp periodic check started");
  
  	this->lastCalledAtMs = 0u;
  
  	this->timer->Start(CheckerInterval, CheckerInterval);
  }
  
  void DepUsrSCTP::Checker::Stop()
  {
  	MS_TRACE();
  
  	MS_DEBUG_TAG(sctp, "usrsctp periodic check stopped");
  
  	this->lastCalledAtMs = 0u;
  
  	this->timer->Stop();
  }
  
  void DepUsrSCTP::Checker::OnTimer(Timer* /*timer*/)
  {
  	MS_TRACE();
  
  	auto nowMs    = DepLibUV::GetTimeMs();
  	int elapsedMs = this->lastCalledAtMs ? static_cast<int>(nowMs - this->lastCalledAtMs) : 0;
  
  	usrsctp_handle_timers(elapsedMs);
  
  	this->lastCalledAtMs = nowMs;
  }
  