mirror of
https://github.com/XRPLF/rippled.git
synced 2026-04-29 15:37:57 +00:00
completes frame reading changes.
fixes 37, fixes 32
This commit is contained in:
@@ -56,9 +56,35 @@ int main(int argc, char* argv[]) {
|
||||
client->set_header("User Agent","WebSocket++/2011-09-25");
|
||||
|
||||
client->connect("ws://localhost:9001/getCaseCount");
|
||||
|
||||
io_service.run();
|
||||
|
||||
std::cout << "case count: " << c->m_case_count;
|
||||
|
||||
for (int i = 1; i <= c->m_case_count; i++) {
|
||||
io_service.reset();
|
||||
//boost::asio::io_service ios;
|
||||
|
||||
//client.reset();
|
||||
//client = websocketpp::client_ptr(new websocketpp::client(io_service,c));
|
||||
|
||||
std::cout << "foo: " << i << std::endl;
|
||||
//websocketpp::client_ptr client2(new websocketpp::client(io_service,c));
|
||||
|
||||
client->set_alog_level(websocketpp::ALOG_OFF);
|
||||
client->set_elog_level(websocketpp::LOG_OFF);
|
||||
|
||||
client->init();
|
||||
client->set_header("User Agent","WebSocket++/2011-09-25");
|
||||
|
||||
|
||||
std::stringstream foo;
|
||||
|
||||
foo << "ws://localhost:9001/runCase?case=" << i << "&agent=\"WebSocket++Snapshot/2011-10-08\"";
|
||||
|
||||
client->connect(foo.str());
|
||||
io_service.run();
|
||||
}
|
||||
|
||||
std::cout << "done" << std::endl;
|
||||
|
||||
} catch (std::exception& e) {
|
||||
|
||||
@@ -42,8 +42,8 @@ void echo_client_handler::on_close(session_ptr s,uint16_t status,const std::stri
|
||||
|
||||
void echo_client_handler::on_message(session_ptr s,const std::string &msg) {
|
||||
if (s->get_resource() == "/getCaseCount") {
|
||||
std::cout << "msg |" << msg.substr(1,msg.size()-2) << "|" << std::endl;
|
||||
m_case_count = atoi(msg.substr(1,msg.size()-2).c_str());
|
||||
std::cout << "Detected " << msg << " test cases." << std::endl;
|
||||
m_case_count = atoi(msg.c_str());
|
||||
} else {
|
||||
s->send(msg);
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
|
||||
// ignore messages
|
||||
void on_message(session_ptr s,const std::vector<unsigned char> &data);
|
||||
private:
|
||||
|
||||
int m_case_count;
|
||||
};
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ int main(int argc, char* argv[]) {
|
||||
);
|
||||
|
||||
// setup server settings
|
||||
server->set_alog_level(websocketpp::ALOG_OFF);
|
||||
server->set_elog_level(websocketpp::LOG_OFF);
|
||||
|
||||
server->add_host(host);
|
||||
server->add_host(full_host);
|
||||
|
||||
|
||||
@@ -124,6 +124,13 @@ void client_session::read_handshake() {
|
||||
|
||||
void client_session::handle_read_handshake(const boost::system::error_code& e,
|
||||
std::size_t bytes_transferred) {
|
||||
|
||||
if (e) {
|
||||
log_error("Error reading server handshake",e);
|
||||
drop_tcp();
|
||||
return;
|
||||
}
|
||||
|
||||
// parse server handshake
|
||||
std::istream response_stream(&m_buf);
|
||||
std::string header;
|
||||
@@ -255,7 +262,7 @@ void client_session::handle_read_handshake(const boost::system::error_code& e,
|
||||
|
||||
log_open_result();
|
||||
|
||||
m_status = OPEN;
|
||||
m_state = STATE_OPEN;
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_open(shared_from_this());
|
||||
@@ -326,8 +333,8 @@ void client_session::write_handshake() {
|
||||
|
||||
void client_session::handle_write_handshake(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing handshake",error);
|
||||
// TODO: close behavior
|
||||
log_error("Error writing handshake",error);
|
||||
drop_tcp();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,47 +46,114 @@ uint8_t frame::get_state() const {
|
||||
return m_state;
|
||||
}
|
||||
|
||||
uint64_t frame::get_bytes_needed() const {
|
||||
return m_bytes_needed;
|
||||
}
|
||||
|
||||
void frame::reset() {
|
||||
m_state = STATE_BASIC_HEADER;
|
||||
m_bytes_needed = BASIC_HEADER_LENGTH;
|
||||
m_degraded = false;
|
||||
m_payload.empty();
|
||||
memset(m_header,0,MAX_HEADER_LENGTH);
|
||||
}
|
||||
|
||||
// Method invariant: One of the following must always be true even in the case
|
||||
// of exceptions.
|
||||
// - m_bytes_needed > 0
|
||||
// - m-state = STATE_READY
|
||||
void frame::consume(std::istream &s) {
|
||||
if (m_state == STATE_BASIC_HEADER) {
|
||||
s.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],m_bytes_needed);
|
||||
try {
|
||||
switch (m_state) {
|
||||
case STATE_BASIC_HEADER:
|
||||
s.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],m_bytes_needed);
|
||||
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
process_basic_header();
|
||||
|
||||
// basic header validation
|
||||
validate_basic_header();
|
||||
|
||||
if (m_bytes_needed > 0) {
|
||||
m_state = STATE_EXTENDED_HEADER;
|
||||
} else {
|
||||
m_state = STATE_PAYLOAD;
|
||||
}
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
process_basic_header();
|
||||
|
||||
validate_basic_header();
|
||||
|
||||
if (m_bytes_needed > 0) {
|
||||
m_state = STATE_EXTENDED_HEADER;
|
||||
} else {
|
||||
process_extended_header();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
m_state = STATE_READY;
|
||||
process_payload();
|
||||
|
||||
} else {
|
||||
m_state = STATE_PAYLOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_EXTENDED_HEADER:
|
||||
s.read(&m_header[get_header_len()-m_bytes_needed],m_bytes_needed);
|
||||
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
process_extended_header();
|
||||
if (m_bytes_needed == 0) {
|
||||
m_state = STATE_READY;
|
||||
process_payload();
|
||||
} else {
|
||||
m_state = STATE_PAYLOAD;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case STATE_PAYLOAD:
|
||||
s.read(reinterpret_cast<char *>(&m_payload[m_payload.size()-m_bytes_needed]),
|
||||
m_bytes_needed);
|
||||
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
m_state = STATE_READY;
|
||||
process_payload();
|
||||
}
|
||||
break;
|
||||
case STATE_RECOVERY:
|
||||
// Recovery state discards all bytes that are not the first byte
|
||||
// of a close frame.
|
||||
do {
|
||||
s.read(reinterpret_cast<char *>(&m_header[0]),1);
|
||||
|
||||
//std::cout << std::hex << int(static_cast<unsigned char>(m_header[0])) << " ";
|
||||
|
||||
if (int(static_cast<unsigned char>(m_header[0])) == 0x88) {
|
||||
//(BPB0_FIN && CONNECTION_CLOSE)
|
||||
m_bytes_needed--;
|
||||
m_state = STATE_BASIC_HEADER;
|
||||
break;
|
||||
}
|
||||
} while (s.gcount() > 0);
|
||||
|
||||
//std::cout << std::endl;
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if (m_state == STATE_EXTENDED_HEADER) {
|
||||
s.read(&m_header[BASIC_HEADER_LENGTH+get_header_len()-m_bytes_needed],m_bytes_needed);
|
||||
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
process_extended_header();
|
||||
m_state = STATE_PAYLOAD;
|
||||
}
|
||||
} else if (m_state == STATE_PAYLOAD) {
|
||||
s.read(reinterpret_cast<char *>(&m_payload[0]),m_bytes_needed);
|
||||
|
||||
m_bytes_needed -= s.gcount();
|
||||
|
||||
if (m_bytes_needed == 0) {
|
||||
process_payload();
|
||||
m_state = STATE_READY;
|
||||
/*if (s.gcount() == 0) {
|
||||
throw frame_error("consume read zero bytes",FERR_FATAL_SESSION_ERROR);
|
||||
}*/
|
||||
} catch (const frame_error& e) {
|
||||
// After this point all non-close frames must be considered garbage,
|
||||
// including the current one. Reset it and put the reading frame into
|
||||
// a recovery state.
|
||||
if (m_degraded == true) {
|
||||
throw frame_error("An error occurred while trying to gracefully recover from a less serious frame error.",FERR_FATAL_SESSION_ERROR);
|
||||
} else {
|
||||
reset();
|
||||
m_state = STATE_RECOVERY;
|
||||
m_degraded = true;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,8 +183,8 @@ unsigned int frame::get_header_len() const {
|
||||
}
|
||||
|
||||
char* frame::get_masking_key() {
|
||||
if (m_extended_header_bytes_needed > 0) {
|
||||
throw "attempted to get masking_key before reading full header";
|
||||
if (m_state != STATE_READY) {
|
||||
throw frame_error("attempted to get masking_key before reading full header");
|
||||
}
|
||||
return m_masking_key;
|
||||
}
|
||||
@@ -178,12 +245,12 @@ frame::opcode frame::get_opcode() const {
|
||||
|
||||
void frame::set_opcode(frame::opcode op) {
|
||||
if (op > 0x0F) {
|
||||
throw "invalid opcode";
|
||||
throw frame_error("invalid opcode",FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
|
||||
if (get_basic_size() > BASIC_PAYLOAD_LIMIT &&
|
||||
is_control()) {
|
||||
throw "control frames can't have large payloads";
|
||||
throw frame_error("control frames can't have large payloads",FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
|
||||
m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits
|
||||
@@ -209,9 +276,9 @@ uint8_t frame::get_basic_size() const {
|
||||
}
|
||||
|
||||
size_t frame::get_payload_size() const {
|
||||
if (m_extended_header_bytes_needed > 0) {
|
||||
if (m_state != STATE_READY && m_state != STATE_PAYLOAD) {
|
||||
// problem
|
||||
throw "attempted to get payload size before reading full header";
|
||||
throw frame_error("attempted to get payload size before reading full header");
|
||||
}
|
||||
|
||||
return m_payload.size();
|
||||
@@ -263,13 +330,13 @@ bool frame::is_control() const {
|
||||
|
||||
void frame::set_payload_helper(size_t s) {
|
||||
if (s > max_payload_size) {
|
||||
throw "requested payload is over implimentation defined limit";
|
||||
throw frame_error("requested payload is over implimentation defined limit",FERR_MSG_TOO_BIG);
|
||||
}
|
||||
|
||||
// limits imposed by the websocket spec
|
||||
if (s > BASIC_PAYLOAD_LIMIT &&
|
||||
get_opcode() > MAX_FRAME_OPCODE) {
|
||||
throw "control frames can't have large payloads";
|
||||
throw frame_error("control frames can't have large payloads",FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
|
||||
if (s <= BASIC_PAYLOAD_LIMIT) {
|
||||
@@ -285,7 +352,7 @@ void frame::set_payload_helper(size_t s) {
|
||||
m_header[1] = BASIC_PAYLOAD_64BIT_CODE;
|
||||
*reinterpret_cast<uint64_t*>(&m_header[BASIC_HEADER_LENGTH]) = htonll(s);
|
||||
} else {
|
||||
throw "payload size limit is 63 bits";
|
||||
throw frame_error("payload size limit is 63 bits",FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
|
||||
m_payload.resize(s);
|
||||
@@ -294,11 +361,11 @@ void frame::set_payload_helper(size_t s) {
|
||||
void frame::set_status(uint16_t status,const std::string message) {
|
||||
// check for valid statuses
|
||||
if (status < 1000 || status > 4999) {
|
||||
throw server_error("Status codes must be in the range 1000-4999");
|
||||
throw frame_error("Status codes must be in the range 1000-4999");
|
||||
}
|
||||
|
||||
if (status == 1005 || status == 1006) {
|
||||
throw server_error("Status codes 1005 and 1006 are reserved for internal use and cannot be written to a frame.");
|
||||
throw frame_error("Status codes 1005 and 1006 are reserved for internal use and cannot be written to a frame.");
|
||||
}
|
||||
|
||||
m_payload.resize(2+message.size());
|
||||
@@ -326,9 +393,13 @@ std::string frame::print_frame() const {
|
||||
f << std::hex << (unsigned short)m_header[i] << " ";
|
||||
}
|
||||
// print message
|
||||
std::vector<unsigned char>::const_iterator it;
|
||||
for (it = m_payload.begin(); it != m_payload.end(); it++) {
|
||||
f << *it;
|
||||
if (m_payload.size() > 50) {
|
||||
f << "[payload of " << m_payload.size() << " bytes]";
|
||||
} else {
|
||||
std::vector<unsigned char>::const_iterator it;
|
||||
for (it = m_payload.begin(); it != m_payload.end(); it++) {
|
||||
f << *it;
|
||||
}
|
||||
}
|
||||
return f.str();
|
||||
}
|
||||
@@ -352,7 +423,10 @@ void frame::process_extended_header() {
|
||||
));
|
||||
|
||||
if (payload_size < s) {
|
||||
throw frame_error("payload length not minimally encoded",
|
||||
std::stringstream err;
|
||||
err << "payload length not minimally encoded. Using 16 bit form for payload size: " << payload_size;
|
||||
m_bytes_needed = payload_size;
|
||||
throw frame_error(err.str(),
|
||||
FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
|
||||
@@ -365,6 +439,7 @@ void frame::process_extended_header() {
|
||||
));
|
||||
|
||||
if (payload_size <= PAYLOAD_16BIT_LIMIT) {
|
||||
m_bytes_needed = payload_size;
|
||||
throw frame_error("payload length not minimally encoded",
|
||||
FERR_PROTOCOL_VIOLATION);
|
||||
}
|
||||
@@ -471,9 +546,7 @@ void frame::generate_masking_key() {
|
||||
//throw "masking key generation not implimented";
|
||||
|
||||
int32_t key = m_gen();
|
||||
|
||||
std::cout << "genkey: " << key << std::endl;
|
||||
|
||||
|
||||
//m_masking_key[0] = reinterpret_cast<char*>(&key)[0];
|
||||
//m_masking_key[1] = reinterpret_cast<char*>(&key)[1];
|
||||
//m_masking_key[2] = reinterpret_cast<char*>(&key)[2];
|
||||
|
||||
@@ -58,11 +58,14 @@ public:
|
||||
static const uint8_t STATE_EXTENDED_HEADER = 2;
|
||||
static const uint8_t STATE_PAYLOAD = 3;
|
||||
static const uint8_t STATE_READY = 4;
|
||||
static const uint8_t STATE_RECOVERY = 5;
|
||||
|
||||
static const uint16_t FERR_FATAL_SESSION_ERROR = 0; // must end session
|
||||
static const uint16_t FERR_FATAL_SESSION_ERROR = 0; // force session end
|
||||
static const uint16_t FERR_SOFT_SESSION_ERROR = 1; // should log and ignore
|
||||
static const uint16_t FERR_PROTOCOL_VIOLATION = 2; // must end session
|
||||
static const uint16_t FERR_PAYLOAD_VIOLATION = 3; // should end session
|
||||
static const uint16_t FERR_INTERNAL_SERVER_ERROR = 4; // cleanly end session
|
||||
static const uint16_t FERR_MSG_TOO_BIG = 5;
|
||||
|
||||
// basic payload byte flags
|
||||
static const uint8_t BPB0_OPCODE = 0x0F;
|
||||
@@ -86,9 +89,8 @@ public:
|
||||
|
||||
// create an empty frame for writing into
|
||||
frame() : m_gen(m_rng,
|
||||
boost::random::uniform_int_distribution<>(INT32_MIN,INT32_MAX)) {
|
||||
// not sure if these are necessary with c++ but putting in just in case
|
||||
memset(m_header,0,MAX_HEADER_LENGTH);
|
||||
boost::random::uniform_int_distribution<>(INT32_MIN,INT32_MAX)),m_degraded(false) {
|
||||
reset();
|
||||
}
|
||||
|
||||
uint8_t get_state() const;
|
||||
@@ -156,12 +158,12 @@ public:
|
||||
private:
|
||||
uint8_t m_state;
|
||||
uint64_t m_bytes_needed;
|
||||
bool m_degraded;
|
||||
|
||||
char m_header[MAX_HEADER_LENGTH];
|
||||
std::vector<unsigned char> m_payload;
|
||||
|
||||
char m_masking_key[4];
|
||||
unsigned int m_extended_header_bytes_needed;
|
||||
|
||||
boost::random::random_device m_rng;
|
||||
boost::random::variate_generator<boost::random::random_device&,
|
||||
@@ -182,7 +184,7 @@ public:
|
||||
}
|
||||
|
||||
uint16_t code() const throw() {
|
||||
|
||||
return m_code;
|
||||
}
|
||||
|
||||
std::string m_msg;
|
||||
|
||||
@@ -314,7 +314,8 @@ void server_session::write_handshake() {
|
||||
|
||||
void server_session::handle_write_handshake(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing handshake response",error);
|
||||
log_error("Error writing handshake response",error);
|
||||
drop_tcp();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -325,11 +326,12 @@ void server_session::handle_write_handshake(const boost::system::error_code& err
|
||||
err << "Handshake ended with HTTP error: " << m_server_http_code << " "
|
||||
<< (m_server_http_string != "" ? m_server_http_string : lookup_http_error_string(m_server_http_code));
|
||||
log(err.str(),LOG_ERROR);
|
||||
// TODO: close behavior
|
||||
drop_tcp();
|
||||
// TODO: tell client that connection failed.
|
||||
return;
|
||||
}
|
||||
|
||||
m_status = OPEN;
|
||||
m_state = STATE_OPEN;
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_open(shared_from_this());
|
||||
|
||||
@@ -47,6 +47,7 @@ session::session (boost::asio::io_service& io_service,
|
||||
websocketpp::connection_handler_ptr defc,
|
||||
uint64_t buf_size)
|
||||
: m_state(STATE_CONNECTING),
|
||||
m_writing(false),
|
||||
m_local_close_code(CLOSE_STATUS_NO_STATUS),
|
||||
m_remote_close_code(CLOSE_STATUS_NO_STATUS),
|
||||
m_was_clean(false),
|
||||
@@ -165,9 +166,6 @@ void session::send_close(uint16_t status,const std::string &message) {
|
||||
|
||||
m_state = STATE_CLOSING;
|
||||
|
||||
m_close_code = status;
|
||||
m_close_message = message;
|
||||
|
||||
m_local_close_code = status;
|
||||
m_local_close_msg = message;
|
||||
|
||||
@@ -177,8 +175,8 @@ void session::send_close(uint16_t status,const std::string &message) {
|
||||
if (status == CLOSE_STATUS_NO_STATUS) {
|
||||
m_write_frame.set_status(CLOSE_STATUS_NORMAL,"");
|
||||
} else if (status == CLOSE_STATUS_ABNORMAL_CLOSE) {
|
||||
// unknown internal error, don't set a status? use protocol error?
|
||||
log("Tried to disconnect with status ABNORMAL_CLOSE",LOG_DEBUG);
|
||||
// Internal implimentation error. There is no good close code for this.
|
||||
m_write_frame.set_status(CLOSE_STATUS_POLICY_VIOLATION,message);
|
||||
} else {
|
||||
m_write_frame.set_status(status,message);
|
||||
}
|
||||
@@ -210,75 +208,136 @@ void session::pong(const std::string &msg) {
|
||||
write_frame();
|
||||
}
|
||||
|
||||
void session::read_frame() {
|
||||
// the initial read in the handshake may have read in the first frame.
|
||||
// handle it (if it exists) before we read anything else.
|
||||
handle_read_frame(boost::system::error_code());
|
||||
}
|
||||
|
||||
// handle_read_frame reads and processes all socket read commands for the
|
||||
// session by consuming the read buffer and then starting an async read with
|
||||
// itself as the callback. The connection is over when this method returns.
|
||||
void session::handle_read_frame(const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error reading extended frame header",error);
|
||||
// TODO: close behavior
|
||||
if (m_state != STATE_OPEN && m_state != STATE_CLOSING) {
|
||||
log("handle_read_frame called in invalid state",LOG_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_state != STATE_OPEN && m_state != STATE_CLOSING) {
|
||||
// stop processing frames.
|
||||
log("handle_read_frame called in invalid state",LOG_ERROR);
|
||||
return;
|
||||
if (error) {
|
||||
if (error == boost::asio::error::eof) {
|
||||
// if this is a case where we are expecting eof, return, else log & drop
|
||||
|
||||
log_error("Recieved EOF",error);
|
||||
//drop_tcp(false);
|
||||
//m_state = STATE_CLOSED;
|
||||
} else if (error == boost::asio::error::operation_aborted) {
|
||||
// some other part of our client called shutdown on our socket.
|
||||
// This is usually due to a write error. Everything should have
|
||||
// already been logged and dropped so we just return here
|
||||
return;
|
||||
} else {
|
||||
log_error("Error reading frame",error);
|
||||
//drop_tcp(false);
|
||||
m_state = STATE_CLOSED;
|
||||
}
|
||||
}
|
||||
|
||||
std::istream s(&m_buf);
|
||||
|
||||
try {
|
||||
while (m_buf.size() > 0) {
|
||||
m_read_frame.consume(s);
|
||||
if (m_read_frame.get_state() == frame::STATE_READY) {
|
||||
process_frame();
|
||||
|
||||
// should we break?
|
||||
while (m_buf.size() > 0 && m_state != STATE_CLOSED) {
|
||||
try {
|
||||
if (m_read_frame.get_bytes_needed() == 0) {
|
||||
throw frame_error("have bytes that no frame needs",frame::FERR_FATAL_SESSION_ERROR);
|
||||
}
|
||||
|
||||
// Consume will read bytes from s
|
||||
// will throw a frame_error on error.
|
||||
|
||||
std::stringstream err;
|
||||
|
||||
err << "consuming. have: " << m_buf.size() << " bytes. Need: " << m_read_frame.get_bytes_needed() << " state: " << (int)m_read_frame.get_state();
|
||||
log(err.str(),LOG_DEBUG);
|
||||
m_read_frame.consume(s);
|
||||
|
||||
err.str("");
|
||||
err << "consume complete, " << m_buf.size() << " bytes left, " << m_read_frame.get_bytes_needed() << " still needed, state: " << (int)m_read_frame.get_state();
|
||||
log(err.str(),LOG_DEBUG);
|
||||
|
||||
if (m_read_frame.get_state() == frame::STATE_READY) {
|
||||
// process frame and reset frame state for the next frame.
|
||||
// will throw a frame_error on error. May set m_state to CLOSED,
|
||||
// if so no more frames should be processed.
|
||||
err.str("");
|
||||
err << "processing frame " << m_buf.size();
|
||||
log(err.str(),LOG_DEBUG);
|
||||
process_frame();
|
||||
}
|
||||
} catch (const frame_error& e) {
|
||||
std::stringstream err;
|
||||
err << "Caught frame exception: " << e.what();
|
||||
|
||||
access_log(e.what(),ALOG_FRAME);
|
||||
log(err.str(),LOG_ERROR);
|
||||
|
||||
// process different types of frame errors
|
||||
//
|
||||
if (e.code() == frame::FERR_PROTOCOL_VIOLATION) {
|
||||
send_close(CLOSE_STATUS_PROTOCOL_ERROR, e.what());
|
||||
} else if (e.code() == frame::FERR_PAYLOAD_VIOLATION) {
|
||||
send_close(CLOSE_STATUS_INVALID_PAYLOAD, e.what());
|
||||
} else if (e.code() == frame::FERR_INTERNAL_SERVER_ERROR) {
|
||||
send_close(CLOSE_STATUS_ABNORMAL_CLOSE, e.what());
|
||||
} else if (e.code() == frame::FERR_SOFT_SESSION_ERROR) {
|
||||
// ignore and continue processing frames
|
||||
continue;
|
||||
} else {
|
||||
// Fatal error, forcibly end connection immediately.
|
||||
drop_tcp(true);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
} catch (const frame_error& e) {
|
||||
std::stringstream err;
|
||||
err << "Caught frame exception: " << e.what();
|
||||
|
||||
access_log(e.what(),ALOG_FRAME);
|
||||
log(err.str(),LOG_ERROR);
|
||||
|
||||
if (e.code() == frame::FERR_PROTOCOL_VIOLATION) {
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR, e.what());
|
||||
} else if (e.code() == frame::FERR_PAYLOAD_VIOLATION) {
|
||||
disconnect(CLOSE_STATUS_INVALID_PAYLOAD, e.what());
|
||||
} else if (e.code() == frame::FERR_SOFT_SESSION_ERROR) {
|
||||
// ???
|
||||
} else {
|
||||
// Fatal error
|
||||
disconnect(CLOSE_STATUS_NO_STATUS, "");
|
||||
}
|
||||
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"");
|
||||
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
if (error == boost::asio::error::eof) {
|
||||
m_state = STATE_CLOSED;
|
||||
}
|
||||
|
||||
// we have read everything, check if we should read more
|
||||
|
||||
if (m_state != STATE_OPEN && m_state != STATE_CLOSING) {
|
||||
// stop processing frames.
|
||||
if ((m_state == STATE_OPEN || m_state == STATE_CLOSING) && m_read_frame.get_bytes_needed() > 0) {
|
||||
std::stringstream msg;
|
||||
msg << "starting async read for " << m_read_frame.get_bytes_needed() << " bytes.";
|
||||
|
||||
log(msg.str(),LOG_DEBUG);
|
||||
|
||||
// TODO: set a timer here in case we don't want to read forever.
|
||||
// Ex: when the frame is in a degraded state.
|
||||
|
||||
boost::asio::async_read(
|
||||
m_socket,
|
||||
m_buf,
|
||||
boost::asio::transfer_at_least(m_read_frame.get_bytes_needed()),
|
||||
boost::bind(
|
||||
&session::handle_read_frame,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
} else if (m_state == STATE_CLOSED) {
|
||||
log_close_result();
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_close(shared_from_this(),m_remote_close_code,m_remote_close_msg);
|
||||
}
|
||||
} else {
|
||||
log("handle_read_frame called in invalid state",LOG_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// read more
|
||||
boost::asio::async_read(
|
||||
m_socket,
|
||||
m_buf,
|
||||
boost::asio::transfer_at_least(m_read_frame.get_bytes_needed()),
|
||||
boost::bind(
|
||||
&session::handle_read_frame,
|
||||
shared_from_this(),
|
||||
boost::asio::placeholders::error
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void session::process_frame () {
|
||||
log("process_frame",LOG_DEBUG);
|
||||
|
||||
if (m_state == STATE_OPEN) {
|
||||
switch (m_read_frame.get_opcode()) {
|
||||
case frame::CONTINUATION_FRAME:
|
||||
@@ -291,6 +350,7 @@ void session::process_frame () {
|
||||
process_binary();
|
||||
break;
|
||||
case frame::CONNECTION_CLOSE:
|
||||
log("process_close",LOG_DEBUG);
|
||||
process_close();
|
||||
break;
|
||||
case frame::PING:
|
||||
@@ -300,8 +360,8 @@ void session::process_frame () {
|
||||
process_pong();
|
||||
break;
|
||||
default:
|
||||
disconnect(CLOSE_STATUS_PROTOCOL_ERROR,"Invalid Opcode");
|
||||
// TODO: close behavior
|
||||
throw frame_error("Invalid Opcode",
|
||||
frame::FERR_PROTOCOL_VIOLATION);
|
||||
break;
|
||||
}
|
||||
} else if (m_state == STATE_CLOSING) {
|
||||
@@ -309,42 +369,24 @@ void session::process_frame () {
|
||||
process_close();
|
||||
} else {
|
||||
// Ignore all other frames in closing state
|
||||
log("ignoring this frame",LOG_DEBUG);
|
||||
}
|
||||
} else {
|
||||
// Recieved message before or after connection was opened/closed
|
||||
// TODO: close behavior
|
||||
return;
|
||||
throw frame_error("process_frame called from invalid state");
|
||||
}
|
||||
|
||||
// check if there was an error processing this frame and fail the connection
|
||||
if (m_error) {
|
||||
log("Connection has been closed uncleanly",LOG_ERROR);
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_status == CLOSED) {
|
||||
log_close_result();
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_close(shared_from_this(),
|
||||
m_close_code,
|
||||
m_close_message);
|
||||
}
|
||||
// TODO: close behavior
|
||||
return;
|
||||
}
|
||||
|
||||
m_read_frame.reset();
|
||||
}
|
||||
|
||||
void session::handle_write_frame (const boost::system::error_code& error) {
|
||||
if (error) {
|
||||
handle_error("Error writing frame data",error);
|
||||
// TODO: close behavior
|
||||
log_error("Error writing frame data",error);
|
||||
drop_tcp(false);
|
||||
}
|
||||
|
||||
//std::cout << "Successfully wrote frame." << std::endl;
|
||||
access_log("handle_write_frame complete",ALOG_FRAME);
|
||||
m_writing = false;
|
||||
}
|
||||
|
||||
void session::process_ping() {
|
||||
@@ -417,11 +459,13 @@ void session::process_close() {
|
||||
m_remote_close_msg = message;
|
||||
|
||||
if (m_state == STATE_OPEN) {
|
||||
log("process_close sending ack",LOG_DEBUG);
|
||||
// This is the case where the remote initiated the close.
|
||||
m_closed_by_me = false;
|
||||
// TODO: close behavior
|
||||
disconnect(status,message);
|
||||
// send acknowledgement
|
||||
send_close(status,message);
|
||||
} else if (m_state == STATE_CLOSING) {
|
||||
log("process_close got ack",LOG_DEBUG);
|
||||
// this is an ack of our close message
|
||||
m_closed_by_me = true;
|
||||
} else {
|
||||
@@ -501,7 +545,9 @@ void session::write_frame() {
|
||||
);
|
||||
|
||||
log("Write Frame: "+m_write_frame.print_frame(),LOG_DEBUG);
|
||||
|
||||
|
||||
m_writing = true;
|
||||
|
||||
boost::asio::async_write(
|
||||
m_socket,
|
||||
data,
|
||||
@@ -547,19 +593,13 @@ void session::log_open_result() {
|
||||
access_log(msg.str(),ALOG_HANDSHAKE);
|
||||
}
|
||||
|
||||
void session::handle_error(std::string msg,
|
||||
const boost::system::error_code& error) {
|
||||
std::stringstream e;
|
||||
// this is called when an async asio call encounters an error
|
||||
void session::log_error(std::string msg,const boost::system::error_code& e) {
|
||||
std::stringstream err;
|
||||
|
||||
e << "[Connection " << this << "] " << msg << " (" << error << ")";
|
||||
err << "[Connection " << this << "] " << msg << " (" << e << ")";
|
||||
|
||||
log(e.str(),LOG_ERROR);
|
||||
|
||||
if (m_local_interface) {
|
||||
m_local_interface->on_close(shared_from_this(),1006,e.str());
|
||||
}
|
||||
|
||||
m_error = true;
|
||||
log(err.str(),LOG_ERROR);
|
||||
}
|
||||
|
||||
// validates status codes that the end application is allowed to use
|
||||
@@ -574,3 +614,12 @@ bool session::validate_app_close_status(uint16_t status) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void session::drop_tcp(bool dropped_by_me) {
|
||||
if (m_socket.is_open()) {
|
||||
m_socket.shutdown(tcp::socket::shutdown_both);
|
||||
m_socket.close();
|
||||
}
|
||||
m_dropped_by_me = dropped_by_me;
|
||||
m_state = STATE_CLOSED;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,42 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Exit path mapping
|
||||
|
||||
In every path:
|
||||
- If it is safe to close cleanly, close cleanly
|
||||
- Write to the access log on clean close
|
||||
- Write to the error log on unclean close and clean closes with a server error.
|
||||
- If session state is open and a local client is connected, send on_close msg
|
||||
|
||||
- make sure the following bits are properly set:
|
||||
|
||||
- If we initiated the close by sending the first close frame or by dropping the TCP connection, set closed_by_me. If the other endpoint sent the first close method or we got an EOF while reading clear closed_by_me
|
||||
- If we initiated the TCP connection drop set dropped_by_me. If we got EOF while reading clear dropped_by_me
|
||||
- If we sent and received a close frame or we received and sent an acknowledgement close frame set was_clean to true.
|
||||
|
||||
- If we are the server we should drop TCP immediately
|
||||
- If we are the client we should drop TCP immediately except in the case where we just recieved an acknowledgement close frame. In this case wait a certain period of time for the server EOF.
|
||||
|
||||
Questions:
|
||||
- if the client rejects
|
||||
|
||||
Paths: (+ indicates path has been checked and implimented)
|
||||
Server Handshake Paths
|
||||
- Accept connection, read handshake, handshake is valid, write handshake, no errors. This is the correct path and leads to the frame reading paths
|
||||
- Accept connection, connection is not in state open after a time out (due to no bytes being read or no CRLFCRLF being read). This needs a time out after which we drop TCP.
|
||||
- Accept connection, read handshake, handshake is invalid. write HTTP error. drop TCP
|
||||
- Accept connection, read handshake, handshake is valid, write handshake returns EOF. This means client rejected something about our response. We should drop and notify our client. (note alternative client handshake reject method is to accept the handshake then immediately send a close message with the non-acceptance reason)
|
||||
- Accept connection, read handshake, handshake is valid, write handshake returns another error. We should drop and notify our client.
|
||||
Client Handshake Paths
|
||||
-
|
||||
Server Frame Reading Paths
|
||||
- async read returns EOF. Close our own socket and notify our local interface.
|
||||
- async read returns another error
|
||||
*/
|
||||
|
||||
#ifndef WEBSOCKET_SESSION_HPP
|
||||
#define WEBSOCKET_SESSION_HPP
|
||||
|
||||
@@ -150,6 +186,7 @@ protected:
|
||||
virtual void write_handshake() = 0;
|
||||
virtual void read_handshake() = 0;
|
||||
|
||||
void read_frame();
|
||||
void handle_read_frame (const boost::system::error_code& error);
|
||||
|
||||
// write m_write_frame out to the socket.
|
||||
@@ -182,13 +219,12 @@ protected:
|
||||
|
||||
void log_close_result();
|
||||
void log_open_result();
|
||||
|
||||
// prints a diagnostic message and disconnects the local interface
|
||||
void handle_error(std::string msg,const boost::system::error_code& error);
|
||||
void log_error(std::string msg,const boost::system::error_code& e);
|
||||
|
||||
// misc helpers
|
||||
bool validate_app_close_status(uint16_t status);
|
||||
void send_close(uint16_t status,const std::string& reason);
|
||||
void drop_tcp(bool dropped_by_me = true);
|
||||
private:
|
||||
std::string get_header(const std::string& key,
|
||||
const header_list& list) const;
|
||||
@@ -215,18 +251,17 @@ protected:
|
||||
std::string m_server_http_string;
|
||||
|
||||
// Mutable connection state;
|
||||
uint8_t m_state;
|
||||
uint16_t m_close_code;
|
||||
std::string m_close_message;
|
||||
uint8_t m_state;
|
||||
bool m_writing;
|
||||
|
||||
// Close state
|
||||
uint16_t m_local_close_code;
|
||||
std::string m_local_close_msg;
|
||||
uint16_t m_remote_close_code;
|
||||
std::string m_remote_close_msg;
|
||||
bool m_was_clean;
|
||||
bool m_closed_by_me;
|
||||
bool m_dropped_by_me;
|
||||
uint16_t m_local_close_code;
|
||||
std::string m_local_close_msg;
|
||||
uint16_t m_remote_close_code;
|
||||
std::string m_remote_close_msg;
|
||||
bool m_was_clean;
|
||||
bool m_closed_by_me;
|
||||
bool m_dropped_by_me;
|
||||
|
||||
// Connection Resources
|
||||
tcp::socket m_socket;
|
||||
@@ -234,7 +269,7 @@ protected:
|
||||
connection_handler_ptr m_local_interface;
|
||||
|
||||
// Buffers
|
||||
boost::asio::streambuf m_buf;
|
||||
boost::asio::streambuf m_buf;
|
||||
|
||||
// current message state
|
||||
uint32_t m_utf8_state;
|
||||
@@ -244,11 +279,11 @@ protected:
|
||||
frame::opcode m_current_opcode;
|
||||
|
||||
// current frame state
|
||||
frame m_read_frame;
|
||||
frame m_read_frame;
|
||||
|
||||
// unorganized
|
||||
frame m_write_frame;
|
||||
bool m_error;
|
||||
frame m_write_frame;
|
||||
bool m_error;
|
||||
};
|
||||
|
||||
// Exception classes
|
||||
@@ -273,13 +308,3 @@ public:
|
||||
}
|
||||
|
||||
#endif // WEBSOCKET_SESSION_HPP
|
||||
|
||||
|
||||
|
||||
// better debug printing system
|
||||
// set acceptible origin and host headers
|
||||
// case sensitive header values? e.g. websocket
|
||||
|
||||
|
||||
// double check bugs in autobahn (sending wrong localhost:9000 header) not
|
||||
// checking masking in the 9.x tests
|
||||
|
||||
Reference in New Issue
Block a user