Squashed 'external/toxcore/c-toxcore/' changes from c9cdae001..9ed2fa80d

9ed2fa80d fix(toxav): remove extra copy of video frame on encode
de30cf3ad docs: Add new file kinds, that should be useful to all clients.
d5b5e879d fix(DHT): Correct node skipping logic timed out nodes.
30e71fe97 refactor: Generate event dispatch functions and add tox_events_dispatch.
8fdbb0b50 style: Format parameter lists in event handlers.
d00dee12b refactor: Add warning logs when losing chat invites.
b144e8db1 feat: Add a way to look up a file number by ID.
849281ea0 feat: Add a way to fetch groups by chat ID.
a2c177396 refactor: Harden event system and improve type safety.
8f5caa656 refactor: Add MessagePack string support to bin_pack.
34e8d5ad5 chore: Add GitHub CodeQL workflow and local Docker runner.
f7b068010 refactor: Add nullability annotations to event headers.
788abe651 refactor(toxav): Use system allocator for mutexes.
2e4b423eb refactor: Use specific typedefs for public API arrays.
2baf34775 docs(toxav): update idle iteration interval see 679444751876fa3882a717772918ebdc8f083354
2f87ac67b feat: Add Event Loop abstraction (Ev).
f8dfc38d8 test: Fix data race in ToxScenario virtual_clock.
38313921e test(TCP): Add regression test for TCP priority queue integrity.
f94a50d9a refactor(toxav): Replace mutable_mutex with dynamically allocated mutex.
ad054511e refactor: Internalize DHT structs and add debug helpers.
8b467cc96 fix: Prevent potential integer overflow in group chat handshake.
4962bdbb8 test: Improve TCP simulation and add tests
5f0227093 refactor: Allow nullable data in group chat handlers.
e97b18ea9 chore: Improve Windows Docker support.
b14943bbd refactor: Move Logger out of Messenger into Tox.
dd3136250 cleanup: Apply nullability qualifiers to C++ codebase.
1849f70fc refactor: Extract low-level networking code to net and os_network.
8fec75421 refactor: Delete tox_random, align on rng and os_random.
a03ae8051 refactor: Delete tox_memory, align on mem and os_memory.
4c88fed2c refactor: Use `std::` prefixes more consistently in C++ code.
72452f2ae test: Add some more tests for onion and shared key cache.
d5a51b09a cleanup: Use tox_attributes.h in tox_private.h and install it.
b6f5b9fc5 test: Add some benchmarks for various high level things.
8a8d02785 test(support): Introduce threaded Tox runner and simulation barrier
d68d1d095 perf(toxav): optimize audio and video intermediate buffers by keeping them around
REVERT: c9cdae001 fix(toxav): remove extra copy of video frame on encode

git-subtree-dir: external/toxcore/c-toxcore
git-subtree-split: 9ed2fa80d582c714d6bdde6a7648220a92cddff8
This commit is contained in:
Green Sky
2026-02-01 14:26:52 +01:00
parent 565efa4f39
commit 9b36dd9d99
274 changed files with 11891 additions and 4292 deletions

View File

@@ -17,7 +17,10 @@ cc_library(
name = "ring_buffer",
srcs = ["ring_buffer.c"],
hdrs = ["ring_buffer.h"],
deps = ["//c-toxcore/toxcore:ccompat"],
deps = [
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:ccompat",
],
)
cc_test(
@@ -26,6 +29,7 @@ cc_test(
srcs = ["ring_buffer_test.cc"],
deps = [
":ring_buffer",
"//c-toxcore/toxcore:attributes",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
@@ -63,6 +67,7 @@ cc_test(
srcs = ["rtp_test.cc"],
deps = [
":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:net_crypto",
@@ -80,6 +85,7 @@ cc_fuzz_test(
deps = [
":rtp",
"//c-toxcore/testing/support",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory",
@@ -106,8 +112,10 @@ cc_test(
srcs = ["bwcontroller_test.cc"],
deps = [
":bwcontroller",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
@@ -139,6 +147,7 @@ cc_library(
":audio",
":rtp",
":video",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory",
@@ -156,6 +165,7 @@ cc_test(
":rtp",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
@@ -187,6 +197,7 @@ cc_test(
":video",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
@@ -201,6 +212,7 @@ cc_binary(
":av_test_support",
":rtp",
":video",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory",
@@ -216,8 +228,10 @@ cc_binary(
":audio",
":av_test_support",
":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:network",
"//c-toxcore/toxcore:os_memory",
"@benchmark",
],
@@ -230,6 +244,7 @@ cc_binary(
deps = [
":av_test_support",
":rtp",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:mono_time",
"//c-toxcore/toxcore:os_memory",
@@ -255,6 +270,8 @@ cc_test(
srcs = ["msi_test.cc"],
deps = [
":msi",
"//c-toxcore/toxcore:attributes",
"//c-toxcore/toxcore:logger",
"//c-toxcore/toxcore:os_memory",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",

View File

@@ -41,6 +41,8 @@ struct ACSession {
pthread_mutex_t queue_mutex[1];
int16_t *decode_buffer;
uint32_t friend_number;
/* Audio frame receive callback */
ac_audio_receive_frame_cb *acb;
@@ -96,6 +98,13 @@ ACSession *ac_new(Mono_Time *mono_time, const Logger *log, uint32_t friend_numbe
ac->mono_time = mono_time;
ac->log = log;
ac->decode_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t));
if (ac->decode_buffer == nullptr) {
LOGGER_ERROR(log, "Failed to allocate memory for audio buffer");
goto DECODER_CLEANUP;
}
/* Initialize encoders with default values */
ac->encoder = create_audio_encoder(log, AUDIO_START_BITRATE, AUDIO_START_SAMPLE_RATE, AUDIO_START_CHANNEL_COUNT);
@@ -124,6 +133,7 @@ ACSession *ac_new(Mono_Time *mono_time, const Logger *log, uint32_t friend_numbe
return ac;
DECODER_CLEANUP:
free(ac->decode_buffer);
opus_decoder_destroy(ac->decoder);
jbuf_free((struct JitterBuffer *)ac->j_buf);
BASE_CLEANUP:
@@ -141,6 +151,7 @@ void ac_kill(ACSession *ac)
opus_encoder_destroy(ac->encoder);
opus_decoder_destroy(ac->decoder);
jbuf_free((struct JitterBuffer *)ac->j_buf);
free(ac->decode_buffer);
pthread_mutex_destroy(ac->queue_mutex);
@@ -156,20 +167,12 @@ void ac_iterate(ACSession *ac)
/* TODO: fix this and jitter buffering */
/* Enough space for the maximum frame size (120 ms 48 KHz stereo audio) */
int16_t *temp_audio_buffer = (int16_t *)malloc(AUDIO_MAX_BUFFER_SIZE_PCM16 * AUDIO_MAX_CHANNEL_COUNT * sizeof(int16_t));
if (temp_audio_buffer == nullptr) {
LOGGER_ERROR(ac->log, "Failed to allocate memory for audio buffer");
return;
}
int rc = 0;
pthread_mutex_lock(ac->queue_mutex);
struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf;
while (true) {
struct JitterBuffer *const j_buf = (struct JitterBuffer *)ac->j_buf;
struct RTPMessage *msg = jbuf_read(j_buf, &rc);
if (msg == nullptr && rc != 2) {
@@ -190,7 +193,7 @@ void ac_iterate(ACSession *ac)
LOGGER_WARNING(ac->log, "Invalid PLC parameters: sr %u, dur %u", sampling_rate, frame_duration);
} else {
const int fs = (sampling_rate * frame_duration) / 1000;
rc = opus_decode(ac->decoder, nullptr, 0, temp_audio_buffer, fs, 1);
rc = opus_decode(ac->decoder, nullptr, 0, ac->decode_buffer, fs, 1);
}
} else {
const uint8_t *msg_data = rtp_message_data(msg);
@@ -240,7 +243,7 @@ void ac_iterate(ACSession *ac)
* max_size is the max duration of the frame in samples (per channel) that can fit
* into the decoded_frame array
*/
rc = opus_decode(ac->decoder, msg_data + 4, msg_length - 4, temp_audio_buffer, AUDIO_MAX_BUFFER_SIZE_PCM16, 0);
rc = opus_decode(ac->decoder, msg_data + 4, msg_length - 4, ac->decode_buffer, AUDIO_MAX_BUFFER_SIZE_PCM16, 0);
free(msg);
}
@@ -249,7 +252,7 @@ void ac_iterate(ACSession *ac)
} else if (ac->acb != nullptr && ac->lp_sampling_rate != 0) {
ac->lp_frame_duration = (rc * 1000) / ac->lp_sampling_rate;
ac->acb(ac->friend_number, temp_audio_buffer, (size_t)rc, ac->lp_channel_count,
ac->acb(ac->friend_number, ac->decode_buffer, (size_t)rc, ac->lp_channel_count,
ac->lp_sampling_rate, ac->user_data);
}
@@ -257,8 +260,6 @@ void ac_iterate(ACSession *ac)
}
pthread_mutex_unlock(ac->queue_mutex);
free(temp_audio_buffer);
}
int ac_queue_message(const Mono_Time *mono_time, void *cs, struct RTPMessage *msg)

View File

@@ -5,9 +5,12 @@
#include <benchmark/benchmark.h>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/network.h"
@@ -22,15 +25,15 @@ class AudioBench : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State &state) override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
log = logger_new(mem);
tm.t = 1000;
mono_time = mono_time_new(mem, mock_time_cb, &tm);
ac = ac_new(mono_time, log, 123, nullptr, nullptr);
sampling_rate = static_cast<uint32_t>(state.range(0));
channels = static_cast<uint8_t>(state.range(1));
uint32_t bitrate = (channels == 1) ? 32000 : 64000;
sampling_rate = static_cast<std::uint32_t>(state.range(0));
channels = static_cast<std::uint8_t>(state.range(1));
std::uint32_t bitrate = (channels == 1) ? 32000 : 64000;
ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels);
@@ -61,24 +64,24 @@ public:
}
}
Logger *log = nullptr;
Mono_Time *mono_time = nullptr;
Logger *_Nullable log = nullptr;
Mono_Time *_Nullable mono_time = nullptr;
MockTime tm;
ACSession *ac = nullptr;
ACSession *_Nullable ac = nullptr;
RtpMock rtp_mock;
uint32_t sampling_rate = 0;
uint8_t channels = 0;
size_t sample_count = 0;
std::vector<int16_t> pcm;
std::uint32_t sampling_rate = 0;
std::uint8_t channels = 0;
std::size_t sample_count = 0;
std::vector<std::int16_t> pcm;
};
// Benchmark encoding a sequence of silent audio frames.
BENCHMARK_DEFINE_F(AudioBench, EncodeSilentSequence)(benchmark::State &state)
{
std::vector<int16_t> silent_pcm(sample_count * channels);
std::vector<std::int16_t> silent_pcm(sample_count * channels);
fill_silent_frame(channels, sample_count, silent_pcm);
std::vector<uint8_t> encoded(2000);
std::vector<std::uint8_t> encoded(2000);
for (auto _ : state) {
int encoded_size
@@ -97,13 +100,13 @@ BENCHMARK_DEFINE_F(AudioBench, EncodeSequence)(benchmark::State &state)
{
int frame_index = 0;
const int num_prefilled = 50;
std::vector<std::vector<int16_t>> pcms(
num_prefilled, std::vector<int16_t>(sample_count * channels));
std::vector<std::vector<std::int16_t>> pcms(
num_prefilled, std::vector<std::int16_t>(sample_count * channels));
for (int i = 0; i < num_prefilled; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]);
}
std::vector<uint8_t> encoded(2000);
std::vector<std::uint8_t> encoded(2000);
for (auto _ : state) {
int idx = frame_index % num_prefilled;
@@ -125,16 +128,16 @@ BENCHMARK_REGISTER_F(AudioBench, EncodeSequence)
BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state)
{
const int num_frames = 50;
std::vector<std::vector<uint8_t>> encoded_frames(num_frames);
std::vector<std::vector<std::uint8_t>> encoded_frames(num_frames);
// Pre-encode
std::vector<uint8_t> encoded_tmp(2000);
std::vector<std::uint8_t> encoded_tmp(2000);
for (int i = 0; i < num_frames; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcm);
int size = ac_encode(ac, pcm.data(), sample_count, encoded_tmp.data(), encoded_tmp.size());
encoded_frames[i].resize(4 + size);
uint32_t net_sr = net_htonl(sampling_rate);
std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(encoded_frames[i].data(), &net_sr, 4);
std::memcpy(encoded_frames[i].data() + 4, encoded_tmp.data(), size);
}
@@ -143,7 +146,7 @@ BENCHMARK_DEFINE_F(AudioBench, DecodeSequence)(benchmark::State &state)
for (auto _ : state) {
int idx = frame_index % num_frames;
rtp_send_data(log, rtp_mock.recv_session, encoded_frames[idx].data(),
static_cast<uint32_t>(encoded_frames[idx].size()), false);
static_cast<std::uint32_t>(encoded_frames[idx].size()), false);
ac_iterate(ac);
frame_index++;
}
@@ -161,26 +164,26 @@ BENCHMARK_DEFINE_F(AudioBench, FullSequence)(benchmark::State &state)
{
int frame_index = 0;
const int num_prefilled = 50;
std::vector<std::vector<int16_t>> pcms(
num_prefilled, std::vector<int16_t>(sample_count * channels));
std::vector<std::vector<std::int16_t>> pcms(
num_prefilled, std::vector<std::int16_t>(sample_count * channels));
for (int i = 0; i < num_prefilled; ++i) {
fill_audio_frame(sampling_rate, channels, i, sample_count, pcms[i]);
}
std::vector<uint8_t> encoded_tmp(2000);
std::vector<std::uint8_t> encoded_tmp(2000);
for (auto _ : state) {
int idx = frame_index % num_prefilled;
int size
= ac_encode(ac, pcms[idx].data(), sample_count, encoded_tmp.data(), encoded_tmp.size());
std::vector<uint8_t> payload(4 + size);
uint32_t net_sr = net_htonl(sampling_rate);
std::vector<std::uint8_t> payload(4 + size);
std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(payload.data(), &net_sr, 4);
std::memcpy(payload.data() + 4, encoded_tmp.data(), size);
rtp_send_data(log, rtp_mock.recv_session, payload.data(),
static_cast<uint32_t>(payload.size()), false);
static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac);
frame_index++;

View File

@@ -4,6 +4,11 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "../toxcore/logger.h"
@@ -38,31 +43,31 @@ TEST_F(AudioTest, EncodeDecodeLoop)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000;
uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz
std::uint32_t sampling_rate = 48000;
std::uint8_t channels = 1;
std::size_t sample_count = 960; // 20ms at 48kHz
// Reconfigure to mono
ASSERT_EQ(ac_reconfigure_encoder(ac, 48000, sampling_rate, channels), 0);
std::vector<int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < pcm.size(); ++i) {
pcm[i] = static_cast<int16_t>(i * 10);
std::vector<std::int16_t> pcm(sample_count * channels);
for (std::size_t i = 0; i < pcm.size(); ++i) {
pcm[i] = static_cast<std::int16_t>(i * 10);
}
std::vector<uint8_t> encoded(2000);
std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0);
// Prepare payload: 4 bytes sampling rate + Opus data
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size));
std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(payload.data(), &net_sr, 4);
std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
// Send via RTP
int rc = rtp_send_data(
log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false);
log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ASSERT_EQ(rc, 0);
// Decode
@@ -92,10 +97,10 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000;
uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz
uint32_t bitrate = 48000;
std::uint32_t sampling_rate = 48000;
std::uint8_t channels = 1;
std::size_t sample_count = 960; // 20ms at 48kHz
std::uint32_t bitrate = 48000;
ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0);
@@ -103,27 +108,28 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
double amplitude = 10000.0;
const double pi = std::acos(-1.0);
std::vector<int16_t> all_sent;
std::vector<int16_t> all_recv;
std::vector<std::int16_t> all_sent;
std::vector<std::int16_t> all_recv;
for (int frame = 0; frame < 50; ++frame) {
std::vector<int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < sample_count; ++i) {
std::vector<std::int16_t> pcm(sample_count * channels);
for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame * sample_count + i) / sampling_rate;
pcm[i] = static_cast<int16_t>(std::sin(2.0 * pi * frequency * t) * amplitude);
pcm[i] = static_cast<std::int16_t>(std::sin(2.0 * pi * frequency * t) * amplitude);
}
all_sent.insert(all_sent.end(), pcm.begin(), pcm.end());
std::vector<uint8_t> encoded(2000);
std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0);
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size));
std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(payload.data(), &net_sr, 4);
std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
rtp_send_data(log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false);
rtp_send_data(
log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac);
@@ -143,7 +149,7 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
for (int delay = 3000; delay < 3500; ++delay) {
double mse = 0;
int count = 0;
for (size_t i = 0; i < 2000; ++i) { // Compare a decent chunk
for (std::size_t i = 0; i < 2000; ++i) { // Compare a decent chunk
if (i + delay < all_sent.size() && i < all_recv.size()) {
int diff = all_sent[i + delay] - all_recv[i];
mse += static_cast<double>(diff) * diff;
@@ -159,7 +165,7 @@ TEST_F(AudioTest, EncodeDecodeRealistic)
}
}
printf("Best audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
std::printf("Best audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
// For 48kbps Opus, the MSE for a sine wave should be quite low once aligned.
// 10M is about 20% of the signal power (50M), which is a safe threshold for verification.
@@ -183,42 +189,43 @@ TEST_F(AudioTest, EncodeDecodeSiren)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint32_t sampling_rate = 48000;
uint8_t channels = 1;
size_t sample_count = 960; // 20ms at 48kHz
uint32_t bitrate = 64000;
std::uint32_t sampling_rate = 48000;
std::uint8_t channels = 1;
std::size_t sample_count = 960; // 20ms at 48kHz
std::uint32_t bitrate = 64000;
ASSERT_EQ(ac_reconfigure_encoder(ac, bitrate, sampling_rate, channels), 0);
double amplitude = 10000.0;
const double pi = std::acos(-1.0);
std::vector<int16_t> all_sent;
std::vector<int16_t> all_recv;
std::vector<std::int16_t> all_sent;
std::vector<std::int16_t> all_recv;
// 1 second of audio (50 frames) is enough for a siren test
for (int frame = 0; frame < 50; ++frame) {
std::vector<int16_t> pcm(sample_count * channels);
for (size_t i = 0; i < sample_count; ++i) {
std::vector<std::int16_t> pcm(sample_count * channels);
for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame * sample_count + i) / sampling_rate;
// Linear frequency sweep from 50Hz to 440Hz over 1 second
// f(t) = 50 + (440-50)/1 * t = 50 + 390t
// phi(t) = 2*pi * integral(f(t)) = 2*pi * (50t + 195t^2)
double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t);
pcm[i] = static_cast<int16_t>(std::sin(phi) * amplitude);
pcm[i] = static_cast<std::int16_t>(std::sin(phi) * amplitude);
}
all_sent.insert(all_sent.end(), pcm.begin(), pcm.end());
std::vector<uint8_t> encoded(2000);
std::vector<std::uint8_t> encoded(2000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0);
std::vector<uint8_t> payload(4 + static_cast<size_t>(encoded_size));
uint32_t net_sr = net_htonl(sampling_rate);
memcpy(payload.data(), &net_sr, 4);
memcpy(payload.data() + 4, encoded.data(), static_cast<size_t>(encoded_size));
std::vector<std::uint8_t> payload(4 + static_cast<std::size_t>(encoded_size));
std::uint32_t net_sr = net_htonl(sampling_rate);
std::memcpy(payload.data(), &net_sr, 4);
std::memcpy(payload.data() + 4, encoded.data(), static_cast<std::size_t>(encoded_size));
rtp_send_data(log, send_rtp, payload.data(), static_cast<uint32_t>(payload.size()), false);
rtp_send_data(
log, send_rtp, payload.data(), static_cast<std::uint32_t>(payload.size()), false);
ac_iterate(ac);
@@ -229,14 +236,14 @@ TEST_F(AudioTest, EncodeDecodeSiren)
ASSERT_FALSE(all_recv.empty());
auto calculate_mse_at = [&](int delay, size_t window) {
auto calculate_mse_at = [&](int delay, std::size_t window) {
double mse = 0;
int count = 0;
for (size_t i = 0; i < window; ++i) {
for (std::size_t i = 0; i < window; ++i) {
int sent_idx = static_cast<int>(i) + delay;
if (sent_idx >= 0 && static_cast<size_t>(sent_idx) < all_sent.size()
if (sent_idx >= 0 && static_cast<std::size_t>(sent_idx) < all_sent.size()
&& i < all_recv.size()) {
int diff = all_sent[static_cast<size_t>(sent_idx)] - all_recv[i];
int diff = all_sent[static_cast<std::size_t>(sent_idx)] - all_recv[i];
mse += static_cast<double>(diff) * diff;
count++;
}
@@ -267,7 +274,7 @@ TEST_F(AudioTest, EncodeDecodeSiren)
}
}
printf("Best siren audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
std::printf("Best siren audio delay found: %d samples, Min MSE: %f\n", best_delay, min_mse);
// For 64kbps Opus, the MSE for a siren wave should be reasonably low once aligned.
EXPECT_LT(min_mse, 20000000.0);
@@ -287,11 +294,11 @@ TEST_F(AudioTest, ReconfigureEncoder)
int rc = ac_reconfigure_encoder(ac, 32000, 24000, 2);
ASSERT_EQ(rc, 0);
size_t sample_count = 480; // 20ms at 24kHz
uint8_t channels = 2;
std::vector<int16_t> pcm(sample_count * channels, 0);
std::size_t sample_count = 480; // 20ms at 24kHz
std::uint8_t channels = 2;
std::vector<std::int16_t> pcm(sample_count * channels, 0);
std::vector<uint8_t> encoded(1000);
std::vector<std::uint8_t> encoded(1000);
int encoded_size = ac_encode(ac, pcm.data(), sample_count, encoded.data(), encoded.size());
ASSERT_GT(encoded_size, 0);
@@ -324,9 +331,9 @@ TEST_F(AudioTest, QueueInvalidMessage)
&rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = audio_recv_rtp;
std::vector<uint8_t> dummy_video(100, 0);
std::vector<std::uint8_t> dummy_video(100, 0);
int rc = rtp_send_data(
log, video_rtp, dummy_video.data(), static_cast<uint32_t>(dummy_video.size()), true);
log, video_rtp, dummy_video.data(), static_cast<std::uint32_t>(dummy_video.size()), true);
ASSERT_EQ(rc, 0);
// Iterate should NOT trigger callback because payload type was wrong
@@ -352,9 +359,9 @@ TEST_F(AudioTest, JitterBufferDuplicate)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
ASSERT_EQ(rtp_mock.captured_packets.size(), 1u);
@@ -393,9 +400,9 @@ TEST_F(AudioTest, JitterBufferOutOfOrder)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
// Capture 3 packets
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
@@ -440,9 +447,9 @@ TEST_F(AudioTest, PacketLossConcealment)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
// Send packet 0 and deliver it immediately.
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
@@ -486,9 +493,9 @@ TEST_F(AudioTest, JitterBufferReset)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet(
@@ -531,12 +538,12 @@ TEST_F(AudioTest, DecoderReconfigureCooldown)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr_48 = net_htonl(48000);
uint32_t net_sr_24 = net_htonl(24000);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr_48 = net_htonl(48000);
std::uint32_t net_sr_24 = net_htonl(24000);
// 1. Reconfigure to 24kHz. The initial sampling rate is 48kHz.
memcpy(dummy_data, &net_sr_24, 4);
std::memcpy(dummy_data, &net_sr_24, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size());
@@ -550,7 +557,7 @@ TEST_F(AudioTest, DecoderReconfigureCooldown)
mono_time_update(mono_time);
// 3. Attempt to reconfigure back to 48kHz.
memcpy(dummy_data, &net_sr_48, 4);
std::memcpy(dummy_data, &net_sr_48, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets.back().data(), rtp_mock.captured_packets.back().size());
@@ -592,9 +599,9 @@ TEST_F(AudioTest, QueueDummyMessage)
&rtp_mock, nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = audio_recv_rtp;
std::vector<uint8_t> dummy_payload(100, 0);
int rc = rtp_send_data(
log, dummy_rtp, dummy_payload.data(), static_cast<uint32_t>(dummy_payload.size()), false);
std::vector<std::uint8_t> dummy_payload(100, 0);
int rc = rtp_send_data(log, dummy_rtp, dummy_payload.data(),
static_cast<std::uint32_t>(dummy_payload.size()), false);
ASSERT_EQ(rc, 0);
// Iterate should NOT trigger callback because it was a dummy packet
@@ -620,9 +627,9 @@ TEST_F(AudioTest, LatePacketReset)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
// 1. Send and process the first packet.
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 0
@@ -633,14 +640,14 @@ TEST_F(AudioTest, LatePacketReset)
data.sample_count = 0;
// 2. Buffer another packet with a different sampling rate (24kHz) but don't process it yet.
uint32_t net_sr_24 = net_htonl(24000);
memcpy(dummy_data, &net_sr_24, 4);
std::uint32_t net_sr_24 = net_htonl(24000);
std::memcpy(dummy_data, &net_sr_24, 4);
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false); // seq 1
rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets[1].data(), rtp_mock.captured_packets[1].size());
// 3. Receive the late packet (seq 0) again.
// This triggers the bug: (uint32_t)(0 - 1) > 16, causing a full jitter buffer reset.
// This triggers the bug: (std::uint32_t)(0 - 1) > 16, causing a full jitter buffer reset.
rtp_receive_packet(
recv_rtp, rtp_mock.captured_packets[0].data(), rtp_mock.captured_packets[0].size());
@@ -672,9 +679,9 @@ TEST_F(AudioTest, InvalidSamplingRate)
rtp_mock.recv_session = recv_rtp;
// 1. Send a packet with an absurdly large sampling rate.
uint8_t malicious_data[100] = {0};
uint32_t net_sr = net_htonl(1000000000); // 1 GHz
memcpy(malicious_data, &net_sr, 4);
std::uint8_t malicious_data[100] = {0};
std::uint32_t net_sr = net_htonl(1000000000); // 1 GHz
std::memcpy(malicious_data, &net_sr, 4);
// Add some dummy Opus data so it's not too short
malicious_data[4] = 0x08;
@@ -720,7 +727,7 @@ TEST_F(AudioTest, ShortPacket)
// 1. Send a packet that is too short (only sampling rate, no Opus data).
// The protocol requires 4 bytes SR + at least 1 byte Opus data.
uint8_t short_data[4] = {0, 0, 0xBB, 0x80}; // 48000
std::uint8_t short_data[4] = {0, 0, 0xBB, 0x80}; // 48000
// rtp_send_data might not like 4 bytes if it expects more, but let's see.
rtp_send_data(log, send_rtp, short_data, sizeof(short_data), false);
@@ -750,16 +757,16 @@ TEST_F(AudioTest, JitterBufferWrapAround)
nullptr, nullptr, nullptr, ac, RtpMock::audio_cb);
rtp_mock.recv_session = recv_rtp;
uint8_t dummy_data[100] = {0};
uint32_t net_sr = net_htonl(48000);
memcpy(dummy_data, &net_sr, 4);
std::uint8_t dummy_data[100] = {0};
std::uint32_t net_sr = net_htonl(48000);
std::memcpy(dummy_data, &net_sr, 4);
// Send enough packets to reach the sequence number wrap-around point (0xFFFF -> 0x0000).
// We detect the current sequence number to minimize the number of iterations.
uint16_t seq = 0;
std::uint16_t seq = 0;
{
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
const uint8_t *pkt = rtp_mock.captured_packets.back().data();
const std::uint8_t *pkt = rtp_mock.captured_packets.back().data();
seq = (pkt[3] << 8) | pkt[4];
rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size());
rtp_mock.captured_packets.clear();
@@ -770,7 +777,7 @@ TEST_F(AudioTest, JitterBufferWrapAround)
int to_send = (65532 - seq + 65536) % 65536;
for (int i = 0; i < to_send; ++i) {
rtp_send_data(log, send_rtp, dummy_data, sizeof(dummy_data), false);
const uint8_t *pkt = rtp_mock.captured_packets.back().data();
const std::uint8_t *pkt = rtp_mock.captured_packets.back().data();
rtp_receive_packet(recv_rtp, pkt, rtp_mock.captured_packets.back().size());
rtp_mock.captured_packets.clear();
ac_iterate(ac);

View File

@@ -2,15 +2,18 @@
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include "../toxcore/os_memory.h"
// Mock Time
uint64_t mock_time_cb(void *ud) { return static_cast<MockTime *>(ud)->t; }
std::uint64_t mock_time_cb(void *_Nullable ud) { return static_cast<MockTime *>(ud)->t; }
// RTP Mock
int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length)
int RtpMock::send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{
auto *self = static_cast<RtpMock *>(user_data);
if (self->capture_packets) {
@@ -21,7 +24,7 @@ int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length)
self->captured_packets[0].assign(data, data + length);
}
} else {
self->captured_packets.push_back(std::vector<uint8_t>(data, data + length));
self->captured_packets.push_back(std::vector<std::uint8_t>(data, data + length));
}
}
if (self->auto_forward && self->recv_session) {
@@ -30,45 +33,49 @@ int RtpMock::send_packet(void *user_data, const uint8_t *data, uint16_t length)
return 0;
}
int RtpMock::audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg)
int RtpMock::audio_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg)
{
return ac_queue_message(mono_time, cs, msg);
}
int RtpMock::video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg)
int RtpMock::video_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg)
{
return vc_queue_message(mono_time, cs, msg);
}
int RtpMock::noop_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage *msg)
int RtpMock::noop_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable /*cs*/, RTPMessage *_Nonnull msg)
{
std::free(msg);
return 0;
}
// Audio Helpers
void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index,
size_t sample_count, std::vector<int16_t> &pcm)
void fill_audio_frame(std::uint32_t sampling_rate, std::uint8_t channels, int frame_index,
std::size_t sample_count, std::vector<std::int16_t> &pcm)
{
const double pi = std::acos(-1.0);
double amplitude = 10000.0;
for (size_t i = 0; i < sample_count; ++i) {
for (std::size_t i = 0; i < sample_count; ++i) {
double t = static_cast<double>(frame_index * sample_count + i) / sampling_rate;
// Linear frequency sweep from 50Hz to 440Hz over 1 second (50 frames)
// f(t) = 50 + 390t
// phi(t) = 2*pi * (50t + 195t^2)
double phi = 2.0 * pi * (50.0 * t + 195.0 * t * t);
int16_t val = static_cast<int16_t>(std::sin(phi) * amplitude);
for (uint8_t c = 0; c < channels; ++c) {
std::int16_t val = static_cast<std::int16_t>(std::sin(phi) * amplitude);
for (std::uint8_t c = 0; c < channels; ++c) {
pcm[i * channels + c] = val;
}
}
}
void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_t> &pcm)
void fill_silent_frame(
std::uint8_t channels, std::size_t sample_count, std::vector<std::int16_t> &pcm)
{
for (size_t i = 0; i < sample_count * channels; ++i) {
for (std::size_t i = 0; i < sample_count * channels; ++i) {
// Very low amplitude white noise (simulating silence with background hiss)
pcm[i] = (std::rand() % 21) - 10;
}
@@ -77,8 +84,9 @@ void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_
AudioTestData::AudioTestData() = default;
AudioTestData::~AudioTestData() = default;
void AudioTestData::receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, void *user_data)
void AudioTestData::receive_frame(std::uint32_t friend_number, const std::int16_t *_Nonnull pcm,
std::size_t sample_count, std::uint8_t channels, std::uint32_t sampling_rate,
void *_Nullable user_data)
{
auto *self = static_cast<AudioTestData *>(user_data);
self->friend_number = friend_number;
@@ -89,8 +97,8 @@ void AudioTestData::receive_frame(uint32_t friend_number, const int16_t *pcm, si
}
// Video Helpers
void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector<uint8_t> &y,
std::vector<uint8_t> &u, std::vector<uint8_t> &v)
void fill_video_frame(std::uint16_t width, std::uint16_t height, int frame_index,
std::vector<std::uint8_t> &y, std::vector<std::uint8_t> &u, std::vector<std::uint8_t> &v)
{
// Background (dark gray)
std::fill(y.begin(), y.end(), 32);
@@ -110,10 +118,10 @@ void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vec
}
}
double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride,
const std::vector<uint8_t> &y_recv, const std::vector<uint8_t> &y_orig)
double calculate_video_mse(std::uint16_t width, std::uint16_t height, std::int32_t ystride,
const std::vector<std::uint8_t> &y_recv, const std::vector<std::uint8_t> &y_orig)
{
if (y_recv.empty() || y_orig.size() != static_cast<size_t>(width) * height) {
if (y_recv.empty() || y_orig.size() != static_cast<std::size_t>(width) * height) {
return 1e10;
}
@@ -124,16 +132,17 @@ double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride,
mse += diff * diff;
}
}
return mse / (static_cast<size_t>(width) * height);
return mse / (static_cast<std::size_t>(width) * height);
}
// Video Test Data Helper
VideoTestData::VideoTestData() = default;
VideoTestData::~VideoTestData() = default;
void VideoTestData::receive_frame(uint32_t friend_number, uint16_t width, uint16_t height,
const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride,
int32_t vstride, void *user_data)
void VideoTestData::receive_frame(std::uint32_t friend_number, std::uint16_t width,
std::uint16_t height, const std::uint8_t *_Nonnull y, const std::uint8_t *_Nonnull u,
const std::uint8_t *_Nonnull v, std::int32_t ystride, std::int32_t ustride,
std::int32_t vstride, void *_Nullable user_data)
{
auto *self = static_cast<VideoTestData *>(user_data);
self->friend_number = friend_number;
@@ -143,12 +152,12 @@ void VideoTestData::receive_frame(uint32_t friend_number, uint16_t width, uint16
self->ustride = ustride;
self->vstride = vstride;
self->y.assign(y, y + static_cast<size_t>(std::abs(ystride)) * height);
self->u.assign(u, u + static_cast<size_t>(std::abs(ustride)) * (height / 2));
self->v.assign(v, v + static_cast<size_t>(std::abs(vstride)) * (height / 2));
self->y.assign(y, y + static_cast<std::size_t>(std::abs(ystride)) * height);
self->u.assign(u, u + static_cast<std::size_t>(std::abs(ustride)) * (height / 2));
self->v.assign(v, v + static_cast<std::size_t>(std::abs(vstride)) * (height / 2));
}
double VideoTestData::calculate_mse(const std::vector<uint8_t> &y_orig) const
double VideoTestData::calculate_mse(const std::vector<std::uint8_t> &y_orig) const
{
return calculate_video_mse(width, height, ystride, y, y_orig);
}
@@ -156,7 +165,7 @@ double VideoTestData::calculate_mse(const std::vector<uint8_t> &y_orig) const
// Common Test Fixture
void AvTest::SetUp()
{
const Memory *mem = os_memory();
mem = os_memory();
log = logger_new(mem);
tm.t = 1000;
mono_time = mono_time_new(mem, mock_time_cb, &tm);
@@ -165,7 +174,6 @@ void AvTest::SetUp()
void AvTest::TearDown()
{
const Memory *mem = os_memory();
mono_time_free(mem, mono_time);
logger_kill(log);
}

View File

@@ -3,9 +3,11 @@
#include <gtest/gtest.h>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "audio.h"
@@ -14,65 +16,72 @@
// Mock Time
struct MockTime {
uint64_t t = 1000;
std::uint64_t t = 1000;
};
uint64_t mock_time_cb(void *ud);
std::uint64_t mock_time_cb(void *_Nullable ud);
// RTP Mock
struct RtpMock {
RTPSession *recv_session = nullptr;
std::vector<std::vector<uint8_t>> captured_packets;
RTPSession *_Nullable recv_session = nullptr;
std::vector<std::vector<std::uint8_t>> captured_packets;
bool auto_forward = true;
bool capture_packets = true;
bool store_last_packet_only = false;
static int send_packet(void *user_data, const uint8_t *data, uint16_t length);
static int audio_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg);
static int video_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg);
static int noop_cb(const Mono_Time *mono_time, void *cs, RTPMessage *msg);
static int send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length);
static int audio_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
static int video_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
static int noop_cb(
const Mono_Time *_Nonnull mono_time, void *_Nullable cs, RTPMessage *_Nonnull msg);
};
// Audio Helpers
void fill_audio_frame(uint32_t sampling_rate, uint8_t channels, int frame_index,
size_t sample_count, std::vector<int16_t> &pcm);
void fill_silent_frame(uint8_t channels, size_t sample_count, std::vector<int16_t> &pcm);
void fill_audio_frame(std::uint32_t sampling_rate, std::uint8_t channels, int frame_index,
std::size_t sample_count, std::vector<std::int16_t> &pcm);
void fill_silent_frame(
std::uint8_t channels, std::size_t sample_count, std::vector<std::int16_t> &pcm);
struct AudioTestData {
uint32_t friend_number = 0;
std::vector<int16_t> last_pcm;
size_t sample_count = 0;
uint8_t channels = 0;
uint32_t sampling_rate = 0;
std::uint32_t friend_number = 0;
std::vector<std::int16_t> last_pcm;
std::size_t sample_count = 0;
std::uint8_t channels = 0;
std::uint32_t sampling_rate = 0;
AudioTestData();
~AudioTestData();
static void receive_frame(uint32_t friend_number, const int16_t *pcm, size_t sample_count,
uint8_t channels, uint32_t sampling_rate, void *user_data);
static void receive_frame(std::uint32_t friend_number, const std::int16_t *_Nonnull pcm,
std::size_t sample_count, std::uint8_t channels, std::uint32_t sampling_rate,
void *_Nullable user_data);
};
// Video Helpers
void fill_video_frame(uint16_t width, uint16_t height, int frame_index, std::vector<uint8_t> &y,
std::vector<uint8_t> &u, std::vector<uint8_t> &v);
double calculate_video_mse(uint16_t width, uint16_t height, int32_t ystride,
const std::vector<uint8_t> &y_recv, const std::vector<uint8_t> &y_orig);
void fill_video_frame(std::uint16_t width, std::uint16_t height, int frame_index,
std::vector<std::uint8_t> &y, std::vector<std::uint8_t> &u, std::vector<std::uint8_t> &v);
double calculate_video_mse(std::uint16_t width, std::uint16_t height, std::int32_t ystride,
const std::vector<std::uint8_t> &y_recv, const std::vector<std::uint8_t> &y_orig);
// Video Test Data Helper
struct VideoTestData {
uint32_t friend_number = 0;
uint16_t width = 0;
uint16_t height = 0;
std::vector<uint8_t> y, u, v;
int32_t ystride = 0, ustride = 0, vstride = 0;
std::uint32_t friend_number = 0;
std::uint16_t width = 0;
std::uint16_t height = 0;
std::vector<std::uint8_t> y, u, v;
std::int32_t ystride = 0, ustride = 0, vstride = 0;
VideoTestData();
~VideoTestData();
static void receive_frame(uint32_t friend_number, uint16_t width, uint16_t height,
const uint8_t *y, const uint8_t *u, const uint8_t *v, int32_t ystride, int32_t ustride,
int32_t vstride, void *user_data);
static void receive_frame(std::uint32_t friend_number, std::uint16_t width,
std::uint16_t height, const std::uint8_t *_Nonnull y, const std::uint8_t *_Nonnull u,
const std::uint8_t *_Nonnull v, std::int32_t ystride, std::int32_t ustride,
std::int32_t vstride, void *_Nullable user_data);
double calculate_mse(const std::vector<uint8_t> &y_orig) const;
double calculate_mse(const std::vector<std::uint8_t> &y_orig) const;
};
// Common Test Fixture
@@ -81,8 +90,9 @@ protected:
void SetUp() override;
void TearDown() override;
Logger *log = nullptr;
Mono_Time *mono_time = nullptr;
Logger *_Nullable log = nullptr;
Mono_Time *_Nullable mono_time = nullptr;
const Memory *_Nullable mem = nullptr;
MockTime tm;
};

View File

@@ -3,8 +3,11 @@
#include <gtest/gtest.h>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/network.h"
@@ -13,17 +16,18 @@
namespace {
struct BwcTimeMock {
uint64_t t;
std::uint64_t t;
};
uint64_t bwc_mock_time_cb(void *ud) { return static_cast<BwcTimeMock *>(ud)->t; }
std::uint64_t bwc_mock_time_cb(void *ud) { return static_cast<BwcTimeMock *>(ud)->t; }
struct MockBwcData {
std::vector<std::vector<uint8_t>> sent_packets;
std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<float> reported_losses;
uint32_t friend_number = 0;
std::uint32_t friend_number = 0;
static int send_packet(void *user_data, const uint8_t *data, uint16_t length)
static int send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{
auto *sd = static_cast<MockBwcData *>(user_data);
if (sd->fail_send) {
@@ -33,8 +37,8 @@ struct MockBwcData {
return 0;
}
static void loss_report(
BWController * /*bwc*/, uint32_t friend_number, float loss, void *user_data)
static void loss_report(BWController *_Nonnull /*bwc*/, std::uint32_t friend_number, float loss,
void *_Nullable user_data)
{
auto *sd = static_cast<MockBwcData *>(user_data);
sd->friend_number = friend_number;
@@ -57,13 +61,13 @@ protected:
void TearDown() override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
mono_time_free(mem, mono_time);
logger_kill(log);
}
Logger *log;
Mono_Time *mono_time;
Logger *_Nullable log;
Mono_Time *_Nullable mono_time;
BwcTimeMock tm;
};
@@ -79,7 +83,7 @@ TEST_F(BwcTest, BasicNewKill)
TEST_F(BwcTest, SendUpdate)
{
MockBwcData sd;
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd,
MockBwcData::send_packet, &sd, mono_time);
ASSERT_NE(bwc, nullptr);
@@ -107,7 +111,7 @@ TEST_F(BwcTest, SendUpdate)
EXPECT_EQ(sd.sent_packets[0][0], BWC_PACKET_ID);
// Packet contains lost (4 bytes) and recv (4 bytes)
uint32_t lost, recv;
std::uint32_t lost, recv;
net_unpack_u32(sd.sent_packets[0].data() + 1, &lost);
net_unpack_u32(sd.sent_packets[0].data() + 5, &recv);
@@ -120,12 +124,12 @@ TEST_F(BwcTest, SendUpdate)
TEST_F(BwcTest, HandlePacket)
{
MockBwcData sd;
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
BWController *bwc = bwc_new(log, friend_number, MockBwcData::loss_report, &sd,
MockBwcData::send_packet, &sd, mono_time);
ASSERT_NE(bwc, nullptr);
uint8_t packet[9];
std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 100); // lost
net_pack_u32(packet + 5, 900); // recv
@@ -155,7 +159,7 @@ TEST_F(BwcTest, InvalidPacketSize)
MockBwcData sd;
BWController *bwc = bwc_new(
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[10] = {0};
std::uint8_t packet[10] = {0};
// Correct size is 9
bwc_handle_packet(bwc, packet, 8);
@@ -193,7 +197,7 @@ TEST_F(BwcTest, NullCallback)
BWController *bwc
= bwc_new(log, 123, nullptr, nullptr, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[9];
std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 100); // lost
net_pack_u32(packet + 5, 900); // recv
@@ -213,7 +217,7 @@ TEST_F(BwcTest, ZeroLoss)
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
// 1. Peer sends update with zero loss
uint8_t packet[9];
std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 0); // lost
net_pack_u32(packet + 5, 1000); // recv
@@ -255,7 +259,7 @@ TEST_F(BwcTest, Overflow)
bwc_add_recv(bwc, 1);
ASSERT_EQ(sd.sent_packets.size(), 1);
uint32_t lost, recv;
std::uint32_t lost, recv;
net_unpack_u32(sd.sent_packets[0].data() + 1, &lost);
net_unpack_u32(sd.sent_packets[0].data() + 5, &recv);
@@ -324,7 +328,7 @@ TEST_F(BwcTest, RecvPlusLostOverflowBug)
MockBwcData sd;
BWController *bwc = bwc_new(
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
uint8_t packet[9];
std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 1);
net_pack_u32(packet + 5, 0xFFFFFFFF);
@@ -342,7 +346,7 @@ TEST_F(BwcTest, RateLimitBypassBug)
log, 123, MockBwcData::loss_report, &sd, MockBwcData::send_packet, &sd, mono_time);
tm.t = 0xFFFFFFF0;
mono_time_update(mono_time);
uint8_t packet[9];
std::uint8_t packet[9];
packet[0] = BWC_PACKET_ID;
net_pack_u32(packet + 1, 1);
net_pack_u32(packet + 5, 100);

View File

@@ -6,16 +6,19 @@
#include <gtest/gtest.h>
#include <cstddef>
#include <cstdint>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/os_memory.h"
namespace {
struct MockMsi {
std::vector<std::vector<uint8_t>> sent_packets;
std::vector<uint32_t> sent_to_friends;
std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<std::uint32_t> sent_to_friends;
struct CallbackStats {
int invite = 0;
@@ -26,11 +29,11 @@ struct MockMsi {
int capabilities = 0;
} stats;
MSICall *last_call = nullptr;
MSICall *_Nullable last_call = nullptr;
MSIError last_error = MSI_E_NONE;
static int send_packet(
void *user_data, uint32_t friend_number, const uint8_t *data, size_t length)
static int send_packet(void *_Nullable user_data, std::uint32_t friend_number,
const std::uint8_t *_Nonnull data, std::size_t length)
{
auto *self = static_cast<MockMsi *>(user_data);
self->sent_packets.emplace_back(data, data + length);
@@ -38,7 +41,7 @@ struct MockMsi {
return 0;
}
static int on_invite(void *object, MSICall *call)
static int on_invite(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.invite++;
@@ -46,7 +49,7 @@ struct MockMsi {
return 0;
}
static int on_start(void *object, MSICall *call)
static int on_start(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.start++;
@@ -54,7 +57,7 @@ struct MockMsi {
return 0;
}
static int on_end(void *object, MSICall *call)
static int on_end(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.end++;
@@ -62,7 +65,7 @@ struct MockMsi {
return 0;
}
static int on_error(void *object, MSICall *call)
static int on_error(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.error++;
@@ -71,7 +74,7 @@ struct MockMsi {
return 0;
}
static int on_peertimeout(void *object, MSICall *call)
static int on_peertimeout(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.peertimeout++;
@@ -79,7 +82,7 @@ struct MockMsi {
return 0;
}
static int on_capabilities(void *object, MSICall *call)
static int on_capabilities(void *_Nullable object, MSICall *_Nonnull call)
{
auto *self = static_cast<MockMsi *>(object);
self->stats.capabilities++;
@@ -92,7 +95,7 @@ class MsiTest : public ::testing::Test {
protected:
void SetUp() override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
log = logger_new(mem);
MSICallbacks callbacks = {MockMsi::on_invite, MockMsi::on_start, MockMsi::on_end,
@@ -109,8 +112,8 @@ protected:
logger_kill(log);
}
Logger *log;
MSISession *session = nullptr;
Logger *_Nullable log;
MSISession *_Nullable session = nullptr;
MockMsi mock;
};
@@ -122,8 +125,8 @@ TEST_F(MsiTest, BasicNewKill)
TEST_F(MsiTest, Invite)
{
MSICall *call = nullptr;
uint32_t friend_number = 123;
uint8_t capabilities = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
std::uint32_t friend_number = 123;
std::uint8_t capabilities = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
int rc = msi_invite(log, session, &call, friend_number, capabilities);
ASSERT_EQ(rc, 0);
@@ -146,11 +149,11 @@ TEST_F(MsiTest, Invite)
TEST_F(MsiTest, HandleIncomingInvite)
{
uint32_t friend_number = 456;
uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
std::uint32_t friend_number = 456;
std::uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
// Craft invite packet
uint8_t invite_pkt[] = {
std::uint8_t invite_pkt[] = {
1, 1, 0, // ID_REQUEST, len 1, REQU_INIT
3, 1, peer_caps, // ID_CAPABILITIES, len 1, caps
0 // end
@@ -168,14 +171,14 @@ TEST_F(MsiTest, HandleIncomingInvite)
TEST_F(MsiTest, Answer)
{
// 1. Receive invite first
uint32_t friend_number = 456;
uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, peer_caps, 0};
std::uint32_t friend_number = 456;
std::uint8_t peer_caps = MSI_CAP_S_VIDEO | MSI_CAP_R_VIDEO;
std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, peer_caps, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call;
// 2. Answer it
uint8_t my_caps = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
std::uint8_t my_caps = MSI_CAP_S_AUDIO | MSI_CAP_R_AUDIO;
int rc = msi_answer(log, call, my_caps);
ASSERT_EQ(rc, 0);
EXPECT_EQ(call->state, MSI_CALL_ACTIVE);
@@ -206,14 +209,14 @@ TEST_F(MsiTest, Hangup)
TEST_F(MsiTest, ChangeCapabilities)
{
// Setup active call
uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
std::uint32_t friend_number = 123;
std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call;
msi_answer(log, call, 0);
mock.sent_packets.clear();
uint8_t new_caps = MSI_CAP_S_VIDEO;
std::uint8_t new_caps = MSI_CAP_S_VIDEO;
int rc = msi_change_capabilities(log, call, new_caps);
ASSERT_EQ(rc, 0);
EXPECT_EQ(call->self_capabilities, new_caps);
@@ -226,7 +229,7 @@ TEST_F(MsiTest, ChangeCapabilities)
TEST_F(MsiTest, PeerTimeout)
{
MSICall *call = nullptr;
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
msi_invite(log, session, &call, friend_number, 0);
msi_call_timeout(session, log, friend_number);
@@ -236,12 +239,12 @@ TEST_F(MsiTest, PeerTimeout)
TEST_F(MsiTest, RemoteHangup)
{
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
MSICall *call = nullptr;
msi_invite(log, session, &call, friend_number, 0);
// Craft pop packet
uint8_t pop_pkt[] = {1, 1, 2, 0}; // REQU_POP
std::uint8_t pop_pkt[] = {1, 1, 2, 0}; // REQU_POP
msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt));
EXPECT_EQ(mock.stats.end, 1);
@@ -249,12 +252,12 @@ TEST_F(MsiTest, RemoteHangup)
TEST_F(MsiTest, RemoteError)
{
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
MSICall *call = nullptr;
msi_invite(log, session, &call, friend_number, 0);
// Craft error packet (ID_ERROR = 2)
uint8_t error_pkt[] = {1, 1, 2, 2, 1, 1, 0}; // REQU_POP + MSI_E_INVALID_MESSAGE
std::uint8_t error_pkt[] = {1, 1, 2, 2, 1, 1, 0}; // REQU_POP + MSI_E_INVALID_MESSAGE
msi_handle_packet(session, log, friend_number, error_pkt, sizeof(error_pkt));
EXPECT_EQ(mock.stats.error, 1);
@@ -278,7 +281,7 @@ TEST_F(MsiTest, MultipleConcurrentCalls)
msi_hangup(log, call1);
// Call 2 should still be there
uint8_t pop_pkt[] = {1, 1, 2, 0};
std::uint8_t pop_pkt[] = {1, 1, 2, 0};
msi_handle_packet(session, log, 2, pop_pkt, sizeof(pop_pkt));
EXPECT_EQ(mock.stats.end, 1);
}
@@ -288,8 +291,8 @@ TEST_F(MsiTest, RemoteAnswer)
MSICall *call = nullptr;
msi_invite(log, session, &call, 123, 0);
uint8_t peer_caps = MSI_CAP_S_AUDIO;
uint8_t push_pkt[] = {1, 1, 1, 3, 1, peer_caps, 0}; // REQU_PUSH + capabilities
std::uint8_t peer_caps = MSI_CAP_S_AUDIO;
std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, peer_caps, 0}; // REQU_PUSH + capabilities
msi_handle_packet(session, log, 123, push_pkt, sizeof(push_pkt));
EXPECT_EQ(mock.stats.start, 1);
@@ -299,14 +302,14 @@ TEST_F(MsiTest, RemoteAnswer)
TEST_F(MsiTest, RemoteCapabilitiesChange)
{
uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
std::uint32_t friend_number = 123;
std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call;
msi_answer(log, call, 0);
uint8_t new_caps = MSI_CAP_S_VIDEO;
uint8_t push_pkt[] = {1, 1, 1, 3, 1, new_caps, 0}; // REQU_PUSH + new capabilities
std::uint8_t new_caps = MSI_CAP_S_VIDEO;
std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, new_caps, 0}; // REQU_PUSH + new capabilities
msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt));
EXPECT_EQ(mock.stats.capabilities, 1);
@@ -315,8 +318,8 @@ TEST_F(MsiTest, RemoteCapabilitiesChange)
TEST_F(MsiTest, FriendRecall)
{
uint32_t friend_number = 123;
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
std::uint32_t friend_number = 123;
std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invite_pkt, sizeof(invite_pkt));
MSICall *call = mock.last_call;
msi_answer(log, call, 0);
@@ -348,30 +351,30 @@ TEST_F(MsiTest, GapInFriendNumbers)
TEST_F(MsiTest, InvalidPackets)
{
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
// Empty packet
uint8_t empty = 0;
std::uint8_t empty = 0;
msi_handle_packet(session, log, friend_number, &empty, 0);
// Missing end byte
uint8_t no_end[] = {1, 1, 0};
std::uint8_t no_end[] = {1, 1, 0};
msi_handle_packet(session, log, friend_number, no_end, sizeof(no_end));
// Invalid ID
uint8_t invalid_id[] = {99, 1, 0, 0};
std::uint8_t invalid_id[] = {99, 1, 0, 0};
msi_handle_packet(session, log, friend_number, invalid_id, sizeof(invalid_id));
// Invalid size (too large)
uint8_t invalid_size[] = {1, 10, 0, 0};
std::uint8_t invalid_size[] = {1, 10, 0, 0};
msi_handle_packet(session, log, friend_number, invalid_size, sizeof(invalid_size));
// Invalid size (mismatch)
uint8_t size_mismatch[] = {1, 2, 0, 0};
std::uint8_t size_mismatch[] = {1, 2, 0, 0};
msi_handle_packet(session, log, friend_number, size_mismatch, sizeof(size_mismatch));
// Missing request field
uint8_t no_request[] = {3, 1, 0, 0};
std::uint8_t no_request[] = {3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, no_request, sizeof(no_request));
}
@@ -387,7 +390,7 @@ TEST_F(MsiTest, CallbackFailure)
MSISession *fail_session = msi_new(log, MockMsi::send_packet, &mock, &callbacks, &mock);
uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
std::uint8_t invite_pkt[] = {1, 1, 0, 3, 1, 0, 0};
msi_handle_packet(fail_session, log, 123, invite_pkt, sizeof(invite_pkt));
// Should have sent an error back
@@ -417,14 +420,14 @@ TEST_F(MsiTest, InvalidStates)
TEST_F(MsiTest, StrayPackets)
{
uint32_t friend_number = 123;
std::uint32_t friend_number = 123;
// PUSH for non-existent call
uint8_t push_pkt[] = {1, 1, 1, 3, 1, 0, 0};
std::uint8_t push_pkt[] = {1, 1, 1, 3, 1, 0, 0};
msi_handle_packet(session, log, friend_number, push_pkt, sizeof(push_pkt));
// POP for non-existent call
uint8_t pop_pkt[] = {1, 1, 2, 0};
std::uint8_t pop_pkt[] = {1, 1, 2, 0};
msi_handle_packet(session, log, friend_number, pop_pkt, sizeof(pop_pkt));
// Error sent back for stray PUSH

View File

@@ -66,6 +66,10 @@ bool rb_read(RingBuffer *b, void **p)
RingBuffer *rb_new(int size)
{
if (size < 0 || size >= 65535) {
return nullptr;
}
RingBuffer *buf = (RingBuffer *)calloc(1, sizeof(RingBuffer));
if (buf == nullptr) {
@@ -103,10 +107,14 @@ uint16_t rb_size(const RingBuffer *b)
(b->size - b->start) + b->end;
}
uint16_t rb_data(const RingBuffer *b, void **dest)
uint16_t rb_data(const RingBuffer *b, void **dest, uint16_t dest_size)
{
uint16_t i;
const uint16_t size = rb_size(b);
uint16_t size = rb_size(b);
if (size > dest_size) {
size = dest_size;
}
for (i = 0; i < size; ++i) {
dest[i] = b->data[(b->start + i) % b->size];

View File

@@ -24,7 +24,7 @@ bool rb_read(RingBuffer *_Nonnull b, void *_Nonnull *_Nullable p);
RingBuffer *_Nullable rb_new(int size);
void rb_kill(RingBuffer *_Nullable b);
uint16_t rb_size(const RingBuffer *_Nonnull b);
uint16_t rb_data(const RingBuffer *_Nonnull b, void *_Nonnull *_Nonnull dest);
uint16_t rb_data(const RingBuffer *_Nonnull b, void *_Nonnull *_Nonnull dest, uint16_t dest_size);
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -4,8 +4,11 @@
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <vector>
#include "../toxcore/attributes.h"
namespace {
template <typename T>
@@ -23,37 +26,39 @@ public:
bool full() const { return rb_full(rb_); }
bool empty() const { return rb_empty(rb_); }
T *write(T *p) { return static_cast<T *>(rb_write(rb_, p)); }
bool read(T **p)
T *_Nullable write(T *_Nullable p) { return static_cast<T *>(rb_write(rb_, p)); }
bool read(T *_Nullable *_Nonnull p)
{
void *vp;
void *_Nullable vp;
bool res = rb_read(rb_, &vp);
*p = static_cast<T *>(vp);
return res;
}
uint16_t size() const { return rb_size(rb_); }
uint16_t data(T **dest) const
std::uint16_t size() const { return rb_size(rb_); }
std::uint16_t data(T *_Nullable *_Nonnull dest, std::uint16_t dest_size) const
{
std::vector<void *> vdest(size());
uint16_t res = rb_data(rb_, vdest.data());
for (uint16_t i = 0; i < size(); i++) {
const std::uint16_t current_size = std::min(size(), dest_size);
std::vector<void *_Nullable> vdest(current_size);
const std::uint16_t res = rb_data(rb_, vdest.data(), current_size);
for (std::uint16_t i = 0; i < res; i++) {
dest[i] = static_cast<T *>(vdest.at(i));
}
return res;
}
bool contains(T *p) const
bool contains(T *_Nullable p) const
{
std::vector<T *> elts(size());
data(elts.data());
const std::uint16_t current_size = size();
std::vector<T *_Nullable> elts(current_size);
data(elts.data(), current_size);
return std::find(elts.begin(), elts.end(), p) != elts.end();
}
bool ok() const { return rb_ != nullptr; }
private:
RingBuffer *rb_;
RingBuffer *_Nullable rb_;
};
TEST(RingBuffer, EmptyBufferReportsEmpty)
@@ -211,4 +216,27 @@ TEST(RingBuffer, SizeIsLimitedByMaxSize)
EXPECT_EQ(rb.size(), 4);
}
TEST(RingBuffer, NewWithInvalidSizeReturnsNull)
{
EXPECT_EQ(nullptr, rb_new(-1));
EXPECT_EQ(nullptr, rb_new(65535));
}
TEST(RingBuffer, DataWithSmallerDestSizeIsTruncated)
{
TypedRingBuffer<int *> rb(4);
ASSERT_TRUE(rb.ok());
int values[] = {1, 2, 3, 4};
rb.write(&values[0]);
rb.write(&values[1]);
rb.write(&values[2]);
rb.write(&values[3]);
int *dest[2];
std::uint16_t res = rb.data(dest, 2);
EXPECT_EQ(res, 2);
EXPECT_EQ(dest[0], &values[0]);
EXPECT_EQ(dest[1], &values[1]);
}
} // namespace

View File

@@ -4,9 +4,12 @@
#include <benchmark/benchmark.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/os_memory.h"
@@ -19,7 +22,7 @@ class RtpBench : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State &) override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
log = logger_new(mem);
mono_time = mono_time_new(mem, nullptr, nullptr);
@@ -37,19 +40,19 @@ public:
logger_kill(log);
}
Logger *log = nullptr;
Mono_Time *mono_time = nullptr;
RTPSession *session = nullptr;
Logger *_Nullable log = nullptr;
Mono_Time *_Nullable mono_time = nullptr;
RTPSession *_Nullable session = nullptr;
RtpMock mock;
};
BENCHMARK_DEFINE_F(RtpBench, SendData)(benchmark::State &state)
{
size_t data_size = static_cast<size_t>(state.range(0));
std::vector<uint8_t> data(data_size, 0xAA);
std::size_t data_size = static_cast<std::size_t>(state.range(0));
std::vector<std::uint8_t> data(data_size, 0xAA);
for (auto _ : state) {
rtp_send_data(log, session, data.data(), static_cast<uint32_t>(data.size()), false);
rtp_send_data(log, session, data.data(), static_cast<std::uint32_t>(data.size()), false);
benchmark::DoNotOptimize(mock.captured_packets.back());
}
}
@@ -57,10 +60,10 @@ BENCHMARK_REGISTER_F(RtpBench, SendData)->Arg(100)->Arg(1000)->Arg(5000);
BENCHMARK_DEFINE_F(RtpBench, ReceivePacket)(benchmark::State &state)
{
size_t data_size = static_cast<size_t>(state.range(0));
std::vector<uint8_t> data(data_size, 0xAA);
rtp_send_data(log, session, data.data(), static_cast<uint32_t>(data.size()), false);
std::vector<uint8_t> packet = mock.captured_packets.back();
std::size_t data_size = static_cast<std::size_t>(state.range(0));
std::vector<std::uint8_t> data(data_size, 0xAA);
rtp_send_data(log, session, data.data(), static_cast<std::uint32_t>(data.size()), false);
std::vector<std::uint8_t> packet = mock.captured_packets.back();
for (auto _ : state) {
rtp_receive_packet(session, packet.data(), packet.size());

View File

@@ -1,10 +1,13 @@
#include "rtp.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <vector>
#include "../testing/support/public/fuzz_data.hh"
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/os_memory.h"
@@ -15,12 +18,14 @@ using tox::test::Fuzz_Data;
struct MockSessionData { };
static int mock_send_packet(void * /*user_data*/, const uint8_t * /*data*/, uint16_t /*length*/)
static int mock_send_packet(
void *_Nullable /*user_data*/, const std::uint8_t *_Nonnull /*data*/, std::uint16_t /*length*/)
{
return 0;
}
static int mock_m_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage *msg)
static int mock_m_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable /*cs*/, RTPMessage *_Nonnull msg)
{
std::free(msg);
return 0;
@@ -28,28 +33,28 @@ static int mock_m_cb(const Mono_Time * /*mono_time*/, void * /*cs*/, RTPMessage
void fuzz_rtp_receive(Fuzz_Data &input)
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
struct LoggerDeleter {
void operator()(Logger *l) { logger_kill(l); }
void operator()(Logger *_Nullable l) { logger_kill(l); }
};
std::unique_ptr<Logger, LoggerDeleter> log(logger_new(mem));
auto time_cb = [](void *) -> uint64_t { return 0; };
auto time_cb = [](void *_Nullable) -> std::uint64_t { return 0; };
struct MonoTimeDeleter {
const Memory *m;
void operator()(Mono_Time *t) { mono_time_free(m, t); }
const Memory *_Nonnull m;
void operator()(Mono_Time *_Nullable t) { mono_time_free(m, t); }
};
std::unique_ptr<Mono_Time, MonoTimeDeleter> mono_time(
mono_time_new(mem, time_cb, nullptr), MonoTimeDeleter{mem});
MockSessionData sd;
CONSUME1_OR_RETURN(uint8_t, payload_type_byte, input);
CONSUME1_OR_RETURN(std::uint8_t, payload_type_byte, input);
int payload_type = (payload_type_byte % 2 == 0) ? RTP_TYPE_AUDIO : RTP_TYPE_VIDEO;
struct RtpSessionDeleter {
Logger *l;
void operator()(RTPSession *s) { rtp_kill(l, s); }
Logger *_Nonnull l;
void operator()(RTPSession *_Nullable s) { rtp_kill(l, s); }
};
std::unique_ptr<RTPSession, RtpSessionDeleter> session(
rtp_new(log.get(), payload_type, mono_time.get(), mock_send_packet, &sd, nullptr, nullptr,
@@ -57,7 +62,7 @@ void fuzz_rtp_receive(Fuzz_Data &input)
RtpSessionDeleter{log.get()});
while (!input.empty()) {
CONSUME1_OR_RETURN(uint16_t, len, input);
CONSUME1_OR_RETURN(std::uint16_t, len, input);
if (input.size() < len) {
len = input.size();
@@ -67,16 +72,16 @@ void fuzz_rtp_receive(Fuzz_Data &input)
break;
}
const uint8_t *pkt_data = input.consume(__func__, len);
const std::uint8_t *pkt_data = input.consume(__func__, len);
rtp_receive_packet(session.get(), pkt_data, len);
}
}
} // namespace
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size);
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size)
{
Fuzz_Data input(data, size);
fuzz_rtp_receive(input);

View File

@@ -3,9 +3,14 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/net_crypto.h"
@@ -17,45 +22,47 @@ struct MockSessionData {
MockSessionData();
~MockSessionData();
std::vector<std::vector<uint8_t>> sent_packets;
std::vector<std::vector<uint8_t>> received_frames;
std::vector<uint16_t> received_frame_lengths;
std::vector<uint32_t> received_32bit_lengths;
std::vector<uint32_t> received_full_lengths;
std::vector<uint16_t> received_sequnums;
std::vector<uint8_t> received_pts;
std::vector<uint64_t> received_flags;
std::vector<std::vector<std::uint8_t>> sent_packets;
std::vector<std::vector<std::uint8_t>> received_frames;
std::vector<std::uint16_t> received_frame_lengths;
std::vector<std::uint32_t> received_32bit_lengths;
std::vector<std::uint32_t> received_full_lengths;
std::vector<std::uint16_t> received_sequnums;
std::vector<std::uint8_t> received_pts;
std::vector<std::uint64_t> received_flags;
uint32_t total_bytes_received = 0;
uint32_t total_bytes_lost = 0;
std::uint32_t total_bytes_received = 0;
std::uint32_t total_bytes_lost = 0;
};
MockSessionData::MockSessionData() = default;
MockSessionData::~MockSessionData() = default;
static int mock_send_packet(void *user_data, const uint8_t *data, uint16_t length)
static int mock_send_packet(
void *_Nullable user_data, const std::uint8_t *_Nonnull data, std::uint16_t length)
{
auto *sd = static_cast<MockSessionData *>(user_data);
sd->sent_packets.emplace_back(data, data + length);
return 0;
}
static int mock_m_cb(const Mono_Time * /*mono_time*/, void *cs, RTPMessage *msg)
static int mock_m_cb(
const Mono_Time *_Nonnull /*mono_time*/, void *_Nullable cs, RTPMessage *_Nonnull msg)
{
auto *sd = static_cast<MockSessionData *>(cs);
sd->received_pts.push_back(rtp_message_pt(msg));
sd->received_flags.push_back(rtp_message_flags(msg));
const uint8_t *data = rtp_message_data(msg);
uint32_t len = rtp_message_len(msg);
uint32_t full_len = rtp_message_data_length_full(msg);
const std::uint8_t *_Nonnull data = rtp_message_data(msg);
std::uint32_t len = rtp_message_len(msg);
std::uint32_t full_len = rtp_message_data_length_full(msg);
// If full_len is not set (old protocol), use len
uint32_t actual_len = (full_len > 0) ? full_len : len;
std::uint32_t actual_len = (full_len > 0) ? full_len : len;
sd->received_frames.emplace_back(data, data + actual_len);
sd->received_frame_lengths.push_back(static_cast<uint16_t>(len));
sd->received_frame_lengths.push_back(static_cast<std::uint16_t>(len));
sd->received_32bit_lengths.push_back(len);
sd->received_full_lengths.push_back(full_len);
sd->received_sequnums.push_back(rtp_message_sequnum(msg));
@@ -64,13 +71,13 @@ static int mock_m_cb(const Mono_Time * /*mono_time*/, void *cs, RTPMessage *msg)
return 0;
}
static void mock_add_recv(void *user_data, uint32_t bytes)
static void mock_add_recv(void *_Nullable user_data, std::uint32_t bytes)
{
auto *sd = static_cast<MockSessionData *>(user_data);
sd->total_bytes_received += bytes;
}
static void mock_add_lost(void *user_data, uint32_t bytes)
static void mock_add_lost(void *_Nullable user_data, std::uint32_t bytes)
{
auto *sd = static_cast<MockSessionData *>(user_data);
sd->total_bytes_lost += bytes;
@@ -88,13 +95,13 @@ protected:
void TearDown() override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
mono_time_free(mem, mono_time);
logger_kill(log);
}
Logger *log;
Mono_Time *mono_time;
Logger *_Nullable log;
Mono_Time *_Nullable mono_time;
};
TEST_F(RtpPublicTest, BasicAudioSendReceive)
@@ -104,7 +111,7 @@ TEST_F(RtpPublicTest, BasicAudioSendReceive)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
ASSERT_NE(session, nullptr);
uint8_t data[] = "Hello RTP";
std::uint8_t data[] = "Hello RTP";
rtp_send_data(log, session, data, sizeof(data), false);
ASSERT_EQ(sd.sent_packets.size(), 1);
@@ -128,9 +135,9 @@ TEST_F(RtpPublicTest, LargeVideoFrameFragmentation)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Frame larger than MAX_CRYPTO_DATA_SIZE
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<uint8_t> data(frame_size);
for (uint32_t i = 0; i < frame_size; ++i)
const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<std::uint8_t> data(frame_size);
for (std::uint32_t i = 0; i < frame_size; ++i)
data[i] = i & 0xFF;
rtp_send_data(log, session, data.data(), frame_size, true);
@@ -158,8 +165,8 @@ TEST_F(RtpPublicTest, OutOfOrderVideoPackets)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd,
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> data(frame_size, 0x55);
const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<std::uint8_t> data(frame_size, 0x55);
rtp_send_data(log, session, data.data(), frame_size, false);
ASSERT_EQ(sd.sent_packets.size(), 2);
@@ -183,15 +190,15 @@ TEST_F(RtpPublicTest, HandlingInvalidPackets)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Packet too short to even contain the Tox packet ID
uint8_t empty[1];
std::uint8_t empty[1];
rtp_receive_packet(session, empty, 0);
// Packet too short (less than RTP_HEADER_SIZE + 1)
uint8_t short_pkt[10] = {RTP_TYPE_AUDIO};
std::uint8_t short_pkt[10] = {RTP_TYPE_AUDIO};
rtp_receive_packet(session, short_pkt, sizeof(short_pkt));
// Wrong packet ID (Tox level)
uint8_t wrong_id[RTP_HEADER_SIZE + 10];
std::uint8_t wrong_id[RTP_HEADER_SIZE + 10];
std::memset(wrong_id, 0, sizeof(wrong_id));
wrong_id[0] = RTP_TYPE_VIDEO; // Session expects AUDIO
rtp_receive_packet(session, wrong_id, sizeof(wrong_id));
@@ -239,8 +246,8 @@ TEST_F(RtpPublicTest, LargeAudioFragmentationOldProtocol)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
// Audio doesn't use RTP_LARGE_FRAME, so it uses the old 16-bit offset/length fields
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<uint8_t> data(frame_size, 0x44);
const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 500;
std::vector<std::uint8_t> data(frame_size, 0x44);
rtp_send_data(log, session, data.data(), frame_size, false);
@@ -263,17 +270,17 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
struct TimeMock {
uint64_t t;
std::uint64_t t;
} tm = {1000};
auto time_cb = [](void *ud) -> uint64_t { return static_cast<TimeMock *>(ud)->t; };
auto time_cb = [](void *ud) -> std::uint64_t { return static_cast<TimeMock *>(ud)->t; };
mono_time_set_current_time_callback(mono_time, time_cb, &tm);
mono_time_update(mono_time);
// USED_RTP_WORKBUFFER_COUNT is 3.
// 1. Start a keyframe (frame 0) but don't finish it.
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> kf_data(frame_size, 0x11);
const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<std::uint8_t> kf_data(frame_size, 0x11);
rtp_send_data(log, session, kf_data.data(), frame_size, true);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear();
@@ -282,7 +289,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
for (int i = 0; i < 2; ++i) {
tm.t += 1;
mono_time_update(mono_time);
std::vector<uint8_t> if_data(frame_size, 0x20 + i);
std::vector<std::uint8_t> if_data(frame_size, 0x20 + i);
rtp_send_data(log, session, if_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear();
@@ -297,7 +304,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
// The new IF should be DROPPED because there's no space and slot 0 is a protected KF.
tm.t += 1;
mono_time_update(mono_time);
std::vector<uint8_t> if3_data(frame_size, 0x33);
std::vector<std::uint8_t> if3_data(frame_size, 0x33);
rtp_send_data(log, session, if3_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
sd.sent_packets.clear();
@@ -311,7 +318,7 @@ TEST_F(RtpPublicTest, WorkBufferEvictionAndKeyframePreservation)
// 5. Start another frame (frame 4).
// Now the old KF should be evicted and processed (sent to callback), making room.
std::vector<uint8_t> if4_data(frame_size, 0x44);
std::vector<std::uint8_t> if4_data(frame_size, 0x44);
rtp_send_data(log, session, if4_data.data(), frame_size, false);
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
@@ -328,7 +335,7 @@ TEST_F(RtpPublicTest, BwcReporting)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd,
mock_add_recv, mock_add_lost, &sd, &sd, mock_m_cb);
uint8_t data[] = "test";
std::uint8_t data[] = "test";
// DISMISS_FIRST_LOST_VIDEO_PACKET_COUNT is 10.
// Packets 1-9 are dismissed. Packet 10 is reported.
for (int i = 0; i < 10; ++i) {
@@ -351,8 +358,8 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
nullptr, nullptr, &sd, mock_m_cb);
// 1. Multipart message interrupted by a newer message.
const uint32_t large_size = 5000;
std::vector<uint8_t> data(large_size, 0xAA);
const std::uint32_t large_size = 5000;
std::vector<std::uint8_t> data(large_size, 0xAA);
rtp_send_data(log, session, data.data(), large_size, false);
ASSERT_GE(sd.sent_packets.size(), 2);
@@ -361,7 +368,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
EXPECT_EQ(sd.received_frames.size(), 0);
// Send a second message (newer)
std::vector<uint8_t> data2 = {0x1, 0x2, 0x3};
std::vector<std::uint8_t> data2 = {0x1, 0x2, 0x3};
rtp_send_data(log, session, data2.data(), data2.size(), false);
// The second message is the last one in sent_packets.
rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size());
@@ -370,7 +377,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
ASSERT_EQ(sd.received_frames.size(), 2);
EXPECT_LT(sd.received_frame_lengths[0], large_size);
EXPECT_EQ(sd.received_pts[0], RTP_TYPE_AUDIO % 128);
EXPECT_EQ(sd.received_frame_lengths[1], static_cast<uint16_t>(data2.size()));
EXPECT_EQ(sd.received_frame_lengths[1], static_cast<std::uint16_t>(data2.size()));
// 2. Discarding old message part
sd.received_frames.clear();
@@ -379,7 +386,7 @@ TEST_F(RtpPublicTest, OldProtocolEdgeCases)
sd.received_pts.clear();
// Send a very new message.
std::vector<uint8_t> data3 = {0xDE, 0xAD};
std::vector<std::uint8_t> data3 = {0xDE, 0xAD};
rtp_send_data(log, session, data3.data(), data3.size(), false);
rtp_receive_packet(session, sd.sent_packets.back().data(), sd.sent_packets.back().size());
EXPECT_EQ(sd.received_frames.size(), 1);
@@ -399,13 +406,19 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
nullptr, nullptr, &sd, mock_m_cb);
// Get a valid packet to start with
uint8_t data[] = "test";
std::uint8_t data[] = "test";
rtp_send_data(log, session, data, sizeof(data), false);
std::vector<uint8_t> valid_pkt = sd.sent_packets[0];
ASSERT_FALSE(sd.sent_packets.empty());
if (sd.sent_packets.empty())
return;
const std::vector<std::uint8_t> &src_pkt = sd.sent_packets[0];
if (src_pkt.size() > 65536)
return;
std::vector<std::uint8_t> valid_pkt(src_pkt.begin(), src_pkt.end());
sd.sent_packets.clear();
// 1. RTPHeader packet type and Tox protocol packet type do not agree
std::vector<uint8_t> bad_pkt_1 = valid_pkt;
std::vector<std::uint8_t> bad_pkt_1 = valid_pkt;
bad_pkt_1[0] = RTP_TYPE_AUDIO; // Tox ID says AUDIO, but header (byte 2) still says VIDEO
rtp_receive_packet(session, bad_pkt_1.data(), bad_pkt_1.size());
EXPECT_EQ(sd.received_frames.size(), 0);
@@ -421,7 +434,7 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
// 3. Invalid video packet: offset >= length
// From rtp.c, offset_full is at byte 20 and data_length_full at byte 24 of the RTP header.
// The RTP header starts at index 1 of the packet.
std::vector<uint8_t> bad_pkt_3 = valid_pkt;
std::vector<std::uint8_t> bad_pkt_3 = valid_pkt;
// Set offset (bytes 21-24) to be equal to length (bytes 25-28)
// For a small packet, both are usually 0 and sizeof(data) respectively.
// Let's just make offset very large.
@@ -437,10 +450,16 @@ TEST_F(RtpPublicTest, MoreInvalidPackets)
nullptr, nullptr, nullptr, &sd, mock_m_cb);
rtp_send_data(log, session_audio2, data, sizeof(data), false);
std::vector<uint8_t> audio_pkt = sd.sent_packets[0];
ASSERT_FALSE(sd.sent_packets.empty());
if (sd.sent_packets.empty())
return;
const std::vector<std::uint8_t> &src_audio = sd.sent_packets[0];
if (src_audio.size() > 65536)
return;
std::vector<std::uint8_t> audio_pkt(src_audio.begin(), src_audio.end());
sd.sent_packets.clear();
std::vector<uint8_t> bad_pkt_4 = audio_pkt;
std::vector<std::uint8_t> bad_pkt_4 = audio_pkt;
// Set offset_lower (byte 1 + 76) > data_length_lower (byte 1 + 78)
bad_pkt_4[1 + 76] = 0x01; // offset = 256
bad_pkt_4[1 + 77] = 0x00;
@@ -461,20 +480,20 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
nullptr, nullptr, &sd, mock_m_cb);
// Use a large frame size to force fragmentation and keep slots occupied
const uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<uint8_t> data(frame_size, 0);
const std::uint32_t frame_size = MAX_CRYPTO_DATA_SIZE + 100;
std::vector<std::uint8_t> data(frame_size, 0);
// Advancing time for subsequent frames
struct TimeMock {
uint64_t t;
std::uint64_t t;
} tm = {1000};
auto time_cb = [](void *ud) -> uint64_t { return static_cast<TimeMock *>(ud)->t; };
auto time_cb = [](void *ud) -> std::uint64_t { return static_cast<TimeMock *>(ud)->t; };
mono_time_set_current_time_callback(mono_time, time_cb, &tm);
mono_time_update(mono_time);
// 1. Packet too old for work buffer
rtp_send_data(log, session, data.data(), frame_size, false); // Time 1000ms
std::vector<uint8_t> old_pkt = sd.sent_packets[0];
std::vector<std::uint8_t> old_pkt = sd.sent_packets[0];
// Receive only first part to keep slot occupied
rtp_receive_packet(session, sd.sent_packets[0].data(), sd.sent_packets[0].size());
EXPECT_EQ(sd.received_frames.size(), 0);
@@ -501,7 +520,7 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
nullptr, &sd, mock_m_cb);
// Fill slot 0 with an incomplete Keyframe
std::vector<uint8_t> kf_data(frame_size, 0x11);
std::vector<std::uint8_t> kf_data(frame_size, 0x11);
tm.t = 3000;
mono_time_update(mono_time);
rtp_send_data(log, session, kf_data.data(), frame_size, true);
@@ -510,7 +529,7 @@ TEST_F(RtpPublicTest, VideoJitterBufferEdgeCases)
sd.sent_packets.clear();
// Now send a complete Interframe
std::vector<uint8_t> if_data(10, 0x22);
std::vector<std::uint8_t> if_data(10, 0x22);
tm.t += 1;
mono_time_update(mono_time);
rtp_send_data(log, session, if_data.data(), if_data.size(), false);
@@ -531,9 +550,9 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
// 1. Packet claiming a smaller length than its payload.
// This triggers the condition that previously caused a DoS crash via
// an assertion failure in new_message().
uint8_t data[10] = {0};
std::uint8_t data[10] = {0};
rtp_send_data(log, session, data, sizeof(data), false);
std::vector<uint8_t> pkt = sd.sent_packets[0];
std::vector<std::uint8_t> pkt = sd.sent_packets[0];
sd.sent_packets.clear();
// Modify data_length_lower (byte 1 + 78) to be 2, while payload is 10.
@@ -545,8 +564,8 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
EXPECT_EQ(sd.received_frames.size(), 0);
// 2. Corruption check for an EXISTING multipart message.
const uint32_t multipart_size = 5000;
std::vector<uint8_t> multipart_data(multipart_size, 0xBB);
const std::uint32_t multipart_size = 5000;
std::vector<std::uint8_t> multipart_data(multipart_size, 0xBB);
rtp_send_data(log, session, multipart_data.data(), multipart_size, false);
// Receive the first part
@@ -554,7 +573,7 @@ TEST_F(RtpPublicTest, OldProtocolCorruption)
EXPECT_EQ(sd.received_frames.size(), 0);
// Now receive a corrupted second part that claims a weird offset
std::vector<uint8_t> corrupted_part = sd.sent_packets[1];
std::vector<std::uint8_t> corrupted_part = sd.sent_packets[1];
// offset_lower is at byte 76. Set it beyond data_length_lower.
corrupted_part[1 + 76] = 0xFF;
corrupted_part[1 + 77] = 0xFF;
@@ -572,11 +591,11 @@ TEST_F(RtpPublicTest, HugeVideoFrameInternalLength)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb);
// Frame larger than 64KB (uint16_t max)
const uint32_t huge_frame_size = 65540;
std::vector<uint8_t> data(huge_frame_size);
for (uint32_t i = 0; i < huge_frame_size; ++i) {
data[i] = static_cast<uint8_t>(i & 0xFF);
// Frame larger than 64KB (std::uint16_t max)
const std::uint32_t huge_frame_size = 65540;
std::vector<std::uint8_t> data(huge_frame_size);
for (std::uint32_t i = 0; i < huge_frame_size; ++i) {
data[i] = static_cast<std::uint8_t>(i & 0xFF);
}
rtp_send_data(log, session, data.data(), huge_frame_size, false);
@@ -592,7 +611,7 @@ TEST_F(RtpPublicTest, HugeVideoFrameInternalLength)
ASSERT_EQ(sd.received_frames.size(), 1);
// This verifies that the internal 32-bit length is working correctly.
// We cast huge_frame_size to 16-bit to show what it would have been if it truncated.
EXPECT_NE(static_cast<uint16_t>(sd.received_32bit_lengths[0]), huge_frame_size);
EXPECT_NE(static_cast<std::uint16_t>(sd.received_32bit_lengths[0]), huge_frame_size);
EXPECT_EQ(sd.received_32bit_lengths[0], huge_frame_size);
EXPECT_EQ(sd.received_full_lengths[0], huge_frame_size);
EXPECT_EQ(sd.received_frames[0].size(), huge_frame_size);
@@ -609,10 +628,10 @@ TEST_F(RtpPublicTest, HeapBufferOverflowRaw)
// Manually construct a malicious packet.
// 1 byte ID + 80 bytes Header + 200 bytes Payload
const size_t header_size = 80;
const size_t payload_size = 200;
const size_t total_size = 1 + header_size + payload_size;
std::vector<uint8_t> pkt(total_size, 0);
const std::size_t header_size = 80;
const std::size_t payload_size = 200;
const std::size_t total_size = 1 + header_size + payload_size;
std::vector<std::uint8_t> pkt(total_size, 0);
// 0: Packet ID
pkt[0] = RTP_TYPE_VIDEO;
@@ -650,21 +669,21 @@ TEST_F(RtpPublicTest, HeapBufferOverflow)
nullptr, nullptr, &sd, mock_m_cb);
// Common parameters
uint16_t sequnum = 100;
uint32_t timestamp = 12345;
uint32_t ssrc = 0x11223344;
std::uint16_t sequnum = 100;
std::uint32_t timestamp = 12345;
std::uint32_t ssrc = 0x11223344;
// --- Packet 1: Small allocation ---
// data_length_full = 10
// offset_full = 0
// payload_len = 5
{
uint8_t packet[100];
std::uint8_t packet[100];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO; // Tox Packet ID
// RTP Header
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
// Byte 0: VE=2 (0x80)
h[0] = 0x80;
// Byte 1: PT=0x41
@@ -720,11 +739,11 @@ TEST_F(RtpPublicTest, HeapBufferOverflow)
// Memcpy to buf->data + 10. Buf was allocated with size 10. Writing 100
// bytes to offset 10 -> Overflow.
{
uint8_t packet[200];
std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO;
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
h[0] = 0x80;
h[1] = 0x41;
h[2] = (sequnum >> 8) & 0xFF;
@@ -769,15 +788,15 @@ TEST_F(RtpPublicTest, AudioHeapBufferOverflow)
RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 100;
uint32_t timestamp = 12345;
uint32_t ssrc = 0x11223344;
std::uint16_t sequnum = 100;
std::uint32_t timestamp = 12345;
std::uint32_t ssrc = 0x11223344;
uint8_t packet[200];
std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
h[0] = 0x80;
h[1] = 0x40; // 64 (Audio)
h[2] = (sequnum >> 8) & 0xFF;
@@ -816,21 +835,21 @@ TEST_F(RtpPublicTest, HeapBufferOverflowMultipartAudio)
RTPSession *session = rtp_new(log, RTP_TYPE_AUDIO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 200;
uint32_t timestamp = 67890;
uint32_t ssrc = 0x55667788;
uint16_t total_len = 100;
std::uint16_t sequnum = 200;
std::uint32_t timestamp = 67890;
std::uint32_t ssrc = 0x55667788;
std::uint16_t total_len = 100;
// --- Packet 1: Allocate buffer ---
// data_length_lower = 100
// offset_lower = 0
// payload_len = 10
{
uint8_t packet[200];
std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
h[0] = 0x80;
h[1] = 0x40; // Audio
h[2] = (sequnum >> 8) & 0xFF;
@@ -865,11 +884,11 @@ TEST_F(RtpPublicTest, HeapBufferOverflowMultipartAudio)
// Check 2: total (100) > offset (95). Safe.
// Write: 95 + 10 = 105. Overflow.
{
uint8_t packet[200];
std::uint8_t packet[200];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_AUDIO;
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
h[0] = 0x80;
h[1] = 0x40;
h[2] = (sequnum >> 8) & 0xFF;
@@ -905,18 +924,18 @@ TEST_F(RtpPublicTest, HeapBufferOverflowLogRead)
RTPSession *session = rtp_new(log, RTP_TYPE_VIDEO, mono_time, mock_send_packet, &sd, nullptr,
nullptr, nullptr, &sd, mock_m_cb);
uint16_t sequnum = 123;
uint32_t timestamp = 99999;
uint32_t ssrc = 0x88776655;
std::uint16_t sequnum = 123;
std::uint32_t timestamp = 99999;
std::uint32_t ssrc = 0x88776655;
// Packet with data_length_full = 1.
// The logger tries to read data[0] and data[1].
// data[1] will be out of bounds if only 1 byte is allocated.
uint8_t packet[100];
std::uint8_t packet[100];
std::memset(packet, 0, sizeof(packet));
packet[0] = RTP_TYPE_VIDEO;
uint8_t *h = &packet[1];
std::uint8_t *h = &packet[1];
h[0] = 0x80;
h[1] = 0x41; // Video
h[2] = (sequnum >> 8) & 0xFF;

View File

@@ -86,7 +86,7 @@ typedef struct DecodeTimeStats {
} DecodeTimeStats;
struct ToxAV {
const struct Tox_Memory *mem;
const struct Memory *mem;
Logger *log;
Tox *tox;
MSISession *msi;
@@ -95,8 +95,7 @@ struct ToxAV {
ToxAVCall **calls;
uint32_t calls_tail;
uint32_t calls_head;
pthread_mutex_t mutex[1];
pthread_mutex_t *mutable_mutex;
pthread_mutex_t *mutex;
/* Call callback */
toxav_call_cb *ccb;
@@ -345,11 +344,17 @@ ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error)
goto RETURN;
}
if (create_recursive_mutex(av->mutex) != 0) {
av->mutex = (pthread_mutex_t *)mem_alloc(tox->sys.mem, sizeof(pthread_mutex_t));
if (av->mutex == nullptr) {
rc = TOXAV_ERR_NEW_MALLOC;
goto RETURN;
}
if (create_recursive_mutex(av->mutex) != 0) {
mem_delete(tox->sys.mem, av->mutex);
rc = TOXAV_ERR_NEW_MALLOC;
goto RETURN;
}
av->mutable_mutex = av->mutex;
av->mem = tox->sys.mem;
av->log = tox->m->log;
@@ -365,7 +370,15 @@ ToxAV *toxav_new(Tox *tox, Toxav_Err_New *error)
av->toxav_mono_time = mono_time_new(tox->sys.mem, nullptr, nullptr);
if (av->msi == nullptr) {
tox_callback_friend_lossy_packet_per_pktid(av->tox, nullptr, RTP_TYPE_AUDIO);
tox_callback_friend_lossy_packet_per_pktid(av->tox, nullptr, RTP_TYPE_VIDEO);
tox_callback_friend_lossy_packet_per_pktid(av->tox, nullptr, BWC_PACKET_ID);
tox_callback_friend_lossless_packet_per_pktid(av->tox, nullptr, PACKET_ID_MSI);
mono_time_free(tox->sys.mem, av->toxav_mono_time);
pthread_mutex_destroy(av->mutex);
mem_delete(tox->sys.mem, av->mutex);
rc = TOXAV_ERR_NEW_MALLOC;
goto RETURN;
}
@@ -431,6 +444,7 @@ void toxav_kill(ToxAV *av)
pthread_mutex_unlock(av->mutex);
pthread_mutex_destroy(av->mutex);
mem_delete(av->tox->sys.mem, av->mutex);
// set ToxAV object to NULL in toxcore, to signal ToxAV has been shutdown
tox_set_av_object(av->tox, nullptr);
@@ -441,9 +455,9 @@ void toxav_kill(ToxAV *av)
Tox *toxav_get_tox(const ToxAV *av)
{
Tox *tox;
pthread_mutex_lock(av->mutable_mutex);
pthread_mutex_lock(av->mutex);
tox = av->tox;
pthread_mutex_unlock(av->mutable_mutex);
pthread_mutex_unlock(av->mutex);
return tox;
}
@@ -465,15 +479,15 @@ uint32_t toxav_iteration_interval(const ToxAV *av)
/**
* @brief calc_interval Calculates the needed iteration interval based on previous decode times
* @param av ToxAV struct to work on
* @param mono_time Mono_Time struct to work on
* @param stats Statistics to update
* @param frame_time the duration of the current frame in ms
* @param start_time the timestamp when decoding of this frame started
*/
static void calc_interval(const ToxAV *_Nonnull av, DecodeTimeStats *_Nonnull stats, int32_t frame_time, uint64_t start_time)
static void calc_interval(const Mono_Time *_Nonnull mono_time, DecodeTimeStats *_Nonnull stats, int32_t frame_time, uint64_t start_time)
{
stats->interval = frame_time < stats->average ? 0 : (frame_time - stats->average);
stats->total += current_time_monotonic(av->toxav_mono_time) - start_time;
stats->total += current_time_monotonic(mono_time) - start_time;
if (++stats->count == 3) {
/* NOTE: Magic Offset for precision */
@@ -497,7 +511,8 @@ static void iterate_common(ToxAV *_Nonnull av, bool audio)
return;
}
const uint64_t start = current_time_monotonic(av->toxav_mono_time);
const Mono_Time *mono_time = av->toxav_mono_time;
const uint64_t start = current_time_monotonic(mono_time);
int32_t frame_time = IDLE_ITERATION_INTERVAL_MS;
for (ToxAVCall *i = av->calls[av->calls_head]; i != nullptr; i = i->next) {
@@ -547,7 +562,7 @@ static void iterate_common(ToxAV *_Nonnull av, bool audio)
}
DecodeTimeStats *stats = audio ? &av->audio_stats : &av->video_stats;
calc_interval(av, stats, frame_time, start);
calc_interval(mono_time, stats, frame_time, start);
pthread_mutex_unlock(av->mutex);
}
@@ -1324,6 +1339,16 @@ static int callback_invite(void *_Nonnull object, MSICall *_Nonnull call)
return 0;
}
static void handle_call_error(ToxAV *toxav, MSICall *call)
{
invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR);
if (call->user_data != nullptr) {
call_kill_transmission((ToxAVCall *)call->user_data);
call_remove((ToxAVCall *)call->user_data);
}
}
static int callback_start(void *_Nonnull object, MSICall *_Nonnull call)
{
ToxAV *toxav = (ToxAV *)object;
@@ -1338,13 +1363,13 @@ static int callback_start(void *_Nonnull object, MSICall *_Nonnull call)
}
if (!call_prepare_transmission(av_call)) {
callback_error(toxav, call);
handle_call_error(toxav, call);
pthread_mutex_unlock(toxav->mutex);
return -1;
}
if (!invoke_call_state_callback(toxav, call->friend_number, call->peer_capabilities)) {
callback_error(toxav, call);
handle_call_error(toxav, call);
pthread_mutex_unlock(toxav->mutex);
return -1;
}
@@ -1374,12 +1399,7 @@ static int callback_error(void *_Nonnull object, MSICall *_Nonnull call)
ToxAV *toxav = (ToxAV *)object;
pthread_mutex_lock(toxav->mutex);
invoke_call_state_callback(toxav, call->friend_number, TOXAV_FRIEND_CALL_STATE_ERROR);
if (call->user_data != nullptr) {
call_kill_transmission((ToxAVCall *)call->user_data);
call_remove((ToxAVCall *)call->user_data);
}
handle_call_error(toxav, call);
pthread_mutex_unlock(toxav->mutex);
return 0;
@@ -1637,7 +1657,7 @@ static bool call_prepare_transmission(ToxAVCall *_Nullable call)
{ /* Prepare video */
call->vcb = av->vcb;
call->vcb_user_data = av->vcb_user_data;
call->video = vc_new(av->log, av->toxav_mono_time, call->friend_number, handle_video_frame, call);
call->video = vc_new(av->mem, av->log, av->toxav_mono_time, call->friend_number, handle_video_frame, call);
if (call->video == nullptr) {
LOGGER_ERROR(av->log, "Failed to create video codec session");

View File

@@ -158,7 +158,7 @@ Tox *toxav_get_tox(const ToxAV *av);
/**
* Returns the interval in milliseconds when the next toxav_iterate call should
* be. If no call is active at the moment, this function returns 200.
* be. If no call is active at the moment, this function returns 1000.
* This function MUST be called from the same thread as toxav_iterate.
*/
uint32_t toxav_iteration_interval(const ToxAV *av);
@@ -178,7 +178,7 @@ void toxav_iterate(ToxAV *av);
/**
* Returns the interval in milliseconds when the next toxav_audio_iterate call
* should be. If no call is active at the moment, this function returns 200.
* should be. If no call is active at the moment, this function returns 1000.
* This function MUST be called from the same thread as toxav_audio_iterate.
*/
uint32_t toxav_audio_iteration_interval(const ToxAV *av);
@@ -194,7 +194,7 @@ void toxav_audio_iterate(ToxAV *av);
/**
* Returns the interval in milliseconds when the next toxav_video_iterate call
* should be. If no call is active at the moment, this function returns 200.
* should be. If no call is active at the moment, this function returns 1000.
* This function MUST be called from the same thread as toxav_video_iterate.
*/
uint32_t toxav_video_iteration_interval(const ToxAV *av);

View File

@@ -28,6 +28,9 @@ struct VCSession {
vpx_codec_ctx_t encoder[1];
uint32_t frame_counter;
vpx_image_t raw_encoder_frame;
bool raw_encoder_frame_allocated;
/* decoding */
vpx_codec_ctx_t decoder[1];
struct RingBuffer *vbuf_raw; /* Un-decoded data */
@@ -41,9 +44,9 @@ struct VCSession {
vc_video_receive_frame_cb *vcb;
void *user_data;
pthread_mutex_t queue_mutex[1];
pthread_mutex_t *mutable_queue_mutex;
pthread_mutex_t *queue_mutex;
const Logger *log;
const Memory *mem;
vpx_codec_iter_t iter;
};
@@ -159,7 +162,7 @@ static void vc_init_encoder_cfg(const Logger *_Nonnull log, vpx_codec_enc_cfg_t
#endif /* 0 */
}
VCSession *vc_new(const Logger *log, const Mono_Time *mono_time, uint32_t friend_number,
VCSession *vc_new(const Memory *mem, const Logger *log, const Mono_Time *mono_time, uint32_t friend_number,
vc_video_receive_frame_cb *cb, void *user_data)
{
if (mono_time == nullptr) {
@@ -174,12 +177,21 @@ VCSession *vc_new(const Logger *log, const Mono_Time *mono_time, uint32_t friend
return nullptr;
}
if (create_recursive_mutex(vc->queue_mutex) != 0) {
LOGGER_WARNING(log, "Failed to create recursive mutex!");
vc->mem = mem;
vc->queue_mutex = (pthread_mutex_t *)mem_alloc(mem, sizeof(pthread_mutex_t));
if (vc->queue_mutex == nullptr) {
LOGGER_WARNING(log, "Allocation failed! Application might misbehave!");
free(vc);
return nullptr;
}
if (create_recursive_mutex(vc->queue_mutex) != 0) {
LOGGER_WARNING(log, "Failed to create recursive mutex!");
mem_delete(mem, vc->queue_mutex);
free(vc);
return nullptr;
}
vc->mutable_queue_mutex = vc->queue_mutex;
const int cpu_used_value = VP8E_SET_CPUUSED_VALUE;
@@ -284,6 +296,7 @@ BASE_CLEANUP_1:
vpx_codec_destroy(vc->decoder);
BASE_CLEANUP:
pthread_mutex_destroy(vc->queue_mutex);
mem_delete(vc->mem, vc->queue_mutex);
rb_kill(vc->vbuf_raw);
free(vc);
@@ -296,6 +309,10 @@ void vc_kill(VCSession *vc)
return;
}
if (vc->raw_encoder_frame_allocated) {
vpx_img_free(&vc->raw_encoder_frame);
}
vpx_codec_destroy(vc->encoder);
vpx_codec_destroy(vc->decoder);
void *p;
@@ -306,6 +323,7 @@ void vc_kill(VCSession *vc)
rb_kill(vc->vbuf_raw);
pthread_mutex_destroy(vc->queue_mutex);
mem_delete(vc->mem, vc->queue_mutex);
LOGGER_DEBUG(vc->log, "Terminated video handler: %p", (void *)vc);
free(vc);
}
@@ -493,27 +511,34 @@ int vc_reconfigure_encoder(VCSession *vc, uint32_t bit_rate, uint16_t width, uin
int vc_encode(VCSession *vc, uint16_t width, uint16_t height, const uint8_t *y,
const uint8_t *u, const uint8_t *v, int encode_flags)
{
vpx_image_t img;
if (vc->raw_encoder_frame_allocated && (vc->raw_encoder_frame.d_w != width || vc->raw_encoder_frame.d_h != height)) {
vpx_img_free(&vc->raw_encoder_frame);
vc->raw_encoder_frame_allocated = false;
}
// TODO(Green-Sky): figure out stride_align
// TODO(Green-Sky): check memory alignment?
if (vpx_img_wrap(&img, VPX_IMG_FMT_I420, width, height, 0, (uint8_t *)y) != nullptr) {
vpx_image_t *img = nullptr;
vpx_image_t img_wrapped;
if (vpx_img_wrap(&img_wrapped, VPX_IMG_FMT_I420, width, height, 1, (uint8_t *)y) != nullptr) {
img = &img_wrapped;
// vpx_img_wrap assumes contigues memory, so we fix that
img.planes[VPX_PLANE_U] = (uint8_t *)u;
img.planes[VPX_PLANE_V] = (uint8_t *)v;
img->planes[VPX_PLANE_U] = (uint8_t *)u;
img->planes[VPX_PLANE_V] = (uint8_t *)v;
} else {
// call to wrap failed, falling back to copy
if (vpx_img_alloc(&img, VPX_IMG_FMT_I420, width, height, 0) == nullptr) {
LOGGER_ERROR(vc->log, "Could not allocate image for frame");
return -1;
if (!vc->raw_encoder_frame_allocated) {
if (vpx_img_alloc(&vc->raw_encoder_frame, VPX_IMG_FMT_I420, width, height, 1) == nullptr) {
LOGGER_ERROR(vc->log, "Could not allocate image for frame");
return -1;
}
vc->raw_encoder_frame_allocated = true;
}
/* I420 "It comprises an NxM Y plane followed by (N/2)x(M/2) V and U planes."
* http://fourcc.org/yuv.php#IYUV
*/
memcpy(img.planes[VPX_PLANE_Y], y, (size_t)width * height);
memcpy(img.planes[VPX_PLANE_U], u, ((size_t)width / 2) * (height / 2));
memcpy(img.planes[VPX_PLANE_V], v, ((size_t)width / 2) * (height / 2));
img = &vc->raw_encoder_frame;
memcpy(img->planes[VPX_PLANE_Y], y, (size_t)width * height);
memcpy(img->planes[VPX_PLANE_U], u, ((size_t)width / 2) * (height / 2));
memcpy(img->planes[VPX_PLANE_V], v, ((size_t)width / 2) * (height / 2));
}
int vpx_flags = 0;
@@ -522,11 +547,9 @@ int vc_encode(VCSession *vc, uint16_t width, uint16_t height, const uint8_t *y,
vpx_flags |= VPX_EFLAG_FORCE_KF;
}
const vpx_codec_err_t vrc = vpx_codec_encode(vc->encoder, &img,
const vpx_codec_err_t vrc = vpx_codec_encode(vc->encoder, img,
vc->frame_counter, 1, vpx_flags, VPX_DL_REALTIME);
vpx_img_free(&img);
if (vrc != VPX_CODEC_OK) {
LOGGER_ERROR(vc->log, "Could not encode video frame: %s", vpx_codec_err_to_string(vrc));
return -1;
@@ -558,15 +581,15 @@ int vc_get_cx_data(VCSession *vc, uint8_t **data, uint32_t *size, bool *is_keyfr
uint32_t vc_get_lcfd(const VCSession *vc)
{
uint32_t lcfd;
pthread_mutex_lock(vc->mutable_queue_mutex);
pthread_mutex_lock(vc->queue_mutex);
lcfd = vc->lcfd;
pthread_mutex_unlock(vc->mutable_queue_mutex);
pthread_mutex_unlock(vc->queue_mutex);
return lcfd;
}
pthread_mutex_t *vc_get_queue_mutex(VCSession *vc)
{
return &vc->queue_mutex[0];
return vc->queue_mutex;
}
void vc_increment_frame_counter(VCSession *vc)

View File

@@ -27,7 +27,7 @@ typedef struct VCSession VCSession;
struct RTPMessage;
VCSession *_Nullable vc_new(const Logger *_Nonnull log, const Mono_Time *_Nonnull mono_time, uint32_t friend_number,
VCSession *_Nullable vc_new(const Memory *_Nonnull mem, const Logger *_Nonnull log, const Mono_Time *_Nonnull mono_time, uint32_t friend_number,
vc_video_receive_frame_cb *_Nullable cb, void *_Nullable user_data);
void vc_kill(VCSession *_Nullable vc);
void vc_iterate(VCSession *_Nullable vc);

View File

@@ -4,8 +4,12 @@
#include <benchmark/benchmark.h>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
#include "../toxcore/attributes.h"
#include "../toxcore/logger.h"
#include "../toxcore/mono_time.h"
#include "../toxcore/os_memory.h"
@@ -19,20 +23,20 @@ class VideoBench : public benchmark::Fixture {
public:
void SetUp(const ::benchmark::State &state) override
{
const Memory *mem = os_memory();
const Memory *_Nonnull mem = os_memory();
log = logger_new(mem);
tm.t = 1000;
mono_time = mono_time_new(mem, mock_time_cb, &tm);
vc = vc_new(log, mono_time, 123, nullptr, nullptr);
vc = vc_new(mem, log, mono_time, 123, nullptr, nullptr);
width = static_cast<uint16_t>(state.range(0));
height = static_cast<uint16_t>(state.range(1));
width = static_cast<std::uint16_t>(state.range(0));
height = static_cast<std::uint16_t>(state.range(1));
// Use a standard bitrate for benchmarks
vc_reconfigure_encoder(vc, 2000, width, height, -1);
y.resize(static_cast<size_t>(width) * height);
u.resize((static_cast<size_t>(width) / 2) * (static_cast<size_t>(height) / 2));
v.resize((static_cast<size_t>(width) / 2) * (static_cast<size_t>(height) / 2));
y.resize(static_cast<std::size_t>(width) * height);
u.resize((static_cast<std::size_t>(width) / 2) * (static_cast<std::size_t>(height) / 2));
v.resize((static_cast<std::size_t>(width) / 2) * (static_cast<std::size_t>(height) / 2));
rtp_mock.capture_packets = false; // Disable capturing for benchmarks
rtp_mock.auto_forward = true;
@@ -58,13 +62,13 @@ public:
}
}
Logger *log = nullptr;
Mono_Time *mono_time = nullptr;
Logger *_Nullable log = nullptr;
Mono_Time *_Nullable mono_time = nullptr;
MockTime tm;
VCSession *vc = nullptr;
VCSession *_Nullable vc = nullptr;
RtpMock rtp_mock;
uint16_t width = 0, height = 0;
std::vector<uint8_t> y, u, v;
std::uint16_t width = 0, height = 0;
std::vector<std::uint8_t> y, u, v;
};
// Benchmark encoding a sequence of frames.
@@ -74,11 +78,12 @@ BENCHMARK_DEFINE_F(VideoBench, EncodeSequence)(benchmark::State &state)
int frame_index = 0;
// Pre-fill frames to avoid measuring fill_frame time
const int num_prefilled = 100;
std::vector<std::vector<uint8_t>> ys(num_prefilled, std::vector<uint8_t>(width * height));
std::vector<std::vector<uint8_t>> us(
num_prefilled, std::vector<uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<uint8_t>> vs(
num_prefilled, std::vector<uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<std::uint8_t>> ys(
num_prefilled, std::vector<std::uint8_t>(width * height));
std::vector<std::vector<std::uint8_t>> us(
num_prefilled, std::vector<std::uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<std::uint8_t>> vs(
num_prefilled, std::vector<std::uint8_t>((width / 2) * (height / 2)));
for (int i = 0; i < num_prefilled; ++i) {
fill_video_frame(width, height, i, ys[i], us[i], vs[i]);
}
@@ -90,8 +95,8 @@ BENCHMARK_DEFINE_F(VideoBench, EncodeSequence)(benchmark::State &state)
vc_encode(vc, width, height, ys[idx].data(), us[idx].data(), vs[idx].data(), flags);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
benchmark::DoNotOptimize(pkt_data);
@@ -112,7 +117,7 @@ BENCHMARK_REGISTER_F(VideoBench, EncodeSequence)
BENCHMARK_DEFINE_F(VideoBench, DecodeSequence)(benchmark::State &state)
{
const int num_frames = 100;
std::vector<std::vector<uint8_t>> encoded_frames(num_frames);
std::vector<std::vector<std::uint8_t>> encoded_frames(num_frames);
std::vector<bool> is_keyframe_list(num_frames);
// Pre-encode
@@ -122,8 +127,8 @@ BENCHMARK_DEFINE_F(VideoBench, DecodeSequence)(benchmark::State &state)
vc_encode(vc, width, height, y.data(), u.data(), v.data(), flags);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_kf;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_kf)) {
encoded_frames[i].insert(encoded_frames[i].end(), pkt_data, pkt_data + pkt_size);
@@ -136,7 +141,7 @@ BENCHMARK_DEFINE_F(VideoBench, DecodeSequence)(benchmark::State &state)
int idx = frame_index % num_frames;
const auto &encoded_data = encoded_frames[idx];
rtp_send_data(log, rtp_mock.recv_session, encoded_data.data(),
static_cast<uint32_t>(encoded_data.size()), is_keyframe_list[idx]);
static_cast<std::uint32_t>(encoded_data.size()), is_keyframe_list[idx]);
vc_iterate(vc);
frame_index++;
}
@@ -153,11 +158,12 @@ BENCHMARK_DEFINE_F(VideoBench, FullSequence)(benchmark::State &state)
{
int frame_index = 0;
const int num_prefilled = 100;
std::vector<std::vector<uint8_t>> ys(num_prefilled, std::vector<uint8_t>(width * height));
std::vector<std::vector<uint8_t>> us(
num_prefilled, std::vector<uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<uint8_t>> vs(
num_prefilled, std::vector<uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<std::uint8_t>> ys(
num_prefilled, std::vector<std::uint8_t>(width * height));
std::vector<std::vector<std::uint8_t>> us(
num_prefilled, std::vector<std::uint8_t>((width / 2) * (height / 2)));
std::vector<std::vector<std::uint8_t>> vs(
num_prefilled, std::vector<std::uint8_t>((width / 2) * (height / 2)));
for (int i = 0; i < num_prefilled; ++i) {
fill_video_frame(width, height, i, ys[i], us[i], vs[i]);
}
@@ -168,17 +174,17 @@ BENCHMARK_DEFINE_F(VideoBench, FullSequence)(benchmark::State &state)
vc_encode(vc, width, height, ys[idx].data(), us[idx].data(), vs[idx].data(), flags);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe = false;
// We need to collect all packets for the frame before sending to decoder
std::vector<uint8_t> frame_data;
std::vector<std::uint8_t> frame_data;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
frame_data.insert(frame_data.end(), pkt_data, pkt_data + pkt_size);
}
rtp_send_data(log, rtp_mock.recv_session, frame_data.data(),
static_cast<uint32_t>(frame_data.size()), is_keyframe);
static_cast<std::uint32_t>(frame_data.size()), is_keyframe);
vc_iterate(vc);
frame_index++;

View File

@@ -3,7 +3,11 @@
#include <gtest/gtest.h>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "../toxcore/logger.h"
@@ -20,7 +24,7 @@ using VideoTest = AvTest;
TEST_F(VideoTest, BasicNewKill)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
vc_kill(vc);
}
@@ -28,7 +32,7 @@ TEST_F(VideoTest, BasicNewKill)
TEST_F(VideoTest, EncodeDecodeLoop)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -38,21 +42,21 @@ TEST_F(VideoTest, EncodeDecodeLoop)
nullptr, nullptr, nullptr, vc, RtpMock::video_cb);
rtp_mock.recv_session = recv_rtp;
uint16_t width = 320;
uint16_t height = 240;
uint32_t bitrate = 500;
std::uint16_t width = 320;
std::uint16_t height = 240;
std::uint32_t bitrate = 500;
ASSERT_EQ(vc_reconfigure_encoder(vc, bitrate, width, height, -1), 0);
std::vector<uint8_t> y(width * height, 128);
std::vector<uint8_t> u((width / 2) * (height / 2), 64);
std::vector<uint8_t> v((width / 2) * (height / 2), 192);
std::vector<std::uint8_t> y(width * height, 128);
std::vector<std::uint8_t> u((width / 2) * (height / 2), 64);
std::vector<std::uint8_t> v((width / 2) * (height / 2), 192);
ASSERT_EQ(vc_encode(vc, width, height, y.data(), u.data(), v.data(), VC_EFLAG_FORCE_KF), 0);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
@@ -75,7 +79,7 @@ TEST_F(VideoTest, EncodeDecodeLoop)
TEST_F(VideoTest, EncodeDecodeSequence)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -85,16 +89,16 @@ TEST_F(VideoTest, EncodeDecodeSequence)
nullptr, nullptr, nullptr, vc, RtpMock::video_cb);
rtp_mock.recv_session = recv_rtp;
uint16_t width = 320;
uint16_t height = 240;
uint32_t bitrate = 2000;
std::uint16_t width = 320;
std::uint16_t height = 240;
std::uint32_t bitrate = 2000;
ASSERT_EQ(vc_reconfigure_encoder(vc, bitrate, width, height, -1), 0);
for (int i = 0; i < 20; ++i) {
std::vector<uint8_t> y(width * height);
std::vector<uint8_t> u((width / 2) * (height / 2));
std::vector<uint8_t> v((width / 2) * (height / 2));
std::vector<std::uint8_t> y(width * height);
std::vector<std::uint8_t> u((width / 2) * (height / 2));
std::vector<std::uint8_t> v((width / 2) * (height / 2));
// Background
std::fill(y.begin(), y.end(), 16);
@@ -116,8 +120,8 @@ TEST_F(VideoTest, EncodeDecodeSequence)
0);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
@@ -142,7 +146,7 @@ TEST_F(VideoTest, EncodeDecodeSequence)
TEST_F(VideoTest, EncodeDecodeResolutionChange)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -152,20 +156,20 @@ TEST_F(VideoTest, EncodeDecodeResolutionChange)
nullptr, nullptr, nullptr, vc, RtpMock::video_cb);
rtp_mock.recv_session = recv_rtp;
uint16_t widths[] = {320, 160, 480};
uint16_t heights[] = {240, 120, 360};
std::uint16_t widths[] = {320, 160, 480};
std::uint16_t heights[] = {240, 120, 360};
for (int res = 0; res < 3; ++res) {
uint16_t width = widths[res];
uint16_t height = heights[res];
std::uint16_t width = widths[res];
std::uint16_t height = heights[res];
ASSERT_EQ(vc_reconfigure_encoder(vc, 2000, width, height, -1), 0);
for (int i = 0; i < 5; ++i) {
std::vector<uint8_t> y(width * height);
std::vector<uint8_t> u((width / 2) * (height / 2));
std::vector<uint8_t> v((width / 2) * (height / 2));
std::vector<std::uint8_t> y(width * height);
std::vector<std::uint8_t> u((width / 2) * (height / 2));
std::vector<std::uint8_t> v((width / 2) * (height / 2));
std::fill(y.begin(), y.end(), static_cast<uint8_t>((res * 50 + i * 10) % 256));
std::fill(y.begin(), y.end(), static_cast<std::uint8_t>((res * 50 + i * 10) % 256));
std::fill(u.begin(), u.end(), 128);
std::fill(v.begin(), v.end(), 128);
@@ -174,8 +178,8 @@ TEST_F(VideoTest, EncodeDecodeResolutionChange)
0);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
@@ -199,15 +203,15 @@ TEST_F(VideoTest, EncodeDecodeResolutionChange)
TEST_F(VideoTest, EncodeDecodeBitrateImpact)
{
uint32_t bitrates[] = {100, 500, 2000};
std::uint32_t bitrates[] = {100, 500, 2000};
double mses[3];
uint16_t width = 320;
uint16_t height = 240;
std::uint16_t width = 320;
std::uint16_t height = 240;
for (int b = 0; b < 3; ++b) {
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -222,12 +226,12 @@ TEST_F(VideoTest, EncodeDecodeBitrateImpact)
double total_mse = 0;
int frames = 10;
for (int i = 0; i < frames; ++i) {
std::vector<uint8_t> y(width * height);
std::vector<uint8_t> u((width / 2) * (height / 2));
std::vector<uint8_t> v((width / 2) * (height / 2));
std::vector<std::uint8_t> y(width * height);
std::vector<std::uint8_t> u((width / 2) * (height / 2));
std::vector<std::uint8_t> v((width / 2) * (height / 2));
for (size_t j = 0; j < y.size(); ++j)
y[j] = static_cast<uint8_t>((j + i * 10) % 256);
for (std::size_t j = 0; j < y.size(); ++j)
y[j] = static_cast<std::uint8_t>((j + i * 10) % 256);
std::fill(u.begin(), u.end(), 128);
std::fill(v.begin(), v.end(), 128);
@@ -236,8 +240,8 @@ TEST_F(VideoTest, EncodeDecodeBitrateImpact)
0);
vc_increment_frame_counter(vc);
uint8_t *pkt_data;
uint32_t pkt_size;
std::uint8_t *pkt_data;
std::uint32_t pkt_size;
bool is_keyframe;
while (vc_get_cx_data(vc, &pkt_data, &pkt_size, &is_keyframe)) {
@@ -260,13 +264,13 @@ TEST_F(VideoTest, EncodeDecodeBitrateImpact)
EXPECT_GT(mses[0], mses[1]);
EXPECT_GT(mses[1], mses[2]);
printf("MSE results: 100kbps: %f, 500kbps: %f, 2000kbps: %f\n", mses[0], mses[1], mses[2]);
std::printf("MSE results: 100kbps: %f, 500kbps: %f, 2000kbps: %f\n", mses[0], mses[1], mses[2]);
}
TEST_F(VideoTest, ReconfigureEncoder)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
// Initial reconfigure
@@ -275,9 +279,9 @@ TEST_F(VideoTest, ReconfigureEncoder)
// Change bitrate and resolution
ASSERT_EQ(vc_reconfigure_encoder(vc, 1000, 640, 480, -1), 0);
std::vector<uint8_t> y(640 * 480, 128);
std::vector<uint8_t> u(320 * 240, 64);
std::vector<uint8_t> v(320 * 240, 192);
std::vector<std::uint8_t> y(640 * 480, 128);
std::vector<std::uint8_t> u(320 * 240, 64);
std::vector<std::uint8_t> v(320 * 240, 192);
ASSERT_EQ(vc_encode(vc, 640, 480, y.data(), u.data(), v.data(), VC_EFLAG_NONE), 0);
@@ -287,7 +291,7 @@ TEST_F(VideoTest, ReconfigureEncoder)
TEST_F(VideoTest, GetLcfd)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
// Default lcfd is 60 in video.c
@@ -299,7 +303,7 @@ TEST_F(VideoTest, GetLcfd)
TEST_F(VideoTest, QueueInvalidMessage)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -310,9 +314,9 @@ TEST_F(VideoTest, QueueInvalidMessage)
&rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb);
rtp_mock.recv_session = video_recv_rtp;
std::vector<uint8_t> dummy_audio(100, 0);
std::vector<std::uint8_t> dummy_audio(100, 0);
int rc = rtp_send_data(
log, audio_rtp, dummy_audio.data(), static_cast<uint32_t>(dummy_audio.size()), false);
log, audio_rtp, dummy_audio.data(), static_cast<std::uint32_t>(dummy_audio.size()), false);
ASSERT_EQ(rc, 0);
// Iterate should NOT trigger callback because payload type was wrong
@@ -327,7 +331,7 @@ TEST_F(VideoTest, QueueInvalidMessage)
TEST_F(VideoTest, ReconfigureOptimizations)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
// 1. Reconfigure with same values (should do nothing)
@@ -346,7 +350,7 @@ TEST_F(VideoTest, ReconfigureOptimizations)
TEST_F(VideoTest, LcfdAndSpecialPackets)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -357,9 +361,9 @@ TEST_F(VideoTest, LcfdAndSpecialPackets)
// 1. Test lcfd update
tm.t += 50; // Advance time by 50ms
mono_time_update(mono_time);
std::vector<uint8_t> dummy_frame(10, 0);
rtp_send_data(
log, video_recv_rtp, dummy_frame.data(), static_cast<uint32_t>(dummy_frame.size()), true);
std::vector<std::uint8_t> dummy_frame(10, 0);
rtp_send_data(log, video_recv_rtp, dummy_frame.data(),
static_cast<std::uint32_t>(dummy_frame.size()), true);
// lcfd should be updated. Initial linfts was set at vc_new (tm.t=1000).
// Now tm.t is 1050. t_lcfd = 1050 - 1000 = 50.
@@ -368,8 +372,8 @@ TEST_F(VideoTest, LcfdAndSpecialPackets)
// 2. Test lcfd threshold (t_lcfd > 100 should be ignored)
tm.t += 200;
mono_time_update(mono_time);
rtp_send_data(
log, video_recv_rtp, dummy_frame.data(), static_cast<uint32_t>(dummy_frame.size()), true);
rtp_send_data(log, video_recv_rtp, dummy_frame.data(),
static_cast<std::uint32_t>(dummy_frame.size()), true);
EXPECT_EQ(vc_get_lcfd(vc), 50u); // Should still be 50
// 3. Test dummy packet PT = (RTP_TYPE_VIDEO + 2) % 128
@@ -377,7 +381,7 @@ TEST_F(VideoTest, LcfdAndSpecialPackets)
&rtp_mock, nullptr, nullptr, nullptr, vc, RtpMock::video_cb);
rtp_mock.recv_session = dummy_rtp;
rtp_send_data(
log, dummy_rtp, dummy_frame.data(), static_cast<uint32_t>(dummy_frame.size()), false);
log, dummy_rtp, dummy_frame.data(), static_cast<std::uint32_t>(dummy_frame.size()), false);
// Should return 0 but do nothing (logged as "Got dummy!")
// 4. Test GetQueueMutex
@@ -391,15 +395,15 @@ TEST_F(VideoTest, LcfdAndSpecialPackets)
TEST_F(VideoTest, MultiReconfigureEncode)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
for (int i = 0; i < 5; ++i) {
uint16_t w = static_cast<uint16_t>(160 + (i * 16));
uint16_t h = static_cast<uint16_t>(120 + (i * 16));
std::vector<uint8_t> y(static_cast<size_t>(w) * h, 128);
std::vector<uint8_t> u((static_cast<size_t>(w) / 2) * (h / 2), 64);
std::vector<uint8_t> v((static_cast<size_t>(w) / 2) * (h / 2), 192);
std::uint16_t w = static_cast<std::uint16_t>(160 + (i * 16));
std::uint16_t h = static_cast<std::uint16_t>(120 + (i * 16));
std::vector<std::uint8_t> y(static_cast<std::size_t>(w) * h, 128);
std::vector<std::uint8_t> u((static_cast<std::size_t>(w) / 2) * (h / 2), 64);
std::vector<std::uint8_t> v((static_cast<std::size_t>(w) / 2) * (h / 2), 192);
ASSERT_EQ(vc_reconfigure_encoder(vc, 1000, w, h, -1), 0);
ASSERT_EQ(vc_encode(vc, w, h, y.data(), u.data(), v.data(), VC_EFLAG_NONE), 0);
@@ -411,7 +415,7 @@ TEST_F(VideoTest, MultiReconfigureEncode)
TEST_F(VideoTest, ReconfigureFailDoS)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
// Trigger failure by passing invalid resolution (0)
@@ -419,9 +423,9 @@ TEST_F(VideoTest, ReconfigureFailDoS)
ASSERT_EQ(vc_reconfigure_encoder(vc, 1000, 0, 0, -1), -1);
// Attempt to encode. This is expected to crash because vc->encoder is destroyed.
std::vector<uint8_t> y(320 * 240, 128);
std::vector<uint8_t> u(160 * 120, 64);
std::vector<uint8_t> v(160 * 120, 192);
std::vector<std::uint8_t> y(320 * 240, 128);
std::vector<std::uint8_t> u(160 * 120, 64);
std::vector<std::uint8_t> v(160 * 120, 192);
// This call will crash in the current unfixed code.
vc_encode(vc, 320, 240, y.data(), u.data(), v.data(), VC_EFLAG_NONE);
@@ -431,7 +435,7 @@ TEST_F(VideoTest, ReconfigureFailDoS)
TEST_F(VideoTest, LyingLengthOOB)
{
VideoTestData data;
VCSession *vc = vc_new(log, mono_time, 123, VideoTestData::receive_frame, &data);
VCSession *vc = vc_new(mem, log, mono_time, 123, VideoTestData::receive_frame, &data);
ASSERT_NE(vc, nullptr);
RtpMock rtp_mock;
@@ -440,31 +444,31 @@ TEST_F(VideoTest, LyingLengthOOB)
rtp_mock.recv_session = recv_rtp;
// Craft a malicious RTP packet
uint16_t payload_len = 10;
uint8_t packet[RTP_HEADER_SIZE + 11]; // +1 for Tox ID
memset(packet, 0, sizeof(packet));
std::uint16_t payload_len = 10;
std::uint8_t packet[RTP_HEADER_SIZE + 11]; // +1 for Tox ID
std::memset(packet, 0, sizeof(packet));
// Tox ID
packet[0] = static_cast<uint8_t>(RTP_TYPE_VIDEO);
packet[0] = static_cast<std::uint8_t>(RTP_TYPE_VIDEO);
auto pack_u16 = [](uint8_t *p, uint16_t v) {
p[0] = static_cast<uint8_t>(v >> 8);
p[1] = static_cast<uint8_t>(v & 0xff);
auto pack_u16 = [](std::uint8_t *p, std::uint16_t v) {
p[0] = static_cast<std::uint8_t>(v >> 8);
p[1] = static_cast<std::uint8_t>(v & 0xff);
};
auto pack_u32 = [](uint8_t *p, uint32_t v) {
p[0] = static_cast<uint8_t>(v >> 24);
p[1] = static_cast<uint8_t>((v >> 16) & 0xff);
p[2] = static_cast<uint8_t>((v >> 8) & 0xff);
p[3] = static_cast<uint8_t>(v & 0xff);
auto pack_u32 = [](std::uint8_t *p, std::uint32_t v) {
p[0] = static_cast<std::uint8_t>(v >> 24);
p[1] = static_cast<std::uint8_t>((v >> 16) & 0xff);
p[2] = static_cast<std::uint8_t>((v >> 8) & 0xff);
p[3] = static_cast<std::uint8_t>(v & 0xff);
};
auto pack_u64 = [&](uint8_t *p, uint64_t v) {
pack_u32(p, static_cast<uint32_t>(v >> 32));
pack_u32(p + 4, static_cast<uint32_t>(v & 0xffffffff));
auto pack_u64 = [&](std::uint8_t *p, std::uint64_t v) {
pack_u32(p, static_cast<std::uint32_t>(v >> 32));
pack_u32(p + 4, static_cast<std::uint32_t>(v & 0xffffffff));
};
// RTP Header starts at packet[1]
packet[1] = 2 << 6; // ve = 2
packet[2] = static_cast<uint8_t>(RTP_TYPE_VIDEO % 128);
packet[2] = static_cast<std::uint8_t>(RTP_TYPE_VIDEO % 128);
pack_u16(packet + 3, 1); // sequnum
pack_u32(packet + 5, 1000); // timestamp