/src/mosh/src/statesync/completeterminal.cc
Line | Count | Source (jump to first uncovered line) |
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 | | #include <climits> |
34 | | |
35 | | #include "src/protobufs/hostinput.pb.h" |
36 | | #include "src/statesync/completeterminal.h" |
37 | | #include "src/util/fatal_assert.h" |
38 | | |
39 | | using namespace std; |
40 | | using namespace Parser; |
41 | | using namespace Terminal; |
42 | | using namespace HostBuffers; |
43 | | |
44 | | string Complete::act( const string& str ) |
45 | 0 | { |
46 | 0 | for ( unsigned int i = 0; i < str.size(); i++ ) { |
47 | | /* parse octet into up to three actions */ |
48 | 0 | parser.input( str[i], actions ); |
49 | | |
50 | | /* apply actions to terminal and delete them */ |
51 | 0 | for ( Actions::iterator it = actions.begin(); it != actions.end(); it++ ) { |
52 | 0 | Action& act = **it; |
53 | 0 | act.act_on_terminal( &terminal ); |
54 | 0 | } |
55 | 0 | actions.clear(); |
56 | 0 | } |
57 | |
|
58 | 0 | return terminal.read_octets_to_host(); |
59 | 0 | } |
60 | | |
61 | | string Complete::act( const Action& act ) |
62 | 4.19M | { |
63 | | /* apply action to terminal */ |
64 | 4.19M | act.act_on_terminal( &terminal ); |
65 | 4.19M | return terminal.read_octets_to_host(); |
66 | 4.19M | } |
67 | | |
68 | | /* interface for Network::Transport */ |
69 | | string Complete::diff_from( const Complete& existing ) const |
70 | 0 | { |
71 | 0 | HostBuffers::HostMessage output; |
72 | |
|
73 | 0 | if ( existing.get_echo_ack() != get_echo_ack() ) { |
74 | 0 | assert( get_echo_ack() >= existing.get_echo_ack() ); |
75 | 0 | Instruction* new_echo = output.add_instruction(); |
76 | 0 | new_echo->MutableExtension( echoack )->set_echo_ack_num( get_echo_ack() ); |
77 | 0 | } |
78 | | |
79 | 0 | if ( !( existing.get_fb() == get_fb() ) ) { |
80 | 0 | if ( ( existing.get_fb().ds.get_width() != terminal.get_fb().ds.get_width() ) |
81 | 0 | || ( existing.get_fb().ds.get_height() != terminal.get_fb().ds.get_height() ) ) { |
82 | 0 | Instruction* new_res = output.add_instruction(); |
83 | 0 | new_res->MutableExtension( resize )->set_width( terminal.get_fb().ds.get_width() ); |
84 | 0 | new_res->MutableExtension( resize )->set_height( terminal.get_fb().ds.get_height() ); |
85 | 0 | } |
86 | 0 | string update = display.new_frame( true, existing.get_fb(), terminal.get_fb() ); |
87 | 0 | if ( !update.empty() ) { |
88 | 0 | Instruction* new_inst = output.add_instruction(); |
89 | 0 | new_inst->MutableExtension( hostbytes )->set_hoststring( update ); |
90 | 0 | } |
91 | 0 | } |
92 | |
|
93 | 0 | return output.SerializeAsString(); |
94 | 0 | } |
95 | | |
96 | | string Complete::init_diff( void ) const |
97 | 0 | { |
98 | 0 | return diff_from( Complete( get_fb().ds.get_width(), get_fb().ds.get_height() ) ); |
99 | 0 | } |
100 | | |
101 | | void Complete::apply_string( const string& diff ) |
102 | 0 | { |
103 | 0 | HostBuffers::HostMessage input; |
104 | 0 | fatal_assert( input.ParseFromString( diff ) ); |
105 | |
|
106 | 0 | for ( int i = 0; i < input.instruction_size(); i++ ) { |
107 | 0 | if ( input.instruction( i ).HasExtension( hostbytes ) ) { |
108 | 0 | string terminal_to_host = act( input.instruction( i ).GetExtension( hostbytes ).hoststring() ); |
109 | 0 | assert( terminal_to_host.empty() ); /* server never interrogates client terminal */ |
110 | 0 | } else if ( input.instruction( i ).HasExtension( resize ) ) { |
111 | 0 | act( Resize( input.instruction( i ).GetExtension( resize ).width(), |
112 | 0 | input.instruction( i ).GetExtension( resize ).height() ) ); |
113 | 0 | } else if ( input.instruction( i ).HasExtension( echoack ) ) { |
114 | 0 | uint64_t inst_echo_ack_num = input.instruction( i ).GetExtension( echoack ).echo_ack_num(); |
115 | 0 | assert( inst_echo_ack_num >= echo_ack ); |
116 | 0 | echo_ack = inst_echo_ack_num; |
117 | 0 | } |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | bool Complete::operator==( Complete const& x ) const |
122 | 0 | { |
123 | | // assert( parser == x.parser ); /* parser state is irrelevant for us */ |
124 | 0 | return ( terminal == x.terminal ) && ( echo_ack == x.echo_ack ); |
125 | 0 | } |
126 | | |
127 | | bool Complete::set_echo_ack( uint64_t now ) |
128 | 0 | { |
129 | 0 | bool ret = false; |
130 | 0 | uint64_t newest_echo_ack = 0; |
131 | |
|
132 | 0 | for ( input_history_type::const_iterator i = input_history.begin(); i != input_history.end(); i++ ) { |
133 | 0 | if ( i->second <= now - ECHO_TIMEOUT ) { |
134 | 0 | newest_echo_ack = i->first; |
135 | 0 | } |
136 | 0 | } |
137 | |
|
138 | 0 | for ( input_history_type::iterator i = input_history.begin(); i != input_history.end(); ) { |
139 | 0 | input_history_type::iterator i_next = i; |
140 | 0 | i_next++; |
141 | 0 | if ( i->first < newest_echo_ack ) { |
142 | 0 | input_history.erase( i ); |
143 | 0 | } |
144 | 0 | i = i_next; |
145 | 0 | } |
146 | |
|
147 | 0 | if ( echo_ack != newest_echo_ack ) { |
148 | 0 | ret = true; |
149 | 0 | } |
150 | |
|
151 | 0 | echo_ack = newest_echo_ack; |
152 | |
|
153 | 0 | return ret; |
154 | 0 | } |
155 | | |
156 | | void Complete::register_input_frame( uint64_t n, uint64_t now ) |
157 | 0 | { |
158 | 0 | input_history.push_back( std::make_pair( n, now ) ); |
159 | 0 | } |
160 | | |
161 | | int Complete::wait_time( uint64_t now ) const |
162 | 0 | { |
163 | 0 | if ( input_history.size() < 2 ) { |
164 | 0 | return INT_MAX; |
165 | 0 | } |
166 | | |
167 | 0 | input_history_type::const_iterator it = input_history.begin(); |
168 | 0 | it++; |
169 | |
|
170 | 0 | uint64_t next_echo_ack_time = it->second + ECHO_TIMEOUT; |
171 | 0 | if ( next_echo_ack_time <= now ) { |
172 | 0 | return 0; |
173 | 0 | } |
174 | 0 | return next_echo_ack_time - now; |
175 | 0 | } |
176 | | |
177 | | bool Complete::compare( const Complete& other ) const |
178 | 0 | { |
179 | 0 | bool ret = false; |
180 | 0 | const Framebuffer& fb = terminal.get_fb(); |
181 | 0 | const Framebuffer& other_fb = other.terminal.get_fb(); |
182 | 0 | const int height = fb.ds.get_height(); |
183 | 0 | const int other_height = other_fb.ds.get_height(); |
184 | 0 | const int width = fb.ds.get_width(); |
185 | 0 | const int other_width = other_fb.ds.get_width(); |
186 | |
|
187 | 0 | if ( height != other_height || width != other_width ) { |
188 | 0 | fprintf( stderr, "Framebuffer size (%dx%d, %dx%d) differs.\n", width, height, other_width, other_height ); |
189 | 0 | return true; |
190 | 0 | } |
191 | | |
192 | 0 | for ( int y = 0; y < height; y++ ) { |
193 | 0 | for ( int x = 0; x < width; x++ ) { |
194 | 0 | if ( fb.get_cell( y, x )->compare( *other_fb.get_cell( y, x ) ) ) { |
195 | 0 | fprintf( stderr, "Cell (%d, %d) differs.\n", y, x ); |
196 | 0 | ret = true; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | } |
200 | |
|
201 | 0 | if ( ( fb.ds.get_cursor_row() != other_fb.ds.get_cursor_row() ) |
202 | 0 | || ( fb.ds.get_cursor_col() != other_fb.ds.get_cursor_col() ) ) { |
203 | 0 | fprintf( stderr, |
204 | 0 | "Cursor mismatch: (%d, %d) vs. (%d, %d).\n", |
205 | 0 | fb.ds.get_cursor_row(), |
206 | 0 | fb.ds.get_cursor_col(), |
207 | 0 | other_fb.ds.get_cursor_row(), |
208 | 0 | other_fb.ds.get_cursor_col() ); |
209 | 0 | ret = true; |
210 | 0 | } |
211 | | /* XXX should compare other terminal state too (mouse mode, bell. etc.) */ |
212 | |
|
213 | 0 | return ret; |
214 | 0 | } |