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