1 /*
2     Mosh: the mobile shell
3     Copyright 2012 Keith Winstein
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 
18     In addition, as a special exception, the copyright holders give
19     permission to link the code of portions of this program with the
20     OpenSSL library under certain conditions as described in each
21     individual source file, and distribute linked combinations including
22     the two.
23 
24     You must obey the GNU General Public License in all respects for all
25     of the code used other than OpenSSL. If you modify file(s) with this
26     exception, you may extend this exception to your version of the
27     file(s), but you are not obligated to do so. If you do not wish to do
28     so, delete this exception statement from your version. If you delete
29     this exception statement from all source files in the program, then
30     also delete it here.
31 */
32 
33 #ifndef TERMINAL_OVERLAY_HPP
34 #define TERMINAL_OVERLAY_HPP
35 
36 #include "terminalframebuffer.h"
37 #include "network.h"
38 #include "transportsender.h"
39 #include "parser.h"
40 
41 #include <vector>
42 #include <limits.h>
43 
44 namespace Overlay {
45   using namespace Terminal;
46   using namespace Network;
47   using std::deque;
48   using std::list;
49   using std::vector;
50   using std::wstring;
51 
52   enum Validity {
53     Pending,
54     Correct,
55     CorrectNoCredit,
56     IncorrectOrExpired,
57     Inactive
58   };
59 
60   class ConditionalOverlay {
61   public:
62     uint64_t expiration_frame;
63     int col;
64     bool active; /* represents a prediction at all */
65     uint64_t tentative_until_epoch; /* when to show */
66     uint64_t prediction_time; /* used to find long-pending predictions */
67 
ConditionalOverlay(uint64_t s_exp,int s_col,uint64_t s_tentative)68     ConditionalOverlay( uint64_t s_exp, int s_col, uint64_t s_tentative )
69       : expiration_frame( s_exp ), col( s_col ),
70 	active( false ),
71 	tentative_until_epoch( s_tentative ), prediction_time( uint64_t( -1 ) )
72     {}
73 
~ConditionalOverlay()74     virtual ~ConditionalOverlay() {}
75 
tentative(uint64_t confirmed_epoch)76     bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; }
reset(void)77     void reset( void ) { expiration_frame = tentative_until_epoch = -1; active = false; }
expire(uint64_t s_exp,uint64_t now)78     void expire( uint64_t s_exp, uint64_t now )
79     {
80       expiration_frame = s_exp; prediction_time = now;
81     }
82   };
83 
84   class ConditionalCursorMove : public ConditionalOverlay {
85   public:
86     int row;
87 
88     void apply( Framebuffer &fb, uint64_t confirmed_epoch ) const;
89 
90     Validity get_validity( const Framebuffer &fb, uint64_t early_ack, uint64_t late_ack ) const;
91 
ConditionalCursorMove(uint64_t s_exp,int s_row,int s_col,uint64_t s_tentative)92     ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col, uint64_t s_tentative )
93       : ConditionalOverlay( s_exp, s_col, s_tentative ), row( s_row )
94     {}
95   };
96 
97   class ConditionalOverlayCell : public ConditionalOverlay {
98   public:
99     Cell replacement;
100     bool unknown;
101 
102     vector<Cell> original_contents; /* we don't give credit for correct predictions
103 				       that match the original contents */
104 
105     void apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const;
106     Validity get_validity( const Framebuffer &fb, int row, uint64_t early_ack, uint64_t late_ack ) const;
107 
ConditionalOverlayCell(uint64_t s_exp,int s_col,uint64_t s_tentative)108     ConditionalOverlayCell( uint64_t s_exp, int s_col, uint64_t s_tentative )
109       : ConditionalOverlay( s_exp, s_col, s_tentative ),
110 	replacement( 0 ),
111 	unknown( false ),
112 	original_contents()
113     {}
114 
reset(void)115     void reset( void ) { unknown = false; original_contents.clear(); ConditionalOverlay::reset(); }
reset_with_orig(void)116     void reset_with_orig( void ) {
117       if ( (!active) || unknown ) {
118 	reset();
119 	return;
120       }
121 
122       original_contents.push_back( replacement );
123       ConditionalOverlay::reset();
124     }
125   };
126 
127   class ConditionalOverlayRow {
128   public:
129     int row_num;
130 
131     typedef vector<ConditionalOverlayCell> overlay_cells_type;
132     overlay_cells_type overlay_cells;
133 
134     void apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const;
135 
136     /* For use with find_if */
row_num_eq(int v)137     bool row_num_eq( int v ) const { return row_num == v; }
138 
ConditionalOverlayRow(int s_row_num)139     ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {}
140   };
141 
142   /* the various overlays */
143   class NotificationEngine {
144   private:
145     uint64_t last_word_from_server;
146     uint64_t last_acked_state;
147     string escape_key_string;
148     wstring message;
149     bool message_is_network_error;
150     uint64_t message_expiration;
151     bool show_quit_keystroke;
152 
server_late(uint64_t ts)153     bool server_late( uint64_t ts ) const { return (ts - last_word_from_server) > 6500; }
reply_late(uint64_t ts)154     bool reply_late( uint64_t ts ) const { return (ts - last_acked_state) > 10000; }
need_countup(uint64_t ts)155     bool need_countup( uint64_t ts ) const { return server_late( ts ) || reply_late( ts ); }
156 
157   public:
158     void adjust_message( void );
159     void apply( Framebuffer &fb ) const;
get_notification_string(void)160     const wstring &get_notification_string( void ) const { return message; }
server_heard(uint64_t s_last_word)161     void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; }
server_acked(uint64_t s_last_acked)162     void server_acked( uint64_t s_last_acked ) { last_acked_state = s_last_acked; }
163     int wait_time( void ) const;
164 
165     void set_notification_string( const wstring &s_message, bool permanent = false, bool s_show_quit_keystroke = true )
166     {
167       message = s_message;
168       if ( permanent ) {
169         message_expiration = -1;
170       } else {
171         message_expiration = timestamp() + 1000;
172       }
173       message_is_network_error = false;
174       show_quit_keystroke = s_show_quit_keystroke;
175     }
176 
set_escape_key_string(const string & s_name)177     void set_escape_key_string( const string &s_name )
178     {
179       char tmp[ 128 ];
180       snprintf( tmp, sizeof tmp, " [To quit: %s .]", s_name.c_str() );
181       escape_key_string = tmp;
182     }
183 
set_network_error(const std::string & s)184     void set_network_error( const std::string &s )
185     {
186       wchar_t tmp[ 128 ];
187       swprintf( tmp, 128, L"%s", s.c_str() );
188 
189       message = tmp;
190       message_is_network_error = true;
191       message_expiration = timestamp() + Network::ACK_INTERVAL + 100;
192     }
193 
clear_network_error()194     void clear_network_error()
195     {
196       if ( message_is_network_error ) {
197 	message_expiration = std::min( message_expiration, timestamp() + 1000 );
198       }
199     }
200 
201     NotificationEngine();
202   };
203 
204   class PredictionEngine {
205   private:
206     static const uint64_t SRTT_TRIGGER_LOW = 20; /* <= ms cures SRTT trigger to show predictions */
207     static const uint64_t SRTT_TRIGGER_HIGH = 30; /* > ms starts SRTT trigger */
208 
209     static const uint64_t FLAG_TRIGGER_LOW = 50; /* <= ms cures flagging */
210     static const uint64_t FLAG_TRIGGER_HIGH = 80; /* > ms starts flagging */
211 
212     static const uint64_t GLITCH_THRESHOLD = 250; /* prediction outstanding this long is glitch */
213     static const uint64_t GLITCH_REPAIR_COUNT = 10; /* non-glitches required to cure glitch trigger */
214     static const uint64_t GLITCH_REPAIR_MININTERVAL = 150; /* required time in between non-glitches */
215 
216     static const uint64_t GLITCH_FLAG_THRESHOLD = 5000; /* prediction outstanding this long => underline */
217 
218     char last_byte;
219     Parser::UTF8Parser parser;
220 
221     typedef list<ConditionalOverlayRow> overlays_type;
222     overlays_type overlays;
223 
224     typedef list<ConditionalCursorMove> cursors_type;
225     cursors_type cursors;
226 
227     typedef ConditionalOverlayRow::overlay_cells_type overlay_cells_type;
228 
229     uint64_t local_frame_sent, local_frame_acked, local_frame_late_acked;
230 
231     ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols );
232 
233     uint64_t prediction_epoch;
234     uint64_t confirmed_epoch;
235 
236     void become_tentative( void );
237 
238     void newline_carriage_return( const Framebuffer &fb );
239 
240     bool flagging; /* whether we are underlining predictions */
241     bool srtt_trigger; /* show predictions because of slow round trip time */
242     unsigned int glitch_trigger; /* show predictions temporarily because of long-pending prediction */
243     uint64_t last_quick_confirmation;
244 
cursor(void)245     ConditionalCursorMove & cursor( void ) { assert( !cursors.empty() ); return cursors.back(); }
246 
247     void kill_epoch( uint64_t epoch, const Framebuffer &fb );
248 
249     void init_cursor( const Framebuffer &fb );
250 
251     unsigned int send_interval;
252 
253     int last_height, last_width;
254 
255   public:
256     enum DisplayPreference {
257       Always,
258       Never,
259       Adaptive,
260       Experimental
261     };
262 
263   private:
264     DisplayPreference display_preference;
265 
266     bool active( void ) const;
267 
timing_tests_necessary(void)268     bool timing_tests_necessary( void ) const {
269       /* Are there any timing-based triggers that haven't fired yet? */
270       return !( glitch_trigger && flagging );
271     }
272 
273   public:
set_display_preference(DisplayPreference s_pref)274     void set_display_preference( DisplayPreference s_pref ) { display_preference = s_pref; }
275 
276     void apply( Framebuffer &fb ) const;
277     void new_user_byte( char the_byte, const Framebuffer &fb );
278     void cull( const Framebuffer &fb );
279 
280     void reset( void );
281 
set_local_frame_sent(uint64_t x)282     void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; }
set_local_frame_acked(uint64_t x)283     void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; }
set_local_frame_late_acked(uint64_t x)284     void set_local_frame_late_acked( uint64_t x ) { local_frame_late_acked = x; }
285 
set_send_interval(unsigned int x)286     void set_send_interval( unsigned int x ) { send_interval = x; }
287 
wait_time(void)288     int wait_time( void ) const
289     {
290       return ( timing_tests_necessary() && active() )
291           ? 50
292           : INT_MAX;
293     }
294 
PredictionEngine(void)295     PredictionEngine( void ) : last_byte( 0 ), parser(), overlays(), cursors(),
296 			       local_frame_sent( 0 ), local_frame_acked( 0 ),
297 			       local_frame_late_acked( 0 ),
298 			       prediction_epoch( 1 ), confirmed_epoch( 0 ),
299 			       flagging( false ),
300 			       srtt_trigger( false ),
301 			       glitch_trigger( 0 ),
302 			       last_quick_confirmation( 0 ),
303 			       send_interval( 250 ),
304 			       last_height( 0 ), last_width( 0 ),
305 			       display_preference( Adaptive )
306     {
307     }
308   };
309 
310   class TitleEngine {
311   private:
312     Terminal::Framebuffer::title_type prefix;
313 
314   public:
apply(Framebuffer & fb)315     void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); }
TitleEngine()316     TitleEngine() : prefix() {}
317     void set_prefix( const wstring &s );
318   };
319 
320   /* the overlay manager */
321   class OverlayManager {
322   private:
323     NotificationEngine notifications;
324     PredictionEngine predictions;
325     TitleEngine title;
326 
327   public:
328     void apply( Framebuffer &fb );
329 
get_notification_engine(void)330     NotificationEngine & get_notification_engine( void ) { return notifications; }
get_prediction_engine(void)331     PredictionEngine & get_prediction_engine( void ) { return predictions; }
332 
set_title_prefix(const wstring & s)333     void set_title_prefix( const wstring &s ) { title.set_prefix( s ); }
334 
OverlayManager()335     OverlayManager() : notifications(), predictions(), title() {}
336 
wait_time(void)337     int wait_time( void ) const
338     {
339       return std::min( notifications.wait_time(), predictions.wait_time() );
340     }
341   };
342 }
343 
344 #endif
345