Repository: versatica/mediasoup (2025-07-09T15:32:51Z)

Issue:
Worker hits ASSERT condition in production
worker hits assert in

https://github.com/versatica/mediasoup/blob/v3/worker/src/RTC/RateCalculator.cpp#L37

```
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007f8d1f851535 in __GI_abort () at abort.c:79
#2  0x000055814b9c3e7f in RTC::RateCalculator::Update(unsigned long, unsigned long) ()
#3  0x000055814ba754d1 in RTC::WebRtcTransport::SendRtpPacket(RTC::Consumer*, RTC::RtpPacket*, std::function<void (bool)> const*) ()
#4  0x000055814ba528d2 in RTC::Transport::OnConsumerSendRtpPacket(RTC::Consumer*, RTC::RtpPacket*) ()
#5  0x000055814ba116f0 in RTC::SimpleConsumer::SendRtpPacket(RTC::RtpPacket*, std::shared_ptr<RTC::RtpPacket>&) ()
#6  0x000055814b9d38c2 in RTC::Router::OnTransportProducerRtpPacketReceived(RTC::Transport*, RTC::Producer*, RTC::RtpPacket*) ()
#7  0x000055814ba510a0 in RTC::Transport::OnProducerRtpPacketReceived(RTC::Producer*, RTC::RtpPacket*) ()
#8  0x000055814b9bd28e in RTC::Producer::ReceiveRtpPacket(RTC::RtpPacket*) ()
#9  0x000055814ba3d326 in RTC::Transport::ReceiveRtpPacket(RTC::RtpPacket*) ()
#10 0x000055814ba7ee97 in RTC::WebRtcTransport::OnRtpDataReceived(RTC::TransportTuple*, unsigned char const*, unsigned long) ()
#11 0x000055814ba808b3 in RTC::WebRtcTransport::OnPacketReceived(RTC::TransportTuple*, unsigned char const*, unsigned long) ()
#12 0x000055814ba81192 in non-virtual thunk to RTC::WebRtcTransport::OnUdpSocketPacketReceived(RTC::UdpSocket*, unsigned char const*, unsigned long, sockaddr const*) ()
#13 0x000055814ba6a5d1 in RTC::UdpSocket::UserOnUdpDatagramReceived(unsigned char const*, unsigned long, sockaddr const*) ()
#14 0x000055814b9218ce in onRecv(uv_udp_s*, long, uv_buf_t const*, sockaddr const*, unsigned int) ()
#15 0x000055814bde4c5d in uv.udp_io ()
#16 0x000055814bde7fd6 in uv.io_poll ()
#17 0x000055814bddb6ae in uv_run ()
#18 0x000055814b8c410f in DepLibUV::RunLoop() ()
#19 0x000055814b8ed381 in Worker::Worker(Channel::ChannelSocket*, PayloadChannel::PayloadChannelSocket*) ()
#20 0x000055814b8be813 in mediasoup_worker_run ()
#21 0x000055814b8bc709 in main ()
```


Discussion:
@gitamirp,

* Which mediasoup version are you using?
* Can you repro? can you provide an STR?
* Please provide as much context info as you can.
@jmillan thanks for looking into it. 
We are using version 3.12.12. 
Unfortunately, it happens in a production environment therefore its harder to get detailed logs or STR for it. 
It rarely happens but I can try to get some more info around it. 
You should upgrade to latest release first, 3.12.12 is very old and the issue you are hitting might have been long fixed
Yes please, you're using quite an old version. Upgrade it as soon as you can.

Don't hesitate to reopen the issue if it comes out in the future.
Honestly there is no change since 3.12.12 that could have fix this issue. Let me reopen to not forget. I think we need a fuzzer test for this class.
thanks, fwiw there were no significant changes to RateCalculator.cpp over the last 2 years. 

https://github.com/versatica/mediasoup/commits/v3/worker/src/RTC/RateCalculator.cpp

I suspect it might occur when

https://github.com/versatica/mediasoup/blob/v3/worker/src/RTC/RateCalculator.cpp#L27

`nowMs - this->newestItemStartTime >= this->itemSizeMs`

and

https://github.com/versatica/mediasoup/blob/v3/worker/src/RTC/RateCalculator.cpp#L32

`this->newestItemIndex >= this->windowItems`

and 

`this->oldestItemIndex == 0`

probably just logging an error and resting the RateCalculator will be more appropriate error handling
> probably just logging an error and resting the RateCalculator will be more appropriate error handling

Defensive programming hides real bugs. We don't do that. If something should never happen then  it must never happen, otherwise it's a bug that needs to be fixed.
If there was a memory corruption somewhere, side-effects might be unexpected. This needs to be confirmed on latest version first.
> > probably just logging an error and resting the RateCalculator will be more appropriate error handling
> 
> Defensive programming hides real bugs. We don't do that. If something should never happen then it must never happen, otherwise it's a bug that needs to be fixed.

also true
> If there was a memory corruption somewhere, side-effects might be unexpected. This needs to be confirmed on latest version first.

I am not sure how fast we can get these hosts on latest release. for now I will just patch it to suit our needs.

I'll let you know if I find more about this condition.

thanks
@gitamirp any news? I assumed you patched version 3.12.12 so it's not a problem for you anymore but perhaps you updated to latest version (without patching it)?
We patched 3.12.12 by using 

`			MS_ASSERT(
			  this->newestItemIndex != this->oldestItemIndex || this->oldestItemIndex == -1 || this->newestItemIndex,
			  "newest index overlaps with the oldest one");
`

I will let you know if we run into this ASSERT again with this patch.
If you are interested we can add some logs when 

`this->newestItemIndex == this->oldestItemIndex`

I believe that in 3.12.12 both newest && oldest can be zero at the same time but I don't have logs to support it.
Yes please. Add those logs and comment here when you get something. We will investigate this next week. Too busy these days.
Sure, we'll do. thanks
@gitamirp do you have new info about this issue? We have never seen it in our running servers.
no new info, thanks
> no new info, thanks

Does it mean that it didn't crash again?
Correct, we haven't seen this specific crash.
> Correct, we haven't seen this specific crash.

Sorry, I would need more context:

1. Which mediasoup version are you using?
2. Have you applied any patch into it?
3. Since when are you running that mediasoup version (with the patch)?
4. Did you experience crashes in the past using the **same** mediasoup version without the patch?
Sure, no problem.

I originally made this patch against version 3.12.12:

```c++
MS_ASSERT(
  this->newestItemIndex != this->oldestItemIndex || this->oldestItemIndex == -1 || this->newestItemIndex,
  "newest index overlaps with the oldest one");
```

The intention was to avoid asserting when newestItemIndex == 0, so I added the clause || this->newestItemIndex.
Looking at it again, I realize that was a mistake. what I meant to add was:

```c++
|| this->newestItemIndex == 0
```

As it stands, the assertion will only trigger if both newestItemIndex and oldestItemIndex are zero.

This patch has been running in production for over a year and a half without issues. Prior to applying it, we did observe this crash on the same version.

That said, we don’t have logs going back far enough to confirm whether the problematic condition has occurred at any point during the past year and a half.

Let me know if you like to get more information
So potentially the issue may have happened many times, it's just that you are not checking the associated logs and your patch avoids the crash.
Notes for myself:

- mediasoup 3.12.12 was released in Sep 7, 2023.
- Here the diff with latest 3.16.5 release: https://github.com/versatica/mediasoup/compare/3.12.12...3.16.5 
- In summary there are 0 changes in `RateCalculator` class since 3.12.12.
Unrelated but I don't like this:

```c++
void RateCalculator::Update(size_t size, uint64_t nowMs)
{
	MS_TRACE();

	// Ignore too old data. Should never happen.
	if (nowMs < this->oldestItemStartTime)
	{
		return;
	}
```

There is a chance that `nowMs` overlaps maximum value of `int64_t` and becomes 0. We should compare the same way we do in `SeqManager`.

Same when we do this:

```c++
const uint64_t newOldestTime = nowMs - this->windowSizeMs;

		// Oldest item already removed.
		if (newOldestTime < this->oldestItemStartTime)
		{
			return;
		}

		// A whole window size time has elapsed since last entry. Reset the buffer.
		if (newOldestTime >= this->newestItemStartTime)
		{
			Reset();

			return;
		}

		while (newOldestTime >= this->oldestItemStartTime)
```

In fact, this little change in `TestRateCalculator.cpp` makes all tests fail:

```diff
+++ b/worker/test/src/RTC/TestRateCalculator.cpp
@@ -27,6 +27,9 @@ SCENARIO("Bitrate calculator", "[rtp][bitrate]")
 {
 	uint64_t nowMs = DepLibUV::GetTimeMs();
 
+	// TODO: TMP
+	nowMs = std::numeric_limits<uint64_t>::max() - 10;
+
 	SECTION("receive single item per 1000 ms")
 	{
 		RateCalculator rate;
```

NOTE: I'm not getting any crash with that change.
Another thing: I see that `this->buffer` is only filled when `Reset()` is called, which literally fills all bytes of `this->buffer` with zeroes. However that's not done unless `Reset()` is called, which only happens in a specific condition within the `RemoveOldData()` method.
So we have a fix: https://github.com/versatica/mediasoup/pull/1570. Will release a new version tomorrow.
This is fixed and released in mediasoup 3.16.6



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28a9a66e05..ab6654b968 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 - Remove H265 codec and deprecated frame-marking RTP extension ([PR #1564](https://github.com/versatica/mediasoup/pull/1564)).
 - `SimulcastConsumer`: Fix selecting spatial layer higher than preferred one ([PR #1565](https://github.com/versatica/mediasoup/pull/1565)).
 - Remove H264-SVC codec ([PR #1568](https://github.com/versatica/mediasoup/pull/1568)).
+- `RateCalculator`: Fix crash due to buffer overflow and avoid time overflow ([PR #1570](https://github.com/versatica/mediasoup/pull/1570)).
 
 ### 3.16.5
 
diff --git a/worker/include/RTC/RateCalculator.hpp b/worker/include/RTC/RateCalculator.hpp
index ea892bd033..f70fa41d85 100644
--- a/worker/include/RTC/RateCalculator.hpp
+++ b/worker/include/RTC/RateCalculator.hpp
@@ -4,6 +4,7 @@
 #include "common.hpp"
 #include "DepLibUV.hpp"
 #include "RTC/RtpPacket.hpp"
+#include <optional>
 #include <vector>
 
 namespace RTC
@@ -21,12 +22,7 @@ namespace RTC
 		explicit RateCalculator(
 		  size_t windowSizeMs  = DefaultWindowSize,
 		  float scale          = DefaultBpsScale,
-		  uint16_t windowItems = DefaultWindowItems)
-		  : windowSizeMs(windowSizeMs), scale(scale), windowItems(windowItems)
-		{
-			this->itemSizeMs = std::max(windowSizeMs / windowItems, size_t{ 1 });
-			this->buffer.resize(windowItems);
-		}
+		  uint16_t windowItems = DefaultWindowItems);
 
 		void Update(size_t size, uint64_t nowMs);
 
@@ -37,11 +33,11 @@ namespace RTC
 			return this->bytes;
 		}
 
+		void Reset();
+
 	private:
 		void RemoveOldData(uint64_t nowMs);
 
-		void Reset();
-
 	private:
 		struct BufferItem
 		{
@@ -61,11 +57,11 @@ namespace RTC
 		// Buffer to keep data.
 		std::vector<BufferItem> buffer;
 		// Time (in milliseconds) for last item in the time window.
-		uint64_t newestItemStartTime{ 0u };
+		std::optional<uint64_t> newestItemStartTime{ std::nullopt };
 		// Index for the last item in the time window.
 		int32_t newestItemIndex{ -1 };
 		// Time (in milliseconds) for oldest item in the time window.
-		uint64_t oldestItemStartTime{ 0u };
+		std::optional<uint64_t> oldestItemStartTime{ std::nullopt };
 		// Index for the oldest item in the time window.
 		int32_t oldestItemIndex{ -1 };
 		// Total count in the time window.
@@ -75,7 +71,7 @@ namespace RTC
 		// Last value calculated by GetRate().
 		uint32_t lastRate{ 0u };
 		// Last time GetRate() was called.
-		uint64_t lastTime{ 0u };
+		std::optional<uint64_t> lastTime{ std::nullopt };
 	};
 
 	class RtpDataCounter
diff --git a/worker/include/Utils.hpp b/worker/include/Utils.hpp
index 078dbbba33..92d1154a64 100644
--- a/worker/include/Utils.hpp
+++ b/worker/include/Utils.hpp
@@ -5,6 +5,7 @@
 #include <openssl/evp.h>
 #include <cmath>
 #include <cstring> // std::memcmp(), std::memcpy()
+#include <limits>  // std::numeric_limits
 #include <string>
 #include <type_traits> // std::enable_if()
 #include <vector>
@@ -300,6 +301,8 @@ namespace Utils
 		static constexpr uint32_t UnixNtpOffset{ 0x83AA7E80 };
 		// NTP fractional unit.
 		static constexpr uint64_t NtpFractionalUnit{ 1LL << 32 };
+		// Max time value (for uint64_t values obtained via DepLibUV).
+		static constexpr uint64_t TimeHighestValue = std::numeric_limits<uint64_t>::max();
 
 	public:
 		struct Ntp
@@ -353,6 +356,40 @@ namespace Utils
 		{
 			return static_cast<uint32_t>(((ms << 18) + 500) / 1000) & 0x00FFFFFF;
 		}
+
+		static bool IsTimeLowerThan(uint64_t msA, uint64_t msB)
+		{
+			// clang-format off
+			return ((msB > msA) && (msB - msA <= TimeHighestValue / 2)) ||
+			       ((msA > msB) && (msA - msB > TimeHighestValue / 2));
+			// clang-format on
+		}
+
+		static bool IsTimeLowerOrEqualThan(uint64_t msA, uint64_t msB)
+		{
+			// clang-format off
+			return (msA == msB) ||
+			       ((msB > msA) && (msB - msA <= TimeHighestValue / 2)) ||
+			       ((msA > msB) && (msA - msB > TimeHighestValue / 2));
+			// clang-format on
+		}
+
+		static bool IsTimeHigherThan(uint64_t msA, uint64_t msB)
+		{
+			// clang-format off
+			return ((msA > msB) && (msA - msB <= TimeHighestValue / 2)) ||
+			       ((msB > msA) && (msB - msA > TimeHighestValue / 2));
+			// clang-format on
+		}
+
+		static bool IsTimeHigherOrEqualThan(uint64_t msA, uint64_t msB)
+		{
+			// clang-format off
+			return (msA == msB) ||
+			       ((msA > msB) && (msA - msB <= TimeHighestValue / 2)) ||
+			       ((msB > msA) && (msB - msA > TimeHighestValue / 2));
+			// clang-format on
+		}
 	};
 
 	class BitStream
diff --git a/worker/src/RTC/RateCalculator.cpp b/worker/src/RTC/RateCalculator.cpp
index 830568946b..030c317047 100644
--- a/worker/src/RTC/RateCalculator.cpp
+++ b/worker/src/RTC/RateCalculator.cpp
@@ -3,17 +3,32 @@
 
 #include "RTC/RateCalculator.hpp"
 #include "Logger.hpp"
+#include "Utils.hpp"
 #include <cmath>   // std::trunc()
 #include <cstring> // std::memset()
 
 namespace RTC
 {
+	RateCalculator::RateCalculator(size_t windowSizeMs, float scale, uint16_t windowItems)
+	  : windowSizeMs(windowSizeMs), scale(scale), windowItems(windowItems),
+	    itemSizeMs(std::max(windowSizeMs / windowItems, size_t{ 1 }))
+	{
+		MS_TRACE();
+
+		this->buffer.resize(windowItems);
+
+		std::memset(
+		  static_cast<void*>(std::addressof(this->buffer.front())),
+		  0,
+		  sizeof(BufferItem) * this->buffer.size());
+	}
+
 	void RateCalculator::Update(size_t size, uint64_t nowMs)
 	{
 		MS_TRACE();
 
 		// Ignore too old data. Should never happen.
-		if (nowMs < this->oldestItemStartTime)
+		if (this->oldestItemStartTime.has_value() && Utils::Time::IsTimeLowerThan(nowMs, *this->oldestItemStartTime))
 		{
 			MS_WARN_DEV("nowMs < this->oldestItemStartTime, should never happen");
 
@@ -27,7 +42,9 @@ namespace RTC
 
 		// If the elapsed time from the newest item start time is greater than the
 		// item size (in milliseconds), increase the item index.
-		if (this->newestItemIndex < 0 || nowMs - this->newestItemStartTime >= this->itemSizeMs)
+		if (
+		  this->newestItemIndex < 0 || !this->newestItemStartTime.has_value() ||
+		  Utils::Time::IsTimeHigherOrEqualThan(nowMs - *this->newestItemStartTime, this->itemSizeMs))
 		{
 			this->newestItemIndex++;
 			this->newestItemStartTime = nowMs;
@@ -39,6 +56,17 @@ namespace RTC
 				this->newestItemIndex = 0;
 			}
 
+			// Advance oldestItemIndex if buffer is full.
+			// NOTE: This avoids a crash:
+			//   https://github.com/versatica/mediasoup/issues/1316
+			if (this->newestItemIndex == this->oldestItemIndex && this->oldestItemIndex != -1)
+			{
+				if (++this->oldestItemIndex >= this->windowItems)
+				{
+					this->oldestItemIndex = 0;
+				}
+			}
+
 			MS_ASSERT(
 			  this->newestItemIndex != this->oldestItemIndex || this->oldestItemIndex == -1,
 			  "newest index overlaps with the oldest one [newestItemIndex:%" PRId32
@@ -73,14 +101,14 @@ namespace RTC
 		// Reset lastRate and lastTime so GetRate() will calculate rate again even
 		// if called with same now in the same loop iteration.
 		this->lastRate = 0;
-		this->lastTime = 0;
+		this->lastTime = std::nullopt;
 	}
 
 	uint32_t RateCalculator::GetRate(uint64_t nowMs)
 	{
 		MS_TRACE();
 
-		if (nowMs == this->lastTime)
+		if (this->lastTime.has_value() && nowMs == *this->lastTime)
 		{
 			MS_DEBUG_DEV("nowMs == this->lastTime, early return");
 
@@ -97,10 +125,33 @@ namespace RTC
 		return this->lastRate;
 	}
 
+	void RateCalculator::Reset()
+	{
+		MS_TRACE();
+
+		std::memset(
+		  static_cast<void*>(std::addressof(this->buffer.front())),
+		  0,
+		  sizeof(BufferItem) * this->buffer.size());
+
+		this->newestItemStartTime = std::nullopt;
+		this->newestItemIndex     = -1;
+		this->oldestItemStartTime = std::nullopt;
+		this->oldestItemIndex     = -1;
+		this->totalCount          = 0u;
+		this->lastRate            = 0u;
+		this->lastTime            = std::nullopt;
+	}
+
 	void RateCalculator::RemoveOldData(uint64_t nowMs)
 	{
 		MS_TRACE();
 
+		if (!this->oldestItemStartTime.has_value())
+		{
+			return;
+		}
+
 		// No item set.
 		if (this->newestItemIndex < 0 || this->oldestItemIndex < 0)
 		{
@@ -110,13 +161,15 @@ namespace RTC
 		const uint64_t newOldestTime = nowMs - this->windowSizeMs;
 
 		// Oldest item already removed.
-		if (newOldestTime < this->oldestItemStartTime)
+		if (Utils::Time::IsTimeLowerThan(newOldestTime, *this->oldestItemStartTime))
 		{
 			return;
 		}
 
 		// A whole window size time has elapsed since last entry. Reset the buffer.
-		if (newOldestTime >= this->newestItemStartTime)
+		if (
+		  this->newestItemStartTime.has_value() &&
+		  Utils::Time::IsTimeHigherOrEqualThan(newOldestTime, *this->newestItemStartTime))
 		{
 			MS_DEBUG_DEV("newOldestTime >= this->newestItemStartTime, resetting the buffer");
 
@@ -125,7 +178,7 @@ namespace RTC
 			return;
 		}
 
-		while (newOldestTime >= this->oldestItemStartTime)
+		while (Utils::Time::IsTimeHigherOrEqualThan(newOldestTime, *this->oldestItemStartTime))
 		{
 			BufferItem& oldestItem = this->buffer[this->oldestItemIndex];
 			this->totalCount -= oldestItem.count;
@@ -142,24 +195,6 @@ namespace RTC
 		}
 	}
 
-	void RateCalculator::Reset()
-	{
-		MS_TRACE();
-
-		std::memset(
-		  static_cast<void*>(std::addressof(this->buffer.front())),
-		  0,
-		  sizeof(BufferItem) * this->buffer.size());
-
-		this->newestItemStartTime = 0u;
-		this->newestItemIndex     = -1;
-		this->oldestItemStartTime = 0u;
-		this->oldestItemIndex     = -1;
-		this->totalCount          = 0u;
-		this->lastRate            = 0u;
-		this->lastTime            = 0u;
-	}
-
 	void RtpDataCounter::Update(RTC::RtpPacket* packet)
 	{
 		const uint64_t nowMs = DepLibUV::GetTimeMs();
diff --git a/worker/src/RTC/SeqManager.cpp b/worker/src/RTC/SeqManager.cpp
index 119f46148a..3796550194 100644
--- a/worker/src/RTC/SeqManager.cpp
+++ b/worker/src/RTC/SeqManager.cpp
@@ -10,15 +10,15 @@ namespace RTC
 	template<typename T, uint8_t N>
 	bool SeqManager<T, N>::SeqLowerThan::operator()(T lhs, T rhs) const
 	{
-		return ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) ||
-		       ((lhs > rhs) && (lhs - rhs > MaxValue / 2));
+		return ((rhs > lhs) && (rhs - lhs <= SeqManager::MaxValue / 2)) ||
+		       ((lhs > rhs) && (lhs - rhs > SeqManager::MaxValue / 2));
 	}
 
 	template<typename T, uint8_t N>
 	bool SeqManager<T, N>::SeqHigherThan::operator()(T lhs, T rhs) const
 	{
-		return ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) ||
-		       ((rhs > lhs) && (rhs - lhs > MaxValue / 2));
+		return ((lhs > rhs) && (lhs - rhs <= SeqManager::MaxValue / 2)) ||
+		       ((rhs > lhs) && (rhs - lhs > SeqManager::MaxValue / 2));
 	}
 
 	template<typename T, uint8_t N>
@@ -30,13 +30,13 @@ namespace RTC
 	template<typename T, uint8_t N>
 	bool SeqManager<T, N>::IsSeqLowerThan(T lhs, T rhs)
 	{
-		return isSeqLowerThan(lhs, rhs);
+		return SeqManager::isSeqLowerThan(lhs, rhs);
 	}
 
 	template<typename T, uint8_t N>
 	bool SeqManager<T, N>::IsSeqHigherThan(T lhs, T rhs)
 	{
-		return isSeqHigherThan(lhs, rhs);
+		return SeqManager::isSeqHigherThan(lhs, rhs);
 	}
 
 	template<typename T, uint8_t N>
@@ -51,7 +51,7 @@ namespace RTC
 		MS_TRACE();
 
 		// Update base.
-		this->base = (this->maxOutput - input) & MaxValue;
+		this->base = (this->maxOutput - input) & SeqManager::MaxValue;
 
 		// Update maxInput.
 		this->maxInput = input;
@@ -125,11 +125,11 @@ namespace RTC
 			auto it = this->dropped.lower_bound(input);
 
 			droppedCount -= std::distance(it, this->dropped.end());
-			base = (this->base - droppedCount) & MaxValue;
+			base = (this->base - droppedCount) & SeqManager::MaxValue;
 		}
 
 	done:
-		output = (input + base) & MaxValue;
+		output = (input + base) & SeqManager::MaxValue;
 
 		if (!this->started)
 		{
@@ -152,7 +152,7 @@ namespace RTC
 			}
 		}
 
-		output = (output + this->initialOutput) & MaxValue;
+		output = (output + this->initialOutput) & SeqManager::MaxValue;
 
 		return true;
 	}
@@ -201,7 +201,7 @@ namespace RTC
 		}
 
 		// Adapt base.
-		this->base = (this->base - (previousDroppedSize - this->dropped.size())) & MaxValue;
+		this->base = (this->base - (previousDroppedSize - this->dropped.size())) & SeqManager::MaxValue;
 	}
 
 	// Explicit instantiation to have all SeqManager definitions in this file.


Test patch:
diff --git a/worker/test/src/RTC/TestRateCalculator.cpp b/worker/test/src/RTC/TestRateCalculator.cpp
index fb3c0d498d..be5a28d54d 100644
--- a/worker/test/src/RTC/TestRateCalculator.cpp
+++ b/worker/test/src/RTC/TestRateCalculator.cpp
@@ -2,6 +2,7 @@
 #include "DepLibUV.hpp"
 #include "RTC/RateCalculator.hpp"
 #include <catch2/catch_test_macros.hpp>
+#include <limits> // std::numeric_limits()
 #include <vector>
 
 using namespace RTC;
@@ -21,9 +22,29 @@ void validate(RateCalculator& rate, uint64_t timeBase, std::vector<TestRateCalcu
 
 		REQUIRE(rate.GetRate(timeBase + item.offset) == item.rate);
 	}
+
+	// Repeat forcing nowMs to be 0.
+	rate.Reset();
+
+	for (auto& item : input)
+	{
+		rate.Update(item.size, timeBase + item.offset);
+
+		REQUIRE(rate.GetRate(0 + item.offset) == item.rate);
+	}
+
+	// Repeat forcing nowMs to be std::numeric_limits<uint64_t>::max() - 100.
+	rate.Reset();
+
+	for (auto& item : input)
+	{
+		rate.Update(item.size, timeBase + item.offset);
+
+		REQUIRE(rate.GetRate(std::numeric_limits<uint64_t>::max() - 100 + item.offset) == item.rate);
+	}
 }
 
-SCENARIO("Bitrate calculator", "[rtp][bitrate]")
+SCENARIO("Rate calculator", "[rtp][RateCalculator]")
 {
 	uint64_t nowMs = DepLibUV::GetTimeMs();
 
@@ -139,4 +160,24 @@ SCENARIO("Bitrate calculator", "[rtp][bitrate]")
 
 		validate(rate, nowMs, input);
 	}
+
+	// NOTE: This test reproduces a crash (now fixed):
+	//   https://github.com/versatica/mediasoup/issues/1316
+	SECTION("buffer overflow should not crash")
+	{
+		// window: 1000ms, items: 3 (granularity: 333ms)
+		RateCalculator rate(1000, 8000, 3);
+
+		// clang-format off
+		std::vector<TestRateCalculatorData> input =
+		{
+			{ 0,   1, 8  },
+			{ 333, 1, 16  },
+			{ 666, 1, 24  },
+			{ 999, 1, 32 },
+  	};
+		// clang-format on
+
+		validate(rate, nowMs, input);
+	}
 }
diff --git a/worker/test/src/Utils/TestTime.cpp b/worker/test/src/Utils/TestTime.cpp
index f286659ebf..9a2c32ca7f 100644
--- a/worker/test/src/Utils/TestTime.cpp
+++ b/worker/test/src/Utils/TestTime.cpp
@@ -5,14 +5,68 @@
 
 using namespace Utils;
 
-SCENARIO("Utils::Time::TimeMs2Ntp() and Utils::Time::Ntp2TimeMs()", "[utils][time]")
+SCENARIO("Utils::Time", "[utils][time]")
 {
-	auto nowMs  = DepLibUV::GetTimeMs();
-	auto ntp    = Time::TimeMs2Ntp(nowMs);
-	auto nowMs2 = Time::Ntp2TimeMs(ntp);
-	auto ntp2   = Time::TimeMs2Ntp(nowMs2);
-
-	REQUIRE(nowMs2 == nowMs);
-	REQUIRE(ntp2.seconds == ntp.seconds);
-	REQUIRE(ntp2.fractions == ntp.fractions);
+	SECTION("Utils::Time::Ntp2TimeMs()")
+	{
+		auto nowMs  = DepLibUV::GetTimeMs();
+		auto ntp    = Time::TimeMs2Ntp(nowMs);
+		auto nowMs2 = Time::Ntp2TimeMs(ntp);
+		auto ntp2   = Time::TimeMs2Ntp(nowMs2);
+
+		REQUIRE(nowMs2 == nowMs);
+		REQUIRE(ntp2.seconds == ntp.seconds);
+		REQUIRE(ntp2.fractions == ntp.fractions);
+	}
+
+	SECTION("Utils::Time::IsTimeLowerThan()")
+	{
+		// 1000000 is lower than 2000000.
+		REQUIRE(Utils::Time::IsTimeLowerThan(1000000, 2000000) == true);
+
+		// std::numeric_limits<uint64_t>::max() is lower than 0.
+		REQUIRE(Utils::Time::IsTimeLowerThan(std::numeric_limits<uint64_t>::max(), 0) == true);
+
+		// (std::numeric_limits<uint64_t>::max() / 2) + 1 is lower than
+		// std::numeric_limits<uint64_t>::max().
+		REQUIRE(
+		  Utils::Time::IsTimeLowerThan(
+		    (std::numeric_limits<uint64_t>::max() / 2) + 1, std::numeric_limits<uint64_t>::max()) == true);
+
+		// std::numeric_limits<uint64_t>::max() is lower than
+		// (std::numeric_limits<uint64_t>::max() / 2) - 1.
+		REQUIRE(
+		  Utils::Time::IsTimeLowerThan(
+		    std::numeric_limits<uint64_t>::max(), (std::numeric_limits<uint64_t>::max() / 2) - 1) == true);
+	}
+
+	SECTION("Utils::Time::IsTimeHigherThan()")
+	{
+		// 0 is greater than std::numeric_limits<uint64_t>::max().
+		REQUIRE(Utils::Time::IsTimeHigherThan(0, std::numeric_limits<uint64_t>::max()) == true);
+
+		// std::numeric_limits<uint64_t>::max() / 2) - 1 is higher than
+		// std::numeric_limits<uint64_t>::max().
+		REQUIRE(
+		  Utils::Time::IsTimeHigherThan(
+		    (std::numeric_limits<uint64_t>::max() / 2) - 1, std::numeric_limits<uint64_t>::max()) == true);
+
+		// std::numeric_limits<uint64_t>::max() is higher than
+		// (std::numeric_limits<uint64_t>::max() / 2) + 1.
+		REQUIRE(
+		  Utils::Time::IsTimeHigherThan(
+		    std::numeric_limits<uint64_t>::max(), (std::numeric_limits<uint64_t>::max() / 2) + 1) == true);
+	}
+
+	SECTION("Utils::Time::IsTimeHigherOrEqualThan()")
+	{
+		// 0 is greater or equal than std::numeric_limits<uint64_t>::max().
+		REQUIRE(Utils::Time::IsTimeHigherOrEqualThan(0, std::numeric_limits<uint64_t>::max()) == true);
+	}
+
+	SECTION("Utils::Time::IsTimeLowerOrEqualThan()")
+	{
+		// std::numeric_limits<uint64_t>::max() is lower or equal than 0.
+		REQUIRE(Utils::Time::IsTimeLowerOrEqualThan(std::numeric_limits<uint64_t>::max(), 0) == true);
+	}
 }