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