aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorlloyd <[email protected]>2014-10-06 01:29:13 +0000
committerlloyd <[email protected]>2014-10-06 01:29:13 +0000
commit2d6a5e530c8db496aad61b5a9ab3107dd1ed646b (patch)
tree29d92fc311f65ca88b812dadf3462c3ad1fdb0f9 /src
parent97010abaf527fdbe6e308cb3570f9167c1dc9ec1 (diff)
Add support for DTLS handshake timeouts and retransmissions.
Diffstat (limited to 'src')
-rw-r--r--src/lib/tls/tls_channel.cpp93
-rw-r--r--src/lib/tls/tls_channel.h15
-rw-r--r--src/lib/tls/tls_handshake_io.cpp101
-rw-r--r--src/lib/tls/tls_handshake_io.h43
-rw-r--r--src/lib/tls/tls_reader.h4
-rw-r--r--src/lib/tls/tls_record.cpp9
-rw-r--r--src/lib/tls/tls_record.h2
-rw-r--r--src/lib/tls/tls_seq_numbers.h22
8 files changed, 224 insertions, 65 deletions
diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp
index 30f30d623..0617f992c 100644
--- a/src/lib/tls/tls_channel.cpp
+++ b/src/lib/tls/tls_channel.cpp
@@ -1,6 +1,6 @@
/*
* TLS Channels
-* (C) 2011-2012 Jack Lloyd
+* (C) 2011,2012,2014 Jack Lloyd
*
* Released under the terms of the Botan license
*/
@@ -113,19 +113,22 @@ Handshake_State& Channel::create_handshake_state(Protocol_Version version)
m_sequence_numbers.reset(new Stream_Sequence_Numbers);
}
+ using namespace std::placeholders;
+
std::unique_ptr<Handshake_IO> io;
if(version.is_datagram_protocol())
+ {
+ // default MTU is IPv6 min MTU minus UDP/IP headers (TODO: make configurable)
+ const u16bit mtu = 1280 - 40 - 8;
+
io.reset(new Datagram_Handshake_IO(
sequence_numbers(),
- std::bind(&Channel::send_record_under_epoch, this,
- std::placeholders::_1,
- std::placeholders::_2,
- std::placeholders::_3)));
+ std::bind(&Channel::send_record_under_epoch, this, _1, _2, _3),
+ mtu));
+ }
else
io.reset(new Stream_Handshake_IO(
- std::bind(&Channel::send_record, this,
- std::placeholders::_1,
- std::placeholders::_2)));
+ std::bind(&Channel::send_record, this, _1, _2)));
m_pending_state.reset(new_handshake_state(io.release()));
@@ -135,6 +138,13 @@ Handshake_State& Channel::create_handshake_state(Protocol_Version version)
return *m_pending_state.get();
}
+bool Channel::timeout_check()
+ {
+ if(m_pending_state)
+ return m_pending_state->handshake_io().timeout_check();
+ return false;
+ }
+
void Channel::renegotiate(bool force_full_renegotiation)
{
if(pending_state()) // currently in handshake?
@@ -280,9 +290,6 @@ size_t Channel::received_data(const std::vector<byte>& buf)
size_t Channel::received_data(const byte input[], size_t input_size)
{
- const auto get_cipherstate = [this](u16bit epoch)
- { return this->read_cipher_state_epoch(epoch).get(); };
-
const size_t max_fragment_size = maximum_fragment_size();
try
@@ -306,7 +313,10 @@ size_t Channel::received_data(const byte input[], size_t input_size)
&record_version,
&record_type,
m_sequence_numbers.get(),
- get_cipherstate);
+ std::bind(&TLS::Channel::read_cipher_state_epoch, this,
+ std::placeholders::_1));
+
+ BOTAN_ASSERT(consumed > 0, "Got to eat something");
BOTAN_ASSERT(consumed <= input_size,
"Record reader consumed sane amount");
@@ -328,24 +338,50 @@ size_t Channel::received_data(const byte input[], size_t input_size)
{
if(!m_pending_state)
{
- create_handshake_state(record_version);
if(record_version.is_datagram_protocol())
+ {
sequence_numbers().read_accept(record_sequence);
- }
- m_pending_state->handshake_io().add_record(unlock(record),
- record_type,
- record_sequence);
+ /*
+ * Might be a peer retransmit under epoch - 1 in which
+ * case we must retransmit last flight
+ */
+
+ const u16bit epoch = record_sequence >> 48;
+
+ if(epoch == sequence_numbers().current_read_epoch())
+ {
+ create_handshake_state(record_version);
+ }
+ else if(epoch == sequence_numbers().current_read_epoch() - 1)
+ {
+ m_active_state->handshake_io().add_record(unlock(record),
+ record_type,
+ record_sequence);
+ }
+ }
+ else
+ {
+ create_handshake_state(record_version);
+ }
+ }
- while(auto pending = m_pending_state.get())
+ if(m_pending_state)
{
- auto msg = pending->get_next_handshake_msg();
+ m_pending_state->handshake_io().add_record(unlock(record),
+ record_type,
+ record_sequence);
+
+ while(auto pending = m_pending_state.get())
+ {
+ auto msg = pending->get_next_handshake_msg();
- if(msg.first == HANDSHAKE_NONE) // no full handshake yet
- break;
+ if(msg.first == HANDSHAKE_NONE) // no full handshake yet
+ break;
- process_handshake_msg(active_state(), *pending,
- msg.first, msg.second);
+ process_handshake_msg(active_state(), *pending,
+ msg.first, msg.second);
+ }
}
}
else if(record_type == HEARTBEAT && peer_supports_heartbeats())
@@ -450,11 +486,10 @@ void Channel::heartbeat(const byte payload[], size_t payload_size)
}
}
-void Channel::write_record(Connection_Cipher_State* cipher_state,
+void Channel::write_record(Connection_Cipher_State* cipher_state, u16bit epoch,
byte record_type, const byte input[], size_t length)
{
- BOTAN_ASSERT(m_pending_state || m_active_state,
- "Some connection state exists");
+ BOTAN_ASSERT(m_pending_state || m_active_state, "Some connection state exists");
Protocol_Version record_version =
(m_pending_state) ? (m_pending_state->version()) : (m_active_state->version());
@@ -464,7 +499,7 @@ void Channel::write_record(Connection_Cipher_State* cipher_state,
input,
length,
record_version,
- sequence_numbers().next_write_sequence(),
+ sequence_numbers().next_write_sequence(epoch),
cipher_state,
m_rng);
@@ -492,7 +527,7 @@ void Channel::send_record_array(u16bit epoch, byte type, const byte input[], siz
if(type == APPLICATION_DATA && cipher_state->cbc_without_explicit_iv())
{
- write_record(cipher_state.get(), type, &input[0], 1);
+ write_record(cipher_state.get(), epoch, type, &input[0], 1);
input += 1;
length -= 1;
}
@@ -502,7 +537,7 @@ void Channel::send_record_array(u16bit epoch, byte type, const byte input[], siz
while(length)
{
const size_t sending = std::min(length, max_fragment_size);
- write_record(cipher_state.get(), type, &input[0], sending);
+ write_record(cipher_state.get(), epoch, type, &input[0], sending);
input += sending;
length -= sending;
diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h
index 6c159689a..3cdfe3d5e 100644
--- a/src/lib/tls/tls_channel.h
+++ b/src/lib/tls/tls_channel.h
@@ -1,6 +1,6 @@
/*
* TLS Channel
-* (C) 2011,2012 Jack Lloyd
+* (C) 2011,2012,2014 Jack Lloyd
*
* Released under the terms of the Botan license
*/
@@ -46,17 +46,28 @@ class BOTAN_DLL Channel
size_t received_data(const std::vector<byte>& buf);
/**
+ * Perform a handshake timeout check. This does nothing unless
+ * this is a DTLS channel with a pending handshake state, in
+ * which case we check for timeout and potentially retransmit
+ * handshake packets.
+ */
+ bool timeout_check();
+
+ /**
* Inject plaintext intended for counterparty
+ * Throws an exception if is_active() is false
*/
void send(const byte buf[], size_t buf_size);
/**
* Inject plaintext intended for counterparty
+ * Throws an exception if is_active() is false
*/
void send(const std::string& val);
/**
* Inject plaintext intended for counterparty
+ * Throws an exception if is_active() is false
*/
template<typename Alloc>
void send(const std::vector<unsigned char, Alloc>& val)
@@ -209,7 +220,7 @@ class BOTAN_DLL Channel
const byte input[], size_t length);
void write_record(Connection_Cipher_State* cipher_state,
- byte type, const byte input[], size_t length);
+ u16bit epoch, byte type, const byte input[], size_t length);
Connection_Sequence_Numbers& sequence_numbers() const;
diff --git a/src/lib/tls/tls_handshake_io.cpp b/src/lib/tls/tls_handshake_io.cpp
index 287918841..da27cc4ce 100644
--- a/src/lib/tls/tls_handshake_io.cpp
+++ b/src/lib/tls/tls_handshake_io.cpp
@@ -1,6 +1,6 @@
/*
* TLS Handshake IO
-* (C) 2012 Jack Lloyd
+* (C) 2012,2014 Jack Lloyd
*
* Released under the terms of the Botan license
*/
@@ -10,6 +10,7 @@
#include <botan/internal/tls_record.h>
#include <botan/internal/tls_seq_numbers.h>
#include <botan/exceptn.h>
+#include <chrono>
namespace Botan {
@@ -56,7 +57,7 @@ void Stream_Handshake_IO::add_record(const std::vector<byte>& record,
m_queue.insert(m_queue.end(), ccs_hs, ccs_hs + sizeof(ccs_hs));
}
else
- throw Decoding_Error("Unknown message type in handshake processing");
+ throw Decoding_Error("Unknown message type " + std::to_string(record_type) + " in handshake processing");
}
std::pair<Handshake_Type, std::vector<byte>>
@@ -119,6 +120,65 @@ Protocol_Version Datagram_Handshake_IO::initial_record_version() const
return Protocol_Version::DTLS_V10;
}
+namespace {
+
+// 1 second initial timeout, 60 second max - see RFC 6347 sec 4.2.4.1
+const u64bit INITIAL_TIMEOUT = 1*1000;
+const u64bit MAXIMUM_TIMEOUT = 60*1000;
+
+u64bit steady_clock_ms()
+ {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now().time_since_epoch()).count();
+ }
+
+}
+
+bool Datagram_Handshake_IO::timeout_check()
+ {
+ if(m_last_write == 0 || (m_flights.size() > 1 && !m_flights.rbegin()->empty()))
+ {
+ /*
+ If we haven't written anything yet obviously no timeout.
+ Also no timeout possible if we are mid-flight,
+ */
+ return false;
+ }
+
+ const u64bit ms_since_write = steady_clock_ms() - m_last_write;
+
+ if(ms_since_write < m_next_timeout)
+ return false;
+
+ std::vector<u16bit> flight;
+ if(m_flights.size() == 1)
+ flight = m_flights.at(0); // lost initial client hello
+ else
+ flight = m_flights.at(m_flights.size() - 2);
+
+ BOTAN_ASSERT(flight.size() > 0, "Nonempty flight to retransmit");
+
+ u16bit epoch = m_flight_data[flight[0]].epoch;
+
+ for(auto msg_seq : flight)
+ {
+ auto& msg = m_flight_data[msg_seq];
+
+ if(msg.epoch != epoch)
+ {
+ // Epoch gap: insert the CCS
+ std::vector<byte> ccs(1, 1);
+ m_send_hs(epoch, CHANGE_CIPHER_SPEC, ccs);
+ }
+
+ send_message(msg_seq, msg.epoch, msg.msg_type, msg.msg_bits);
+ epoch = msg.epoch;
+ }
+
+ m_next_timeout = std::min(2 * m_next_timeout, MAXIMUM_TIMEOUT);
+ return true;
+ }
+
void Datagram_Handshake_IO::add_record(const std::vector<byte>& record,
Record_Type record_type,
u64bit record_sequence)
@@ -127,6 +187,7 @@ void Datagram_Handshake_IO::add_record(const std::vector<byte>& record,
if(record_type == CHANGE_CIPHER_SPEC)
{
+ // TODO: check this is otherwise empty
m_ccs_epochs.insert(epoch);
return;
}
@@ -161,6 +222,10 @@ void Datagram_Handshake_IO::add_record(const std::vector<byte>& record,
msg_type,
msg_len);
}
+ else
+ {
+ // TODO: detect retransmitted flight
+ }
record_bits += total_size;
record_size -= total_size;
@@ -170,6 +235,7 @@ void Datagram_Handshake_IO::add_record(const std::vector<byte>& record,
std::pair<Handshake_Type, std::vector<byte>>
Datagram_Handshake_IO::get_next_record(bool expecting_ccs)
{
+ // Expecting a message means the last flight is concluded
if(!m_flights.rbegin()->empty())
m_flights.push_back(std::vector<u16bit>());
@@ -215,7 +281,7 @@ void Datagram_Handshake_IO::Handshake_Reassembly::add_fragment(
}
if(msg_type != m_msg_type || msg_length != m_msg_length || epoch != m_epoch)
- throw Decoding_Error("Inconsistent values in DTLS handshake header");
+ throw Decoding_Error("Inconsistent values in fragmented DTLS handshake header");
if(fragment_offset > m_msg_length)
throw Decoding_Error("Fragment offset past end of message");
@@ -327,16 +393,30 @@ Datagram_Handshake_IO::send(const Handshake_Message& msg)
const u16bit epoch = m_seqs.current_write_epoch();
const Handshake_Type msg_type = msg.type();
- std::tuple<u16bit, byte, std::vector<byte>> msg_info(epoch, msg_type, msg_bits);
-
if(msg_type == HANDSHAKE_CCS)
{
m_send_hs(epoch, CHANGE_CIPHER_SPEC, msg_bits);
return std::vector<byte>(); // not included in handshake hashes
}
+ // Note: not saving CCS, instead we know it was there due to change in epoch
+ m_flights.rbegin()->push_back(m_out_message_seq);
+ m_flight_data[m_out_message_seq] = Message_Info(epoch, msg_type, msg_bits);
+
+ m_out_message_seq += 1;
+ m_last_write = steady_clock_ms();
+ m_next_timeout = INITIAL_TIMEOUT;
+
+ return send_message(m_out_message_seq - 1, epoch, msg_type, msg_bits);
+ }
+
+std::vector<byte> Datagram_Handshake_IO::send_message(u16bit msg_seq,
+ u16bit epoch,
+ Handshake_Type msg_type,
+ const std::vector<byte>& msg_bits)
+ {
const std::vector<byte> no_fragment =
- format_w_seq(msg_bits, msg_type, m_out_message_seq);
+ format_w_seq(msg_bits, msg_type, msg_seq);
if(no_fragment.size() + DTLS_HEADER_SIZE <= m_mtu)
m_send_hs(epoch, HANDSHAKE, no_fragment);
@@ -361,21 +441,14 @@ Datagram_Handshake_IO::send(const Handshake_Message& msg)
frag_offset,
msg_bits.size(),
msg_type,
- m_out_message_seq));
+ msg_seq));
frag_offset += frag_len;
}
}
- // Note: not saving CCS, instead we know it was there due to change in epoch
- m_flights.rbegin()->push_back(m_out_message_seq);
- m_flight_data[m_out_message_seq] = msg_info;
-
- m_out_message_seq += 1;
-
return no_fragment;
}
}
-
}
diff --git a/src/lib/tls/tls_handshake_io.h b/src/lib/tls/tls_handshake_io.h
index 36c605c30..b13a81700 100644
--- a/src/lib/tls/tls_handshake_io.h
+++ b/src/lib/tls/tls_handshake_io.h
@@ -1,6 +1,6 @@
/*
* TLS Handshake Serialization
-* (C) 2012 Jack Lloyd
+* (C) 2012,2014 Jack Lloyd
*
* Released under the terms of the Botan license
*/
@@ -17,7 +17,6 @@
#include <map>
#include <set>
#include <utility>
-#include <tuple>
namespace Botan {
@@ -35,6 +34,8 @@ class Handshake_IO
virtual std::vector<byte> send(const Handshake_Message& msg) = 0;
+ virtual bool timeout_check() = 0;
+
virtual std::vector<byte> format(
const std::vector<byte>& handshake_msg,
Handshake_Type handshake_type) const = 0;
@@ -69,6 +70,8 @@ class Stream_Handshake_IO : public Handshake_IO
Protocol_Version initial_record_version() const override;
+ bool timeout_check() override { return false; }
+
std::vector<byte> send(const Handshake_Message& msg) override;
std::vector<byte> format(
@@ -93,11 +96,14 @@ class Datagram_Handshake_IO : public Handshake_IO
{
public:
Datagram_Handshake_IO(class Connection_Sequence_Numbers& seq,
- std::function<void (u16bit, byte, const std::vector<byte>&)> writer) :
- m_seqs(seq), m_flights(1), m_send_hs(writer) {}
+ std::function<void (u16bit, byte, const std::vector<byte>&)> writer,
+ u16bit mtu) :
+ m_seqs(seq), m_flights(1), m_send_hs(writer), m_mtu(mtu) {}
Protocol_Version initial_record_version() const override;
+ bool timeout_check() override;
+
std::vector<byte> send(const Handshake_Message& msg) override;
std::vector<byte> format(
@@ -124,6 +130,10 @@ class Datagram_Handshake_IO : public Handshake_IO
Handshake_Type handshake_type,
u16bit msg_sequence) const;
+ std::vector<byte> send_message(u16bit msg_seq, u16bit epoch,
+ Handshake_Type msg_type,
+ const std::vector<byte>& msg);
+
class Handshake_Reassembly
{
public:
@@ -144,21 +154,40 @@ class Datagram_Handshake_IO : public Handshake_IO
size_t m_msg_length = 0;
u16bit m_epoch = 0;
+ // vector<bool> m_seen;
+ // vector<byte> m_fragments
std::map<size_t, byte> m_fragments;
std::vector<byte> m_message;
};
+ struct Message_Info
+ {
+ Message_Info(u16bit e, Handshake_Type mt, const std::vector<byte>& msg) :
+ epoch(e), msg_type(mt), msg_bits(msg) {}
+
+ Message_Info(const Message_Info& other) = default;
+
+ Message_Info() : epoch(0xFFFF), msg_type(HANDSHAKE_NONE) {}
+
+ u16bit epoch;
+ Handshake_Type msg_type;
+ std::vector<byte> msg_bits;
+ };
+
class Connection_Sequence_Numbers& m_seqs;
std::map<u16bit, Handshake_Reassembly> m_messages;
std::set<u16bit> m_ccs_epochs;
std::vector<std::vector<u16bit>> m_flights;
- std::map<u16bit, std::tuple<u16bit, byte, std::vector<byte>>> m_flight_data;
+ std::map<u16bit, Message_Info> m_flight_data;
+
+ u64bit m_last_write = 0;
+ u64bit m_next_timeout = 0;
- // default MTU is IPv6 min MTU minus UDP/IP headers
- u16bit m_mtu = 1280 - 40 - 8;
u16bit m_in_message_seq = 0;
u16bit m_out_message_seq = 0;
+
std::function<void (u16bit, byte, const std::vector<byte>&)> m_send_hs;
+ u16bit m_mtu;
};
}
diff --git a/src/lib/tls/tls_reader.h b/src/lib/tls/tls_reader.h
index 1c4f0f456..028893cc1 100644
--- a/src/lib/tls/tls_reader.h
+++ b/src/lib/tls/tls_reader.h
@@ -92,8 +92,8 @@ class TLS_Data_Reader
template<typename T>
std::vector<T> get_range(size_t len_bytes,
- size_t min_elems,
- size_t max_elems)
+ size_t min_elems,
+ size_t max_elems)
{
const size_t num_elems =
get_num_elems(len_bytes, sizeof(T), min_elems, max_elems);
diff --git a/src/lib/tls/tls_record.cpp b/src/lib/tls/tls_record.cpp
index fc4908dc5..be0777573 100644
--- a/src/lib/tls/tls_record.cpp
+++ b/src/lib/tls/tls_record.cpp
@@ -1,6 +1,6 @@
/*
* TLS Record Handling
-* (C) 2012,2013 Jack Lloyd
+* (C) 2012,2013,2014 Jack Lloyd
*
* Released under the terms of the Botan license
*/
@@ -477,7 +477,7 @@ size_t read_record(secure_vector<byte>& readbuf,
Protocol_Version* record_version,
Record_Type* record_type,
Connection_Sequence_Numbers* sequence_numbers,
- std::function<Connection_Cipher_State* (u16bit)> get_cipherstate)
+ std::function<std::shared_ptr<Connection_Cipher_State> (u16bit)> get_cipherstate)
{
consumed = 0;
@@ -584,7 +584,10 @@ size_t read_record(secure_vector<byte>& readbuf,
}
if(sequence_numbers && sequence_numbers->already_seen(*record_sequence))
+ {
+ readbuf.clear();
return 0;
+ }
byte* record_contents = &readbuf[header_size];
@@ -596,7 +599,7 @@ size_t read_record(secure_vector<byte>& readbuf,
}
// Otherwise, decrypt, check MAC, return plaintext
- Connection_Cipher_State* cipherstate = get_cipherstate(epoch);
+ auto cipherstate = get_cipherstate(epoch);
// FIXME: DTLS reordering might cause us not to have the cipher state
diff --git a/src/lib/tls/tls_record.h b/src/lib/tls/tls_record.h
index c9f164407..fb727753a 100644
--- a/src/lib/tls/tls_record.h
+++ b/src/lib/tls/tls_record.h
@@ -125,7 +125,7 @@ size_t read_record(secure_vector<byte>& read_buffer,
Protocol_Version* record_version,
Record_Type* record_type,
Connection_Sequence_Numbers* sequence_numbers,
- std::function<Connection_Cipher_State* (u16bit)> get_cipherstate);
+ std::function<std::shared_ptr<Connection_Cipher_State> (u16bit)> get_cipherstate);
}
diff --git a/src/lib/tls/tls_seq_numbers.h b/src/lib/tls/tls_seq_numbers.h
index 87edf3130..d7b8c919c 100644
--- a/src/lib/tls/tls_seq_numbers.h
+++ b/src/lib/tls/tls_seq_numbers.h
@@ -24,7 +24,7 @@ class Connection_Sequence_Numbers
virtual u16bit current_read_epoch() const = 0;
virtual u16bit current_write_epoch() const = 0;
- virtual u64bit next_write_sequence() = 0;
+ virtual u64bit next_write_sequence(u16bit) = 0;
virtual u64bit next_read_sequence() = 0;
virtual bool already_seen(u64bit seq) const = 0;
@@ -40,7 +40,7 @@ class Stream_Sequence_Numbers : public Connection_Sequence_Numbers
u16bit current_read_epoch() const override { return m_read_epoch; }
u16bit current_write_epoch() const override { return m_write_epoch; }
- u64bit next_write_sequence() override { return m_write_seq_no++; }
+ u64bit next_write_sequence(u16bit) override { return m_write_seq_no++; }
u64bit next_read_sequence() override { return m_read_seq_no; }
bool already_seen(u64bit) const override { return false; }
@@ -55,18 +55,25 @@ class Stream_Sequence_Numbers : public Connection_Sequence_Numbers
class Datagram_Sequence_Numbers : public Connection_Sequence_Numbers
{
public:
+ Datagram_Sequence_Numbers() { m_write_seqs[0] = 0; }
+
void new_read_cipher_state() override { m_read_epoch += 1; }
void new_write_cipher_state() override
{
- // increment epoch
- m_write_seq_no = ((m_write_seq_no >> 48) + 1) << 48;
+ m_write_epoch += 1;
+ m_write_seqs[m_write_epoch] = 0;
}
u16bit current_read_epoch() const override { return m_read_epoch; }
- u16bit current_write_epoch() const override { return (m_write_seq_no >> 48); }
+ u16bit current_write_epoch() const override { return m_write_epoch; }
- u64bit next_write_sequence() override { return m_write_seq_no++; }
+ u64bit next_write_sequence(u16bit epoch) override
+ {
+ auto i = m_write_seqs.find(epoch);
+ BOTAN_ASSERT(i != m_write_seqs.end(), "Found epoch");
+ return (static_cast<u64bit>(epoch) << 48) | i->second++;
+ }
u64bit next_read_sequence() override
{
@@ -112,7 +119,8 @@ class Datagram_Sequence_Numbers : public Connection_Sequence_Numbers
}
private:
- u64bit m_write_seq_no = 0;
+ std::map<u16bit, u64bit> m_write_seqs;
+ u16bit m_write_epoch = 0;
u16bit m_read_epoch = 0;
u64bit m_window_highest = 0;
u64bit m_window_bits = 0;