dcsctp: Added common utilities

These are quite generic utilities that are used by multiple modules
within dcSCTP. Some would be good to have in rtc_base and are simple
replicas of utilities available in abseil.

Bug: webrtc:12614
Change-Id: I9914286ced7317a34628a71697da9149d6d19d38
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/213190
Reviewed-by: Tommi <tommi@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33609}
This commit is contained in:
Victor Boivie 2021-03-30 10:03:51 +02:00 committed by Commit Bot
parent 5457ec05b4
commit a4d5e24c11
10 changed files with 628 additions and 0 deletions

View File

@ -13,6 +13,7 @@ if (rtc_include_tests) {
testonly = true
deps = [
"../../test:test_main",
"common:dcsctp_common_unittests",
"packet:dcsctp_packet_unittests",
]
}

View File

@ -0,0 +1,55 @@
# Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
import("//build/config/linux/pkg_config.gni")
import("../../../webrtc.gni")
rtc_source_set("math") {
deps = []
sources = [ "math.h" ]
}
rtc_source_set("pair_hash") {
deps = []
sources = [ "pair_hash.h" ]
}
rtc_source_set("sequence_numbers") {
deps = []
sources = [ "sequence_numbers.h" ]
}
rtc_source_set("str_join") {
deps = []
sources = [ "str_join.h" ]
}
if (rtc_include_tests) {
rtc_library("dcsctp_common_unittests") {
testonly = true
defines = []
deps = [
":math",
":pair_hash",
":sequence_numbers",
":str_join",
"../../../api:array_view",
"../../../rtc_base:checks",
"../../../rtc_base:gunit_helpers",
"../../../rtc_base:rtc_base_approved",
"../../../test:test_support",
]
sources = [
"math_test.cc",
"pair_hash_test.cc",
"sequence_numbers_test.cc",
"str_join_test.cc",
]
}
}

24
net/dcsctp/common/math.h Normal file
View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_COMMON_MATH_H_
#define NET_DCSCTP_COMMON_MATH_H_
namespace dcsctp {
// Rounds up `val` to the nearest value that is divisible by four. Frequently
// used to e.g. pad chunks or parameters to an even 32-bit offset.
template <typename IntType>
IntType RoundUpTo4(IntType val) {
return (val + 3) & -4;
}
} // namespace dcsctp
#endif // NET_DCSCTP_COMMON_MATH_H_

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/common/math.h"
#include "test/gmock.h"
namespace dcsctp {
namespace {
TEST(MathUtilTest, CanRoundUpTo4) {
EXPECT_EQ(RoundUpTo4(0), 0);
EXPECT_EQ(RoundUpTo4(1), 4);
EXPECT_EQ(RoundUpTo4(2), 4);
EXPECT_EQ(RoundUpTo4(3), 4);
EXPECT_EQ(RoundUpTo4(4), 4);
EXPECT_EQ(RoundUpTo4(5), 8);
EXPECT_EQ(RoundUpTo4(6), 8);
EXPECT_EQ(RoundUpTo4(7), 8);
EXPECT_EQ(RoundUpTo4(8), 8);
EXPECT_EQ(RoundUpTo4(10000000000), 10000000000);
EXPECT_EQ(RoundUpTo4(10000000001), 10000000004);
}
} // namespace
} // namespace dcsctp

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_COMMON_PAIR_HASH_H_
#define NET_DCSCTP_COMMON_PAIR_HASH_H_
#include <stddef.h>
#include <functional>
#include <utility>
namespace dcsctp {
// A custom hash function for std::pair, to be able to be used as key in a
// std::unordered_map. If absl::flat_hash_map would ever be used, this is
// unnecessary as it already has a hash function for std::pair.
struct PairHash {
template <class T1, class T2>
size_t operator()(const std::pair<T1, T2>& p) const {
return (3 * std::hash<T1>{}(p.first)) ^ std::hash<T2>{}(p.second);
}
};
} // namespace dcsctp
#endif // NET_DCSCTP_COMMON_PAIR_HASH_H_

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/common/pair_hash.h"
#include <unordered_map>
#include <unordered_set>
#include "test/gmock.h"
namespace dcsctp {
namespace {
TEST(PairHashTest, CanInsertIntoSet) {
using MyPair = std::pair<int, int>;
std::unordered_set<MyPair, PairHash> pairs;
pairs.insert({1, 2});
pairs.insert({3, 4});
EXPECT_NE(pairs.find({1, 2}), pairs.end());
EXPECT_NE(pairs.find({3, 4}), pairs.end());
EXPECT_EQ(pairs.find({1, 3}), pairs.end());
EXPECT_EQ(pairs.find({3, 3}), pairs.end());
}
TEST(PairHashTest, CanInsertIntoMap) {
using MyPair = std::pair<int, int>;
std::unordered_map<MyPair, int, PairHash> pairs;
pairs[{1, 2}] = 99;
pairs[{3, 4}] = 100;
EXPECT_EQ((pairs[{1, 2}]), 99);
EXPECT_EQ((pairs[{3, 4}]), 100);
EXPECT_EQ(pairs.find({1, 3}), pairs.end());
EXPECT_EQ(pairs.find({3, 3}), pairs.end());
}
} // namespace
} // namespace dcsctp

View File

@ -0,0 +1,150 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_COMMON_SEQUENCE_NUMBERS_H_
#define NET_DCSCTP_COMMON_SEQUENCE_NUMBERS_H_
#include <cstdint>
#include <limits>
#include <utility>
namespace dcsctp {
// UnwrappedSequenceNumber handles wrapping sequence numbers and unwraps them to
// an int64_t value space, to allow wrapped sequence numbers to be easily
// compared for ordering.
//
// Sequence numbers are expected to be monotonically increasing, but they do not
// need to be unwrapped in order, as long as the difference to the previous one
// is not larger than half the range of the wrapped sequence number.
template <typename WrappedType>
class UnwrappedSequenceNumber {
public:
static_assert(!std::numeric_limits<WrappedType>::is_signed,
"The wrapped type must be unsigned");
static_assert(std::numeric_limits<WrappedType>::max() <
std::numeric_limits<int64_t>::max(),
"The wrapped type must be less than the int64_t value space");
// The unwrapper is a sort of factory and converts wrapped sequence numbers to
// unwrapped ones.
class Unwrapper {
public:
Unwrapper() : largest_(kValueLimit) {}
Unwrapper(const Unwrapper&) = default;
Unwrapper& operator=(const Unwrapper&) = default;
// Given a wrapped `value`, and with knowledge of its current last seen
// largest number, will return a value that can be compared using normal
// operators, such as less-than, greater-than etc.
//
// This will also update the Unwrapper's state, to track the last seen
// largest value.
UnwrappedSequenceNumber<WrappedType> Unwrap(WrappedType value) {
WrappedType wrapped_largest =
static_cast<WrappedType>(largest_ % kValueLimit);
int64_t result = largest_ + Delta(value, wrapped_largest);
if (largest_ < result) {
largest_ = result;
}
return UnwrappedSequenceNumber<WrappedType>(result);
}
// Similar to `Unwrap`, but will not update the Unwrappers's internal state.
UnwrappedSequenceNumber<WrappedType> PeekUnwrap(WrappedType value) const {
WrappedType uint32_largest =
static_cast<WrappedType>(largest_ % kValueLimit);
int64_t result = largest_ + Delta(value, uint32_largest);
return UnwrappedSequenceNumber<WrappedType>(result);
}
// Resets the Unwrapper to its pristine state. Used when a sequence number
// is to be reset to zero.
void Reset() { largest_ = kValueLimit; }
private:
static int64_t Delta(WrappedType value, WrappedType prev_value) {
static constexpr WrappedType kBreakpoint = kValueLimit / 2;
WrappedType diff = value - prev_value;
diff %= kValueLimit;
if (diff < kBreakpoint) {
return static_cast<int64_t>(diff);
}
return static_cast<int64_t>(diff) - kValueLimit;
}
int64_t largest_;
};
// Returns the wrapped value this type represents.
WrappedType Wrap() const {
return static_cast<WrappedType>(value_ % kValueLimit);
}
template <typename H>
friend H AbslHashValue(H state,
const UnwrappedSequenceNumber<WrappedType>& hash) {
return H::combine(std::move(state), hash.value_);
}
bool operator==(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ == other.value_;
}
bool operator!=(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ != other.value_;
}
bool operator<(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ < other.value_;
}
bool operator>(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ > other.value_;
}
bool operator>=(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ >= other.value_;
}
bool operator<=(const UnwrappedSequenceNumber<WrappedType>& other) const {
return value_ <= other.value_;
}
// Increments the value.
void Increment() { ++value_; }
UnwrappedSequenceNumber<WrappedType> next_value() const {
return UnwrappedSequenceNumber<WrappedType>(value_ + 1);
}
// Adds a delta to the current value.
UnwrappedSequenceNumber<WrappedType> AddTo(int delta) const {
return UnwrappedSequenceNumber<WrappedType>(value_ + delta);
}
// Compares the difference between two sequence numbers.
WrappedType Difference(UnwrappedSequenceNumber<WrappedType> other) const {
return value_ - other.value_;
}
private:
explicit UnwrappedSequenceNumber(int64_t value) : value_(value) {}
static constexpr int64_t kValueLimit =
static_cast<int64_t>(1) << std::numeric_limits<WrappedType>::digits;
int64_t value_;
};
// Transmission Sequence Numbers (TSN)
using TSN = UnwrappedSequenceNumber<uint32_t>;
// Stream Sequence Numbers (SSN)
using SSN = UnwrappedSequenceNumber<uint16_t>;
// Message Identifier (MID)
using MID = UnwrappedSequenceNumber<uint32_t>;
} // namespace dcsctp
#endif // NET_DCSCTP_COMMON_SEQUENCE_NUMBERS_H_

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/common/sequence_numbers.h"
#include "test/gmock.h"
namespace dcsctp {
namespace {
using TestSequence = UnwrappedSequenceNumber<uint16_t>;
TEST(SequenceNumbersTest, SimpleUnwrapping) {
TestSequence::Unwrapper unwrapper;
TestSequence s0 = unwrapper.Unwrap(0);
TestSequence s1 = unwrapper.Unwrap(1);
TestSequence s2 = unwrapper.Unwrap(2);
TestSequence s3 = unwrapper.Unwrap(3);
EXPECT_LT(s0, s1);
EXPECT_LT(s0, s2);
EXPECT_LT(s0, s3);
EXPECT_LT(s1, s2);
EXPECT_LT(s1, s3);
EXPECT_LT(s2, s3);
EXPECT_EQ(s1.Difference(s0), 1);
EXPECT_EQ(s2.Difference(s0), 2);
EXPECT_EQ(s3.Difference(s0), 3);
EXPECT_GT(s1, s0);
EXPECT_GT(s2, s0);
EXPECT_GT(s3, s0);
EXPECT_GT(s2, s1);
EXPECT_GT(s3, s1);
EXPECT_GT(s3, s2);
s0.Increment();
EXPECT_EQ(s0, s1);
s1.Increment();
EXPECT_EQ(s1, s2);
s2.Increment();
EXPECT_EQ(s2, s3);
EXPECT_EQ(s0.AddTo(2), s3);
}
TEST(SequenceNumbersTest, MidValueUnwrapping) {
TestSequence::Unwrapper unwrapper;
TestSequence s0 = unwrapper.Unwrap(0x7FFE);
TestSequence s1 = unwrapper.Unwrap(0x7FFF);
TestSequence s2 = unwrapper.Unwrap(0x8000);
TestSequence s3 = unwrapper.Unwrap(0x8001);
EXPECT_LT(s0, s1);
EXPECT_LT(s0, s2);
EXPECT_LT(s0, s3);
EXPECT_LT(s1, s2);
EXPECT_LT(s1, s3);
EXPECT_LT(s2, s3);
EXPECT_EQ(s1.Difference(s0), 1);
EXPECT_EQ(s2.Difference(s0), 2);
EXPECT_EQ(s3.Difference(s0), 3);
EXPECT_GT(s1, s0);
EXPECT_GT(s2, s0);
EXPECT_GT(s3, s0);
EXPECT_GT(s2, s1);
EXPECT_GT(s3, s1);
EXPECT_GT(s3, s2);
s0.Increment();
EXPECT_EQ(s0, s1);
s1.Increment();
EXPECT_EQ(s1, s2);
s2.Increment();
EXPECT_EQ(s2, s3);
EXPECT_EQ(s0.AddTo(2), s3);
}
TEST(SequenceNumbersTest, WrappedUnwrapping) {
TestSequence::Unwrapper unwrapper;
TestSequence s0 = unwrapper.Unwrap(0xFFFE);
TestSequence s1 = unwrapper.Unwrap(0xFFFF);
TestSequence s2 = unwrapper.Unwrap(0x0000);
TestSequence s3 = unwrapper.Unwrap(0x0001);
EXPECT_LT(s0, s1);
EXPECT_LT(s0, s2);
EXPECT_LT(s0, s3);
EXPECT_LT(s1, s2);
EXPECT_LT(s1, s3);
EXPECT_LT(s2, s3);
EXPECT_EQ(s1.Difference(s0), 1);
EXPECT_EQ(s2.Difference(s0), 2);
EXPECT_EQ(s3.Difference(s0), 3);
EXPECT_GT(s1, s0);
EXPECT_GT(s2, s0);
EXPECT_GT(s3, s0);
EXPECT_GT(s2, s1);
EXPECT_GT(s3, s1);
EXPECT_GT(s3, s2);
s0.Increment();
EXPECT_EQ(s0, s1);
s1.Increment();
EXPECT_EQ(s1, s2);
s2.Increment();
EXPECT_EQ(s2, s3);
EXPECT_EQ(s0.AddTo(2), s3);
}
TEST(SequenceNumbersTest, WrapAroundAFewTimes) {
TestSequence::Unwrapper unwrapper;
TestSequence s0 = unwrapper.Unwrap(0);
TestSequence prev = s0;
for (uint32_t i = 1; i < 65536 * 3; i++) {
uint16_t wrapped = static_cast<uint16_t>(i);
TestSequence si = unwrapper.Unwrap(wrapped);
EXPECT_LT(s0, si);
EXPECT_LT(prev, si);
prev = si;
}
}
TEST(SequenceNumbersTest, IncrementIsSameAsWrapped) {
TestSequence::Unwrapper unwrapper;
TestSequence s0 = unwrapper.Unwrap(0);
for (uint32_t i = 1; i < 65536 * 2; i++) {
uint16_t wrapped = static_cast<uint16_t>(i);
TestSequence si = unwrapper.Unwrap(wrapped);
s0.Increment();
EXPECT_EQ(s0, si);
}
}
TEST(SequenceNumbersTest, UnwrappingLargerNumberIsAlwaysLarger) {
TestSequence::Unwrapper unwrapper;
for (uint32_t i = 1; i < 65536 * 2; i++) {
uint16_t wrapped = static_cast<uint16_t>(i);
TestSequence si = unwrapper.Unwrap(wrapped);
EXPECT_GT(unwrapper.Unwrap(wrapped + 1), si);
EXPECT_GT(unwrapper.Unwrap(wrapped + 5), si);
EXPECT_GT(unwrapper.Unwrap(wrapped + 10), si);
EXPECT_GT(unwrapper.Unwrap(wrapped + 100), si);
}
}
TEST(SequenceNumbersTest, UnwrappingSmallerNumberIsAlwaysSmaller) {
TestSequence::Unwrapper unwrapper;
for (uint32_t i = 1; i < 65536 * 2; i++) {
uint16_t wrapped = static_cast<uint16_t>(i);
TestSequence si = unwrapper.Unwrap(wrapped);
EXPECT_LT(unwrapper.Unwrap(wrapped - 1), si);
EXPECT_LT(unwrapper.Unwrap(wrapped - 5), si);
EXPECT_LT(unwrapper.Unwrap(wrapped - 10), si);
EXPECT_LT(unwrapper.Unwrap(wrapped - 100), si);
}
}
} // namespace
} // namespace dcsctp

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_COMMON_STR_JOIN_H_
#define NET_DCSCTP_COMMON_STR_JOIN_H_
#include <string>
#include "absl/strings/string_view.h"
#include "rtc_base/strings/string_builder.h"
namespace dcsctp {
template <typename Range>
std::string StrJoin(const Range& seq, absl::string_view delimiter) {
rtc::StringBuilder sb;
int idx = 0;
for (const typename Range::value_type& elem : seq) {
if (idx > 0) {
sb << delimiter;
}
sb << elem;
++idx;
}
return sb.Release();
}
template <typename Range, typename Functor>
std::string StrJoin(const Range& seq,
absl::string_view delimiter,
const Functor& fn) {
rtc::StringBuilder sb;
int idx = 0;
for (const typename Range::value_type& elem : seq) {
if (idx > 0) {
sb << delimiter;
}
fn(sb, elem);
++idx;
}
return sb.Release();
}
} // namespace dcsctp
#endif // NET_DCSCTP_COMMON_STR_JOIN_H_

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/common/str_join.h"
#include <string>
#include <utility>
#include <vector>
#include "test/gmock.h"
namespace dcsctp {
namespace {
TEST(StrJoinTest, CanJoinStringsFromVector) {
std::vector<std::string> strings = {"Hello", "World"};
std::string s = StrJoin(strings, " ");
EXPECT_EQ(s, "Hello World");
}
TEST(StrJoinTest, CanJoinNumbersFromArray) {
std::array<int, 3> numbers = {1, 2, 3};
std::string s = StrJoin(numbers, ",");
EXPECT_EQ(s, "1,2,3");
}
TEST(StrJoinTest, CanFormatElementsWhileJoining) {
std::vector<std::pair<std::string, std::string>> pairs = {
{"hello", "world"}, {"foo", "bar"}, {"fum", "gazonk"}};
std::string s = StrJoin(pairs, ",",
[&](rtc::StringBuilder& sb,
const std::pair<std::string, std::string>& p) {
sb << p.first << "=" << p.second;
});
EXPECT_EQ(s, "hello=world,foo=bar,fum=gazonk");
}
} // namespace
} // namespace dcsctp