##// END OF EJS Templates
wireprotov2: change behavior of error frame...
Gregory Szorc -
r37744:0c184ca5 default
parent child Browse files
Show More
@@ -1,1892 +1,1913 b''
1 The Mercurial wire protocol is a request-response based protocol
1 The Mercurial wire protocol is a request-response based protocol
2 with multiple wire representations.
2 with multiple wire representations.
3
3
4 Each request is modeled as a command name, a dictionary of arguments, and
4 Each request is modeled as a command name, a dictionary of arguments, and
5 optional raw input. Command arguments and their types are intrinsic
5 optional raw input. Command arguments and their types are intrinsic
6 properties of commands. So is the response type of the command. This means
6 properties of commands. So is the response type of the command. This means
7 clients can't always send arbitrary arguments to servers and servers can't
7 clients can't always send arbitrary arguments to servers and servers can't
8 return multiple response types.
8 return multiple response types.
9
9
10 The protocol is synchronous and does not support multiplexing (concurrent
10 The protocol is synchronous and does not support multiplexing (concurrent
11 commands).
11 commands).
12
12
13 Handshake
13 Handshake
14 =========
14 =========
15
15
16 It is required or common for clients to perform a *handshake* when connecting
16 It is required or common for clients to perform a *handshake* when connecting
17 to a server. The handshake serves the following purposes:
17 to a server. The handshake serves the following purposes:
18
18
19 * Negotiating protocol/transport level options
19 * Negotiating protocol/transport level options
20 * Allows the client to learn about server capabilities to influence
20 * Allows the client to learn about server capabilities to influence
21 future requests
21 future requests
22 * Ensures the underlying transport channel is in a *clean* state
22 * Ensures the underlying transport channel is in a *clean* state
23
23
24 An important goal of the handshake is to allow clients to use more modern
24 An important goal of the handshake is to allow clients to use more modern
25 wire protocol features. By default, clients must assume they are talking
25 wire protocol features. By default, clients must assume they are talking
26 to an old version of Mercurial server (possibly even the very first
26 to an old version of Mercurial server (possibly even the very first
27 implementation). So, clients should not attempt to call or utilize modern
27 implementation). So, clients should not attempt to call or utilize modern
28 wire protocol features until they have confirmation that the server
28 wire protocol features until they have confirmation that the server
29 supports them. The handshake implementation is designed to allow both
29 supports them. The handshake implementation is designed to allow both
30 ends to utilize the latest set of features and capabilities with as
30 ends to utilize the latest set of features and capabilities with as
31 few round trips as possible.
31 few round trips as possible.
32
32
33 The handshake mechanism varies by transport and protocol and is documented
33 The handshake mechanism varies by transport and protocol and is documented
34 in the sections below.
34 in the sections below.
35
35
36 HTTP Protocol
36 HTTP Protocol
37 =============
37 =============
38
38
39 Handshake
39 Handshake
40 ---------
40 ---------
41
41
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
43 as soon as HTTP requests may be issued.
43 as soon as HTTP requests may be issued.
44
44
45 By default, the server responds with a version 1 capabilities string, which
45 By default, the server responds with a version 1 capabilities string, which
46 the client parses to learn about the server's abilities. The ``Content-Type``
46 the client parses to learn about the server's abilities. The ``Content-Type``
47 for this response is ``application/mercurial-0.1`` or
47 for this response is ``application/mercurial-0.1`` or
48 ``application/mercurial-0.2`` depending on whether the client advertised
48 ``application/mercurial-0.2`` depending on whether the client advertised
49 support for version ``0.2`` in its request. (Clients aren't supposed to
49 support for version ``0.2`` in its request. (Clients aren't supposed to
50 advertise support for ``0.2`` until the capabilities response indicates
50 advertise support for ``0.2`` until the capabilities response indicates
51 the server's support for that media type. However, a client could
51 the server's support for that media type. However, a client could
52 conceivably cache this metadata and issue the capabilities request in such
52 conceivably cache this metadata and issue the capabilities request in such
53 a way to elicit an ``application/mercurial-0.2`` response.)
53 a way to elicit an ``application/mercurial-0.2`` response.)
54
54
55 Clients wishing to switch to a newer API service may send an
55 Clients wishing to switch to a newer API service may send an
56 ``X-HgUpgrade-<X>`` header containing a space-delimited list of API service
56 ``X-HgUpgrade-<X>`` header containing a space-delimited list of API service
57 names the client is capable of speaking. The request MUST also include an
57 names the client is capable of speaking. The request MUST also include an
58 ``X-HgProto-<X>`` header advertising a known serialization format for the
58 ``X-HgProto-<X>`` header advertising a known serialization format for the
59 response. ``cbor`` is currently the only defined serialization format.
59 response. ``cbor`` is currently the only defined serialization format.
60
60
61 If the request contains these headers, the response ``Content-Type`` MAY
61 If the request contains these headers, the response ``Content-Type`` MAY
62 be for a different media type. e.g. ``application/mercurial-cbor`` if the
62 be for a different media type. e.g. ``application/mercurial-cbor`` if the
63 client advertises support for CBOR.
63 client advertises support for CBOR.
64
64
65 The response MUST be deserializable to a map with the following keys:
65 The response MUST be deserializable to a map with the following keys:
66
66
67 apibase
67 apibase
68 URL path to API services, relative to the repository root. e.g. ``api/``.
68 URL path to API services, relative to the repository root. e.g. ``api/``.
69
69
70 apis
70 apis
71 A map of API service names to API descriptors. An API descriptor contains
71 A map of API service names to API descriptors. An API descriptor contains
72 more details about that API. In the case of the HTTP Version 2 Transport,
72 more details about that API. In the case of the HTTP Version 2 Transport,
73 it will be the normal response to a ``capabilities`` command.
73 it will be the normal response to a ``capabilities`` command.
74
74
75 Only the services advertised by the client that are also available on
75 Only the services advertised by the client that are also available on
76 the server are advertised.
76 the server are advertised.
77
77
78 v1capabilities
78 v1capabilities
79 The capabilities string that would be returned by a version 1 response.
79 The capabilities string that would be returned by a version 1 response.
80
80
81 The client can then inspect the server-advertised APIs and decide which
81 The client can then inspect the server-advertised APIs and decide which
82 API to use, including continuing to use the HTTP Version 1 Transport.
82 API to use, including continuing to use the HTTP Version 1 Transport.
83
83
84 HTTP Version 1 Transport
84 HTTP Version 1 Transport
85 ------------------------
85 ------------------------
86
86
87 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
87 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
88 sent to the base URL of the repository with the command name sent in
88 sent to the base URL of the repository with the command name sent in
89 the ``cmd`` query string parameter. e.g.
89 the ``cmd`` query string parameter. e.g.
90 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
90 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
91 or ``POST`` depending on the command and whether there is a request
91 or ``POST`` depending on the command and whether there is a request
92 body.
92 body.
93
93
94 Command arguments can be sent multiple ways.
94 Command arguments can be sent multiple ways.
95
95
96 The simplest is part of the URL query string using ``x-www-form-urlencoded``
96 The simplest is part of the URL query string using ``x-www-form-urlencoded``
97 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
97 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
98 length limitations on the URL. So this mechanism is typically only used if
98 length limitations on the URL. So this mechanism is typically only used if
99 the server doesn't support other mechanisms.
99 the server doesn't support other mechanisms.
100
100
101 If the server supports the ``httpheader`` capability, command arguments can
101 If the server supports the ``httpheader`` capability, command arguments can
102 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
102 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
103 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
103 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
104 arguments is obtained. This full string is then split into chunks and sent
104 arguments is obtained. This full string is then split into chunks and sent
105 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
105 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
106 is defined by the server in the ``httpheader`` capability value, which defaults
106 is defined by the server in the ``httpheader`` capability value, which defaults
107 to ``1024``. The server reassembles the encoded arguments string by
107 to ``1024``. The server reassembles the encoded arguments string by
108 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
108 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
109 dictionary.
109 dictionary.
110
110
111 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
111 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
112 header to instruct caches to take these headers into consideration when caching
112 header to instruct caches to take these headers into consideration when caching
113 requests.
113 requests.
114
114
115 If the server supports the ``httppostargs`` capability, the client
115 If the server supports the ``httppostargs`` capability, the client
116 may send command arguments in the HTTP request body as part of an
116 may send command arguments in the HTTP request body as part of an
117 HTTP POST request. The command arguments will be URL encoded just like
117 HTTP POST request. The command arguments will be URL encoded just like
118 they would for sending them via HTTP headers. However, no splitting is
118 they would for sending them via HTTP headers. However, no splitting is
119 performed: the raw arguments are included in the HTTP request body.
119 performed: the raw arguments are included in the HTTP request body.
120
120
121 The client sends a ``X-HgArgs-Post`` header with the string length of the
121 The client sends a ``X-HgArgs-Post`` header with the string length of the
122 encoded arguments data. Additional data may be included in the HTTP
122 encoded arguments data. Additional data may be included in the HTTP
123 request body immediately following the argument data. The offset of the
123 request body immediately following the argument data. The offset of the
124 non-argument data is defined by the ``X-HgArgs-Post`` header. The
124 non-argument data is defined by the ``X-HgArgs-Post`` header. The
125 ``X-HgArgs-Post`` header is not required if there is no argument data.
125 ``X-HgArgs-Post`` header is not required if there is no argument data.
126
126
127 Additional command data can be sent as part of the HTTP request body. The
127 Additional command data can be sent as part of the HTTP request body. The
128 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
128 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
129 A ``Content-Length`` header is currently always sent.
129 A ``Content-Length`` header is currently always sent.
130
130
131 Example HTTP requests::
131 Example HTTP requests::
132
132
133 GET /repo?cmd=capabilities
133 GET /repo?cmd=capabilities
134 X-HgArg-1: foo=bar&baz=hello%20world
134 X-HgArg-1: foo=bar&baz=hello%20world
135
135
136 The request media type should be chosen based on server support. If the
136 The request media type should be chosen based on server support. If the
137 ``httpmediatype`` server capability is present, the client should send
137 ``httpmediatype`` server capability is present, the client should send
138 the newest mutually supported media type. If this capability is absent,
138 the newest mutually supported media type. If this capability is absent,
139 the client must assume the server only supports the
139 the client must assume the server only supports the
140 ``application/mercurial-0.1`` media type.
140 ``application/mercurial-0.1`` media type.
141
141
142 The ``Content-Type`` HTTP response header identifies the response as coming
142 The ``Content-Type`` HTTP response header identifies the response as coming
143 from Mercurial and can also be used to signal an error has occurred.
143 from Mercurial and can also be used to signal an error has occurred.
144
144
145 The ``application/mercurial-*`` media types indicate a generic Mercurial
145 The ``application/mercurial-*`` media types indicate a generic Mercurial
146 data type.
146 data type.
147
147
148 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
148 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
149 predecessor of the format below.
149 predecessor of the format below.
150
150
151 The ``application/mercurial-0.2`` media type is compression framed Mercurial
151 The ``application/mercurial-0.2`` media type is compression framed Mercurial
152 data. The first byte of the payload indicates the length of the compression
152 data. The first byte of the payload indicates the length of the compression
153 format identifier that follows. Next are N bytes indicating the compression
153 format identifier that follows. Next are N bytes indicating the compression
154 format. e.g. ``zlib``. The remaining bytes are compressed according to that
154 format. e.g. ``zlib``. The remaining bytes are compressed according to that
155 compression format. The decompressed data behaves the same as with
155 compression format. The decompressed data behaves the same as with
156 ``application/mercurial-0.1``.
156 ``application/mercurial-0.1``.
157
157
158 The ``application/hg-error`` media type indicates a generic error occurred.
158 The ``application/hg-error`` media type indicates a generic error occurred.
159 The content of the HTTP response body typically holds text describing the
159 The content of the HTTP response body typically holds text describing the
160 error.
160 error.
161
161
162 The ``application/mercurial-cbor`` media type indicates a CBOR payload
162 The ``application/mercurial-cbor`` media type indicates a CBOR payload
163 and should be interpreted as identical to ``application/cbor``.
163 and should be interpreted as identical to ``application/cbor``.
164
164
165 Behavior of media types is further described in the ``Content Negotiation``
165 Behavior of media types is further described in the ``Content Negotiation``
166 section below.
166 section below.
167
167
168 Clients should issue a ``User-Agent`` request header that identifies the client.
168 Clients should issue a ``User-Agent`` request header that identifies the client.
169 The server should not use the ``User-Agent`` for feature detection.
169 The server should not use the ``User-Agent`` for feature detection.
170
170
171 A command returning a ``string`` response issues a
171 A command returning a ``string`` response issues a
172 ``application/mercurial-0.*`` media type and the HTTP response body contains
172 ``application/mercurial-0.*`` media type and the HTTP response body contains
173 the raw string value (after compression decoding, if used). A
173 the raw string value (after compression decoding, if used). A
174 ``Content-Length`` header is typically issued, but not required.
174 ``Content-Length`` header is typically issued, but not required.
175
175
176 A command returning a ``stream`` response issues a
176 A command returning a ``stream`` response issues a
177 ``application/mercurial-0.*`` media type and the HTTP response is typically
177 ``application/mercurial-0.*`` media type and the HTTP response is typically
178 using *chunked transfer* (``Transfer-Encoding: chunked``).
178 using *chunked transfer* (``Transfer-Encoding: chunked``).
179
179
180 HTTP Version 2 Transport
180 HTTP Version 2 Transport
181 ------------------------
181 ------------------------
182
182
183 **Experimental - feature under active development**
183 **Experimental - feature under active development**
184
184
185 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
185 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
186 It's final API name is not yet formalized.
186 It's final API name is not yet formalized.
187
187
188 Commands are triggered by sending HTTP POST requests against URLs of the
188 Commands are triggered by sending HTTP POST requests against URLs of the
189 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
189 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
190 ``rw``, meaning read-only and read-write, respectively and ``<command>``
190 ``rw``, meaning read-only and read-write, respectively and ``<command>``
191 is a named wire protocol command.
191 is a named wire protocol command.
192
192
193 Non-POST request methods MUST be rejected by the server with an HTTP
193 Non-POST request methods MUST be rejected by the server with an HTTP
194 405 response.
194 405 response.
195
195
196 Commands that modify repository state in meaningful ways MUST NOT be
196 Commands that modify repository state in meaningful ways MUST NOT be
197 exposed under the ``ro`` URL prefix. All available commands MUST be
197 exposed under the ``ro`` URL prefix. All available commands MUST be
198 available under the ``rw`` URL prefix.
198 available under the ``rw`` URL prefix.
199
199
200 Server adminstrators MAY implement blanket HTTP authentication keyed
200 Server adminstrators MAY implement blanket HTTP authentication keyed
201 off the URL prefix. For example, a server may require authentication
201 off the URL prefix. For example, a server may require authentication
202 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
202 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
203 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
203 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
204 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
204 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
205 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
205 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
206 make an attempt to recognize unknown schemes using the
206 make an attempt to recognize unknown schemes using the
207 ``WWW-Authenticate`` response header on a 401 response, as defined by
207 ``WWW-Authenticate`` response header on a 401 response, as defined by
208 RFC 7235.
208 RFC 7235.
209
209
210 Read-only commands are accessible under ``rw/*`` URLs so clients can
210 Read-only commands are accessible under ``rw/*`` URLs so clients can
211 signal the intent of the operation very early in the connection
211 signal the intent of the operation very early in the connection
212 lifecycle. For example, a ``push`` operation - which consists of
212 lifecycle. For example, a ``push`` operation - which consists of
213 various read-only commands mixed with at least one read-write command -
213 various read-only commands mixed with at least one read-write command -
214 can perform all commands against ``rw/*`` URLs so that any server-side
214 can perform all commands against ``rw/*`` URLs so that any server-side
215 authentication requirements are discovered upon attempting the first
215 authentication requirements are discovered upon attempting the first
216 command - not potentially several commands into the exchange. This
216 command - not potentially several commands into the exchange. This
217 allows clients to fail faster or prompt for credentials as soon as the
217 allows clients to fail faster or prompt for credentials as soon as the
218 exchange takes place. This provides a better end-user experience.
218 exchange takes place. This provides a better end-user experience.
219
219
220 Requests to unknown commands or URLS result in an HTTP 404.
220 Requests to unknown commands or URLS result in an HTTP 404.
221 TODO formally define response type, how error is communicated, etc.
221 TODO formally define response type, how error is communicated, etc.
222
222
223 HTTP request and response bodies use the *Unified Frame-Based Protocol*
223 HTTP request and response bodies use the *Unified Frame-Based Protocol*
224 (defined below) for media exchange. The entirety of the HTTP message
224 (defined below) for media exchange. The entirety of the HTTP message
225 body is 0 or more frames as defined by this protocol.
225 body is 0 or more frames as defined by this protocol.
226
226
227 Clients and servers MUST advertise the ``TBD`` media type via the
227 Clients and servers MUST advertise the ``TBD`` media type via the
228 ``Content-Type`` request and response headers. In addition, clients MUST
228 ``Content-Type`` request and response headers. In addition, clients MUST
229 advertise this media type value in their ``Accept`` request header in all
229 advertise this media type value in their ``Accept`` request header in all
230 requests.
230 requests.
231 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
231 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
232
232
233 Servers receiving requests without an ``Accept`` header SHOULD respond with
233 Servers receiving requests without an ``Accept`` header SHOULD respond with
234 an HTTP 406.
234 an HTTP 406.
235
235
236 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
236 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
237 respond with an HTTP 415.
237 respond with an HTTP 415.
238
238
239 The command to run is specified in the POST payload as defined by the
239 The command to run is specified in the POST payload as defined by the
240 *Unified Frame-Based Protocol*. This is redundant with data already
240 *Unified Frame-Based Protocol*. This is redundant with data already
241 encoded in the URL. This is by design, so server operators can have
241 encoded in the URL. This is by design, so server operators can have
242 better understanding about server activity from looking merely at
242 better understanding about server activity from looking merely at
243 HTTP access logs.
243 HTTP access logs.
244
244
245 In most circumstances, the command specified in the URL MUST match
245 In most circumstances, the command specified in the URL MUST match
246 the command specified in the frame-based payload or the server will
246 the command specified in the frame-based payload or the server will
247 respond with an error. The exception to this is the special
247 respond with an error. The exception to this is the special
248 ``multirequest`` URL. (See below.) In addition, HTTP requests
248 ``multirequest`` URL. (See below.) In addition, HTTP requests
249 are limited to one command invocation. The exception is the special
249 are limited to one command invocation. The exception is the special
250 ``multirequest`` URL.
250 ``multirequest`` URL.
251
251
252 The ``multirequest`` command endpoints (``ro/multirequest`` and
252 The ``multirequest`` command endpoints (``ro/multirequest`` and
253 ``rw/multirequest``) are special in that they allow the execution of
253 ``rw/multirequest``) are special in that they allow the execution of
254 *any* command and allow the execution of multiple commands. If the
254 *any* command and allow the execution of multiple commands. If the
255 HTTP request issues multiple commands across multiple frames, all
255 HTTP request issues multiple commands across multiple frames, all
256 issued commands will be processed by the server. Per the defined
256 issued commands will be processed by the server. Per the defined
257 behavior of the *Unified Frame-Based Protocol*, commands may be
257 behavior of the *Unified Frame-Based Protocol*, commands may be
258 issued interleaved and responses may come back in a different order
258 issued interleaved and responses may come back in a different order
259 than they were issued. Clients MUST be able to deal with this.
259 than they were issued. Clients MUST be able to deal with this.
260
260
261 SSH Protocol
261 SSH Protocol
262 ============
262 ============
263
263
264 Handshake
264 Handshake
265 ---------
265 ---------
266
266
267 For all clients, the handshake consists of the client sending 1 or more
267 For all clients, the handshake consists of the client sending 1 or more
268 commands to the server using version 1 of the transport. Servers respond
268 commands to the server using version 1 of the transport. Servers respond
269 to commands they know how to respond to and send an empty response (``0\n``)
269 to commands they know how to respond to and send an empty response (``0\n``)
270 for unknown commands (per standard behavior of version 1 of the transport).
270 for unknown commands (per standard behavior of version 1 of the transport).
271 Clients then typically look for a response to the newest sent command to
271 Clients then typically look for a response to the newest sent command to
272 determine which transport version to use and what the available features for
272 determine which transport version to use and what the available features for
273 the connection and server are.
273 the connection and server are.
274
274
275 Preceding any response from client-issued commands, the server may print
275 Preceding any response from client-issued commands, the server may print
276 non-protocol output. It is common for SSH servers to print banners, message
276 non-protocol output. It is common for SSH servers to print banners, message
277 of the day announcements, etc when clients connect. It is assumed that any
277 of the day announcements, etc when clients connect. It is assumed that any
278 such *banner* output will precede any Mercurial server output. So clients
278 such *banner* output will precede any Mercurial server output. So clients
279 must be prepared to handle server output on initial connect that isn't
279 must be prepared to handle server output on initial connect that isn't
280 in response to any client-issued command and doesn't conform to Mercurial's
280 in response to any client-issued command and doesn't conform to Mercurial's
281 wire protocol. This *banner* output should only be on stdout. However,
281 wire protocol. This *banner* output should only be on stdout. However,
282 some servers may send output on stderr.
282 some servers may send output on stderr.
283
283
284 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
284 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
285 having the value
285 having the value
286 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
286 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
287
287
288 The ``between`` command has been supported since the original Mercurial
288 The ``between`` command has been supported since the original Mercurial
289 SSH server. Requesting the empty range will return a ``\n`` string response,
289 SSH server. Requesting the empty range will return a ``\n`` string response,
290 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
290 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
291 followed by the value, which happens to be a newline).
291 followed by the value, which happens to be a newline).
292
292
293 For pre 0.9.1 clients and all servers, the exchange looks like::
293 For pre 0.9.1 clients and all servers, the exchange looks like::
294
294
295 c: between\n
295 c: between\n
296 c: pairs 81\n
296 c: pairs 81\n
297 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
297 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
298 s: 1\n
298 s: 1\n
299 s: \n
299 s: \n
300
300
301 0.9.1+ clients send a ``hello`` command (with no arguments) before the
301 0.9.1+ clients send a ``hello`` command (with no arguments) before the
302 ``between`` command. The response to this command allows clients to
302 ``between`` command. The response to this command allows clients to
303 discover server capabilities and settings.
303 discover server capabilities and settings.
304
304
305 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
305 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
306 like::
306 like::
307
307
308 c: hello\n
308 c: hello\n
309 c: between\n
309 c: between\n
310 c: pairs 81\n
310 c: pairs 81\n
311 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
311 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
312 s: 324\n
312 s: 324\n
313 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
313 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
314 s: 1\n
314 s: 1\n
315 s: \n
315 s: \n
316
316
317 And a similar scenario but with servers sending a banner on connect::
317 And a similar scenario but with servers sending a banner on connect::
318
318
319 c: hello\n
319 c: hello\n
320 c: between\n
320 c: between\n
321 c: pairs 81\n
321 c: pairs 81\n
322 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
322 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
323 s: welcome to the server\n
323 s: welcome to the server\n
324 s: if you find any issues, email someone@somewhere.com\n
324 s: if you find any issues, email someone@somewhere.com\n
325 s: 324\n
325 s: 324\n
326 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
326 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
327 s: 1\n
327 s: 1\n
328 s: \n
328 s: \n
329
329
330 Note that output from the ``hello`` command is terminated by a ``\n``. This is
330 Note that output from the ``hello`` command is terminated by a ``\n``. This is
331 part of the response payload and not part of the wire protocol adding a newline
331 part of the response payload and not part of the wire protocol adding a newline
332 after responses. In other words, the length of the response contains the
332 after responses. In other words, the length of the response contains the
333 trailing ``\n``.
333 trailing ``\n``.
334
334
335 Clients supporting version 2 of the SSH transport send a line beginning
335 Clients supporting version 2 of the SSH transport send a line beginning
336 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
336 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
337 (which isn't a well-formed command line because it doesn't consist of a
337 (which isn't a well-formed command line because it doesn't consist of a
338 single command name) serves to both communicate the client's intent to
338 single command name) serves to both communicate the client's intent to
339 switch to transport version 2 (transports are version 1 by default) as
339 switch to transport version 2 (transports are version 1 by default) as
340 well as to advertise the client's transport-level capabilities so the
340 well as to advertise the client's transport-level capabilities so the
341 server may satisfy that request immediately.
341 server may satisfy that request immediately.
342
342
343 The upgrade line has the form:
343 The upgrade line has the form:
344
344
345 upgrade <token> <transport capabilities>
345 upgrade <token> <transport capabilities>
346
346
347 That is the literal string ``upgrade`` followed by a space, followed by
347 That is the literal string ``upgrade`` followed by a space, followed by
348 a randomly generated string, followed by a space, followed by a string
348 a randomly generated string, followed by a space, followed by a string
349 denoting the client's transport capabilities.
349 denoting the client's transport capabilities.
350
350
351 The token can be anything. However, a random UUID is recommended. (Use
351 The token can be anything. However, a random UUID is recommended. (Use
352 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
352 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
353 client's MAC address.)
353 client's MAC address.)
354
354
355 The transport capabilities string is a URL/percent encoded string
355 The transport capabilities string is a URL/percent encoded string
356 containing key-value pairs defining the client's transport-level
356 containing key-value pairs defining the client's transport-level
357 capabilities. The following capabilities are defined:
357 capabilities. The following capabilities are defined:
358
358
359 proto
359 proto
360 A comma-delimited list of transport protocol versions the client
360 A comma-delimited list of transport protocol versions the client
361 supports. e.g. ``ssh-v2``.
361 supports. e.g. ``ssh-v2``.
362
362
363 If the server does not recognize the ``upgrade`` line, it should issue
363 If the server does not recognize the ``upgrade`` line, it should issue
364 an empty response and continue processing the ``hello`` and ``between``
364 an empty response and continue processing the ``hello`` and ``between``
365 commands. Here is an example handshake between a version 2 aware client
365 commands. Here is an example handshake between a version 2 aware client
366 and a non version 2 aware server:
366 and a non version 2 aware server:
367
367
368 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
368 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
369 c: hello\n
369 c: hello\n
370 c: between\n
370 c: between\n
371 c: pairs 81\n
371 c: pairs 81\n
372 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
372 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
373 s: 0\n
373 s: 0\n
374 s: 324\n
374 s: 324\n
375 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
375 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
376 s: 1\n
376 s: 1\n
377 s: \n
377 s: \n
378
378
379 (The initial ``0\n`` line from the server indicates an empty response to
379 (The initial ``0\n`` line from the server indicates an empty response to
380 the unknown ``upgrade ..`` command/line.)
380 the unknown ``upgrade ..`` command/line.)
381
381
382 If the server recognizes the ``upgrade`` line and is willing to satisfy that
382 If the server recognizes the ``upgrade`` line and is willing to satisfy that
383 upgrade request, it replies to with a payload of the following form:
383 upgrade request, it replies to with a payload of the following form:
384
384
385 upgraded <token> <transport name>\n
385 upgraded <token> <transport name>\n
386
386
387 This line is the literal string ``upgraded``, a space, the token that was
387 This line is the literal string ``upgraded``, a space, the token that was
388 specified by the client in its ``upgrade ...`` request line, a space, and the
388 specified by the client in its ``upgrade ...`` request line, a space, and the
389 name of the transport protocol that was chosen by the server. The transport
389 name of the transport protocol that was chosen by the server. The transport
390 name MUST match one of the names the client specified in the ``proto`` field
390 name MUST match one of the names the client specified in the ``proto`` field
391 of its ``upgrade ...`` request line.
391 of its ``upgrade ...`` request line.
392
392
393 If a server issues an ``upgraded`` response, it MUST also read and ignore
393 If a server issues an ``upgraded`` response, it MUST also read and ignore
394 the lines associated with the ``hello`` and ``between`` command requests
394 the lines associated with the ``hello`` and ``between`` command requests
395 that were issued by the server. It is assumed that the negotiated transport
395 that were issued by the server. It is assumed that the negotiated transport
396 will respond with equivalent requested information following the transport
396 will respond with equivalent requested information following the transport
397 handshake.
397 handshake.
398
398
399 All data following the ``\n`` terminating the ``upgraded`` line is the
399 All data following the ``\n`` terminating the ``upgraded`` line is the
400 domain of the negotiated transport. It is common for the data immediately
400 domain of the negotiated transport. It is common for the data immediately
401 following to contain additional metadata about the state of the transport and
401 following to contain additional metadata about the state of the transport and
402 the server. However, this isn't strictly speaking part of the transport
402 the server. However, this isn't strictly speaking part of the transport
403 handshake and isn't covered by this section.
403 handshake and isn't covered by this section.
404
404
405 Here is an example handshake between a version 2 aware client and a version
405 Here is an example handshake between a version 2 aware client and a version
406 2 aware server:
406 2 aware server:
407
407
408 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
408 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
409 c: hello\n
409 c: hello\n
410 c: between\n
410 c: between\n
411 c: pairs 81\n
411 c: pairs 81\n
412 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
412 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
413 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
413 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
414 s: <additional transport specific data>
414 s: <additional transport specific data>
415
415
416 The client-issued token that is echoed in the response provides a more
416 The client-issued token that is echoed in the response provides a more
417 resilient mechanism for differentiating *banner* output from Mercurial
417 resilient mechanism for differentiating *banner* output from Mercurial
418 output. In version 1, properly formatted banner output could get confused
418 output. In version 1, properly formatted banner output could get confused
419 for Mercurial server output. By submitting a randomly generated token
419 for Mercurial server output. By submitting a randomly generated token
420 that is then present in the response, the client can look for that token
420 that is then present in the response, the client can look for that token
421 in response lines and have reasonable certainty that the line did not
421 in response lines and have reasonable certainty that the line did not
422 originate from a *banner* message.
422 originate from a *banner* message.
423
423
424 SSH Version 1 Transport
424 SSH Version 1 Transport
425 -----------------------
425 -----------------------
426
426
427 The SSH transport (version 1) is a custom text-based protocol suitable for
427 The SSH transport (version 1) is a custom text-based protocol suitable for
428 use over any bi-directional stream transport. It is most commonly used with
428 use over any bi-directional stream transport. It is most commonly used with
429 SSH.
429 SSH.
430
430
431 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
431 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
432 stderr, and stdout file descriptors of the started process are used to exchange
432 stderr, and stdout file descriptors of the started process are used to exchange
433 data. When Mercurial connects to a remote server over SSH, it actually starts
433 data. When Mercurial connects to a remote server over SSH, it actually starts
434 a ``hg serve --stdio`` process on the remote server.
434 a ``hg serve --stdio`` process on the remote server.
435
435
436 Commands are issued by sending the command name followed by a trailing newline
436 Commands are issued by sending the command name followed by a trailing newline
437 ``\n`` to the server. e.g. ``capabilities\n``.
437 ``\n`` to the server. e.g. ``capabilities\n``.
438
438
439 Command arguments are sent in the following format::
439 Command arguments are sent in the following format::
440
440
441 <argument> <length>\n<value>
441 <argument> <length>\n<value>
442
442
443 That is, the argument string name followed by a space followed by the
443 That is, the argument string name followed by a space followed by the
444 integer length of the value (expressed as a string) followed by a newline
444 integer length of the value (expressed as a string) followed by a newline
445 (``\n``) followed by the raw argument value.
445 (``\n``) followed by the raw argument value.
446
446
447 Dictionary arguments are encoded differently::
447 Dictionary arguments are encoded differently::
448
448
449 <argument> <# elements>\n
449 <argument> <# elements>\n
450 <key1> <length1>\n<value1>
450 <key1> <length1>\n<value1>
451 <key2> <length2>\n<value2>
451 <key2> <length2>\n<value2>
452 ...
452 ...
453
453
454 Non-argument data is sent immediately after the final argument value. It is
454 Non-argument data is sent immediately after the final argument value. It is
455 encoded in chunks::
455 encoded in chunks::
456
456
457 <length>\n<data>
457 <length>\n<data>
458
458
459 Each command declares a list of supported arguments and their types. If a
459 Each command declares a list of supported arguments and their types. If a
460 client sends an unknown argument to the server, the server should abort
460 client sends an unknown argument to the server, the server should abort
461 immediately. The special argument ``*`` in a command's definition indicates
461 immediately. The special argument ``*`` in a command's definition indicates
462 that all argument names are allowed.
462 that all argument names are allowed.
463
463
464 The definition of supported arguments and types is initially made when a
464 The definition of supported arguments and types is initially made when a
465 new command is implemented. The client and server must initially independently
465 new command is implemented. The client and server must initially independently
466 agree on the arguments and their types. This initial set of arguments can be
466 agree on the arguments and their types. This initial set of arguments can be
467 supplemented through the presence of *capabilities* advertised by the server.
467 supplemented through the presence of *capabilities* advertised by the server.
468
468
469 Each command has a defined expected response type.
469 Each command has a defined expected response type.
470
470
471 A ``string`` response type is a length framed value. The response consists of
471 A ``string`` response type is a length framed value. The response consists of
472 the string encoded integer length of a value followed by a newline (``\n``)
472 the string encoded integer length of a value followed by a newline (``\n``)
473 followed by the value. Empty values are allowed (and are represented as
473 followed by the value. Empty values are allowed (and are represented as
474 ``0\n``).
474 ``0\n``).
475
475
476 A ``stream`` response type consists of raw bytes of data. There is no framing.
476 A ``stream`` response type consists of raw bytes of data. There is no framing.
477
477
478 A generic error response type is also supported. It consists of a an error
478 A generic error response type is also supported. It consists of a an error
479 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
479 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
480 written to ``stdout``.
480 written to ``stdout``.
481
481
482 If the server receives an unknown command, it will send an empty ``string``
482 If the server receives an unknown command, it will send an empty ``string``
483 response.
483 response.
484
484
485 The server terminates if it receives an empty command (a ``\n`` character).
485 The server terminates if it receives an empty command (a ``\n`` character).
486
486
487 If the server announces support for the ``protocaps`` capability, the client
487 If the server announces support for the ``protocaps`` capability, the client
488 should issue a ``protocaps`` command after the initial handshake to annonunce
488 should issue a ``protocaps`` command after the initial handshake to annonunce
489 its own capabilities. The client capabilities are persistent.
489 its own capabilities. The client capabilities are persistent.
490
490
491 SSH Version 2 Transport
491 SSH Version 2 Transport
492 -----------------------
492 -----------------------
493
493
494 **Experimental and under development**
494 **Experimental and under development**
495
495
496 Version 2 of the SSH transport behaves identically to version 1 of the SSH
496 Version 2 of the SSH transport behaves identically to version 1 of the SSH
497 transport with the exception of handshake semantics. See above for how
497 transport with the exception of handshake semantics. See above for how
498 version 2 of the SSH transport is negotiated.
498 version 2 of the SSH transport is negotiated.
499
499
500 Immediately following the ``upgraded`` line signaling a switch to version
500 Immediately following the ``upgraded`` line signaling a switch to version
501 2 of the SSH protocol, the server automatically sends additional details
501 2 of the SSH protocol, the server automatically sends additional details
502 about the capabilities of the remote server. This has the form:
502 about the capabilities of the remote server. This has the form:
503
503
504 <integer length of value>\n
504 <integer length of value>\n
505 capabilities: ...\n
505 capabilities: ...\n
506
506
507 e.g.
507 e.g.
508
508
509 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
509 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
510 s: 240\n
510 s: 240\n
511 s: capabilities: known getbundle batch ...\n
511 s: capabilities: known getbundle batch ...\n
512
512
513 Following capabilities advertisement, the peers communicate using version
513 Following capabilities advertisement, the peers communicate using version
514 1 of the SSH transport.
514 1 of the SSH transport.
515
515
516 Unified Frame-Based Protocol
516 Unified Frame-Based Protocol
517 ============================
517 ============================
518
518
519 **Experimental and under development**
519 **Experimental and under development**
520
520
521 The *Unified Frame-Based Protocol* is a communications protocol between
521 The *Unified Frame-Based Protocol* is a communications protocol between
522 Mercurial peers. The protocol aims to be mostly transport agnostic
522 Mercurial peers. The protocol aims to be mostly transport agnostic
523 (works similarly on HTTP, SSH, etc).
523 (works similarly on HTTP, SSH, etc).
524
524
525 To operate the protocol, a bi-directional, half-duplex pipe supporting
525 To operate the protocol, a bi-directional, half-duplex pipe supporting
526 ordered sends and receives is required. That is, each peer has one pipe
526 ordered sends and receives is required. That is, each peer has one pipe
527 for sending data and another for receiving.
527 for sending data and another for receiving.
528
528
529 All data is read and written in atomic units called *frames*. These
529 All data is read and written in atomic units called *frames*. These
530 are conceptually similar to TCP packets. Higher-level functionality
530 are conceptually similar to TCP packets. Higher-level functionality
531 is built on the exchange and processing of frames.
531 is built on the exchange and processing of frames.
532
532
533 All frames are associated with a *stream*. A *stream* provides a
533 All frames are associated with a *stream*. A *stream* provides a
534 unidirectional grouping of frames. Streams facilitate two goals:
534 unidirectional grouping of frames. Streams facilitate two goals:
535 content encoding and parallelism. There is a dedicated section on
535 content encoding and parallelism. There is a dedicated section on
536 streams below.
536 streams below.
537
537
538 The protocol is request-response based: the client issues requests to
538 The protocol is request-response based: the client issues requests to
539 the server, which issues replies to those requests. Server-initiated
539 the server, which issues replies to those requests. Server-initiated
540 messaging is not currently supported, but this specification carves
540 messaging is not currently supported, but this specification carves
541 out room to implement it.
541 out room to implement it.
542
542
543 All frames are associated with a numbered request. Frames can thus
543 All frames are associated with a numbered request. Frames can thus
544 be logically grouped by their request ID.
544 be logically grouped by their request ID.
545
545
546 Frames begin with an 8 octet header followed by a variable length
546 Frames begin with an 8 octet header followed by a variable length
547 payload::
547 payload::
548
548
549 +------------------------------------------------+
549 +------------------------------------------------+
550 | Length (24) |
550 | Length (24) |
551 +--------------------------------+---------------+
551 +--------------------------------+---------------+
552 | Request ID (16) | Stream ID (8) |
552 | Request ID (16) | Stream ID (8) |
553 +------------------+-------------+---------------+
553 +------------------+-------------+---------------+
554 | Stream Flags (8) |
554 | Stream Flags (8) |
555 +-----------+------+
555 +-----------+------+
556 | Type (4) |
556 | Type (4) |
557 +-----------+
557 +-----------+
558 | Flags (4) |
558 | Flags (4) |
559 +===========+===================================================|
559 +===========+===================================================|
560 | Frame Payload (0...) ...
560 | Frame Payload (0...) ...
561 +---------------------------------------------------------------+
561 +---------------------------------------------------------------+
562
562
563 The length of the frame payload is expressed as an unsigned 24 bit
563 The length of the frame payload is expressed as an unsigned 24 bit
564 little endian integer. Values larger than 65535 MUST NOT be used unless
564 little endian integer. Values larger than 65535 MUST NOT be used unless
565 given permission by the server as part of the negotiated capabilities
565 given permission by the server as part of the negotiated capabilities
566 during the handshake. The frame header is not part of the advertised
566 during the handshake. The frame header is not part of the advertised
567 frame length. The payload length is the over-the-wire length. If there
567 frame length. The payload length is the over-the-wire length. If there
568 is content encoding applied to the payload as part of the frame's stream,
568 is content encoding applied to the payload as part of the frame's stream,
569 the length is the output of that content encoding, not the input.
569 the length is the output of that content encoding, not the input.
570
570
571 The 16-bit ``Request ID`` field denotes the integer request identifier,
571 The 16-bit ``Request ID`` field denotes the integer request identifier,
572 stored as an unsigned little endian integer. Odd numbered requests are
572 stored as an unsigned little endian integer. Odd numbered requests are
573 client-initiated. Even numbered requests are server-initiated. This
573 client-initiated. Even numbered requests are server-initiated. This
574 refers to where the *request* was initiated - not where the *frame* was
574 refers to where the *request* was initiated - not where the *frame* was
575 initiated, so servers will send frames with odd ``Request ID`` in
575 initiated, so servers will send frames with odd ``Request ID`` in
576 response to client-initiated requests. Implementations are advised to
576 response to client-initiated requests. Implementations are advised to
577 start ordering request identifiers at ``1`` and ``0``, increment by
577 start ordering request identifiers at ``1`` and ``0``, increment by
578 ``2``, and wrap around if all available numbers have been exhausted.
578 ``2``, and wrap around if all available numbers have been exhausted.
579
579
580 The 8-bit ``Stream ID`` field denotes the stream that the frame is
580 The 8-bit ``Stream ID`` field denotes the stream that the frame is
581 associated with. Frames belonging to a stream may have content
581 associated with. Frames belonging to a stream may have content
582 encoding applied and the receiver may need to decode the raw frame
582 encoding applied and the receiver may need to decode the raw frame
583 payload to obtain the original data. Odd numbered IDs are
583 payload to obtain the original data. Odd numbered IDs are
584 client-initiated. Even numbered IDs are server-initiated.
584 client-initiated. Even numbered IDs are server-initiated.
585
585
586 The 8-bit ``Stream Flags`` field defines stream processing semantics.
586 The 8-bit ``Stream Flags`` field defines stream processing semantics.
587 See the section on streams below.
587 See the section on streams below.
588
588
589 The 4-bit ``Type`` field denotes the type of frame being sent.
589 The 4-bit ``Type`` field denotes the type of frame being sent.
590
590
591 The 4-bit ``Flags`` field defines special, per-type attributes for
591 The 4-bit ``Flags`` field defines special, per-type attributes for
592 the frame.
592 the frame.
593
593
594 The sections below define the frame types and their behavior.
594 The sections below define the frame types and their behavior.
595
595
596 Command Request (``0x01``)
596 Command Request (``0x01``)
597 --------------------------
597 --------------------------
598
598
599 This frame contains a request to run a command.
599 This frame contains a request to run a command.
600
600
601 The payload consists of a CBOR map defining the command request. The
601 The payload consists of a CBOR map defining the command request. The
602 bytestring keys of that map are:
602 bytestring keys of that map are:
603
603
604 name
604 name
605 Name of the command that should be executed (bytestring).
605 Name of the command that should be executed (bytestring).
606 args
606 args
607 Map of bytestring keys to various value types containing the named
607 Map of bytestring keys to various value types containing the named
608 arguments to this command.
608 arguments to this command.
609
609
610 Each command defines its own set of argument names and their expected
610 Each command defines its own set of argument names and their expected
611 types.
611 types.
612
612
613 This frame type MUST ONLY be sent from clients to servers: it is illegal
613 This frame type MUST ONLY be sent from clients to servers: it is illegal
614 for a server to send this frame to a client.
614 for a server to send this frame to a client.
615
615
616 The following flag values are defined for this type:
616 The following flag values are defined for this type:
617
617
618 0x01
618 0x01
619 New command request. When set, this frame represents the beginning
619 New command request. When set, this frame represents the beginning
620 of a new request to run a command. The ``Request ID`` attached to this
620 of a new request to run a command. The ``Request ID`` attached to this
621 frame MUST NOT be active.
621 frame MUST NOT be active.
622 0x02
622 0x02
623 Command request continuation. When set, this frame is a continuation
623 Command request continuation. When set, this frame is a continuation
624 from a previous command request frame for its ``Request ID``. This
624 from a previous command request frame for its ``Request ID``. This
625 flag is set when the CBOR data for a command request does not fit
625 flag is set when the CBOR data for a command request does not fit
626 in a single frame.
626 in a single frame.
627 0x04
627 0x04
628 Additional frames expected. When set, the command request didn't fit
628 Additional frames expected. When set, the command request didn't fit
629 into a single frame and additional CBOR data follows in a subsequent
629 into a single frame and additional CBOR data follows in a subsequent
630 frame.
630 frame.
631 0x08
631 0x08
632 Command data frames expected. When set, command data frames are
632 Command data frames expected. When set, command data frames are
633 expected to follow the final command request frame for this request.
633 expected to follow the final command request frame for this request.
634
634
635 ``0x01`` MUST be set on the initial command request frame for a
635 ``0x01`` MUST be set on the initial command request frame for a
636 ``Request ID``.
636 ``Request ID``.
637
637
638 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
638 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
639 a series of command request frames.
639 a series of command request frames.
640
640
641 If command data frames are to be sent, ``0x08`` MUST be set on ALL
641 If command data frames are to be sent, ``0x08`` MUST be set on ALL
642 command request frames.
642 command request frames.
643
643
644 Command Data (``0x02``)
644 Command Data (``0x02``)
645 -----------------------
645 -----------------------
646
646
647 This frame contains raw data for a command.
647 This frame contains raw data for a command.
648
648
649 Most commands can be executed by specifying arguments. However,
649 Most commands can be executed by specifying arguments. However,
650 arguments have an upper bound to their length. For commands that
650 arguments have an upper bound to their length. For commands that
651 accept data that is beyond this length or whose length isn't known
651 accept data that is beyond this length or whose length isn't known
652 when the command is initially sent, they will need to stream
652 when the command is initially sent, they will need to stream
653 arbitrary data to the server. This frame type facilitates the sending
653 arbitrary data to the server. This frame type facilitates the sending
654 of this data.
654 of this data.
655
655
656 The payload of this frame type consists of a stream of raw data to be
656 The payload of this frame type consists of a stream of raw data to be
657 consumed by the command handler on the server. The format of the data
657 consumed by the command handler on the server. The format of the data
658 is command specific.
658 is command specific.
659
659
660 The following flag values are defined for this type:
660 The following flag values are defined for this type:
661
661
662 0x01
662 0x01
663 Command data continuation. When set, the data for this command
663 Command data continuation. When set, the data for this command
664 continues into a subsequent frame.
664 continues into a subsequent frame.
665
665
666 0x02
666 0x02
667 End of data. When set, command data has been fully sent to the
667 End of data. When set, command data has been fully sent to the
668 server. The command has been fully issued and no new data for this
668 server. The command has been fully issued and no new data for this
669 command will be sent. The next frame will belong to a new command.
669 command will be sent. The next frame will belong to a new command.
670
670
671 Command Response Data (``0x03``)
671 Command Response Data (``0x03``)
672 --------------------------------
672 --------------------------------
673
673
674 This frame contains response data to an issued command.
674 This frame contains response data to an issued command.
675
675
676 Response data ALWAYS consists of a series of 1 or more CBOR encoded
676 Response data ALWAYS consists of a series of 1 or more CBOR encoded
677 values. A CBOR value may be using indefinite length encoding. And the
677 values. A CBOR value may be using indefinite length encoding. And the
678 bytes constituting the value may span several frames.
678 bytes constituting the value may span several frames.
679
679
680 The following flag values are defined for this type:
680 The following flag values are defined for this type:
681
681
682 0x01
682 0x01
683 Data continuation. When set, an additional frame containing response data
683 Data continuation. When set, an additional frame containing response data
684 will follow.
684 will follow.
685 0x02
685 0x02
686 End of data. When set, the response data has been fully sent and
686 End of data. When set, the response data has been fully sent and
687 no additional frames for this response will be sent.
687 no additional frames for this response will be sent.
688
688
689 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
689 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
690
690
691 Error Response (``0x05``)
691 Error Occurred (``0x05``)
692 -------------------------
692 -------------------------
693
693
694 An error occurred when processing a request. This could indicate
694 Some kind of error occurred.
695 a protocol-level failure or an application level failure depending
695
696 on the flags for this message type.
696 There are 3 general kinds of failures that can occur:
697
697
698 The payload for this type is an error message that should be
698 * Command error encountered before any response issued
699 displayed to the user.
699 * Command error encountered after a response was issued
700 * Protocol or stream level error
701
702 This frame type is used to capture the latter cases. (The general
703 command error case is handled by the leading CBOR map in
704 ``Command Response`` frames.)
705
706 The payload of this frame contains a CBOR map detailing the error. That
707 map has the following bytestring keys:
700
708
701 The following flag values are defined for this type:
709 type
710 (bytestring) The overall type of error encountered. Can be one of the
711 following values:
712
713 protocol
714 A protocol-level error occurred. This typically means someone
715 is violating the framing protocol semantics and the server is
716 refusing to proceed.
702
717
703 0x01
718 server
704 The error occurred at the transport/protocol level. If set, the
719 A server-level error occurred. This typically indicates some kind of
705 connection should be closed.
720 logic error on the server, likely the fault of the server.
706 0x02
721
707 The error occurred at the application level. e.g. invalid command.
722 command
723 A command-level error, likely the fault of the client.
724
725 message
726 (array of maps) A richly formatted message that is intended for
727 human consumption. See the ``Human Output Side-Channel`` frame
728 section for a description of the format of this data structure.
708
729
709 Human Output Side-Channel (``0x06``)
730 Human Output Side-Channel (``0x06``)
710 ------------------------------------
731 ------------------------------------
711
732
712 This frame contains a message that is intended to be displayed to
733 This frame contains a message that is intended to be displayed to
713 people. Whereas most frames communicate machine readable data, this
734 people. Whereas most frames communicate machine readable data, this
714 frame communicates textual data that is intended to be shown to
735 frame communicates textual data that is intended to be shown to
715 humans.
736 humans.
716
737
717 The frame consists of a series of *formatting requests*. Each formatting
738 The frame consists of a series of *formatting requests*. Each formatting
718 request consists of a formatting string, arguments for that formatting
739 request consists of a formatting string, arguments for that formatting
719 string, and labels to apply to that formatting string.
740 string, and labels to apply to that formatting string.
720
741
721 A formatting string is a printf()-like string that allows variable
742 A formatting string is a printf()-like string that allows variable
722 substitution within the string. Labels allow the rendered text to be
743 substitution within the string. Labels allow the rendered text to be
723 *decorated*. Assuming use of the canonical Mercurial code base, a
744 *decorated*. Assuming use of the canonical Mercurial code base, a
724 formatting string can be the input to the ``i18n._`` function. This
745 formatting string can be the input to the ``i18n._`` function. This
725 allows messages emitted from the server to be localized. So even if
746 allows messages emitted from the server to be localized. So even if
726 the server has different i18n settings, people could see messages in
747 the server has different i18n settings, people could see messages in
727 their *native* settings. Similarly, the use of labels allows
748 their *native* settings. Similarly, the use of labels allows
728 decorations like coloring and underlining to be applied using the
749 decorations like coloring and underlining to be applied using the
729 client's configured rendering settings.
750 client's configured rendering settings.
730
751
731 Formatting strings are similar to ``printf()`` strings or how
752 Formatting strings are similar to ``printf()`` strings or how
732 Python's ``%`` operator works. The only supported formatting sequences
753 Python's ``%`` operator works. The only supported formatting sequences
733 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
754 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
734 at that position resolves to. ``%%`` will be replaced by ``%``. All
755 at that position resolves to. ``%%`` will be replaced by ``%``. All
735 other 2-byte sequences beginning with ``%`` represent a literal
756 other 2-byte sequences beginning with ``%`` represent a literal
736 ``%`` followed by that character. However, future versions of the
757 ``%`` followed by that character. However, future versions of the
737 wire protocol reserve the right to allow clients to opt in to receiving
758 wire protocol reserve the right to allow clients to opt in to receiving
738 formatting strings with additional formatters, hence why ``%%`` is
759 formatting strings with additional formatters, hence why ``%%`` is
739 required to represent the literal ``%``.
760 required to represent the literal ``%``.
740
761
741 The frame payload consists of a CBOR array of CBOR maps. Each map
762 The frame payload consists of a CBOR array of CBOR maps. Each map
742 defines an *atom* of text data to print. Each *atom* has the following
763 defines an *atom* of text data to print. Each *atom* has the following
743 bytestring keys:
764 bytestring keys:
744
765
745 msg
766 msg
746 (bytestring) The formatting string. Content MUST be ASCII.
767 (bytestring) The formatting string. Content MUST be ASCII.
747 args (optional)
768 args (optional)
748 Array of bytestrings defining arguments to the formatting string.
769 Array of bytestrings defining arguments to the formatting string.
749 labels (optional)
770 labels (optional)
750 Array of bytestrings defining labels to apply to this atom.
771 Array of bytestrings defining labels to apply to this atom.
751
772
752 All data to be printed MUST be encoded into a single frame: this frame
773 All data to be printed MUST be encoded into a single frame: this frame
753 does not support spanning data across multiple frames.
774 does not support spanning data across multiple frames.
754
775
755 All textual data encoded in these frames is assumed to be line delimited.
776 All textual data encoded in these frames is assumed to be line delimited.
756 The last atom in the frame SHOULD end with a newline (``\n``). If it
777 The last atom in the frame SHOULD end with a newline (``\n``). If it
757 doesn't, clients MAY add a newline to facilitate immediate printing.
778 doesn't, clients MAY add a newline to facilitate immediate printing.
758
779
759 Progress Update (``0x07``)
780 Progress Update (``0x07``)
760 --------------------------
781 --------------------------
761
782
762 This frame holds the progress of an operation on the peer. Consumption
783 This frame holds the progress of an operation on the peer. Consumption
763 of these frames allows clients to display progress bars, estimated
784 of these frames allows clients to display progress bars, estimated
764 completion times, etc.
785 completion times, etc.
765
786
766 Each frame defines the progress of a single operation on the peer. The
787 Each frame defines the progress of a single operation on the peer. The
767 payload consists of a CBOR map with the following bytestring keys:
788 payload consists of a CBOR map with the following bytestring keys:
768
789
769 topic
790 topic
770 Topic name (string)
791 Topic name (string)
771 pos
792 pos
772 Current numeric position within the topic (integer)
793 Current numeric position within the topic (integer)
773 total
794 total
774 Total/end numeric position of this topic (unsigned integer)
795 Total/end numeric position of this topic (unsigned integer)
775 label (optional)
796 label (optional)
776 Unit label (string)
797 Unit label (string)
777 item (optional)
798 item (optional)
778 Item name (string)
799 Item name (string)
779
800
780 Progress state is created when a frame is received referencing a
801 Progress state is created when a frame is received referencing a
781 *topic* that isn't currently tracked. Progress tracking for that
802 *topic* that isn't currently tracked. Progress tracking for that
782 *topic* is finished when a frame is received reporting the current
803 *topic* is finished when a frame is received reporting the current
783 position of that topic as ``-1``.
804 position of that topic as ``-1``.
784
805
785 Multiple *topics* may be active at any given time.
806 Multiple *topics* may be active at any given time.
786
807
787 Rendering of progress information is not mandated or governed by this
808 Rendering of progress information is not mandated or governed by this
788 specification: implementations MAY render progress information however
809 specification: implementations MAY render progress information however
789 they see fit, including not at all.
810 they see fit, including not at all.
790
811
791 The string data describing the topic SHOULD be static strings to
812 The string data describing the topic SHOULD be static strings to
792 facilitate receivers localizing that string data. The emitter
813 facilitate receivers localizing that string data. The emitter
793 MUST normalize all string data to valid UTF-8 and receivers SHOULD
814 MUST normalize all string data to valid UTF-8 and receivers SHOULD
794 validate that received data conforms to UTF-8. The topic name
815 validate that received data conforms to UTF-8. The topic name
795 SHOULD be ASCII.
816 SHOULD be ASCII.
796
817
797 Stream Encoding Settings (``0x08``)
818 Stream Encoding Settings (``0x08``)
798 -----------------------------------
819 -----------------------------------
799
820
800 This frame type holds information defining the content encoding
821 This frame type holds information defining the content encoding
801 settings for a *stream*.
822 settings for a *stream*.
802
823
803 This frame type is likely consumed by the protocol layer and is not
824 This frame type is likely consumed by the protocol layer and is not
804 passed on to applications.
825 passed on to applications.
805
826
806 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
827 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
807 ``Stream Flag`` set.
828 ``Stream Flag`` set.
808
829
809 The payload of this frame defines what content encoding has (possibly)
830 The payload of this frame defines what content encoding has (possibly)
810 been applied to the payloads of subsequent frames in this stream.
831 been applied to the payloads of subsequent frames in this stream.
811
832
812 The payload begins with an 8-bit integer defining the length of the
833 The payload begins with an 8-bit integer defining the length of the
813 encoding *profile*, followed by the string name of that profile, which
834 encoding *profile*, followed by the string name of that profile, which
814 must be an ASCII string. All bytes that follow can be used by that
835 must be an ASCII string. All bytes that follow can be used by that
815 profile for supplemental settings definitions. See the section below
836 profile for supplemental settings definitions. See the section below
816 on defined encoding profiles.
837 on defined encoding profiles.
817
838
818 Stream States and Flags
839 Stream States and Flags
819 -----------------------
840 -----------------------
820
841
821 Streams can be in two states: *open* and *closed*. An *open* stream
842 Streams can be in two states: *open* and *closed*. An *open* stream
822 is active and frames attached to that stream could arrive at any time.
843 is active and frames attached to that stream could arrive at any time.
823 A *closed* stream is not active. If a frame attached to a *closed*
844 A *closed* stream is not active. If a frame attached to a *closed*
824 stream arrives, that frame MUST have an appropriate stream flag
845 stream arrives, that frame MUST have an appropriate stream flag
825 set indicating beginning of stream. All streams are in the *closed*
846 set indicating beginning of stream. All streams are in the *closed*
826 state by default.
847 state by default.
827
848
828 The ``Stream Flags`` field denotes a set of bit flags for defining
849 The ``Stream Flags`` field denotes a set of bit flags for defining
829 the relationship of this frame within a stream. The following flags
850 the relationship of this frame within a stream. The following flags
830 are defined:
851 are defined:
831
852
832 0x01
853 0x01
833 Beginning of stream. The first frame in the stream MUST set this
854 Beginning of stream. The first frame in the stream MUST set this
834 flag. When received, the ``Stream ID`` this frame is attached to
855 flag. When received, the ``Stream ID`` this frame is attached to
835 becomes ``open``.
856 becomes ``open``.
836
857
837 0x02
858 0x02
838 End of stream. The last frame in a stream MUST set this flag. When
859 End of stream. The last frame in a stream MUST set this flag. When
839 received, the ``Stream ID`` this frame is attached to becomes
860 received, the ``Stream ID`` this frame is attached to becomes
840 ``closed``. Any content encoding context associated with this stream
861 ``closed``. Any content encoding context associated with this stream
841 can be destroyed after processing the payload of this frame.
862 can be destroyed after processing the payload of this frame.
842
863
843 0x04
864 0x04
844 Apply content encoding. When set, any content encoding settings
865 Apply content encoding. When set, any content encoding settings
845 defined by the stream should be applied when attempting to read
866 defined by the stream should be applied when attempting to read
846 the frame. When not set, the frame payload isn't encoded.
867 the frame. When not set, the frame payload isn't encoded.
847
868
848 Streams
869 Streams
849 -------
870 -------
850
871
851 Streams - along with ``Request IDs`` - facilitate grouping of frames.
872 Streams - along with ``Request IDs`` - facilitate grouping of frames.
852 But the purpose of each is quite different and the groupings they
873 But the purpose of each is quite different and the groupings they
853 constitute are independent.
874 constitute are independent.
854
875
855 A ``Request ID`` is essentially a tag. It tells you which logical
876 A ``Request ID`` is essentially a tag. It tells you which logical
856 request a frame is associated with.
877 request a frame is associated with.
857
878
858 A *stream* is a sequence of frames grouped for the express purpose
879 A *stream* is a sequence of frames grouped for the express purpose
859 of applying a stateful encoding or for denoting sub-groups of frames.
880 of applying a stateful encoding or for denoting sub-groups of frames.
860
881
861 Unlike ``Request ID``s which span the request and response, a stream
882 Unlike ``Request ID``s which span the request and response, a stream
862 is unidirectional and stream IDs are independent from client to
883 is unidirectional and stream IDs are independent from client to
863 server.
884 server.
864
885
865 There is no strict hierarchical relationship between ``Request IDs``
886 There is no strict hierarchical relationship between ``Request IDs``
866 and *streams*. A stream can contain frames having multiple
887 and *streams*. A stream can contain frames having multiple
867 ``Request IDs``. Frames belonging to the same ``Request ID`` can
888 ``Request IDs``. Frames belonging to the same ``Request ID`` can
868 span multiple streams.
889 span multiple streams.
869
890
870 One goal of streams is to facilitate content encoding. A stream can
891 One goal of streams is to facilitate content encoding. A stream can
871 define an encoding to be applied to frame payloads. For example, the
892 define an encoding to be applied to frame payloads. For example, the
872 payload transmitted over the wire may contain output from a
893 payload transmitted over the wire may contain output from a
873 zstandard compression operation and the receiving end may decompress
894 zstandard compression operation and the receiving end may decompress
874 that payload to obtain the original data.
895 that payload to obtain the original data.
875
896
876 The other goal of streams is to facilitate concurrent execution. For
897 The other goal of streams is to facilitate concurrent execution. For
877 example, a server could spawn 4 threads to service a request that can
898 example, a server could spawn 4 threads to service a request that can
878 be easily parallelized. Each of those 4 threads could write into its
899 be easily parallelized. Each of those 4 threads could write into its
879 own stream. Those streams could then in turn be delivered to 4 threads
900 own stream. Those streams could then in turn be delivered to 4 threads
880 on the receiving end, with each thread consuming its stream in near
901 on the receiving end, with each thread consuming its stream in near
881 isolation. The *main* thread on both ends merely does I/O and
902 isolation. The *main* thread on both ends merely does I/O and
882 encodes/decodes frame headers: the bulk of the work is done by worker
903 encodes/decodes frame headers: the bulk of the work is done by worker
883 threads.
904 threads.
884
905
885 In addition, since content encoding is defined per stream, each
906 In addition, since content encoding is defined per stream, each
886 *worker thread* could perform potentially CPU bound work concurrently
907 *worker thread* could perform potentially CPU bound work concurrently
887 with other threads. This approach of applying encoding at the
908 with other threads. This approach of applying encoding at the
888 sub-protocol / stream level eliminates a potential resource constraint
909 sub-protocol / stream level eliminates a potential resource constraint
889 on the protocol stream as a whole (it is common for the throughput of
910 on the protocol stream as a whole (it is common for the throughput of
890 a compression engine to be smaller than the throughput of a network).
911 a compression engine to be smaller than the throughput of a network).
891
912
892 Having multiple streams - each with their own encoding settings - also
913 Having multiple streams - each with their own encoding settings - also
893 facilitates the use of advanced data compression techniques. For
914 facilitates the use of advanced data compression techniques. For
894 example, a transmitter could see that it is generating data faster
915 example, a transmitter could see that it is generating data faster
895 and slower than the receiving end is consuming it and adjust its
916 and slower than the receiving end is consuming it and adjust its
896 compression settings to trade CPU for compression ratio accordingly.
917 compression settings to trade CPU for compression ratio accordingly.
897
918
898 While streams can define a content encoding, not all frames within
919 While streams can define a content encoding, not all frames within
899 that stream must use that content encoding. This can be useful when
920 that stream must use that content encoding. This can be useful when
900 data is being served from caches and being derived dynamically. A
921 data is being served from caches and being derived dynamically. A
901 cache could pre-compressed data so the server doesn't have to
922 cache could pre-compressed data so the server doesn't have to
902 recompress it. The ability to pick and choose which frames are
923 recompress it. The ability to pick and choose which frames are
903 compressed allows servers to easily send data to the wire without
924 compressed allows servers to easily send data to the wire without
904 involving potentially expensive encoding overhead.
925 involving potentially expensive encoding overhead.
905
926
906 Content Encoding Profiles
927 Content Encoding Profiles
907 -------------------------
928 -------------------------
908
929
909 Streams can have named content encoding *profiles* associated with
930 Streams can have named content encoding *profiles* associated with
910 them. A profile defines a shared understanding of content encoding
931 them. A profile defines a shared understanding of content encoding
911 settings and behavior.
932 settings and behavior.
912
933
913 The following profiles are defined:
934 The following profiles are defined:
914
935
915 TBD
936 TBD
916
937
917 Command Protocol
938 Command Protocol
918 ----------------
939 ----------------
919
940
920 A client can request that a remote run a command by sending it
941 A client can request that a remote run a command by sending it
921 frames defining that command. This logical stream is composed of
942 frames defining that command. This logical stream is composed of
922 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
943 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
923 frames.
944 frames.
924
945
925 All frames composing a single command request MUST be associated with
946 All frames composing a single command request MUST be associated with
926 the same ``Request ID``.
947 the same ``Request ID``.
927
948
928 Clients MAY send additional command requests without waiting on the
949 Clients MAY send additional command requests without waiting on the
929 response to a previous command request. If they do so, they MUST ensure
950 response to a previous command request. If they do so, they MUST ensure
930 that the ``Request ID`` field of outbound frames does not conflict
951 that the ``Request ID`` field of outbound frames does not conflict
931 with that of an active ``Request ID`` whose response has not yet been
952 with that of an active ``Request ID`` whose response has not yet been
932 fully received.
953 fully received.
933
954
934 Servers MAY respond to commands in a different order than they were
955 Servers MAY respond to commands in a different order than they were
935 sent over the wire. Clients MUST be prepared to deal with this. Servers
956 sent over the wire. Clients MUST be prepared to deal with this. Servers
936 also MAY start executing commands in a different order than they were
957 also MAY start executing commands in a different order than they were
937 received, or MAY execute multiple commands concurrently.
958 received, or MAY execute multiple commands concurrently.
938
959
939 If there is a dependency between commands or a race condition between
960 If there is a dependency between commands or a race condition between
940 commands executing (e.g. a read-only command that depends on the results
961 commands executing (e.g. a read-only command that depends on the results
941 of a command that mutates the repository), then clients MUST NOT send
962 of a command that mutates the repository), then clients MUST NOT send
942 frames issuing a command until a response to all dependent commands has
963 frames issuing a command until a response to all dependent commands has
943 been received.
964 been received.
944 TODO think about whether we should express dependencies between commands
965 TODO think about whether we should express dependencies between commands
945 to avoid roundtrip latency.
966 to avoid roundtrip latency.
946
967
947 A command is defined by a command name, 0 or more command arguments,
968 A command is defined by a command name, 0 or more command arguments,
948 and optional command data.
969 and optional command data.
949
970
950 Arguments are the recommended mechanism for transferring fixed sets of
971 Arguments are the recommended mechanism for transferring fixed sets of
951 parameters to a command. Data is appropriate for transferring variable
972 parameters to a command. Data is appropriate for transferring variable
952 data. Thinking in terms of HTTP, arguments would be headers and data
973 data. Thinking in terms of HTTP, arguments would be headers and data
953 would be the message body.
974 would be the message body.
954
975
955 It is recommended for servers to delay the dispatch of a command
976 It is recommended for servers to delay the dispatch of a command
956 until all argument have been received. Servers MAY impose limits on the
977 until all argument have been received. Servers MAY impose limits on the
957 maximum argument size.
978 maximum argument size.
958 TODO define failure mechanism.
979 TODO define failure mechanism.
959
980
960 Servers MAY dispatch to commands immediately once argument data
981 Servers MAY dispatch to commands immediately once argument data
961 is available or delay until command data is received in full.
982 is available or delay until command data is received in full.
962
983
963 Once a ``Command Request`` frame is sent, a client must be prepared to
984 Once a ``Command Request`` frame is sent, a client must be prepared to
964 receive any of the following frames associated with that request:
985 receive any of the following frames associated with that request:
965 ``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
986 ``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
966 ``Progress Update``.
987 ``Progress Update``.
967
988
968 The *main* response for a command will be in ``Command Response`` frames.
989 The *main* response for a command will be in ``Command Response`` frames.
969 The payloads of these frames consist of 1 or more CBOR encoded values.
990 The payloads of these frames consist of 1 or more CBOR encoded values.
970 The first CBOR value on the first ``Command Response`` frame is special
991 The first CBOR value on the first ``Command Response`` frame is special
971 and denotes the overall status of the command. This CBOR map contains
992 and denotes the overall status of the command. This CBOR map contains
972 the following bytestring keys:
993 the following bytestring keys:
973
994
974 status
995 status
975 (bytestring) A well-defined message containing the overall status of
996 (bytestring) A well-defined message containing the overall status of
976 this command request. The following values are defined:
997 this command request. The following values are defined:
977
998
978 ok
999 ok
979 The command was received successfully and its response follows.
1000 The command was received successfully and its response follows.
980 error
1001 error
981 There was an error processing the command. More details about the
1002 There was an error processing the command. More details about the
982 error are encoded in the ``error`` key.
1003 error are encoded in the ``error`` key.
983
1004
984 error (optional)
1005 error (optional)
985 A map containing information about an encountered error. The map has the
1006 A map containing information about an encountered error. The map has the
986 following keys:
1007 following keys:
987
1008
988 message
1009 message
989 (array of maps) A message describing the error. The message uses the
1010 (array of maps) A message describing the error. The message uses the
990 same format as those in the ``Human Output Side-Channel`` frame.
1011 same format as those in the ``Human Output Side-Channel`` frame.
991
1012
992 Capabilities
1013 Capabilities
993 ============
1014 ============
994
1015
995 Servers advertise supported wire protocol features. This allows clients to
1016 Servers advertise supported wire protocol features. This allows clients to
996 probe for server features before blindly calling a command or passing a
1017 probe for server features before blindly calling a command or passing a
997 specific argument.
1018 specific argument.
998
1019
999 The server's features are exposed via a *capabilities* string. This is a
1020 The server's features are exposed via a *capabilities* string. This is a
1000 space-delimited string of tokens/features. Some features are single words
1021 space-delimited string of tokens/features. Some features are single words
1001 like ``lookup`` or ``batch``. Others are complicated key-value pairs
1022 like ``lookup`` or ``batch``. Others are complicated key-value pairs
1002 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
1023 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
1003 values are used, each feature name can define its own encoding of sub-values.
1024 values are used, each feature name can define its own encoding of sub-values.
1004 Comma-delimited and ``x-www-form-urlencoded`` values are common.
1025 Comma-delimited and ``x-www-form-urlencoded`` values are common.
1005
1026
1006 The following document capabilities defined by the canonical Mercurial server
1027 The following document capabilities defined by the canonical Mercurial server
1007 implementation.
1028 implementation.
1008
1029
1009 batch
1030 batch
1010 -----
1031 -----
1011
1032
1012 Whether the server supports the ``batch`` command.
1033 Whether the server supports the ``batch`` command.
1013
1034
1014 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1035 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1015
1036
1016 branchmap
1037 branchmap
1017 ---------
1038 ---------
1018
1039
1019 Whether the server supports the ``branchmap`` command.
1040 Whether the server supports the ``branchmap`` command.
1020
1041
1021 This capability/command was introduced in Mercurial 1.3 (released July 2009).
1042 This capability/command was introduced in Mercurial 1.3 (released July 2009).
1022
1043
1023 bundle2-exp
1044 bundle2-exp
1024 -----------
1045 -----------
1025
1046
1026 Precursor to ``bundle2`` capability that was used before bundle2 was a
1047 Precursor to ``bundle2`` capability that was used before bundle2 was a
1027 stable feature.
1048 stable feature.
1028
1049
1029 This capability was introduced in Mercurial 3.0 behind an experimental
1050 This capability was introduced in Mercurial 3.0 behind an experimental
1030 flag. This capability should not be observed in the wild.
1051 flag. This capability should not be observed in the wild.
1031
1052
1032 bundle2
1053 bundle2
1033 -------
1054 -------
1034
1055
1035 Indicates whether the server supports the ``bundle2`` data exchange format.
1056 Indicates whether the server supports the ``bundle2`` data exchange format.
1036
1057
1037 The value of the capability is a URL quoted, newline (``\n``) delimited
1058 The value of the capability is a URL quoted, newline (``\n``) delimited
1038 list of keys or key-value pairs.
1059 list of keys or key-value pairs.
1039
1060
1040 A key is simply a URL encoded string.
1061 A key is simply a URL encoded string.
1041
1062
1042 A key-value pair is a URL encoded key separated from a URL encoded value by
1063 A key-value pair is a URL encoded key separated from a URL encoded value by
1043 an ``=``. If the value is a list, elements are delimited by a ``,`` after
1064 an ``=``. If the value is a list, elements are delimited by a ``,`` after
1044 URL encoding.
1065 URL encoding.
1045
1066
1046 For example, say we have the values::
1067 For example, say we have the values::
1047
1068
1048 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
1069 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
1049
1070
1050 We would first construct a string::
1071 We would first construct a string::
1051
1072
1052 HG20\nchangegroup=01,02\ndigests=sha1,sha512
1073 HG20\nchangegroup=01,02\ndigests=sha1,sha512
1053
1074
1054 We would then URL quote this string::
1075 We would then URL quote this string::
1055
1076
1056 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
1077 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
1057
1078
1058 This capability was introduced in Mercurial 3.4 (released May 2015).
1079 This capability was introduced in Mercurial 3.4 (released May 2015).
1059
1080
1060 changegroupsubset
1081 changegroupsubset
1061 -----------------
1082 -----------------
1062
1083
1063 Whether the server supports the ``changegroupsubset`` command.
1084 Whether the server supports the ``changegroupsubset`` command.
1064
1085
1065 This capability was introduced in Mercurial 0.9.2 (released December
1086 This capability was introduced in Mercurial 0.9.2 (released December
1066 2006).
1087 2006).
1067
1088
1068 This capability was introduced at the same time as the ``lookup``
1089 This capability was introduced at the same time as the ``lookup``
1069 capability/command.
1090 capability/command.
1070
1091
1071 compression
1092 compression
1072 -----------
1093 -----------
1073
1094
1074 Declares support for negotiating compression formats.
1095 Declares support for negotiating compression formats.
1075
1096
1076 Presence of this capability indicates the server supports dynamic selection
1097 Presence of this capability indicates the server supports dynamic selection
1077 of compression formats based on the client request.
1098 of compression formats based on the client request.
1078
1099
1079 Servers advertising this capability are required to support the
1100 Servers advertising this capability are required to support the
1080 ``application/mercurial-0.2`` media type in response to commands returning
1101 ``application/mercurial-0.2`` media type in response to commands returning
1081 streams. Servers may support this media type on any command.
1102 streams. Servers may support this media type on any command.
1082
1103
1083 The value of the capability is a comma-delimited list of strings declaring
1104 The value of the capability is a comma-delimited list of strings declaring
1084 supported compression formats. The order of the compression formats is in
1105 supported compression formats. The order of the compression formats is in
1085 server-preferred order, most preferred first.
1106 server-preferred order, most preferred first.
1086
1107
1087 The identifiers used by the official Mercurial distribution are:
1108 The identifiers used by the official Mercurial distribution are:
1088
1109
1089 bzip2
1110 bzip2
1090 bzip2
1111 bzip2
1091 none
1112 none
1092 uncompressed / raw data
1113 uncompressed / raw data
1093 zlib
1114 zlib
1094 zlib (no gzip header)
1115 zlib (no gzip header)
1095 zstd
1116 zstd
1096 zstd
1117 zstd
1097
1118
1098 This capability was introduced in Mercurial 4.1 (released February 2017).
1119 This capability was introduced in Mercurial 4.1 (released February 2017).
1099
1120
1100 getbundle
1121 getbundle
1101 ---------
1122 ---------
1102
1123
1103 Whether the server supports the ``getbundle`` command.
1124 Whether the server supports the ``getbundle`` command.
1104
1125
1105 This capability was introduced in Mercurial 1.9 (released July 2011).
1126 This capability was introduced in Mercurial 1.9 (released July 2011).
1106
1127
1107 httpheader
1128 httpheader
1108 ----------
1129 ----------
1109
1130
1110 Whether the server supports receiving command arguments via HTTP request
1131 Whether the server supports receiving command arguments via HTTP request
1111 headers.
1132 headers.
1112
1133
1113 The value of the capability is an integer describing the max header
1134 The value of the capability is an integer describing the max header
1114 length that clients should send. Clients should ignore any content after a
1135 length that clients should send. Clients should ignore any content after a
1115 comma in the value, as this is reserved for future use.
1136 comma in the value, as this is reserved for future use.
1116
1137
1117 This capability was introduced in Mercurial 1.9 (released July 2011).
1138 This capability was introduced in Mercurial 1.9 (released July 2011).
1118
1139
1119 httpmediatype
1140 httpmediatype
1120 -------------
1141 -------------
1121
1142
1122 Indicates which HTTP media types (``Content-Type`` header) the server is
1143 Indicates which HTTP media types (``Content-Type`` header) the server is
1123 capable of receiving and sending.
1144 capable of receiving and sending.
1124
1145
1125 The value of the capability is a comma-delimited list of strings identifying
1146 The value of the capability is a comma-delimited list of strings identifying
1126 support for media type and transmission direction. The following strings may
1147 support for media type and transmission direction. The following strings may
1127 be present:
1148 be present:
1128
1149
1129 0.1rx
1150 0.1rx
1130 Indicates server support for receiving ``application/mercurial-0.1`` media
1151 Indicates server support for receiving ``application/mercurial-0.1`` media
1131 types.
1152 types.
1132
1153
1133 0.1tx
1154 0.1tx
1134 Indicates server support for sending ``application/mercurial-0.1`` media
1155 Indicates server support for sending ``application/mercurial-0.1`` media
1135 types.
1156 types.
1136
1157
1137 0.2rx
1158 0.2rx
1138 Indicates server support for receiving ``application/mercurial-0.2`` media
1159 Indicates server support for receiving ``application/mercurial-0.2`` media
1139 types.
1160 types.
1140
1161
1141 0.2tx
1162 0.2tx
1142 Indicates server support for sending ``application/mercurial-0.2`` media
1163 Indicates server support for sending ``application/mercurial-0.2`` media
1143 types.
1164 types.
1144
1165
1145 minrx=X
1166 minrx=X
1146 Minimum media type version the server is capable of receiving. Value is a
1167 Minimum media type version the server is capable of receiving. Value is a
1147 string like ``0.2``.
1168 string like ``0.2``.
1148
1169
1149 This capability can be used by servers to limit connections from legacy
1170 This capability can be used by servers to limit connections from legacy
1150 clients not using the latest supported media type. However, only clients
1171 clients not using the latest supported media type. However, only clients
1151 with knowledge of this capability will know to consult this value. This
1172 with knowledge of this capability will know to consult this value. This
1152 capability is present so the client may issue a more user-friendly error
1173 capability is present so the client may issue a more user-friendly error
1153 when the server has locked out a legacy client.
1174 when the server has locked out a legacy client.
1154
1175
1155 mintx=X
1176 mintx=X
1156 Minimum media type version the server is capable of sending. Value is a
1177 Minimum media type version the server is capable of sending. Value is a
1157 string like ``0.1``.
1178 string like ``0.1``.
1158
1179
1159 Servers advertising support for the ``application/mercurial-0.2`` media type
1180 Servers advertising support for the ``application/mercurial-0.2`` media type
1160 should also advertise the ``compression`` capability.
1181 should also advertise the ``compression`` capability.
1161
1182
1162 This capability was introduced in Mercurial 4.1 (released February 2017).
1183 This capability was introduced in Mercurial 4.1 (released February 2017).
1163
1184
1164 httppostargs
1185 httppostargs
1165 ------------
1186 ------------
1166
1187
1167 **Experimental**
1188 **Experimental**
1168
1189
1169 Indicates that the server supports and prefers clients send command arguments
1190 Indicates that the server supports and prefers clients send command arguments
1170 via a HTTP POST request as part of the request body.
1191 via a HTTP POST request as part of the request body.
1171
1192
1172 This capability was introduced in Mercurial 3.8 (released May 2016).
1193 This capability was introduced in Mercurial 3.8 (released May 2016).
1173
1194
1174 known
1195 known
1175 -----
1196 -----
1176
1197
1177 Whether the server supports the ``known`` command.
1198 Whether the server supports the ``known`` command.
1178
1199
1179 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1200 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1180
1201
1181 lookup
1202 lookup
1182 ------
1203 ------
1183
1204
1184 Whether the server supports the ``lookup`` command.
1205 Whether the server supports the ``lookup`` command.
1185
1206
1186 This capability was introduced in Mercurial 0.9.2 (released December
1207 This capability was introduced in Mercurial 0.9.2 (released December
1187 2006).
1208 2006).
1188
1209
1189 This capability was introduced at the same time as the ``changegroupsubset``
1210 This capability was introduced at the same time as the ``changegroupsubset``
1190 capability/command.
1211 capability/command.
1191
1212
1192 partial-pull
1213 partial-pull
1193 ------------
1214 ------------
1194
1215
1195 Indicates that the client can deal with partial answers to pull requests
1216 Indicates that the client can deal with partial answers to pull requests
1196 by repeating the request.
1217 by repeating the request.
1197
1218
1198 If this parameter is not advertised, the server will not send pull bundles.
1219 If this parameter is not advertised, the server will not send pull bundles.
1199
1220
1200 This client capability was introduced in Mercurial 4.6.
1221 This client capability was introduced in Mercurial 4.6.
1201
1222
1202 protocaps
1223 protocaps
1203 ---------
1224 ---------
1204
1225
1205 Whether the server supports the ``protocaps`` command for SSH V1 transport.
1226 Whether the server supports the ``protocaps`` command for SSH V1 transport.
1206
1227
1207 This capability was introduced in Mercurial 4.6.
1228 This capability was introduced in Mercurial 4.6.
1208
1229
1209 pushkey
1230 pushkey
1210 -------
1231 -------
1211
1232
1212 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
1233 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
1213
1234
1214 This capability was introduced in Mercurial 1.6 (released July 2010).
1235 This capability was introduced in Mercurial 1.6 (released July 2010).
1215
1236
1216 standardbundle
1237 standardbundle
1217 --------------
1238 --------------
1218
1239
1219 **Unsupported**
1240 **Unsupported**
1220
1241
1221 This capability was introduced during the Mercurial 0.9.2 development cycle in
1242 This capability was introduced during the Mercurial 0.9.2 development cycle in
1222 2006. It was never present in a release, as it was replaced by the ``unbundle``
1243 2006. It was never present in a release, as it was replaced by the ``unbundle``
1223 capability. This capability should not be encountered in the wild.
1244 capability. This capability should not be encountered in the wild.
1224
1245
1225 stream-preferred
1246 stream-preferred
1226 ----------------
1247 ----------------
1227
1248
1228 If present the server prefers that clients clone using the streaming clone
1249 If present the server prefers that clients clone using the streaming clone
1229 protocol (``hg clone --stream``) rather than the standard
1250 protocol (``hg clone --stream``) rather than the standard
1230 changegroup/bundle based protocol.
1251 changegroup/bundle based protocol.
1231
1252
1232 This capability was introduced in Mercurial 2.2 (released May 2012).
1253 This capability was introduced in Mercurial 2.2 (released May 2012).
1233
1254
1234 streamreqs
1255 streamreqs
1235 ----------
1256 ----------
1236
1257
1237 Indicates whether the server supports *streaming clones* and the *requirements*
1258 Indicates whether the server supports *streaming clones* and the *requirements*
1238 that clients must support to receive it.
1259 that clients must support to receive it.
1239
1260
1240 If present, the server supports the ``stream_out`` command, which transmits
1261 If present, the server supports the ``stream_out`` command, which transmits
1241 raw revlogs from the repository instead of changegroups. This provides a faster
1262 raw revlogs from the repository instead of changegroups. This provides a faster
1242 cloning mechanism at the expense of more bandwidth used.
1263 cloning mechanism at the expense of more bandwidth used.
1243
1264
1244 The value of this capability is a comma-delimited list of repo format
1265 The value of this capability is a comma-delimited list of repo format
1245 *requirements*. These are requirements that impact the reading of data in
1266 *requirements*. These are requirements that impact the reading of data in
1246 the ``.hg/store`` directory. An example value is
1267 the ``.hg/store`` directory. An example value is
1247 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1268 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1248 the ``revlogv1`` and ``generaldelta`` requirements.
1269 the ``revlogv1`` and ``generaldelta`` requirements.
1249
1270
1250 If the only format requirement is ``revlogv1``, the server may expose the
1271 If the only format requirement is ``revlogv1``, the server may expose the
1251 ``stream`` capability instead of the ``streamreqs`` capability.
1272 ``stream`` capability instead of the ``streamreqs`` capability.
1252
1273
1253 This capability was introduced in Mercurial 1.7 (released November 2010).
1274 This capability was introduced in Mercurial 1.7 (released November 2010).
1254
1275
1255 stream
1276 stream
1256 ------
1277 ------
1257
1278
1258 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1279 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1259
1280
1260 If present, the server supports the ``stream_out`` command, which transmits
1281 If present, the server supports the ``stream_out`` command, which transmits
1261 raw revlogs from the repository instead of changegroups. This provides a faster
1282 raw revlogs from the repository instead of changegroups. This provides a faster
1262 cloning mechanism at the expense of more bandwidth used.
1283 cloning mechanism at the expense of more bandwidth used.
1263
1284
1264 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1285 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1265
1286
1266 When initially introduced, the value of the capability was the numeric
1287 When initially introduced, the value of the capability was the numeric
1267 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1288 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1268 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1289 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1269 ``streamreqs`` capability was invented to handle cases where the repo
1290 ``streamreqs`` capability was invented to handle cases where the repo
1270 requirements have more than just ``revlogv1``. Newer servers omit the
1291 requirements have more than just ``revlogv1``. Newer servers omit the
1271 ``=1`` since it was the only value supported and the value of ``1`` can
1292 ``=1`` since it was the only value supported and the value of ``1`` can
1272 be implied by clients.
1293 be implied by clients.
1273
1294
1274 unbundlehash
1295 unbundlehash
1275 ------------
1296 ------------
1276
1297
1277 Whether the ``unbundle`` commands supports receiving a hash of all the
1298 Whether the ``unbundle`` commands supports receiving a hash of all the
1278 heads instead of a list.
1299 heads instead of a list.
1279
1300
1280 For more, see the documentation for the ``unbundle`` command.
1301 For more, see the documentation for the ``unbundle`` command.
1281
1302
1282 This capability was introduced in Mercurial 1.9 (released July 2011).
1303 This capability was introduced in Mercurial 1.9 (released July 2011).
1283
1304
1284 unbundle
1305 unbundle
1285 --------
1306 --------
1286
1307
1287 Whether the server supports pushing via the ``unbundle`` command.
1308 Whether the server supports pushing via the ``unbundle`` command.
1288
1309
1289 This capability/command has been present since Mercurial 0.9.1 (released
1310 This capability/command has been present since Mercurial 0.9.1 (released
1290 July 2006).
1311 July 2006).
1291
1312
1292 Mercurial 0.9.2 (released December 2006) added values to the capability
1313 Mercurial 0.9.2 (released December 2006) added values to the capability
1293 indicating which bundle types the server supports receiving. This value is a
1314 indicating which bundle types the server supports receiving. This value is a
1294 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1315 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1295 reflects the priority/preference of that type, where the first value is the
1316 reflects the priority/preference of that type, where the first value is the
1296 most preferred type.
1317 most preferred type.
1297
1318
1298 Content Negotiation
1319 Content Negotiation
1299 ===================
1320 ===================
1300
1321
1301 The wire protocol has some mechanisms to help peers determine what content
1322 The wire protocol has some mechanisms to help peers determine what content
1302 types and encoding the other side will accept. Historically, these mechanisms
1323 types and encoding the other side will accept. Historically, these mechanisms
1303 have been built into commands themselves because most commands only send a
1324 have been built into commands themselves because most commands only send a
1304 well-defined response type and only certain commands needed to support
1325 well-defined response type and only certain commands needed to support
1305 functionality like compression.
1326 functionality like compression.
1306
1327
1307 Currently, only the HTTP version 1 transport supports content negotiation
1328 Currently, only the HTTP version 1 transport supports content negotiation
1308 at the protocol layer.
1329 at the protocol layer.
1309
1330
1310 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1331 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1311 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1332 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1312 value to span multiple headers. This value consists of a list of
1333 value to span multiple headers. This value consists of a list of
1313 space-delimited parameters. Each parameter denotes a feature or capability.
1334 space-delimited parameters. Each parameter denotes a feature or capability.
1314
1335
1315 The following parameters are defined:
1336 The following parameters are defined:
1316
1337
1317 0.1
1338 0.1
1318 Indicates the client supports receiving ``application/mercurial-0.1``
1339 Indicates the client supports receiving ``application/mercurial-0.1``
1319 responses.
1340 responses.
1320
1341
1321 0.2
1342 0.2
1322 Indicates the client supports receiving ``application/mercurial-0.2``
1343 Indicates the client supports receiving ``application/mercurial-0.2``
1323 responses.
1344 responses.
1324
1345
1325 cbor
1346 cbor
1326 Indicates the client supports receiving ``application/mercurial-cbor``
1347 Indicates the client supports receiving ``application/mercurial-cbor``
1327 responses.
1348 responses.
1328
1349
1329 (Only intended to be used with version 2 transports.)
1350 (Only intended to be used with version 2 transports.)
1330
1351
1331 comp
1352 comp
1332 Indicates compression formats the client can decode. Value is a list of
1353 Indicates compression formats the client can decode. Value is a list of
1333 comma delimited strings identifying compression formats ordered from
1354 comma delimited strings identifying compression formats ordered from
1334 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1355 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1335
1356
1336 This parameter does not have an effect if only the ``0.1`` parameter
1357 This parameter does not have an effect if only the ``0.1`` parameter
1337 is defined, as support for ``application/mercurial-0.2`` or greater is
1358 is defined, as support for ``application/mercurial-0.2`` or greater is
1338 required to use arbitrary compression formats.
1359 required to use arbitrary compression formats.
1339
1360
1340 If this parameter is not advertised, the server interprets this as
1361 If this parameter is not advertised, the server interprets this as
1341 equivalent to ``zlib,none``.
1362 equivalent to ``zlib,none``.
1342
1363
1343 Clients may choose to only send this header if the ``httpmediatype``
1364 Clients may choose to only send this header if the ``httpmediatype``
1344 server capability is present, as currently all server-side features
1365 server capability is present, as currently all server-side features
1345 consulting this header require the client to opt in to new protocol features
1366 consulting this header require the client to opt in to new protocol features
1346 advertised via the ``httpmediatype`` capability.
1367 advertised via the ``httpmediatype`` capability.
1347
1368
1348 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1369 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1349 value of ``0.1``. This is compatible with legacy clients.
1370 value of ``0.1``. This is compatible with legacy clients.
1350
1371
1351 A server receiving a request indicating support for multiple media type
1372 A server receiving a request indicating support for multiple media type
1352 versions may respond with any of the supported media types. Not all servers
1373 versions may respond with any of the supported media types. Not all servers
1353 may support all media types on all commands.
1374 may support all media types on all commands.
1354
1375
1355 Commands
1376 Commands
1356 ========
1377 ========
1357
1378
1358 This section contains a list of all wire protocol commands implemented by
1379 This section contains a list of all wire protocol commands implemented by
1359 the canonical Mercurial server.
1380 the canonical Mercurial server.
1360
1381
1361 batch
1382 batch
1362 -----
1383 -----
1363
1384
1364 Issue multiple commands while sending a single command request. The purpose
1385 Issue multiple commands while sending a single command request. The purpose
1365 of this command is to allow a client to issue multiple commands while avoiding
1386 of this command is to allow a client to issue multiple commands while avoiding
1366 multiple round trips to the server therefore enabling commands to complete
1387 multiple round trips to the server therefore enabling commands to complete
1367 quicker.
1388 quicker.
1368
1389
1369 The command accepts a ``cmds`` argument that contains a list of commands to
1390 The command accepts a ``cmds`` argument that contains a list of commands to
1370 execute.
1391 execute.
1371
1392
1372 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1393 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1373 form ``<command> <arguments>``. That is, the command name followed by a space
1394 form ``<command> <arguments>``. That is, the command name followed by a space
1374 followed by an argument string.
1395 followed by an argument string.
1375
1396
1376 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1397 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1377 corresponding to command arguments. Both the argument name and value are
1398 corresponding to command arguments. Both the argument name and value are
1378 escaped using a special substitution map::
1399 escaped using a special substitution map::
1379
1400
1380 : -> :c
1401 : -> :c
1381 , -> :o
1402 , -> :o
1382 ; -> :s
1403 ; -> :s
1383 = -> :e
1404 = -> :e
1384
1405
1385 The response type for this command is ``string``. The value contains a
1406 The response type for this command is ``string``. The value contains a
1386 ``;`` delimited list of responses for each requested command. Each value
1407 ``;`` delimited list of responses for each requested command. Each value
1387 in this list is escaped using the same substitution map used for arguments.
1408 in this list is escaped using the same substitution map used for arguments.
1388
1409
1389 If an error occurs, the generic error response may be sent.
1410 If an error occurs, the generic error response may be sent.
1390
1411
1391 between
1412 between
1392 -------
1413 -------
1393
1414
1394 (Legacy command used for discovery in old clients)
1415 (Legacy command used for discovery in old clients)
1395
1416
1396 Obtain nodes between pairs of nodes.
1417 Obtain nodes between pairs of nodes.
1397
1418
1398 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1419 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1399 hex node pairs. e.g.::
1420 hex node pairs. e.g.::
1400
1421
1401 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1422 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1402
1423
1403 Return type is a ``string``. Value consists of lines corresponding to each
1424 Return type is a ``string``. Value consists of lines corresponding to each
1404 requested range. Each line contains a space-delimited list of hex nodes.
1425 requested range. Each line contains a space-delimited list of hex nodes.
1405 A newline ``\n`` terminates each line, including the last one.
1426 A newline ``\n`` terminates each line, including the last one.
1406
1427
1407 branchmap
1428 branchmap
1408 ---------
1429 ---------
1409
1430
1410 Obtain heads in named branches.
1431 Obtain heads in named branches.
1411
1432
1412 Accepts no arguments. Return type is a ``string``.
1433 Accepts no arguments. Return type is a ``string``.
1413
1434
1414 Return value contains lines with URL encoded branch names followed by a space
1435 Return value contains lines with URL encoded branch names followed by a space
1415 followed by a space-delimited list of hex nodes of heads on that branch.
1436 followed by a space-delimited list of hex nodes of heads on that branch.
1416 e.g.::
1437 e.g.::
1417
1438
1418 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1439 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1419 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1440 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1420
1441
1421 There is no trailing newline.
1442 There is no trailing newline.
1422
1443
1423 branches
1444 branches
1424 --------
1445 --------
1425
1446
1426 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1447 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1427 use the ``known`` and ``heads`` commands instead.)
1448 use the ``known`` and ``heads`` commands instead.)
1428
1449
1429 Obtain ancestor changesets of specific nodes back to a branch point.
1450 Obtain ancestor changesets of specific nodes back to a branch point.
1430
1451
1431 Despite the name, this command has nothing to do with Mercurial named branches.
1452 Despite the name, this command has nothing to do with Mercurial named branches.
1432 Instead, it is related to DAG branches.
1453 Instead, it is related to DAG branches.
1433
1454
1434 The command accepts a ``nodes`` argument, which is a string of space-delimited
1455 The command accepts a ``nodes`` argument, which is a string of space-delimited
1435 hex nodes.
1456 hex nodes.
1436
1457
1437 For each node requested, the server will find the first ancestor node that is
1458 For each node requested, the server will find the first ancestor node that is
1438 a DAG root or is a merge.
1459 a DAG root or is a merge.
1439
1460
1440 Return type is a ``string``. Return value contains lines with result data for
1461 Return type is a ``string``. Return value contains lines with result data for
1441 each requested node. Each line contains space-delimited nodes followed by a
1462 each requested node. Each line contains space-delimited nodes followed by a
1442 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1463 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1443 node, the ancestor node found, and its 2 parent nodes (which may be the null
1464 node, the ancestor node found, and its 2 parent nodes (which may be the null
1444 node).
1465 node).
1445
1466
1446 capabilities
1467 capabilities
1447 ------------
1468 ------------
1448
1469
1449 Obtain the capabilities string for the repo.
1470 Obtain the capabilities string for the repo.
1450
1471
1451 Unlike the ``hello`` command, the capabilities string is not prefixed.
1472 Unlike the ``hello`` command, the capabilities string is not prefixed.
1452 There is no trailing newline.
1473 There is no trailing newline.
1453
1474
1454 This command does not accept any arguments. Return type is a ``string``.
1475 This command does not accept any arguments. Return type is a ``string``.
1455
1476
1456 This command was introduced in Mercurial 0.9.1 (released July 2006).
1477 This command was introduced in Mercurial 0.9.1 (released July 2006).
1457
1478
1458 changegroup
1479 changegroup
1459 -----------
1480 -----------
1460
1481
1461 (Legacy command: use ``getbundle`` instead)
1482 (Legacy command: use ``getbundle`` instead)
1462
1483
1463 Obtain a changegroup version 1 with data for changesets that are
1484 Obtain a changegroup version 1 with data for changesets that are
1464 descendants of client-specified changesets.
1485 descendants of client-specified changesets.
1465
1486
1466 The ``roots`` arguments contains a list of space-delimited hex nodes.
1487 The ``roots`` arguments contains a list of space-delimited hex nodes.
1467
1488
1468 The server responds with a changegroup version 1 containing all
1489 The server responds with a changegroup version 1 containing all
1469 changesets between the requested root/base nodes and the repo's head nodes
1490 changesets between the requested root/base nodes and the repo's head nodes
1470 at the time of the request.
1491 at the time of the request.
1471
1492
1472 The return type is a ``stream``.
1493 The return type is a ``stream``.
1473
1494
1474 changegroupsubset
1495 changegroupsubset
1475 -----------------
1496 -----------------
1476
1497
1477 (Legacy command: use ``getbundle`` instead)
1498 (Legacy command: use ``getbundle`` instead)
1478
1499
1479 Obtain a changegroup version 1 with data for changesetsets between
1500 Obtain a changegroup version 1 with data for changesetsets between
1480 client specified base and head nodes.
1501 client specified base and head nodes.
1481
1502
1482 The ``bases`` argument contains a list of space-delimited hex nodes.
1503 The ``bases`` argument contains a list of space-delimited hex nodes.
1483 The ``heads`` argument contains a list of space-delimited hex nodes.
1504 The ``heads`` argument contains a list of space-delimited hex nodes.
1484
1505
1485 The server responds with a changegroup version 1 containing all
1506 The server responds with a changegroup version 1 containing all
1486 changesets between the requested base and head nodes at the time of the
1507 changesets between the requested base and head nodes at the time of the
1487 request.
1508 request.
1488
1509
1489 The return type is a ``stream``.
1510 The return type is a ``stream``.
1490
1511
1491 clonebundles
1512 clonebundles
1492 ------------
1513 ------------
1493
1514
1494 Obtains a manifest of bundle URLs available to seed clones.
1515 Obtains a manifest of bundle URLs available to seed clones.
1495
1516
1496 Each returned line contains a URL followed by metadata. See the
1517 Each returned line contains a URL followed by metadata. See the
1497 documentation in the ``clonebundles`` extension for more.
1518 documentation in the ``clonebundles`` extension for more.
1498
1519
1499 The return type is a ``string``.
1520 The return type is a ``string``.
1500
1521
1501 getbundle
1522 getbundle
1502 ---------
1523 ---------
1503
1524
1504 Obtain a bundle containing repository data.
1525 Obtain a bundle containing repository data.
1505
1526
1506 This command accepts the following arguments:
1527 This command accepts the following arguments:
1507
1528
1508 heads
1529 heads
1509 List of space-delimited hex nodes of heads to retrieve.
1530 List of space-delimited hex nodes of heads to retrieve.
1510 common
1531 common
1511 List of space-delimited hex nodes that the client has in common with the
1532 List of space-delimited hex nodes that the client has in common with the
1512 server.
1533 server.
1513 obsmarkers
1534 obsmarkers
1514 Boolean indicating whether to include obsolescence markers as part
1535 Boolean indicating whether to include obsolescence markers as part
1515 of the response. Only works with bundle2.
1536 of the response. Only works with bundle2.
1516 bundlecaps
1537 bundlecaps
1517 Comma-delimited set of strings defining client bundle capabilities.
1538 Comma-delimited set of strings defining client bundle capabilities.
1518 listkeys
1539 listkeys
1519 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1540 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1520 namespace listed, a bundle2 part will be included with the content of
1541 namespace listed, a bundle2 part will be included with the content of
1521 that namespace.
1542 that namespace.
1522 cg
1543 cg
1523 Boolean indicating whether changegroup data is requested.
1544 Boolean indicating whether changegroup data is requested.
1524 cbattempted
1545 cbattempted
1525 Boolean indicating whether the client attempted to use the *clone bundles*
1546 Boolean indicating whether the client attempted to use the *clone bundles*
1526 feature before performing this request.
1547 feature before performing this request.
1527 bookmarks
1548 bookmarks
1528 Boolean indicating whether bookmark data is requested.
1549 Boolean indicating whether bookmark data is requested.
1529 phases
1550 phases
1530 Boolean indicating whether phases data is requested.
1551 Boolean indicating whether phases data is requested.
1531
1552
1532 The return type on success is a ``stream`` where the value is bundle.
1553 The return type on success is a ``stream`` where the value is bundle.
1533 On the HTTP version 1 transport, the response is zlib compressed.
1554 On the HTTP version 1 transport, the response is zlib compressed.
1534
1555
1535 If an error occurs, a generic error response can be sent.
1556 If an error occurs, a generic error response can be sent.
1536
1557
1537 Unless the client sends a false value for the ``cg`` argument, the returned
1558 Unless the client sends a false value for the ``cg`` argument, the returned
1538 bundle contains a changegroup with the nodes between the specified ``common``
1559 bundle contains a changegroup with the nodes between the specified ``common``
1539 and ``heads`` nodes. Depending on the command arguments, the type and content
1560 and ``heads`` nodes. Depending on the command arguments, the type and content
1540 of the returned bundle can vary significantly.
1561 of the returned bundle can vary significantly.
1541
1562
1542 The default behavior is for the server to send a raw changegroup version
1563 The default behavior is for the server to send a raw changegroup version
1543 ``01`` response.
1564 ``01`` response.
1544
1565
1545 If the ``bundlecaps`` provided by the client contain a value beginning
1566 If the ``bundlecaps`` provided by the client contain a value beginning
1546 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1567 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1547 additional repository data, such as ``pushkey`` namespace values.
1568 additional repository data, such as ``pushkey`` namespace values.
1548
1569
1549 heads
1570 heads
1550 -----
1571 -----
1551
1572
1552 Returns a list of space-delimited hex nodes of repository heads followed
1573 Returns a list of space-delimited hex nodes of repository heads followed
1553 by a newline. e.g.
1574 by a newline. e.g.
1554 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1575 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1555
1576
1556 This command does not accept any arguments. The return type is a ``string``.
1577 This command does not accept any arguments. The return type is a ``string``.
1557
1578
1558 hello
1579 hello
1559 -----
1580 -----
1560
1581
1561 Returns lines describing interesting things about the server in an RFC-822
1582 Returns lines describing interesting things about the server in an RFC-822
1562 like format.
1583 like format.
1563
1584
1564 Currently, the only line defines the server capabilities. It has the form::
1585 Currently, the only line defines the server capabilities. It has the form::
1565
1586
1566 capabilities: <value>
1587 capabilities: <value>
1567
1588
1568 See above for more about the capabilities string.
1589 See above for more about the capabilities string.
1569
1590
1570 SSH clients typically issue this command as soon as a connection is
1591 SSH clients typically issue this command as soon as a connection is
1571 established.
1592 established.
1572
1593
1573 This command does not accept any arguments. The return type is a ``string``.
1594 This command does not accept any arguments. The return type is a ``string``.
1574
1595
1575 This command was introduced in Mercurial 0.9.1 (released July 2006).
1596 This command was introduced in Mercurial 0.9.1 (released July 2006).
1576
1597
1577 listkeys
1598 listkeys
1578 --------
1599 --------
1579
1600
1580 List values in a specified ``pushkey`` namespace.
1601 List values in a specified ``pushkey`` namespace.
1581
1602
1582 The ``namespace`` argument defines the pushkey namespace to operate on.
1603 The ``namespace`` argument defines the pushkey namespace to operate on.
1583
1604
1584 The return type is a ``string``. The value is an encoded dictionary of keys.
1605 The return type is a ``string``. The value is an encoded dictionary of keys.
1585
1606
1586 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1607 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1587 values are separated by a tab (``\t``). Keys and values are both strings.
1608 values are separated by a tab (``\t``). Keys and values are both strings.
1588
1609
1589 lookup
1610 lookup
1590 ------
1611 ------
1591
1612
1592 Try to resolve a value to a known repository revision.
1613 Try to resolve a value to a known repository revision.
1593
1614
1594 The ``key`` argument is converted from bytes to an
1615 The ``key`` argument is converted from bytes to an
1595 ``encoding.localstr`` instance then passed into
1616 ``encoding.localstr`` instance then passed into
1596 ``localrepository.__getitem__`` in an attempt to resolve it.
1617 ``localrepository.__getitem__`` in an attempt to resolve it.
1597
1618
1598 The return type is a ``string``.
1619 The return type is a ``string``.
1599
1620
1600 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1621 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1601 returns ``0 <error string>\n``. e.g.::
1622 returns ``0 <error string>\n``. e.g.::
1602
1623
1603 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1624 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1604
1625
1605 0 unknown revision 'foo'\n
1626 0 unknown revision 'foo'\n
1606
1627
1607 known
1628 known
1608 -----
1629 -----
1609
1630
1610 Determine whether multiple nodes are known.
1631 Determine whether multiple nodes are known.
1611
1632
1612 The ``nodes`` argument is a list of space-delimited hex nodes to check
1633 The ``nodes`` argument is a list of space-delimited hex nodes to check
1613 for existence.
1634 for existence.
1614
1635
1615 The return type is ``string``.
1636 The return type is ``string``.
1616
1637
1617 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1638 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1618 are known. If the Nth node specified in the ``nodes`` argument is known,
1639 are known. If the Nth node specified in the ``nodes`` argument is known,
1619 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1640 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1620 will be present at byte offset N.
1641 will be present at byte offset N.
1621
1642
1622 There is no trailing newline.
1643 There is no trailing newline.
1623
1644
1624 protocaps
1645 protocaps
1625 ---------
1646 ---------
1626
1647
1627 Notify the server about the client capabilities in the SSH V1 transport
1648 Notify the server about the client capabilities in the SSH V1 transport
1628 protocol.
1649 protocol.
1629
1650
1630 The ``caps`` argument is a space-delimited list of capabilities.
1651 The ``caps`` argument is a space-delimited list of capabilities.
1631
1652
1632 The server will reply with the string ``OK``.
1653 The server will reply with the string ``OK``.
1633
1654
1634 pushkey
1655 pushkey
1635 -------
1656 -------
1636
1657
1637 Set a value using the ``pushkey`` protocol.
1658 Set a value using the ``pushkey`` protocol.
1638
1659
1639 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1660 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1640 correspond to the pushkey namespace to operate on, the key within that
1661 correspond to the pushkey namespace to operate on, the key within that
1641 namespace to change, the old value (which may be empty), and the new value.
1662 namespace to change, the old value (which may be empty), and the new value.
1642 All arguments are string types.
1663 All arguments are string types.
1643
1664
1644 The return type is a ``string``. The value depends on the transport protocol.
1665 The return type is a ``string``. The value depends on the transport protocol.
1645
1666
1646 The SSH version 1 transport sends a string encoded integer followed by a
1667 The SSH version 1 transport sends a string encoded integer followed by a
1647 newline (``\n``) which indicates operation result. The server may send
1668 newline (``\n``) which indicates operation result. The server may send
1648 additional output on the ``stderr`` stream that should be displayed to the
1669 additional output on the ``stderr`` stream that should be displayed to the
1649 user.
1670 user.
1650
1671
1651 The HTTP version 1 transport sends a string encoded integer followed by a
1672 The HTTP version 1 transport sends a string encoded integer followed by a
1652 newline followed by additional server output that should be displayed to
1673 newline followed by additional server output that should be displayed to
1653 the user. This may include output from hooks, etc.
1674 the user. This may include output from hooks, etc.
1654
1675
1655 The integer result varies by namespace. ``0`` means an error has occurred
1676 The integer result varies by namespace. ``0`` means an error has occurred
1656 and there should be additional output to display to the user.
1677 and there should be additional output to display to the user.
1657
1678
1658 stream_out
1679 stream_out
1659 ----------
1680 ----------
1660
1681
1661 Obtain *streaming clone* data.
1682 Obtain *streaming clone* data.
1662
1683
1663 The return type is either a ``string`` or a ``stream``, depending on
1684 The return type is either a ``string`` or a ``stream``, depending on
1664 whether the request was fulfilled properly.
1685 whether the request was fulfilled properly.
1665
1686
1666 A return value of ``1\n`` indicates the server is not configured to serve
1687 A return value of ``1\n`` indicates the server is not configured to serve
1667 this data. If this is seen by the client, they may not have verified the
1688 this data. If this is seen by the client, they may not have verified the
1668 ``stream`` capability is set before making the request.
1689 ``stream`` capability is set before making the request.
1669
1690
1670 A return value of ``2\n`` indicates the server was unable to lock the
1691 A return value of ``2\n`` indicates the server was unable to lock the
1671 repository to generate data.
1692 repository to generate data.
1672
1693
1673 All other responses are a ``stream`` of bytes. The first line of this data
1694 All other responses are a ``stream`` of bytes. The first line of this data
1674 contains 2 space-delimited integers corresponding to the path count and
1695 contains 2 space-delimited integers corresponding to the path count and
1675 payload size, respectively::
1696 payload size, respectively::
1676
1697
1677 <path count> <payload size>\n
1698 <path count> <payload size>\n
1678
1699
1679 The ``<payload size>`` is the total size of path data: it does not include
1700 The ``<payload size>`` is the total size of path data: it does not include
1680 the size of the per-path header lines.
1701 the size of the per-path header lines.
1681
1702
1682 Following that header are ``<path count>`` entries. Each entry consists of a
1703 Following that header are ``<path count>`` entries. Each entry consists of a
1683 line with metadata followed by raw revlog data. The line consists of::
1704 line with metadata followed by raw revlog data. The line consists of::
1684
1705
1685 <store path>\0<size>\n
1706 <store path>\0<size>\n
1686
1707
1687 The ``<store path>`` is the encoded store path of the data that follows.
1708 The ``<store path>`` is the encoded store path of the data that follows.
1688 ``<size>`` is the amount of data for this store path/revlog that follows the
1709 ``<size>`` is the amount of data for this store path/revlog that follows the
1689 newline.
1710 newline.
1690
1711
1691 There is no trailer to indicate end of data. Instead, the client should stop
1712 There is no trailer to indicate end of data. Instead, the client should stop
1692 reading after ``<path count>`` entries are consumed.
1713 reading after ``<path count>`` entries are consumed.
1693
1714
1694 unbundle
1715 unbundle
1695 --------
1716 --------
1696
1717
1697 Send a bundle containing data (usually changegroup data) to the server.
1718 Send a bundle containing data (usually changegroup data) to the server.
1698
1719
1699 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1720 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1700 corresponding to server repository heads observed by the client. This is used
1721 corresponding to server repository heads observed by the client. This is used
1701 to detect race conditions and abort push operations before a server performs
1722 to detect race conditions and abort push operations before a server performs
1702 too much work or a client transfers too much data.
1723 too much work or a client transfers too much data.
1703
1724
1704 The request payload consists of a bundle to be applied to the repository,
1725 The request payload consists of a bundle to be applied to the repository,
1705 similarly to as if :hg:`unbundle` were called.
1726 similarly to as if :hg:`unbundle` were called.
1706
1727
1707 In most scenarios, a special ``push response`` type is returned. This type
1728 In most scenarios, a special ``push response`` type is returned. This type
1708 contains an integer describing the change in heads as a result of the
1729 contains an integer describing the change in heads as a result of the
1709 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1730 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1710 of heads remained the same. Values ``2`` and larger indicate the number of
1731 of heads remained the same. Values ``2`` and larger indicate the number of
1711 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1732 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1712 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1733 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1713 is 1 fewer head.
1734 is 1 fewer head.
1714
1735
1715 The encoding of the ``push response`` type varies by transport.
1736 The encoding of the ``push response`` type varies by transport.
1716
1737
1717 For the SSH version 1 transport, this type is composed of 2 ``string``
1738 For the SSH version 1 transport, this type is composed of 2 ``string``
1718 responses: an empty response (``0\n``) followed by the integer result value.
1739 responses: an empty response (``0\n``) followed by the integer result value.
1719 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1740 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1720
1741
1721 For the HTTP version 1 transport, the response is a ``string`` type composed
1742 For the HTTP version 1 transport, the response is a ``string`` type composed
1722 of an integer result value followed by a newline (``\n``) followed by string
1743 of an integer result value followed by a newline (``\n``) followed by string
1723 content holding server output that should be displayed on the client (output
1744 content holding server output that should be displayed on the client (output
1724 hooks, etc).
1745 hooks, etc).
1725
1746
1726 In some cases, the server may respond with a ``bundle2`` bundle. In this
1747 In some cases, the server may respond with a ``bundle2`` bundle. In this
1727 case, the response type is ``stream``. For the HTTP version 1 transport, the
1748 case, the response type is ``stream``. For the HTTP version 1 transport, the
1728 response is zlib compressed.
1749 response is zlib compressed.
1729
1750
1730 The server may also respond with a generic error type, which contains a string
1751 The server may also respond with a generic error type, which contains a string
1731 indicating the failure.
1752 indicating the failure.
1732
1753
1733 Frame-Based Protocol Commands
1754 Frame-Based Protocol Commands
1734 =============================
1755 =============================
1735
1756
1736 **Experimental and under active development**
1757 **Experimental and under active development**
1737
1758
1738 This section documents the wire protocol commands exposed to transports
1759 This section documents the wire protocol commands exposed to transports
1739 using the frame-based protocol. The set of commands exposed through
1760 using the frame-based protocol. The set of commands exposed through
1740 these transports is distinct from the set of commands exposed to legacy
1761 these transports is distinct from the set of commands exposed to legacy
1741 transports.
1762 transports.
1742
1763
1743 The frame-based protocol uses CBOR to encode command execution requests.
1764 The frame-based protocol uses CBOR to encode command execution requests.
1744 All command arguments must be mapped to a specific or set of CBOR data
1765 All command arguments must be mapped to a specific or set of CBOR data
1745 types.
1766 types.
1746
1767
1747 The response to many commands is also CBOR. There is no common response
1768 The response to many commands is also CBOR. There is no common response
1748 format: each command defines its own response format.
1769 format: each command defines its own response format.
1749
1770
1750 TODO require node type be specified, as N bytes of binary node value
1771 TODO require node type be specified, as N bytes of binary node value
1751 could be ambiguous once SHA-1 is replaced.
1772 could be ambiguous once SHA-1 is replaced.
1752
1773
1753 branchmap
1774 branchmap
1754 ---------
1775 ---------
1755
1776
1756 Obtain heads in named branches.
1777 Obtain heads in named branches.
1757
1778
1758 Receives no arguments.
1779 Receives no arguments.
1759
1780
1760 The response is a map with bytestring keys defining the branch name.
1781 The response is a map with bytestring keys defining the branch name.
1761 Values are arrays of bytestring defining raw changeset nodes.
1782 Values are arrays of bytestring defining raw changeset nodes.
1762
1783
1763 capabilities
1784 capabilities
1764 ------------
1785 ------------
1765
1786
1766 Obtain the server's capabilities.
1787 Obtain the server's capabilities.
1767
1788
1768 Receives no arguments.
1789 Receives no arguments.
1769
1790
1770 This command is typically called only as part of the handshake during
1791 This command is typically called only as part of the handshake during
1771 initial connection establishment.
1792 initial connection establishment.
1772
1793
1773 The response is a map with bytestring keys defining server information.
1794 The response is a map with bytestring keys defining server information.
1774
1795
1775 The defined keys are:
1796 The defined keys are:
1776
1797
1777 commands
1798 commands
1778 A map defining available wire protocol commands on this server.
1799 A map defining available wire protocol commands on this server.
1779
1800
1780 Keys in the map are the names of commands that can be invoked. Values
1801 Keys in the map are the names of commands that can be invoked. Values
1781 are maps defining information about that command. The bytestring keys
1802 are maps defining information about that command. The bytestring keys
1782 are:
1803 are:
1783
1804
1784 args
1805 args
1785 A map of argument names and their expected types.
1806 A map of argument names and their expected types.
1786
1807
1787 Types are defined as a representative value for the expected type.
1808 Types are defined as a representative value for the expected type.
1788 e.g. an argument expecting a boolean type will have its value
1809 e.g. an argument expecting a boolean type will have its value
1789 set to true. An integer type will have its value set to 42. The
1810 set to true. An integer type will have its value set to 42. The
1790 actual values are arbitrary and may not have meaning.
1811 actual values are arbitrary and may not have meaning.
1791 permissions
1812 permissions
1792 An array of permissions required to execute this command.
1813 An array of permissions required to execute this command.
1793
1814
1794 compression
1815 compression
1795 An array of maps defining available compression format support.
1816 An array of maps defining available compression format support.
1796
1817
1797 The array is sorted from most preferred to least preferred.
1818 The array is sorted from most preferred to least preferred.
1798
1819
1799 Each entry has the following bytestring keys:
1820 Each entry has the following bytestring keys:
1800
1821
1801 name
1822 name
1802 Name of the compression engine. e.g. ``zstd`` or ``zlib``.
1823 Name of the compression engine. e.g. ``zstd`` or ``zlib``.
1803
1824
1804 framingmediatypes
1825 framingmediatypes
1805 An array of bytestrings defining the supported framing protocol
1826 An array of bytestrings defining the supported framing protocol
1806 media types. Servers will not accept media types not in this list.
1827 media types. Servers will not accept media types not in this list.
1807
1828
1808 rawrepoformats
1829 rawrepoformats
1809 An array of storage formats the repository is using. This set of
1830 An array of storage formats the repository is using. This set of
1810 requirements can be used to determine whether a client can read a
1831 requirements can be used to determine whether a client can read a
1811 *raw* copy of file data available.
1832 *raw* copy of file data available.
1812
1833
1813 heads
1834 heads
1814 -----
1835 -----
1815
1836
1816 Obtain DAG heads in the repository.
1837 Obtain DAG heads in the repository.
1817
1838
1818 The command accepts the following arguments:
1839 The command accepts the following arguments:
1819
1840
1820 publiconly (optional)
1841 publiconly (optional)
1821 (boolean) If set, operate on the DAG for public phase changesets only.
1842 (boolean) If set, operate on the DAG for public phase changesets only.
1822 Non-public (i.e. draft) phase DAG heads will not be returned.
1843 Non-public (i.e. draft) phase DAG heads will not be returned.
1823
1844
1824 The response is a CBOR array of bytestrings defining changeset nodes
1845 The response is a CBOR array of bytestrings defining changeset nodes
1825 of DAG heads. The array can be empty if the repository is empty or no
1846 of DAG heads. The array can be empty if the repository is empty or no
1826 changesets satisfied the request.
1847 changesets satisfied the request.
1827
1848
1828 TODO consider exposing phase of heads in response
1849 TODO consider exposing phase of heads in response
1829
1850
1830 known
1851 known
1831 -----
1852 -----
1832
1853
1833 Determine whether a series of changeset nodes is known to the server.
1854 Determine whether a series of changeset nodes is known to the server.
1834
1855
1835 The command accepts the following arguments:
1856 The command accepts the following arguments:
1836
1857
1837 nodes
1858 nodes
1838 (array of bytestrings) List of changeset nodes whose presence to
1859 (array of bytestrings) List of changeset nodes whose presence to
1839 query.
1860 query.
1840
1861
1841 The response is a bytestring where each byte contains a 0 or 1 for the
1862 The response is a bytestring where each byte contains a 0 or 1 for the
1842 corresponding requested node at the same index.
1863 corresponding requested node at the same index.
1843
1864
1844 TODO use a bit array for even more compact response
1865 TODO use a bit array for even more compact response
1845
1866
1846 listkeys
1867 listkeys
1847 --------
1868 --------
1848
1869
1849 List values in a specified ``pushkey`` namespace.
1870 List values in a specified ``pushkey`` namespace.
1850
1871
1851 The command receives the following arguments:
1872 The command receives the following arguments:
1852
1873
1853 namespace
1874 namespace
1854 (bytestring) Pushkey namespace to query.
1875 (bytestring) Pushkey namespace to query.
1855
1876
1856 The response is a map with bytestring keys and values.
1877 The response is a map with bytestring keys and values.
1857
1878
1858 TODO consider using binary to represent nodes in certain pushkey namespaces.
1879 TODO consider using binary to represent nodes in certain pushkey namespaces.
1859
1880
1860 lookup
1881 lookup
1861 ------
1882 ------
1862
1883
1863 Try to resolve a value to a changeset revision.
1884 Try to resolve a value to a changeset revision.
1864
1885
1865 Unlike ``known`` which operates on changeset nodes, lookup operates on
1886 Unlike ``known`` which operates on changeset nodes, lookup operates on
1866 node fragments and other names that a user may use.
1887 node fragments and other names that a user may use.
1867
1888
1868 The command receives the following arguments:
1889 The command receives the following arguments:
1869
1890
1870 key
1891 key
1871 (bytestring) Value to try to resolve.
1892 (bytestring) Value to try to resolve.
1872
1893
1873 On success, returns a bytestring containing the resolved node.
1894 On success, returns a bytestring containing the resolved node.
1874
1895
1875 pushkey
1896 pushkey
1876 -------
1897 -------
1877
1898
1878 Set a value using the ``pushkey`` protocol.
1899 Set a value using the ``pushkey`` protocol.
1879
1900
1880 The command receives the following arguments:
1901 The command receives the following arguments:
1881
1902
1882 namespace
1903 namespace
1883 (bytestring) Pushkey namespace to operate on.
1904 (bytestring) Pushkey namespace to operate on.
1884 key
1905 key
1885 (bytestring) The pushkey key to set.
1906 (bytestring) The pushkey key to set.
1886 old
1907 old
1887 (bytestring) Old value for this key.
1908 (bytestring) Old value for this key.
1888 new
1909 new
1889 (bytestring) New value for this key.
1910 (bytestring) New value for this key.
1890
1911
1891 TODO consider using binary to represent nodes is certain pushkey namespaces.
1912 TODO consider using binary to represent nodes is certain pushkey namespaces.
1892 TODO better define response type and meaning.
1913 TODO better define response type and meaning.
@@ -1,1073 +1,1078 b''
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import collections
14 import collections
15 import struct
15 import struct
16
16
17 from .i18n import _
17 from .i18n import _
18 from .thirdparty import (
18 from .thirdparty import (
19 attr,
19 attr,
20 cbor,
20 cbor,
21 )
21 )
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 util,
25 util,
26 )
26 )
27 from .utils import (
27 from .utils import (
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 FRAME_HEADER_SIZE = 8
31 FRAME_HEADER_SIZE = 8
32 DEFAULT_MAX_FRAME_SIZE = 32768
32 DEFAULT_MAX_FRAME_SIZE = 32768
33
33
34 STREAM_FLAG_BEGIN_STREAM = 0x01
34 STREAM_FLAG_BEGIN_STREAM = 0x01
35 STREAM_FLAG_END_STREAM = 0x02
35 STREAM_FLAG_END_STREAM = 0x02
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
37
37
38 STREAM_FLAGS = {
38 STREAM_FLAGS = {
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
42 }
42 }
43
43
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
45 FRAME_TYPE_COMMAND_DATA = 0x02
45 FRAME_TYPE_COMMAND_DATA = 0x02
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
49 FRAME_TYPE_PROGRESS = 0x07
49 FRAME_TYPE_PROGRESS = 0x07
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
51
51
52 FRAME_TYPES = {
52 FRAME_TYPES = {
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
58 b'progress': FRAME_TYPE_PROGRESS,
58 b'progress': FRAME_TYPE_PROGRESS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
60 }
60 }
61
61
62 FLAG_COMMAND_REQUEST_NEW = 0x01
62 FLAG_COMMAND_REQUEST_NEW = 0x01
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
66
66
67 FLAGS_COMMAND_REQUEST = {
67 FLAGS_COMMAND_REQUEST = {
68 b'new': FLAG_COMMAND_REQUEST_NEW,
68 b'new': FLAG_COMMAND_REQUEST_NEW,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
72 }
72 }
73
73
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
75 FLAG_COMMAND_DATA_EOS = 0x02
75 FLAG_COMMAND_DATA_EOS = 0x02
76
76
77 FLAGS_COMMAND_DATA = {
77 FLAGS_COMMAND_DATA = {
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
79 b'eos': FLAG_COMMAND_DATA_EOS,
79 b'eos': FLAG_COMMAND_DATA_EOS,
80 }
80 }
81
81
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
84
84
85 FLAGS_COMMAND_RESPONSE = {
85 FLAGS_COMMAND_RESPONSE = {
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
88 }
88 }
89
89
90 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
91 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
92
93 FLAGS_ERROR_RESPONSE = {
94 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
95 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
96 }
97
98 # Maps frame types to their available flags.
90 # Maps frame types to their available flags.
99 FRAME_TYPE_FLAGS = {
91 FRAME_TYPE_FLAGS = {
100 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
92 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
101 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
93 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
102 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
94 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
103 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
95 FRAME_TYPE_ERROR_RESPONSE: {},
104 FRAME_TYPE_TEXT_OUTPUT: {},
96 FRAME_TYPE_TEXT_OUTPUT: {},
105 FRAME_TYPE_PROGRESS: {},
97 FRAME_TYPE_PROGRESS: {},
106 FRAME_TYPE_STREAM_SETTINGS: {},
98 FRAME_TYPE_STREAM_SETTINGS: {},
107 }
99 }
108
100
109 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
101 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
110
102
111 def humanflags(mapping, value):
103 def humanflags(mapping, value):
112 """Convert a numeric flags value to a human value, using a mapping table."""
104 """Convert a numeric flags value to a human value, using a mapping table."""
113 namemap = {v: k for k, v in mapping.iteritems()}
105 namemap = {v: k for k, v in mapping.iteritems()}
114 flags = []
106 flags = []
115 val = 1
107 val = 1
116 while value >= val:
108 while value >= val:
117 if value & val:
109 if value & val:
118 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
110 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
119 val <<= 1
111 val <<= 1
120
112
121 return b'|'.join(flags)
113 return b'|'.join(flags)
122
114
123 @attr.s(slots=True)
115 @attr.s(slots=True)
124 class frameheader(object):
116 class frameheader(object):
125 """Represents the data in a frame header."""
117 """Represents the data in a frame header."""
126
118
127 length = attr.ib()
119 length = attr.ib()
128 requestid = attr.ib()
120 requestid = attr.ib()
129 streamid = attr.ib()
121 streamid = attr.ib()
130 streamflags = attr.ib()
122 streamflags = attr.ib()
131 typeid = attr.ib()
123 typeid = attr.ib()
132 flags = attr.ib()
124 flags = attr.ib()
133
125
134 @attr.s(slots=True, repr=False)
126 @attr.s(slots=True, repr=False)
135 class frame(object):
127 class frame(object):
136 """Represents a parsed frame."""
128 """Represents a parsed frame."""
137
129
138 requestid = attr.ib()
130 requestid = attr.ib()
139 streamid = attr.ib()
131 streamid = attr.ib()
140 streamflags = attr.ib()
132 streamflags = attr.ib()
141 typeid = attr.ib()
133 typeid = attr.ib()
142 flags = attr.ib()
134 flags = attr.ib()
143 payload = attr.ib()
135 payload = attr.ib()
144
136
145 @encoding.strmethod
137 @encoding.strmethod
146 def __repr__(self):
138 def __repr__(self):
147 typename = '<unknown 0x%02x>' % self.typeid
139 typename = '<unknown 0x%02x>' % self.typeid
148 for name, value in FRAME_TYPES.iteritems():
140 for name, value in FRAME_TYPES.iteritems():
149 if value == self.typeid:
141 if value == self.typeid:
150 typename = name
142 typename = name
151 break
143 break
152
144
153 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
145 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
154 'type=%s; flags=%s)' % (
146 'type=%s; flags=%s)' % (
155 len(self.payload), self.requestid, self.streamid,
147 len(self.payload), self.requestid, self.streamid,
156 humanflags(STREAM_FLAGS, self.streamflags), typename,
148 humanflags(STREAM_FLAGS, self.streamflags), typename,
157 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
149 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
158
150
159 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
151 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
160 """Assemble a frame into a byte array."""
152 """Assemble a frame into a byte array."""
161 # TODO assert size of payload.
153 # TODO assert size of payload.
162 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
154 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
163
155
164 # 24 bits length
156 # 24 bits length
165 # 16 bits request id
157 # 16 bits request id
166 # 8 bits stream id
158 # 8 bits stream id
167 # 8 bits stream flags
159 # 8 bits stream flags
168 # 4 bits type
160 # 4 bits type
169 # 4 bits flags
161 # 4 bits flags
170
162
171 l = struct.pack(r'<I', len(payload))
163 l = struct.pack(r'<I', len(payload))
172 frame[0:3] = l[0:3]
164 frame[0:3] = l[0:3]
173 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
165 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
174 frame[7] = (typeid << 4) | flags
166 frame[7] = (typeid << 4) | flags
175 frame[8:] = payload
167 frame[8:] = payload
176
168
177 return frame
169 return frame
178
170
179 def makeframefromhumanstring(s):
171 def makeframefromhumanstring(s):
180 """Create a frame from a human readable string
172 """Create a frame from a human readable string
181
173
182 Strings have the form:
174 Strings have the form:
183
175
184 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
176 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
185
177
186 This can be used by user-facing applications and tests for creating
178 This can be used by user-facing applications and tests for creating
187 frames easily without having to type out a bunch of constants.
179 frames easily without having to type out a bunch of constants.
188
180
189 Request ID and stream IDs are integers.
181 Request ID and stream IDs are integers.
190
182
191 Stream flags, frame type, and flags can be specified by integer or
183 Stream flags, frame type, and flags can be specified by integer or
192 named constant.
184 named constant.
193
185
194 Flags can be delimited by `|` to bitwise OR them together.
186 Flags can be delimited by `|` to bitwise OR them together.
195
187
196 If the payload begins with ``cbor:``, the following string will be
188 If the payload begins with ``cbor:``, the following string will be
197 evaluated as Python literal and the resulting object will be fed into
189 evaluated as Python literal and the resulting object will be fed into
198 a CBOR encoder. Otherwise, the payload is interpreted as a Python
190 a CBOR encoder. Otherwise, the payload is interpreted as a Python
199 byte string literal.
191 byte string literal.
200 """
192 """
201 fields = s.split(b' ', 5)
193 fields = s.split(b' ', 5)
202 requestid, streamid, streamflags, frametype, frameflags, payload = fields
194 requestid, streamid, streamflags, frametype, frameflags, payload = fields
203
195
204 requestid = int(requestid)
196 requestid = int(requestid)
205 streamid = int(streamid)
197 streamid = int(streamid)
206
198
207 finalstreamflags = 0
199 finalstreamflags = 0
208 for flag in streamflags.split(b'|'):
200 for flag in streamflags.split(b'|'):
209 if flag in STREAM_FLAGS:
201 if flag in STREAM_FLAGS:
210 finalstreamflags |= STREAM_FLAGS[flag]
202 finalstreamflags |= STREAM_FLAGS[flag]
211 else:
203 else:
212 finalstreamflags |= int(flag)
204 finalstreamflags |= int(flag)
213
205
214 if frametype in FRAME_TYPES:
206 if frametype in FRAME_TYPES:
215 frametype = FRAME_TYPES[frametype]
207 frametype = FRAME_TYPES[frametype]
216 else:
208 else:
217 frametype = int(frametype)
209 frametype = int(frametype)
218
210
219 finalflags = 0
211 finalflags = 0
220 validflags = FRAME_TYPE_FLAGS[frametype]
212 validflags = FRAME_TYPE_FLAGS[frametype]
221 for flag in frameflags.split(b'|'):
213 for flag in frameflags.split(b'|'):
222 if flag in validflags:
214 if flag in validflags:
223 finalflags |= validflags[flag]
215 finalflags |= validflags[flag]
224 else:
216 else:
225 finalflags |= int(flag)
217 finalflags |= int(flag)
226
218
227 if payload.startswith(b'cbor:'):
219 if payload.startswith(b'cbor:'):
228 payload = cbor.dumps(stringutil.evalpythonliteral(payload[5:]),
220 payload = cbor.dumps(stringutil.evalpythonliteral(payload[5:]),
229 canonical=True)
221 canonical=True)
230
222
231 else:
223 else:
232 payload = stringutil.unescapestr(payload)
224 payload = stringutil.unescapestr(payload)
233
225
234 return makeframe(requestid=requestid, streamid=streamid,
226 return makeframe(requestid=requestid, streamid=streamid,
235 streamflags=finalstreamflags, typeid=frametype,
227 streamflags=finalstreamflags, typeid=frametype,
236 flags=finalflags, payload=payload)
228 flags=finalflags, payload=payload)
237
229
238 def parseheader(data):
230 def parseheader(data):
239 """Parse a unified framing protocol frame header from a buffer.
231 """Parse a unified framing protocol frame header from a buffer.
240
232
241 The header is expected to be in the buffer at offset 0 and the
233 The header is expected to be in the buffer at offset 0 and the
242 buffer is expected to be large enough to hold a full header.
234 buffer is expected to be large enough to hold a full header.
243 """
235 """
244 # 24 bits payload length (little endian)
236 # 24 bits payload length (little endian)
245 # 16 bits request ID
237 # 16 bits request ID
246 # 8 bits stream ID
238 # 8 bits stream ID
247 # 8 bits stream flags
239 # 8 bits stream flags
248 # 4 bits frame type
240 # 4 bits frame type
249 # 4 bits frame flags
241 # 4 bits frame flags
250 # ... payload
242 # ... payload
251 framelength = data[0] + 256 * data[1] + 16384 * data[2]
243 framelength = data[0] + 256 * data[1] + 16384 * data[2]
252 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
244 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
253 typeflags = data[7]
245 typeflags = data[7]
254
246
255 frametype = (typeflags & 0xf0) >> 4
247 frametype = (typeflags & 0xf0) >> 4
256 frameflags = typeflags & 0x0f
248 frameflags = typeflags & 0x0f
257
249
258 return frameheader(framelength, requestid, streamid, streamflags,
250 return frameheader(framelength, requestid, streamid, streamflags,
259 frametype, frameflags)
251 frametype, frameflags)
260
252
261 def readframe(fh):
253 def readframe(fh):
262 """Read a unified framing protocol frame from a file object.
254 """Read a unified framing protocol frame from a file object.
263
255
264 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
256 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
265 None if no frame is available. May raise if a malformed frame is
257 None if no frame is available. May raise if a malformed frame is
266 seen.
258 seen.
267 """
259 """
268 header = bytearray(FRAME_HEADER_SIZE)
260 header = bytearray(FRAME_HEADER_SIZE)
269
261
270 readcount = fh.readinto(header)
262 readcount = fh.readinto(header)
271
263
272 if readcount == 0:
264 if readcount == 0:
273 return None
265 return None
274
266
275 if readcount != FRAME_HEADER_SIZE:
267 if readcount != FRAME_HEADER_SIZE:
276 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
268 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
277 (readcount, header))
269 (readcount, header))
278
270
279 h = parseheader(header)
271 h = parseheader(header)
280
272
281 payload = fh.read(h.length)
273 payload = fh.read(h.length)
282 if len(payload) != h.length:
274 if len(payload) != h.length:
283 raise error.Abort(_('frame length error: expected %d; got %d') %
275 raise error.Abort(_('frame length error: expected %d; got %d') %
284 (h.length, len(payload)))
276 (h.length, len(payload)))
285
277
286 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
278 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
287 payload)
279 payload)
288
280
289 def createcommandframes(stream, requestid, cmd, args, datafh=None,
281 def createcommandframes(stream, requestid, cmd, args, datafh=None,
290 maxframesize=DEFAULT_MAX_FRAME_SIZE):
282 maxframesize=DEFAULT_MAX_FRAME_SIZE):
291 """Create frames necessary to transmit a request to run a command.
283 """Create frames necessary to transmit a request to run a command.
292
284
293 This is a generator of bytearrays. Each item represents a frame
285 This is a generator of bytearrays. Each item represents a frame
294 ready to be sent over the wire to a peer.
286 ready to be sent over the wire to a peer.
295 """
287 """
296 data = {b'name': cmd}
288 data = {b'name': cmd}
297 if args:
289 if args:
298 data[b'args'] = args
290 data[b'args'] = args
299
291
300 data = cbor.dumps(data, canonical=True)
292 data = cbor.dumps(data, canonical=True)
301
293
302 offset = 0
294 offset = 0
303
295
304 while True:
296 while True:
305 flags = 0
297 flags = 0
306
298
307 # Must set new or continuation flag.
299 # Must set new or continuation flag.
308 if not offset:
300 if not offset:
309 flags |= FLAG_COMMAND_REQUEST_NEW
301 flags |= FLAG_COMMAND_REQUEST_NEW
310 else:
302 else:
311 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
303 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
312
304
313 # Data frames is set on all frames.
305 # Data frames is set on all frames.
314 if datafh:
306 if datafh:
315 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
307 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
316
308
317 payload = data[offset:offset + maxframesize]
309 payload = data[offset:offset + maxframesize]
318 offset += len(payload)
310 offset += len(payload)
319
311
320 if len(payload) == maxframesize and offset < len(data):
312 if len(payload) == maxframesize and offset < len(data):
321 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
313 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
322
314
323 yield stream.makeframe(requestid=requestid,
315 yield stream.makeframe(requestid=requestid,
324 typeid=FRAME_TYPE_COMMAND_REQUEST,
316 typeid=FRAME_TYPE_COMMAND_REQUEST,
325 flags=flags,
317 flags=flags,
326 payload=payload)
318 payload=payload)
327
319
328 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
320 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
329 break
321 break
330
322
331 if datafh:
323 if datafh:
332 while True:
324 while True:
333 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
325 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
334
326
335 done = False
327 done = False
336 if len(data) == DEFAULT_MAX_FRAME_SIZE:
328 if len(data) == DEFAULT_MAX_FRAME_SIZE:
337 flags = FLAG_COMMAND_DATA_CONTINUATION
329 flags = FLAG_COMMAND_DATA_CONTINUATION
338 else:
330 else:
339 flags = FLAG_COMMAND_DATA_EOS
331 flags = FLAG_COMMAND_DATA_EOS
340 assert datafh.read(1) == b''
332 assert datafh.read(1) == b''
341 done = True
333 done = True
342
334
343 yield stream.makeframe(requestid=requestid,
335 yield stream.makeframe(requestid=requestid,
344 typeid=FRAME_TYPE_COMMAND_DATA,
336 typeid=FRAME_TYPE_COMMAND_DATA,
345 flags=flags,
337 flags=flags,
346 payload=data)
338 payload=data)
347
339
348 if done:
340 if done:
349 break
341 break
350
342
351 def createcommandresponseframesfrombytes(stream, requestid, data,
343 def createcommandresponseframesfrombytes(stream, requestid, data,
352 maxframesize=DEFAULT_MAX_FRAME_SIZE):
344 maxframesize=DEFAULT_MAX_FRAME_SIZE):
353 """Create a raw frame to send a bytes response from static bytes input.
345 """Create a raw frame to send a bytes response from static bytes input.
354
346
355 Returns a generator of bytearrays.
347 Returns a generator of bytearrays.
356 """
348 """
357 # Automatically send the overall CBOR response map.
349 # Automatically send the overall CBOR response map.
358 overall = cbor.dumps({b'status': b'ok'}, canonical=True)
350 overall = cbor.dumps({b'status': b'ok'}, canonical=True)
359 if len(overall) > maxframesize:
351 if len(overall) > maxframesize:
360 raise error.ProgrammingError('not yet implemented')
352 raise error.ProgrammingError('not yet implemented')
361
353
362 # Simple case where we can fit the full response in a single frame.
354 # Simple case where we can fit the full response in a single frame.
363 if len(overall) + len(data) <= maxframesize:
355 if len(overall) + len(data) <= maxframesize:
364 flags = FLAG_COMMAND_RESPONSE_EOS
356 flags = FLAG_COMMAND_RESPONSE_EOS
365 yield stream.makeframe(requestid=requestid,
357 yield stream.makeframe(requestid=requestid,
366 typeid=FRAME_TYPE_COMMAND_RESPONSE,
358 typeid=FRAME_TYPE_COMMAND_RESPONSE,
367 flags=flags,
359 flags=flags,
368 payload=overall + data)
360 payload=overall + data)
369 return
361 return
370
362
371 # It's easier to send the overall CBOR map in its own frame than to track
363 # It's easier to send the overall CBOR map in its own frame than to track
372 # offsets.
364 # offsets.
373 yield stream.makeframe(requestid=requestid,
365 yield stream.makeframe(requestid=requestid,
374 typeid=FRAME_TYPE_COMMAND_RESPONSE,
366 typeid=FRAME_TYPE_COMMAND_RESPONSE,
375 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
367 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
376 payload=overall)
368 payload=overall)
377
369
378 offset = 0
370 offset = 0
379 while True:
371 while True:
380 chunk = data[offset:offset + maxframesize]
372 chunk = data[offset:offset + maxframesize]
381 offset += len(chunk)
373 offset += len(chunk)
382 done = offset == len(data)
374 done = offset == len(data)
383
375
384 if done:
376 if done:
385 flags = FLAG_COMMAND_RESPONSE_EOS
377 flags = FLAG_COMMAND_RESPONSE_EOS
386 else:
378 else:
387 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
379 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
388
380
389 yield stream.makeframe(requestid=requestid,
381 yield stream.makeframe(requestid=requestid,
390 typeid=FRAME_TYPE_COMMAND_RESPONSE,
382 typeid=FRAME_TYPE_COMMAND_RESPONSE,
391 flags=flags,
383 flags=flags,
392 payload=chunk)
384 payload=chunk)
393
385
394 if done:
386 if done:
395 break
387 break
396
388
397 def createerrorframe(stream, requestid, msg, protocol=False, application=False):
389 def createerrorframe(stream, requestid, msg, errtype):
398 # TODO properly handle frame size limits.
390 # TODO properly handle frame size limits.
399 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
391 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
400
392
401 flags = 0
393 payload = cbor.dumps({
402 if protocol:
394 b'type': errtype,
403 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
395 b'message': [{b'msg': msg}],
404 if application:
396 }, canonical=True)
405 flags |= FLAG_ERROR_RESPONSE_APPLICATION
406
397
407 yield stream.makeframe(requestid=requestid,
398 yield stream.makeframe(requestid=requestid,
408 typeid=FRAME_TYPE_ERROR_RESPONSE,
399 typeid=FRAME_TYPE_ERROR_RESPONSE,
409 flags=flags,
400 flags=0,
410 payload=msg)
401 payload=payload)
411
402
412 def createtextoutputframe(stream, requestid, atoms,
403 def createtextoutputframe(stream, requestid, atoms,
413 maxframesize=DEFAULT_MAX_FRAME_SIZE):
404 maxframesize=DEFAULT_MAX_FRAME_SIZE):
414 """Create a text output frame to render text to people.
405 """Create a text output frame to render text to people.
415
406
416 ``atoms`` is a 3-tuple of (formatting string, args, labels).
407 ``atoms`` is a 3-tuple of (formatting string, args, labels).
417
408
418 The formatting string contains ``%s`` tokens to be replaced by the
409 The formatting string contains ``%s`` tokens to be replaced by the
419 corresponding indexed entry in ``args``. ``labels`` is an iterable of
410 corresponding indexed entry in ``args``. ``labels`` is an iterable of
420 formatters to be applied at rendering time. In terms of the ``ui``
411 formatters to be applied at rendering time. In terms of the ``ui``
421 class, each atom corresponds to a ``ui.write()``.
412 class, each atom corresponds to a ``ui.write()``.
422 """
413 """
423 atomdicts = []
414 atomdicts = []
424
415
425 for (formatting, args, labels) in atoms:
416 for (formatting, args, labels) in atoms:
426 # TODO look for localstr, other types here?
417 # TODO look for localstr, other types here?
427
418
428 if not isinstance(formatting, bytes):
419 if not isinstance(formatting, bytes):
429 raise ValueError('must use bytes formatting strings')
420 raise ValueError('must use bytes formatting strings')
430 for arg in args:
421 for arg in args:
431 if not isinstance(arg, bytes):
422 if not isinstance(arg, bytes):
432 raise ValueError('must use bytes for arguments')
423 raise ValueError('must use bytes for arguments')
433 for label in labels:
424 for label in labels:
434 if not isinstance(label, bytes):
425 if not isinstance(label, bytes):
435 raise ValueError('must use bytes for labels')
426 raise ValueError('must use bytes for labels')
436
427
437 # Formatting string must be ASCII.
428 # Formatting string must be ASCII.
438 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
429 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
439
430
440 # Arguments must be UTF-8.
431 # Arguments must be UTF-8.
441 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
432 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
442
433
443 # Labels must be ASCII.
434 # Labels must be ASCII.
444 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
435 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
445 for l in labels]
436 for l in labels]
446
437
447 atom = {b'msg': formatting}
438 atom = {b'msg': formatting}
448 if args:
439 if args:
449 atom[b'args'] = args
440 atom[b'args'] = args
450 if labels:
441 if labels:
451 atom[b'labels'] = labels
442 atom[b'labels'] = labels
452
443
453 atomdicts.append(atom)
444 atomdicts.append(atom)
454
445
455 payload = cbor.dumps(atomdicts, canonical=True)
446 payload = cbor.dumps(atomdicts, canonical=True)
456
447
457 if len(payload) > maxframesize:
448 if len(payload) > maxframesize:
458 raise ValueError('cannot encode data in a single frame')
449 raise ValueError('cannot encode data in a single frame')
459
450
460 yield stream.makeframe(requestid=requestid,
451 yield stream.makeframe(requestid=requestid,
461 typeid=FRAME_TYPE_TEXT_OUTPUT,
452 typeid=FRAME_TYPE_TEXT_OUTPUT,
462 flags=0,
453 flags=0,
463 payload=payload)
454 payload=payload)
464
455
465 class stream(object):
456 class stream(object):
466 """Represents a logical unidirectional series of frames."""
457 """Represents a logical unidirectional series of frames."""
467
458
468 def __init__(self, streamid, active=False):
459 def __init__(self, streamid, active=False):
469 self.streamid = streamid
460 self.streamid = streamid
470 self._active = active
461 self._active = active
471
462
472 def makeframe(self, requestid, typeid, flags, payload):
463 def makeframe(self, requestid, typeid, flags, payload):
473 """Create a frame to be sent out over this stream.
464 """Create a frame to be sent out over this stream.
474
465
475 Only returns the frame instance. Does not actually send it.
466 Only returns the frame instance. Does not actually send it.
476 """
467 """
477 streamflags = 0
468 streamflags = 0
478 if not self._active:
469 if not self._active:
479 streamflags |= STREAM_FLAG_BEGIN_STREAM
470 streamflags |= STREAM_FLAG_BEGIN_STREAM
480 self._active = True
471 self._active = True
481
472
482 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
473 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
483 payload)
474 payload)
484
475
485 def ensureserverstream(stream):
476 def ensureserverstream(stream):
486 if stream.streamid % 2:
477 if stream.streamid % 2:
487 raise error.ProgrammingError('server should only write to even '
478 raise error.ProgrammingError('server should only write to even '
488 'numbered streams; %d is not even' %
479 'numbered streams; %d is not even' %
489 stream.streamid)
480 stream.streamid)
490
481
491 class serverreactor(object):
482 class serverreactor(object):
492 """Holds state of a server handling frame-based protocol requests.
483 """Holds state of a server handling frame-based protocol requests.
493
484
494 This class is the "brain" of the unified frame-based protocol server
485 This class is the "brain" of the unified frame-based protocol server
495 component. While the protocol is stateless from the perspective of
486 component. While the protocol is stateless from the perspective of
496 requests/commands, something needs to track which frames have been
487 requests/commands, something needs to track which frames have been
497 received, what frames to expect, etc. This class is that thing.
488 received, what frames to expect, etc. This class is that thing.
498
489
499 Instances are modeled as a state machine of sorts. Instances are also
490 Instances are modeled as a state machine of sorts. Instances are also
500 reactionary to external events. The point of this class is to encapsulate
491 reactionary to external events. The point of this class is to encapsulate
501 the state of the connection and the exchange of frames, not to perform
492 the state of the connection and the exchange of frames, not to perform
502 work. Instead, callers tell this class when something occurs, like a
493 work. Instead, callers tell this class when something occurs, like a
503 frame arriving. If that activity is worthy of a follow-up action (say
494 frame arriving. If that activity is worthy of a follow-up action (say
504 *run a command*), the return value of that handler will say so.
495 *run a command*), the return value of that handler will say so.
505
496
506 I/O and CPU intensive operations are purposefully delegated outside of
497 I/O and CPU intensive operations are purposefully delegated outside of
507 this class.
498 this class.
508
499
509 Consumers are expected to tell instances when events occur. They do so by
500 Consumers are expected to tell instances when events occur. They do so by
510 calling the various ``on*`` methods. These methods return a 2-tuple
501 calling the various ``on*`` methods. These methods return a 2-tuple
511 describing any follow-up action(s) to take. The first element is the
502 describing any follow-up action(s) to take. The first element is the
512 name of an action to perform. The second is a data structure (usually
503 name of an action to perform. The second is a data structure (usually
513 a dict) specific to that action that contains more information. e.g.
504 a dict) specific to that action that contains more information. e.g.
514 if the server wants to send frames back to the client, the data structure
505 if the server wants to send frames back to the client, the data structure
515 will contain a reference to those frames.
506 will contain a reference to those frames.
516
507
517 Valid actions that consumers can be instructed to take are:
508 Valid actions that consumers can be instructed to take are:
518
509
519 sendframes
510 sendframes
520 Indicates that frames should be sent to the client. The ``framegen``
511 Indicates that frames should be sent to the client. The ``framegen``
521 key contains a generator of frames that should be sent. The server
512 key contains a generator of frames that should be sent. The server
522 assumes that all frames are sent to the client.
513 assumes that all frames are sent to the client.
523
514
524 error
515 error
525 Indicates that an error occurred. Consumer should probably abort.
516 Indicates that an error occurred. Consumer should probably abort.
526
517
527 runcommand
518 runcommand
528 Indicates that the consumer should run a wire protocol command. Details
519 Indicates that the consumer should run a wire protocol command. Details
529 of the command to run are given in the data structure.
520 of the command to run are given in the data structure.
530
521
531 wantframe
522 wantframe
532 Indicates that nothing of interest happened and the server is waiting on
523 Indicates that nothing of interest happened and the server is waiting on
533 more frames from the client before anything interesting can be done.
524 more frames from the client before anything interesting can be done.
534
525
535 noop
526 noop
536 Indicates no additional action is required.
527 Indicates no additional action is required.
537
528
538 Known Issues
529 Known Issues
539 ------------
530 ------------
540
531
541 There are no limits to the number of partially received commands or their
532 There are no limits to the number of partially received commands or their
542 size. A malicious client could stream command request data and exhaust the
533 size. A malicious client could stream command request data and exhaust the
543 server's memory.
534 server's memory.
544
535
545 Partially received commands are not acted upon when end of input is
536 Partially received commands are not acted upon when end of input is
546 reached. Should the server error if it receives a partial request?
537 reached. Should the server error if it receives a partial request?
547 Should the client send a message to abort a partially transmitted request
538 Should the client send a message to abort a partially transmitted request
548 to facilitate graceful shutdown?
539 to facilitate graceful shutdown?
549
540
550 Active requests that haven't been responded to aren't tracked. This means
541 Active requests that haven't been responded to aren't tracked. This means
551 that if we receive a command and instruct its dispatch, another command
542 that if we receive a command and instruct its dispatch, another command
552 with its request ID can come in over the wire and there will be a race
543 with its request ID can come in over the wire and there will be a race
553 between who responds to what.
544 between who responds to what.
554 """
545 """
555
546
556 def __init__(self, deferoutput=False):
547 def __init__(self, deferoutput=False):
557 """Construct a new server reactor.
548 """Construct a new server reactor.
558
549
559 ``deferoutput`` can be used to indicate that no output frames should be
550 ``deferoutput`` can be used to indicate that no output frames should be
560 instructed to be sent until input has been exhausted. In this mode,
551 instructed to be sent until input has been exhausted. In this mode,
561 events that would normally generate output frames (such as a command
552 events that would normally generate output frames (such as a command
562 response being ready) will instead defer instructing the consumer to
553 response being ready) will instead defer instructing the consumer to
563 send those frames. This is useful for half-duplex transports where the
554 send those frames. This is useful for half-duplex transports where the
564 sender cannot receive until all data has been transmitted.
555 sender cannot receive until all data has been transmitted.
565 """
556 """
566 self._deferoutput = deferoutput
557 self._deferoutput = deferoutput
567 self._state = 'idle'
558 self._state = 'idle'
568 self._nextoutgoingstreamid = 2
559 self._nextoutgoingstreamid = 2
569 self._bufferedframegens = []
560 self._bufferedframegens = []
570 # stream id -> stream instance for all active streams from the client.
561 # stream id -> stream instance for all active streams from the client.
571 self._incomingstreams = {}
562 self._incomingstreams = {}
572 self._outgoingstreams = {}
563 self._outgoingstreams = {}
573 # request id -> dict of commands that are actively being received.
564 # request id -> dict of commands that are actively being received.
574 self._receivingcommands = {}
565 self._receivingcommands = {}
575 # Request IDs that have been received and are actively being processed.
566 # Request IDs that have been received and are actively being processed.
576 # Once all output for a request has been sent, it is removed from this
567 # Once all output for a request has been sent, it is removed from this
577 # set.
568 # set.
578 self._activecommands = set()
569 self._activecommands = set()
579
570
580 def onframerecv(self, frame):
571 def onframerecv(self, frame):
581 """Process a frame that has been received off the wire.
572 """Process a frame that has been received off the wire.
582
573
583 Returns a dict with an ``action`` key that details what action,
574 Returns a dict with an ``action`` key that details what action,
584 if any, the consumer should take next.
575 if any, the consumer should take next.
585 """
576 """
586 if not frame.streamid % 2:
577 if not frame.streamid % 2:
587 self._state = 'errored'
578 self._state = 'errored'
588 return self._makeerrorresult(
579 return self._makeerrorresult(
589 _('received frame with even numbered stream ID: %d') %
580 _('received frame with even numbered stream ID: %d') %
590 frame.streamid)
581 frame.streamid)
591
582
592 if frame.streamid not in self._incomingstreams:
583 if frame.streamid not in self._incomingstreams:
593 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
584 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
594 self._state = 'errored'
585 self._state = 'errored'
595 return self._makeerrorresult(
586 return self._makeerrorresult(
596 _('received frame on unknown inactive stream without '
587 _('received frame on unknown inactive stream without '
597 'beginning of stream flag set'))
588 'beginning of stream flag set'))
598
589
599 self._incomingstreams[frame.streamid] = stream(frame.streamid)
590 self._incomingstreams[frame.streamid] = stream(frame.streamid)
600
591
601 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
592 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
602 # TODO handle decoding frames
593 # TODO handle decoding frames
603 self._state = 'errored'
594 self._state = 'errored'
604 raise error.ProgrammingError('support for decoding stream payloads '
595 raise error.ProgrammingError('support for decoding stream payloads '
605 'not yet implemented')
596 'not yet implemented')
606
597
607 if frame.streamflags & STREAM_FLAG_END_STREAM:
598 if frame.streamflags & STREAM_FLAG_END_STREAM:
608 del self._incomingstreams[frame.streamid]
599 del self._incomingstreams[frame.streamid]
609
600
610 handlers = {
601 handlers = {
611 'idle': self._onframeidle,
602 'idle': self._onframeidle,
612 'command-receiving': self._onframecommandreceiving,
603 'command-receiving': self._onframecommandreceiving,
613 'errored': self._onframeerrored,
604 'errored': self._onframeerrored,
614 }
605 }
615
606
616 meth = handlers.get(self._state)
607 meth = handlers.get(self._state)
617 if not meth:
608 if not meth:
618 raise error.ProgrammingError('unhandled state: %s' % self._state)
609 raise error.ProgrammingError('unhandled state: %s' % self._state)
619
610
620 return meth(frame)
611 return meth(frame)
621
612
622 def oncommandresponseready(self, stream, requestid, data):
613 def oncommandresponseready(self, stream, requestid, data):
623 """Signal that a bytes response is ready to be sent to the client.
614 """Signal that a bytes response is ready to be sent to the client.
624
615
625 The raw bytes response is passed as an argument.
616 The raw bytes response is passed as an argument.
626 """
617 """
627 ensureserverstream(stream)
618 ensureserverstream(stream)
628
619
629 def sendframes():
620 def sendframes():
630 for frame in createcommandresponseframesfrombytes(stream, requestid,
621 for frame in createcommandresponseframesfrombytes(stream, requestid,
631 data):
622 data):
632 yield frame
623 yield frame
633
624
634 self._activecommands.remove(requestid)
625 self._activecommands.remove(requestid)
635
626
636 result = sendframes()
627 result = sendframes()
637
628
638 if self._deferoutput:
629 if self._deferoutput:
639 self._bufferedframegens.append(result)
630 self._bufferedframegens.append(result)
640 return 'noop', {}
631 return 'noop', {}
641 else:
632 else:
642 return 'sendframes', {
633 return 'sendframes', {
643 'framegen': result,
634 'framegen': result,
644 }
635 }
645
636
646 def oninputeof(self):
637 def oninputeof(self):
647 """Signals that end of input has been received.
638 """Signals that end of input has been received.
648
639
649 No more frames will be received. All pending activity should be
640 No more frames will be received. All pending activity should be
650 completed.
641 completed.
651 """
642 """
652 # TODO should we do anything about in-flight commands?
643 # TODO should we do anything about in-flight commands?
653
644
654 if not self._deferoutput or not self._bufferedframegens:
645 if not self._deferoutput or not self._bufferedframegens:
655 return 'noop', {}
646 return 'noop', {}
656
647
657 # If we buffered all our responses, emit those.
648 # If we buffered all our responses, emit those.
658 def makegen():
649 def makegen():
659 for gen in self._bufferedframegens:
650 for gen in self._bufferedframegens:
660 for frame in gen:
651 for frame in gen:
661 yield frame
652 yield frame
662
653
663 return 'sendframes', {
654 return 'sendframes', {
664 'framegen': makegen(),
655 'framegen': makegen(),
665 }
656 }
666
657
667 def onapplicationerror(self, stream, requestid, msg):
658 def onservererror(self, stream, requestid, msg):
668 ensureserverstream(stream)
659 ensureserverstream(stream)
669
660
670 return 'sendframes', {
661 return 'sendframes', {
671 'framegen': createerrorframe(stream, requestid, msg,
662 'framegen': createerrorframe(stream, requestid, msg,
672 application=True),
663 errtype='server'),
673 }
664 }
674
665
675 def makeoutputstream(self):
666 def makeoutputstream(self):
676 """Create a stream to be used for sending data to the client."""
667 """Create a stream to be used for sending data to the client."""
677 streamid = self._nextoutgoingstreamid
668 streamid = self._nextoutgoingstreamid
678 self._nextoutgoingstreamid += 2
669 self._nextoutgoingstreamid += 2
679
670
680 s = stream(streamid)
671 s = stream(streamid)
681 self._outgoingstreams[streamid] = s
672 self._outgoingstreams[streamid] = s
682
673
683 return s
674 return s
684
675
685 def _makeerrorresult(self, msg):
676 def _makeerrorresult(self, msg):
686 return 'error', {
677 return 'error', {
687 'message': msg,
678 'message': msg,
688 }
679 }
689
680
690 def _makeruncommandresult(self, requestid):
681 def _makeruncommandresult(self, requestid):
691 entry = self._receivingcommands[requestid]
682 entry = self._receivingcommands[requestid]
692
683
693 if not entry['requestdone']:
684 if not entry['requestdone']:
694 self._state = 'errored'
685 self._state = 'errored'
695 raise error.ProgrammingError('should not be called without '
686 raise error.ProgrammingError('should not be called without '
696 'requestdone set')
687 'requestdone set')
697
688
698 del self._receivingcommands[requestid]
689 del self._receivingcommands[requestid]
699
690
700 if self._receivingcommands:
691 if self._receivingcommands:
701 self._state = 'command-receiving'
692 self._state = 'command-receiving'
702 else:
693 else:
703 self._state = 'idle'
694 self._state = 'idle'
704
695
705 # Decode the payloads as CBOR.
696 # Decode the payloads as CBOR.
706 entry['payload'].seek(0)
697 entry['payload'].seek(0)
707 request = cbor.load(entry['payload'])
698 request = cbor.load(entry['payload'])
708
699
709 if b'name' not in request:
700 if b'name' not in request:
710 self._state = 'errored'
701 self._state = 'errored'
711 return self._makeerrorresult(
702 return self._makeerrorresult(
712 _('command request missing "name" field'))
703 _('command request missing "name" field'))
713
704
714 if b'args' not in request:
705 if b'args' not in request:
715 request[b'args'] = {}
706 request[b'args'] = {}
716
707
717 assert requestid not in self._activecommands
708 assert requestid not in self._activecommands
718 self._activecommands.add(requestid)
709 self._activecommands.add(requestid)
719
710
720 return 'runcommand', {
711 return 'runcommand', {
721 'requestid': requestid,
712 'requestid': requestid,
722 'command': request[b'name'],
713 'command': request[b'name'],
723 'args': request[b'args'],
714 'args': request[b'args'],
724 'data': entry['data'].getvalue() if entry['data'] else None,
715 'data': entry['data'].getvalue() if entry['data'] else None,
725 }
716 }
726
717
727 def _makewantframeresult(self):
718 def _makewantframeresult(self):
728 return 'wantframe', {
719 return 'wantframe', {
729 'state': self._state,
720 'state': self._state,
730 }
721 }
731
722
732 def _validatecommandrequestframe(self, frame):
723 def _validatecommandrequestframe(self, frame):
733 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
724 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
734 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
725 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
735
726
736 if new and continuation:
727 if new and continuation:
737 self._state = 'errored'
728 self._state = 'errored'
738 return self._makeerrorresult(
729 return self._makeerrorresult(
739 _('received command request frame with both new and '
730 _('received command request frame with both new and '
740 'continuation flags set'))
731 'continuation flags set'))
741
732
742 if not new and not continuation:
733 if not new and not continuation:
743 self._state = 'errored'
734 self._state = 'errored'
744 return self._makeerrorresult(
735 return self._makeerrorresult(
745 _('received command request frame with neither new nor '
736 _('received command request frame with neither new nor '
746 'continuation flags set'))
737 'continuation flags set'))
747
738
748 def _onframeidle(self, frame):
739 def _onframeidle(self, frame):
749 # The only frame type that should be received in this state is a
740 # The only frame type that should be received in this state is a
750 # command request.
741 # command request.
751 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
742 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
752 self._state = 'errored'
743 self._state = 'errored'
753 return self._makeerrorresult(
744 return self._makeerrorresult(
754 _('expected command request frame; got %d') % frame.typeid)
745 _('expected command request frame; got %d') % frame.typeid)
755
746
756 res = self._validatecommandrequestframe(frame)
747 res = self._validatecommandrequestframe(frame)
757 if res:
748 if res:
758 return res
749 return res
759
750
760 if frame.requestid in self._receivingcommands:
751 if frame.requestid in self._receivingcommands:
761 self._state = 'errored'
752 self._state = 'errored'
762 return self._makeerrorresult(
753 return self._makeerrorresult(
763 _('request with ID %d already received') % frame.requestid)
754 _('request with ID %d already received') % frame.requestid)
764
755
765 if frame.requestid in self._activecommands:
756 if frame.requestid in self._activecommands:
766 self._state = 'errored'
757 self._state = 'errored'
767 return self._makeerrorresult(
758 return self._makeerrorresult(
768 _('request with ID %d is already active') % frame.requestid)
759 _('request with ID %d is already active') % frame.requestid)
769
760
770 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
761 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
771 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
762 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
772 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
763 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
773
764
774 if not new:
765 if not new:
775 self._state = 'errored'
766 self._state = 'errored'
776 return self._makeerrorresult(
767 return self._makeerrorresult(
777 _('received command request frame without new flag set'))
768 _('received command request frame without new flag set'))
778
769
779 payload = util.bytesio()
770 payload = util.bytesio()
780 payload.write(frame.payload)
771 payload.write(frame.payload)
781
772
782 self._receivingcommands[frame.requestid] = {
773 self._receivingcommands[frame.requestid] = {
783 'payload': payload,
774 'payload': payload,
784 'data': None,
775 'data': None,
785 'requestdone': not moreframes,
776 'requestdone': not moreframes,
786 'expectingdata': bool(expectingdata),
777 'expectingdata': bool(expectingdata),
787 }
778 }
788
779
789 # This is the final frame for this request. Dispatch it.
780 # This is the final frame for this request. Dispatch it.
790 if not moreframes and not expectingdata:
781 if not moreframes and not expectingdata:
791 return self._makeruncommandresult(frame.requestid)
782 return self._makeruncommandresult(frame.requestid)
792
783
793 assert moreframes or expectingdata
784 assert moreframes or expectingdata
794 self._state = 'command-receiving'
785 self._state = 'command-receiving'
795 return self._makewantframeresult()
786 return self._makewantframeresult()
796
787
797 def _onframecommandreceiving(self, frame):
788 def _onframecommandreceiving(self, frame):
798 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
789 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
799 # Process new command requests as such.
790 # Process new command requests as such.
800 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
791 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
801 return self._onframeidle(frame)
792 return self._onframeidle(frame)
802
793
803 res = self._validatecommandrequestframe(frame)
794 res = self._validatecommandrequestframe(frame)
804 if res:
795 if res:
805 return res
796 return res
806
797
807 # All other frames should be related to a command that is currently
798 # All other frames should be related to a command that is currently
808 # receiving but is not active.
799 # receiving but is not active.
809 if frame.requestid in self._activecommands:
800 if frame.requestid in self._activecommands:
810 self._state = 'errored'
801 self._state = 'errored'
811 return self._makeerrorresult(
802 return self._makeerrorresult(
812 _('received frame for request that is still active: %d') %
803 _('received frame for request that is still active: %d') %
813 frame.requestid)
804 frame.requestid)
814
805
815 if frame.requestid not in self._receivingcommands:
806 if frame.requestid not in self._receivingcommands:
816 self._state = 'errored'
807 self._state = 'errored'
817 return self._makeerrorresult(
808 return self._makeerrorresult(
818 _('received frame for request that is not receiving: %d') %
809 _('received frame for request that is not receiving: %d') %
819 frame.requestid)
810 frame.requestid)
820
811
821 entry = self._receivingcommands[frame.requestid]
812 entry = self._receivingcommands[frame.requestid]
822
813
823 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
814 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
824 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
815 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
825 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
816 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
826
817
827 if entry['requestdone']:
818 if entry['requestdone']:
828 self._state = 'errored'
819 self._state = 'errored'
829 return self._makeerrorresult(
820 return self._makeerrorresult(
830 _('received command request frame when request frames '
821 _('received command request frame when request frames '
831 'were supposedly done'))
822 'were supposedly done'))
832
823
833 if expectingdata != entry['expectingdata']:
824 if expectingdata != entry['expectingdata']:
834 self._state = 'errored'
825 self._state = 'errored'
835 return self._makeerrorresult(
826 return self._makeerrorresult(
836 _('mismatch between expect data flag and previous frame'))
827 _('mismatch between expect data flag and previous frame'))
837
828
838 entry['payload'].write(frame.payload)
829 entry['payload'].write(frame.payload)
839
830
840 if not moreframes:
831 if not moreframes:
841 entry['requestdone'] = True
832 entry['requestdone'] = True
842
833
843 if not moreframes and not expectingdata:
834 if not moreframes and not expectingdata:
844 return self._makeruncommandresult(frame.requestid)
835 return self._makeruncommandresult(frame.requestid)
845
836
846 return self._makewantframeresult()
837 return self._makewantframeresult()
847
838
848 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
839 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
849 if not entry['expectingdata']:
840 if not entry['expectingdata']:
850 self._state = 'errored'
841 self._state = 'errored'
851 return self._makeerrorresult(_(
842 return self._makeerrorresult(_(
852 'received command data frame for request that is not '
843 'received command data frame for request that is not '
853 'expecting data: %d') % frame.requestid)
844 'expecting data: %d') % frame.requestid)
854
845
855 if entry['data'] is None:
846 if entry['data'] is None:
856 entry['data'] = util.bytesio()
847 entry['data'] = util.bytesio()
857
848
858 return self._handlecommanddataframe(frame, entry)
849 return self._handlecommanddataframe(frame, entry)
859 else:
850 else:
860 self._state = 'errored'
851 self._state = 'errored'
861 return self._makeerrorresult(_(
852 return self._makeerrorresult(_(
862 'received unexpected frame type: %d') % frame.typeid)
853 'received unexpected frame type: %d') % frame.typeid)
863
854
864 def _handlecommanddataframe(self, frame, entry):
855 def _handlecommanddataframe(self, frame, entry):
865 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
856 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
866
857
867 # TODO support streaming data instead of buffering it.
858 # TODO support streaming data instead of buffering it.
868 entry['data'].write(frame.payload)
859 entry['data'].write(frame.payload)
869
860
870 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
861 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
871 return self._makewantframeresult()
862 return self._makewantframeresult()
872 elif frame.flags & FLAG_COMMAND_DATA_EOS:
863 elif frame.flags & FLAG_COMMAND_DATA_EOS:
873 entry['data'].seek(0)
864 entry['data'].seek(0)
874 return self._makeruncommandresult(frame.requestid)
865 return self._makeruncommandresult(frame.requestid)
875 else:
866 else:
876 self._state = 'errored'
867 self._state = 'errored'
877 return self._makeerrorresult(_('command data frame without '
868 return self._makeerrorresult(_('command data frame without '
878 'flags'))
869 'flags'))
879
870
880 def _onframeerrored(self, frame):
871 def _onframeerrored(self, frame):
881 return self._makeerrorresult(_('server already errored'))
872 return self._makeerrorresult(_('server already errored'))
882
873
883 class commandrequest(object):
874 class commandrequest(object):
884 """Represents a request to run a command."""
875 """Represents a request to run a command."""
885
876
886 def __init__(self, requestid, name, args, datafh=None):
877 def __init__(self, requestid, name, args, datafh=None):
887 self.requestid = requestid
878 self.requestid = requestid
888 self.name = name
879 self.name = name
889 self.args = args
880 self.args = args
890 self.datafh = datafh
881 self.datafh = datafh
891 self.state = 'pending'
882 self.state = 'pending'
892
883
893 class clientreactor(object):
884 class clientreactor(object):
894 """Holds state of a client issuing frame-based protocol requests.
885 """Holds state of a client issuing frame-based protocol requests.
895
886
896 This is like ``serverreactor`` but for client-side state.
887 This is like ``serverreactor`` but for client-side state.
897
888
898 Each instance is bound to the lifetime of a connection. For persistent
889 Each instance is bound to the lifetime of a connection. For persistent
899 connection transports using e.g. TCP sockets and speaking the raw
890 connection transports using e.g. TCP sockets and speaking the raw
900 framing protocol, there will be a single instance for the lifetime of
891 framing protocol, there will be a single instance for the lifetime of
901 the TCP socket. For transports where there are multiple discrete
892 the TCP socket. For transports where there are multiple discrete
902 interactions (say tunneled within in HTTP request), there will be a
893 interactions (say tunneled within in HTTP request), there will be a
903 separate instance for each distinct interaction.
894 separate instance for each distinct interaction.
904 """
895 """
905 def __init__(self, hasmultiplesend=False, buffersends=True):
896 def __init__(self, hasmultiplesend=False, buffersends=True):
906 """Create a new instance.
897 """Create a new instance.
907
898
908 ``hasmultiplesend`` indicates whether multiple sends are supported
899 ``hasmultiplesend`` indicates whether multiple sends are supported
909 by the transport. When True, it is possible to send commands immediately
900 by the transport. When True, it is possible to send commands immediately
910 instead of buffering until the caller signals an intent to finish a
901 instead of buffering until the caller signals an intent to finish a
911 send operation.
902 send operation.
912
903
913 ``buffercommands`` indicates whether sends should be buffered until the
904 ``buffercommands`` indicates whether sends should be buffered until the
914 last request has been issued.
905 last request has been issued.
915 """
906 """
916 self._hasmultiplesend = hasmultiplesend
907 self._hasmultiplesend = hasmultiplesend
917 self._buffersends = buffersends
908 self._buffersends = buffersends
918
909
919 self._canissuecommands = True
910 self._canissuecommands = True
920 self._cansend = True
911 self._cansend = True
921
912
922 self._nextrequestid = 1
913 self._nextrequestid = 1
923 # We only support a single outgoing stream for now.
914 # We only support a single outgoing stream for now.
924 self._outgoingstream = stream(1)
915 self._outgoingstream = stream(1)
925 self._pendingrequests = collections.deque()
916 self._pendingrequests = collections.deque()
926 self._activerequests = {}
917 self._activerequests = {}
927 self._incomingstreams = {}
918 self._incomingstreams = {}
928
919
929 def callcommand(self, name, args, datafh=None):
920 def callcommand(self, name, args, datafh=None):
930 """Request that a command be executed.
921 """Request that a command be executed.
931
922
932 Receives the command name, a dict of arguments to pass to the command,
923 Receives the command name, a dict of arguments to pass to the command,
933 and an optional file object containing the raw data for the command.
924 and an optional file object containing the raw data for the command.
934
925
935 Returns a 3-tuple of (request, action, action data).
926 Returns a 3-tuple of (request, action, action data).
936 """
927 """
937 if not self._canissuecommands:
928 if not self._canissuecommands:
938 raise error.ProgrammingError('cannot issue new commands')
929 raise error.ProgrammingError('cannot issue new commands')
939
930
940 requestid = self._nextrequestid
931 requestid = self._nextrequestid
941 self._nextrequestid += 2
932 self._nextrequestid += 2
942
933
943 request = commandrequest(requestid, name, args, datafh=datafh)
934 request = commandrequest(requestid, name, args, datafh=datafh)
944
935
945 if self._buffersends:
936 if self._buffersends:
946 self._pendingrequests.append(request)
937 self._pendingrequests.append(request)
947 return request, 'noop', {}
938 return request, 'noop', {}
948 else:
939 else:
949 if not self._cansend:
940 if not self._cansend:
950 raise error.ProgrammingError('sends cannot be performed on '
941 raise error.ProgrammingError('sends cannot be performed on '
951 'this instance')
942 'this instance')
952
943
953 if not self._hasmultiplesend:
944 if not self._hasmultiplesend:
954 self._cansend = False
945 self._cansend = False
955 self._canissuecommands = False
946 self._canissuecommands = False
956
947
957 return request, 'sendframes', {
948 return request, 'sendframes', {
958 'framegen': self._makecommandframes(request),
949 'framegen': self._makecommandframes(request),
959 }
950 }
960
951
961 def flushcommands(self):
952 def flushcommands(self):
962 """Request that all queued commands be sent.
953 """Request that all queued commands be sent.
963
954
964 If any commands are buffered, this will instruct the caller to send
955 If any commands are buffered, this will instruct the caller to send
965 them over the wire. If no commands are buffered it instructs the client
956 them over the wire. If no commands are buffered it instructs the client
966 to no-op.
957 to no-op.
967
958
968 If instances aren't configured for multiple sends, no new command
959 If instances aren't configured for multiple sends, no new command
969 requests are allowed after this is called.
960 requests are allowed after this is called.
970 """
961 """
971 if not self._pendingrequests:
962 if not self._pendingrequests:
972 return 'noop', {}
963 return 'noop', {}
973
964
974 if not self._cansend:
965 if not self._cansend:
975 raise error.ProgrammingError('sends cannot be performed on this '
966 raise error.ProgrammingError('sends cannot be performed on this '
976 'instance')
967 'instance')
977
968
978 # If the instance only allows sending once, mark that we have fired
969 # If the instance only allows sending once, mark that we have fired
979 # our one shot.
970 # our one shot.
980 if not self._hasmultiplesend:
971 if not self._hasmultiplesend:
981 self._canissuecommands = False
972 self._canissuecommands = False
982 self._cansend = False
973 self._cansend = False
983
974
984 def makeframes():
975 def makeframes():
985 while self._pendingrequests:
976 while self._pendingrequests:
986 request = self._pendingrequests.popleft()
977 request = self._pendingrequests.popleft()
987 for frame in self._makecommandframes(request):
978 for frame in self._makecommandframes(request):
988 yield frame
979 yield frame
989
980
990 return 'sendframes', {
981 return 'sendframes', {
991 'framegen': makeframes(),
982 'framegen': makeframes(),
992 }
983 }
993
984
994 def _makecommandframes(self, request):
985 def _makecommandframes(self, request):
995 """Emit frames to issue a command request.
986 """Emit frames to issue a command request.
996
987
997 As a side-effect, update request accounting to reflect its changed
988 As a side-effect, update request accounting to reflect its changed
998 state.
989 state.
999 """
990 """
1000 self._activerequests[request.requestid] = request
991 self._activerequests[request.requestid] = request
1001 request.state = 'sending'
992 request.state = 'sending'
1002
993
1003 res = createcommandframes(self._outgoingstream,
994 res = createcommandframes(self._outgoingstream,
1004 request.requestid,
995 request.requestid,
1005 request.name,
996 request.name,
1006 request.args,
997 request.args,
1007 request.datafh)
998 request.datafh)
1008
999
1009 for frame in res:
1000 for frame in res:
1010 yield frame
1001 yield frame
1011
1002
1012 request.state = 'sent'
1003 request.state = 'sent'
1013
1004
1014 def onframerecv(self, frame):
1005 def onframerecv(self, frame):
1015 """Process a frame that has been received off the wire.
1006 """Process a frame that has been received off the wire.
1016
1007
1017 Returns a 2-tuple of (action, meta) describing further action the
1008 Returns a 2-tuple of (action, meta) describing further action the
1018 caller needs to take as a result of receiving this frame.
1009 caller needs to take as a result of receiving this frame.
1019 """
1010 """
1020 if frame.streamid % 2:
1011 if frame.streamid % 2:
1021 return 'error', {
1012 return 'error', {
1022 'message': (
1013 'message': (
1023 _('received frame with odd numbered stream ID: %d') %
1014 _('received frame with odd numbered stream ID: %d') %
1024 frame.streamid),
1015 frame.streamid),
1025 }
1016 }
1026
1017
1027 if frame.streamid not in self._incomingstreams:
1018 if frame.streamid not in self._incomingstreams:
1028 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1019 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1029 return 'error', {
1020 return 'error', {
1030 'message': _('received frame on unknown stream '
1021 'message': _('received frame on unknown stream '
1031 'without beginning of stream flag set'),
1022 'without beginning of stream flag set'),
1032 }
1023 }
1033
1024
1034 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1025 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1035
1026
1036 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1027 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1037 raise error.ProgrammingError('support for decoding stream '
1028 raise error.ProgrammingError('support for decoding stream '
1038 'payloads not yet implemneted')
1029 'payloads not yet implemneted')
1039
1030
1040 if frame.streamflags & STREAM_FLAG_END_STREAM:
1031 if frame.streamflags & STREAM_FLAG_END_STREAM:
1041 del self._incomingstreams[frame.streamid]
1032 del self._incomingstreams[frame.streamid]
1042
1033
1043 if frame.requestid not in self._activerequests:
1034 if frame.requestid not in self._activerequests:
1044 return 'error', {
1035 return 'error', {
1045 'message': (_('received frame for inactive request ID: %d') %
1036 'message': (_('received frame for inactive request ID: %d') %
1046 frame.requestid),
1037 frame.requestid),
1047 }
1038 }
1048
1039
1049 request = self._activerequests[frame.requestid]
1040 request = self._activerequests[frame.requestid]
1050 request.state = 'receiving'
1041 request.state = 'receiving'
1051
1042
1052 handlers = {
1043 handlers = {
1053 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1044 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1045 FRAME_TYPE_ERROR_RESPONSE: self._onerrorresponseframe,
1054 }
1046 }
1055
1047
1056 meth = handlers.get(frame.typeid)
1048 meth = handlers.get(frame.typeid)
1057 if not meth:
1049 if not meth:
1058 raise error.ProgrammingError('unhandled frame type: %d' %
1050 raise error.ProgrammingError('unhandled frame type: %d' %
1059 frame.typeid)
1051 frame.typeid)
1060
1052
1061 return meth(request, frame)
1053 return meth(request, frame)
1062
1054
1063 def _oncommandresponseframe(self, request, frame):
1055 def _oncommandresponseframe(self, request, frame):
1064 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1056 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1065 request.state = 'received'
1057 request.state = 'received'
1066 del self._activerequests[request.requestid]
1058 del self._activerequests[request.requestid]
1067
1059
1068 return 'responsedata', {
1060 return 'responsedata', {
1069 'request': request,
1061 'request': request,
1070 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1062 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1071 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1063 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1072 'data': frame.payload,
1064 'data': frame.payload,
1073 }
1065 }
1066
1067 def _onerrorresponseframe(self, request, frame):
1068 request.state = 'errored'
1069 del self._activerequests[request.requestid]
1070
1071 # The payload should be a CBOR map.
1072 m = cbor.loads(frame.payload)
1073
1074 return 'error', {
1075 'request': request,
1076 'type': m['type'],
1077 'message': m['message'],
1078 }
@@ -1,484 +1,484 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10
10
11 from .i18n import _
11 from .i18n import _
12 from .thirdparty import (
12 from .thirdparty import (
13 cbor,
13 cbor,
14 )
14 )
15 from .thirdparty.zope import (
15 from .thirdparty.zope import (
16 interface as zi,
16 interface as zi,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 streamclone,
22 streamclone,
23 util,
23 util,
24 wireproto,
24 wireproto,
25 wireprotoframing,
25 wireprotoframing,
26 wireprototypes,
26 wireprototypes,
27 )
27 )
28
28
29 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
29 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
30
30
31 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
31 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
32
32
33 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
33 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
34 from .hgweb import common as hgwebcommon
34 from .hgweb import common as hgwebcommon
35
35
36 # URL space looks like: <permissions>/<command>, where <permission> can
36 # URL space looks like: <permissions>/<command>, where <permission> can
37 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
37 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
38
38
39 # Root URL does nothing meaningful... yet.
39 # Root URL does nothing meaningful... yet.
40 if not urlparts:
40 if not urlparts:
41 res.status = b'200 OK'
41 res.status = b'200 OK'
42 res.headers[b'Content-Type'] = b'text/plain'
42 res.headers[b'Content-Type'] = b'text/plain'
43 res.setbodybytes(_('HTTP version 2 API handler'))
43 res.setbodybytes(_('HTTP version 2 API handler'))
44 return
44 return
45
45
46 if len(urlparts) == 1:
46 if len(urlparts) == 1:
47 res.status = b'404 Not Found'
47 res.status = b'404 Not Found'
48 res.headers[b'Content-Type'] = b'text/plain'
48 res.headers[b'Content-Type'] = b'text/plain'
49 res.setbodybytes(_('do not know how to process %s\n') %
49 res.setbodybytes(_('do not know how to process %s\n') %
50 req.dispatchpath)
50 req.dispatchpath)
51 return
51 return
52
52
53 permission, command = urlparts[0:2]
53 permission, command = urlparts[0:2]
54
54
55 if permission not in (b'ro', b'rw'):
55 if permission not in (b'ro', b'rw'):
56 res.status = b'404 Not Found'
56 res.status = b'404 Not Found'
57 res.headers[b'Content-Type'] = b'text/plain'
57 res.headers[b'Content-Type'] = b'text/plain'
58 res.setbodybytes(_('unknown permission: %s') % permission)
58 res.setbodybytes(_('unknown permission: %s') % permission)
59 return
59 return
60
60
61 if req.method != 'POST':
61 if req.method != 'POST':
62 res.status = b'405 Method Not Allowed'
62 res.status = b'405 Method Not Allowed'
63 res.headers[b'Allow'] = b'POST'
63 res.headers[b'Allow'] = b'POST'
64 res.setbodybytes(_('commands require POST requests'))
64 res.setbodybytes(_('commands require POST requests'))
65 return
65 return
66
66
67 # At some point we'll want to use our own API instead of recycling the
67 # At some point we'll want to use our own API instead of recycling the
68 # behavior of version 1 of the wire protocol...
68 # behavior of version 1 of the wire protocol...
69 # TODO return reasonable responses - not responses that overload the
69 # TODO return reasonable responses - not responses that overload the
70 # HTTP status line message for error reporting.
70 # HTTP status line message for error reporting.
71 try:
71 try:
72 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
72 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
73 except hgwebcommon.ErrorResponse as e:
73 except hgwebcommon.ErrorResponse as e:
74 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
74 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
75 for k, v in e.headers:
75 for k, v in e.headers:
76 res.headers[k] = v
76 res.headers[k] = v
77 res.setbodybytes('permission denied')
77 res.setbodybytes('permission denied')
78 return
78 return
79
79
80 # We have a special endpoint to reflect the request back at the client.
80 # We have a special endpoint to reflect the request back at the client.
81 if command == b'debugreflect':
81 if command == b'debugreflect':
82 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
82 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
83 return
83 return
84
84
85 # Extra commands that we handle that aren't really wire protocol
85 # Extra commands that we handle that aren't really wire protocol
86 # commands. Think extra hard before making this hackery available to
86 # commands. Think extra hard before making this hackery available to
87 # extension.
87 # extension.
88 extracommands = {'multirequest'}
88 extracommands = {'multirequest'}
89
89
90 if command not in wireproto.commandsv2 and command not in extracommands:
90 if command not in wireproto.commandsv2 and command not in extracommands:
91 res.status = b'404 Not Found'
91 res.status = b'404 Not Found'
92 res.headers[b'Content-Type'] = b'text/plain'
92 res.headers[b'Content-Type'] = b'text/plain'
93 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
93 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
94 return
94 return
95
95
96 repo = rctx.repo
96 repo = rctx.repo
97 ui = repo.ui
97 ui = repo.ui
98
98
99 proto = httpv2protocolhandler(req, ui)
99 proto = httpv2protocolhandler(req, ui)
100
100
101 if (not wireproto.commandsv2.commandavailable(command, proto)
101 if (not wireproto.commandsv2.commandavailable(command, proto)
102 and command not in extracommands):
102 and command not in extracommands):
103 res.status = b'404 Not Found'
103 res.status = b'404 Not Found'
104 res.headers[b'Content-Type'] = b'text/plain'
104 res.headers[b'Content-Type'] = b'text/plain'
105 res.setbodybytes(_('invalid wire protocol command: %s') % command)
105 res.setbodybytes(_('invalid wire protocol command: %s') % command)
106 return
106 return
107
107
108 # TODO consider cases where proxies may add additional Accept headers.
108 # TODO consider cases where proxies may add additional Accept headers.
109 if req.headers.get(b'Accept') != FRAMINGTYPE:
109 if req.headers.get(b'Accept') != FRAMINGTYPE:
110 res.status = b'406 Not Acceptable'
110 res.status = b'406 Not Acceptable'
111 res.headers[b'Content-Type'] = b'text/plain'
111 res.headers[b'Content-Type'] = b'text/plain'
112 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
112 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
113 % FRAMINGTYPE)
113 % FRAMINGTYPE)
114 return
114 return
115
115
116 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
116 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
117 res.status = b'415 Unsupported Media Type'
117 res.status = b'415 Unsupported Media Type'
118 # TODO we should send a response with appropriate media type,
118 # TODO we should send a response with appropriate media type,
119 # since client does Accept it.
119 # since client does Accept it.
120 res.headers[b'Content-Type'] = b'text/plain'
120 res.headers[b'Content-Type'] = b'text/plain'
121 res.setbodybytes(_('client MUST send Content-Type header with '
121 res.setbodybytes(_('client MUST send Content-Type header with '
122 'value: %s\n') % FRAMINGTYPE)
122 'value: %s\n') % FRAMINGTYPE)
123 return
123 return
124
124
125 _processhttpv2request(ui, repo, req, res, permission, command, proto)
125 _processhttpv2request(ui, repo, req, res, permission, command, proto)
126
126
127 def _processhttpv2reflectrequest(ui, repo, req, res):
127 def _processhttpv2reflectrequest(ui, repo, req, res):
128 """Reads unified frame protocol request and dumps out state to client.
128 """Reads unified frame protocol request and dumps out state to client.
129
129
130 This special endpoint can be used to help debug the wire protocol.
130 This special endpoint can be used to help debug the wire protocol.
131
131
132 Instead of routing the request through the normal dispatch mechanism,
132 Instead of routing the request through the normal dispatch mechanism,
133 we instead read all frames, decode them, and feed them into our state
133 we instead read all frames, decode them, and feed them into our state
134 tracker. We then dump the log of all that activity back out to the
134 tracker. We then dump the log of all that activity back out to the
135 client.
135 client.
136 """
136 """
137 import json
137 import json
138
138
139 # Reflection APIs have a history of being abused, accidentally disclosing
139 # Reflection APIs have a history of being abused, accidentally disclosing
140 # sensitive data, etc. So we have a config knob.
140 # sensitive data, etc. So we have a config knob.
141 if not ui.configbool('experimental', 'web.api.debugreflect'):
141 if not ui.configbool('experimental', 'web.api.debugreflect'):
142 res.status = b'404 Not Found'
142 res.status = b'404 Not Found'
143 res.headers[b'Content-Type'] = b'text/plain'
143 res.headers[b'Content-Type'] = b'text/plain'
144 res.setbodybytes(_('debugreflect service not available'))
144 res.setbodybytes(_('debugreflect service not available'))
145 return
145 return
146
146
147 # We assume we have a unified framing protocol request body.
147 # We assume we have a unified framing protocol request body.
148
148
149 reactor = wireprotoframing.serverreactor()
149 reactor = wireprotoframing.serverreactor()
150 states = []
150 states = []
151
151
152 while True:
152 while True:
153 frame = wireprotoframing.readframe(req.bodyfh)
153 frame = wireprotoframing.readframe(req.bodyfh)
154
154
155 if not frame:
155 if not frame:
156 states.append(b'received: <no frame>')
156 states.append(b'received: <no frame>')
157 break
157 break
158
158
159 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
159 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
160 frame.requestid,
160 frame.requestid,
161 frame.payload))
161 frame.payload))
162
162
163 action, meta = reactor.onframerecv(frame)
163 action, meta = reactor.onframerecv(frame)
164 states.append(json.dumps((action, meta), sort_keys=True,
164 states.append(json.dumps((action, meta), sort_keys=True,
165 separators=(', ', ': ')))
165 separators=(', ', ': ')))
166
166
167 action, meta = reactor.oninputeof()
167 action, meta = reactor.oninputeof()
168 meta['action'] = action
168 meta['action'] = action
169 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
169 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
170
170
171 res.status = b'200 OK'
171 res.status = b'200 OK'
172 res.headers[b'Content-Type'] = b'text/plain'
172 res.headers[b'Content-Type'] = b'text/plain'
173 res.setbodybytes(b'\n'.join(states))
173 res.setbodybytes(b'\n'.join(states))
174
174
175 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
175 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
176 """Post-validation handler for HTTPv2 requests.
176 """Post-validation handler for HTTPv2 requests.
177
177
178 Called when the HTTP request contains unified frame-based protocol
178 Called when the HTTP request contains unified frame-based protocol
179 frames for evaluation.
179 frames for evaluation.
180 """
180 """
181 # TODO Some HTTP clients are full duplex and can receive data before
181 # TODO Some HTTP clients are full duplex and can receive data before
182 # the entire request is transmitted. Figure out a way to indicate support
182 # the entire request is transmitted. Figure out a way to indicate support
183 # for that so we can opt into full duplex mode.
183 # for that so we can opt into full duplex mode.
184 reactor = wireprotoframing.serverreactor(deferoutput=True)
184 reactor = wireprotoframing.serverreactor(deferoutput=True)
185 seencommand = False
185 seencommand = False
186
186
187 outstream = reactor.makeoutputstream()
187 outstream = reactor.makeoutputstream()
188
188
189 while True:
189 while True:
190 frame = wireprotoframing.readframe(req.bodyfh)
190 frame = wireprotoframing.readframe(req.bodyfh)
191 if not frame:
191 if not frame:
192 break
192 break
193
193
194 action, meta = reactor.onframerecv(frame)
194 action, meta = reactor.onframerecv(frame)
195
195
196 if action == 'wantframe':
196 if action == 'wantframe':
197 # Need more data before we can do anything.
197 # Need more data before we can do anything.
198 continue
198 continue
199 elif action == 'runcommand':
199 elif action == 'runcommand':
200 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
200 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
201 reqcommand, reactor, outstream,
201 reqcommand, reactor, outstream,
202 meta, issubsequent=seencommand)
202 meta, issubsequent=seencommand)
203
203
204 if sentoutput:
204 if sentoutput:
205 return
205 return
206
206
207 seencommand = True
207 seencommand = True
208
208
209 elif action == 'error':
209 elif action == 'error':
210 # TODO define proper error mechanism.
210 # TODO define proper error mechanism.
211 res.status = b'200 OK'
211 res.status = b'200 OK'
212 res.headers[b'Content-Type'] = b'text/plain'
212 res.headers[b'Content-Type'] = b'text/plain'
213 res.setbodybytes(meta['message'] + b'\n')
213 res.setbodybytes(meta['message'] + b'\n')
214 return
214 return
215 else:
215 else:
216 raise error.ProgrammingError(
216 raise error.ProgrammingError(
217 'unhandled action from frame processor: %s' % action)
217 'unhandled action from frame processor: %s' % action)
218
218
219 action, meta = reactor.oninputeof()
219 action, meta = reactor.oninputeof()
220 if action == 'sendframes':
220 if action == 'sendframes':
221 # We assume we haven't started sending the response yet. If we're
221 # We assume we haven't started sending the response yet. If we're
222 # wrong, the response type will raise an exception.
222 # wrong, the response type will raise an exception.
223 res.status = b'200 OK'
223 res.status = b'200 OK'
224 res.headers[b'Content-Type'] = FRAMINGTYPE
224 res.headers[b'Content-Type'] = FRAMINGTYPE
225 res.setbodygen(meta['framegen'])
225 res.setbodygen(meta['framegen'])
226 elif action == 'noop':
226 elif action == 'noop':
227 pass
227 pass
228 else:
228 else:
229 raise error.ProgrammingError('unhandled action from frame processor: %s'
229 raise error.ProgrammingError('unhandled action from frame processor: %s'
230 % action)
230 % action)
231
231
232 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
232 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
233 outstream, command, issubsequent):
233 outstream, command, issubsequent):
234 """Dispatch a wire protocol command made from HTTPv2 requests.
234 """Dispatch a wire protocol command made from HTTPv2 requests.
235
235
236 The authenticated permission (``authedperm``) along with the original
236 The authenticated permission (``authedperm``) along with the original
237 command from the URL (``reqcommand``) are passed in.
237 command from the URL (``reqcommand``) are passed in.
238 """
238 """
239 # We already validated that the session has permissions to perform the
239 # We already validated that the session has permissions to perform the
240 # actions in ``authedperm``. In the unified frame protocol, the canonical
240 # actions in ``authedperm``. In the unified frame protocol, the canonical
241 # command to run is expressed in a frame. However, the URL also requested
241 # command to run is expressed in a frame. However, the URL also requested
242 # to run a specific command. We need to be careful that the command we
242 # to run a specific command. We need to be careful that the command we
243 # run doesn't have permissions requirements greater than what was granted
243 # run doesn't have permissions requirements greater than what was granted
244 # by ``authedperm``.
244 # by ``authedperm``.
245 #
245 #
246 # Our rule for this is we only allow one command per HTTP request and
246 # Our rule for this is we only allow one command per HTTP request and
247 # that command must match the command in the URL. However, we make
247 # that command must match the command in the URL. However, we make
248 # an exception for the ``multirequest`` URL. This URL is allowed to
248 # an exception for the ``multirequest`` URL. This URL is allowed to
249 # execute multiple commands. We double check permissions of each command
249 # execute multiple commands. We double check permissions of each command
250 # as it is invoked to ensure there is no privilege escalation.
250 # as it is invoked to ensure there is no privilege escalation.
251 # TODO consider allowing multiple commands to regular command URLs
251 # TODO consider allowing multiple commands to regular command URLs
252 # iff each command is the same.
252 # iff each command is the same.
253
253
254 proto = httpv2protocolhandler(req, ui, args=command['args'])
254 proto = httpv2protocolhandler(req, ui, args=command['args'])
255
255
256 if reqcommand == b'multirequest':
256 if reqcommand == b'multirequest':
257 if not wireproto.commandsv2.commandavailable(command['command'], proto):
257 if not wireproto.commandsv2.commandavailable(command['command'], proto):
258 # TODO proper error mechanism
258 # TODO proper error mechanism
259 res.status = b'200 OK'
259 res.status = b'200 OK'
260 res.headers[b'Content-Type'] = b'text/plain'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('wire protocol command not available: %s') %
261 res.setbodybytes(_('wire protocol command not available: %s') %
262 command['command'])
262 command['command'])
263 return True
263 return True
264
264
265 # TODO don't use assert here, since it may be elided by -O.
265 # TODO don't use assert here, since it may be elided by -O.
266 assert authedperm in (b'ro', b'rw')
266 assert authedperm in (b'ro', b'rw')
267 wirecommand = wireproto.commandsv2[command['command']]
267 wirecommand = wireproto.commandsv2[command['command']]
268 assert wirecommand.permission in ('push', 'pull')
268 assert wirecommand.permission in ('push', 'pull')
269
269
270 if authedperm == b'ro' and wirecommand.permission != 'pull':
270 if authedperm == b'ro' and wirecommand.permission != 'pull':
271 # TODO proper error mechanism
271 # TODO proper error mechanism
272 res.status = b'403 Forbidden'
272 res.status = b'403 Forbidden'
273 res.headers[b'Content-Type'] = b'text/plain'
273 res.headers[b'Content-Type'] = b'text/plain'
274 res.setbodybytes(_('insufficient permissions to execute '
274 res.setbodybytes(_('insufficient permissions to execute '
275 'command: %s') % command['command'])
275 'command: %s') % command['command'])
276 return True
276 return True
277
277
278 # TODO should we also call checkperm() here? Maybe not if we're going
278 # TODO should we also call checkperm() here? Maybe not if we're going
279 # to overhaul that API. The granted scope from the URL check should
279 # to overhaul that API. The granted scope from the URL check should
280 # be good enough.
280 # be good enough.
281
281
282 else:
282 else:
283 # Don't allow multiple commands outside of ``multirequest`` URL.
283 # Don't allow multiple commands outside of ``multirequest`` URL.
284 if issubsequent:
284 if issubsequent:
285 # TODO proper error mechanism
285 # TODO proper error mechanism
286 res.status = b'200 OK'
286 res.status = b'200 OK'
287 res.headers[b'Content-Type'] = b'text/plain'
287 res.headers[b'Content-Type'] = b'text/plain'
288 res.setbodybytes(_('multiple commands cannot be issued to this '
288 res.setbodybytes(_('multiple commands cannot be issued to this '
289 'URL'))
289 'URL'))
290 return True
290 return True
291
291
292 if reqcommand != command['command']:
292 if reqcommand != command['command']:
293 # TODO define proper error mechanism
293 # TODO define proper error mechanism
294 res.status = b'200 OK'
294 res.status = b'200 OK'
295 res.headers[b'Content-Type'] = b'text/plain'
295 res.headers[b'Content-Type'] = b'text/plain'
296 res.setbodybytes(_('command in frame must match command in URL'))
296 res.setbodybytes(_('command in frame must match command in URL'))
297 return True
297 return True
298
298
299 rsp = wireproto.dispatch(repo, proto, command['command'])
299 rsp = wireproto.dispatch(repo, proto, command['command'])
300
300
301 res.status = b'200 OK'
301 res.status = b'200 OK'
302 res.headers[b'Content-Type'] = FRAMINGTYPE
302 res.headers[b'Content-Type'] = FRAMINGTYPE
303
303
304 if isinstance(rsp, wireprototypes.bytesresponse):
304 if isinstance(rsp, wireprototypes.bytesresponse):
305 action, meta = reactor.oncommandresponseready(outstream,
305 action, meta = reactor.oncommandresponseready(outstream,
306 command['requestid'],
306 command['requestid'],
307 rsp.data)
307 rsp.data)
308 elif isinstance(rsp, wireprototypes.cborresponse):
308 elif isinstance(rsp, wireprototypes.cborresponse):
309 encoded = cbor.dumps(rsp.value, canonical=True)
309 encoded = cbor.dumps(rsp.value, canonical=True)
310 action, meta = reactor.oncommandresponseready(outstream,
310 action, meta = reactor.oncommandresponseready(outstream,
311 command['requestid'],
311 command['requestid'],
312 encoded)
312 encoded)
313 else:
313 else:
314 action, meta = reactor.onapplicationerror(
314 action, meta = reactor.onservererror(
315 _('unhandled response type from wire proto command'))
315 _('unhandled response type from wire proto command'))
316
316
317 if action == 'sendframes':
317 if action == 'sendframes':
318 res.setbodygen(meta['framegen'])
318 res.setbodygen(meta['framegen'])
319 return True
319 return True
320 elif action == 'noop':
320 elif action == 'noop':
321 return False
321 return False
322 else:
322 else:
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
324 action)
324 action)
325
325
326 @zi.implementer(wireprototypes.baseprotocolhandler)
326 @zi.implementer(wireprototypes.baseprotocolhandler)
327 class httpv2protocolhandler(object):
327 class httpv2protocolhandler(object):
328 def __init__(self, req, ui, args=None):
328 def __init__(self, req, ui, args=None):
329 self._req = req
329 self._req = req
330 self._ui = ui
330 self._ui = ui
331 self._args = args
331 self._args = args
332
332
333 @property
333 @property
334 def name(self):
334 def name(self):
335 return HTTP_WIREPROTO_V2
335 return HTTP_WIREPROTO_V2
336
336
337 def getargs(self, args):
337 def getargs(self, args):
338 data = {}
338 data = {}
339 for k, typ in args.items():
339 for k, typ in args.items():
340 if k == '*':
340 if k == '*':
341 raise NotImplementedError('do not support * args')
341 raise NotImplementedError('do not support * args')
342 elif k in self._args:
342 elif k in self._args:
343 # TODO consider validating value types.
343 # TODO consider validating value types.
344 data[k] = self._args[k]
344 data[k] = self._args[k]
345
345
346 return data
346 return data
347
347
348 def getprotocaps(self):
348 def getprotocaps(self):
349 # Protocol capabilities are currently not implemented for HTTP V2.
349 # Protocol capabilities are currently not implemented for HTTP V2.
350 return set()
350 return set()
351
351
352 def getpayload(self):
352 def getpayload(self):
353 raise NotImplementedError
353 raise NotImplementedError
354
354
355 @contextlib.contextmanager
355 @contextlib.contextmanager
356 def mayberedirectstdio(self):
356 def mayberedirectstdio(self):
357 raise NotImplementedError
357 raise NotImplementedError
358
358
359 def client(self):
359 def client(self):
360 raise NotImplementedError
360 raise NotImplementedError
361
361
362 def addcapabilities(self, repo, caps):
362 def addcapabilities(self, repo, caps):
363 return caps
363 return caps
364
364
365 def checkperm(self, perm):
365 def checkperm(self, perm):
366 raise NotImplementedError
366 raise NotImplementedError
367
367
368 def httpv2apidescriptor(req, repo):
368 def httpv2apidescriptor(req, repo):
369 proto = httpv2protocolhandler(req, repo.ui)
369 proto = httpv2protocolhandler(req, repo.ui)
370
370
371 return _capabilitiesv2(repo, proto)
371 return _capabilitiesv2(repo, proto)
372
372
373 def _capabilitiesv2(repo, proto):
373 def _capabilitiesv2(repo, proto):
374 """Obtain the set of capabilities for version 2 transports.
374 """Obtain the set of capabilities for version 2 transports.
375
375
376 These capabilities are distinct from the capabilities for version 1
376 These capabilities are distinct from the capabilities for version 1
377 transports.
377 transports.
378 """
378 """
379 compression = []
379 compression = []
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
381 compression.append({
381 compression.append({
382 b'name': engine.wireprotosupport().name,
382 b'name': engine.wireprotosupport().name,
383 })
383 })
384
384
385 caps = {
385 caps = {
386 'commands': {},
386 'commands': {},
387 'compression': compression,
387 'compression': compression,
388 'framingmediatypes': [FRAMINGTYPE],
388 'framingmediatypes': [FRAMINGTYPE],
389 }
389 }
390
390
391 for command, entry in wireproto.commandsv2.items():
391 for command, entry in wireproto.commandsv2.items():
392 caps['commands'][command] = {
392 caps['commands'][command] = {
393 'args': entry.args,
393 'args': entry.args,
394 'permissions': [entry.permission],
394 'permissions': [entry.permission],
395 }
395 }
396
396
397 if streamclone.allowservergeneration(repo):
397 if streamclone.allowservergeneration(repo):
398 caps['rawrepoformats'] = sorted(repo.requirements &
398 caps['rawrepoformats'] = sorted(repo.requirements &
399 repo.supportedformats)
399 repo.supportedformats)
400
400
401 return proto.addcapabilities(repo, caps)
401 return proto.addcapabilities(repo, caps)
402
402
403 def wireprotocommand(*args, **kwargs):
403 def wireprotocommand(*args, **kwargs):
404 def register(func):
404 def register(func):
405 return wireproto.wireprotocommand(
405 return wireproto.wireprotocommand(
406 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
406 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
407
407
408 return register
408 return register
409
409
410 @wireprotocommand('branchmap', permission='pull')
410 @wireprotocommand('branchmap', permission='pull')
411 def branchmapv2(repo, proto):
411 def branchmapv2(repo, proto):
412 branchmap = {encoding.fromlocal(k): v
412 branchmap = {encoding.fromlocal(k): v
413 for k, v in repo.branchmap().iteritems()}
413 for k, v in repo.branchmap().iteritems()}
414
414
415 return wireprototypes.cborresponse(branchmap)
415 return wireprototypes.cborresponse(branchmap)
416
416
417 @wireprotocommand('capabilities', permission='pull')
417 @wireprotocommand('capabilities', permission='pull')
418 def capabilitiesv2(repo, proto):
418 def capabilitiesv2(repo, proto):
419 caps = _capabilitiesv2(repo, proto)
419 caps = _capabilitiesv2(repo, proto)
420
420
421 return wireprototypes.cborresponse(caps)
421 return wireprototypes.cborresponse(caps)
422
422
423 @wireprotocommand('heads',
423 @wireprotocommand('heads',
424 args={
424 args={
425 'publiconly': False,
425 'publiconly': False,
426 },
426 },
427 permission='pull')
427 permission='pull')
428 def headsv2(repo, proto, publiconly=False):
428 def headsv2(repo, proto, publiconly=False):
429 if publiconly:
429 if publiconly:
430 repo = repo.filtered('immutable')
430 repo = repo.filtered('immutable')
431
431
432 return wireprototypes.cborresponse(repo.heads())
432 return wireprototypes.cborresponse(repo.heads())
433
433
434 @wireprotocommand('known',
434 @wireprotocommand('known',
435 args={
435 args={
436 'nodes': [b'deadbeef'],
436 'nodes': [b'deadbeef'],
437 },
437 },
438 permission='pull')
438 permission='pull')
439 def knownv2(repo, proto, nodes=None):
439 def knownv2(repo, proto, nodes=None):
440 nodes = nodes or []
440 nodes = nodes or []
441 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
441 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
442 return wireprototypes.cborresponse(result)
442 return wireprototypes.cborresponse(result)
443
443
444 @wireprotocommand('listkeys',
444 @wireprotocommand('listkeys',
445 args={
445 args={
446 'namespace': b'ns',
446 'namespace': b'ns',
447 },
447 },
448 permission='pull')
448 permission='pull')
449 def listkeysv2(repo, proto, namespace=None):
449 def listkeysv2(repo, proto, namespace=None):
450 keys = repo.listkeys(encoding.tolocal(namespace))
450 keys = repo.listkeys(encoding.tolocal(namespace))
451 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
451 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
452 for k, v in keys.iteritems()}
452 for k, v in keys.iteritems()}
453
453
454 return wireprototypes.cborresponse(keys)
454 return wireprototypes.cborresponse(keys)
455
455
456 @wireprotocommand('lookup',
456 @wireprotocommand('lookup',
457 args={
457 args={
458 'key': b'foo',
458 'key': b'foo',
459 },
459 },
460 permission='pull')
460 permission='pull')
461 def lookupv2(repo, proto, key):
461 def lookupv2(repo, proto, key):
462 key = encoding.tolocal(key)
462 key = encoding.tolocal(key)
463
463
464 # TODO handle exception.
464 # TODO handle exception.
465 node = repo.lookup(key)
465 node = repo.lookup(key)
466
466
467 return wireprototypes.cborresponse(node)
467 return wireprototypes.cborresponse(node)
468
468
469 @wireprotocommand('pushkey',
469 @wireprotocommand('pushkey',
470 args={
470 args={
471 'namespace': b'ns',
471 'namespace': b'ns',
472 'key': b'key',
472 'key': b'key',
473 'old': b'old',
473 'old': b'old',
474 'new': b'new',
474 'new': b'new',
475 },
475 },
476 permission='push')
476 permission='push')
477 def pushkeyv2(repo, proto, namespace, key, old, new):
477 def pushkeyv2(repo, proto, namespace, key, old, new):
478 # TODO handle ui output redirection
478 # TODO handle ui output redirection
479 r = repo.pushkey(encoding.tolocal(namespace),
479 r = repo.pushkey(encoding.tolocal(namespace),
480 encoding.tolocal(key),
480 encoding.tolocal(key),
481 encoding.tolocal(old),
481 encoding.tolocal(old),
482 encoding.tolocal(new))
482 encoding.tolocal(new))
483
483
484 return wireprototypes.cborresponse(r)
484 return wireprototypes.cborresponse(r)
@@ -1,488 +1,490 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import unittest
3 import unittest
4
4
5 from mercurial.thirdparty import (
5 from mercurial.thirdparty import (
6 cbor,
6 cbor,
7 )
7 )
8 from mercurial import (
8 from mercurial import (
9 util,
9 util,
10 wireprotoframing as framing,
10 wireprotoframing as framing,
11 )
11 )
12
12
13 ffs = framing.makeframefromhumanstring
13 ffs = framing.makeframefromhumanstring
14
14
15 OK = cbor.dumps({b'status': b'ok'})
15 OK = cbor.dumps({b'status': b'ok'})
16
16
17 def makereactor(deferoutput=False):
17 def makereactor(deferoutput=False):
18 return framing.serverreactor(deferoutput=deferoutput)
18 return framing.serverreactor(deferoutput=deferoutput)
19
19
20 def sendframes(reactor, gen):
20 def sendframes(reactor, gen):
21 """Send a generator of frame bytearray to a reactor.
21 """Send a generator of frame bytearray to a reactor.
22
22
23 Emits a generator of results from ``onframerecv()`` calls.
23 Emits a generator of results from ``onframerecv()`` calls.
24 """
24 """
25 for frame in gen:
25 for frame in gen:
26 header = framing.parseheader(frame)
26 header = framing.parseheader(frame)
27 payload = frame[framing.FRAME_HEADER_SIZE:]
27 payload = frame[framing.FRAME_HEADER_SIZE:]
28 assert len(payload) == header.length
28 assert len(payload) == header.length
29
29
30 yield reactor.onframerecv(framing.frame(header.requestid,
30 yield reactor.onframerecv(framing.frame(header.requestid,
31 header.streamid,
31 header.streamid,
32 header.streamflags,
32 header.streamflags,
33 header.typeid,
33 header.typeid,
34 header.flags,
34 header.flags,
35 payload))
35 payload))
36
36
37 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
37 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
38 """Generate frames to run a command and send them to a reactor."""
38 """Generate frames to run a command and send them to a reactor."""
39 return sendframes(reactor,
39 return sendframes(reactor,
40 framing.createcommandframes(stream, rid, cmd, args,
40 framing.createcommandframes(stream, rid, cmd, args,
41 datafh))
41 datafh))
42
42
43
43
44 class ServerReactorTests(unittest.TestCase):
44 class ServerReactorTests(unittest.TestCase):
45 def _sendsingleframe(self, reactor, f):
45 def _sendsingleframe(self, reactor, f):
46 results = list(sendframes(reactor, [f]))
46 results = list(sendframes(reactor, [f]))
47 self.assertEqual(len(results), 1)
47 self.assertEqual(len(results), 1)
48
48
49 return results[0]
49 return results[0]
50
50
51 def assertaction(self, res, expected):
51 def assertaction(self, res, expected):
52 self.assertIsInstance(res, tuple)
52 self.assertIsInstance(res, tuple)
53 self.assertEqual(len(res), 2)
53 self.assertEqual(len(res), 2)
54 self.assertIsInstance(res[1], dict)
54 self.assertIsInstance(res[1], dict)
55 self.assertEqual(res[0], expected)
55 self.assertEqual(res[0], expected)
56
56
57 def assertframesequal(self, frames, framestrings):
57 def assertframesequal(self, frames, framestrings):
58 expected = [ffs(s) for s in framestrings]
58 expected = [ffs(s) for s in framestrings]
59 self.assertEqual(list(frames), expected)
59 self.assertEqual(list(frames), expected)
60
60
61 def test1framecommand(self):
61 def test1framecommand(self):
62 """Receiving a command in a single frame yields request to run it."""
62 """Receiving a command in a single frame yields request to run it."""
63 reactor = makereactor()
63 reactor = makereactor()
64 stream = framing.stream(1)
64 stream = framing.stream(1)
65 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
65 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
66 self.assertEqual(len(results), 1)
66 self.assertEqual(len(results), 1)
67 self.assertaction(results[0], b'runcommand')
67 self.assertaction(results[0], b'runcommand')
68 self.assertEqual(results[0][1], {
68 self.assertEqual(results[0][1], {
69 b'requestid': 1,
69 b'requestid': 1,
70 b'command': b'mycommand',
70 b'command': b'mycommand',
71 b'args': {},
71 b'args': {},
72 b'data': None,
72 b'data': None,
73 })
73 })
74
74
75 result = reactor.oninputeof()
75 result = reactor.oninputeof()
76 self.assertaction(result, b'noop')
76 self.assertaction(result, b'noop')
77
77
78 def test1argument(self):
78 def test1argument(self):
79 reactor = makereactor()
79 reactor = makereactor()
80 stream = framing.stream(1)
80 stream = framing.stream(1)
81 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
81 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
82 {b'foo': b'bar'}))
82 {b'foo': b'bar'}))
83 self.assertEqual(len(results), 1)
83 self.assertEqual(len(results), 1)
84 self.assertaction(results[0], b'runcommand')
84 self.assertaction(results[0], b'runcommand')
85 self.assertEqual(results[0][1], {
85 self.assertEqual(results[0][1], {
86 b'requestid': 41,
86 b'requestid': 41,
87 b'command': b'mycommand',
87 b'command': b'mycommand',
88 b'args': {b'foo': b'bar'},
88 b'args': {b'foo': b'bar'},
89 b'data': None,
89 b'data': None,
90 })
90 })
91
91
92 def testmultiarguments(self):
92 def testmultiarguments(self):
93 reactor = makereactor()
93 reactor = makereactor()
94 stream = framing.stream(1)
94 stream = framing.stream(1)
95 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
95 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
96 {b'foo': b'bar', b'biz': b'baz'}))
96 {b'foo': b'bar', b'biz': b'baz'}))
97 self.assertEqual(len(results), 1)
97 self.assertEqual(len(results), 1)
98 self.assertaction(results[0], b'runcommand')
98 self.assertaction(results[0], b'runcommand')
99 self.assertEqual(results[0][1], {
99 self.assertEqual(results[0][1], {
100 b'requestid': 1,
100 b'requestid': 1,
101 b'command': b'mycommand',
101 b'command': b'mycommand',
102 b'args': {b'foo': b'bar', b'biz': b'baz'},
102 b'args': {b'foo': b'bar', b'biz': b'baz'},
103 b'data': None,
103 b'data': None,
104 })
104 })
105
105
106 def testsimplecommanddata(self):
106 def testsimplecommanddata(self):
107 reactor = makereactor()
107 reactor = makereactor()
108 stream = framing.stream(1)
108 stream = framing.stream(1)
109 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
109 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
110 util.bytesio(b'data!')))
110 util.bytesio(b'data!')))
111 self.assertEqual(len(results), 2)
111 self.assertEqual(len(results), 2)
112 self.assertaction(results[0], b'wantframe')
112 self.assertaction(results[0], b'wantframe')
113 self.assertaction(results[1], b'runcommand')
113 self.assertaction(results[1], b'runcommand')
114 self.assertEqual(results[1][1], {
114 self.assertEqual(results[1][1], {
115 b'requestid': 1,
115 b'requestid': 1,
116 b'command': b'mycommand',
116 b'command': b'mycommand',
117 b'args': {},
117 b'args': {},
118 b'data': b'data!',
118 b'data': b'data!',
119 })
119 })
120
120
121 def testmultipledataframes(self):
121 def testmultipledataframes(self):
122 frames = [
122 frames = [
123 ffs(b'1 1 stream-begin command-request new|have-data '
123 ffs(b'1 1 stream-begin command-request new|have-data '
124 b"cbor:{b'name': b'mycommand'}"),
124 b"cbor:{b'name': b'mycommand'}"),
125 ffs(b'1 1 0 command-data continuation data1'),
125 ffs(b'1 1 0 command-data continuation data1'),
126 ffs(b'1 1 0 command-data continuation data2'),
126 ffs(b'1 1 0 command-data continuation data2'),
127 ffs(b'1 1 0 command-data eos data3'),
127 ffs(b'1 1 0 command-data eos data3'),
128 ]
128 ]
129
129
130 reactor = makereactor()
130 reactor = makereactor()
131 results = list(sendframes(reactor, frames))
131 results = list(sendframes(reactor, frames))
132 self.assertEqual(len(results), 4)
132 self.assertEqual(len(results), 4)
133 for i in range(3):
133 for i in range(3):
134 self.assertaction(results[i], b'wantframe')
134 self.assertaction(results[i], b'wantframe')
135 self.assertaction(results[3], b'runcommand')
135 self.assertaction(results[3], b'runcommand')
136 self.assertEqual(results[3][1], {
136 self.assertEqual(results[3][1], {
137 b'requestid': 1,
137 b'requestid': 1,
138 b'command': b'mycommand',
138 b'command': b'mycommand',
139 b'args': {},
139 b'args': {},
140 b'data': b'data1data2data3',
140 b'data': b'data1data2data3',
141 })
141 })
142
142
143 def testargumentanddata(self):
143 def testargumentanddata(self):
144 frames = [
144 frames = [
145 ffs(b'1 1 stream-begin command-request new|have-data '
145 ffs(b'1 1 stream-begin command-request new|have-data '
146 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
146 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
147 b"b'foo': b'bar'}}"),
147 b"b'foo': b'bar'}}"),
148 ffs(b'1 1 0 command-data continuation value1'),
148 ffs(b'1 1 0 command-data continuation value1'),
149 ffs(b'1 1 0 command-data eos value2'),
149 ffs(b'1 1 0 command-data eos value2'),
150 ]
150 ]
151
151
152 reactor = makereactor()
152 reactor = makereactor()
153 results = list(sendframes(reactor, frames))
153 results = list(sendframes(reactor, frames))
154
154
155 self.assertaction(results[-1], b'runcommand')
155 self.assertaction(results[-1], b'runcommand')
156 self.assertEqual(results[-1][1], {
156 self.assertEqual(results[-1][1], {
157 b'requestid': 1,
157 b'requestid': 1,
158 b'command': b'command',
158 b'command': b'command',
159 b'args': {
159 b'args': {
160 b'key': b'val',
160 b'key': b'val',
161 b'foo': b'bar',
161 b'foo': b'bar',
162 },
162 },
163 b'data': b'value1value2',
163 b'data': b'value1value2',
164 })
164 })
165
165
166 def testnewandcontinuation(self):
166 def testnewandcontinuation(self):
167 result = self._sendsingleframe(makereactor(),
167 result = self._sendsingleframe(makereactor(),
168 ffs(b'1 1 stream-begin command-request new|continuation '))
168 ffs(b'1 1 stream-begin command-request new|continuation '))
169 self.assertaction(result, b'error')
169 self.assertaction(result, b'error')
170 self.assertEqual(result[1], {
170 self.assertEqual(result[1], {
171 b'message': b'received command request frame with both new and '
171 b'message': b'received command request frame with both new and '
172 b'continuation flags set',
172 b'continuation flags set',
173 })
173 })
174
174
175 def testneithernewnorcontinuation(self):
175 def testneithernewnorcontinuation(self):
176 result = self._sendsingleframe(makereactor(),
176 result = self._sendsingleframe(makereactor(),
177 ffs(b'1 1 stream-begin command-request 0 '))
177 ffs(b'1 1 stream-begin command-request 0 '))
178 self.assertaction(result, b'error')
178 self.assertaction(result, b'error')
179 self.assertEqual(result[1], {
179 self.assertEqual(result[1], {
180 b'message': b'received command request frame with neither new nor '
180 b'message': b'received command request frame with neither new nor '
181 b'continuation flags set',
181 b'continuation flags set',
182 })
182 })
183
183
184 def testunexpectedcommanddata(self):
184 def testunexpectedcommanddata(self):
185 """Command data frame when not running a command is an error."""
185 """Command data frame when not running a command is an error."""
186 result = self._sendsingleframe(makereactor(),
186 result = self._sendsingleframe(makereactor(),
187 ffs(b'1 1 stream-begin command-data 0 ignored'))
187 ffs(b'1 1 stream-begin command-data 0 ignored'))
188 self.assertaction(result, b'error')
188 self.assertaction(result, b'error')
189 self.assertEqual(result[1], {
189 self.assertEqual(result[1], {
190 b'message': b'expected command request frame; got 2',
190 b'message': b'expected command request frame; got 2',
191 })
191 })
192
192
193 def testunexpectedcommanddatareceiving(self):
193 def testunexpectedcommanddatareceiving(self):
194 """Same as above except the command is receiving."""
194 """Same as above except the command is receiving."""
195 results = list(sendframes(makereactor(), [
195 results = list(sendframes(makereactor(), [
196 ffs(b'1 1 stream-begin command-request new|more '
196 ffs(b'1 1 stream-begin command-request new|more '
197 b"cbor:{b'name': b'ignored'}"),
197 b"cbor:{b'name': b'ignored'}"),
198 ffs(b'1 1 0 command-data eos ignored'),
198 ffs(b'1 1 0 command-data eos ignored'),
199 ]))
199 ]))
200
200
201 self.assertaction(results[0], b'wantframe')
201 self.assertaction(results[0], b'wantframe')
202 self.assertaction(results[1], b'error')
202 self.assertaction(results[1], b'error')
203 self.assertEqual(results[1][1], {
203 self.assertEqual(results[1][1], {
204 b'message': b'received command data frame for request that is not '
204 b'message': b'received command data frame for request that is not '
205 b'expecting data: 1',
205 b'expecting data: 1',
206 })
206 })
207
207
208 def testconflictingrequestidallowed(self):
208 def testconflictingrequestidallowed(self):
209 """Multiple fully serviced commands with same request ID is allowed."""
209 """Multiple fully serviced commands with same request ID is allowed."""
210 reactor = makereactor()
210 reactor = makereactor()
211 results = []
211 results = []
212 outstream = reactor.makeoutputstream()
212 outstream = reactor.makeoutputstream()
213 results.append(self._sendsingleframe(
213 results.append(self._sendsingleframe(
214 reactor, ffs(b'1 1 stream-begin command-request new '
214 reactor, ffs(b'1 1 stream-begin command-request new '
215 b"cbor:{b'name': b'command'}")))
215 b"cbor:{b'name': b'command'}")))
216 result = reactor.oncommandresponseready(outstream, 1, b'response1')
216 result = reactor.oncommandresponseready(outstream, 1, b'response1')
217 self.assertaction(result, b'sendframes')
217 self.assertaction(result, b'sendframes')
218 list(result[1][b'framegen'])
218 list(result[1][b'framegen'])
219 results.append(self._sendsingleframe(
219 results.append(self._sendsingleframe(
220 reactor, ffs(b'1 1 stream-begin command-request new '
220 reactor, ffs(b'1 1 stream-begin command-request new '
221 b"cbor:{b'name': b'command'}")))
221 b"cbor:{b'name': b'command'}")))
222 result = reactor.oncommandresponseready(outstream, 1, b'response2')
222 result = reactor.oncommandresponseready(outstream, 1, b'response2')
223 self.assertaction(result, b'sendframes')
223 self.assertaction(result, b'sendframes')
224 list(result[1][b'framegen'])
224 list(result[1][b'framegen'])
225 results.append(self._sendsingleframe(
225 results.append(self._sendsingleframe(
226 reactor, ffs(b'1 1 stream-begin command-request new '
226 reactor, ffs(b'1 1 stream-begin command-request new '
227 b"cbor:{b'name': b'command'}")))
227 b"cbor:{b'name': b'command'}")))
228 result = reactor.oncommandresponseready(outstream, 1, b'response3')
228 result = reactor.oncommandresponseready(outstream, 1, b'response3')
229 self.assertaction(result, b'sendframes')
229 self.assertaction(result, b'sendframes')
230 list(result[1][b'framegen'])
230 list(result[1][b'framegen'])
231
231
232 for i in range(3):
232 for i in range(3):
233 self.assertaction(results[i], b'runcommand')
233 self.assertaction(results[i], b'runcommand')
234 self.assertEqual(results[i][1], {
234 self.assertEqual(results[i][1], {
235 b'requestid': 1,
235 b'requestid': 1,
236 b'command': b'command',
236 b'command': b'command',
237 b'args': {},
237 b'args': {},
238 b'data': None,
238 b'data': None,
239 })
239 })
240
240
241 def testconflictingrequestid(self):
241 def testconflictingrequestid(self):
242 """Request ID for new command matching in-flight command is illegal."""
242 """Request ID for new command matching in-flight command is illegal."""
243 results = list(sendframes(makereactor(), [
243 results = list(sendframes(makereactor(), [
244 ffs(b'1 1 stream-begin command-request new|more '
244 ffs(b'1 1 stream-begin command-request new|more '
245 b"cbor:{b'name': b'command'}"),
245 b"cbor:{b'name': b'command'}"),
246 ffs(b'1 1 0 command-request new '
246 ffs(b'1 1 0 command-request new '
247 b"cbor:{b'name': b'command1'}"),
247 b"cbor:{b'name': b'command1'}"),
248 ]))
248 ]))
249
249
250 self.assertaction(results[0], b'wantframe')
250 self.assertaction(results[0], b'wantframe')
251 self.assertaction(results[1], b'error')
251 self.assertaction(results[1], b'error')
252 self.assertEqual(results[1][1], {
252 self.assertEqual(results[1][1], {
253 b'message': b'request with ID 1 already received',
253 b'message': b'request with ID 1 already received',
254 })
254 })
255
255
256 def testinterleavedcommands(self):
256 def testinterleavedcommands(self):
257 cbor1 = cbor.dumps({
257 cbor1 = cbor.dumps({
258 b'name': b'command1',
258 b'name': b'command1',
259 b'args': {
259 b'args': {
260 b'foo': b'bar',
260 b'foo': b'bar',
261 b'key1': b'val',
261 b'key1': b'val',
262 }
262 }
263 }, canonical=True)
263 }, canonical=True)
264 cbor3 = cbor.dumps({
264 cbor3 = cbor.dumps({
265 b'name': b'command3',
265 b'name': b'command3',
266 b'args': {
266 b'args': {
267 b'biz': b'baz',
267 b'biz': b'baz',
268 b'key': b'val',
268 b'key': b'val',
269 },
269 },
270 }, canonical=True)
270 }, canonical=True)
271
271
272 results = list(sendframes(makereactor(), [
272 results = list(sendframes(makereactor(), [
273 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
273 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
274 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
274 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
275 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
275 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
276 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
276 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
277 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
277 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
278 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
278 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
279 ]))
279 ]))
280
280
281 self.assertEqual([t[0] for t in results], [
281 self.assertEqual([t[0] for t in results], [
282 b'wantframe',
282 b'wantframe',
283 b'wantframe',
283 b'wantframe',
284 b'wantframe',
284 b'wantframe',
285 b'wantframe',
285 b'wantframe',
286 b'runcommand',
286 b'runcommand',
287 b'runcommand',
287 b'runcommand',
288 ])
288 ])
289
289
290 self.assertEqual(results[4][1], {
290 self.assertEqual(results[4][1], {
291 b'requestid': 3,
291 b'requestid': 3,
292 b'command': b'command3',
292 b'command': b'command3',
293 b'args': {b'biz': b'baz', b'key': b'val'},
293 b'args': {b'biz': b'baz', b'key': b'val'},
294 b'data': None,
294 b'data': None,
295 })
295 })
296 self.assertEqual(results[5][1], {
296 self.assertEqual(results[5][1], {
297 b'requestid': 1,
297 b'requestid': 1,
298 b'command': b'command1',
298 b'command': b'command1',
299 b'args': {b'foo': b'bar', b'key1': b'val'},
299 b'args': {b'foo': b'bar', b'key1': b'val'},
300 b'data': None,
300 b'data': None,
301 })
301 })
302
302
303 def testmissingcommanddataframe(self):
303 def testmissingcommanddataframe(self):
304 # The reactor doesn't currently handle partially received commands.
304 # The reactor doesn't currently handle partially received commands.
305 # So this test is failing to do anything with request 1.
305 # So this test is failing to do anything with request 1.
306 frames = [
306 frames = [
307 ffs(b'1 1 stream-begin command-request new|have-data '
307 ffs(b'1 1 stream-begin command-request new|have-data '
308 b"cbor:{b'name': b'command1'}"),
308 b"cbor:{b'name': b'command1'}"),
309 ffs(b'3 1 0 command-request new '
309 ffs(b'3 1 0 command-request new '
310 b"cbor:{b'name': b'command2'}"),
310 b"cbor:{b'name': b'command2'}"),
311 ]
311 ]
312 results = list(sendframes(makereactor(), frames))
312 results = list(sendframes(makereactor(), frames))
313 self.assertEqual(len(results), 2)
313 self.assertEqual(len(results), 2)
314 self.assertaction(results[0], b'wantframe')
314 self.assertaction(results[0], b'wantframe')
315 self.assertaction(results[1], b'runcommand')
315 self.assertaction(results[1], b'runcommand')
316
316
317 def testmissingcommanddataframeflags(self):
317 def testmissingcommanddataframeflags(self):
318 frames = [
318 frames = [
319 ffs(b'1 1 stream-begin command-request new|have-data '
319 ffs(b'1 1 stream-begin command-request new|have-data '
320 b"cbor:{b'name': b'command1'}"),
320 b"cbor:{b'name': b'command1'}"),
321 ffs(b'1 1 0 command-data 0 data'),
321 ffs(b'1 1 0 command-data 0 data'),
322 ]
322 ]
323 results = list(sendframes(makereactor(), frames))
323 results = list(sendframes(makereactor(), frames))
324 self.assertEqual(len(results), 2)
324 self.assertEqual(len(results), 2)
325 self.assertaction(results[0], b'wantframe')
325 self.assertaction(results[0], b'wantframe')
326 self.assertaction(results[1], b'error')
326 self.assertaction(results[1], b'error')
327 self.assertEqual(results[1][1], {
327 self.assertEqual(results[1][1], {
328 b'message': b'command data frame without flags',
328 b'message': b'command data frame without flags',
329 })
329 })
330
330
331 def testframefornonreceivingrequest(self):
331 def testframefornonreceivingrequest(self):
332 """Receiving a frame for a command that is not receiving is illegal."""
332 """Receiving a frame for a command that is not receiving is illegal."""
333 results = list(sendframes(makereactor(), [
333 results = list(sendframes(makereactor(), [
334 ffs(b'1 1 stream-begin command-request new '
334 ffs(b'1 1 stream-begin command-request new '
335 b"cbor:{b'name': b'command1'}"),
335 b"cbor:{b'name': b'command1'}"),
336 ffs(b'3 1 0 command-request new|have-data '
336 ffs(b'3 1 0 command-request new|have-data '
337 b"cbor:{b'name': b'command3'}"),
337 b"cbor:{b'name': b'command3'}"),
338 ffs(b'5 1 0 command-data eos ignored'),
338 ffs(b'5 1 0 command-data eos ignored'),
339 ]))
339 ]))
340 self.assertaction(results[2], b'error')
340 self.assertaction(results[2], b'error')
341 self.assertEqual(results[2][1], {
341 self.assertEqual(results[2][1], {
342 b'message': b'received frame for request that is not receiving: 5',
342 b'message': b'received frame for request that is not receiving: 5',
343 })
343 })
344
344
345 def testsimpleresponse(self):
345 def testsimpleresponse(self):
346 """Bytes response to command sends result frames."""
346 """Bytes response to command sends result frames."""
347 reactor = makereactor()
347 reactor = makereactor()
348 instream = framing.stream(1)
348 instream = framing.stream(1)
349 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
349 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
350
350
351 outstream = reactor.makeoutputstream()
351 outstream = reactor.makeoutputstream()
352 result = reactor.oncommandresponseready(outstream, 1, b'response')
352 result = reactor.oncommandresponseready(outstream, 1, b'response')
353 self.assertaction(result, b'sendframes')
353 self.assertaction(result, b'sendframes')
354 self.assertframesequal(result[1][b'framegen'], [
354 self.assertframesequal(result[1][b'framegen'], [
355 b'1 2 stream-begin command-response eos %sresponse' % OK,
355 b'1 2 stream-begin command-response eos %sresponse' % OK,
356 ])
356 ])
357
357
358 def testmultiframeresponse(self):
358 def testmultiframeresponse(self):
359 """Bytes response spanning multiple frames is handled."""
359 """Bytes response spanning multiple frames is handled."""
360 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
360 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
361 second = b'y' * 100
361 second = b'y' * 100
362
362
363 reactor = makereactor()
363 reactor = makereactor()
364 instream = framing.stream(1)
364 instream = framing.stream(1)
365 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
365 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
366
366
367 outstream = reactor.makeoutputstream()
367 outstream = reactor.makeoutputstream()
368 result = reactor.oncommandresponseready(outstream, 1, first + second)
368 result = reactor.oncommandresponseready(outstream, 1, first + second)
369 self.assertaction(result, b'sendframes')
369 self.assertaction(result, b'sendframes')
370 self.assertframesequal(result[1][b'framegen'], [
370 self.assertframesequal(result[1][b'framegen'], [
371 b'1 2 stream-begin command-response continuation %s' % OK,
371 b'1 2 stream-begin command-response continuation %s' % OK,
372 b'1 2 0 command-response continuation %s' % first,
372 b'1 2 0 command-response continuation %s' % first,
373 b'1 2 0 command-response eos %s' % second,
373 b'1 2 0 command-response eos %s' % second,
374 ])
374 ])
375
375
376 def testapplicationerror(self):
376 def testservererror(self):
377 reactor = makereactor()
377 reactor = makereactor()
378 instream = framing.stream(1)
378 instream = framing.stream(1)
379 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
379 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
380
380
381 outstream = reactor.makeoutputstream()
381 outstream = reactor.makeoutputstream()
382 result = reactor.onapplicationerror(outstream, 1, b'some message')
382 result = reactor.onservererror(outstream, 1, b'some message')
383 self.assertaction(result, b'sendframes')
383 self.assertaction(result, b'sendframes')
384 self.assertframesequal(result[1][b'framegen'], [
384 self.assertframesequal(result[1][b'framegen'], [
385 b'1 2 stream-begin error-response application some message',
385 b"1 2 stream-begin error-response 0 "
386 b"cbor:{b'type': b'server', "
387 b"b'message': [{b'msg': b'some message'}]}",
386 ])
388 ])
387
389
388 def test1commanddeferresponse(self):
390 def test1commanddeferresponse(self):
389 """Responses when in deferred output mode are delayed until EOF."""
391 """Responses when in deferred output mode are delayed until EOF."""
390 reactor = makereactor(deferoutput=True)
392 reactor = makereactor(deferoutput=True)
391 instream = framing.stream(1)
393 instream = framing.stream(1)
392 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
394 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
393 {}))
395 {}))
394 self.assertEqual(len(results), 1)
396 self.assertEqual(len(results), 1)
395 self.assertaction(results[0], b'runcommand')
397 self.assertaction(results[0], b'runcommand')
396
398
397 outstream = reactor.makeoutputstream()
399 outstream = reactor.makeoutputstream()
398 result = reactor.oncommandresponseready(outstream, 1, b'response')
400 result = reactor.oncommandresponseready(outstream, 1, b'response')
399 self.assertaction(result, b'noop')
401 self.assertaction(result, b'noop')
400 result = reactor.oninputeof()
402 result = reactor.oninputeof()
401 self.assertaction(result, b'sendframes')
403 self.assertaction(result, b'sendframes')
402 self.assertframesequal(result[1][b'framegen'], [
404 self.assertframesequal(result[1][b'framegen'], [
403 b'1 2 stream-begin command-response eos %sresponse' % OK,
405 b'1 2 stream-begin command-response eos %sresponse' % OK,
404 ])
406 ])
405
407
406 def testmultiplecommanddeferresponse(self):
408 def testmultiplecommanddeferresponse(self):
407 reactor = makereactor(deferoutput=True)
409 reactor = makereactor(deferoutput=True)
408 instream = framing.stream(1)
410 instream = framing.stream(1)
409 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
411 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
410 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
412 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
411
413
412 outstream = reactor.makeoutputstream()
414 outstream = reactor.makeoutputstream()
413 result = reactor.oncommandresponseready(outstream, 1, b'response1')
415 result = reactor.oncommandresponseready(outstream, 1, b'response1')
414 self.assertaction(result, b'noop')
416 self.assertaction(result, b'noop')
415 result = reactor.oncommandresponseready(outstream, 3, b'response2')
417 result = reactor.oncommandresponseready(outstream, 3, b'response2')
416 self.assertaction(result, b'noop')
418 self.assertaction(result, b'noop')
417 result = reactor.oninputeof()
419 result = reactor.oninputeof()
418 self.assertaction(result, b'sendframes')
420 self.assertaction(result, b'sendframes')
419 self.assertframesequal(result[1][b'framegen'], [
421 self.assertframesequal(result[1][b'framegen'], [
420 b'1 2 stream-begin command-response eos %sresponse1' % OK,
422 b'1 2 stream-begin command-response eos %sresponse1' % OK,
421 b'3 2 0 command-response eos %sresponse2' % OK,
423 b'3 2 0 command-response eos %sresponse2' % OK,
422 ])
424 ])
423
425
424 def testrequestidtracking(self):
426 def testrequestidtracking(self):
425 reactor = makereactor(deferoutput=True)
427 reactor = makereactor(deferoutput=True)
426 instream = framing.stream(1)
428 instream = framing.stream(1)
427 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
429 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
428 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
430 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
429 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
431 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
430
432
431 # Register results for commands out of order.
433 # Register results for commands out of order.
432 outstream = reactor.makeoutputstream()
434 outstream = reactor.makeoutputstream()
433 reactor.oncommandresponseready(outstream, 3, b'response3')
435 reactor.oncommandresponseready(outstream, 3, b'response3')
434 reactor.oncommandresponseready(outstream, 1, b'response1')
436 reactor.oncommandresponseready(outstream, 1, b'response1')
435 reactor.oncommandresponseready(outstream, 5, b'response5')
437 reactor.oncommandresponseready(outstream, 5, b'response5')
436
438
437 result = reactor.oninputeof()
439 result = reactor.oninputeof()
438 self.assertaction(result, b'sendframes')
440 self.assertaction(result, b'sendframes')
439 self.assertframesequal(result[1][b'framegen'], [
441 self.assertframesequal(result[1][b'framegen'], [
440 b'3 2 stream-begin command-response eos %sresponse3' % OK,
442 b'3 2 stream-begin command-response eos %sresponse3' % OK,
441 b'1 2 0 command-response eos %sresponse1' % OK,
443 b'1 2 0 command-response eos %sresponse1' % OK,
442 b'5 2 0 command-response eos %sresponse5' % OK,
444 b'5 2 0 command-response eos %sresponse5' % OK,
443 ])
445 ])
444
446
445 def testduplicaterequestonactivecommand(self):
447 def testduplicaterequestonactivecommand(self):
446 """Receiving a request ID that matches a request that isn't finished."""
448 """Receiving a request ID that matches a request that isn't finished."""
447 reactor = makereactor()
449 reactor = makereactor()
448 stream = framing.stream(1)
450 stream = framing.stream(1)
449 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
451 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
450 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
452 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
451
453
452 self.assertaction(results[0], b'error')
454 self.assertaction(results[0], b'error')
453 self.assertEqual(results[0][1], {
455 self.assertEqual(results[0][1], {
454 b'message': b'request with ID 1 is already active',
456 b'message': b'request with ID 1 is already active',
455 })
457 })
456
458
457 def testduplicaterequestonactivecommandnosend(self):
459 def testduplicaterequestonactivecommandnosend(self):
458 """Same as above but we've registered a response but haven't sent it."""
460 """Same as above but we've registered a response but haven't sent it."""
459 reactor = makereactor()
461 reactor = makereactor()
460 instream = framing.stream(1)
462 instream = framing.stream(1)
461 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
463 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
462 outstream = reactor.makeoutputstream()
464 outstream = reactor.makeoutputstream()
463 reactor.oncommandresponseready(outstream, 1, b'response')
465 reactor.oncommandresponseready(outstream, 1, b'response')
464
466
465 # We've registered the response but haven't sent it. From the
467 # We've registered the response but haven't sent it. From the
466 # perspective of the reactor, the command is still active.
468 # perspective of the reactor, the command is still active.
467
469
468 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
470 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
469 self.assertaction(results[0], b'error')
471 self.assertaction(results[0], b'error')
470 self.assertEqual(results[0][1], {
472 self.assertEqual(results[0][1], {
471 b'message': b'request with ID 1 is already active',
473 b'message': b'request with ID 1 is already active',
472 })
474 })
473
475
474 def testduplicaterequestaftersend(self):
476 def testduplicaterequestaftersend(self):
475 """We can use a duplicate request ID after we've sent the response."""
477 """We can use a duplicate request ID after we've sent the response."""
476 reactor = makereactor()
478 reactor = makereactor()
477 instream = framing.stream(1)
479 instream = framing.stream(1)
478 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
480 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
479 outstream = reactor.makeoutputstream()
481 outstream = reactor.makeoutputstream()
480 res = reactor.oncommandresponseready(outstream, 1, b'response')
482 res = reactor.oncommandresponseready(outstream, 1, b'response')
481 list(res[1][b'framegen'])
483 list(res[1][b'framegen'])
482
484
483 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
485 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
484 self.assertaction(results[0], b'runcommand')
486 self.assertaction(results[0], b'runcommand')
485
487
486 if __name__ == '__main__':
488 if __name__ == '__main__':
487 import silenttestrunner
489 import silenttestrunner
488 silenttestrunner.main(__name__)
490 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now