-
Notifications
You must be signed in to change notification settings - Fork 30k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
quic: add multiple internal utilities
* add the CID implementation * add the PreferredAddress implementation * add Path and PathStorage implementations * add Store implementation * add QuicError implementation PR-URL: #47263 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
- Loading branch information
Showing
8 changed files
with
1,006 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
#include "cid.h" | ||
#include <crypto/crypto_util.h> | ||
#include <memory_tracker-inl.h> | ||
#include <node_mutex.h> | ||
#include <string_bytes.h> | ||
|
||
namespace node { | ||
namespace quic { | ||
|
||
// ============================================================================ | ||
// CID | ||
|
||
CID::CID() : ptr_(&cid_) { | ||
cid_.datalen = 0; | ||
} | ||
|
||
CID::CID(const ngtcp2_cid& cid) : CID(cid.data, cid.datalen) {} | ||
|
||
CID::CID(const uint8_t* data, size_t len) : CID() { | ||
DCHECK_GE(len, kMinLength); | ||
DCHECK_LE(len, kMaxLength); | ||
ngtcp2_cid_init(&cid_, data, len); | ||
} | ||
|
||
CID::CID(const ngtcp2_cid* cid) : ptr_(cid) { | ||
CHECK_NOT_NULL(cid); | ||
DCHECK_GE(cid->datalen, kMinLength); | ||
DCHECK_LE(cid->datalen, kMaxLength); | ||
} | ||
|
||
CID::CID(const CID& other) : ptr_(&cid_) { | ||
CHECK_NOT_NULL(other.ptr_); | ||
ngtcp2_cid_init(&cid_, other.ptr_->data, other.ptr_->datalen); | ||
} | ||
|
||
bool CID::operator==(const CID& other) const noexcept { | ||
if (this == &other || (length() == 0 && other.length() == 0)) return true; | ||
if (length() != other.length()) return false; | ||
return memcmp(ptr_->data, other.ptr_->data, ptr_->datalen) == 0; | ||
} | ||
|
||
bool CID::operator!=(const CID& other) const noexcept { | ||
return !(*this == other); | ||
} | ||
|
||
CID::operator const uint8_t*() const { | ||
return ptr_->data; | ||
} | ||
CID::operator const ngtcp2_cid&() const { | ||
return *ptr_; | ||
} | ||
CID::operator const ngtcp2_cid*() const { | ||
return ptr_; | ||
} | ||
CID::operator bool() const { | ||
return ptr_->datalen >= kMinLength; | ||
} | ||
|
||
size_t CID::length() const { | ||
return ptr_->datalen; | ||
} | ||
|
||
std::string CID::ToString() const { | ||
char dest[kMaxLength * 2]; | ||
size_t written = | ||
StringBytes::hex_encode(reinterpret_cast<const char*>(ptr_->data), | ||
ptr_->datalen, | ||
dest, | ||
arraysize(dest)); | ||
return std::string(dest, written); | ||
} | ||
|
||
CID CID::kInvalid{}; | ||
|
||
// ============================================================================ | ||
// CID::Hash | ||
|
||
size_t CID::Hash::operator()(const CID& cid) const { | ||
size_t hash = 0; | ||
for (size_t n = 0; n < cid.length(); n++) { | ||
hash ^= std::hash<uint8_t>{}(cid.ptr_->data[n] + 0x9e3779b9 + (hash << 6) + | ||
(hash >> 2)); | ||
} | ||
return hash; | ||
} | ||
|
||
// ============================================================================ | ||
// CID::Factory | ||
|
||
namespace { | ||
class RandomCIDFactory : public CID::Factory { | ||
public: | ||
RandomCIDFactory() = default; | ||
RandomCIDFactory(const RandomCIDFactory&) = delete; | ||
RandomCIDFactory(RandomCIDFactory&&) = delete; | ||
RandomCIDFactory& operator=(const RandomCIDFactory&) = delete; | ||
RandomCIDFactory& operator=(RandomCIDFactory&&) = delete; | ||
|
||
CID Generate(size_t length_hint) const override { | ||
DCHECK_GE(length_hint, CID::kMinLength); | ||
DCHECK_LE(length_hint, CID::kMaxLength); | ||
Mutex::ScopedLock lock(mutex_); | ||
maybe_refresh_pool(length_hint); | ||
auto start = pool_ + pos_; | ||
pos_ += length_hint; | ||
return CID(start, length_hint); | ||
} | ||
|
||
void GenerateInto(ngtcp2_cid* cid, | ||
size_t length_hint = CID::kMaxLength) const override { | ||
DCHECK_GE(length_hint, CID::kMinLength); | ||
DCHECK_LE(length_hint, CID::kMaxLength); | ||
Mutex::ScopedLock lock(mutex_); | ||
maybe_refresh_pool(length_hint); | ||
auto start = pool_ + pos_; | ||
pos_ += length_hint; | ||
ngtcp2_cid_init(cid, start, length_hint); | ||
} | ||
|
||
private: | ||
void maybe_refresh_pool(size_t length_hint) const { | ||
// We generate a pool of random data kPoolSize in length | ||
// and pull our random CID from that. If we don't have | ||
// enough random random remaining in the pool to generate | ||
// a CID of the requested size, we regenerate the pool | ||
// and reset it to zero. | ||
if (pos_ + length_hint > kPoolSize) { | ||
CHECK(crypto::CSPRNG(pool_, kPoolSize).is_ok()); | ||
pos_ = 0; | ||
} | ||
} | ||
|
||
static constexpr int kPoolSize = 4096; | ||
mutable int pos_ = kPoolSize; | ||
mutable uint8_t pool_[kPoolSize]; | ||
mutable Mutex mutex_; | ||
}; | ||
} // namespace | ||
|
||
const CID::Factory& CID::Factory::random() { | ||
static RandomCIDFactory instance; | ||
return instance; | ||
} | ||
|
||
} // namespace quic | ||
} // namespace node | ||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#pragma once | ||
|
||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
#if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
#include <memory_tracker.h> | ||
#include <ngtcp2/ngtcp2.h> | ||
#include <string> | ||
|
||
namespace node { | ||
namespace quic { | ||
|
||
// CIDS are used to identify endpoints participating in a QUIC session. | ||
// Once created, CID instances are immutable. | ||
// | ||
// CIDs contain between 1 to 20 bytes. Most typically they are selected | ||
// randomly but there is a spec for creating "routable" CIDs that encode | ||
// a specific structure that is meaningful only to the side that creates | ||
// the CID. For most purposes, CIDs should be treated as opaque tokens. | ||
// | ||
// Each peer in a QUIC session generates one or more CIDs that the *other* | ||
// peer will use to identify the session. When a QUIC client initiates a | ||
// brand new session, it will initially generates a CID of its own (its | ||
// source CID) and a random placeholder CID for the server (the original | ||
// destination CID). When the server receives the initial packet, it will | ||
// generate its own source CID and use the clients source CID as the | ||
// server's destination CID. | ||
// | ||
// Client Server | ||
// ------------------------------------------- | ||
// Source CID <====> Destination CID | ||
// Destination CID <====> Source CID | ||
// | ||
// While the connection is being established, it is possible for either | ||
// peer to generate additional CIDs that are also associated with the | ||
// connection. | ||
class CID final : public MemoryRetainer { | ||
public: | ||
static constexpr size_t kMinLength = NGTCP2_MIN_CIDLEN; | ||
static constexpr size_t kMaxLength = NGTCP2_MAX_CIDLEN; | ||
|
||
// Copy the given ngtcp2_cid. | ||
explicit CID(const ngtcp2_cid& cid); | ||
|
||
// Copy the given buffer as a CID. The len must be within | ||
// kMinLength and kMaxLength. | ||
explicit CID(const uint8_t* data, size_t len); | ||
|
||
// Wrap the given ngtcp2_cid. The CID does not take ownership | ||
// of the underlying ngtcp2_cid. | ||
explicit CID(const ngtcp2_cid* cid); | ||
|
||
CID(const CID& other); | ||
CID(CID&& other) = delete; | ||
|
||
struct Hash final { | ||
size_t operator()(const CID& cid) const; | ||
}; | ||
|
||
bool operator==(const CID& other) const noexcept; | ||
bool operator!=(const CID& other) const noexcept; | ||
|
||
operator const uint8_t*() const; | ||
operator const ngtcp2_cid&() const; | ||
operator const ngtcp2_cid*() const; | ||
|
||
// True if the CID length is at least kMinLength; | ||
operator bool() const; | ||
size_t length() const; | ||
|
||
std::string ToString() const; | ||
|
||
SET_NO_MEMORY_INFO() | ||
SET_MEMORY_INFO_NAME(CID) | ||
SET_SELF_SIZE(CID) | ||
|
||
template <typename T> | ||
using Map = std::unordered_map<CID, T, CID::Hash>; | ||
|
||
// A CID::Factory, as the name suggests, is used to create new CIDs. | ||
// Per https://datatracker.ietf.org/doc/draft-ietf-quic-load-balancers/, QUIC | ||
// implementations MAY use the Connection ID associated with a QUIC session | ||
// as a routing mechanism, with each CID instance securely encoding the | ||
// routing information. By default, our implementation creates CIDs randomly | ||
// but will allow user code to provide their own CID::Factory implementation. | ||
class Factory; | ||
|
||
static CID kInvalid; | ||
|
||
private: | ||
// The default constructor creates an empty, zero-length CID. | ||
// Zero-length CIDs are not usable. We use them as a placeholder | ||
// for a missing or empty CID value. | ||
CID(); | ||
|
||
ngtcp2_cid cid_; | ||
const ngtcp2_cid* ptr_; | ||
|
||
friend struct Hash; | ||
}; | ||
|
||
class CID::Factory { | ||
public: | ||
virtual ~Factory() = default; | ||
|
||
// Generate a new CID. The length_hint must be between CID::kMinLength | ||
// and CID::kMaxLength. The implementation can choose to ignore the length. | ||
virtual CID Generate(size_t length_hint = CID::kMaxLength) const = 0; | ||
|
||
// Generate a new CID into the given ngtcp2_cid. This variation of | ||
// Generate should be used far less commonly. It is provided largely | ||
// for a couple of internal cases. | ||
virtual void GenerateInto(ngtcp2_cid* cid, | ||
size_t length_hint = CID::kMaxLength) const = 0; | ||
|
||
// The default random CID generator instance. | ||
static const Factory& random(); | ||
|
||
// TODO(@jasnell): This will soon also include additional implementations | ||
// of CID::Factory that implement the QUIC Load Balancers spec. | ||
}; | ||
|
||
} // namespace quic | ||
} // namespace node | ||
|
||
#endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC | ||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
Oops, something went wrong.