mirror of
https://github.com/mfontanini/libtins
synced 2026-01-23 02:35:57 +01:00
Move TCP data tracking into a separate class
This commit is contained in:
138
include/tins/tcp_ip/data_tracker.h
Normal file
138
include/tins/tcp_ip/data_tracker.h
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Matias Fontanini
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TINS_TCP_IP_DATA_TRACKER_H
|
||||
#define TINS_TCP_IP_DATA_TRACKER_H
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include "../config.h"
|
||||
|
||||
#ifdef TINS_HAVE_TCPIP
|
||||
|
||||
namespace Tins {
|
||||
namespace TCPIP {
|
||||
|
||||
/**
|
||||
* \class DataTracker
|
||||
*
|
||||
* Stores and tracks data in a TCP stream, reassembling segments, handling
|
||||
* out of order packets, etc.
|
||||
*/
|
||||
class DataTracker {
|
||||
public:
|
||||
/**
|
||||
* The type used to store the payload
|
||||
*/
|
||||
typedef std::vector<uint8_t> payload_type;
|
||||
|
||||
/**
|
||||
* The type used to store the buffered payload
|
||||
*/
|
||||
typedef std::map<uint32_t, payload_type> buffered_payload_type;
|
||||
|
||||
/**
|
||||
* Default constructs an instance
|
||||
*/
|
||||
DataTracker();
|
||||
|
||||
/**
|
||||
* \brief Constructs an instance using the given sequence number as the initial one
|
||||
*
|
||||
* \param seq_number The sequence number to use
|
||||
*/
|
||||
DataTracker(uint32_t seq_number);
|
||||
|
||||
/**
|
||||
* \brief Processes the given payload
|
||||
*
|
||||
* This will buffer the given data on the payload buffer or store it on the
|
||||
* buffered payload map, depending the sequence number given.
|
||||
*
|
||||
* This method returns true iff any data was added to the payload buffer. That is
|
||||
* if this method returns true, then the size of the payload will be greater than
|
||||
* what it was before calling the function.
|
||||
*
|
||||
* \brief seq The payload's sequence number
|
||||
* \brief payload The payload to process
|
||||
* \return true iff any data was added to the payload buffer
|
||||
*/
|
||||
bool process_payload(uint32_t seq, payload_type payload);
|
||||
|
||||
/**
|
||||
* Retrieves the current sequence number
|
||||
*/
|
||||
uint32_t sequence_number() const;
|
||||
|
||||
/**
|
||||
* Sets the current sequence number
|
||||
*/
|
||||
void sequence_number(uint32_t seq);
|
||||
|
||||
/**
|
||||
* Retrieves the available payload (const)
|
||||
*/
|
||||
const payload_type& payload() const;
|
||||
|
||||
/**
|
||||
* Retrieves the available payload
|
||||
*/
|
||||
payload_type& payload();
|
||||
|
||||
/**
|
||||
* Retrieves the buffered payload (const)
|
||||
*/
|
||||
const buffered_payload_type& buffered_payload() const;
|
||||
|
||||
/**
|
||||
* Retrieves the buffered payload
|
||||
*/
|
||||
buffered_payload_type& buffered_payload();
|
||||
|
||||
/**
|
||||
* Retrieves the total amount of buffered bytes
|
||||
*/
|
||||
uint32_t total_buffered_bytes() const;
|
||||
private:
|
||||
void store_payload(uint32_t seq, payload_type payload);
|
||||
buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter);
|
||||
|
||||
payload_type payload_;
|
||||
buffered_payload_type buffered_payload_;
|
||||
uint32_t seq_number_;
|
||||
uint32_t total_buffered_bytes_;
|
||||
};
|
||||
|
||||
} // TCPIP
|
||||
} // Tins
|
||||
|
||||
#endif // TINS_HAVE_TCPIP
|
||||
|
||||
#endif // TINS_TCP_IP_DATA_TRACKER_H
|
||||
@@ -39,9 +39,10 @@
|
||||
#include <map>
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
#include "../hw_address.h"
|
||||
#include "../macros.h"
|
||||
#include "ack_tracker.h"
|
||||
#include "../hw_address.h"
|
||||
#include "data_tracker.h"
|
||||
|
||||
namespace Tins {
|
||||
|
||||
@@ -86,12 +87,12 @@ public:
|
||||
/**
|
||||
* The type used to store the payload
|
||||
*/
|
||||
typedef std::vector<uint8_t> payload_type;
|
||||
typedef DataTracker::payload_type payload_type;
|
||||
|
||||
/**
|
||||
* The type used to store the buffered payload
|
||||
*/
|
||||
typedef std::map<uint32_t, payload_type> buffered_payload_type;
|
||||
typedef DataTracker::buffered_payload_type buffered_payload_type;
|
||||
|
||||
/**
|
||||
* The type used to store the callback called when new data is available
|
||||
@@ -212,7 +213,7 @@ public:
|
||||
const payload_type& payload() const;
|
||||
|
||||
/**
|
||||
* Retrieves this flow's destination port
|
||||
* Retrieves this flow's payload
|
||||
*/
|
||||
payload_type& payload();
|
||||
|
||||
@@ -306,15 +307,10 @@ private:
|
||||
ack_tracking:1;
|
||||
};
|
||||
|
||||
void store_payload(uint32_t seq, payload_type payload);
|
||||
buffered_payload_type::iterator erase_iterator(buffered_payload_type::iterator iter);
|
||||
void update_state(const TCP& tcp);
|
||||
void initialize();
|
||||
|
||||
payload_type payload_;
|
||||
buffered_payload_type buffered_payload_;
|
||||
uint32_t seq_number_;
|
||||
uint32_t total_buffered_bytes_;
|
||||
DataTracker data_tracker_;
|
||||
std::array<uint8_t, 16> dest_address_;
|
||||
uint16_t dest_port_;
|
||||
data_available_callback_type on_data_callback_;
|
||||
|
||||
@@ -55,6 +55,7 @@ ADD_LIBRARY(
|
||||
tcp.cpp
|
||||
tcp_ip/ack_tracker.cpp
|
||||
tcp_ip/flow.cpp
|
||||
tcp_ip/data_tracker.cpp
|
||||
tcp_ip/stream.cpp
|
||||
tcp_ip/stream_follower.cpp
|
||||
tcp_ip/stream_identifier.cpp
|
||||
|
||||
170
src/tcp_ip/data_tracker.cpp
Normal file
170
src/tcp_ip/data_tracker.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2016, Matias Fontanini
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following disclaimer
|
||||
* in the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "tcp_ip/data_tracker.h"
|
||||
|
||||
#ifdef TINS_HAVE_TCPIP
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
using std::move;
|
||||
|
||||
using Tins::Internals::seq_compare;
|
||||
|
||||
namespace Tins {
|
||||
namespace TCPIP {
|
||||
|
||||
DataTracker::DataTracker()
|
||||
: seq_number_(0), total_buffered_bytes_(0) {
|
||||
|
||||
}
|
||||
|
||||
DataTracker::DataTracker(uint32_t seq_number)
|
||||
: seq_number_(seq_number), total_buffered_bytes_(0) {
|
||||
|
||||
}
|
||||
|
||||
bool DataTracker::process_payload(uint32_t seq, payload_type payload) {
|
||||
const uint32_t chunk_end = seq + payload.size();
|
||||
// If the end of the chunk ends before current sequence number, ignore it.
|
||||
if (seq_compare(chunk_end, seq_number_) < 0) {
|
||||
return false;
|
||||
}
|
||||
// If it starts before our sequence number, slice it
|
||||
if (seq_compare(seq, seq_number_) < 0) {
|
||||
const uint32_t diff = seq_number_ - seq;
|
||||
payload.erase(
|
||||
payload.begin(),
|
||||
payload.begin() + diff
|
||||
);
|
||||
seq = seq_number_;
|
||||
}
|
||||
bool added_some = false;
|
||||
// Store this payload
|
||||
store_payload(seq, move(payload));
|
||||
// Keep looping while the fragments seq is lower or equal to our seq
|
||||
buffered_payload_type::iterator iter = buffered_payload_.find(seq_number_);
|
||||
while (iter != buffered_payload_.end() && seq_compare(iter->first, seq_number_) <= 0) {
|
||||
// Does this fragment start before our sequence number?
|
||||
if (seq_compare(iter->first, seq_number_) < 0) {
|
||||
uint32_t fragment_end = iter->first + iter->second.size();
|
||||
int comparison = seq_compare(fragment_end, seq_number_);
|
||||
// Does it end after our sequence number?
|
||||
if (comparison > 0) {
|
||||
// Then slice it
|
||||
payload_type& payload = iter->second;
|
||||
// First update this counter
|
||||
total_buffered_bytes_ -= payload.size();
|
||||
payload.erase(
|
||||
payload.begin(),
|
||||
payload.begin() + (seq_number_ - iter->first)
|
||||
);
|
||||
store_payload(seq_number_, move(iter->second));
|
||||
iter = erase_iterator(iter);
|
||||
}
|
||||
else {
|
||||
// Otherwise, we've seen this part of the payload. Erase it.
|
||||
iter = erase_iterator(iter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// They're equal. Add this payload.
|
||||
payload_.insert(
|
||||
payload_.end(),
|
||||
iter->second.begin(),
|
||||
iter->second.end()
|
||||
);
|
||||
seq_number_ += iter->second.size();
|
||||
iter = erase_iterator(iter);
|
||||
added_some = true;
|
||||
}
|
||||
}
|
||||
return added_some;
|
||||
}
|
||||
|
||||
uint32_t DataTracker::sequence_number() const {
|
||||
return seq_number_;
|
||||
}
|
||||
|
||||
void DataTracker::sequence_number(uint32_t seq) {
|
||||
seq_number_ = seq;
|
||||
}
|
||||
|
||||
const DataTracker::payload_type& DataTracker::payload() const {
|
||||
return payload_;
|
||||
}
|
||||
|
||||
DataTracker::payload_type& DataTracker::payload() {
|
||||
return payload_;
|
||||
}
|
||||
|
||||
const DataTracker::buffered_payload_type& DataTracker::buffered_payload() const {
|
||||
return buffered_payload_;
|
||||
}
|
||||
|
||||
DataTracker::buffered_payload_type& DataTracker::buffered_payload() {
|
||||
return buffered_payload_;
|
||||
}
|
||||
|
||||
uint32_t DataTracker::total_buffered_bytes() const {
|
||||
return total_buffered_bytes_;
|
||||
}
|
||||
|
||||
void DataTracker::store_payload(uint32_t seq, payload_type payload) {
|
||||
buffered_payload_type::iterator iter = buffered_payload_.find(seq);
|
||||
// New segment, store it
|
||||
if (iter == buffered_payload_.end()) {
|
||||
total_buffered_bytes_ += payload.size();
|
||||
buffered_payload_.insert(make_pair(seq, move(payload)));
|
||||
}
|
||||
else if (iter->second.size() < payload.size()) {
|
||||
// Increment by the diff between sizes
|
||||
total_buffered_bytes_ += (payload.size() - iter->second.size());
|
||||
// If we already have payload on this position but it's a shorter
|
||||
// chunk than the new one, replace it
|
||||
iter->second = move(payload);
|
||||
}
|
||||
}
|
||||
|
||||
DataTracker::buffered_payload_type::iterator
|
||||
DataTracker::erase_iterator(buffered_payload_type::iterator iter) {
|
||||
buffered_payload_type::iterator output = iter;
|
||||
total_buffered_bytes_ -= iter->second.size();
|
||||
++output;
|
||||
buffered_payload_.erase(iter);
|
||||
if (output == buffered_payload_.end()) {
|
||||
output = buffered_payload_.begin();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
} // TCPIP
|
||||
} // Tins
|
||||
|
||||
#endif // TINS_HAVE_TCPIP
|
||||
@@ -61,7 +61,7 @@ namespace TCPIP {
|
||||
|
||||
Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port,
|
||||
uint32_t sequence_number)
|
||||
: seq_number_(sequence_number), dest_port_(dest_port) {
|
||||
: data_tracker_(sequence_number), dest_port_(dest_port) {
|
||||
OutputMemoryStream output(dest_address_.data(), dest_address_.size());
|
||||
output.write(dest_address);
|
||||
flags_.is_v6 = false;
|
||||
@@ -70,7 +70,7 @@ Flow::Flow(const IPv4Address& dest_address, uint16_t dest_port,
|
||||
|
||||
Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port,
|
||||
uint32_t sequence_number)
|
||||
: seq_number_(sequence_number), dest_port_(dest_port) {
|
||||
: data_tracker_(sequence_number), dest_port_(dest_port) {
|
||||
OutputMemoryStream output(dest_address_.data(), dest_address_.size());
|
||||
output.write(dest_address);
|
||||
flags_.is_v6 = true;
|
||||
@@ -78,7 +78,6 @@ Flow::Flow(const IPv6Address& dest_address, uint16_t dest_port,
|
||||
}
|
||||
|
||||
void Flow::initialize() {
|
||||
total_buffered_bytes_ = 0;
|
||||
state_ = UNKNOWN;
|
||||
mss_ = -1;
|
||||
}
|
||||
@@ -110,64 +109,15 @@ void Flow::process_packet(PDU& pdu) {
|
||||
return;
|
||||
}
|
||||
const uint32_t chunk_end = tcp->seq() + raw->payload_size();
|
||||
const uint32_t current_seq = data_tracker_.sequence_number();
|
||||
// If the end of the chunk ends after our current sequence number, process it.
|
||||
if (seq_compare(chunk_end, seq_number_) >= 0) {
|
||||
bool added_some = false;
|
||||
if (seq_compare(chunk_end, current_seq) >= 0) {
|
||||
uint32_t seq = tcp->seq();
|
||||
// If we're going to buffer this and we have a buffering callback, execute it
|
||||
if (seq > seq_number_ && on_out_of_order_callback_) {
|
||||
if (seq > current_seq && on_out_of_order_callback_) {
|
||||
on_out_of_order_callback_(*this, seq, raw->payload());
|
||||
}
|
||||
|
||||
// If it starts before our sequence number, slice it
|
||||
if (seq_compare(seq, seq_number_) < 0) {
|
||||
const uint32_t diff = seq_number_ - seq;
|
||||
raw->payload().erase(
|
||||
raw->payload().begin(),
|
||||
raw->payload().begin() + diff
|
||||
);
|
||||
seq = seq_number_;
|
||||
}
|
||||
// Store this payload
|
||||
store_payload(seq, move(raw->payload()));
|
||||
// Keep looping while the fragments seq is lower or equal to our seq
|
||||
buffered_payload_type::iterator iter = buffered_payload_.find(seq_number_);
|
||||
while (iter != buffered_payload_.end() && seq_compare(iter->first, seq_number_) <= 0) {
|
||||
// Does this fragment start before our sequence number?
|
||||
if (seq_compare(iter->first, seq_number_) < 0) {
|
||||
uint32_t fragment_end = iter->first + iter->second.size();
|
||||
int comparison = seq_compare(fragment_end, seq_number_);
|
||||
// Does it end after our sequence number?
|
||||
if (comparison > 0) {
|
||||
// Then slice it
|
||||
payload_type& payload = iter->second;
|
||||
// First update this counter
|
||||
total_buffered_bytes_ -= payload.size();
|
||||
payload.erase(
|
||||
payload.begin(),
|
||||
payload.begin() + (seq_number_ - iter->first)
|
||||
);
|
||||
store_payload(seq_number_, move(iter->second));
|
||||
iter = erase_iterator(iter);
|
||||
}
|
||||
else {
|
||||
// Otherwise, we've seen this part of the payload. Erase it.
|
||||
iter = erase_iterator(iter);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// They're equal. Add this payload.
|
||||
payload_.insert(
|
||||
payload_.end(),
|
||||
iter->second.begin(),
|
||||
iter->second.end()
|
||||
);
|
||||
seq_number_ += iter->second.size();
|
||||
iter = erase_iterator(iter);
|
||||
added_some = true;
|
||||
}
|
||||
}
|
||||
if (added_some) {
|
||||
if (data_tracker_.process_payload(seq, move(raw->payload()))) {
|
||||
if (on_data_callback_) {
|
||||
on_data_callback_(*this);
|
||||
}
|
||||
@@ -178,33 +128,6 @@ void Flow::process_packet(PDU& pdu) {
|
||||
}
|
||||
}
|
||||
|
||||
void Flow::store_payload(uint32_t seq, payload_type payload) {
|
||||
buffered_payload_type::iterator iter = buffered_payload_.find(seq);
|
||||
// New segment, store it
|
||||
if (iter == buffered_payload_.end()) {
|
||||
total_buffered_bytes_ += payload.size();
|
||||
buffered_payload_.insert(make_pair(seq, move(payload)));
|
||||
}
|
||||
else if (iter->second.size() < payload.size()) {
|
||||
// Increment by the diff between sizes
|
||||
total_buffered_bytes_ += (payload.size() - iter->second.size());
|
||||
// If we already have payload on this position but it's a shorter
|
||||
// chunk than the new one, replace it
|
||||
iter->second = move(payload);
|
||||
}
|
||||
}
|
||||
|
||||
Flow::buffered_payload_type::iterator Flow::erase_iterator(buffered_payload_type::iterator iter) {
|
||||
buffered_payload_type::iterator output = iter;
|
||||
total_buffered_bytes_ -= iter->second.size();
|
||||
++output;
|
||||
buffered_payload_.erase(iter);
|
||||
if (output == buffered_payload_.end()) {
|
||||
output = buffered_payload_.begin();
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
void Flow::update_state(const TCP& tcp) {
|
||||
if ((tcp.flags() & TCP::FIN) != 0) {
|
||||
state_ = FIN_SENT;
|
||||
@@ -217,7 +140,7 @@ void Flow::update_state(const TCP& tcp) {
|
||||
ack_tracker_ = AckTracker(tcp.ack_seq());
|
||||
#endif // TINS_HAVE_ACK_TRACKER
|
||||
state_ = ESTABLISHED;
|
||||
seq_number_++;
|
||||
data_tracker_.sequence_number(data_tracker_.sequence_number() + 1);
|
||||
}
|
||||
else if (state_ == UNKNOWN && (tcp.flags() & TCP::SYN) != 0) {
|
||||
// This is the server's state, sending it's first SYN|ACK
|
||||
@@ -225,7 +148,7 @@ void Flow::update_state(const TCP& tcp) {
|
||||
ack_tracker_ = AckTracker(tcp.ack_seq());
|
||||
#endif // TINS_HAVE_ACK_TRACKER
|
||||
state_ = SYN_SENT;
|
||||
seq_number_ = tcp.seq();
|
||||
data_tracker_.sequence_number(tcp.seq());
|
||||
const TCP::option* mss_option = tcp.search_option(TCP::MSS);
|
||||
if (mss_option) {
|
||||
mss_ = mss_option->to<uint16_t>();
|
||||
@@ -274,7 +197,7 @@ uint16_t Flow::dport() const {
|
||||
}
|
||||
|
||||
const Flow::payload_type& Flow::payload() const {
|
||||
return payload_;
|
||||
return data_tracker_.payload();
|
||||
}
|
||||
|
||||
Flow::State Flow::state() const {
|
||||
@@ -282,23 +205,23 @@ Flow::State Flow::state() const {
|
||||
}
|
||||
|
||||
uint32_t Flow::sequence_number() const {
|
||||
return seq_number_;
|
||||
return data_tracker_.sequence_number();
|
||||
}
|
||||
|
||||
const Flow::buffered_payload_type& Flow::buffered_payload() const {
|
||||
return buffered_payload_;
|
||||
return data_tracker_.buffered_payload();
|
||||
}
|
||||
|
||||
Flow::buffered_payload_type& Flow::buffered_payload() {
|
||||
return buffered_payload_;
|
||||
return data_tracker_.buffered_payload();
|
||||
}
|
||||
|
||||
uint32_t Flow::total_buffered_bytes() const {
|
||||
return total_buffered_bytes_;
|
||||
return data_tracker_.total_buffered_bytes();
|
||||
}
|
||||
|
||||
Flow::payload_type& Flow::payload() {
|
||||
return payload_;
|
||||
return data_tracker_.payload();
|
||||
}
|
||||
|
||||
void Flow::state(State new_state) {
|
||||
|
||||
Reference in New Issue
Block a user