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