3 final class DiffusionMercurialWireClientSSHProtocolChannel
4 extends PhutilProtocolChannel
{
7 private $state = 'command';
8 private $expectArgumentCount;
15 protected function encodeMessage($message) {
19 private function initializeState($last_command = null) {
20 if ($last_command == 'unbundle') {
21 $this->command
= '<raw-data>';
22 $this->state
= 'data-length';
24 $this->state
= 'command';
26 $this->expectArgumentCount
= null;
27 $this->expectBytes
= null;
28 $this->command
= null;
29 $this->argumentName
= null;
30 $this->arguments
= array();
34 private function readProtocolLine() {
35 $pos = strpos($this->buffer
, "\n");
41 $line = substr($this->buffer
, 0, $pos);
43 $this->raw
.= $line."\n";
44 $this->buffer
= substr($this->buffer
, $pos +
1);
49 private function readProtocolBytes() {
50 if (strlen($this->buffer
) < $this->expectBytes
) {
54 $bytes = substr($this->buffer
, 0, $this->expectBytes
);
56 $this->buffer
= substr($this->buffer
, $this->expectBytes
);
61 private function newMessageAndResetState() {
63 'command' => $this->command
,
64 'arguments' => $this->arguments
,
67 $this->initializeState($this->command
);
71 private function newDataMessage($bytes) {
73 'command' => '<raw-data>',
74 'raw' => strlen($bytes)."\n".$bytes,
79 protected function decodeStream($data) {
80 $this->buffer
.= $data;
86 if ($this->state
== 'command') {
87 $this->initializeState();
89 // We're reading a command. It looks like:
93 $line = $this->readProtocolLine();
98 $this->command
= $line;
99 $this->state
= 'arguments';
100 } else if ($this->state
== 'arguments') {
102 // Check if we're still waiting for arguments.
103 $args = DiffusionMercurialWireProtocol
::getCommandArgs($this->command
);
104 $have = array_select_keys($this->arguments
, $args);
105 if (count($have) == count($args)) {
106 // We have all the arguments. Emit a message and read the next
108 $messages[] = $this->newMessageAndResetState();
110 // We're still reading arguments. They can either look like:
112 // <name> <length(value)>
119 // <name1> <length(value1)>
123 $line = $this->readProtocolLine();
124 if ($line === null) {
128 list($arg, $size) = explode(' ', $line, 2);
132 $this->expectBytes
= $size;
133 $this->argumentName
= $arg;
134 $this->state
= 'value';
136 $this->arguments
['*'] = array();
137 $this->expectArgumentCount
= $size;
138 $this->state
= 'argv';
141 } else if ($this->state
== 'value' ||
$this->state
== 'argv-value') {
143 // We're reading the value of an argument. We just need to wait for
144 // the right number of bytes to show up.
146 $bytes = $this->readProtocolBytes();
147 if ($bytes === null) {
151 if ($this->state
== 'argv-value') {
152 $this->arguments
['*'][$this->argumentName
] = $bytes;
153 $this->state
= 'argv';
155 $this->arguments
[$this->argumentName
] = $bytes;
156 $this->state
= 'arguments';
160 } else if ($this->state
== 'argv') {
162 // We're reading a variable number of arguments. We need to wait for
163 // the arguments to arrive.
165 if ($this->expectArgumentCount
) {
166 $line = $this->readProtocolLine();
167 if ($line === null) {
171 list($arg, $size) = explode(' ', $line, 2);
174 $this->expectBytes
= $size;
175 $this->argumentName
= $arg;
176 $this->state
= 'argv-value';
178 $this->expectArgumentCount
--;
180 $this->state
= 'arguments';
182 } else if ($this->state
== 'data-length') {
184 // We're reading the length of a chunk of raw data. It looks like
187 // <length-in-bytes>\n
189 // The length is human-readable text (for example, "4096"), and
192 $line = $this->readProtocolLine();
193 if ($line === null) {
196 $this->expectBytes
= (int)$line;
197 if (!$this->expectBytes
) {
198 $messages[] = $this->newDataMessage('');
199 $this->initializeState();
201 $this->state
= 'data-bytes';
203 } else if ($this->state
== 'data-bytes') {
205 // We're reading some known, nonzero number of raw bytes of data.
207 // If we don't have any more bytes on the buffer yet, just bail:
208 // otherwise, we'll emit a pointless and possibly harmful 0-byte data
209 // frame. See T13036 for discussion.
210 if (!strlen($this->buffer
)) {
214 $bytes = substr($this->buffer
, 0, $this->expectBytes
);
215 $this->buffer
= substr($this->buffer
, strlen($bytes));
216 $this->expectBytes
-= strlen($bytes);
218 // NOTE: We emit a data frame as soon as we read some data. This can
219 // cause us to repackage frames: for example, if we receive one large
220 // frame slowly, we may emit it as several smaller frames. In theory
221 // this is good; in practice, Mercurial never seems to select a frame
222 // size larger than 4096 bytes naturally and this may be more
223 // complexity and trouble than it is worth. See T13036.
225 $messages[] = $this->newDataMessage($bytes);
227 if (!$this->expectBytes
) {
228 // We've finished reading this chunk, so go read the next chunk.
229 $this->state
= 'data-length';
231 // We're waiting for more data, and have read everything available
236 throw new Exception(pht("Bad parser state '%s'!", $this->state
));