First step of sending side of transport layer

This commit is contained in:
Keith Winstein
2011-08-08 18:41:09 -04:00
parent b0b36ddf94
commit cae526fceb
6 changed files with 255 additions and 33 deletions
+3 -3
View File
@@ -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)
+6 -18
View File
@@ -4,27 +4,15 @@
#include <string>
#include <assert.h>
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
+177
View File
@@ -0,0 +1,177 @@
#include <assert.h>
#include "networktransport.hpp"
using namespace Network;
using namespace std;
template <class MyState, class RemoteState>
uint64_t Transport<MyState, RemoteState>::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 <class MyState, class RemoteState>
Transport<MyState, RemoteState>::Transport( MyState &initial_state )
: connection(),
server( true ),
current_state( initial_state ),
sent_states( 1, TimestampedState<MyState>( timestamp(), 0, initial_state ) ),
assumed_receiver_state( sent_states.begin() ),
timeout( INITIAL_TIMEOUT ),
highest_state_received( 0 )
{
/* server */
}
template <class MyState, class RemoteState>
Transport<MyState, RemoteState>::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<MyState>( timestamp(), 0, initial_state ) ),
assumed_receiver_state( sent_states.begin() ),
timeout( INITIAL_TIMEOUT ),
highest_state_received( 0 )
{
/* client */
}
template <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::new_state( MyState &s )
{
current_state = s;
tick();
}
template <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::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 <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::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<MyState> >::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<MyState>( 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 <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::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<MyState> >::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 <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::rationalize_states( void )
{
MyState * const known_receiver_state = &sent_states.front().state;
for ( typename list< TimestampedState<MyState> >::iterator i = sent_states.begin();
i != sent_states.end();
i++ ) {
i->state.subtract( known_receiver_state );
}
}
+64 -12
View File
@@ -1,32 +1,84 @@
#ifndef NETWORK_TRANSPORT_HPP
#define NETWORK_TRANSPORT_HPP
#include <google/dense_hash_map>
#include <string>
#include <signal.h>
#include <time.h>
#include <list>
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 State>
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 MyState, class RemoteState>
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<MyState> > 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<MyState> >::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
+4
View File
@@ -6,6 +6,9 @@
#include "terminal.hpp"
#include "keystroke.hpp"
#include "networktransport.cpp"
namespace Parser {
class Action;
}
@@ -22,3 +25,4 @@ template class vector<int>;
template class map<string, Function>;
template class vector<bool>;
template class Network::Transport<KeyStroke, KeyStroke>;
+1
View File
@@ -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 * );