|
@@ -24,6 +24,10 @@
|
|
|
#include <cstring>
|
|
|
#include <cstdlib>
|
|
|
|
|
|
+#ifndef __GNUC__
|
|
|
+#include <atomic>
|
|
|
+#endif
|
|
|
+
|
|
|
// Buffers are 16384 bytes in size because this is the smallest size that can hold any packet
|
|
|
// and is a power of two. It needs to be a power of two because masking is significantly faster
|
|
|
// than integer division modulus.
|
|
@@ -32,6 +36,12 @@
|
|
|
|
|
|
namespace ZeroTier {
|
|
|
|
|
|
+#ifdef __GNUC__
|
|
|
+extern uintptr_t Buf_pool;
|
|
|
+#else
|
|
|
+extern std::atomic<uintptr_t> Buf_pool;
|
|
|
+#endif
|
|
|
+
|
|
|
/**
|
|
|
* Buffer and methods for branch-free bounds-checked data assembly and parsing
|
|
|
*
|
|
@@ -62,28 +72,93 @@ namespace ZeroTier {
|
|
|
* reads to ensure that overflow did not occur.
|
|
|
*
|
|
|
* Buf uses a lock-free pool for extremely fast allocation and deallocation.
|
|
|
+ *
|
|
|
+ * Buf can optionally take a template parameter that will be placed in the 'data'
|
|
|
+ * union as 'fields.' This must be a basic plain data type and must be no larger than
|
|
|
+ * ZT_BUF_MEM_SIZE. It's typically a packed struct.
|
|
|
+ *
|
|
|
+ * @tparam U Type to overlap with data bytes in data union (can't be larger than ZT_BUF_MEM_SIZE)
|
|
|
*/
|
|
|
+template<typename U = void>
|
|
|
class Buf
|
|
|
{
|
|
|
- friend class SharedPtr<Buf>;
|
|
|
+ friend class SharedPtr< Buf<U> >;
|
|
|
|
|
|
private:
|
|
|
// Direct construction isn't allowed; use get().
|
|
|
ZT_ALWAYS_INLINE Buf()
|
|
|
{}
|
|
|
|
|
|
- ZT_ALWAYS_INLINE Buf(const Buf &b)
|
|
|
- {}
|
|
|
+ template<typename X>
|
|
|
+ ZT_ALWAYS_INLINE Buf(const Buf<X> &b)
|
|
|
+ { memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE); }
|
|
|
|
|
|
public:
|
|
|
- static void operator delete(void *ptr,std::size_t sz);
|
|
|
+ static void operator delete(void *ptr,std::size_t sz)
|
|
|
+ {
|
|
|
+ if (ptr) {
|
|
|
+ uintptr_t bb;
|
|
|
+ const uintptr_t locked = ~((uintptr_t)0);
|
|
|
+ for (;;) {
|
|
|
+#ifdef __GNUC__
|
|
|
+ bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
|
|
|
+#else
|
|
|
+ bb = s_pool.fetch_or(locked);
|
|
|
+#endif
|
|
|
+ if (bb != locked)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ((Buf *)ptr)->__nextInPool = bb;
|
|
|
+#ifdef __GNUC__
|
|
|
+ __sync_fetch_and_and(&Buf_pool,(uintptr_t)ptr);
|
|
|
+#else
|
|
|
+ s_pool.store((uintptr_t)ptr);
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* Get obtains a buffer from the pool or allocates a new buffer if the pool is empty
|
|
|
*
|
|
|
* @return Buffer
|
|
|
*/
|
|
|
- static SharedPtr<Buf> get();
|
|
|
+ static ZT_ALWAYS_INLINE SharedPtr< Buf<U> > get()
|
|
|
+ {
|
|
|
+ uintptr_t bb;
|
|
|
+ const uintptr_t locked = ~((uintptr_t)0);
|
|
|
+ for (;;) {
|
|
|
+#ifdef __GNUC__
|
|
|
+ bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
|
|
|
+#else
|
|
|
+ bb = s_pool.fetch_or(locked);
|
|
|
+#endif
|
|
|
+ if (bb != locked)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ Buf *b;
|
|
|
+ if (bb == 0) {
|
|
|
+#ifdef __GNUC__
|
|
|
+ __sync_fetch_and_and(&Buf_pool,bb);
|
|
|
+#else
|
|
|
+ s_pool.store(bb);
|
|
|
+#endif
|
|
|
+ b = (Buf *)malloc(sizeof(Buf));
|
|
|
+ if (!b)
|
|
|
+ return SharedPtr<Buf>();
|
|
|
+ } else {
|
|
|
+ b = (Buf *)bb;
|
|
|
+#ifdef __GNUC__
|
|
|
+ __sync_fetch_and_and(&Buf_pool,b->__nextInPool);
|
|
|
+#else
|
|
|
+ s_pool.store(b->__nextInPool);
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ b->__refCount.zero();
|
|
|
+ return SharedPtr<Buf>(b);
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* Free buffers in the pool
|
|
@@ -92,7 +167,32 @@ public:
|
|
|
* and outstanding buffers will still be returned to the pool. This just
|
|
|
* frees buffers currently held in reserve.
|
|
|
*/
|
|
|
- static void freePool();
|
|
|
+ static inline void freePool()
|
|
|
+ {
|
|
|
+ uintptr_t bb;
|
|
|
+ const uintptr_t locked = ~((uintptr_t)0);
|
|
|
+ for (;;) {
|
|
|
+#ifdef __GNUC__
|
|
|
+ bb = __sync_fetch_and_or(&Buf_pool,locked); // get value of s_pool and "lock" by filling with all 1's
|
|
|
+#else
|
|
|
+ bb = s_pool.fetch_or(locked);
|
|
|
+#endif
|
|
|
+ if (bb != locked)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef __GNUC__
|
|
|
+ __sync_fetch_and_and(&Buf_pool,(uintptr_t)0);
|
|
|
+#else
|
|
|
+ s_pool.store((uintptr_t)0);
|
|
|
+#endif
|
|
|
+
|
|
|
+ while (bb != 0) {
|
|
|
+ uintptr_t next = ((Buf *)bb)->__nextInPool;
|
|
|
+ free((void *)bb);
|
|
|
+ bb = next;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* Check for overflow beyond the size of the buffer
|
|
@@ -119,9 +219,19 @@ public:
|
|
|
static ZT_ALWAYS_INLINE bool readOverflow(const int &ii,const unsigned int size)
|
|
|
{ return ((ii - (int)size) > 0); }
|
|
|
|
|
|
- ////////////////////////////////////////////////////////////////////////////
|
|
|
- // Read methods
|
|
|
- ////////////////////////////////////////////////////////////////////////////
|
|
|
+ template<typename X>
|
|
|
+ ZT_ALWAYS_INLINE Buf &operator=(const Buf<X> &b) const
|
|
|
+ {
|
|
|
+ memcpy(data.bytes,b.data.bytes,ZT_BUF_MEM_SIZE);
|
|
|
+ return *this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Zero memory
|
|
|
+ *
|
|
|
+ * For performance reasons Buf does not do this on every get().
|
|
|
+ */
|
|
|
+ ZT_ALWAYS_INLINE void clear() { memset(data.bytes,0,ZT_BUF_MEM_SIZE); }
|
|
|
|
|
|
/**
|
|
|
* Read a byte
|
|
@@ -132,7 +242,7 @@ public:
|
|
|
ZT_ALWAYS_INLINE uint8_t rI8(int &ii) const
|
|
|
{
|
|
|
const unsigned int s = (unsigned int)ii++;
|
|
|
- return data[s & ZT_BUF_MEM_MASK];
|
|
|
+ return data.bytes[s & ZT_BUF_MEM_MASK];
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -147,10 +257,10 @@ public:
|
|
|
ii += 2;
|
|
|
#ifdef ZT_NO_UNALIGNED_ACCESS
|
|
|
return (
|
|
|
- ((uint16_t)data[s] << 8U) |
|
|
|
- (uint16_t)data[s + 1]);
|
|
|
+ ((uint16_t)data.bytes[s] << 8U) |
|
|
|
+ (uint16_t)data.bytes[s + 1]);
|
|
|
#else
|
|
|
- return Utils::ntoh(*reinterpret_cast<const uint16_t *>(data + s));
|
|
|
+ return Utils::ntoh(*reinterpret_cast<const uint16_t *>(data.bytes + s));
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -166,12 +276,12 @@ public:
|
|
|
ii += 4;
|
|
|
#ifdef ZT_NO_UNALIGNED_ACCESS
|
|
|
return (
|
|
|
- ((uint32_t)data[s] << 24U) |
|
|
|
- ((uint32_t)data[s + 1] << 16U) |
|
|
|
- ((uint32_t)data[s + 2] << 8U) |
|
|
|
- (uint32_t)data[s + 3]);
|
|
|
+ ((uint32_t)data.bytes[s] << 24U) |
|
|
|
+ ((uint32_t)data.bytes[s + 1] << 16U) |
|
|
|
+ ((uint32_t)data.bytes[s + 2] << 8U) |
|
|
|
+ (uint32_t)data.bytes[s + 3]);
|
|
|
#else
|
|
|
- return Utils::ntoh(*reinterpret_cast<const uint32_t *>(data + s));
|
|
|
+ return Utils::ntoh(*reinterpret_cast<const uint32_t *>(data.bytes + s));
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -187,16 +297,16 @@ public:
|
|
|
ii += 8;
|
|
|
#ifdef ZT_NO_UNALIGNED_ACCESS
|
|
|
return (
|
|
|
- ((uint64_t)data[s] << 56U) |
|
|
|
- ((uint64_t)data[s + 1] << 48U) |
|
|
|
- ((uint64_t)data[s + 2] << 40U) |
|
|
|
- ((uint64_t)data[s + 3] << 32U) |
|
|
|
- ((uint64_t)data[s + 4] << 24U) |
|
|
|
- ((uint64_t)data[s + 5] << 16U) |
|
|
|
- ((uint64_t)data[s + 6] << 8U) |
|
|
|
- (uint64_t)data[s + 7]);
|
|
|
+ ((uint64_t)data.bytes[s] << 56U) |
|
|
|
+ ((uint64_t)data.bytes[s + 1] << 48U) |
|
|
|
+ ((uint64_t)data.bytes[s + 2] << 40U) |
|
|
|
+ ((uint64_t)data.bytes[s + 3] << 32U) |
|
|
|
+ ((uint64_t)data.bytes[s + 4] << 24U) |
|
|
|
+ ((uint64_t)data.bytes[s + 5] << 16U) |
|
|
|
+ ((uint64_t)data.bytes[s + 6] << 8U) |
|
|
|
+ (uint64_t)data.bytes[s + 7]);
|
|
|
#else
|
|
|
- return Utils::ntoh(*reinterpret_cast<const uint64_t *>(data + s));
|
|
|
+ return Utils::ntoh(*reinterpret_cast<const uint64_t *>(data.bytes + s));
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -219,7 +329,7 @@ public:
|
|
|
ZT_ALWAYS_INLINE int rO(int &ii,T &obj) const
|
|
|
{
|
|
|
if (ii < ZT_BUF_MEM_SIZE) {
|
|
|
- int ms = obj.unmarshal(data + ii,ZT_BUF_MEM_SIZE - ii);
|
|
|
+ int ms = obj.unmarshal(data.bytes + ii,ZT_BUF_MEM_SIZE - ii);
|
|
|
if (ms > 0)
|
|
|
ii += ms;
|
|
|
return ms;
|
|
@@ -240,10 +350,10 @@ public:
|
|
|
*/
|
|
|
ZT_ALWAYS_INLINE char *rS(int &ii,char *const buf,const unsigned int bufSize) const
|
|
|
{
|
|
|
- const char *const s = (const char *)(data + ii);
|
|
|
+ const char *const s = (const char *)(data.bytes + ii);
|
|
|
const int sii = ii;
|
|
|
while (ii < ZT_BUF_MEM_SIZE) {
|
|
|
- if (data[ii++] == 0) {
|
|
|
+ if (data.bytes[ii++] == 0) {
|
|
|
memcpy(buf,s,ii - sii);
|
|
|
return buf;
|
|
|
}
|
|
@@ -266,9 +376,9 @@ public:
|
|
|
*/
|
|
|
ZT_ALWAYS_INLINE const char *rSnc(int &ii) const
|
|
|
{
|
|
|
- const char *const s = (const char *)(data + ii);
|
|
|
+ const char *const s = (const char *)(data.bytes + ii);
|
|
|
while (ii < ZT_BUF_MEM_SIZE) {
|
|
|
- if (data[ii++] == 0)
|
|
|
+ if (data.bytes[ii++] == 0)
|
|
|
return s;
|
|
|
}
|
|
|
return nullptr;
|
|
@@ -287,7 +397,7 @@ public:
|
|
|
*/
|
|
|
ZT_ALWAYS_INLINE void *rB(int &ii,void *bytes,unsigned int len) const
|
|
|
{
|
|
|
- const void *const b = (const void *)(data + ii);
|
|
|
+ const void *const b = (const void *)(data.bytes + ii);
|
|
|
if ((ii += (int)len) <= ZT_BUF_MEM_SIZE) {
|
|
|
memcpy(bytes,b,len);
|
|
|
return bytes;
|
|
@@ -310,14 +420,10 @@ public:
|
|
|
*/
|
|
|
ZT_ALWAYS_INLINE const void *rBnc(int &ii,unsigned int len) const
|
|
|
{
|
|
|
- const void *const b = (const void *)(data + ii);
|
|
|
+ const void *const b = (const void *)(data.bytes + ii);
|
|
|
return ((ii += (int)len) <= ZT_BUF_MEM_SIZE) ? b : nullptr;
|
|
|
}
|
|
|
|
|
|
- ////////////////////////////////////////////////////////////////////////////
|
|
|
- // Write methods
|
|
|
- ////////////////////////////////////////////////////////////////////////////
|
|
|
-
|
|
|
/**
|
|
|
* Write a byte
|
|
|
*
|
|
@@ -344,7 +450,7 @@ public:
|
|
|
data[s] = (uint8_t)(n >> 8U);
|
|
|
data[s + 1] = (uint8_t)n;
|
|
|
#else
|
|
|
- *reinterpret_cast<uint16_t *>(data + s) = Utils::hton(n);
|
|
|
+ *reinterpret_cast<uint16_t *>(data.bytes + s) = Utils::hton(n);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -364,7 +470,7 @@ public:
|
|
|
data[s + 2] = (uint8_t)(n >> 8U);
|
|
|
data[s + 3] = (uint8_t)n;
|
|
|
#else
|
|
|
- *reinterpret_cast<uint32_t *>(data + s) = Utils::hton(n);
|
|
|
+ *reinterpret_cast<uint32_t *>(data.bytes + s) = Utils::hton(n);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -388,7 +494,7 @@ public:
|
|
|
data[s + 6] = (uint8_t)(n >> 8U);
|
|
|
data[s + 7] = (uint8_t)n;
|
|
|
#else
|
|
|
- *reinterpret_cast<uint64_t *>(data + s) = Utils::hton(n);
|
|
|
+ *reinterpret_cast<uint64_t *>(data.bytes + s) = Utils::hton(n);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
@@ -404,7 +510,7 @@ public:
|
|
|
{
|
|
|
const unsigned int s = (unsigned int)ii;
|
|
|
if ((s + T::marshalSizeMax()) <= ZT_BUF_MEM_SIZE) {
|
|
|
- int ms = t.marshal(data + s);
|
|
|
+ int ms = t.marshal(data.bytes + s);
|
|
|
if (ms > 0)
|
|
|
ii += ms;
|
|
|
} else {
|
|
@@ -442,28 +548,22 @@ public:
|
|
|
{
|
|
|
unsigned int s = (unsigned int)ii;
|
|
|
if ((ii += (int)len) <= ZT_BUF_MEM_SIZE)
|
|
|
- memcpy(data + s,bytes,len);
|
|
|
- }
|
|
|
-
|
|
|
- ////////////////////////////////////////////////////////////////////////////
|
|
|
-
|
|
|
- ZT_ALWAYS_INLINE Buf &operator=(const Buf &b)
|
|
|
- {
|
|
|
- if (&b != this)
|
|
|
- memcpy(data,b.data,ZT_BUF_MEM_SIZE);
|
|
|
- return *this;
|
|
|
+ memcpy(data.bytes + s,bytes,len);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Raw buffer
|
|
|
+ * Raw data and fields (if U template parameter is set)
|
|
|
*
|
|
|
* The extra eight bytes permit silent overflow of integer types without reading or writing
|
|
|
* beyond Buf's memory and without branching or extra masks. They can be ignored otherwise.
|
|
|
*/
|
|
|
- uint8_t data[ZT_BUF_MEM_SIZE + 8];
|
|
|
+ ZT_PACKED_STRUCT(union {
|
|
|
+ uint8_t bytes[ZT_BUF_MEM_SIZE + 8];
|
|
|
+ U fields;
|
|
|
+ }) data;
|
|
|
|
|
|
private:
|
|
|
- volatile uintptr_t __nextInPool;
|
|
|
+ volatile uintptr_t __nextInPool; // next item in free pool if this Buf is in Buf_pool
|
|
|
AtomicCounter __refCount;
|
|
|
};
|
|
|
|