~ubuntu-branches/ubuntu/vivid/phabricator/vivid-proposed

« back to all changes in this revision

Viewing changes to arcanist/src/hgdaemon/ArcanistHgClientChannel.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-10-23 20:49:26 UTC
  • mfrom: (0.2.1) (0.1.1)
  • Revision ID: package-import@ubuntu.com-20141023204926-vq80u1op4df44azb
Tags: 0~git20141023-1
Initial release (closes: #703046)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * Channel to a Mercurial "cmdserver" client. For a detailed description of the
 
5
 * "cmdserver" protocol, see @{class:ArcanistHgServerChannel}. This channel
 
6
 * implements the other half of the protocol: it decodes messages from the
 
7
 * client and encodes messages from the server.
 
8
 *
 
9
 * Because the proxy server speaks the exact same protocol that Mercurial
 
10
 * does and fully decodes both sides of the protocol, we need this half of the
 
11
 * decode/encode to talk to clients. Without it, we wouldn't be able to
 
12
 * determine when a client request had completed and was ready for transmission
 
13
 * to the Mercurial server.
 
14
 *
 
15
 * (Technically, we could get away without re-encoding messages from the
 
16
 * server, but the serialization is not complicated and having a general
 
17
 * implementation of encoded/decode for both the client and server dialects
 
18
 * seemed useful.)
 
19
 *
 
20
 * @task protocol Protocol Implementation
 
21
 */
 
22
final class ArcanistHgClientChannel extends PhutilProtocolChannel {
 
23
 
 
24
  const MODE_COMMAND    = 'command';
 
25
  const MODE_LENGTH     = 'length';
 
26
  const MODE_ARGUMENTS  = 'arguments';
 
27
 
 
28
  private $command;
 
29
  private $byteLengthOfNextChunk;
 
30
 
 
31
  private $buf = '';
 
32
  private $mode = self::MODE_COMMAND;
 
33
 
 
34
 
 
35
/* -(  Protocol Implementation  )-------------------------------------------- */
 
36
 
 
37
 
 
38
  /**
 
39
   * Encode a message for transmission to the client. The message should be
 
40
   * a pair with the channel name and the a block of data, like this:
 
41
   *
 
42
   *   array('o', '<some data...>');
 
43
   *
 
44
   * We encode it like this:
 
45
   *
 
46
   *   o
 
47
   *   1234                # Length, as a 4-byte unsigned long.
 
48
   *   <data: 1234 bytes>
 
49
   *
 
50
   * For a detailed description of the cmdserver protocol, see
 
51
   * @{class:ArcanistHgServerChannel}.
 
52
   *
 
53
   * @param pair<string,string> The <channel, data> pair to encode.
 
54
   * @return string Encoded string for transmission to the client.
 
55
   *
 
56
   * @task protocol
 
57
   */
 
58
  protected function encodeMessage($argv) {
 
59
    if (!is_array($argv) || count($argv) !== 2) {
 
60
      throw new Exception('Message should be <channel, data>.');
 
61
    }
 
62
 
 
63
    $channel = head($argv);
 
64
    $data    = last($argv);
 
65
 
 
66
    $len = strlen($data);
 
67
    $len = pack('N', $len);
 
68
 
 
69
    return "{$channel}{$len}{$data}";
 
70
  }
 
71
 
 
72
 
 
73
  /**
 
74
   * Decode a message received from the client. The message looks like this:
 
75
   *
 
76
   *   runcommand\n
 
77
   *   8                   # Length, as a 4-byte unsigned long.
 
78
   *   log\0
 
79
   *   -l\0
 
80
   *   5
 
81
   *
 
82
   * We decode it into a list in PHP, which looks like this:
 
83
   *
 
84
   *   array(
 
85
   *     'runcommand',
 
86
   *     'log',
 
87
   *     '-l',
 
88
   *     '5',
 
89
   *   );
 
90
   *
 
91
   * @param string Bytes from the server.
 
92
   * @return list<list<string>> Zero or more complete commands.
 
93
   *
 
94
   * @task protocol
 
95
   */
 
96
  protected function decodeStream($data) {
 
97
    $this->buf .= $data;
 
98
 
 
99
    // The first part is terminated by "\n", so we don't always know how many
 
100
    // bytes we need to look for. This makes parsing a bit of a pain.
 
101
 
 
102
    $messages = array();
 
103
 
 
104
    do {
 
105
      $continue_parsing = false;
 
106
 
 
107
      switch ($this->mode) {
 
108
        case self::MODE_COMMAND:
 
109
          // We're looking for "\n", which indicates the end of the command
 
110
          // name, like "runcommand". Next, we'll expect a length.
 
111
 
 
112
          $pos = strpos($this->buf, "\n");
 
113
          if ($pos === false) {
 
114
            break;
 
115
          }
 
116
 
 
117
          $this->command = substr($this->buf, 0, $pos);
 
118
          $this->buf = substr($this->buf, $pos + 1);
 
119
          $this->mode = self::MODE_LENGTH;
 
120
 
 
121
          $continue_parsing = true;
 
122
          break;
 
123
        case self::MODE_LENGTH:
 
124
          // We're looking for a byte length, as a 4-byte big-endian unsigned
 
125
          // integer. Next, we'll expect that many bytes of data.
 
126
 
 
127
          if (strlen($this->buf) < 4) {
 
128
            break;
 
129
          }
 
130
 
 
131
          $len = substr($this->buf, 0, 4);
 
132
          $len = unpack('N', $len);
 
133
          $len = head($len);
 
134
 
 
135
          $this->buf = substr($this->buf, 4);
 
136
 
 
137
          $this->mode = self::MODE_ARGUMENTS;
 
138
          $this->byteLengthOfNextChunk = $len;
 
139
 
 
140
          $continue_parsing = true;
 
141
          break;
 
142
        case self::MODE_ARGUMENTS:
 
143
          // We're looking for the data itself, which is a block of bytes
 
144
          // of the given length. These are arguments delimited by "\0". Next
 
145
          // we'll expect another command.
 
146
 
 
147
          if (strlen($this->buf) < $this->byteLengthOfNextChunk) {
 
148
            break;
 
149
          }
 
150
 
 
151
          $data = substr($this->buf, 0, $this->byteLengthOfNextChunk);
 
152
          $this->buf = substr($this->buf, $this->byteLengthOfNextChunk);
 
153
 
 
154
          $message = array_merge(array($this->command), explode("\0", $data));
 
155
 
 
156
          $this->mode = self::MODE_COMMAND;
 
157
          $this->command = null;
 
158
          $this->byteLengthOfNextChunk = null;
 
159
 
 
160
          $messages[] = $message;
 
161
 
 
162
          $continue_parsing = true;
 
163
          break;
 
164
      }
 
165
    } while ($continue_parsing);
 
166
 
 
167
    return $messages;
 
168
  }
 
169
 
 
170
}