From cae526fcebf5a2cc1f74ff19bf6ee7f9b15ce3e7 Mon Sep 17 00:00:00 2001 From: Keith Winstein Date: Mon, 8 Aug 2011 18:41:09 -0400 Subject: [PATCH] First step of sending side of transport layer --- Makefile | 6 +- keystroke.hpp | 24 ++---- networktransport.cpp | 177 +++++++++++++++++++++++++++++++++++++++++++ networktransport.hpp | 76 ++++++++++++++++--- templates.cpp | 4 + terminal.hpp | 1 + 6 files changed, 255 insertions(+), 33 deletions(-) create mode 100644 networktransport.cpp diff --git a/Makefile b/Makefile index 57a3b6b..5791349 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -source = parse.cpp parserstate.cpp parser.cpp templates.cpp terminal.cpp termemu.cpp parseraction.cpp terminalfunctions.cpp swrite.cpp terminalframebuffer.cpp terminaldispatcher.cpp terminaluserinput.cpp terminaldisplay.cpp network.cpp ntester.cpp ocb.cpp base64.cpp encrypt.cpp decrypt.cpp crypto.cpp -objects = parserstate.o parser.o templates.o terminal.o parseraction.o terminalfunctions.o swrite.o terminalframebuffer.o terminaldispatcher.o terminaluserinput.o terminaldisplay.o network.o ocb.o base64.o crypto.o +source = parse.cpp parserstate.cpp parser.cpp templates.cpp terminal.cpp termemu.cpp parseraction.cpp terminalfunctions.cpp swrite.cpp terminalframebuffer.cpp terminaldispatcher.cpp terminaluserinput.cpp terminaldisplay.cpp network.cpp ntester.cpp ocb.cpp base64.cpp encrypt.cpp decrypt.cpp crypto.cpp networktransport.cpp +objects = parserstate.o parser.o templates.o terminal.o parseraction.o terminalfunctions.o swrite.o terminalframebuffer.o terminaldispatcher.o terminaluserinput.o terminaldisplay.o network.o ocb.o base64.o crypto.o networktransport.o repos = templates.rpo executables = parse termemu ntester encrypt decrypt CXX = g++ CXXFLAGS = -g --std=c++0x -pedantic -Werror -Wall -Wextra -Weffc++ -fno-implicit-templates -fno-default-inline -pipe -D_FILE_OFFSET_BITS=64 -D_XOPEN_SOURCE=500 -D_GNU_SOURCE -LIBS = -lutil -lssl +LIBS = -lutil -lssl -lrt all: $(executables) diff --git a/keystroke.hpp b/keystroke.hpp index 924b177..e9177d1 100644 --- a/keystroke.hpp +++ b/keystroke.hpp @@ -4,27 +4,15 @@ #include #include +using namespace std; + class KeyStroke { public: - char letter; - - string tostring( void ) - { - return string( &letter, 1 ); - }; - - KeyStroke( const string x ) - : letter() - { - assert( x.size() == 1 ); - - letter = x[ 0 ]; - } - - KeyStroke() - : letter( 0 ) - {} + void subtract( KeyStroke * const ) {} + string diff_from( KeyStroke const &, int ) { return ""; } + void apply_string( string ) {} + bool operator==( KeyStroke const & ) const { return true; } }; #endif diff --git a/networktransport.cpp b/networktransport.cpp new file mode 100644 index 0000000..4620d45 --- /dev/null +++ b/networktransport.cpp @@ -0,0 +1,177 @@ +#include + +#include "networktransport.hpp" + +using namespace Network; +using namespace std; + +template +uint64_t Transport::timestamp( void ) +{ + struct timespec tp; + + if ( clock_gettime( CLOCK_MONOTONIC, &tp ) < 0 ) { + throw NetworkException( "clock_gettime", errno ); + } + + uint64_t millis = tp.tv_nsec / 1000000; + millis += uint64_t( tp.tv_sec ) * 1000000; + + return millis; +} + +template +Transport::Transport( MyState &initial_state ) + : connection(), + server( true ), + current_state( initial_state ), + sent_states( 1, TimestampedState( timestamp(), 0, initial_state ) ), + assumed_receiver_state( sent_states.begin() ), + timeout( INITIAL_TIMEOUT ), + highest_state_received( 0 ) +{ + /* server */ +} + +template +Transport::Transport( MyState &initial_state, + const char *key_str, const char *ip, int port ) + : connection( key_str, ip, port ), + server( false ), + current_state( initial_state ), + sent_states( 1, TimestampedState( timestamp(), 0, initial_state ) ), + assumed_receiver_state( sent_states.begin() ), + timeout( INITIAL_TIMEOUT ), + highest_state_received( 0 ) +{ + /* client */ +} + +template +void Transport::new_state( MyState &s ) +{ + current_state = s; + + tick(); +} + +template +void Transport::tick( void ) +{ + /* Update assumed receiver state */ + update_assumed_receiver_state(); + + /* Cut out common prefix of all states */ + rationalize_states(); + + /* Determine if a new diff or empty ack needs to be sent */ + if ( timestamp() - sent_states.back().timestamp >= int64_t( SEND_INTERVAL ) ) { + /* Send diffs or ack */ + send_to_receiver(); + } +} + +template +void Transport::send_to_receiver( void ) +{ + if ( assumed_receiver_state->state == sent_states.back().state ) { + /* send empty ack */ + Instruction inst( assumed_receiver_state->num, + assumed_receiver_state->num, + "", + highest_state_received ); + string s = inst.tostring(); + connection.send( s ); + sent_states.back().timestamp = timestamp(); + return; + } + + /* Otherwise, send sequence of diffs between assumed receiver state and current state */ + + /* We don't want to assume that this sequence of diffs will + necessarily bring the receiver to the _actual_ current + state. That requires perfect round-trip stability of the diff + mechanism -- stronger than we need (and probably too fragile). + Instead, we produce the full diff, unlimited by MTU, between + the assumed receiver state and current state, and apply that + diff to produce a target receiver state. Then we produce a + sequence of diffs (this time limited by MTU) that bring us to + that state. */ + + MyState target_receiver_state( assumed_receiver_state->state ); + target_receiver_state.apply_string( current_state.diff_from( target_receiver_state, -1 ) ); + + while ( !(assumed_receiver_state->state == target_receiver_state) ) { + Instruction inst( assumed_receiver_state->num, -1, + current_state.diff_from( assumed_receiver_state->state, + connection.get_MTU() - HEADER_LEN ), + highest_state_received ); + MyState new_state = assumed_receiver_state->state; + new_state.apply_string( inst.diff ); + + /* Find the right "new_num" for this instruction. */ + /* Has this state previously been sent? */ + /* should replace with hash table if this becomes demanding */ + typename list< TimestampedState >::iterator previously_sent = sent_states.begin(); + while ( ( previously_sent != sent_states.end() ) + && ( !(previously_sent->state == new_state) ) ) { + previously_sent++; + } + + if ( previously_sent == sent_states.end() ) { /* not previously sent */ + inst.new_num = sent_states.back().num + 1; + sent_states.push_back( TimestampedState( timestamp(), inst.new_num, new_state ) ); + previously_sent = sent_states.end(); + previously_sent--; + } else { + inst.new_num = previously_sent->num; + previously_sent->timestamp = timestamp(); + } + + /* send instruction */ + /* XXX what about MTU problem? */ + string s = inst.tostring(); + + try { + connection.send( s ); + } catch ( MTUException m ) { + continue; + } + + /* successfully sent, probably */ + /* ("probably" because the FIRST size-exceeded datagram doesn't get an error) */ + assumed_receiver_state = previously_sent; + } +} + +template +void Transport::update_assumed_receiver_state( void ) +{ + uint64_t now = timestamp(); + + /* start from what is known and give benefit of the doubt to unacknowledged states + transmitted recently enough ago */ + assumed_receiver_state = sent_states.begin(); + + for ( typename list< TimestampedState >::iterator i = sent_states.begin(); + i != sent_states.end(); + i++ ) { + assert( now >= i->timestamp ); + + if ( now - i->timestamp < int64_t(timeout) ) { + assumed_receiver_state = i; + } + } +} + +template +void Transport::rationalize_states( void ) +{ + MyState * const known_receiver_state = &sent_states.front().state; + + for ( typename list< TimestampedState >::iterator i = sent_states.begin(); + i != sent_states.end(); + i++ ) { + i->state.subtract( known_receiver_state ); + } +} diff --git a/networktransport.hpp b/networktransport.hpp index a8322db..5a8a7cb 100644 --- a/networktransport.hpp +++ b/networktransport.hpp @@ -1,32 +1,84 @@ #ifndef NETWORK_TRANSPORT_HPP #define NETWORK_TRANSPORT_HPP -#include +#include +#include +#include +#include -using google::dense_hash_map; +#include "network.hpp" + +using namespace std; namespace Network { + class Instruction + { + public: + uint64_t old_num, new_num; + string diff; + + uint64_t ack_num; + + Instruction( uint64_t s_old_num, uint64_t s_new_num, string s_diff, uint64_t s_ack_num ) + : old_num( s_old_num ), new_num( s_new_num ), diff( s_diff ), ack_num( s_ack_num ) + {} + + string tostring( void ) { return ""; } + }; + + template + class TimestampedState + { + public: + uint64_t timestamp; + uint64_t num; + State state; + + TimestampedState( uint64_t s_timestamp, uint64_t s_num, State &s_state ) + : timestamp( s_timestamp ), num( s_num ), state( s_state ) + {} + }; + template class Transport { private: + static const int INITIAL_TIMEOUT = 1000; /* ms, same as TCP */ + static const int SEND_INTERVAL = 20; /* ms between frames */ + static const int HEADER_LEN = 40; + + /* helper methods for tick() */ + void update_assumed_receiver_state( void ); + void rationalize_states( void ); + void send_to_receiver( void ); + Connection connection; + bool server; - typedef dense_hash_map< uint64_t, MyState > StateMapper; + uint64_t timestamp( void ); - dense_hash_map< uint64_t, MyState > sent; - dense_hash_map< uint64_t, RemoteState > received; + /* sender */ + MyState current_state; - uint64_t known_receiver_state; - uint64_t assumed_receiver_state; - uint64_t last_sent_state; + list< TimestampedState > sent_states; + /* first element: known, acknowledged receiver state */ + /* last element: last sent state */ + /* somewhere in the middle: the assumed state of the receiver */ + + typename list< TimestampedState >::iterator assumed_receiver_state; + + int timeout; + + /* simple receiver */ + uint64_t highest_state_received; public: - Transport(); - Transport( const char *key_str, const char *ip, int port ); + Transport( MyState &initial_state ); + Transport( MyState &initial_state, const char *key_str, const char *ip, int port ); - + void new_state( MyState &s ); + void tick( void ); }; -}; +} #endif diff --git a/templates.cpp b/templates.cpp index ff66ccd..5044399 100644 --- a/templates.cpp +++ b/templates.cpp @@ -6,6 +6,9 @@ #include "terminal.hpp" +#include "keystroke.hpp" +#include "networktransport.cpp" + namespace Parser { class Action; } @@ -22,3 +25,4 @@ template class vector; template class map; template class vector; +template class Network::Transport; diff --git a/terminal.hpp b/terminal.hpp index 69fe811..50400ae 100644 --- a/terminal.hpp +++ b/terminal.hpp @@ -24,6 +24,7 @@ namespace Terminal { friend void Parser::OSC_Start::act_on_terminal( Emulator * ); friend void Parser::OSC_Put::act_on_terminal( Emulator * ); friend void Parser::OSC_End::act_on_terminal( Emulator * ); + friend void Parser::UserByte::act_on_terminal( Emulator * ); friend void Parser::Resize::act_on_terminal( Emulator * );