diff --git a/net/dcsctp/BUILD.gn b/net/dcsctp/BUILD.gn index 0ccc897775..ee15a45361 100644 --- a/net/dcsctp/BUILD.gn +++ b/net/dcsctp/BUILD.gn @@ -13,6 +13,7 @@ if (rtc_include_tests) { testonly = true deps = [ "../../test:test_main", + "common:dcsctp_common_unittests", "packet:dcsctp_packet_unittests", ] } diff --git a/net/dcsctp/common/BUILD.gn b/net/dcsctp/common/BUILD.gn new file mode 100644 index 0000000000..55fa86b984 --- /dev/null +++ b/net/dcsctp/common/BUILD.gn @@ -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", + ] + } +} diff --git a/net/dcsctp/common/math.h b/net/dcsctp/common/math.h new file mode 100644 index 0000000000..ee161d2c8a --- /dev/null +++ b/net/dcsctp/common/math.h @@ -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 +IntType RoundUpTo4(IntType val) { + return (val + 3) & -4; +} + +} // namespace dcsctp + +#endif // NET_DCSCTP_COMMON_MATH_H_ diff --git a/net/dcsctp/common/math_test.cc b/net/dcsctp/common/math_test.cc new file mode 100644 index 0000000000..902aefa906 --- /dev/null +++ b/net/dcsctp/common/math_test.cc @@ -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 diff --git a/net/dcsctp/common/pair_hash.h b/net/dcsctp/common/pair_hash.h new file mode 100644 index 0000000000..62af8b4221 --- /dev/null +++ b/net/dcsctp/common/pair_hash.h @@ -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 + +#include +#include + +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 + size_t operator()(const std::pair& p) const { + return (3 * std::hash{}(p.first)) ^ std::hash{}(p.second); + } +}; +} // namespace dcsctp + +#endif // NET_DCSCTP_COMMON_PAIR_HASH_H_ diff --git a/net/dcsctp/common/pair_hash_test.cc b/net/dcsctp/common/pair_hash_test.cc new file mode 100644 index 0000000000..bcc3ec86c0 --- /dev/null +++ b/net/dcsctp/common/pair_hash_test.cc @@ -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 +#include + +#include "test/gmock.h" + +namespace dcsctp { +namespace { + +TEST(PairHashTest, CanInsertIntoSet) { + using MyPair = std::pair; + + std::unordered_set 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; + + std::unordered_map 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 diff --git a/net/dcsctp/common/sequence_numbers.h b/net/dcsctp/common/sequence_numbers.h new file mode 100644 index 0000000000..f60433757c --- /dev/null +++ b/net/dcsctp/common/sequence_numbers.h @@ -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 +#include +#include + +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 +class UnwrappedSequenceNumber { + public: + static_assert(!std::numeric_limits::is_signed, + "The wrapped type must be unsigned"); + static_assert(std::numeric_limits::max() < + std::numeric_limits::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 Unwrap(WrappedType value) { + WrappedType wrapped_largest = + static_cast(largest_ % kValueLimit); + int64_t result = largest_ + Delta(value, wrapped_largest); + if (largest_ < result) { + largest_ = result; + } + return UnwrappedSequenceNumber(result); + } + + // Similar to `Unwrap`, but will not update the Unwrappers's internal state. + UnwrappedSequenceNumber PeekUnwrap(WrappedType value) const { + WrappedType uint32_largest = + static_cast(largest_ % kValueLimit); + int64_t result = largest_ + Delta(value, uint32_largest); + return UnwrappedSequenceNumber(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(diff); + } + return static_cast(diff) - kValueLimit; + } + + int64_t largest_; + }; + + // Returns the wrapped value this type represents. + WrappedType Wrap() const { + return static_cast(value_ % kValueLimit); + } + + template + friend H AbslHashValue(H state, + const UnwrappedSequenceNumber& hash) { + return H::combine(std::move(state), hash.value_); + } + + bool operator==(const UnwrappedSequenceNumber& other) const { + return value_ == other.value_; + } + bool operator!=(const UnwrappedSequenceNumber& other) const { + return value_ != other.value_; + } + bool operator<(const UnwrappedSequenceNumber& other) const { + return value_ < other.value_; + } + bool operator>(const UnwrappedSequenceNumber& other) const { + return value_ > other.value_; + } + bool operator>=(const UnwrappedSequenceNumber& other) const { + return value_ >= other.value_; + } + bool operator<=(const UnwrappedSequenceNumber& other) const { + return value_ <= other.value_; + } + + // Increments the value. + void Increment() { ++value_; } + UnwrappedSequenceNumber next_value() const { + return UnwrappedSequenceNumber(value_ + 1); + } + + // Adds a delta to the current value. + UnwrappedSequenceNumber AddTo(int delta) const { + return UnwrappedSequenceNumber(value_ + delta); + } + + // Compares the difference between two sequence numbers. + WrappedType Difference(UnwrappedSequenceNumber other) const { + return value_ - other.value_; + } + + private: + explicit UnwrappedSequenceNumber(int64_t value) : value_(value) {} + static constexpr int64_t kValueLimit = + static_cast(1) << std::numeric_limits::digits; + + int64_t value_; +}; + +// Transmission Sequence Numbers (TSN) +using TSN = UnwrappedSequenceNumber; + +// Stream Sequence Numbers (SSN) +using SSN = UnwrappedSequenceNumber; + +// Message Identifier (MID) +using MID = UnwrappedSequenceNumber; + +} // namespace dcsctp + +#endif // NET_DCSCTP_COMMON_SEQUENCE_NUMBERS_H_ diff --git a/net/dcsctp/common/sequence_numbers_test.cc b/net/dcsctp/common/sequence_numbers_test.cc new file mode 100644 index 0000000000..f7ecd9b942 --- /dev/null +++ b/net/dcsctp/common/sequence_numbers_test.cc @@ -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; + +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(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(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(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(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 diff --git a/net/dcsctp/common/str_join.h b/net/dcsctp/common/str_join.h new file mode 100644 index 0000000000..04517827b7 --- /dev/null +++ b/net/dcsctp/common/str_join.h @@ -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 + +#include "absl/strings/string_view.h" +#include "rtc_base/strings/string_builder.h" + +namespace dcsctp { + +template +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 +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_ diff --git a/net/dcsctp/common/str_join_test.cc b/net/dcsctp/common/str_join_test.cc new file mode 100644 index 0000000000..dbfd92c1cf --- /dev/null +++ b/net/dcsctp/common/str_join_test.cc @@ -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 +#include +#include + +#include "test/gmock.h" + +namespace dcsctp { +namespace { + +TEST(StrJoinTest, CanJoinStringsFromVector) { + std::vector strings = {"Hello", "World"}; + std::string s = StrJoin(strings, " "); + EXPECT_EQ(s, "Hello World"); +} + +TEST(StrJoinTest, CanJoinNumbersFromArray) { + std::array numbers = {1, 2, 3}; + std::string s = StrJoin(numbers, ","); + EXPECT_EQ(s, "1,2,3"); +} + +TEST(StrJoinTest, CanFormatElementsWhileJoining) { + std::vector> pairs = { + {"hello", "world"}, {"foo", "bar"}, {"fum", "gazonk"}}; + std::string s = StrJoin(pairs, ",", + [&](rtc::StringBuilder& sb, + const std::pair& p) { + sb << p.first << "=" << p.second; + }); + EXPECT_EQ(s, "hello=world,foo=bar,fum=gazonk"); +} + +} // namespace +} // namespace dcsctp