more unfinished header writing work

This commit is contained in:
Peter Thorson
2011-12-26 11:45:41 -06:00
parent f933519e2b
commit a848d1dde5
6 changed files with 300 additions and 329 deletions

View File

@@ -99,53 +99,52 @@ namespace websocketpp {
}
// TODO functions for application ranges?
}
}
} // namespace status
} // namespace close
namespace frame {
// Opcodes are 4 bits
// See spec section 5.2
namespace opcode {
enum value {
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF,
};
inline bool reserved(value v) {
return (v >= RSV3 && v <= RSV7) ||
(v >= CONTROL_RSVB && v <= CONTROL_RSVF);
// Opcodes are 4 bits
// See spec section 5.2
namespace opcode {
enum value {
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF,
};
inline bool reserved(value v) {
return (v >= RSV3 && v <= RSV7) ||
(v >= CONTROL_RSVB && v <= CONTROL_RSVF);
}
inline bool invalid(value v) {
return (v > 0xF || v < 0);
}
inline bool is_control(value v) {
return v >= 0x8;
}
}
inline bool invalid(value v) {
return (v > 0xF || v < 0);
namespace limits {
static const uint8_t PAYLOAD_SIZE_BASIC = 125;
static const uint16_t PAYLOAD_SIZE_EXTENDED = 0xFFFF; // 2^16, 65535
static const uint64_t PAYLOAD_SIZE_JUMBO = 0x7FFFFFFFFFFFFFFF;//2^63
}
inline bool is_control(value v) {
return v >= 0x8;
}
}
namespace limits {
static const uint8_t PAYLOAD_SIZE_BASIC = 125;
static const uint16_t PAYLOAD_SIZE_EXTENDED = 0xFFFF; // 2^16, 65535
static const uint64_t PAYLOAD_SIZE_JUMBO = 0x7FFFFFFFFFFFFFFF;//2^63
}
} // namespace frame
}
#endif // WEBSOCKET_CONSTANTS_HPP

View File

@@ -140,3 +140,6 @@ void data::process() {
}
}
void data::set_header(const std::string& header) {
m_header = header;
}

View File

@@ -63,6 +63,8 @@ public:
// immediately and throws processor::exception if it fails
void set_payload(const std::string& payload);
void set_header(const std::string& header);
// Performs masking and header generation if it has not been done already.
void process();

View File

@@ -104,7 +104,10 @@ private:
template <class connection_type>
class hybi : public processor_base {
public:
hybi(connection_type &connection) : m_connection(connection),m_write_frame(connection) {
hybi(connection_type &connection)
: m_connection(connection),
m_write_frame(connection)
{
reset();
}
@@ -276,7 +279,8 @@ public:
m_control_message = m_connection.get_control_message();
if (!m_control_message) {
throw processor::exception("Out of control messages",processor::error::OUT_OF_MESSAGES);
throw processor::exception("Out of control messages",
processor::error::OUT_OF_MESSAGES);
}
m_control_message->reset(m_header.get_opcode(),m_header.get_masking_key());
@@ -300,7 +304,8 @@ public:
m_data_message = m_connection.get_data_message();
if (!m_data_message) {
throw processor::exception("Out of data messages",processor::error::OUT_OF_MESSAGES);
throw processor::exception("Out of data messages",
processor::error::OUT_OF_MESSAGES);
}
m_data_message->reset(m_header.get_opcode());
@@ -488,6 +493,19 @@ public:
return response;
}
// new prepare frame stuff
void prepare_frame(message::data_ptr msg, bool masked, int32_t mask) {
m_write_header.reset();
m_write_header.set_fin(true);
m_write_header.set_opcode(msg->get_opcode());
m_write_header.set_masked(masked,mask);
m_write_header.set_payload_size(msg->get_payload().size());
m_write_header.complete();
msg->set_header(m_write_header.get_header_bytes());
}
private:
connection_type& m_connection;
int m_state;
@@ -495,8 +513,10 @@ private:
message::data_ptr m_data_message;
message::control_ptr m_control_message;
hybi_header m_header;
hybi_header m_write_header;
uint64_t m_payload_left;
frame::parser<connection_type> m_write_frame; // TODO: refactor this out
};

View File

@@ -29,53 +29,23 @@
#include <cstring>
using websocketpp::processor::hybi_header_writer;
hybi_header_writer::hybi_header_writer() {
reset();
}
void hybi_header_writer::reset() {
memset(m_header, 0x00, MAX_HEADER_LENGTH);
}
std::string hybi_header_writer::get_header_bytes() const {
return std::string(m_header);
}
using websocketpp::processor::hybi_header;
hybi_header::hybi_header(bool reading) {
reset(reading);
hybi_header::hybi_header() {
reset();
}
uint64_t hybi_header::get_bytes_needed() const {
return m_bytes_needed;
}
void hybi_header::reset(bool reading) {
if (reading) {
m_state = STATE_BASIC_HEADER;
m_bytes_needed = BASIC_HEADER_LENGTH;
} else {
m_state = STATE_WRITE;
}
}
bool hybi_header::ready() const {
return m_state == STATE_READY;
void hybi_header::reset() {
memset(m_header, 0x00, MAX_HEADER_LENGTH);
m_state = STATE_BASIC_HEADER;
m_bytes_needed = BASIC_HEADER_LENGTH;
}
// Writing interface (parse a byte stream)
void hybi_header::consume(std::istream& input) {
switch (m_state) {
case STATE_BASIC_HEADER:
input.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],m_bytes_needed);
input.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],
m_bytes_needed);
m_bytes_needed -= input.gcount();
@@ -93,7 +63,8 @@ void hybi_header::consume(std::istream& input) {
}
break;
case STATE_EXTENDED_HEADER:
input.read(&m_header[get_header_len()-m_bytes_needed],m_bytes_needed);
input.read(&m_header[get_header_len()-m_bytes_needed],
m_bytes_needed);
m_bytes_needed -= input.gcount();
@@ -106,7 +77,113 @@ void hybi_header::consume(std::istream& input) {
break;
}
}
uint64_t hybi_header::get_bytes_needed() const {
return m_bytes_needed;
}
bool hybi_header::ready() const {
return m_state == STATE_READY;
}
// Writing interface (set fields directly)
void hybi_header::set_fin(bool fin) {
set_header_bit(BPB0_FIN,0,fin);
}
void hybi_header::set_rsv1(bool b) {
set_header_bit(BPB0_RSV1,0,b);
}
void hybi_header::set_rsv2(bool b) {
set_header_bit(BPB0_RSV2,0,b);
}
void hybi_header::set_rsv3(bool b) {
set_header_bit(BPB0_RSV3,0,b);
}
void hybi_header::set_opcode(frame::opcode::value op) {
m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits
m_header[0] |= op; // set op bits
}
void hybi_header::set_masked(bool masked,int32_t key) {
if (masked) {
m_header[1] |= BPB1_MASK;
set_masking_key(key);
} else {
m_header[1] &= (0xFF ^ BPB1_MASK);
clear_masking_key();
}
}
void hybi_header::set_payload_size(uint64_t size) {
if (size <= frame::limits::PAYLOAD_SIZE_BASIC) {
m_header[1] &= (size & BPB1_PAYLOAD);
m_payload_size = size;
} else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
if (get_masked()) {
// shift mask bytes to the correct position given the new size
unsigned int mask_offset = get_header_len()-4;
m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD);
memcpy(&m_header[get_header_len()-4], &m_header[mask_offset], 4);
} else {
m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD);
}
m_payload_size = size;
*(reinterpret_cast<uint16_t*>(&m_header[BASIC_HEADER_LENGTH])) = htons(size);
} else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
if (get_masked()) {
// shift mask bytes to the correct position given the new size
unsigned int mask_offset = get_header_len()-4;
m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD);
memcpy(&m_header[get_header_len()-4], &m_header[mask_offset], 4);
} else {
m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD);
}
m_payload_size = size;
*(reinterpret_cast<uint64_t*>(&m_header[BASIC_HEADER_LENGTH])) = htonll(size);
} else {
throw processor::exception("set_payload_size called with value that was too large (>2^63)",processor::error::MESSAGE_TOO_BIG);
}
}
void hybi_header::complete() {
validate_basic_header();
m_state = STATE_READY;
}
// Reading interface (get string of bytes)
std::string hybi_header::get_header_bytes() const {
return std::string(m_header,get_header_len());
}
// Reading interface (get fields directly)
bool hybi_header::get_fin() const {
return ((m_header[0] & BPB0_FIN) == BPB0_FIN);
}
bool hybi_header::get_rsv1() const {
return ((m_header[0] & BPB0_RSV1) == BPB0_RSV1);
}
bool hybi_header::get_rsv2() const {
return ((m_header[0] & BPB0_RSV2) == BPB0_RSV2);
}
bool hybi_header::get_rsv3() const {
return ((m_header[0] & BPB0_RSV3) == BPB0_RSV3);
}
websocketpp::frame::opcode::value hybi_header::get_opcode() const {
return frame::opcode::value(m_header[0] & BPB0_OPCODE);
}
bool hybi_header::get_masked() const {
return ((m_header[1] & BPB1_MASK) == BPB1_MASK);
}
int32_t hybi_header::get_masking_key() const {
if (!get_masked()) {
return 0;
}
return *reinterpret_cast<const int32_t*>(&m_header[get_header_len()-4]);
}
uint64_t hybi_header::get_payload_size() const {
return m_payload_size;
}
bool hybi_header::is_control() const {
return (frame::opcode::is_control(get_opcode()));
}
// private
unsigned int hybi_header::get_header_len() const {
unsigned int temp = 2;
@@ -123,175 +200,9 @@ unsigned int hybi_header::get_header_len() const {
return temp;
}
int32_t hybi_header::get_masking_key() {
if (m_state != STATE_READY) {
throw processor::exception("attempted to get masking_key before reading full header");
}
if (!get_masked()) {
// masking key of 0 and not masked are equivalent
return 0;
}
return *reinterpret_cast<int32_t*>(&m_header[get_header_len()-4]);
}
// get and set header bits
bool hybi_header::get_fin() const {
return ((m_header[0] & BPB0_FIN) == BPB0_FIN);
}
void hybi_header::set_fin(bool fin) {
if (fin) {
m_header[0] |= BPB0_FIN;
} else {
m_header[0] &= (0xFF ^ BPB0_FIN);
}
}
bool hybi_header::get_rsv1() const {
return ((m_header[0] & BPB0_RSV1) == BPB0_RSV1);
}
void hybi_header::set_rsv1(bool b) {
if (b) {
m_header[0] |= BPB0_RSV1;
} else {
m_header[0] &= (0xFF ^ BPB0_RSV1);
}
}
bool hybi_header::get_rsv2() const {
return ((m_header[0] & BPB0_RSV2) == BPB0_RSV2);
}
void hybi_header::set_rsv2(bool b) {
if (b) {
m_header[0] |= BPB0_RSV2;
} else {
m_header[0] &= (0xFF ^ BPB0_RSV2);
}
}
bool hybi_header::get_rsv3() const {
return ((m_header[0] & BPB0_RSV3) == BPB0_RSV3);
}
void hybi_header::set_rsv3(bool b) {
if (b) {
m_header[0] |= BPB0_RSV3;
} else {
m_header[0] &= (0xFF ^ BPB0_RSV3);
}
}
websocketpp::frame::opcode::value hybi_header::get_opcode() const {
return frame::opcode::value(m_header[0] & BPB0_OPCODE);
}
void hybi_header::set_opcode(frame::opcode::value op) {
if (frame::opcode::reserved(op)) {
throw processor::exception("reserved opcode",processor::error::PROTOCOL_VIOLATION);
}
if (frame::opcode::invalid(op)) {
throw processor::exception("invalid opcode",processor::error::PROTOCOL_VIOLATION);
}
if (is_control() && get_basic_size() > frame::limits::PAYLOAD_SIZE_BASIC) {
throw processor::exception("control frames can't have large payloads",processor::error::PROTOCOL_VIOLATION);
}
m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits
m_header[0] |= op; // set op bits
}
bool hybi_header::get_masked() const {
return ((m_header[1] & BPB1_MASK) == BPB1_MASK);
}
void hybi_header::set_masked(bool masked,int32_t key) {
if (masked) {
m_header[1] |= BPB1_MASK;
set_masking_key(key);
} else {
m_header[1] &= (0xFF ^ BPB1_MASK);
clear_masking_key();
}
}
uint8_t hybi_header::get_basic_size() const {
return m_header[1] & BPB1_PAYLOAD;
}
size_t hybi_header::get_payload_size() const {
if (m_state != STATE_READY) {
// TODO: how to handle errors like this?
throw "attempted to get payload size before reading full header";
}
return m_payload_size;
}
void hybi_header::set_payload_size(size_t size) {
if (size <= frame::limits::PAYLOAD_SIZE_BASIC) {
// encode in byte 2
m_header[1] &= (size & BPB1_PAYLOAD);
} else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
// encode two byte
m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD);
if (get_masked()) {
}
*(reinterpret_cast<uint16_t*>(&m_header[BASIC_HEADER_LENGTH])) = htons(size);
} else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
// encode two byte
m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD);
} else {
throw processor::exception("Client attempted to send a message that was too big",processor::error::MESSAGE_TOO_BIG);
}
}
bool hybi_header::is_control() const {
return (frame::opcode::is_control(get_opcode()));
}
void hybi_header::process_basic_header() {
m_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH;
}
void hybi_header::process_extended_header() {
uint8_t s = get_basic_size();
if (s <= frame::limits::PAYLOAD_SIZE_BASIC) {
m_payload_size = s;
} else if (s == BASIC_PAYLOAD_16BIT_CODE) {
// reinterpret the second two bytes as a 16 bit integer in network
// byte order. Convert to host byte order and store locally.
m_payload_size = ntohs(*(
reinterpret_cast<uint16_t*>(&m_header[BASIC_HEADER_LENGTH])
));
if (m_payload_size < s) {
std::stringstream err;
err << "payload length not minimally encoded. Using 16 bit form for payload size: " << m_payload_size;
throw processor::exception(err.str(),processor::error::PROTOCOL_VIOLATION);
}
} else if (s == BASIC_PAYLOAD_64BIT_CODE) {
// reinterpret the second eight bytes as a 64 bit integer in
// network byte order. Convert to host byte order and store.
m_payload_size = ntohll(*(
reinterpret_cast<uint64_t*>(&m_header[BASIC_HEADER_LENGTH])
));
if (m_payload_size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
throw processor::exception("payload length not minimally encoded",
processor::error::PROTOCOL_VIOLATION);
}
} else {
// TODO: shouldn't be here how to handle?
throw processor::exception("invalid get_basic_size in process_extended_header");
}
}
void hybi_header::validate_basic_header() const {
// check for control frame size
@@ -308,6 +219,11 @@ void hybi_header::validate_basic_header() const {
if (frame::opcode::reserved(get_opcode())) {
throw processor::exception("Reserved opcode used",processor::error::PROTOCOL_VIOLATION);
}
// check for invalid opcodes
if (frame::opcode::invalid(get_opcode())) {
throw processor::exception("Invalid opcode used",processor::error::PROTOCOL_VIOLATION);
}
// check for fragmented control message
if (is_control() && !get_fin()) {
@@ -315,6 +231,53 @@ void hybi_header::validate_basic_header() const {
}
}
void hybi_header::process_basic_header() {
m_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH;
}
void hybi_header::process_extended_header() {
uint8_t s = get_basic_size();
if (s <= frame::limits::PAYLOAD_SIZE_BASIC) {
m_payload_size = s;
} else if (s == BASIC_PAYLOAD_16BIT_CODE) {
// reinterpret the second two bytes as a 16 bit integer in network
// byte order. Convert to host byte order and store locally.
m_payload_size = ntohs(*(
reinterpret_cast<uint16_t*>(&m_header[BASIC_HEADER_LENGTH])
));
if (m_payload_size < s) {
std::stringstream err;
err << "payload length not minimally encoded. Using 16 bit form for payload size: " << m_payload_size;
throw processor::exception(err.str(),processor::error::PROTOCOL_VIOLATION);
}
} else if (s == BASIC_PAYLOAD_64BIT_CODE) {
// reinterpret the second eight bytes as a 64 bit integer in
// network byte order. Convert to host byte order and store.
m_payload_size = ntohll(*(
reinterpret_cast<uint64_t*>(&m_header[BASIC_HEADER_LENGTH])
));
if (m_payload_size <= frame::limits::PAYLOAD_SIZE_EXTENDED) {
throw processor::exception("payload length not minimally encoded",
processor::error::PROTOCOL_VIOLATION);
}
} else {
// TODO: shouldn't be here how to handle?
throw processor::exception("invalid get_basic_size in process_extended_header");
}
}
void hybi_header::set_header_bit(uint8_t bit,int byte,bool value) {
if (value) {
m_header[byte] |= bit;
} else {
m_header[byte] &= (0xFF ^ bit);
}
}
void hybi_header::set_masking_key(int32_t key) {
*(reinterpret_cast<int32_t *>(&m_header[get_header_len()-4])) = key;
}

View File

@@ -29,108 +29,92 @@
#define WEBSOCKET_PROCESSOR_HYBI_HEADER_HPP
#include "processor.hpp"
#include "../websocket_frame.hpp" // TODO: remove this dependency
namespace websocketpp {
namespace processor {
class hybi_header_writer {
/// Describes a processor for reading and writing WebSocket frame headers
/**
* The hybi_header class provides a processor capable of reading and writing
* WebSocket frame headers. It has two writing modes and two reading modes.
*
* Writing method 1: call consume() until ready()
* Writing method 2: call set_* methods followed by complete()
*
* Writing methods are valid only when ready() returns false. Use reset() to
* reset the header for writing again. Mixing writing methods between calls to
* reset() may behave unpredictably.
*
* Reading method 1: call get_header_bytes() to return a string of bytes
* Reading method 2: call get_* methods to read individual values
*
* Reading methods are valid only when ready() is true.
*
* @par Thread Safety
* @e Distinct @e objects: Safe.@n
* @e Shared @e objects: Unsafe
*/
class hybi_header {
public:
hybi_header_writer();
void reset();
/// Construct a header processor and initialize for writing
hybi_header();
/// Reset a header processor for writing
void reset();
// Writing interface (parse a byte stream)
// valid only if ready() returns false
// Consume will throw a processor::exception in the case that the bytes it
// read do not form a valid WebSocket frame header.
void consume(std::istream& input);
uint64_t get_bytes_needed() const;
bool ready() const;
// Writing interface (set fields directly)
// valid only if ready() returns false
// set_* may allow invalid values. Call complete() once values are set to
// check for header validity.
void set_fin(bool fin);
void set_rsv1(bool b);
void set_rsv2(bool b);
void set_rsv3(bool b);
void set_opcode(frame::opcode::value op);
void set_masked(bool masked,int32_t key);
void set_payload_size(size_t size);
void set_payload_size(uint64_t size);
// Complete will throw a processor::exception in the case that the
// combination of values set do not form a valid WebSocket frame header.
void complete();
// Reading interface (get string of bytes)
// valid only if ready() returns true
std::string get_header_bytes() const;
private:
// basic payload byte flags
static const uint8_t BPB0_OPCODE = 0x0F;
static const uint8_t BPB0_RSV3 = 0x10;
static const uint8_t BPB0_RSV2 = 0x20;
static const uint8_t BPB0_RSV1 = 0x40;
static const uint8_t BPB0_FIN = 0x80;
static const uint8_t BPB1_PAYLOAD = 0x7F;
static const uint8_t BPB1_MASK = 0x80;
static const uint8_t BASIC_PAYLOAD_16BIT_CODE = 0x7E; // 126
static const uint8_t BASIC_PAYLOAD_64BIT_CODE = 0x7F; // 127
static const unsigned int BASIC_HEADER_LENGTH = 2;
static const unsigned int MAX_HEADER_LENGTH = 15;
char m_header[MAX_HEADER_LENGTH];
};
// hybi header can be used for the following two tasks:
// - parse a sequence of bytes into a complete header and read fields
// - set fields and then generate a sequence of header bytes
class hybi_header {
public:
// constructs/resets a hybi_header for the purpose of either reading or writing
hybi_header(bool reading = true);
void reset(bool reading = true);
// Reading interface (Parse a byte stream)
void consume(std::istream& input);
uint64_t get_bytes_needed() const;
bool ready() const;
// Reading interface (get fields directly)
// valid only if ready() returns true
bool get_fin() const;
bool get_rsv1() const;
bool get_rsv2() const;
bool get_rsv3() const;
frame::opcode::value get_opcode() const;
bool get_masked() const;
// will return zero in the case where get_masked() is false. Note:
// a masking key of zero is slightly different than no mask at all.
int32_t get_masking_key() const;
uint64_t get_payload_size() const;
// Writing interface (Set fields directly)
void set_fin(bool fin);
void set_rsv1(bool b);
void set_rsv2(bool b);
void set_rsv3(bool b);
void set_opcode(frame::opcode::value op);
void set_masked(bool masked,int32_t key);
void set_payload_size(size_t size);
std::string get_header_bytes() const;
unsigned int get_header_len() const;
int32_t get_masking_key();
// get and set header bits
uint8_t get_basic_size() const;
size_t get_payload_size() const;
bool is_control() const;
void process_basic_header();
void process_extended_header();
void validate_basic_header() const;
void set_masking_key(int32_t key);
void clear_masking_key();
bool is_control() const;
private:
// general helper functions
unsigned int get_header_len() const;
uint8_t get_basic_size() const;
void validate_basic_header() const;
// helper functions for writing
void process_basic_header();
void process_extended_header();
void set_header_bit(uint8_t bit,int byte,bool value);
void set_masking_key(int32_t key);
void clear_masking_key();
// basic payload byte flags
static const uint8_t BPB0_OPCODE = 0x0F;
static const uint8_t BPB0_RSV3 = 0x10;