##// END OF EJS Templates
wireprotov2: change command response protocol to include a leading map...
Gregory Szorc -
r37743:3ea8323d default
parent child Browse files
Show More
@@ -1,1863 +1,1892 b''
1 The Mercurial wire protocol is a request-response based protocol
1 The Mercurial wire protocol is a request-response based protocol
2 with multiple wire representations.
2 with multiple wire representations.
3
3
4 Each request is modeled as a command name, a dictionary of arguments, and
4 Each request is modeled as a command name, a dictionary of arguments, and
5 optional raw input. Command arguments and their types are intrinsic
5 optional raw input. Command arguments and their types are intrinsic
6 properties of commands. So is the response type of the command. This means
6 properties of commands. So is the response type of the command. This means
7 clients can't always send arbitrary arguments to servers and servers can't
7 clients can't always send arbitrary arguments to servers and servers can't
8 return multiple response types.
8 return multiple response types.
9
9
10 The protocol is synchronous and does not support multiplexing (concurrent
10 The protocol is synchronous and does not support multiplexing (concurrent
11 commands).
11 commands).
12
12
13 Handshake
13 Handshake
14 =========
14 =========
15
15
16 It is required or common for clients to perform a *handshake* when connecting
16 It is required or common for clients to perform a *handshake* when connecting
17 to a server. The handshake serves the following purposes:
17 to a server. The handshake serves the following purposes:
18
18
19 * Negotiating protocol/transport level options
19 * Negotiating protocol/transport level options
20 * Allows the client to learn about server capabilities to influence
20 * Allows the client to learn about server capabilities to influence
21 future requests
21 future requests
22 * Ensures the underlying transport channel is in a *clean* state
22 * Ensures the underlying transport channel is in a *clean* state
23
23
24 An important goal of the handshake is to allow clients to use more modern
24 An important goal of the handshake is to allow clients to use more modern
25 wire protocol features. By default, clients must assume they are talking
25 wire protocol features. By default, clients must assume they are talking
26 to an old version of Mercurial server (possibly even the very first
26 to an old version of Mercurial server (possibly even the very first
27 implementation). So, clients should not attempt to call or utilize modern
27 implementation). So, clients should not attempt to call or utilize modern
28 wire protocol features until they have confirmation that the server
28 wire protocol features until they have confirmation that the server
29 supports them. The handshake implementation is designed to allow both
29 supports them. The handshake implementation is designed to allow both
30 ends to utilize the latest set of features and capabilities with as
30 ends to utilize the latest set of features and capabilities with as
31 few round trips as possible.
31 few round trips as possible.
32
32
33 The handshake mechanism varies by transport and protocol and is documented
33 The handshake mechanism varies by transport and protocol and is documented
34 in the sections below.
34 in the sections below.
35
35
36 HTTP Protocol
36 HTTP Protocol
37 =============
37 =============
38
38
39 Handshake
39 Handshake
40 ---------
40 ---------
41
41
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
42 The client sends a ``capabilities`` command request (``?cmd=capabilities``)
43 as soon as HTTP requests may be issued.
43 as soon as HTTP requests may be issued.
44
44
45 By default, the server responds with a version 1 capabilities string, which
45 By default, the server responds with a version 1 capabilities string, which
46 the client parses to learn about the server's abilities. The ``Content-Type``
46 the client parses to learn about the server's abilities. The ``Content-Type``
47 for this response is ``application/mercurial-0.1`` or
47 for this response is ``application/mercurial-0.1`` or
48 ``application/mercurial-0.2`` depending on whether the client advertised
48 ``application/mercurial-0.2`` depending on whether the client advertised
49 support for version ``0.2`` in its request. (Clients aren't supposed to
49 support for version ``0.2`` in its request. (Clients aren't supposed to
50 advertise support for ``0.2`` until the capabilities response indicates
50 advertise support for ``0.2`` until the capabilities response indicates
51 the server's support for that media type. However, a client could
51 the server's support for that media type. However, a client could
52 conceivably cache this metadata and issue the capabilities request in such
52 conceivably cache this metadata and issue the capabilities request in such
53 a way to elicit an ``application/mercurial-0.2`` response.)
53 a way to elicit an ``application/mercurial-0.2`` response.)
54
54
55 Clients wishing to switch to a newer API service may send an
55 Clients wishing to switch to a newer API service may send an
56 ``X-HgUpgrade-<X>`` header containing a space-delimited list of API service
56 ``X-HgUpgrade-<X>`` header containing a space-delimited list of API service
57 names the client is capable of speaking. The request MUST also include an
57 names the client is capable of speaking. The request MUST also include an
58 ``X-HgProto-<X>`` header advertising a known serialization format for the
58 ``X-HgProto-<X>`` header advertising a known serialization format for the
59 response. ``cbor`` is currently the only defined serialization format.
59 response. ``cbor`` is currently the only defined serialization format.
60
60
61 If the request contains these headers, the response ``Content-Type`` MAY
61 If the request contains these headers, the response ``Content-Type`` MAY
62 be for a different media type. e.g. ``application/mercurial-cbor`` if the
62 be for a different media type. e.g. ``application/mercurial-cbor`` if the
63 client advertises support for CBOR.
63 client advertises support for CBOR.
64
64
65 The response MUST be deserializable to a map with the following keys:
65 The response MUST be deserializable to a map with the following keys:
66
66
67 apibase
67 apibase
68 URL path to API services, relative to the repository root. e.g. ``api/``.
68 URL path to API services, relative to the repository root. e.g. ``api/``.
69
69
70 apis
70 apis
71 A map of API service names to API descriptors. An API descriptor contains
71 A map of API service names to API descriptors. An API descriptor contains
72 more details about that API. In the case of the HTTP Version 2 Transport,
72 more details about that API. In the case of the HTTP Version 2 Transport,
73 it will be the normal response to a ``capabilities`` command.
73 it will be the normal response to a ``capabilities`` command.
74
74
75 Only the services advertised by the client that are also available on
75 Only the services advertised by the client that are also available on
76 the server are advertised.
76 the server are advertised.
77
77
78 v1capabilities
78 v1capabilities
79 The capabilities string that would be returned by a version 1 response.
79 The capabilities string that would be returned by a version 1 response.
80
80
81 The client can then inspect the server-advertised APIs and decide which
81 The client can then inspect the server-advertised APIs and decide which
82 API to use, including continuing to use the HTTP Version 1 Transport.
82 API to use, including continuing to use the HTTP Version 1 Transport.
83
83
84 HTTP Version 1 Transport
84 HTTP Version 1 Transport
85 ------------------------
85 ------------------------
86
86
87 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
87 Commands are issued as HTTP/1.0 or HTTP/1.1 requests. Commands are
88 sent to the base URL of the repository with the command name sent in
88 sent to the base URL of the repository with the command name sent in
89 the ``cmd`` query string parameter. e.g.
89 the ``cmd`` query string parameter. e.g.
90 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
90 ``https://example.com/repo?cmd=capabilities``. The HTTP method is ``GET``
91 or ``POST`` depending on the command and whether there is a request
91 or ``POST`` depending on the command and whether there is a request
92 body.
92 body.
93
93
94 Command arguments can be sent multiple ways.
94 Command arguments can be sent multiple ways.
95
95
96 The simplest is part of the URL query string using ``x-www-form-urlencoded``
96 The simplest is part of the URL query string using ``x-www-form-urlencoded``
97 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
97 encoding (see Python's ``urllib.urlencode()``. However, many servers impose
98 length limitations on the URL. So this mechanism is typically only used if
98 length limitations on the URL. So this mechanism is typically only used if
99 the server doesn't support other mechanisms.
99 the server doesn't support other mechanisms.
100
100
101 If the server supports the ``httpheader`` capability, command arguments can
101 If the server supports the ``httpheader`` capability, command arguments can
102 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
102 be sent in HTTP request headers named ``X-HgArg-<N>`` where ``<N>`` is an
103 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
103 integer starting at 1. A ``x-www-form-urlencoded`` representation of the
104 arguments is obtained. This full string is then split into chunks and sent
104 arguments is obtained. This full string is then split into chunks and sent
105 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
105 in numbered ``X-HgArg-<N>`` headers. The maximum length of each HTTP header
106 is defined by the server in the ``httpheader`` capability value, which defaults
106 is defined by the server in the ``httpheader`` capability value, which defaults
107 to ``1024``. The server reassembles the encoded arguments string by
107 to ``1024``. The server reassembles the encoded arguments string by
108 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
108 concatenating the ``X-HgArg-<N>`` headers then URL decodes them into a
109 dictionary.
109 dictionary.
110
110
111 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
111 The list of ``X-HgArg-<N>`` headers should be added to the ``Vary`` request
112 header to instruct caches to take these headers into consideration when caching
112 header to instruct caches to take these headers into consideration when caching
113 requests.
113 requests.
114
114
115 If the server supports the ``httppostargs`` capability, the client
115 If the server supports the ``httppostargs`` capability, the client
116 may send command arguments in the HTTP request body as part of an
116 may send command arguments in the HTTP request body as part of an
117 HTTP POST request. The command arguments will be URL encoded just like
117 HTTP POST request. The command arguments will be URL encoded just like
118 they would for sending them via HTTP headers. However, no splitting is
118 they would for sending them via HTTP headers. However, no splitting is
119 performed: the raw arguments are included in the HTTP request body.
119 performed: the raw arguments are included in the HTTP request body.
120
120
121 The client sends a ``X-HgArgs-Post`` header with the string length of the
121 The client sends a ``X-HgArgs-Post`` header with the string length of the
122 encoded arguments data. Additional data may be included in the HTTP
122 encoded arguments data. Additional data may be included in the HTTP
123 request body immediately following the argument data. The offset of the
123 request body immediately following the argument data. The offset of the
124 non-argument data is defined by the ``X-HgArgs-Post`` header. The
124 non-argument data is defined by the ``X-HgArgs-Post`` header. The
125 ``X-HgArgs-Post`` header is not required if there is no argument data.
125 ``X-HgArgs-Post`` header is not required if there is no argument data.
126
126
127 Additional command data can be sent as part of the HTTP request body. The
127 Additional command data can be sent as part of the HTTP request body. The
128 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
128 default ``Content-Type`` when sending data is ``application/mercurial-0.1``.
129 A ``Content-Length`` header is currently always sent.
129 A ``Content-Length`` header is currently always sent.
130
130
131 Example HTTP requests::
131 Example HTTP requests::
132
132
133 GET /repo?cmd=capabilities
133 GET /repo?cmd=capabilities
134 X-HgArg-1: foo=bar&baz=hello%20world
134 X-HgArg-1: foo=bar&baz=hello%20world
135
135
136 The request media type should be chosen based on server support. If the
136 The request media type should be chosen based on server support. If the
137 ``httpmediatype`` server capability is present, the client should send
137 ``httpmediatype`` server capability is present, the client should send
138 the newest mutually supported media type. If this capability is absent,
138 the newest mutually supported media type. If this capability is absent,
139 the client must assume the server only supports the
139 the client must assume the server only supports the
140 ``application/mercurial-0.1`` media type.
140 ``application/mercurial-0.1`` media type.
141
141
142 The ``Content-Type`` HTTP response header identifies the response as coming
142 The ``Content-Type`` HTTP response header identifies the response as coming
143 from Mercurial and can also be used to signal an error has occurred.
143 from Mercurial and can also be used to signal an error has occurred.
144
144
145 The ``application/mercurial-*`` media types indicate a generic Mercurial
145 The ``application/mercurial-*`` media types indicate a generic Mercurial
146 data type.
146 data type.
147
147
148 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
148 The ``application/mercurial-0.1`` media type is raw Mercurial data. It is the
149 predecessor of the format below.
149 predecessor of the format below.
150
150
151 The ``application/mercurial-0.2`` media type is compression framed Mercurial
151 The ``application/mercurial-0.2`` media type is compression framed Mercurial
152 data. The first byte of the payload indicates the length of the compression
152 data. The first byte of the payload indicates the length of the compression
153 format identifier that follows. Next are N bytes indicating the compression
153 format identifier that follows. Next are N bytes indicating the compression
154 format. e.g. ``zlib``. The remaining bytes are compressed according to that
154 format. e.g. ``zlib``. The remaining bytes are compressed according to that
155 compression format. The decompressed data behaves the same as with
155 compression format. The decompressed data behaves the same as with
156 ``application/mercurial-0.1``.
156 ``application/mercurial-0.1``.
157
157
158 The ``application/hg-error`` media type indicates a generic error occurred.
158 The ``application/hg-error`` media type indicates a generic error occurred.
159 The content of the HTTP response body typically holds text describing the
159 The content of the HTTP response body typically holds text describing the
160 error.
160 error.
161
161
162 The ``application/mercurial-cbor`` media type indicates a CBOR payload
162 The ``application/mercurial-cbor`` media type indicates a CBOR payload
163 and should be interpreted as identical to ``application/cbor``.
163 and should be interpreted as identical to ``application/cbor``.
164
164
165 Behavior of media types is further described in the ``Content Negotiation``
165 Behavior of media types is further described in the ``Content Negotiation``
166 section below.
166 section below.
167
167
168 Clients should issue a ``User-Agent`` request header that identifies the client.
168 Clients should issue a ``User-Agent`` request header that identifies the client.
169 The server should not use the ``User-Agent`` for feature detection.
169 The server should not use the ``User-Agent`` for feature detection.
170
170
171 A command returning a ``string`` response issues a
171 A command returning a ``string`` response issues a
172 ``application/mercurial-0.*`` media type and the HTTP response body contains
172 ``application/mercurial-0.*`` media type and the HTTP response body contains
173 the raw string value (after compression decoding, if used). A
173 the raw string value (after compression decoding, if used). A
174 ``Content-Length`` header is typically issued, but not required.
174 ``Content-Length`` header is typically issued, but not required.
175
175
176 A command returning a ``stream`` response issues a
176 A command returning a ``stream`` response issues a
177 ``application/mercurial-0.*`` media type and the HTTP response is typically
177 ``application/mercurial-0.*`` media type and the HTTP response is typically
178 using *chunked transfer* (``Transfer-Encoding: chunked``).
178 using *chunked transfer* (``Transfer-Encoding: chunked``).
179
179
180 HTTP Version 2 Transport
180 HTTP Version 2 Transport
181 ------------------------
181 ------------------------
182
182
183 **Experimental - feature under active development**
183 **Experimental - feature under active development**
184
184
185 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
185 Version 2 of the HTTP protocol is exposed under the ``/api/*`` URL space.
186 It's final API name is not yet formalized.
186 It's final API name is not yet formalized.
187
187
188 Commands are triggered by sending HTTP POST requests against URLs of the
188 Commands are triggered by sending HTTP POST requests against URLs of the
189 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
189 form ``<permission>/<command>``, where ``<permission>`` is ``ro`` or
190 ``rw``, meaning read-only and read-write, respectively and ``<command>``
190 ``rw``, meaning read-only and read-write, respectively and ``<command>``
191 is a named wire protocol command.
191 is a named wire protocol command.
192
192
193 Non-POST request methods MUST be rejected by the server with an HTTP
193 Non-POST request methods MUST be rejected by the server with an HTTP
194 405 response.
194 405 response.
195
195
196 Commands that modify repository state in meaningful ways MUST NOT be
196 Commands that modify repository state in meaningful ways MUST NOT be
197 exposed under the ``ro`` URL prefix. All available commands MUST be
197 exposed under the ``ro`` URL prefix. All available commands MUST be
198 available under the ``rw`` URL prefix.
198 available under the ``rw`` URL prefix.
199
199
200 Server adminstrators MAY implement blanket HTTP authentication keyed
200 Server adminstrators MAY implement blanket HTTP authentication keyed
201 off the URL prefix. For example, a server may require authentication
201 off the URL prefix. For example, a server may require authentication
202 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
202 for all ``rw/*`` URLs and let unauthenticated requests to ``ro/*``
203 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
203 URL proceed. A server MAY issue an HTTP 401, 403, or 407 response
204 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
204 in accordance with RFC 7235. Clients SHOULD recognize the HTTP Basic
205 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
205 (RFC 7617) and Digest (RFC 7616) authentication schemes. Clients SHOULD
206 make an attempt to recognize unknown schemes using the
206 make an attempt to recognize unknown schemes using the
207 ``WWW-Authenticate`` response header on a 401 response, as defined by
207 ``WWW-Authenticate`` response header on a 401 response, as defined by
208 RFC 7235.
208 RFC 7235.
209
209
210 Read-only commands are accessible under ``rw/*`` URLs so clients can
210 Read-only commands are accessible under ``rw/*`` URLs so clients can
211 signal the intent of the operation very early in the connection
211 signal the intent of the operation very early in the connection
212 lifecycle. For example, a ``push`` operation - which consists of
212 lifecycle. For example, a ``push`` operation - which consists of
213 various read-only commands mixed with at least one read-write command -
213 various read-only commands mixed with at least one read-write command -
214 can perform all commands against ``rw/*`` URLs so that any server-side
214 can perform all commands against ``rw/*`` URLs so that any server-side
215 authentication requirements are discovered upon attempting the first
215 authentication requirements are discovered upon attempting the first
216 command - not potentially several commands into the exchange. This
216 command - not potentially several commands into the exchange. This
217 allows clients to fail faster or prompt for credentials as soon as the
217 allows clients to fail faster or prompt for credentials as soon as the
218 exchange takes place. This provides a better end-user experience.
218 exchange takes place. This provides a better end-user experience.
219
219
220 Requests to unknown commands or URLS result in an HTTP 404.
220 Requests to unknown commands or URLS result in an HTTP 404.
221 TODO formally define response type, how error is communicated, etc.
221 TODO formally define response type, how error is communicated, etc.
222
222
223 HTTP request and response bodies use the *Unified Frame-Based Protocol*
223 HTTP request and response bodies use the *Unified Frame-Based Protocol*
224 (defined below) for media exchange. The entirety of the HTTP message
224 (defined below) for media exchange. The entirety of the HTTP message
225 body is 0 or more frames as defined by this protocol.
225 body is 0 or more frames as defined by this protocol.
226
226
227 Clients and servers MUST advertise the ``TBD`` media type via the
227 Clients and servers MUST advertise the ``TBD`` media type via the
228 ``Content-Type`` request and response headers. In addition, clients MUST
228 ``Content-Type`` request and response headers. In addition, clients MUST
229 advertise this media type value in their ``Accept`` request header in all
229 advertise this media type value in their ``Accept`` request header in all
230 requests.
230 requests.
231 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
231 TODO finalize the media type. For now, it is defined in wireprotoserver.py.
232
232
233 Servers receiving requests without an ``Accept`` header SHOULD respond with
233 Servers receiving requests without an ``Accept`` header SHOULD respond with
234 an HTTP 406.
234 an HTTP 406.
235
235
236 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
236 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
237 respond with an HTTP 415.
237 respond with an HTTP 415.
238
238
239 The command to run is specified in the POST payload as defined by the
239 The command to run is specified in the POST payload as defined by the
240 *Unified Frame-Based Protocol*. This is redundant with data already
240 *Unified Frame-Based Protocol*. This is redundant with data already
241 encoded in the URL. This is by design, so server operators can have
241 encoded in the URL. This is by design, so server operators can have
242 better understanding about server activity from looking merely at
242 better understanding about server activity from looking merely at
243 HTTP access logs.
243 HTTP access logs.
244
244
245 In most circumstances, the command specified in the URL MUST match
245 In most circumstances, the command specified in the URL MUST match
246 the command specified in the frame-based payload or the server will
246 the command specified in the frame-based payload or the server will
247 respond with an error. The exception to this is the special
247 respond with an error. The exception to this is the special
248 ``multirequest`` URL. (See below.) In addition, HTTP requests
248 ``multirequest`` URL. (See below.) In addition, HTTP requests
249 are limited to one command invocation. The exception is the special
249 are limited to one command invocation. The exception is the special
250 ``multirequest`` URL.
250 ``multirequest`` URL.
251
251
252 The ``multirequest`` command endpoints (``ro/multirequest`` and
252 The ``multirequest`` command endpoints (``ro/multirequest`` and
253 ``rw/multirequest``) are special in that they allow the execution of
253 ``rw/multirequest``) are special in that they allow the execution of
254 *any* command and allow the execution of multiple commands. If the
254 *any* command and allow the execution of multiple commands. If the
255 HTTP request issues multiple commands across multiple frames, all
255 HTTP request issues multiple commands across multiple frames, all
256 issued commands will be processed by the server. Per the defined
256 issued commands will be processed by the server. Per the defined
257 behavior of the *Unified Frame-Based Protocol*, commands may be
257 behavior of the *Unified Frame-Based Protocol*, commands may be
258 issued interleaved and responses may come back in a different order
258 issued interleaved and responses may come back in a different order
259 than they were issued. Clients MUST be able to deal with this.
259 than they were issued. Clients MUST be able to deal with this.
260
260
261 SSH Protocol
261 SSH Protocol
262 ============
262 ============
263
263
264 Handshake
264 Handshake
265 ---------
265 ---------
266
266
267 For all clients, the handshake consists of the client sending 1 or more
267 For all clients, the handshake consists of the client sending 1 or more
268 commands to the server using version 1 of the transport. Servers respond
268 commands to the server using version 1 of the transport. Servers respond
269 to commands they know how to respond to and send an empty response (``0\n``)
269 to commands they know how to respond to and send an empty response (``0\n``)
270 for unknown commands (per standard behavior of version 1 of the transport).
270 for unknown commands (per standard behavior of version 1 of the transport).
271 Clients then typically look for a response to the newest sent command to
271 Clients then typically look for a response to the newest sent command to
272 determine which transport version to use and what the available features for
272 determine which transport version to use and what the available features for
273 the connection and server are.
273 the connection and server are.
274
274
275 Preceding any response from client-issued commands, the server may print
275 Preceding any response from client-issued commands, the server may print
276 non-protocol output. It is common for SSH servers to print banners, message
276 non-protocol output. It is common for SSH servers to print banners, message
277 of the day announcements, etc when clients connect. It is assumed that any
277 of the day announcements, etc when clients connect. It is assumed that any
278 such *banner* output will precede any Mercurial server output. So clients
278 such *banner* output will precede any Mercurial server output. So clients
279 must be prepared to handle server output on initial connect that isn't
279 must be prepared to handle server output on initial connect that isn't
280 in response to any client-issued command and doesn't conform to Mercurial's
280 in response to any client-issued command and doesn't conform to Mercurial's
281 wire protocol. This *banner* output should only be on stdout. However,
281 wire protocol. This *banner* output should only be on stdout. However,
282 some servers may send output on stderr.
282 some servers may send output on stderr.
283
283
284 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
284 Pre 0.9.1 clients issue a ``between`` command with the ``pairs`` argument
285 having the value
285 having the value
286 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
286 ``0000000000000000000000000000000000000000-0000000000000000000000000000000000000000``.
287
287
288 The ``between`` command has been supported since the original Mercurial
288 The ``between`` command has been supported since the original Mercurial
289 SSH server. Requesting the empty range will return a ``\n`` string response,
289 SSH server. Requesting the empty range will return a ``\n`` string response,
290 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
290 which will be encoded as ``1\n\n`` (value length of ``1`` followed by a newline
291 followed by the value, which happens to be a newline).
291 followed by the value, which happens to be a newline).
292
292
293 For pre 0.9.1 clients and all servers, the exchange looks like::
293 For pre 0.9.1 clients and all servers, the exchange looks like::
294
294
295 c: between\n
295 c: between\n
296 c: pairs 81\n
296 c: pairs 81\n
297 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
297 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
298 s: 1\n
298 s: 1\n
299 s: \n
299 s: \n
300
300
301 0.9.1+ clients send a ``hello`` command (with no arguments) before the
301 0.9.1+ clients send a ``hello`` command (with no arguments) before the
302 ``between`` command. The response to this command allows clients to
302 ``between`` command. The response to this command allows clients to
303 discover server capabilities and settings.
303 discover server capabilities and settings.
304
304
305 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
305 An example exchange between 0.9.1+ clients and a ``hello`` aware server looks
306 like::
306 like::
307
307
308 c: hello\n
308 c: hello\n
309 c: between\n
309 c: between\n
310 c: pairs 81\n
310 c: pairs 81\n
311 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
311 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
312 s: 324\n
312 s: 324\n
313 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
313 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
314 s: 1\n
314 s: 1\n
315 s: \n
315 s: \n
316
316
317 And a similar scenario but with servers sending a banner on connect::
317 And a similar scenario but with servers sending a banner on connect::
318
318
319 c: hello\n
319 c: hello\n
320 c: between\n
320 c: between\n
321 c: pairs 81\n
321 c: pairs 81\n
322 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
322 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
323 s: welcome to the server\n
323 s: welcome to the server\n
324 s: if you find any issues, email someone@somewhere.com\n
324 s: if you find any issues, email someone@somewhere.com\n
325 s: 324\n
325 s: 324\n
326 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
326 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
327 s: 1\n
327 s: 1\n
328 s: \n
328 s: \n
329
329
330 Note that output from the ``hello`` command is terminated by a ``\n``. This is
330 Note that output from the ``hello`` command is terminated by a ``\n``. This is
331 part of the response payload and not part of the wire protocol adding a newline
331 part of the response payload and not part of the wire protocol adding a newline
332 after responses. In other words, the length of the response contains the
332 after responses. In other words, the length of the response contains the
333 trailing ``\n``.
333 trailing ``\n``.
334
334
335 Clients supporting version 2 of the SSH transport send a line beginning
335 Clients supporting version 2 of the SSH transport send a line beginning
336 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
336 with ``upgrade`` before the ``hello`` and ``between`` commands. The line
337 (which isn't a well-formed command line because it doesn't consist of a
337 (which isn't a well-formed command line because it doesn't consist of a
338 single command name) serves to both communicate the client's intent to
338 single command name) serves to both communicate the client's intent to
339 switch to transport version 2 (transports are version 1 by default) as
339 switch to transport version 2 (transports are version 1 by default) as
340 well as to advertise the client's transport-level capabilities so the
340 well as to advertise the client's transport-level capabilities so the
341 server may satisfy that request immediately.
341 server may satisfy that request immediately.
342
342
343 The upgrade line has the form:
343 The upgrade line has the form:
344
344
345 upgrade <token> <transport capabilities>
345 upgrade <token> <transport capabilities>
346
346
347 That is the literal string ``upgrade`` followed by a space, followed by
347 That is the literal string ``upgrade`` followed by a space, followed by
348 a randomly generated string, followed by a space, followed by a string
348 a randomly generated string, followed by a space, followed by a string
349 denoting the client's transport capabilities.
349 denoting the client's transport capabilities.
350
350
351 The token can be anything. However, a random UUID is recommended. (Use
351 The token can be anything. However, a random UUID is recommended. (Use
352 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
352 of version 4 UUIDs is recommended because version 1 UUIDs can leak the
353 client's MAC address.)
353 client's MAC address.)
354
354
355 The transport capabilities string is a URL/percent encoded string
355 The transport capabilities string is a URL/percent encoded string
356 containing key-value pairs defining the client's transport-level
356 containing key-value pairs defining the client's transport-level
357 capabilities. The following capabilities are defined:
357 capabilities. The following capabilities are defined:
358
358
359 proto
359 proto
360 A comma-delimited list of transport protocol versions the client
360 A comma-delimited list of transport protocol versions the client
361 supports. e.g. ``ssh-v2``.
361 supports. e.g. ``ssh-v2``.
362
362
363 If the server does not recognize the ``upgrade`` line, it should issue
363 If the server does not recognize the ``upgrade`` line, it should issue
364 an empty response and continue processing the ``hello`` and ``between``
364 an empty response and continue processing the ``hello`` and ``between``
365 commands. Here is an example handshake between a version 2 aware client
365 commands. Here is an example handshake between a version 2 aware client
366 and a non version 2 aware server:
366 and a non version 2 aware server:
367
367
368 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
368 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
369 c: hello\n
369 c: hello\n
370 c: between\n
370 c: between\n
371 c: pairs 81\n
371 c: pairs 81\n
372 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
372 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
373 s: 0\n
373 s: 0\n
374 s: 324\n
374 s: 324\n
375 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
375 s: capabilities: lookup changegroupsubset branchmap pushkey known getbundle ...\n
376 s: 1\n
376 s: 1\n
377 s: \n
377 s: \n
378
378
379 (The initial ``0\n`` line from the server indicates an empty response to
379 (The initial ``0\n`` line from the server indicates an empty response to
380 the unknown ``upgrade ..`` command/line.)
380 the unknown ``upgrade ..`` command/line.)
381
381
382 If the server recognizes the ``upgrade`` line and is willing to satisfy that
382 If the server recognizes the ``upgrade`` line and is willing to satisfy that
383 upgrade request, it replies to with a payload of the following form:
383 upgrade request, it replies to with a payload of the following form:
384
384
385 upgraded <token> <transport name>\n
385 upgraded <token> <transport name>\n
386
386
387 This line is the literal string ``upgraded``, a space, the token that was
387 This line is the literal string ``upgraded``, a space, the token that was
388 specified by the client in its ``upgrade ...`` request line, a space, and the
388 specified by the client in its ``upgrade ...`` request line, a space, and the
389 name of the transport protocol that was chosen by the server. The transport
389 name of the transport protocol that was chosen by the server. The transport
390 name MUST match one of the names the client specified in the ``proto`` field
390 name MUST match one of the names the client specified in the ``proto`` field
391 of its ``upgrade ...`` request line.
391 of its ``upgrade ...`` request line.
392
392
393 If a server issues an ``upgraded`` response, it MUST also read and ignore
393 If a server issues an ``upgraded`` response, it MUST also read and ignore
394 the lines associated with the ``hello`` and ``between`` command requests
394 the lines associated with the ``hello`` and ``between`` command requests
395 that were issued by the server. It is assumed that the negotiated transport
395 that were issued by the server. It is assumed that the negotiated transport
396 will respond with equivalent requested information following the transport
396 will respond with equivalent requested information following the transport
397 handshake.
397 handshake.
398
398
399 All data following the ``\n`` terminating the ``upgraded`` line is the
399 All data following the ``\n`` terminating the ``upgraded`` line is the
400 domain of the negotiated transport. It is common for the data immediately
400 domain of the negotiated transport. It is common for the data immediately
401 following to contain additional metadata about the state of the transport and
401 following to contain additional metadata about the state of the transport and
402 the server. However, this isn't strictly speaking part of the transport
402 the server. However, this isn't strictly speaking part of the transport
403 handshake and isn't covered by this section.
403 handshake and isn't covered by this section.
404
404
405 Here is an example handshake between a version 2 aware client and a version
405 Here is an example handshake between a version 2 aware client and a version
406 2 aware server:
406 2 aware server:
407
407
408 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
408 c: upgrade 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a proto=ssh-v2
409 c: hello\n
409 c: hello\n
410 c: between\n
410 c: between\n
411 c: pairs 81\n
411 c: pairs 81\n
412 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
412 c: 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
413 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
413 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
414 s: <additional transport specific data>
414 s: <additional transport specific data>
415
415
416 The client-issued token that is echoed in the response provides a more
416 The client-issued token that is echoed in the response provides a more
417 resilient mechanism for differentiating *banner* output from Mercurial
417 resilient mechanism for differentiating *banner* output from Mercurial
418 output. In version 1, properly formatted banner output could get confused
418 output. In version 1, properly formatted banner output could get confused
419 for Mercurial server output. By submitting a randomly generated token
419 for Mercurial server output. By submitting a randomly generated token
420 that is then present in the response, the client can look for that token
420 that is then present in the response, the client can look for that token
421 in response lines and have reasonable certainty that the line did not
421 in response lines and have reasonable certainty that the line did not
422 originate from a *banner* message.
422 originate from a *banner* message.
423
423
424 SSH Version 1 Transport
424 SSH Version 1 Transport
425 -----------------------
425 -----------------------
426
426
427 The SSH transport (version 1) is a custom text-based protocol suitable for
427 The SSH transport (version 1) is a custom text-based protocol suitable for
428 use over any bi-directional stream transport. It is most commonly used with
428 use over any bi-directional stream transport. It is most commonly used with
429 SSH.
429 SSH.
430
430
431 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
431 A SSH transport server can be started with ``hg serve --stdio``. The stdin,
432 stderr, and stdout file descriptors of the started process are used to exchange
432 stderr, and stdout file descriptors of the started process are used to exchange
433 data. When Mercurial connects to a remote server over SSH, it actually starts
433 data. When Mercurial connects to a remote server over SSH, it actually starts
434 a ``hg serve --stdio`` process on the remote server.
434 a ``hg serve --stdio`` process on the remote server.
435
435
436 Commands are issued by sending the command name followed by a trailing newline
436 Commands are issued by sending the command name followed by a trailing newline
437 ``\n`` to the server. e.g. ``capabilities\n``.
437 ``\n`` to the server. e.g. ``capabilities\n``.
438
438
439 Command arguments are sent in the following format::
439 Command arguments are sent in the following format::
440
440
441 <argument> <length>\n<value>
441 <argument> <length>\n<value>
442
442
443 That is, the argument string name followed by a space followed by the
443 That is, the argument string name followed by a space followed by the
444 integer length of the value (expressed as a string) followed by a newline
444 integer length of the value (expressed as a string) followed by a newline
445 (``\n``) followed by the raw argument value.
445 (``\n``) followed by the raw argument value.
446
446
447 Dictionary arguments are encoded differently::
447 Dictionary arguments are encoded differently::
448
448
449 <argument> <# elements>\n
449 <argument> <# elements>\n
450 <key1> <length1>\n<value1>
450 <key1> <length1>\n<value1>
451 <key2> <length2>\n<value2>
451 <key2> <length2>\n<value2>
452 ...
452 ...
453
453
454 Non-argument data is sent immediately after the final argument value. It is
454 Non-argument data is sent immediately after the final argument value. It is
455 encoded in chunks::
455 encoded in chunks::
456
456
457 <length>\n<data>
457 <length>\n<data>
458
458
459 Each command declares a list of supported arguments and their types. If a
459 Each command declares a list of supported arguments and their types. If a
460 client sends an unknown argument to the server, the server should abort
460 client sends an unknown argument to the server, the server should abort
461 immediately. The special argument ``*`` in a command's definition indicates
461 immediately. The special argument ``*`` in a command's definition indicates
462 that all argument names are allowed.
462 that all argument names are allowed.
463
463
464 The definition of supported arguments and types is initially made when a
464 The definition of supported arguments and types is initially made when a
465 new command is implemented. The client and server must initially independently
465 new command is implemented. The client and server must initially independently
466 agree on the arguments and their types. This initial set of arguments can be
466 agree on the arguments and their types. This initial set of arguments can be
467 supplemented through the presence of *capabilities* advertised by the server.
467 supplemented through the presence of *capabilities* advertised by the server.
468
468
469 Each command has a defined expected response type.
469 Each command has a defined expected response type.
470
470
471 A ``string`` response type is a length framed value. The response consists of
471 A ``string`` response type is a length framed value. The response consists of
472 the string encoded integer length of a value followed by a newline (``\n``)
472 the string encoded integer length of a value followed by a newline (``\n``)
473 followed by the value. Empty values are allowed (and are represented as
473 followed by the value. Empty values are allowed (and are represented as
474 ``0\n``).
474 ``0\n``).
475
475
476 A ``stream`` response type consists of raw bytes of data. There is no framing.
476 A ``stream`` response type consists of raw bytes of data. There is no framing.
477
477
478 A generic error response type is also supported. It consists of a an error
478 A generic error response type is also supported. It consists of a an error
479 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
479 message written to ``stderr`` followed by ``\n-\n``. In addition, ``\n`` is
480 written to ``stdout``.
480 written to ``stdout``.
481
481
482 If the server receives an unknown command, it will send an empty ``string``
482 If the server receives an unknown command, it will send an empty ``string``
483 response.
483 response.
484
484
485 The server terminates if it receives an empty command (a ``\n`` character).
485 The server terminates if it receives an empty command (a ``\n`` character).
486
486
487 If the server announces support for the ``protocaps`` capability, the client
487 If the server announces support for the ``protocaps`` capability, the client
488 should issue a ``protocaps`` command after the initial handshake to annonunce
488 should issue a ``protocaps`` command after the initial handshake to annonunce
489 its own capabilities. The client capabilities are persistent.
489 its own capabilities. The client capabilities are persistent.
490
490
491 SSH Version 2 Transport
491 SSH Version 2 Transport
492 -----------------------
492 -----------------------
493
493
494 **Experimental and under development**
494 **Experimental and under development**
495
495
496 Version 2 of the SSH transport behaves identically to version 1 of the SSH
496 Version 2 of the SSH transport behaves identically to version 1 of the SSH
497 transport with the exception of handshake semantics. See above for how
497 transport with the exception of handshake semantics. See above for how
498 version 2 of the SSH transport is negotiated.
498 version 2 of the SSH transport is negotiated.
499
499
500 Immediately following the ``upgraded`` line signaling a switch to version
500 Immediately following the ``upgraded`` line signaling a switch to version
501 2 of the SSH protocol, the server automatically sends additional details
501 2 of the SSH protocol, the server automatically sends additional details
502 about the capabilities of the remote server. This has the form:
502 about the capabilities of the remote server. This has the form:
503
503
504 <integer length of value>\n
504 <integer length of value>\n
505 capabilities: ...\n
505 capabilities: ...\n
506
506
507 e.g.
507 e.g.
508
508
509 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
509 s: upgraded 2e82ab3f-9ce3-4b4e-8f8c-6fd1c0e9e23a ssh-v2\n
510 s: 240\n
510 s: 240\n
511 s: capabilities: known getbundle batch ...\n
511 s: capabilities: known getbundle batch ...\n
512
512
513 Following capabilities advertisement, the peers communicate using version
513 Following capabilities advertisement, the peers communicate using version
514 1 of the SSH transport.
514 1 of the SSH transport.
515
515
516 Unified Frame-Based Protocol
516 Unified Frame-Based Protocol
517 ============================
517 ============================
518
518
519 **Experimental and under development**
519 **Experimental and under development**
520
520
521 The *Unified Frame-Based Protocol* is a communications protocol between
521 The *Unified Frame-Based Protocol* is a communications protocol between
522 Mercurial peers. The protocol aims to be mostly transport agnostic
522 Mercurial peers. The protocol aims to be mostly transport agnostic
523 (works similarly on HTTP, SSH, etc).
523 (works similarly on HTTP, SSH, etc).
524
524
525 To operate the protocol, a bi-directional, half-duplex pipe supporting
525 To operate the protocol, a bi-directional, half-duplex pipe supporting
526 ordered sends and receives is required. That is, each peer has one pipe
526 ordered sends and receives is required. That is, each peer has one pipe
527 for sending data and another for receiving.
527 for sending data and another for receiving.
528
528
529 All data is read and written in atomic units called *frames*. These
529 All data is read and written in atomic units called *frames*. These
530 are conceptually similar to TCP packets. Higher-level functionality
530 are conceptually similar to TCP packets. Higher-level functionality
531 is built on the exchange and processing of frames.
531 is built on the exchange and processing of frames.
532
532
533 All frames are associated with a *stream*. A *stream* provides a
533 All frames are associated with a *stream*. A *stream* provides a
534 unidirectional grouping of frames. Streams facilitate two goals:
534 unidirectional grouping of frames. Streams facilitate two goals:
535 content encoding and parallelism. There is a dedicated section on
535 content encoding and parallelism. There is a dedicated section on
536 streams below.
536 streams below.
537
537
538 The protocol is request-response based: the client issues requests to
538 The protocol is request-response based: the client issues requests to
539 the server, which issues replies to those requests. Server-initiated
539 the server, which issues replies to those requests. Server-initiated
540 messaging is not currently supported, but this specification carves
540 messaging is not currently supported, but this specification carves
541 out room to implement it.
541 out room to implement it.
542
542
543 All frames are associated with a numbered request. Frames can thus
543 All frames are associated with a numbered request. Frames can thus
544 be logically grouped by their request ID.
544 be logically grouped by their request ID.
545
545
546 Frames begin with an 8 octet header followed by a variable length
546 Frames begin with an 8 octet header followed by a variable length
547 payload::
547 payload::
548
548
549 +------------------------------------------------+
549 +------------------------------------------------+
550 | Length (24) |
550 | Length (24) |
551 +--------------------------------+---------------+
551 +--------------------------------+---------------+
552 | Request ID (16) | Stream ID (8) |
552 | Request ID (16) | Stream ID (8) |
553 +------------------+-------------+---------------+
553 +------------------+-------------+---------------+
554 | Stream Flags (8) |
554 | Stream Flags (8) |
555 +-----------+------+
555 +-----------+------+
556 | Type (4) |
556 | Type (4) |
557 +-----------+
557 +-----------+
558 | Flags (4) |
558 | Flags (4) |
559 +===========+===================================================|
559 +===========+===================================================|
560 | Frame Payload (0...) ...
560 | Frame Payload (0...) ...
561 +---------------------------------------------------------------+
561 +---------------------------------------------------------------+
562
562
563 The length of the frame payload is expressed as an unsigned 24 bit
563 The length of the frame payload is expressed as an unsigned 24 bit
564 little endian integer. Values larger than 65535 MUST NOT be used unless
564 little endian integer. Values larger than 65535 MUST NOT be used unless
565 given permission by the server as part of the negotiated capabilities
565 given permission by the server as part of the negotiated capabilities
566 during the handshake. The frame header is not part of the advertised
566 during the handshake. The frame header is not part of the advertised
567 frame length. The payload length is the over-the-wire length. If there
567 frame length. The payload length is the over-the-wire length. If there
568 is content encoding applied to the payload as part of the frame's stream,
568 is content encoding applied to the payload as part of the frame's stream,
569 the length is the output of that content encoding, not the input.
569 the length is the output of that content encoding, not the input.
570
570
571 The 16-bit ``Request ID`` field denotes the integer request identifier,
571 The 16-bit ``Request ID`` field denotes the integer request identifier,
572 stored as an unsigned little endian integer. Odd numbered requests are
572 stored as an unsigned little endian integer. Odd numbered requests are
573 client-initiated. Even numbered requests are server-initiated. This
573 client-initiated. Even numbered requests are server-initiated. This
574 refers to where the *request* was initiated - not where the *frame* was
574 refers to where the *request* was initiated - not where the *frame* was
575 initiated, so servers will send frames with odd ``Request ID`` in
575 initiated, so servers will send frames with odd ``Request ID`` in
576 response to client-initiated requests. Implementations are advised to
576 response to client-initiated requests. Implementations are advised to
577 start ordering request identifiers at ``1`` and ``0``, increment by
577 start ordering request identifiers at ``1`` and ``0``, increment by
578 ``2``, and wrap around if all available numbers have been exhausted.
578 ``2``, and wrap around if all available numbers have been exhausted.
579
579
580 The 8-bit ``Stream ID`` field denotes the stream that the frame is
580 The 8-bit ``Stream ID`` field denotes the stream that the frame is
581 associated with. Frames belonging to a stream may have content
581 associated with. Frames belonging to a stream may have content
582 encoding applied and the receiver may need to decode the raw frame
582 encoding applied and the receiver may need to decode the raw frame
583 payload to obtain the original data. Odd numbered IDs are
583 payload to obtain the original data. Odd numbered IDs are
584 client-initiated. Even numbered IDs are server-initiated.
584 client-initiated. Even numbered IDs are server-initiated.
585
585
586 The 8-bit ``Stream Flags`` field defines stream processing semantics.
586 The 8-bit ``Stream Flags`` field defines stream processing semantics.
587 See the section on streams below.
587 See the section on streams below.
588
588
589 The 4-bit ``Type`` field denotes the type of frame being sent.
589 The 4-bit ``Type`` field denotes the type of frame being sent.
590
590
591 The 4-bit ``Flags`` field defines special, per-type attributes for
591 The 4-bit ``Flags`` field defines special, per-type attributes for
592 the frame.
592 the frame.
593
593
594 The sections below define the frame types and their behavior.
594 The sections below define the frame types and their behavior.
595
595
596 Command Request (``0x01``)
596 Command Request (``0x01``)
597 --------------------------
597 --------------------------
598
598
599 This frame contains a request to run a command.
599 This frame contains a request to run a command.
600
600
601 The payload consists of a CBOR map defining the command request. The
601 The payload consists of a CBOR map defining the command request. The
602 bytestring keys of that map are:
602 bytestring keys of that map are:
603
603
604 name
604 name
605 Name of the command that should be executed (bytestring).
605 Name of the command that should be executed (bytestring).
606 args
606 args
607 Map of bytestring keys to various value types containing the named
607 Map of bytestring keys to various value types containing the named
608 arguments to this command.
608 arguments to this command.
609
609
610 Each command defines its own set of argument names and their expected
610 Each command defines its own set of argument names and their expected
611 types.
611 types.
612
612
613 This frame type MUST ONLY be sent from clients to servers: it is illegal
613 This frame type MUST ONLY be sent from clients to servers: it is illegal
614 for a server to send this frame to a client.
614 for a server to send this frame to a client.
615
615
616 The following flag values are defined for this type:
616 The following flag values are defined for this type:
617
617
618 0x01
618 0x01
619 New command request. When set, this frame represents the beginning
619 New command request. When set, this frame represents the beginning
620 of a new request to run a command. The ``Request ID`` attached to this
620 of a new request to run a command. The ``Request ID`` attached to this
621 frame MUST NOT be active.
621 frame MUST NOT be active.
622 0x02
622 0x02
623 Command request continuation. When set, this frame is a continuation
623 Command request continuation. When set, this frame is a continuation
624 from a previous command request frame for its ``Request ID``. This
624 from a previous command request frame for its ``Request ID``. This
625 flag is set when the CBOR data for a command request does not fit
625 flag is set when the CBOR data for a command request does not fit
626 in a single frame.
626 in a single frame.
627 0x04
627 0x04
628 Additional frames expected. When set, the command request didn't fit
628 Additional frames expected. When set, the command request didn't fit
629 into a single frame and additional CBOR data follows in a subsequent
629 into a single frame and additional CBOR data follows in a subsequent
630 frame.
630 frame.
631 0x08
631 0x08
632 Command data frames expected. When set, command data frames are
632 Command data frames expected. When set, command data frames are
633 expected to follow the final command request frame for this request.
633 expected to follow the final command request frame for this request.
634
634
635 ``0x01`` MUST be set on the initial command request frame for a
635 ``0x01`` MUST be set on the initial command request frame for a
636 ``Request ID``.
636 ``Request ID``.
637
637
638 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
638 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
639 a series of command request frames.
639 a series of command request frames.
640
640
641 If command data frames are to be sent, ``0x08`` MUST be set on ALL
641 If command data frames are to be sent, ``0x08`` MUST be set on ALL
642 command request frames.
642 command request frames.
643
643
644 Command Data (``0x02``)
644 Command Data (``0x02``)
645 -----------------------
645 -----------------------
646
646
647 This frame contains raw data for a command.
647 This frame contains raw data for a command.
648
648
649 Most commands can be executed by specifying arguments. However,
649 Most commands can be executed by specifying arguments. However,
650 arguments have an upper bound to their length. For commands that
650 arguments have an upper bound to their length. For commands that
651 accept data that is beyond this length or whose length isn't known
651 accept data that is beyond this length or whose length isn't known
652 when the command is initially sent, they will need to stream
652 when the command is initially sent, they will need to stream
653 arbitrary data to the server. This frame type facilitates the sending
653 arbitrary data to the server. This frame type facilitates the sending
654 of this data.
654 of this data.
655
655
656 The payload of this frame type consists of a stream of raw data to be
656 The payload of this frame type consists of a stream of raw data to be
657 consumed by the command handler on the server. The format of the data
657 consumed by the command handler on the server. The format of the data
658 is command specific.
658 is command specific.
659
659
660 The following flag values are defined for this type:
660 The following flag values are defined for this type:
661
661
662 0x01
662 0x01
663 Command data continuation. When set, the data for this command
663 Command data continuation. When set, the data for this command
664 continues into a subsequent frame.
664 continues into a subsequent frame.
665
665
666 0x02
666 0x02
667 End of data. When set, command data has been fully sent to the
667 End of data. When set, command data has been fully sent to the
668 server. The command has been fully issued and no new data for this
668 server. The command has been fully issued and no new data for this
669 command will be sent. The next frame will belong to a new command.
669 command will be sent. The next frame will belong to a new command.
670
670
671 Command Response Data (``0x03``)
671 Command Response Data (``0x03``)
672 --------------------------------
672 --------------------------------
673
673
674 This frame contains response data to an issued command.
674 This frame contains response data to an issued command.
675
675
676 Response data ALWAYS consists of a series of 0 or more CBOR encoded
676 Response data ALWAYS consists of a series of 1 or more CBOR encoded
677 values. A CBOR value may be using indefinite length encoding. And the
677 values. A CBOR value may be using indefinite length encoding. And the
678 bytes constituting the value may span several frames.
678 bytes constituting the value may span several frames.
679
679
680 The following flag values are defined for this type:
680 The following flag values are defined for this type:
681
681
682 0x01
682 0x01
683 Data continuation. When set, an additional frame containing response data
683 Data continuation. When set, an additional frame containing response data
684 will follow.
684 will follow.
685 0x02
685 0x02
686 End of data. When set, the response data has been fully sent and
686 End of data. When set, the response data has been fully sent and
687 no additional frames for this response will be sent.
687 no additional frames for this response will be sent.
688
688
689 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
689 The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
690
690
691 Error Response (``0x05``)
691 Error Response (``0x05``)
692 -------------------------
692 -------------------------
693
693
694 An error occurred when processing a request. This could indicate
694 An error occurred when processing a request. This could indicate
695 a protocol-level failure or an application level failure depending
695 a protocol-level failure or an application level failure depending
696 on the flags for this message type.
696 on the flags for this message type.
697
697
698 The payload for this type is an error message that should be
698 The payload for this type is an error message that should be
699 displayed to the user.
699 displayed to the user.
700
700
701 The following flag values are defined for this type:
701 The following flag values are defined for this type:
702
702
703 0x01
703 0x01
704 The error occurred at the transport/protocol level. If set, the
704 The error occurred at the transport/protocol level. If set, the
705 connection should be closed.
705 connection should be closed.
706 0x02
706 0x02
707 The error occurred at the application level. e.g. invalid command.
707 The error occurred at the application level. e.g. invalid command.
708
708
709 Human Output Side-Channel (``0x06``)
709 Human Output Side-Channel (``0x06``)
710 ------------------------------------
710 ------------------------------------
711
711
712 This frame contains a message that is intended to be displayed to
712 This frame contains a message that is intended to be displayed to
713 people. Whereas most frames communicate machine readable data, this
713 people. Whereas most frames communicate machine readable data, this
714 frame communicates textual data that is intended to be shown to
714 frame communicates textual data that is intended to be shown to
715 humans.
715 humans.
716
716
717 The frame consists of a series of *formatting requests*. Each formatting
717 The frame consists of a series of *formatting requests*. Each formatting
718 request consists of a formatting string, arguments for that formatting
718 request consists of a formatting string, arguments for that formatting
719 string, and labels to apply to that formatting string.
719 string, and labels to apply to that formatting string.
720
720
721 A formatting string is a printf()-like string that allows variable
721 A formatting string is a printf()-like string that allows variable
722 substitution within the string. Labels allow the rendered text to be
722 substitution within the string. Labels allow the rendered text to be
723 *decorated*. Assuming use of the canonical Mercurial code base, a
723 *decorated*. Assuming use of the canonical Mercurial code base, a
724 formatting string can be the input to the ``i18n._`` function. This
724 formatting string can be the input to the ``i18n._`` function. This
725 allows messages emitted from the server to be localized. So even if
725 allows messages emitted from the server to be localized. So even if
726 the server has different i18n settings, people could see messages in
726 the server has different i18n settings, people could see messages in
727 their *native* settings. Similarly, the use of labels allows
727 their *native* settings. Similarly, the use of labels allows
728 decorations like coloring and underlining to be applied using the
728 decorations like coloring and underlining to be applied using the
729 client's configured rendering settings.
729 client's configured rendering settings.
730
730
731 Formatting strings are similar to ``printf()`` strings or how
731 Formatting strings are similar to ``printf()`` strings or how
732 Python's ``%`` operator works. The only supported formatting sequences
732 Python's ``%`` operator works. The only supported formatting sequences
733 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
733 are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
734 at that position resolves to. ``%%`` will be replaced by ``%``. All
734 at that position resolves to. ``%%`` will be replaced by ``%``. All
735 other 2-byte sequences beginning with ``%`` represent a literal
735 other 2-byte sequences beginning with ``%`` represent a literal
736 ``%`` followed by that character. However, future versions of the
736 ``%`` followed by that character. However, future versions of the
737 wire protocol reserve the right to allow clients to opt in to receiving
737 wire protocol reserve the right to allow clients to opt in to receiving
738 formatting strings with additional formatters, hence why ``%%`` is
738 formatting strings with additional formatters, hence why ``%%`` is
739 required to represent the literal ``%``.
739 required to represent the literal ``%``.
740
740
741 The frame payload consists of a CBOR array of CBOR maps. Each map
741 The frame payload consists of a CBOR array of CBOR maps. Each map
742 defines an *atom* of text data to print. Each *atom* has the following
742 defines an *atom* of text data to print. Each *atom* has the following
743 bytestring keys:
743 bytestring keys:
744
744
745 msg
745 msg
746 (bytestring) The formatting string. Content MUST be ASCII.
746 (bytestring) The formatting string. Content MUST be ASCII.
747 args (optional)
747 args (optional)
748 Array of bytestrings defining arguments to the formatting string.
748 Array of bytestrings defining arguments to the formatting string.
749 labels (optional)
749 labels (optional)
750 Array of bytestrings defining labels to apply to this atom.
750 Array of bytestrings defining labels to apply to this atom.
751
751
752 All data to be printed MUST be encoded into a single frame: this frame
752 All data to be printed MUST be encoded into a single frame: this frame
753 does not support spanning data across multiple frames.
753 does not support spanning data across multiple frames.
754
754
755 All textual data encoded in these frames is assumed to be line delimited.
755 All textual data encoded in these frames is assumed to be line delimited.
756 The last atom in the frame SHOULD end with a newline (``\n``). If it
756 The last atom in the frame SHOULD end with a newline (``\n``). If it
757 doesn't, clients MAY add a newline to facilitate immediate printing.
757 doesn't, clients MAY add a newline to facilitate immediate printing.
758
758
759 Progress Update (``0x07``)
759 Progress Update (``0x07``)
760 --------------------------
760 --------------------------
761
761
762 This frame holds the progress of an operation on the peer. Consumption
762 This frame holds the progress of an operation on the peer. Consumption
763 of these frames allows clients to display progress bars, estimated
763 of these frames allows clients to display progress bars, estimated
764 completion times, etc.
764 completion times, etc.
765
765
766 Each frame defines the progress of a single operation on the peer. The
766 Each frame defines the progress of a single operation on the peer. The
767 payload consists of a CBOR map with the following bytestring keys:
767 payload consists of a CBOR map with the following bytestring keys:
768
768
769 topic
769 topic
770 Topic name (string)
770 Topic name (string)
771 pos
771 pos
772 Current numeric position within the topic (integer)
772 Current numeric position within the topic (integer)
773 total
773 total
774 Total/end numeric position of this topic (unsigned integer)
774 Total/end numeric position of this topic (unsigned integer)
775 label (optional)
775 label (optional)
776 Unit label (string)
776 Unit label (string)
777 item (optional)
777 item (optional)
778 Item name (string)
778 Item name (string)
779
779
780 Progress state is created when a frame is received referencing a
780 Progress state is created when a frame is received referencing a
781 *topic* that isn't currently tracked. Progress tracking for that
781 *topic* that isn't currently tracked. Progress tracking for that
782 *topic* is finished when a frame is received reporting the current
782 *topic* is finished when a frame is received reporting the current
783 position of that topic as ``-1``.
783 position of that topic as ``-1``.
784
784
785 Multiple *topics* may be active at any given time.
785 Multiple *topics* may be active at any given time.
786
786
787 Rendering of progress information is not mandated or governed by this
787 Rendering of progress information is not mandated or governed by this
788 specification: implementations MAY render progress information however
788 specification: implementations MAY render progress information however
789 they see fit, including not at all.
789 they see fit, including not at all.
790
790
791 The string data describing the topic SHOULD be static strings to
791 The string data describing the topic SHOULD be static strings to
792 facilitate receivers localizing that string data. The emitter
792 facilitate receivers localizing that string data. The emitter
793 MUST normalize all string data to valid UTF-8 and receivers SHOULD
793 MUST normalize all string data to valid UTF-8 and receivers SHOULD
794 validate that received data conforms to UTF-8. The topic name
794 validate that received data conforms to UTF-8. The topic name
795 SHOULD be ASCII.
795 SHOULD be ASCII.
796
796
797 Stream Encoding Settings (``0x08``)
797 Stream Encoding Settings (``0x08``)
798 -----------------------------------
798 -----------------------------------
799
799
800 This frame type holds information defining the content encoding
800 This frame type holds information defining the content encoding
801 settings for a *stream*.
801 settings for a *stream*.
802
802
803 This frame type is likely consumed by the protocol layer and is not
803 This frame type is likely consumed by the protocol layer and is not
804 passed on to applications.
804 passed on to applications.
805
805
806 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
806 This frame type MUST ONLY occur on frames having the *Beginning of Stream*
807 ``Stream Flag`` set.
807 ``Stream Flag`` set.
808
808
809 The payload of this frame defines what content encoding has (possibly)
809 The payload of this frame defines what content encoding has (possibly)
810 been applied to the payloads of subsequent frames in this stream.
810 been applied to the payloads of subsequent frames in this stream.
811
811
812 The payload begins with an 8-bit integer defining the length of the
812 The payload begins with an 8-bit integer defining the length of the
813 encoding *profile*, followed by the string name of that profile, which
813 encoding *profile*, followed by the string name of that profile, which
814 must be an ASCII string. All bytes that follow can be used by that
814 must be an ASCII string. All bytes that follow can be used by that
815 profile for supplemental settings definitions. See the section below
815 profile for supplemental settings definitions. See the section below
816 on defined encoding profiles.
816 on defined encoding profiles.
817
817
818 Stream States and Flags
818 Stream States and Flags
819 -----------------------
819 -----------------------
820
820
821 Streams can be in two states: *open* and *closed*. An *open* stream
821 Streams can be in two states: *open* and *closed*. An *open* stream
822 is active and frames attached to that stream could arrive at any time.
822 is active and frames attached to that stream could arrive at any time.
823 A *closed* stream is not active. If a frame attached to a *closed*
823 A *closed* stream is not active. If a frame attached to a *closed*
824 stream arrives, that frame MUST have an appropriate stream flag
824 stream arrives, that frame MUST have an appropriate stream flag
825 set indicating beginning of stream. All streams are in the *closed*
825 set indicating beginning of stream. All streams are in the *closed*
826 state by default.
826 state by default.
827
827
828 The ``Stream Flags`` field denotes a set of bit flags for defining
828 The ``Stream Flags`` field denotes a set of bit flags for defining
829 the relationship of this frame within a stream. The following flags
829 the relationship of this frame within a stream. The following flags
830 are defined:
830 are defined:
831
831
832 0x01
832 0x01
833 Beginning of stream. The first frame in the stream MUST set this
833 Beginning of stream. The first frame in the stream MUST set this
834 flag. When received, the ``Stream ID`` this frame is attached to
834 flag. When received, the ``Stream ID`` this frame is attached to
835 becomes ``open``.
835 becomes ``open``.
836
836
837 0x02
837 0x02
838 End of stream. The last frame in a stream MUST set this flag. When
838 End of stream. The last frame in a stream MUST set this flag. When
839 received, the ``Stream ID`` this frame is attached to becomes
839 received, the ``Stream ID`` this frame is attached to becomes
840 ``closed``. Any content encoding context associated with this stream
840 ``closed``. Any content encoding context associated with this stream
841 can be destroyed after processing the payload of this frame.
841 can be destroyed after processing the payload of this frame.
842
842
843 0x04
843 0x04
844 Apply content encoding. When set, any content encoding settings
844 Apply content encoding. When set, any content encoding settings
845 defined by the stream should be applied when attempting to read
845 defined by the stream should be applied when attempting to read
846 the frame. When not set, the frame payload isn't encoded.
846 the frame. When not set, the frame payload isn't encoded.
847
847
848 Streams
848 Streams
849 -------
849 -------
850
850
851 Streams - along with ``Request IDs`` - facilitate grouping of frames.
851 Streams - along with ``Request IDs`` - facilitate grouping of frames.
852 But the purpose of each is quite different and the groupings they
852 But the purpose of each is quite different and the groupings they
853 constitute are independent.
853 constitute are independent.
854
854
855 A ``Request ID`` is essentially a tag. It tells you which logical
855 A ``Request ID`` is essentially a tag. It tells you which logical
856 request a frame is associated with.
856 request a frame is associated with.
857
857
858 A *stream* is a sequence of frames grouped for the express purpose
858 A *stream* is a sequence of frames grouped for the express purpose
859 of applying a stateful encoding or for denoting sub-groups of frames.
859 of applying a stateful encoding or for denoting sub-groups of frames.
860
860
861 Unlike ``Request ID``s which span the request and response, a stream
861 Unlike ``Request ID``s which span the request and response, a stream
862 is unidirectional and stream IDs are independent from client to
862 is unidirectional and stream IDs are independent from client to
863 server.
863 server.
864
864
865 There is no strict hierarchical relationship between ``Request IDs``
865 There is no strict hierarchical relationship between ``Request IDs``
866 and *streams*. A stream can contain frames having multiple
866 and *streams*. A stream can contain frames having multiple
867 ``Request IDs``. Frames belonging to the same ``Request ID`` can
867 ``Request IDs``. Frames belonging to the same ``Request ID`` can
868 span multiple streams.
868 span multiple streams.
869
869
870 One goal of streams is to facilitate content encoding. A stream can
870 One goal of streams is to facilitate content encoding. A stream can
871 define an encoding to be applied to frame payloads. For example, the
871 define an encoding to be applied to frame payloads. For example, the
872 payload transmitted over the wire may contain output from a
872 payload transmitted over the wire may contain output from a
873 zstandard compression operation and the receiving end may decompress
873 zstandard compression operation and the receiving end may decompress
874 that payload to obtain the original data.
874 that payload to obtain the original data.
875
875
876 The other goal of streams is to facilitate concurrent execution. For
876 The other goal of streams is to facilitate concurrent execution. For
877 example, a server could spawn 4 threads to service a request that can
877 example, a server could spawn 4 threads to service a request that can
878 be easily parallelized. Each of those 4 threads could write into its
878 be easily parallelized. Each of those 4 threads could write into its
879 own stream. Those streams could then in turn be delivered to 4 threads
879 own stream. Those streams could then in turn be delivered to 4 threads
880 on the receiving end, with each thread consuming its stream in near
880 on the receiving end, with each thread consuming its stream in near
881 isolation. The *main* thread on both ends merely does I/O and
881 isolation. The *main* thread on both ends merely does I/O and
882 encodes/decodes frame headers: the bulk of the work is done by worker
882 encodes/decodes frame headers: the bulk of the work is done by worker
883 threads.
883 threads.
884
884
885 In addition, since content encoding is defined per stream, each
885 In addition, since content encoding is defined per stream, each
886 *worker thread* could perform potentially CPU bound work concurrently
886 *worker thread* could perform potentially CPU bound work concurrently
887 with other threads. This approach of applying encoding at the
887 with other threads. This approach of applying encoding at the
888 sub-protocol / stream level eliminates a potential resource constraint
888 sub-protocol / stream level eliminates a potential resource constraint
889 on the protocol stream as a whole (it is common for the throughput of
889 on the protocol stream as a whole (it is common for the throughput of
890 a compression engine to be smaller than the throughput of a network).
890 a compression engine to be smaller than the throughput of a network).
891
891
892 Having multiple streams - each with their own encoding settings - also
892 Having multiple streams - each with their own encoding settings - also
893 facilitates the use of advanced data compression techniques. For
893 facilitates the use of advanced data compression techniques. For
894 example, a transmitter could see that it is generating data faster
894 example, a transmitter could see that it is generating data faster
895 and slower than the receiving end is consuming it and adjust its
895 and slower than the receiving end is consuming it and adjust its
896 compression settings to trade CPU for compression ratio accordingly.
896 compression settings to trade CPU for compression ratio accordingly.
897
897
898 While streams can define a content encoding, not all frames within
898 While streams can define a content encoding, not all frames within
899 that stream must use that content encoding. This can be useful when
899 that stream must use that content encoding. This can be useful when
900 data is being served from caches and being derived dynamically. A
900 data is being served from caches and being derived dynamically. A
901 cache could pre-compressed data so the server doesn't have to
901 cache could pre-compressed data so the server doesn't have to
902 recompress it. The ability to pick and choose which frames are
902 recompress it. The ability to pick and choose which frames are
903 compressed allows servers to easily send data to the wire without
903 compressed allows servers to easily send data to the wire without
904 involving potentially expensive encoding overhead.
904 involving potentially expensive encoding overhead.
905
905
906 Content Encoding Profiles
906 Content Encoding Profiles
907 -------------------------
907 -------------------------
908
908
909 Streams can have named content encoding *profiles* associated with
909 Streams can have named content encoding *profiles* associated with
910 them. A profile defines a shared understanding of content encoding
910 them. A profile defines a shared understanding of content encoding
911 settings and behavior.
911 settings and behavior.
912
912
913 The following profiles are defined:
913 The following profiles are defined:
914
914
915 TBD
915 TBD
916
916
917 Issuing Commands
917 Command Protocol
918 ----------------
918 ----------------
919
919
920 A client can request that a remote run a command by sending it
920 A client can request that a remote run a command by sending it
921 frames defining that command. This logical stream is composed of
921 frames defining that command. This logical stream is composed of
922 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
922 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
923 frames.
923 frames.
924
924
925 All frames composing a single command request MUST be associated with
925 All frames composing a single command request MUST be associated with
926 the same ``Request ID``.
926 the same ``Request ID``.
927
927
928 Clients MAY send additional command requests without waiting on the
928 Clients MAY send additional command requests without waiting on the
929 response to a previous command request. If they do so, they MUST ensure
929 response to a previous command request. If they do so, they MUST ensure
930 that the ``Request ID`` field of outbound frames does not conflict
930 that the ``Request ID`` field of outbound frames does not conflict
931 with that of an active ``Request ID`` whose response has not yet been
931 with that of an active ``Request ID`` whose response has not yet been
932 fully received.
932 fully received.
933
933
934 Servers MAY respond to commands in a different order than they were
934 Servers MAY respond to commands in a different order than they were
935 sent over the wire. Clients MUST be prepared to deal with this. Servers
935 sent over the wire. Clients MUST be prepared to deal with this. Servers
936 also MAY start executing commands in a different order than they were
936 also MAY start executing commands in a different order than they were
937 received, or MAY execute multiple commands concurrently.
937 received, or MAY execute multiple commands concurrently.
938
938
939 If there is a dependency between commands or a race condition between
939 If there is a dependency between commands or a race condition between
940 commands executing (e.g. a read-only command that depends on the results
940 commands executing (e.g. a read-only command that depends on the results
941 of a command that mutates the repository), then clients MUST NOT send
941 of a command that mutates the repository), then clients MUST NOT send
942 frames issuing a command until a response to all dependent commands has
942 frames issuing a command until a response to all dependent commands has
943 been received.
943 been received.
944 TODO think about whether we should express dependencies between commands
944 TODO think about whether we should express dependencies between commands
945 to avoid roundtrip latency.
945 to avoid roundtrip latency.
946
946
947 A command is defined by a command name, 0 or more command arguments,
947 A command is defined by a command name, 0 or more command arguments,
948 and optional command data.
948 and optional command data.
949
949
950 Arguments are the recommended mechanism for transferring fixed sets of
950 Arguments are the recommended mechanism for transferring fixed sets of
951 parameters to a command. Data is appropriate for transferring variable
951 parameters to a command. Data is appropriate for transferring variable
952 data. Thinking in terms of HTTP, arguments would be headers and data
952 data. Thinking in terms of HTTP, arguments would be headers and data
953 would be the message body.
953 would be the message body.
954
954
955 It is recommended for servers to delay the dispatch of a command
955 It is recommended for servers to delay the dispatch of a command
956 until all argument have been received. Servers MAY impose limits on the
956 until all argument have been received. Servers MAY impose limits on the
957 maximum argument size.
957 maximum argument size.
958 TODO define failure mechanism.
958 TODO define failure mechanism.
959
959
960 Servers MAY dispatch to commands immediately once argument data
960 Servers MAY dispatch to commands immediately once argument data
961 is available or delay until command data is received in full.
961 is available or delay until command data is received in full.
962
962
963 Once a ``Command Request`` frame is sent, a client must be prepared to
964 receive any of the following frames associated with that request:
965 ``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
966 ``Progress Update``.
967
968 The *main* response for a command will be in ``Command Response`` frames.
969 The payloads of these frames consist of 1 or more CBOR encoded values.
970 The first CBOR value on the first ``Command Response`` frame is special
971 and denotes the overall status of the command. This CBOR map contains
972 the following bytestring keys:
973
974 status
975 (bytestring) A well-defined message containing the overall status of
976 this command request. The following values are defined:
977
978 ok
979 The command was received successfully and its response follows.
980 error
981 There was an error processing the command. More details about the
982 error are encoded in the ``error`` key.
983
984 error (optional)
985 A map containing information about an encountered error. The map has the
986 following keys:
987
988 message
989 (array of maps) A message describing the error. The message uses the
990 same format as those in the ``Human Output Side-Channel`` frame.
991
963 Capabilities
992 Capabilities
964 ============
993 ============
965
994
966 Servers advertise supported wire protocol features. This allows clients to
995 Servers advertise supported wire protocol features. This allows clients to
967 probe for server features before blindly calling a command or passing a
996 probe for server features before blindly calling a command or passing a
968 specific argument.
997 specific argument.
969
998
970 The server's features are exposed via a *capabilities* string. This is a
999 The server's features are exposed via a *capabilities* string. This is a
971 space-delimited string of tokens/features. Some features are single words
1000 space-delimited string of tokens/features. Some features are single words
972 like ``lookup`` or ``batch``. Others are complicated key-value pairs
1001 like ``lookup`` or ``batch``. Others are complicated key-value pairs
973 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
1002 advertising sub-features. e.g. ``httpheader=2048``. When complex, non-word
974 values are used, each feature name can define its own encoding of sub-values.
1003 values are used, each feature name can define its own encoding of sub-values.
975 Comma-delimited and ``x-www-form-urlencoded`` values are common.
1004 Comma-delimited and ``x-www-form-urlencoded`` values are common.
976
1005
977 The following document capabilities defined by the canonical Mercurial server
1006 The following document capabilities defined by the canonical Mercurial server
978 implementation.
1007 implementation.
979
1008
980 batch
1009 batch
981 -----
1010 -----
982
1011
983 Whether the server supports the ``batch`` command.
1012 Whether the server supports the ``batch`` command.
984
1013
985 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1014 This capability/command was introduced in Mercurial 1.9 (released July 2011).
986
1015
987 branchmap
1016 branchmap
988 ---------
1017 ---------
989
1018
990 Whether the server supports the ``branchmap`` command.
1019 Whether the server supports the ``branchmap`` command.
991
1020
992 This capability/command was introduced in Mercurial 1.3 (released July 2009).
1021 This capability/command was introduced in Mercurial 1.3 (released July 2009).
993
1022
994 bundle2-exp
1023 bundle2-exp
995 -----------
1024 -----------
996
1025
997 Precursor to ``bundle2`` capability that was used before bundle2 was a
1026 Precursor to ``bundle2`` capability that was used before bundle2 was a
998 stable feature.
1027 stable feature.
999
1028
1000 This capability was introduced in Mercurial 3.0 behind an experimental
1029 This capability was introduced in Mercurial 3.0 behind an experimental
1001 flag. This capability should not be observed in the wild.
1030 flag. This capability should not be observed in the wild.
1002
1031
1003 bundle2
1032 bundle2
1004 -------
1033 -------
1005
1034
1006 Indicates whether the server supports the ``bundle2`` data exchange format.
1035 Indicates whether the server supports the ``bundle2`` data exchange format.
1007
1036
1008 The value of the capability is a URL quoted, newline (``\n``) delimited
1037 The value of the capability is a URL quoted, newline (``\n``) delimited
1009 list of keys or key-value pairs.
1038 list of keys or key-value pairs.
1010
1039
1011 A key is simply a URL encoded string.
1040 A key is simply a URL encoded string.
1012
1041
1013 A key-value pair is a URL encoded key separated from a URL encoded value by
1042 A key-value pair is a URL encoded key separated from a URL encoded value by
1014 an ``=``. If the value is a list, elements are delimited by a ``,`` after
1043 an ``=``. If the value is a list, elements are delimited by a ``,`` after
1015 URL encoding.
1044 URL encoding.
1016
1045
1017 For example, say we have the values::
1046 For example, say we have the values::
1018
1047
1019 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
1048 {'HG20': [], 'changegroup': ['01', '02'], 'digests': ['sha1', 'sha512']}
1020
1049
1021 We would first construct a string::
1050 We would first construct a string::
1022
1051
1023 HG20\nchangegroup=01,02\ndigests=sha1,sha512
1052 HG20\nchangegroup=01,02\ndigests=sha1,sha512
1024
1053
1025 We would then URL quote this string::
1054 We would then URL quote this string::
1026
1055
1027 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
1056 HG20%0Achangegroup%3D01%2C02%0Adigests%3Dsha1%2Csha512
1028
1057
1029 This capability was introduced in Mercurial 3.4 (released May 2015).
1058 This capability was introduced in Mercurial 3.4 (released May 2015).
1030
1059
1031 changegroupsubset
1060 changegroupsubset
1032 -----------------
1061 -----------------
1033
1062
1034 Whether the server supports the ``changegroupsubset`` command.
1063 Whether the server supports the ``changegroupsubset`` command.
1035
1064
1036 This capability was introduced in Mercurial 0.9.2 (released December
1065 This capability was introduced in Mercurial 0.9.2 (released December
1037 2006).
1066 2006).
1038
1067
1039 This capability was introduced at the same time as the ``lookup``
1068 This capability was introduced at the same time as the ``lookup``
1040 capability/command.
1069 capability/command.
1041
1070
1042 compression
1071 compression
1043 -----------
1072 -----------
1044
1073
1045 Declares support for negotiating compression formats.
1074 Declares support for negotiating compression formats.
1046
1075
1047 Presence of this capability indicates the server supports dynamic selection
1076 Presence of this capability indicates the server supports dynamic selection
1048 of compression formats based on the client request.
1077 of compression formats based on the client request.
1049
1078
1050 Servers advertising this capability are required to support the
1079 Servers advertising this capability are required to support the
1051 ``application/mercurial-0.2`` media type in response to commands returning
1080 ``application/mercurial-0.2`` media type in response to commands returning
1052 streams. Servers may support this media type on any command.
1081 streams. Servers may support this media type on any command.
1053
1082
1054 The value of the capability is a comma-delimited list of strings declaring
1083 The value of the capability is a comma-delimited list of strings declaring
1055 supported compression formats. The order of the compression formats is in
1084 supported compression formats. The order of the compression formats is in
1056 server-preferred order, most preferred first.
1085 server-preferred order, most preferred first.
1057
1086
1058 The identifiers used by the official Mercurial distribution are:
1087 The identifiers used by the official Mercurial distribution are:
1059
1088
1060 bzip2
1089 bzip2
1061 bzip2
1090 bzip2
1062 none
1091 none
1063 uncompressed / raw data
1092 uncompressed / raw data
1064 zlib
1093 zlib
1065 zlib (no gzip header)
1094 zlib (no gzip header)
1066 zstd
1095 zstd
1067 zstd
1096 zstd
1068
1097
1069 This capability was introduced in Mercurial 4.1 (released February 2017).
1098 This capability was introduced in Mercurial 4.1 (released February 2017).
1070
1099
1071 getbundle
1100 getbundle
1072 ---------
1101 ---------
1073
1102
1074 Whether the server supports the ``getbundle`` command.
1103 Whether the server supports the ``getbundle`` command.
1075
1104
1076 This capability was introduced in Mercurial 1.9 (released July 2011).
1105 This capability was introduced in Mercurial 1.9 (released July 2011).
1077
1106
1078 httpheader
1107 httpheader
1079 ----------
1108 ----------
1080
1109
1081 Whether the server supports receiving command arguments via HTTP request
1110 Whether the server supports receiving command arguments via HTTP request
1082 headers.
1111 headers.
1083
1112
1084 The value of the capability is an integer describing the max header
1113 The value of the capability is an integer describing the max header
1085 length that clients should send. Clients should ignore any content after a
1114 length that clients should send. Clients should ignore any content after a
1086 comma in the value, as this is reserved for future use.
1115 comma in the value, as this is reserved for future use.
1087
1116
1088 This capability was introduced in Mercurial 1.9 (released July 2011).
1117 This capability was introduced in Mercurial 1.9 (released July 2011).
1089
1118
1090 httpmediatype
1119 httpmediatype
1091 -------------
1120 -------------
1092
1121
1093 Indicates which HTTP media types (``Content-Type`` header) the server is
1122 Indicates which HTTP media types (``Content-Type`` header) the server is
1094 capable of receiving and sending.
1123 capable of receiving and sending.
1095
1124
1096 The value of the capability is a comma-delimited list of strings identifying
1125 The value of the capability is a comma-delimited list of strings identifying
1097 support for media type and transmission direction. The following strings may
1126 support for media type and transmission direction. The following strings may
1098 be present:
1127 be present:
1099
1128
1100 0.1rx
1129 0.1rx
1101 Indicates server support for receiving ``application/mercurial-0.1`` media
1130 Indicates server support for receiving ``application/mercurial-0.1`` media
1102 types.
1131 types.
1103
1132
1104 0.1tx
1133 0.1tx
1105 Indicates server support for sending ``application/mercurial-0.1`` media
1134 Indicates server support for sending ``application/mercurial-0.1`` media
1106 types.
1135 types.
1107
1136
1108 0.2rx
1137 0.2rx
1109 Indicates server support for receiving ``application/mercurial-0.2`` media
1138 Indicates server support for receiving ``application/mercurial-0.2`` media
1110 types.
1139 types.
1111
1140
1112 0.2tx
1141 0.2tx
1113 Indicates server support for sending ``application/mercurial-0.2`` media
1142 Indicates server support for sending ``application/mercurial-0.2`` media
1114 types.
1143 types.
1115
1144
1116 minrx=X
1145 minrx=X
1117 Minimum media type version the server is capable of receiving. Value is a
1146 Minimum media type version the server is capable of receiving. Value is a
1118 string like ``0.2``.
1147 string like ``0.2``.
1119
1148
1120 This capability can be used by servers to limit connections from legacy
1149 This capability can be used by servers to limit connections from legacy
1121 clients not using the latest supported media type. However, only clients
1150 clients not using the latest supported media type. However, only clients
1122 with knowledge of this capability will know to consult this value. This
1151 with knowledge of this capability will know to consult this value. This
1123 capability is present so the client may issue a more user-friendly error
1152 capability is present so the client may issue a more user-friendly error
1124 when the server has locked out a legacy client.
1153 when the server has locked out a legacy client.
1125
1154
1126 mintx=X
1155 mintx=X
1127 Minimum media type version the server is capable of sending. Value is a
1156 Minimum media type version the server is capable of sending. Value is a
1128 string like ``0.1``.
1157 string like ``0.1``.
1129
1158
1130 Servers advertising support for the ``application/mercurial-0.2`` media type
1159 Servers advertising support for the ``application/mercurial-0.2`` media type
1131 should also advertise the ``compression`` capability.
1160 should also advertise the ``compression`` capability.
1132
1161
1133 This capability was introduced in Mercurial 4.1 (released February 2017).
1162 This capability was introduced in Mercurial 4.1 (released February 2017).
1134
1163
1135 httppostargs
1164 httppostargs
1136 ------------
1165 ------------
1137
1166
1138 **Experimental**
1167 **Experimental**
1139
1168
1140 Indicates that the server supports and prefers clients send command arguments
1169 Indicates that the server supports and prefers clients send command arguments
1141 via a HTTP POST request as part of the request body.
1170 via a HTTP POST request as part of the request body.
1142
1171
1143 This capability was introduced in Mercurial 3.8 (released May 2016).
1172 This capability was introduced in Mercurial 3.8 (released May 2016).
1144
1173
1145 known
1174 known
1146 -----
1175 -----
1147
1176
1148 Whether the server supports the ``known`` command.
1177 Whether the server supports the ``known`` command.
1149
1178
1150 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1179 This capability/command was introduced in Mercurial 1.9 (released July 2011).
1151
1180
1152 lookup
1181 lookup
1153 ------
1182 ------
1154
1183
1155 Whether the server supports the ``lookup`` command.
1184 Whether the server supports the ``lookup`` command.
1156
1185
1157 This capability was introduced in Mercurial 0.9.2 (released December
1186 This capability was introduced in Mercurial 0.9.2 (released December
1158 2006).
1187 2006).
1159
1188
1160 This capability was introduced at the same time as the ``changegroupsubset``
1189 This capability was introduced at the same time as the ``changegroupsubset``
1161 capability/command.
1190 capability/command.
1162
1191
1163 partial-pull
1192 partial-pull
1164 ------------
1193 ------------
1165
1194
1166 Indicates that the client can deal with partial answers to pull requests
1195 Indicates that the client can deal with partial answers to pull requests
1167 by repeating the request.
1196 by repeating the request.
1168
1197
1169 If this parameter is not advertised, the server will not send pull bundles.
1198 If this parameter is not advertised, the server will not send pull bundles.
1170
1199
1171 This client capability was introduced in Mercurial 4.6.
1200 This client capability was introduced in Mercurial 4.6.
1172
1201
1173 protocaps
1202 protocaps
1174 ---------
1203 ---------
1175
1204
1176 Whether the server supports the ``protocaps`` command for SSH V1 transport.
1205 Whether the server supports the ``protocaps`` command for SSH V1 transport.
1177
1206
1178 This capability was introduced in Mercurial 4.6.
1207 This capability was introduced in Mercurial 4.6.
1179
1208
1180 pushkey
1209 pushkey
1181 -------
1210 -------
1182
1211
1183 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
1212 Whether the server supports the ``pushkey`` and ``listkeys`` commands.
1184
1213
1185 This capability was introduced in Mercurial 1.6 (released July 2010).
1214 This capability was introduced in Mercurial 1.6 (released July 2010).
1186
1215
1187 standardbundle
1216 standardbundle
1188 --------------
1217 --------------
1189
1218
1190 **Unsupported**
1219 **Unsupported**
1191
1220
1192 This capability was introduced during the Mercurial 0.9.2 development cycle in
1221 This capability was introduced during the Mercurial 0.9.2 development cycle in
1193 2006. It was never present in a release, as it was replaced by the ``unbundle``
1222 2006. It was never present in a release, as it was replaced by the ``unbundle``
1194 capability. This capability should not be encountered in the wild.
1223 capability. This capability should not be encountered in the wild.
1195
1224
1196 stream-preferred
1225 stream-preferred
1197 ----------------
1226 ----------------
1198
1227
1199 If present the server prefers that clients clone using the streaming clone
1228 If present the server prefers that clients clone using the streaming clone
1200 protocol (``hg clone --stream``) rather than the standard
1229 protocol (``hg clone --stream``) rather than the standard
1201 changegroup/bundle based protocol.
1230 changegroup/bundle based protocol.
1202
1231
1203 This capability was introduced in Mercurial 2.2 (released May 2012).
1232 This capability was introduced in Mercurial 2.2 (released May 2012).
1204
1233
1205 streamreqs
1234 streamreqs
1206 ----------
1235 ----------
1207
1236
1208 Indicates whether the server supports *streaming clones* and the *requirements*
1237 Indicates whether the server supports *streaming clones* and the *requirements*
1209 that clients must support to receive it.
1238 that clients must support to receive it.
1210
1239
1211 If present, the server supports the ``stream_out`` command, which transmits
1240 If present, the server supports the ``stream_out`` command, which transmits
1212 raw revlogs from the repository instead of changegroups. This provides a faster
1241 raw revlogs from the repository instead of changegroups. This provides a faster
1213 cloning mechanism at the expense of more bandwidth used.
1242 cloning mechanism at the expense of more bandwidth used.
1214
1243
1215 The value of this capability is a comma-delimited list of repo format
1244 The value of this capability is a comma-delimited list of repo format
1216 *requirements*. These are requirements that impact the reading of data in
1245 *requirements*. These are requirements that impact the reading of data in
1217 the ``.hg/store`` directory. An example value is
1246 the ``.hg/store`` directory. An example value is
1218 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1247 ``streamreqs=generaldelta,revlogv1`` indicating the server repo requires
1219 the ``revlogv1`` and ``generaldelta`` requirements.
1248 the ``revlogv1`` and ``generaldelta`` requirements.
1220
1249
1221 If the only format requirement is ``revlogv1``, the server may expose the
1250 If the only format requirement is ``revlogv1``, the server may expose the
1222 ``stream`` capability instead of the ``streamreqs`` capability.
1251 ``stream`` capability instead of the ``streamreqs`` capability.
1223
1252
1224 This capability was introduced in Mercurial 1.7 (released November 2010).
1253 This capability was introduced in Mercurial 1.7 (released November 2010).
1225
1254
1226 stream
1255 stream
1227 ------
1256 ------
1228
1257
1229 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1258 Whether the server supports *streaming clones* from ``revlogv1`` repos.
1230
1259
1231 If present, the server supports the ``stream_out`` command, which transmits
1260 If present, the server supports the ``stream_out`` command, which transmits
1232 raw revlogs from the repository instead of changegroups. This provides a faster
1261 raw revlogs from the repository instead of changegroups. This provides a faster
1233 cloning mechanism at the expense of more bandwidth used.
1262 cloning mechanism at the expense of more bandwidth used.
1234
1263
1235 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1264 This capability was introduced in Mercurial 0.9.1 (released July 2006).
1236
1265
1237 When initially introduced, the value of the capability was the numeric
1266 When initially introduced, the value of the capability was the numeric
1238 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1267 revlog revision. e.g. ``stream=1``. This indicates the changegroup is using
1239 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1268 ``revlogv1``. This simple integer value wasn't powerful enough, so the
1240 ``streamreqs`` capability was invented to handle cases where the repo
1269 ``streamreqs`` capability was invented to handle cases where the repo
1241 requirements have more than just ``revlogv1``. Newer servers omit the
1270 requirements have more than just ``revlogv1``. Newer servers omit the
1242 ``=1`` since it was the only value supported and the value of ``1`` can
1271 ``=1`` since it was the only value supported and the value of ``1`` can
1243 be implied by clients.
1272 be implied by clients.
1244
1273
1245 unbundlehash
1274 unbundlehash
1246 ------------
1275 ------------
1247
1276
1248 Whether the ``unbundle`` commands supports receiving a hash of all the
1277 Whether the ``unbundle`` commands supports receiving a hash of all the
1249 heads instead of a list.
1278 heads instead of a list.
1250
1279
1251 For more, see the documentation for the ``unbundle`` command.
1280 For more, see the documentation for the ``unbundle`` command.
1252
1281
1253 This capability was introduced in Mercurial 1.9 (released July 2011).
1282 This capability was introduced in Mercurial 1.9 (released July 2011).
1254
1283
1255 unbundle
1284 unbundle
1256 --------
1285 --------
1257
1286
1258 Whether the server supports pushing via the ``unbundle`` command.
1287 Whether the server supports pushing via the ``unbundle`` command.
1259
1288
1260 This capability/command has been present since Mercurial 0.9.1 (released
1289 This capability/command has been present since Mercurial 0.9.1 (released
1261 July 2006).
1290 July 2006).
1262
1291
1263 Mercurial 0.9.2 (released December 2006) added values to the capability
1292 Mercurial 0.9.2 (released December 2006) added values to the capability
1264 indicating which bundle types the server supports receiving. This value is a
1293 indicating which bundle types the server supports receiving. This value is a
1265 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1294 comma-delimited list. e.g. ``HG10GZ,HG10BZ,HG10UN``. The order of values
1266 reflects the priority/preference of that type, where the first value is the
1295 reflects the priority/preference of that type, where the first value is the
1267 most preferred type.
1296 most preferred type.
1268
1297
1269 Content Negotiation
1298 Content Negotiation
1270 ===================
1299 ===================
1271
1300
1272 The wire protocol has some mechanisms to help peers determine what content
1301 The wire protocol has some mechanisms to help peers determine what content
1273 types and encoding the other side will accept. Historically, these mechanisms
1302 types and encoding the other side will accept. Historically, these mechanisms
1274 have been built into commands themselves because most commands only send a
1303 have been built into commands themselves because most commands only send a
1275 well-defined response type and only certain commands needed to support
1304 well-defined response type and only certain commands needed to support
1276 functionality like compression.
1305 functionality like compression.
1277
1306
1278 Currently, only the HTTP version 1 transport supports content negotiation
1307 Currently, only the HTTP version 1 transport supports content negotiation
1279 at the protocol layer.
1308 at the protocol layer.
1280
1309
1281 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1310 HTTP requests advertise supported response formats via the ``X-HgProto-<N>``
1282 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1311 request header, where ``<N>`` is an integer starting at 1 allowing the logical
1283 value to span multiple headers. This value consists of a list of
1312 value to span multiple headers. This value consists of a list of
1284 space-delimited parameters. Each parameter denotes a feature or capability.
1313 space-delimited parameters. Each parameter denotes a feature or capability.
1285
1314
1286 The following parameters are defined:
1315 The following parameters are defined:
1287
1316
1288 0.1
1317 0.1
1289 Indicates the client supports receiving ``application/mercurial-0.1``
1318 Indicates the client supports receiving ``application/mercurial-0.1``
1290 responses.
1319 responses.
1291
1320
1292 0.2
1321 0.2
1293 Indicates the client supports receiving ``application/mercurial-0.2``
1322 Indicates the client supports receiving ``application/mercurial-0.2``
1294 responses.
1323 responses.
1295
1324
1296 cbor
1325 cbor
1297 Indicates the client supports receiving ``application/mercurial-cbor``
1326 Indicates the client supports receiving ``application/mercurial-cbor``
1298 responses.
1327 responses.
1299
1328
1300 (Only intended to be used with version 2 transports.)
1329 (Only intended to be used with version 2 transports.)
1301
1330
1302 comp
1331 comp
1303 Indicates compression formats the client can decode. Value is a list of
1332 Indicates compression formats the client can decode. Value is a list of
1304 comma delimited strings identifying compression formats ordered from
1333 comma delimited strings identifying compression formats ordered from
1305 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1334 most preferential to least preferential. e.g. ``comp=zstd,zlib,none``.
1306
1335
1307 This parameter does not have an effect if only the ``0.1`` parameter
1336 This parameter does not have an effect if only the ``0.1`` parameter
1308 is defined, as support for ``application/mercurial-0.2`` or greater is
1337 is defined, as support for ``application/mercurial-0.2`` or greater is
1309 required to use arbitrary compression formats.
1338 required to use arbitrary compression formats.
1310
1339
1311 If this parameter is not advertised, the server interprets this as
1340 If this parameter is not advertised, the server interprets this as
1312 equivalent to ``zlib,none``.
1341 equivalent to ``zlib,none``.
1313
1342
1314 Clients may choose to only send this header if the ``httpmediatype``
1343 Clients may choose to only send this header if the ``httpmediatype``
1315 server capability is present, as currently all server-side features
1344 server capability is present, as currently all server-side features
1316 consulting this header require the client to opt in to new protocol features
1345 consulting this header require the client to opt in to new protocol features
1317 advertised via the ``httpmediatype`` capability.
1346 advertised via the ``httpmediatype`` capability.
1318
1347
1319 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1348 A server that doesn't receive an ``X-HgProto-<N>`` header should infer a
1320 value of ``0.1``. This is compatible with legacy clients.
1349 value of ``0.1``. This is compatible with legacy clients.
1321
1350
1322 A server receiving a request indicating support for multiple media type
1351 A server receiving a request indicating support for multiple media type
1323 versions may respond with any of the supported media types. Not all servers
1352 versions may respond with any of the supported media types. Not all servers
1324 may support all media types on all commands.
1353 may support all media types on all commands.
1325
1354
1326 Commands
1355 Commands
1327 ========
1356 ========
1328
1357
1329 This section contains a list of all wire protocol commands implemented by
1358 This section contains a list of all wire protocol commands implemented by
1330 the canonical Mercurial server.
1359 the canonical Mercurial server.
1331
1360
1332 batch
1361 batch
1333 -----
1362 -----
1334
1363
1335 Issue multiple commands while sending a single command request. The purpose
1364 Issue multiple commands while sending a single command request. The purpose
1336 of this command is to allow a client to issue multiple commands while avoiding
1365 of this command is to allow a client to issue multiple commands while avoiding
1337 multiple round trips to the server therefore enabling commands to complete
1366 multiple round trips to the server therefore enabling commands to complete
1338 quicker.
1367 quicker.
1339
1368
1340 The command accepts a ``cmds`` argument that contains a list of commands to
1369 The command accepts a ``cmds`` argument that contains a list of commands to
1341 execute.
1370 execute.
1342
1371
1343 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1372 The value of ``cmds`` is a ``;`` delimited list of strings. Each string has the
1344 form ``<command> <arguments>``. That is, the command name followed by a space
1373 form ``<command> <arguments>``. That is, the command name followed by a space
1345 followed by an argument string.
1374 followed by an argument string.
1346
1375
1347 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1376 The argument string is a ``,`` delimited list of ``<key>=<value>`` values
1348 corresponding to command arguments. Both the argument name and value are
1377 corresponding to command arguments. Both the argument name and value are
1349 escaped using a special substitution map::
1378 escaped using a special substitution map::
1350
1379
1351 : -> :c
1380 : -> :c
1352 , -> :o
1381 , -> :o
1353 ; -> :s
1382 ; -> :s
1354 = -> :e
1383 = -> :e
1355
1384
1356 The response type for this command is ``string``. The value contains a
1385 The response type for this command is ``string``. The value contains a
1357 ``;`` delimited list of responses for each requested command. Each value
1386 ``;`` delimited list of responses for each requested command. Each value
1358 in this list is escaped using the same substitution map used for arguments.
1387 in this list is escaped using the same substitution map used for arguments.
1359
1388
1360 If an error occurs, the generic error response may be sent.
1389 If an error occurs, the generic error response may be sent.
1361
1390
1362 between
1391 between
1363 -------
1392 -------
1364
1393
1365 (Legacy command used for discovery in old clients)
1394 (Legacy command used for discovery in old clients)
1366
1395
1367 Obtain nodes between pairs of nodes.
1396 Obtain nodes between pairs of nodes.
1368
1397
1369 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1398 The ``pairs`` arguments contains a space-delimited list of ``-`` delimited
1370 hex node pairs. e.g.::
1399 hex node pairs. e.g.::
1371
1400
1372 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1401 a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896-6dc58916e7c070f678682bfe404d2e2d68291a18
1373
1402
1374 Return type is a ``string``. Value consists of lines corresponding to each
1403 Return type is a ``string``. Value consists of lines corresponding to each
1375 requested range. Each line contains a space-delimited list of hex nodes.
1404 requested range. Each line contains a space-delimited list of hex nodes.
1376 A newline ``\n`` terminates each line, including the last one.
1405 A newline ``\n`` terminates each line, including the last one.
1377
1406
1378 branchmap
1407 branchmap
1379 ---------
1408 ---------
1380
1409
1381 Obtain heads in named branches.
1410 Obtain heads in named branches.
1382
1411
1383 Accepts no arguments. Return type is a ``string``.
1412 Accepts no arguments. Return type is a ``string``.
1384
1413
1385 Return value contains lines with URL encoded branch names followed by a space
1414 Return value contains lines with URL encoded branch names followed by a space
1386 followed by a space-delimited list of hex nodes of heads on that branch.
1415 followed by a space-delimited list of hex nodes of heads on that branch.
1387 e.g.::
1416 e.g.::
1388
1417
1389 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1418 default a072279d3f7fd3a4aa7ffa1a5af8efc573e1c896 6dc58916e7c070f678682bfe404d2e2d68291a18
1390 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1419 stable baae3bf31522f41dd5e6d7377d0edd8d1cf3fccc
1391
1420
1392 There is no trailing newline.
1421 There is no trailing newline.
1393
1422
1394 branches
1423 branches
1395 --------
1424 --------
1396
1425
1397 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1426 (Legacy command used for discovery in old clients. Clients with ``getbundle``
1398 use the ``known`` and ``heads`` commands instead.)
1427 use the ``known`` and ``heads`` commands instead.)
1399
1428
1400 Obtain ancestor changesets of specific nodes back to a branch point.
1429 Obtain ancestor changesets of specific nodes back to a branch point.
1401
1430
1402 Despite the name, this command has nothing to do with Mercurial named branches.
1431 Despite the name, this command has nothing to do with Mercurial named branches.
1403 Instead, it is related to DAG branches.
1432 Instead, it is related to DAG branches.
1404
1433
1405 The command accepts a ``nodes`` argument, which is a string of space-delimited
1434 The command accepts a ``nodes`` argument, which is a string of space-delimited
1406 hex nodes.
1435 hex nodes.
1407
1436
1408 For each node requested, the server will find the first ancestor node that is
1437 For each node requested, the server will find the first ancestor node that is
1409 a DAG root or is a merge.
1438 a DAG root or is a merge.
1410
1439
1411 Return type is a ``string``. Return value contains lines with result data for
1440 Return type is a ``string``. Return value contains lines with result data for
1412 each requested node. Each line contains space-delimited nodes followed by a
1441 each requested node. Each line contains space-delimited nodes followed by a
1413 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1442 newline (``\n``). The 4 nodes reported on each line correspond to the requested
1414 node, the ancestor node found, and its 2 parent nodes (which may be the null
1443 node, the ancestor node found, and its 2 parent nodes (which may be the null
1415 node).
1444 node).
1416
1445
1417 capabilities
1446 capabilities
1418 ------------
1447 ------------
1419
1448
1420 Obtain the capabilities string for the repo.
1449 Obtain the capabilities string for the repo.
1421
1450
1422 Unlike the ``hello`` command, the capabilities string is not prefixed.
1451 Unlike the ``hello`` command, the capabilities string is not prefixed.
1423 There is no trailing newline.
1452 There is no trailing newline.
1424
1453
1425 This command does not accept any arguments. Return type is a ``string``.
1454 This command does not accept any arguments. Return type is a ``string``.
1426
1455
1427 This command was introduced in Mercurial 0.9.1 (released July 2006).
1456 This command was introduced in Mercurial 0.9.1 (released July 2006).
1428
1457
1429 changegroup
1458 changegroup
1430 -----------
1459 -----------
1431
1460
1432 (Legacy command: use ``getbundle`` instead)
1461 (Legacy command: use ``getbundle`` instead)
1433
1462
1434 Obtain a changegroup version 1 with data for changesets that are
1463 Obtain a changegroup version 1 with data for changesets that are
1435 descendants of client-specified changesets.
1464 descendants of client-specified changesets.
1436
1465
1437 The ``roots`` arguments contains a list of space-delimited hex nodes.
1466 The ``roots`` arguments contains a list of space-delimited hex nodes.
1438
1467
1439 The server responds with a changegroup version 1 containing all
1468 The server responds with a changegroup version 1 containing all
1440 changesets between the requested root/base nodes and the repo's head nodes
1469 changesets between the requested root/base nodes and the repo's head nodes
1441 at the time of the request.
1470 at the time of the request.
1442
1471
1443 The return type is a ``stream``.
1472 The return type is a ``stream``.
1444
1473
1445 changegroupsubset
1474 changegroupsubset
1446 -----------------
1475 -----------------
1447
1476
1448 (Legacy command: use ``getbundle`` instead)
1477 (Legacy command: use ``getbundle`` instead)
1449
1478
1450 Obtain a changegroup version 1 with data for changesetsets between
1479 Obtain a changegroup version 1 with data for changesetsets between
1451 client specified base and head nodes.
1480 client specified base and head nodes.
1452
1481
1453 The ``bases`` argument contains a list of space-delimited hex nodes.
1482 The ``bases`` argument contains a list of space-delimited hex nodes.
1454 The ``heads`` argument contains a list of space-delimited hex nodes.
1483 The ``heads`` argument contains a list of space-delimited hex nodes.
1455
1484
1456 The server responds with a changegroup version 1 containing all
1485 The server responds with a changegroup version 1 containing all
1457 changesets between the requested base and head nodes at the time of the
1486 changesets between the requested base and head nodes at the time of the
1458 request.
1487 request.
1459
1488
1460 The return type is a ``stream``.
1489 The return type is a ``stream``.
1461
1490
1462 clonebundles
1491 clonebundles
1463 ------------
1492 ------------
1464
1493
1465 Obtains a manifest of bundle URLs available to seed clones.
1494 Obtains a manifest of bundle URLs available to seed clones.
1466
1495
1467 Each returned line contains a URL followed by metadata. See the
1496 Each returned line contains a URL followed by metadata. See the
1468 documentation in the ``clonebundles`` extension for more.
1497 documentation in the ``clonebundles`` extension for more.
1469
1498
1470 The return type is a ``string``.
1499 The return type is a ``string``.
1471
1500
1472 getbundle
1501 getbundle
1473 ---------
1502 ---------
1474
1503
1475 Obtain a bundle containing repository data.
1504 Obtain a bundle containing repository data.
1476
1505
1477 This command accepts the following arguments:
1506 This command accepts the following arguments:
1478
1507
1479 heads
1508 heads
1480 List of space-delimited hex nodes of heads to retrieve.
1509 List of space-delimited hex nodes of heads to retrieve.
1481 common
1510 common
1482 List of space-delimited hex nodes that the client has in common with the
1511 List of space-delimited hex nodes that the client has in common with the
1483 server.
1512 server.
1484 obsmarkers
1513 obsmarkers
1485 Boolean indicating whether to include obsolescence markers as part
1514 Boolean indicating whether to include obsolescence markers as part
1486 of the response. Only works with bundle2.
1515 of the response. Only works with bundle2.
1487 bundlecaps
1516 bundlecaps
1488 Comma-delimited set of strings defining client bundle capabilities.
1517 Comma-delimited set of strings defining client bundle capabilities.
1489 listkeys
1518 listkeys
1490 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1519 Comma-delimited list of strings of ``pushkey`` namespaces. For each
1491 namespace listed, a bundle2 part will be included with the content of
1520 namespace listed, a bundle2 part will be included with the content of
1492 that namespace.
1521 that namespace.
1493 cg
1522 cg
1494 Boolean indicating whether changegroup data is requested.
1523 Boolean indicating whether changegroup data is requested.
1495 cbattempted
1524 cbattempted
1496 Boolean indicating whether the client attempted to use the *clone bundles*
1525 Boolean indicating whether the client attempted to use the *clone bundles*
1497 feature before performing this request.
1526 feature before performing this request.
1498 bookmarks
1527 bookmarks
1499 Boolean indicating whether bookmark data is requested.
1528 Boolean indicating whether bookmark data is requested.
1500 phases
1529 phases
1501 Boolean indicating whether phases data is requested.
1530 Boolean indicating whether phases data is requested.
1502
1531
1503 The return type on success is a ``stream`` where the value is bundle.
1532 The return type on success is a ``stream`` where the value is bundle.
1504 On the HTTP version 1 transport, the response is zlib compressed.
1533 On the HTTP version 1 transport, the response is zlib compressed.
1505
1534
1506 If an error occurs, a generic error response can be sent.
1535 If an error occurs, a generic error response can be sent.
1507
1536
1508 Unless the client sends a false value for the ``cg`` argument, the returned
1537 Unless the client sends a false value for the ``cg`` argument, the returned
1509 bundle contains a changegroup with the nodes between the specified ``common``
1538 bundle contains a changegroup with the nodes between the specified ``common``
1510 and ``heads`` nodes. Depending on the command arguments, the type and content
1539 and ``heads`` nodes. Depending on the command arguments, the type and content
1511 of the returned bundle can vary significantly.
1540 of the returned bundle can vary significantly.
1512
1541
1513 The default behavior is for the server to send a raw changegroup version
1542 The default behavior is for the server to send a raw changegroup version
1514 ``01`` response.
1543 ``01`` response.
1515
1544
1516 If the ``bundlecaps`` provided by the client contain a value beginning
1545 If the ``bundlecaps`` provided by the client contain a value beginning
1517 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1546 with ``HG2``, a bundle2 will be returned. The bundle2 data may contain
1518 additional repository data, such as ``pushkey`` namespace values.
1547 additional repository data, such as ``pushkey`` namespace values.
1519
1548
1520 heads
1549 heads
1521 -----
1550 -----
1522
1551
1523 Returns a list of space-delimited hex nodes of repository heads followed
1552 Returns a list of space-delimited hex nodes of repository heads followed
1524 by a newline. e.g.
1553 by a newline. e.g.
1525 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1554 ``a9eeb3adc7ddb5006c088e9eda61791c777cbf7c 31f91a3da534dc849f0d6bfc00a395a97cf218a1\n``
1526
1555
1527 This command does not accept any arguments. The return type is a ``string``.
1556 This command does not accept any arguments. The return type is a ``string``.
1528
1557
1529 hello
1558 hello
1530 -----
1559 -----
1531
1560
1532 Returns lines describing interesting things about the server in an RFC-822
1561 Returns lines describing interesting things about the server in an RFC-822
1533 like format.
1562 like format.
1534
1563
1535 Currently, the only line defines the server capabilities. It has the form::
1564 Currently, the only line defines the server capabilities. It has the form::
1536
1565
1537 capabilities: <value>
1566 capabilities: <value>
1538
1567
1539 See above for more about the capabilities string.
1568 See above for more about the capabilities string.
1540
1569
1541 SSH clients typically issue this command as soon as a connection is
1570 SSH clients typically issue this command as soon as a connection is
1542 established.
1571 established.
1543
1572
1544 This command does not accept any arguments. The return type is a ``string``.
1573 This command does not accept any arguments. The return type is a ``string``.
1545
1574
1546 This command was introduced in Mercurial 0.9.1 (released July 2006).
1575 This command was introduced in Mercurial 0.9.1 (released July 2006).
1547
1576
1548 listkeys
1577 listkeys
1549 --------
1578 --------
1550
1579
1551 List values in a specified ``pushkey`` namespace.
1580 List values in a specified ``pushkey`` namespace.
1552
1581
1553 The ``namespace`` argument defines the pushkey namespace to operate on.
1582 The ``namespace`` argument defines the pushkey namespace to operate on.
1554
1583
1555 The return type is a ``string``. The value is an encoded dictionary of keys.
1584 The return type is a ``string``. The value is an encoded dictionary of keys.
1556
1585
1557 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1586 Key-value pairs are delimited by newlines (``\n``). Within each line, keys and
1558 values are separated by a tab (``\t``). Keys and values are both strings.
1587 values are separated by a tab (``\t``). Keys and values are both strings.
1559
1588
1560 lookup
1589 lookup
1561 ------
1590 ------
1562
1591
1563 Try to resolve a value to a known repository revision.
1592 Try to resolve a value to a known repository revision.
1564
1593
1565 The ``key`` argument is converted from bytes to an
1594 The ``key`` argument is converted from bytes to an
1566 ``encoding.localstr`` instance then passed into
1595 ``encoding.localstr`` instance then passed into
1567 ``localrepository.__getitem__`` in an attempt to resolve it.
1596 ``localrepository.__getitem__`` in an attempt to resolve it.
1568
1597
1569 The return type is a ``string``.
1598 The return type is a ``string``.
1570
1599
1571 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1600 Upon successful resolution, returns ``1 <hex node>\n``. On failure,
1572 returns ``0 <error string>\n``. e.g.::
1601 returns ``0 <error string>\n``. e.g.::
1573
1602
1574 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1603 1 273ce12ad8f155317b2c078ec75a4eba507f1fba\n
1575
1604
1576 0 unknown revision 'foo'\n
1605 0 unknown revision 'foo'\n
1577
1606
1578 known
1607 known
1579 -----
1608 -----
1580
1609
1581 Determine whether multiple nodes are known.
1610 Determine whether multiple nodes are known.
1582
1611
1583 The ``nodes`` argument is a list of space-delimited hex nodes to check
1612 The ``nodes`` argument is a list of space-delimited hex nodes to check
1584 for existence.
1613 for existence.
1585
1614
1586 The return type is ``string``.
1615 The return type is ``string``.
1587
1616
1588 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1617 Returns a string consisting of ``0``s and ``1``s indicating whether nodes
1589 are known. If the Nth node specified in the ``nodes`` argument is known,
1618 are known. If the Nth node specified in the ``nodes`` argument is known,
1590 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1619 a ``1`` will be returned at byte offset N. If the node isn't known, ``0``
1591 will be present at byte offset N.
1620 will be present at byte offset N.
1592
1621
1593 There is no trailing newline.
1622 There is no trailing newline.
1594
1623
1595 protocaps
1624 protocaps
1596 ---------
1625 ---------
1597
1626
1598 Notify the server about the client capabilities in the SSH V1 transport
1627 Notify the server about the client capabilities in the SSH V1 transport
1599 protocol.
1628 protocol.
1600
1629
1601 The ``caps`` argument is a space-delimited list of capabilities.
1630 The ``caps`` argument is a space-delimited list of capabilities.
1602
1631
1603 The server will reply with the string ``OK``.
1632 The server will reply with the string ``OK``.
1604
1633
1605 pushkey
1634 pushkey
1606 -------
1635 -------
1607
1636
1608 Set a value using the ``pushkey`` protocol.
1637 Set a value using the ``pushkey`` protocol.
1609
1638
1610 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1639 Accepts arguments ``namespace``, ``key``, ``old``, and ``new``, which
1611 correspond to the pushkey namespace to operate on, the key within that
1640 correspond to the pushkey namespace to operate on, the key within that
1612 namespace to change, the old value (which may be empty), and the new value.
1641 namespace to change, the old value (which may be empty), and the new value.
1613 All arguments are string types.
1642 All arguments are string types.
1614
1643
1615 The return type is a ``string``. The value depends on the transport protocol.
1644 The return type is a ``string``. The value depends on the transport protocol.
1616
1645
1617 The SSH version 1 transport sends a string encoded integer followed by a
1646 The SSH version 1 transport sends a string encoded integer followed by a
1618 newline (``\n``) which indicates operation result. The server may send
1647 newline (``\n``) which indicates operation result. The server may send
1619 additional output on the ``stderr`` stream that should be displayed to the
1648 additional output on the ``stderr`` stream that should be displayed to the
1620 user.
1649 user.
1621
1650
1622 The HTTP version 1 transport sends a string encoded integer followed by a
1651 The HTTP version 1 transport sends a string encoded integer followed by a
1623 newline followed by additional server output that should be displayed to
1652 newline followed by additional server output that should be displayed to
1624 the user. This may include output from hooks, etc.
1653 the user. This may include output from hooks, etc.
1625
1654
1626 The integer result varies by namespace. ``0`` means an error has occurred
1655 The integer result varies by namespace. ``0`` means an error has occurred
1627 and there should be additional output to display to the user.
1656 and there should be additional output to display to the user.
1628
1657
1629 stream_out
1658 stream_out
1630 ----------
1659 ----------
1631
1660
1632 Obtain *streaming clone* data.
1661 Obtain *streaming clone* data.
1633
1662
1634 The return type is either a ``string`` or a ``stream``, depending on
1663 The return type is either a ``string`` or a ``stream``, depending on
1635 whether the request was fulfilled properly.
1664 whether the request was fulfilled properly.
1636
1665
1637 A return value of ``1\n`` indicates the server is not configured to serve
1666 A return value of ``1\n`` indicates the server is not configured to serve
1638 this data. If this is seen by the client, they may not have verified the
1667 this data. If this is seen by the client, they may not have verified the
1639 ``stream`` capability is set before making the request.
1668 ``stream`` capability is set before making the request.
1640
1669
1641 A return value of ``2\n`` indicates the server was unable to lock the
1670 A return value of ``2\n`` indicates the server was unable to lock the
1642 repository to generate data.
1671 repository to generate data.
1643
1672
1644 All other responses are a ``stream`` of bytes. The first line of this data
1673 All other responses are a ``stream`` of bytes. The first line of this data
1645 contains 2 space-delimited integers corresponding to the path count and
1674 contains 2 space-delimited integers corresponding to the path count and
1646 payload size, respectively::
1675 payload size, respectively::
1647
1676
1648 <path count> <payload size>\n
1677 <path count> <payload size>\n
1649
1678
1650 The ``<payload size>`` is the total size of path data: it does not include
1679 The ``<payload size>`` is the total size of path data: it does not include
1651 the size of the per-path header lines.
1680 the size of the per-path header lines.
1652
1681
1653 Following that header are ``<path count>`` entries. Each entry consists of a
1682 Following that header are ``<path count>`` entries. Each entry consists of a
1654 line with metadata followed by raw revlog data. The line consists of::
1683 line with metadata followed by raw revlog data. The line consists of::
1655
1684
1656 <store path>\0<size>\n
1685 <store path>\0<size>\n
1657
1686
1658 The ``<store path>`` is the encoded store path of the data that follows.
1687 The ``<store path>`` is the encoded store path of the data that follows.
1659 ``<size>`` is the amount of data for this store path/revlog that follows the
1688 ``<size>`` is the amount of data for this store path/revlog that follows the
1660 newline.
1689 newline.
1661
1690
1662 There is no trailer to indicate end of data. Instead, the client should stop
1691 There is no trailer to indicate end of data. Instead, the client should stop
1663 reading after ``<path count>`` entries are consumed.
1692 reading after ``<path count>`` entries are consumed.
1664
1693
1665 unbundle
1694 unbundle
1666 --------
1695 --------
1667
1696
1668 Send a bundle containing data (usually changegroup data) to the server.
1697 Send a bundle containing data (usually changegroup data) to the server.
1669
1698
1670 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1699 Accepts the argument ``heads``, which is a space-delimited list of hex nodes
1671 corresponding to server repository heads observed by the client. This is used
1700 corresponding to server repository heads observed by the client. This is used
1672 to detect race conditions and abort push operations before a server performs
1701 to detect race conditions and abort push operations before a server performs
1673 too much work or a client transfers too much data.
1702 too much work or a client transfers too much data.
1674
1703
1675 The request payload consists of a bundle to be applied to the repository,
1704 The request payload consists of a bundle to be applied to the repository,
1676 similarly to as if :hg:`unbundle` were called.
1705 similarly to as if :hg:`unbundle` were called.
1677
1706
1678 In most scenarios, a special ``push response`` type is returned. This type
1707 In most scenarios, a special ``push response`` type is returned. This type
1679 contains an integer describing the change in heads as a result of the
1708 contains an integer describing the change in heads as a result of the
1680 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1709 operation. A value of ``0`` indicates nothing changed. ``1`` means the number
1681 of heads remained the same. Values ``2`` and larger indicate the number of
1710 of heads remained the same. Values ``2`` and larger indicate the number of
1682 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1711 added heads minus 1. e.g. ``3`` means 2 heads were added. Negative values
1683 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1712 indicate the number of fewer heads, also off by 1. e.g. ``-2`` means there
1684 is 1 fewer head.
1713 is 1 fewer head.
1685
1714
1686 The encoding of the ``push response`` type varies by transport.
1715 The encoding of the ``push response`` type varies by transport.
1687
1716
1688 For the SSH version 1 transport, this type is composed of 2 ``string``
1717 For the SSH version 1 transport, this type is composed of 2 ``string``
1689 responses: an empty response (``0\n``) followed by the integer result value.
1718 responses: an empty response (``0\n``) followed by the integer result value.
1690 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1719 e.g. ``1\n2``. So the full response might be ``0\n1\n2``.
1691
1720
1692 For the HTTP version 1 transport, the response is a ``string`` type composed
1721 For the HTTP version 1 transport, the response is a ``string`` type composed
1693 of an integer result value followed by a newline (``\n``) followed by string
1722 of an integer result value followed by a newline (``\n``) followed by string
1694 content holding server output that should be displayed on the client (output
1723 content holding server output that should be displayed on the client (output
1695 hooks, etc).
1724 hooks, etc).
1696
1725
1697 In some cases, the server may respond with a ``bundle2`` bundle. In this
1726 In some cases, the server may respond with a ``bundle2`` bundle. In this
1698 case, the response type is ``stream``. For the HTTP version 1 transport, the
1727 case, the response type is ``stream``. For the HTTP version 1 transport, the
1699 response is zlib compressed.
1728 response is zlib compressed.
1700
1729
1701 The server may also respond with a generic error type, which contains a string
1730 The server may also respond with a generic error type, which contains a string
1702 indicating the failure.
1731 indicating the failure.
1703
1732
1704 Frame-Based Protocol Commands
1733 Frame-Based Protocol Commands
1705 =============================
1734 =============================
1706
1735
1707 **Experimental and under active development**
1736 **Experimental and under active development**
1708
1737
1709 This section documents the wire protocol commands exposed to transports
1738 This section documents the wire protocol commands exposed to transports
1710 using the frame-based protocol. The set of commands exposed through
1739 using the frame-based protocol. The set of commands exposed through
1711 these transports is distinct from the set of commands exposed to legacy
1740 these transports is distinct from the set of commands exposed to legacy
1712 transports.
1741 transports.
1713
1742
1714 The frame-based protocol uses CBOR to encode command execution requests.
1743 The frame-based protocol uses CBOR to encode command execution requests.
1715 All command arguments must be mapped to a specific or set of CBOR data
1744 All command arguments must be mapped to a specific or set of CBOR data
1716 types.
1745 types.
1717
1746
1718 The response to many commands is also CBOR. There is no common response
1747 The response to many commands is also CBOR. There is no common response
1719 format: each command defines its own response format.
1748 format: each command defines its own response format.
1720
1749
1721 TODO require node type be specified, as N bytes of binary node value
1750 TODO require node type be specified, as N bytes of binary node value
1722 could be ambiguous once SHA-1 is replaced.
1751 could be ambiguous once SHA-1 is replaced.
1723
1752
1724 branchmap
1753 branchmap
1725 ---------
1754 ---------
1726
1755
1727 Obtain heads in named branches.
1756 Obtain heads in named branches.
1728
1757
1729 Receives no arguments.
1758 Receives no arguments.
1730
1759
1731 The response is a map with bytestring keys defining the branch name.
1760 The response is a map with bytestring keys defining the branch name.
1732 Values are arrays of bytestring defining raw changeset nodes.
1761 Values are arrays of bytestring defining raw changeset nodes.
1733
1762
1734 capabilities
1763 capabilities
1735 ------------
1764 ------------
1736
1765
1737 Obtain the server's capabilities.
1766 Obtain the server's capabilities.
1738
1767
1739 Receives no arguments.
1768 Receives no arguments.
1740
1769
1741 This command is typically called only as part of the handshake during
1770 This command is typically called only as part of the handshake during
1742 initial connection establishment.
1771 initial connection establishment.
1743
1772
1744 The response is a map with bytestring keys defining server information.
1773 The response is a map with bytestring keys defining server information.
1745
1774
1746 The defined keys are:
1775 The defined keys are:
1747
1776
1748 commands
1777 commands
1749 A map defining available wire protocol commands on this server.
1778 A map defining available wire protocol commands on this server.
1750
1779
1751 Keys in the map are the names of commands that can be invoked. Values
1780 Keys in the map are the names of commands that can be invoked. Values
1752 are maps defining information about that command. The bytestring keys
1781 are maps defining information about that command. The bytestring keys
1753 are:
1782 are:
1754
1783
1755 args
1784 args
1756 A map of argument names and their expected types.
1785 A map of argument names and their expected types.
1757
1786
1758 Types are defined as a representative value for the expected type.
1787 Types are defined as a representative value for the expected type.
1759 e.g. an argument expecting a boolean type will have its value
1788 e.g. an argument expecting a boolean type will have its value
1760 set to true. An integer type will have its value set to 42. The
1789 set to true. An integer type will have its value set to 42. The
1761 actual values are arbitrary and may not have meaning.
1790 actual values are arbitrary and may not have meaning.
1762 permissions
1791 permissions
1763 An array of permissions required to execute this command.
1792 An array of permissions required to execute this command.
1764
1793
1765 compression
1794 compression
1766 An array of maps defining available compression format support.
1795 An array of maps defining available compression format support.
1767
1796
1768 The array is sorted from most preferred to least preferred.
1797 The array is sorted from most preferred to least preferred.
1769
1798
1770 Each entry has the following bytestring keys:
1799 Each entry has the following bytestring keys:
1771
1800
1772 name
1801 name
1773 Name of the compression engine. e.g. ``zstd`` or ``zlib``.
1802 Name of the compression engine. e.g. ``zstd`` or ``zlib``.
1774
1803
1775 framingmediatypes
1804 framingmediatypes
1776 An array of bytestrings defining the supported framing protocol
1805 An array of bytestrings defining the supported framing protocol
1777 media types. Servers will not accept media types not in this list.
1806 media types. Servers will not accept media types not in this list.
1778
1807
1779 rawrepoformats
1808 rawrepoformats
1780 An array of storage formats the repository is using. This set of
1809 An array of storage formats the repository is using. This set of
1781 requirements can be used to determine whether a client can read a
1810 requirements can be used to determine whether a client can read a
1782 *raw* copy of file data available.
1811 *raw* copy of file data available.
1783
1812
1784 heads
1813 heads
1785 -----
1814 -----
1786
1815
1787 Obtain DAG heads in the repository.
1816 Obtain DAG heads in the repository.
1788
1817
1789 The command accepts the following arguments:
1818 The command accepts the following arguments:
1790
1819
1791 publiconly (optional)
1820 publiconly (optional)
1792 (boolean) If set, operate on the DAG for public phase changesets only.
1821 (boolean) If set, operate on the DAG for public phase changesets only.
1793 Non-public (i.e. draft) phase DAG heads will not be returned.
1822 Non-public (i.e. draft) phase DAG heads will not be returned.
1794
1823
1795 The response is a CBOR array of bytestrings defining changeset nodes
1824 The response is a CBOR array of bytestrings defining changeset nodes
1796 of DAG heads. The array can be empty if the repository is empty or no
1825 of DAG heads. The array can be empty if the repository is empty or no
1797 changesets satisfied the request.
1826 changesets satisfied the request.
1798
1827
1799 TODO consider exposing phase of heads in response
1828 TODO consider exposing phase of heads in response
1800
1829
1801 known
1830 known
1802 -----
1831 -----
1803
1832
1804 Determine whether a series of changeset nodes is known to the server.
1833 Determine whether a series of changeset nodes is known to the server.
1805
1834
1806 The command accepts the following arguments:
1835 The command accepts the following arguments:
1807
1836
1808 nodes
1837 nodes
1809 (array of bytestrings) List of changeset nodes whose presence to
1838 (array of bytestrings) List of changeset nodes whose presence to
1810 query.
1839 query.
1811
1840
1812 The response is a bytestring where each byte contains a 0 or 1 for the
1841 The response is a bytestring where each byte contains a 0 or 1 for the
1813 corresponding requested node at the same index.
1842 corresponding requested node at the same index.
1814
1843
1815 TODO use a bit array for even more compact response
1844 TODO use a bit array for even more compact response
1816
1845
1817 listkeys
1846 listkeys
1818 --------
1847 --------
1819
1848
1820 List values in a specified ``pushkey`` namespace.
1849 List values in a specified ``pushkey`` namespace.
1821
1850
1822 The command receives the following arguments:
1851 The command receives the following arguments:
1823
1852
1824 namespace
1853 namespace
1825 (bytestring) Pushkey namespace to query.
1854 (bytestring) Pushkey namespace to query.
1826
1855
1827 The response is a map with bytestring keys and values.
1856 The response is a map with bytestring keys and values.
1828
1857
1829 TODO consider using binary to represent nodes in certain pushkey namespaces.
1858 TODO consider using binary to represent nodes in certain pushkey namespaces.
1830
1859
1831 lookup
1860 lookup
1832 ------
1861 ------
1833
1862
1834 Try to resolve a value to a changeset revision.
1863 Try to resolve a value to a changeset revision.
1835
1864
1836 Unlike ``known`` which operates on changeset nodes, lookup operates on
1865 Unlike ``known`` which operates on changeset nodes, lookup operates on
1837 node fragments and other names that a user may use.
1866 node fragments and other names that a user may use.
1838
1867
1839 The command receives the following arguments:
1868 The command receives the following arguments:
1840
1869
1841 key
1870 key
1842 (bytestring) Value to try to resolve.
1871 (bytestring) Value to try to resolve.
1843
1872
1844 On success, returns a bytestring containing the resolved node.
1873 On success, returns a bytestring containing the resolved node.
1845
1874
1846 pushkey
1875 pushkey
1847 -------
1876 -------
1848
1877
1849 Set a value using the ``pushkey`` protocol.
1878 Set a value using the ``pushkey`` protocol.
1850
1879
1851 The command receives the following arguments:
1880 The command receives the following arguments:
1852
1881
1853 namespace
1882 namespace
1854 (bytestring) Pushkey namespace to operate on.
1883 (bytestring) Pushkey namespace to operate on.
1855 key
1884 key
1856 (bytestring) The pushkey key to set.
1885 (bytestring) The pushkey key to set.
1857 old
1886 old
1858 (bytestring) Old value for this key.
1887 (bytestring) Old value for this key.
1859 new
1888 new
1860 (bytestring) New value for this key.
1889 (bytestring) New value for this key.
1861
1890
1862 TODO consider using binary to represent nodes is certain pushkey namespaces.
1891 TODO consider using binary to represent nodes is certain pushkey namespaces.
1863 TODO better define response type and meaning.
1892 TODO better define response type and meaning.
@@ -1,1062 +1,1073 b''
1 # wireprotoframing.py - unified framing protocol for wire protocol
1 # wireprotoframing.py - unified framing protocol for wire protocol
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # This file contains functionality to support the unified frame-based wire
8 # This file contains functionality to support the unified frame-based wire
9 # protocol. For details about the protocol, see
9 # protocol. For details about the protocol, see
10 # `hg help internals.wireprotocol`.
10 # `hg help internals.wireprotocol`.
11
11
12 from __future__ import absolute_import
12 from __future__ import absolute_import
13
13
14 import collections
14 import collections
15 import struct
15 import struct
16
16
17 from .i18n import _
17 from .i18n import _
18 from .thirdparty import (
18 from .thirdparty import (
19 attr,
19 attr,
20 cbor,
20 cbor,
21 )
21 )
22 from . import (
22 from . import (
23 encoding,
23 encoding,
24 error,
24 error,
25 util,
25 util,
26 )
26 )
27 from .utils import (
27 from .utils import (
28 stringutil,
28 stringutil,
29 )
29 )
30
30
31 FRAME_HEADER_SIZE = 8
31 FRAME_HEADER_SIZE = 8
32 DEFAULT_MAX_FRAME_SIZE = 32768
32 DEFAULT_MAX_FRAME_SIZE = 32768
33
33
34 STREAM_FLAG_BEGIN_STREAM = 0x01
34 STREAM_FLAG_BEGIN_STREAM = 0x01
35 STREAM_FLAG_END_STREAM = 0x02
35 STREAM_FLAG_END_STREAM = 0x02
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
36 STREAM_FLAG_ENCODING_APPLIED = 0x04
37
37
38 STREAM_FLAGS = {
38 STREAM_FLAGS = {
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
39 b'stream-begin': STREAM_FLAG_BEGIN_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
40 b'stream-end': STREAM_FLAG_END_STREAM,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
41 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
42 }
42 }
43
43
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
44 FRAME_TYPE_COMMAND_REQUEST = 0x01
45 FRAME_TYPE_COMMAND_DATA = 0x02
45 FRAME_TYPE_COMMAND_DATA = 0x02
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
46 FRAME_TYPE_COMMAND_RESPONSE = 0x03
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
47 FRAME_TYPE_ERROR_RESPONSE = 0x05
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
48 FRAME_TYPE_TEXT_OUTPUT = 0x06
49 FRAME_TYPE_PROGRESS = 0x07
49 FRAME_TYPE_PROGRESS = 0x07
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
50 FRAME_TYPE_STREAM_SETTINGS = 0x08
51
51
52 FRAME_TYPES = {
52 FRAME_TYPES = {
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
53 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
55 b'command-response': FRAME_TYPE_COMMAND_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
57 b'text-output': FRAME_TYPE_TEXT_OUTPUT,
58 b'progress': FRAME_TYPE_PROGRESS,
58 b'progress': FRAME_TYPE_PROGRESS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
60 }
60 }
61
61
62 FLAG_COMMAND_REQUEST_NEW = 0x01
62 FLAG_COMMAND_REQUEST_NEW = 0x01
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
63 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
64 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
65 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
66
66
67 FLAGS_COMMAND_REQUEST = {
67 FLAGS_COMMAND_REQUEST = {
68 b'new': FLAG_COMMAND_REQUEST_NEW,
68 b'new': FLAG_COMMAND_REQUEST_NEW,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
69 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
70 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
71 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
72 }
72 }
73
73
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
74 FLAG_COMMAND_DATA_CONTINUATION = 0x01
75 FLAG_COMMAND_DATA_EOS = 0x02
75 FLAG_COMMAND_DATA_EOS = 0x02
76
76
77 FLAGS_COMMAND_DATA = {
77 FLAGS_COMMAND_DATA = {
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
78 b'continuation': FLAG_COMMAND_DATA_CONTINUATION,
79 b'eos': FLAG_COMMAND_DATA_EOS,
79 b'eos': FLAG_COMMAND_DATA_EOS,
80 }
80 }
81
81
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
82 FLAG_COMMAND_RESPONSE_CONTINUATION = 0x01
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
83 FLAG_COMMAND_RESPONSE_EOS = 0x02
84
84
85 FLAGS_COMMAND_RESPONSE = {
85 FLAGS_COMMAND_RESPONSE = {
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
86 b'continuation': FLAG_COMMAND_RESPONSE_CONTINUATION,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
87 b'eos': FLAG_COMMAND_RESPONSE_EOS,
88 }
88 }
89
89
90 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
90 FLAG_ERROR_RESPONSE_PROTOCOL = 0x01
91 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
91 FLAG_ERROR_RESPONSE_APPLICATION = 0x02
92
92
93 FLAGS_ERROR_RESPONSE = {
93 FLAGS_ERROR_RESPONSE = {
94 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
94 b'protocol': FLAG_ERROR_RESPONSE_PROTOCOL,
95 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
95 b'application': FLAG_ERROR_RESPONSE_APPLICATION,
96 }
96 }
97
97
98 # Maps frame types to their available flags.
98 # Maps frame types to their available flags.
99 FRAME_TYPE_FLAGS = {
99 FRAME_TYPE_FLAGS = {
100 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
100 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
101 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
101 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
102 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
102 FRAME_TYPE_COMMAND_RESPONSE: FLAGS_COMMAND_RESPONSE,
103 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
103 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
104 FRAME_TYPE_TEXT_OUTPUT: {},
104 FRAME_TYPE_TEXT_OUTPUT: {},
105 FRAME_TYPE_PROGRESS: {},
105 FRAME_TYPE_PROGRESS: {},
106 FRAME_TYPE_STREAM_SETTINGS: {},
106 FRAME_TYPE_STREAM_SETTINGS: {},
107 }
107 }
108
108
109 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
109 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
110
110
111 def humanflags(mapping, value):
111 def humanflags(mapping, value):
112 """Convert a numeric flags value to a human value, using a mapping table."""
112 """Convert a numeric flags value to a human value, using a mapping table."""
113 namemap = {v: k for k, v in mapping.iteritems()}
113 namemap = {v: k for k, v in mapping.iteritems()}
114 flags = []
114 flags = []
115 val = 1
115 val = 1
116 while value >= val:
116 while value >= val:
117 if value & val:
117 if value & val:
118 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
118 flags.append(namemap.get(val, '<unknown 0x%02x>' % val))
119 val <<= 1
119 val <<= 1
120
120
121 return b'|'.join(flags)
121 return b'|'.join(flags)
122
122
123 @attr.s(slots=True)
123 @attr.s(slots=True)
124 class frameheader(object):
124 class frameheader(object):
125 """Represents the data in a frame header."""
125 """Represents the data in a frame header."""
126
126
127 length = attr.ib()
127 length = attr.ib()
128 requestid = attr.ib()
128 requestid = attr.ib()
129 streamid = attr.ib()
129 streamid = attr.ib()
130 streamflags = attr.ib()
130 streamflags = attr.ib()
131 typeid = attr.ib()
131 typeid = attr.ib()
132 flags = attr.ib()
132 flags = attr.ib()
133
133
134 @attr.s(slots=True, repr=False)
134 @attr.s(slots=True, repr=False)
135 class frame(object):
135 class frame(object):
136 """Represents a parsed frame."""
136 """Represents a parsed frame."""
137
137
138 requestid = attr.ib()
138 requestid = attr.ib()
139 streamid = attr.ib()
139 streamid = attr.ib()
140 streamflags = attr.ib()
140 streamflags = attr.ib()
141 typeid = attr.ib()
141 typeid = attr.ib()
142 flags = attr.ib()
142 flags = attr.ib()
143 payload = attr.ib()
143 payload = attr.ib()
144
144
145 @encoding.strmethod
145 @encoding.strmethod
146 def __repr__(self):
146 def __repr__(self):
147 typename = '<unknown 0x%02x>' % self.typeid
147 typename = '<unknown 0x%02x>' % self.typeid
148 for name, value in FRAME_TYPES.iteritems():
148 for name, value in FRAME_TYPES.iteritems():
149 if value == self.typeid:
149 if value == self.typeid:
150 typename = name
150 typename = name
151 break
151 break
152
152
153 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
153 return ('frame(size=%d; request=%d; stream=%d; streamflags=%s; '
154 'type=%s; flags=%s)' % (
154 'type=%s; flags=%s)' % (
155 len(self.payload), self.requestid, self.streamid,
155 len(self.payload), self.requestid, self.streamid,
156 humanflags(STREAM_FLAGS, self.streamflags), typename,
156 humanflags(STREAM_FLAGS, self.streamflags), typename,
157 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
157 humanflags(FRAME_TYPE_FLAGS.get(self.typeid, {}), self.flags)))
158
158
159 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
159 def makeframe(requestid, streamid, streamflags, typeid, flags, payload):
160 """Assemble a frame into a byte array."""
160 """Assemble a frame into a byte array."""
161 # TODO assert size of payload.
161 # TODO assert size of payload.
162 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
162 frame = bytearray(FRAME_HEADER_SIZE + len(payload))
163
163
164 # 24 bits length
164 # 24 bits length
165 # 16 bits request id
165 # 16 bits request id
166 # 8 bits stream id
166 # 8 bits stream id
167 # 8 bits stream flags
167 # 8 bits stream flags
168 # 4 bits type
168 # 4 bits type
169 # 4 bits flags
169 # 4 bits flags
170
170
171 l = struct.pack(r'<I', len(payload))
171 l = struct.pack(r'<I', len(payload))
172 frame[0:3] = l[0:3]
172 frame[0:3] = l[0:3]
173 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
173 struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags)
174 frame[7] = (typeid << 4) | flags
174 frame[7] = (typeid << 4) | flags
175 frame[8:] = payload
175 frame[8:] = payload
176
176
177 return frame
177 return frame
178
178
179 def makeframefromhumanstring(s):
179 def makeframefromhumanstring(s):
180 """Create a frame from a human readable string
180 """Create a frame from a human readable string
181
181
182 Strings have the form:
182 Strings have the form:
183
183
184 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
184 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
185
185
186 This can be used by user-facing applications and tests for creating
186 This can be used by user-facing applications and tests for creating
187 frames easily without having to type out a bunch of constants.
187 frames easily without having to type out a bunch of constants.
188
188
189 Request ID and stream IDs are integers.
189 Request ID and stream IDs are integers.
190
190
191 Stream flags, frame type, and flags can be specified by integer or
191 Stream flags, frame type, and flags can be specified by integer or
192 named constant.
192 named constant.
193
193
194 Flags can be delimited by `|` to bitwise OR them together.
194 Flags can be delimited by `|` to bitwise OR them together.
195
195
196 If the payload begins with ``cbor:``, the following string will be
196 If the payload begins with ``cbor:``, the following string will be
197 evaluated as Python literal and the resulting object will be fed into
197 evaluated as Python literal and the resulting object will be fed into
198 a CBOR encoder. Otherwise, the payload is interpreted as a Python
198 a CBOR encoder. Otherwise, the payload is interpreted as a Python
199 byte string literal.
199 byte string literal.
200 """
200 """
201 fields = s.split(b' ', 5)
201 fields = s.split(b' ', 5)
202 requestid, streamid, streamflags, frametype, frameflags, payload = fields
202 requestid, streamid, streamflags, frametype, frameflags, payload = fields
203
203
204 requestid = int(requestid)
204 requestid = int(requestid)
205 streamid = int(streamid)
205 streamid = int(streamid)
206
206
207 finalstreamflags = 0
207 finalstreamflags = 0
208 for flag in streamflags.split(b'|'):
208 for flag in streamflags.split(b'|'):
209 if flag in STREAM_FLAGS:
209 if flag in STREAM_FLAGS:
210 finalstreamflags |= STREAM_FLAGS[flag]
210 finalstreamflags |= STREAM_FLAGS[flag]
211 else:
211 else:
212 finalstreamflags |= int(flag)
212 finalstreamflags |= int(flag)
213
213
214 if frametype in FRAME_TYPES:
214 if frametype in FRAME_TYPES:
215 frametype = FRAME_TYPES[frametype]
215 frametype = FRAME_TYPES[frametype]
216 else:
216 else:
217 frametype = int(frametype)
217 frametype = int(frametype)
218
218
219 finalflags = 0
219 finalflags = 0
220 validflags = FRAME_TYPE_FLAGS[frametype]
220 validflags = FRAME_TYPE_FLAGS[frametype]
221 for flag in frameflags.split(b'|'):
221 for flag in frameflags.split(b'|'):
222 if flag in validflags:
222 if flag in validflags:
223 finalflags |= validflags[flag]
223 finalflags |= validflags[flag]
224 else:
224 else:
225 finalflags |= int(flag)
225 finalflags |= int(flag)
226
226
227 if payload.startswith(b'cbor:'):
227 if payload.startswith(b'cbor:'):
228 payload = cbor.dumps(stringutil.evalpythonliteral(payload[5:]),
228 payload = cbor.dumps(stringutil.evalpythonliteral(payload[5:]),
229 canonical=True)
229 canonical=True)
230
230
231 else:
231 else:
232 payload = stringutil.unescapestr(payload)
232 payload = stringutil.unescapestr(payload)
233
233
234 return makeframe(requestid=requestid, streamid=streamid,
234 return makeframe(requestid=requestid, streamid=streamid,
235 streamflags=finalstreamflags, typeid=frametype,
235 streamflags=finalstreamflags, typeid=frametype,
236 flags=finalflags, payload=payload)
236 flags=finalflags, payload=payload)
237
237
238 def parseheader(data):
238 def parseheader(data):
239 """Parse a unified framing protocol frame header from a buffer.
239 """Parse a unified framing protocol frame header from a buffer.
240
240
241 The header is expected to be in the buffer at offset 0 and the
241 The header is expected to be in the buffer at offset 0 and the
242 buffer is expected to be large enough to hold a full header.
242 buffer is expected to be large enough to hold a full header.
243 """
243 """
244 # 24 bits payload length (little endian)
244 # 24 bits payload length (little endian)
245 # 16 bits request ID
245 # 16 bits request ID
246 # 8 bits stream ID
246 # 8 bits stream ID
247 # 8 bits stream flags
247 # 8 bits stream flags
248 # 4 bits frame type
248 # 4 bits frame type
249 # 4 bits frame flags
249 # 4 bits frame flags
250 # ... payload
250 # ... payload
251 framelength = data[0] + 256 * data[1] + 16384 * data[2]
251 framelength = data[0] + 256 * data[1] + 16384 * data[2]
252 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
252 requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3)
253 typeflags = data[7]
253 typeflags = data[7]
254
254
255 frametype = (typeflags & 0xf0) >> 4
255 frametype = (typeflags & 0xf0) >> 4
256 frameflags = typeflags & 0x0f
256 frameflags = typeflags & 0x0f
257
257
258 return frameheader(framelength, requestid, streamid, streamflags,
258 return frameheader(framelength, requestid, streamid, streamflags,
259 frametype, frameflags)
259 frametype, frameflags)
260
260
261 def readframe(fh):
261 def readframe(fh):
262 """Read a unified framing protocol frame from a file object.
262 """Read a unified framing protocol frame from a file object.
263
263
264 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
264 Returns a 3-tuple of (type, flags, payload) for the decoded frame or
265 None if no frame is available. May raise if a malformed frame is
265 None if no frame is available. May raise if a malformed frame is
266 seen.
266 seen.
267 """
267 """
268 header = bytearray(FRAME_HEADER_SIZE)
268 header = bytearray(FRAME_HEADER_SIZE)
269
269
270 readcount = fh.readinto(header)
270 readcount = fh.readinto(header)
271
271
272 if readcount == 0:
272 if readcount == 0:
273 return None
273 return None
274
274
275 if readcount != FRAME_HEADER_SIZE:
275 if readcount != FRAME_HEADER_SIZE:
276 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
276 raise error.Abort(_('received incomplete frame: got %d bytes: %s') %
277 (readcount, header))
277 (readcount, header))
278
278
279 h = parseheader(header)
279 h = parseheader(header)
280
280
281 payload = fh.read(h.length)
281 payload = fh.read(h.length)
282 if len(payload) != h.length:
282 if len(payload) != h.length:
283 raise error.Abort(_('frame length error: expected %d; got %d') %
283 raise error.Abort(_('frame length error: expected %d; got %d') %
284 (h.length, len(payload)))
284 (h.length, len(payload)))
285
285
286 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
286 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
287 payload)
287 payload)
288
288
289 def createcommandframes(stream, requestid, cmd, args, datafh=None,
289 def createcommandframes(stream, requestid, cmd, args, datafh=None,
290 maxframesize=DEFAULT_MAX_FRAME_SIZE):
290 maxframesize=DEFAULT_MAX_FRAME_SIZE):
291 """Create frames necessary to transmit a request to run a command.
291 """Create frames necessary to transmit a request to run a command.
292
292
293 This is a generator of bytearrays. Each item represents a frame
293 This is a generator of bytearrays. Each item represents a frame
294 ready to be sent over the wire to a peer.
294 ready to be sent over the wire to a peer.
295 """
295 """
296 data = {b'name': cmd}
296 data = {b'name': cmd}
297 if args:
297 if args:
298 data[b'args'] = args
298 data[b'args'] = args
299
299
300 data = cbor.dumps(data, canonical=True)
300 data = cbor.dumps(data, canonical=True)
301
301
302 offset = 0
302 offset = 0
303
303
304 while True:
304 while True:
305 flags = 0
305 flags = 0
306
306
307 # Must set new or continuation flag.
307 # Must set new or continuation flag.
308 if not offset:
308 if not offset:
309 flags |= FLAG_COMMAND_REQUEST_NEW
309 flags |= FLAG_COMMAND_REQUEST_NEW
310 else:
310 else:
311 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
311 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
312
312
313 # Data frames is set on all frames.
313 # Data frames is set on all frames.
314 if datafh:
314 if datafh:
315 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
315 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
316
316
317 payload = data[offset:offset + maxframesize]
317 payload = data[offset:offset + maxframesize]
318 offset += len(payload)
318 offset += len(payload)
319
319
320 if len(payload) == maxframesize and offset < len(data):
320 if len(payload) == maxframesize and offset < len(data):
321 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
321 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
322
322
323 yield stream.makeframe(requestid=requestid,
323 yield stream.makeframe(requestid=requestid,
324 typeid=FRAME_TYPE_COMMAND_REQUEST,
324 typeid=FRAME_TYPE_COMMAND_REQUEST,
325 flags=flags,
325 flags=flags,
326 payload=payload)
326 payload=payload)
327
327
328 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
328 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
329 break
329 break
330
330
331 if datafh:
331 if datafh:
332 while True:
332 while True:
333 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
333 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
334
334
335 done = False
335 done = False
336 if len(data) == DEFAULT_MAX_FRAME_SIZE:
336 if len(data) == DEFAULT_MAX_FRAME_SIZE:
337 flags = FLAG_COMMAND_DATA_CONTINUATION
337 flags = FLAG_COMMAND_DATA_CONTINUATION
338 else:
338 else:
339 flags = FLAG_COMMAND_DATA_EOS
339 flags = FLAG_COMMAND_DATA_EOS
340 assert datafh.read(1) == b''
340 assert datafh.read(1) == b''
341 done = True
341 done = True
342
342
343 yield stream.makeframe(requestid=requestid,
343 yield stream.makeframe(requestid=requestid,
344 typeid=FRAME_TYPE_COMMAND_DATA,
344 typeid=FRAME_TYPE_COMMAND_DATA,
345 flags=flags,
345 flags=flags,
346 payload=data)
346 payload=data)
347
347
348 if done:
348 if done:
349 break
349 break
350
350
351 def createcommandresponseframesfrombytes(stream, requestid, data,
351 def createcommandresponseframesfrombytes(stream, requestid, data,
352 maxframesize=DEFAULT_MAX_FRAME_SIZE):
352 maxframesize=DEFAULT_MAX_FRAME_SIZE):
353 """Create a raw frame to send a bytes response from static bytes input.
353 """Create a raw frame to send a bytes response from static bytes input.
354
354
355 Returns a generator of bytearrays.
355 Returns a generator of bytearrays.
356 """
356 """
357 # Automatically send the overall CBOR response map.
358 overall = cbor.dumps({b'status': b'ok'}, canonical=True)
359 if len(overall) > maxframesize:
360 raise error.ProgrammingError('not yet implemented')
357
361
358 # Simple case of a single frame.
362 # Simple case where we can fit the full response in a single frame.
359 if len(data) <= maxframesize:
363 if len(overall) + len(data) <= maxframesize:
360 flags = FLAG_COMMAND_RESPONSE_EOS
364 flags = FLAG_COMMAND_RESPONSE_EOS
361 yield stream.makeframe(requestid=requestid,
365 yield stream.makeframe(requestid=requestid,
362 typeid=FRAME_TYPE_COMMAND_RESPONSE,
366 typeid=FRAME_TYPE_COMMAND_RESPONSE,
363 flags=flags,
367 flags=flags,
364 payload=data)
368 payload=overall + data)
365 return
369 return
366
370
371 # It's easier to send the overall CBOR map in its own frame than to track
372 # offsets.
373 yield stream.makeframe(requestid=requestid,
374 typeid=FRAME_TYPE_COMMAND_RESPONSE,
375 flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
376 payload=overall)
377
367 offset = 0
378 offset = 0
368 while True:
379 while True:
369 chunk = data[offset:offset + maxframesize]
380 chunk = data[offset:offset + maxframesize]
370 offset += len(chunk)
381 offset += len(chunk)
371 done = offset == len(data)
382 done = offset == len(data)
372
383
373 if done:
384 if done:
374 flags = FLAG_COMMAND_RESPONSE_EOS
385 flags = FLAG_COMMAND_RESPONSE_EOS
375 else:
386 else:
376 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
387 flags = FLAG_COMMAND_RESPONSE_CONTINUATION
377
388
378 yield stream.makeframe(requestid=requestid,
389 yield stream.makeframe(requestid=requestid,
379 typeid=FRAME_TYPE_COMMAND_RESPONSE,
390 typeid=FRAME_TYPE_COMMAND_RESPONSE,
380 flags=flags,
391 flags=flags,
381 payload=chunk)
392 payload=chunk)
382
393
383 if done:
394 if done:
384 break
395 break
385
396
386 def createerrorframe(stream, requestid, msg, protocol=False, application=False):
397 def createerrorframe(stream, requestid, msg, protocol=False, application=False):
387 # TODO properly handle frame size limits.
398 # TODO properly handle frame size limits.
388 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
399 assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
389
400
390 flags = 0
401 flags = 0
391 if protocol:
402 if protocol:
392 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
403 flags |= FLAG_ERROR_RESPONSE_PROTOCOL
393 if application:
404 if application:
394 flags |= FLAG_ERROR_RESPONSE_APPLICATION
405 flags |= FLAG_ERROR_RESPONSE_APPLICATION
395
406
396 yield stream.makeframe(requestid=requestid,
407 yield stream.makeframe(requestid=requestid,
397 typeid=FRAME_TYPE_ERROR_RESPONSE,
408 typeid=FRAME_TYPE_ERROR_RESPONSE,
398 flags=flags,
409 flags=flags,
399 payload=msg)
410 payload=msg)
400
411
401 def createtextoutputframe(stream, requestid, atoms,
412 def createtextoutputframe(stream, requestid, atoms,
402 maxframesize=DEFAULT_MAX_FRAME_SIZE):
413 maxframesize=DEFAULT_MAX_FRAME_SIZE):
403 """Create a text output frame to render text to people.
414 """Create a text output frame to render text to people.
404
415
405 ``atoms`` is a 3-tuple of (formatting string, args, labels).
416 ``atoms`` is a 3-tuple of (formatting string, args, labels).
406
417
407 The formatting string contains ``%s`` tokens to be replaced by the
418 The formatting string contains ``%s`` tokens to be replaced by the
408 corresponding indexed entry in ``args``. ``labels`` is an iterable of
419 corresponding indexed entry in ``args``. ``labels`` is an iterable of
409 formatters to be applied at rendering time. In terms of the ``ui``
420 formatters to be applied at rendering time. In terms of the ``ui``
410 class, each atom corresponds to a ``ui.write()``.
421 class, each atom corresponds to a ``ui.write()``.
411 """
422 """
412 atomdicts = []
423 atomdicts = []
413
424
414 for (formatting, args, labels) in atoms:
425 for (formatting, args, labels) in atoms:
415 # TODO look for localstr, other types here?
426 # TODO look for localstr, other types here?
416
427
417 if not isinstance(formatting, bytes):
428 if not isinstance(formatting, bytes):
418 raise ValueError('must use bytes formatting strings')
429 raise ValueError('must use bytes formatting strings')
419 for arg in args:
430 for arg in args:
420 if not isinstance(arg, bytes):
431 if not isinstance(arg, bytes):
421 raise ValueError('must use bytes for arguments')
432 raise ValueError('must use bytes for arguments')
422 for label in labels:
433 for label in labels:
423 if not isinstance(label, bytes):
434 if not isinstance(label, bytes):
424 raise ValueError('must use bytes for labels')
435 raise ValueError('must use bytes for labels')
425
436
426 # Formatting string must be ASCII.
437 # Formatting string must be ASCII.
427 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
438 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
428
439
429 # Arguments must be UTF-8.
440 # Arguments must be UTF-8.
430 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
441 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
431
442
432 # Labels must be ASCII.
443 # Labels must be ASCII.
433 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
444 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
434 for l in labels]
445 for l in labels]
435
446
436 atom = {b'msg': formatting}
447 atom = {b'msg': formatting}
437 if args:
448 if args:
438 atom[b'args'] = args
449 atom[b'args'] = args
439 if labels:
450 if labels:
440 atom[b'labels'] = labels
451 atom[b'labels'] = labels
441
452
442 atomdicts.append(atom)
453 atomdicts.append(atom)
443
454
444 payload = cbor.dumps(atomdicts, canonical=True)
455 payload = cbor.dumps(atomdicts, canonical=True)
445
456
446 if len(payload) > maxframesize:
457 if len(payload) > maxframesize:
447 raise ValueError('cannot encode data in a single frame')
458 raise ValueError('cannot encode data in a single frame')
448
459
449 yield stream.makeframe(requestid=requestid,
460 yield stream.makeframe(requestid=requestid,
450 typeid=FRAME_TYPE_TEXT_OUTPUT,
461 typeid=FRAME_TYPE_TEXT_OUTPUT,
451 flags=0,
462 flags=0,
452 payload=payload)
463 payload=payload)
453
464
454 class stream(object):
465 class stream(object):
455 """Represents a logical unidirectional series of frames."""
466 """Represents a logical unidirectional series of frames."""
456
467
457 def __init__(self, streamid, active=False):
468 def __init__(self, streamid, active=False):
458 self.streamid = streamid
469 self.streamid = streamid
459 self._active = active
470 self._active = active
460
471
461 def makeframe(self, requestid, typeid, flags, payload):
472 def makeframe(self, requestid, typeid, flags, payload):
462 """Create a frame to be sent out over this stream.
473 """Create a frame to be sent out over this stream.
463
474
464 Only returns the frame instance. Does not actually send it.
475 Only returns the frame instance. Does not actually send it.
465 """
476 """
466 streamflags = 0
477 streamflags = 0
467 if not self._active:
478 if not self._active:
468 streamflags |= STREAM_FLAG_BEGIN_STREAM
479 streamflags |= STREAM_FLAG_BEGIN_STREAM
469 self._active = True
480 self._active = True
470
481
471 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
482 return makeframe(requestid, self.streamid, streamflags, typeid, flags,
472 payload)
483 payload)
473
484
474 def ensureserverstream(stream):
485 def ensureserverstream(stream):
475 if stream.streamid % 2:
486 if stream.streamid % 2:
476 raise error.ProgrammingError('server should only write to even '
487 raise error.ProgrammingError('server should only write to even '
477 'numbered streams; %d is not even' %
488 'numbered streams; %d is not even' %
478 stream.streamid)
489 stream.streamid)
479
490
480 class serverreactor(object):
491 class serverreactor(object):
481 """Holds state of a server handling frame-based protocol requests.
492 """Holds state of a server handling frame-based protocol requests.
482
493
483 This class is the "brain" of the unified frame-based protocol server
494 This class is the "brain" of the unified frame-based protocol server
484 component. While the protocol is stateless from the perspective of
495 component. While the protocol is stateless from the perspective of
485 requests/commands, something needs to track which frames have been
496 requests/commands, something needs to track which frames have been
486 received, what frames to expect, etc. This class is that thing.
497 received, what frames to expect, etc. This class is that thing.
487
498
488 Instances are modeled as a state machine of sorts. Instances are also
499 Instances are modeled as a state machine of sorts. Instances are also
489 reactionary to external events. The point of this class is to encapsulate
500 reactionary to external events. The point of this class is to encapsulate
490 the state of the connection and the exchange of frames, not to perform
501 the state of the connection and the exchange of frames, not to perform
491 work. Instead, callers tell this class when something occurs, like a
502 work. Instead, callers tell this class when something occurs, like a
492 frame arriving. If that activity is worthy of a follow-up action (say
503 frame arriving. If that activity is worthy of a follow-up action (say
493 *run a command*), the return value of that handler will say so.
504 *run a command*), the return value of that handler will say so.
494
505
495 I/O and CPU intensive operations are purposefully delegated outside of
506 I/O and CPU intensive operations are purposefully delegated outside of
496 this class.
507 this class.
497
508
498 Consumers are expected to tell instances when events occur. They do so by
509 Consumers are expected to tell instances when events occur. They do so by
499 calling the various ``on*`` methods. These methods return a 2-tuple
510 calling the various ``on*`` methods. These methods return a 2-tuple
500 describing any follow-up action(s) to take. The first element is the
511 describing any follow-up action(s) to take. The first element is the
501 name of an action to perform. The second is a data structure (usually
512 name of an action to perform. The second is a data structure (usually
502 a dict) specific to that action that contains more information. e.g.
513 a dict) specific to that action that contains more information. e.g.
503 if the server wants to send frames back to the client, the data structure
514 if the server wants to send frames back to the client, the data structure
504 will contain a reference to those frames.
515 will contain a reference to those frames.
505
516
506 Valid actions that consumers can be instructed to take are:
517 Valid actions that consumers can be instructed to take are:
507
518
508 sendframes
519 sendframes
509 Indicates that frames should be sent to the client. The ``framegen``
520 Indicates that frames should be sent to the client. The ``framegen``
510 key contains a generator of frames that should be sent. The server
521 key contains a generator of frames that should be sent. The server
511 assumes that all frames are sent to the client.
522 assumes that all frames are sent to the client.
512
523
513 error
524 error
514 Indicates that an error occurred. Consumer should probably abort.
525 Indicates that an error occurred. Consumer should probably abort.
515
526
516 runcommand
527 runcommand
517 Indicates that the consumer should run a wire protocol command. Details
528 Indicates that the consumer should run a wire protocol command. Details
518 of the command to run are given in the data structure.
529 of the command to run are given in the data structure.
519
530
520 wantframe
531 wantframe
521 Indicates that nothing of interest happened and the server is waiting on
532 Indicates that nothing of interest happened and the server is waiting on
522 more frames from the client before anything interesting can be done.
533 more frames from the client before anything interesting can be done.
523
534
524 noop
535 noop
525 Indicates no additional action is required.
536 Indicates no additional action is required.
526
537
527 Known Issues
538 Known Issues
528 ------------
539 ------------
529
540
530 There are no limits to the number of partially received commands or their
541 There are no limits to the number of partially received commands or their
531 size. A malicious client could stream command request data and exhaust the
542 size. A malicious client could stream command request data and exhaust the
532 server's memory.
543 server's memory.
533
544
534 Partially received commands are not acted upon when end of input is
545 Partially received commands are not acted upon when end of input is
535 reached. Should the server error if it receives a partial request?
546 reached. Should the server error if it receives a partial request?
536 Should the client send a message to abort a partially transmitted request
547 Should the client send a message to abort a partially transmitted request
537 to facilitate graceful shutdown?
548 to facilitate graceful shutdown?
538
549
539 Active requests that haven't been responded to aren't tracked. This means
550 Active requests that haven't been responded to aren't tracked. This means
540 that if we receive a command and instruct its dispatch, another command
551 that if we receive a command and instruct its dispatch, another command
541 with its request ID can come in over the wire and there will be a race
552 with its request ID can come in over the wire and there will be a race
542 between who responds to what.
553 between who responds to what.
543 """
554 """
544
555
545 def __init__(self, deferoutput=False):
556 def __init__(self, deferoutput=False):
546 """Construct a new server reactor.
557 """Construct a new server reactor.
547
558
548 ``deferoutput`` can be used to indicate that no output frames should be
559 ``deferoutput`` can be used to indicate that no output frames should be
549 instructed to be sent until input has been exhausted. In this mode,
560 instructed to be sent until input has been exhausted. In this mode,
550 events that would normally generate output frames (such as a command
561 events that would normally generate output frames (such as a command
551 response being ready) will instead defer instructing the consumer to
562 response being ready) will instead defer instructing the consumer to
552 send those frames. This is useful for half-duplex transports where the
563 send those frames. This is useful for half-duplex transports where the
553 sender cannot receive until all data has been transmitted.
564 sender cannot receive until all data has been transmitted.
554 """
565 """
555 self._deferoutput = deferoutput
566 self._deferoutput = deferoutput
556 self._state = 'idle'
567 self._state = 'idle'
557 self._nextoutgoingstreamid = 2
568 self._nextoutgoingstreamid = 2
558 self._bufferedframegens = []
569 self._bufferedframegens = []
559 # stream id -> stream instance for all active streams from the client.
570 # stream id -> stream instance for all active streams from the client.
560 self._incomingstreams = {}
571 self._incomingstreams = {}
561 self._outgoingstreams = {}
572 self._outgoingstreams = {}
562 # request id -> dict of commands that are actively being received.
573 # request id -> dict of commands that are actively being received.
563 self._receivingcommands = {}
574 self._receivingcommands = {}
564 # Request IDs that have been received and are actively being processed.
575 # Request IDs that have been received and are actively being processed.
565 # Once all output for a request has been sent, it is removed from this
576 # Once all output for a request has been sent, it is removed from this
566 # set.
577 # set.
567 self._activecommands = set()
578 self._activecommands = set()
568
579
569 def onframerecv(self, frame):
580 def onframerecv(self, frame):
570 """Process a frame that has been received off the wire.
581 """Process a frame that has been received off the wire.
571
582
572 Returns a dict with an ``action`` key that details what action,
583 Returns a dict with an ``action`` key that details what action,
573 if any, the consumer should take next.
584 if any, the consumer should take next.
574 """
585 """
575 if not frame.streamid % 2:
586 if not frame.streamid % 2:
576 self._state = 'errored'
587 self._state = 'errored'
577 return self._makeerrorresult(
588 return self._makeerrorresult(
578 _('received frame with even numbered stream ID: %d') %
589 _('received frame with even numbered stream ID: %d') %
579 frame.streamid)
590 frame.streamid)
580
591
581 if frame.streamid not in self._incomingstreams:
592 if frame.streamid not in self._incomingstreams:
582 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
593 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
583 self._state = 'errored'
594 self._state = 'errored'
584 return self._makeerrorresult(
595 return self._makeerrorresult(
585 _('received frame on unknown inactive stream without '
596 _('received frame on unknown inactive stream without '
586 'beginning of stream flag set'))
597 'beginning of stream flag set'))
587
598
588 self._incomingstreams[frame.streamid] = stream(frame.streamid)
599 self._incomingstreams[frame.streamid] = stream(frame.streamid)
589
600
590 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
601 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
591 # TODO handle decoding frames
602 # TODO handle decoding frames
592 self._state = 'errored'
603 self._state = 'errored'
593 raise error.ProgrammingError('support for decoding stream payloads '
604 raise error.ProgrammingError('support for decoding stream payloads '
594 'not yet implemented')
605 'not yet implemented')
595
606
596 if frame.streamflags & STREAM_FLAG_END_STREAM:
607 if frame.streamflags & STREAM_FLAG_END_STREAM:
597 del self._incomingstreams[frame.streamid]
608 del self._incomingstreams[frame.streamid]
598
609
599 handlers = {
610 handlers = {
600 'idle': self._onframeidle,
611 'idle': self._onframeidle,
601 'command-receiving': self._onframecommandreceiving,
612 'command-receiving': self._onframecommandreceiving,
602 'errored': self._onframeerrored,
613 'errored': self._onframeerrored,
603 }
614 }
604
615
605 meth = handlers.get(self._state)
616 meth = handlers.get(self._state)
606 if not meth:
617 if not meth:
607 raise error.ProgrammingError('unhandled state: %s' % self._state)
618 raise error.ProgrammingError('unhandled state: %s' % self._state)
608
619
609 return meth(frame)
620 return meth(frame)
610
621
611 def oncommandresponseready(self, stream, requestid, data):
622 def oncommandresponseready(self, stream, requestid, data):
612 """Signal that a bytes response is ready to be sent to the client.
623 """Signal that a bytes response is ready to be sent to the client.
613
624
614 The raw bytes response is passed as an argument.
625 The raw bytes response is passed as an argument.
615 """
626 """
616 ensureserverstream(stream)
627 ensureserverstream(stream)
617
628
618 def sendframes():
629 def sendframes():
619 for frame in createcommandresponseframesfrombytes(stream, requestid,
630 for frame in createcommandresponseframesfrombytes(stream, requestid,
620 data):
631 data):
621 yield frame
632 yield frame
622
633
623 self._activecommands.remove(requestid)
634 self._activecommands.remove(requestid)
624
635
625 result = sendframes()
636 result = sendframes()
626
637
627 if self._deferoutput:
638 if self._deferoutput:
628 self._bufferedframegens.append(result)
639 self._bufferedframegens.append(result)
629 return 'noop', {}
640 return 'noop', {}
630 else:
641 else:
631 return 'sendframes', {
642 return 'sendframes', {
632 'framegen': result,
643 'framegen': result,
633 }
644 }
634
645
635 def oninputeof(self):
646 def oninputeof(self):
636 """Signals that end of input has been received.
647 """Signals that end of input has been received.
637
648
638 No more frames will be received. All pending activity should be
649 No more frames will be received. All pending activity should be
639 completed.
650 completed.
640 """
651 """
641 # TODO should we do anything about in-flight commands?
652 # TODO should we do anything about in-flight commands?
642
653
643 if not self._deferoutput or not self._bufferedframegens:
654 if not self._deferoutput or not self._bufferedframegens:
644 return 'noop', {}
655 return 'noop', {}
645
656
646 # If we buffered all our responses, emit those.
657 # If we buffered all our responses, emit those.
647 def makegen():
658 def makegen():
648 for gen in self._bufferedframegens:
659 for gen in self._bufferedframegens:
649 for frame in gen:
660 for frame in gen:
650 yield frame
661 yield frame
651
662
652 return 'sendframes', {
663 return 'sendframes', {
653 'framegen': makegen(),
664 'framegen': makegen(),
654 }
665 }
655
666
656 def onapplicationerror(self, stream, requestid, msg):
667 def onapplicationerror(self, stream, requestid, msg):
657 ensureserverstream(stream)
668 ensureserverstream(stream)
658
669
659 return 'sendframes', {
670 return 'sendframes', {
660 'framegen': createerrorframe(stream, requestid, msg,
671 'framegen': createerrorframe(stream, requestid, msg,
661 application=True),
672 application=True),
662 }
673 }
663
674
664 def makeoutputstream(self):
675 def makeoutputstream(self):
665 """Create a stream to be used for sending data to the client."""
676 """Create a stream to be used for sending data to the client."""
666 streamid = self._nextoutgoingstreamid
677 streamid = self._nextoutgoingstreamid
667 self._nextoutgoingstreamid += 2
678 self._nextoutgoingstreamid += 2
668
679
669 s = stream(streamid)
680 s = stream(streamid)
670 self._outgoingstreams[streamid] = s
681 self._outgoingstreams[streamid] = s
671
682
672 return s
683 return s
673
684
674 def _makeerrorresult(self, msg):
685 def _makeerrorresult(self, msg):
675 return 'error', {
686 return 'error', {
676 'message': msg,
687 'message': msg,
677 }
688 }
678
689
679 def _makeruncommandresult(self, requestid):
690 def _makeruncommandresult(self, requestid):
680 entry = self._receivingcommands[requestid]
691 entry = self._receivingcommands[requestid]
681
692
682 if not entry['requestdone']:
693 if not entry['requestdone']:
683 self._state = 'errored'
694 self._state = 'errored'
684 raise error.ProgrammingError('should not be called without '
695 raise error.ProgrammingError('should not be called without '
685 'requestdone set')
696 'requestdone set')
686
697
687 del self._receivingcommands[requestid]
698 del self._receivingcommands[requestid]
688
699
689 if self._receivingcommands:
700 if self._receivingcommands:
690 self._state = 'command-receiving'
701 self._state = 'command-receiving'
691 else:
702 else:
692 self._state = 'idle'
703 self._state = 'idle'
693
704
694 # Decode the payloads as CBOR.
705 # Decode the payloads as CBOR.
695 entry['payload'].seek(0)
706 entry['payload'].seek(0)
696 request = cbor.load(entry['payload'])
707 request = cbor.load(entry['payload'])
697
708
698 if b'name' not in request:
709 if b'name' not in request:
699 self._state = 'errored'
710 self._state = 'errored'
700 return self._makeerrorresult(
711 return self._makeerrorresult(
701 _('command request missing "name" field'))
712 _('command request missing "name" field'))
702
713
703 if b'args' not in request:
714 if b'args' not in request:
704 request[b'args'] = {}
715 request[b'args'] = {}
705
716
706 assert requestid not in self._activecommands
717 assert requestid not in self._activecommands
707 self._activecommands.add(requestid)
718 self._activecommands.add(requestid)
708
719
709 return 'runcommand', {
720 return 'runcommand', {
710 'requestid': requestid,
721 'requestid': requestid,
711 'command': request[b'name'],
722 'command': request[b'name'],
712 'args': request[b'args'],
723 'args': request[b'args'],
713 'data': entry['data'].getvalue() if entry['data'] else None,
724 'data': entry['data'].getvalue() if entry['data'] else None,
714 }
725 }
715
726
716 def _makewantframeresult(self):
727 def _makewantframeresult(self):
717 return 'wantframe', {
728 return 'wantframe', {
718 'state': self._state,
729 'state': self._state,
719 }
730 }
720
731
721 def _validatecommandrequestframe(self, frame):
732 def _validatecommandrequestframe(self, frame):
722 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
733 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
723 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
734 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
724
735
725 if new and continuation:
736 if new and continuation:
726 self._state = 'errored'
737 self._state = 'errored'
727 return self._makeerrorresult(
738 return self._makeerrorresult(
728 _('received command request frame with both new and '
739 _('received command request frame with both new and '
729 'continuation flags set'))
740 'continuation flags set'))
730
741
731 if not new and not continuation:
742 if not new and not continuation:
732 self._state = 'errored'
743 self._state = 'errored'
733 return self._makeerrorresult(
744 return self._makeerrorresult(
734 _('received command request frame with neither new nor '
745 _('received command request frame with neither new nor '
735 'continuation flags set'))
746 'continuation flags set'))
736
747
737 def _onframeidle(self, frame):
748 def _onframeidle(self, frame):
738 # The only frame type that should be received in this state is a
749 # The only frame type that should be received in this state is a
739 # command request.
750 # command request.
740 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
751 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
741 self._state = 'errored'
752 self._state = 'errored'
742 return self._makeerrorresult(
753 return self._makeerrorresult(
743 _('expected command request frame; got %d') % frame.typeid)
754 _('expected command request frame; got %d') % frame.typeid)
744
755
745 res = self._validatecommandrequestframe(frame)
756 res = self._validatecommandrequestframe(frame)
746 if res:
757 if res:
747 return res
758 return res
748
759
749 if frame.requestid in self._receivingcommands:
760 if frame.requestid in self._receivingcommands:
750 self._state = 'errored'
761 self._state = 'errored'
751 return self._makeerrorresult(
762 return self._makeerrorresult(
752 _('request with ID %d already received') % frame.requestid)
763 _('request with ID %d already received') % frame.requestid)
753
764
754 if frame.requestid in self._activecommands:
765 if frame.requestid in self._activecommands:
755 self._state = 'errored'
766 self._state = 'errored'
756 return self._makeerrorresult(
767 return self._makeerrorresult(
757 _('request with ID %d is already active') % frame.requestid)
768 _('request with ID %d is already active') % frame.requestid)
758
769
759 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
770 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
760 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
771 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
761 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
772 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
762
773
763 if not new:
774 if not new:
764 self._state = 'errored'
775 self._state = 'errored'
765 return self._makeerrorresult(
776 return self._makeerrorresult(
766 _('received command request frame without new flag set'))
777 _('received command request frame without new flag set'))
767
778
768 payload = util.bytesio()
779 payload = util.bytesio()
769 payload.write(frame.payload)
780 payload.write(frame.payload)
770
781
771 self._receivingcommands[frame.requestid] = {
782 self._receivingcommands[frame.requestid] = {
772 'payload': payload,
783 'payload': payload,
773 'data': None,
784 'data': None,
774 'requestdone': not moreframes,
785 'requestdone': not moreframes,
775 'expectingdata': bool(expectingdata),
786 'expectingdata': bool(expectingdata),
776 }
787 }
777
788
778 # This is the final frame for this request. Dispatch it.
789 # This is the final frame for this request. Dispatch it.
779 if not moreframes and not expectingdata:
790 if not moreframes and not expectingdata:
780 return self._makeruncommandresult(frame.requestid)
791 return self._makeruncommandresult(frame.requestid)
781
792
782 assert moreframes or expectingdata
793 assert moreframes or expectingdata
783 self._state = 'command-receiving'
794 self._state = 'command-receiving'
784 return self._makewantframeresult()
795 return self._makewantframeresult()
785
796
786 def _onframecommandreceiving(self, frame):
797 def _onframecommandreceiving(self, frame):
787 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
798 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
788 # Process new command requests as such.
799 # Process new command requests as such.
789 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
800 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
790 return self._onframeidle(frame)
801 return self._onframeidle(frame)
791
802
792 res = self._validatecommandrequestframe(frame)
803 res = self._validatecommandrequestframe(frame)
793 if res:
804 if res:
794 return res
805 return res
795
806
796 # All other frames should be related to a command that is currently
807 # All other frames should be related to a command that is currently
797 # receiving but is not active.
808 # receiving but is not active.
798 if frame.requestid in self._activecommands:
809 if frame.requestid in self._activecommands:
799 self._state = 'errored'
810 self._state = 'errored'
800 return self._makeerrorresult(
811 return self._makeerrorresult(
801 _('received frame for request that is still active: %d') %
812 _('received frame for request that is still active: %d') %
802 frame.requestid)
813 frame.requestid)
803
814
804 if frame.requestid not in self._receivingcommands:
815 if frame.requestid not in self._receivingcommands:
805 self._state = 'errored'
816 self._state = 'errored'
806 return self._makeerrorresult(
817 return self._makeerrorresult(
807 _('received frame for request that is not receiving: %d') %
818 _('received frame for request that is not receiving: %d') %
808 frame.requestid)
819 frame.requestid)
809
820
810 entry = self._receivingcommands[frame.requestid]
821 entry = self._receivingcommands[frame.requestid]
811
822
812 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
823 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
813 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
824 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
814 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
825 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
815
826
816 if entry['requestdone']:
827 if entry['requestdone']:
817 self._state = 'errored'
828 self._state = 'errored'
818 return self._makeerrorresult(
829 return self._makeerrorresult(
819 _('received command request frame when request frames '
830 _('received command request frame when request frames '
820 'were supposedly done'))
831 'were supposedly done'))
821
832
822 if expectingdata != entry['expectingdata']:
833 if expectingdata != entry['expectingdata']:
823 self._state = 'errored'
834 self._state = 'errored'
824 return self._makeerrorresult(
835 return self._makeerrorresult(
825 _('mismatch between expect data flag and previous frame'))
836 _('mismatch between expect data flag and previous frame'))
826
837
827 entry['payload'].write(frame.payload)
838 entry['payload'].write(frame.payload)
828
839
829 if not moreframes:
840 if not moreframes:
830 entry['requestdone'] = True
841 entry['requestdone'] = True
831
842
832 if not moreframes and not expectingdata:
843 if not moreframes and not expectingdata:
833 return self._makeruncommandresult(frame.requestid)
844 return self._makeruncommandresult(frame.requestid)
834
845
835 return self._makewantframeresult()
846 return self._makewantframeresult()
836
847
837 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
848 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
838 if not entry['expectingdata']:
849 if not entry['expectingdata']:
839 self._state = 'errored'
850 self._state = 'errored'
840 return self._makeerrorresult(_(
851 return self._makeerrorresult(_(
841 'received command data frame for request that is not '
852 'received command data frame for request that is not '
842 'expecting data: %d') % frame.requestid)
853 'expecting data: %d') % frame.requestid)
843
854
844 if entry['data'] is None:
855 if entry['data'] is None:
845 entry['data'] = util.bytesio()
856 entry['data'] = util.bytesio()
846
857
847 return self._handlecommanddataframe(frame, entry)
858 return self._handlecommanddataframe(frame, entry)
848 else:
859 else:
849 self._state = 'errored'
860 self._state = 'errored'
850 return self._makeerrorresult(_(
861 return self._makeerrorresult(_(
851 'received unexpected frame type: %d') % frame.typeid)
862 'received unexpected frame type: %d') % frame.typeid)
852
863
853 def _handlecommanddataframe(self, frame, entry):
864 def _handlecommanddataframe(self, frame, entry):
854 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
865 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
855
866
856 # TODO support streaming data instead of buffering it.
867 # TODO support streaming data instead of buffering it.
857 entry['data'].write(frame.payload)
868 entry['data'].write(frame.payload)
858
869
859 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
870 if frame.flags & FLAG_COMMAND_DATA_CONTINUATION:
860 return self._makewantframeresult()
871 return self._makewantframeresult()
861 elif frame.flags & FLAG_COMMAND_DATA_EOS:
872 elif frame.flags & FLAG_COMMAND_DATA_EOS:
862 entry['data'].seek(0)
873 entry['data'].seek(0)
863 return self._makeruncommandresult(frame.requestid)
874 return self._makeruncommandresult(frame.requestid)
864 else:
875 else:
865 self._state = 'errored'
876 self._state = 'errored'
866 return self._makeerrorresult(_('command data frame without '
877 return self._makeerrorresult(_('command data frame without '
867 'flags'))
878 'flags'))
868
879
869 def _onframeerrored(self, frame):
880 def _onframeerrored(self, frame):
870 return self._makeerrorresult(_('server already errored'))
881 return self._makeerrorresult(_('server already errored'))
871
882
872 class commandrequest(object):
883 class commandrequest(object):
873 """Represents a request to run a command."""
884 """Represents a request to run a command."""
874
885
875 def __init__(self, requestid, name, args, datafh=None):
886 def __init__(self, requestid, name, args, datafh=None):
876 self.requestid = requestid
887 self.requestid = requestid
877 self.name = name
888 self.name = name
878 self.args = args
889 self.args = args
879 self.datafh = datafh
890 self.datafh = datafh
880 self.state = 'pending'
891 self.state = 'pending'
881
892
882 class clientreactor(object):
893 class clientreactor(object):
883 """Holds state of a client issuing frame-based protocol requests.
894 """Holds state of a client issuing frame-based protocol requests.
884
895
885 This is like ``serverreactor`` but for client-side state.
896 This is like ``serverreactor`` but for client-side state.
886
897
887 Each instance is bound to the lifetime of a connection. For persistent
898 Each instance is bound to the lifetime of a connection. For persistent
888 connection transports using e.g. TCP sockets and speaking the raw
899 connection transports using e.g. TCP sockets and speaking the raw
889 framing protocol, there will be a single instance for the lifetime of
900 framing protocol, there will be a single instance for the lifetime of
890 the TCP socket. For transports where there are multiple discrete
901 the TCP socket. For transports where there are multiple discrete
891 interactions (say tunneled within in HTTP request), there will be a
902 interactions (say tunneled within in HTTP request), there will be a
892 separate instance for each distinct interaction.
903 separate instance for each distinct interaction.
893 """
904 """
894 def __init__(self, hasmultiplesend=False, buffersends=True):
905 def __init__(self, hasmultiplesend=False, buffersends=True):
895 """Create a new instance.
906 """Create a new instance.
896
907
897 ``hasmultiplesend`` indicates whether multiple sends are supported
908 ``hasmultiplesend`` indicates whether multiple sends are supported
898 by the transport. When True, it is possible to send commands immediately
909 by the transport. When True, it is possible to send commands immediately
899 instead of buffering until the caller signals an intent to finish a
910 instead of buffering until the caller signals an intent to finish a
900 send operation.
911 send operation.
901
912
902 ``buffercommands`` indicates whether sends should be buffered until the
913 ``buffercommands`` indicates whether sends should be buffered until the
903 last request has been issued.
914 last request has been issued.
904 """
915 """
905 self._hasmultiplesend = hasmultiplesend
916 self._hasmultiplesend = hasmultiplesend
906 self._buffersends = buffersends
917 self._buffersends = buffersends
907
918
908 self._canissuecommands = True
919 self._canissuecommands = True
909 self._cansend = True
920 self._cansend = True
910
921
911 self._nextrequestid = 1
922 self._nextrequestid = 1
912 # We only support a single outgoing stream for now.
923 # We only support a single outgoing stream for now.
913 self._outgoingstream = stream(1)
924 self._outgoingstream = stream(1)
914 self._pendingrequests = collections.deque()
925 self._pendingrequests = collections.deque()
915 self._activerequests = {}
926 self._activerequests = {}
916 self._incomingstreams = {}
927 self._incomingstreams = {}
917
928
918 def callcommand(self, name, args, datafh=None):
929 def callcommand(self, name, args, datafh=None):
919 """Request that a command be executed.
930 """Request that a command be executed.
920
931
921 Receives the command name, a dict of arguments to pass to the command,
932 Receives the command name, a dict of arguments to pass to the command,
922 and an optional file object containing the raw data for the command.
933 and an optional file object containing the raw data for the command.
923
934
924 Returns a 3-tuple of (request, action, action data).
935 Returns a 3-tuple of (request, action, action data).
925 """
936 """
926 if not self._canissuecommands:
937 if not self._canissuecommands:
927 raise error.ProgrammingError('cannot issue new commands')
938 raise error.ProgrammingError('cannot issue new commands')
928
939
929 requestid = self._nextrequestid
940 requestid = self._nextrequestid
930 self._nextrequestid += 2
941 self._nextrequestid += 2
931
942
932 request = commandrequest(requestid, name, args, datafh=datafh)
943 request = commandrequest(requestid, name, args, datafh=datafh)
933
944
934 if self._buffersends:
945 if self._buffersends:
935 self._pendingrequests.append(request)
946 self._pendingrequests.append(request)
936 return request, 'noop', {}
947 return request, 'noop', {}
937 else:
948 else:
938 if not self._cansend:
949 if not self._cansend:
939 raise error.ProgrammingError('sends cannot be performed on '
950 raise error.ProgrammingError('sends cannot be performed on '
940 'this instance')
951 'this instance')
941
952
942 if not self._hasmultiplesend:
953 if not self._hasmultiplesend:
943 self._cansend = False
954 self._cansend = False
944 self._canissuecommands = False
955 self._canissuecommands = False
945
956
946 return request, 'sendframes', {
957 return request, 'sendframes', {
947 'framegen': self._makecommandframes(request),
958 'framegen': self._makecommandframes(request),
948 }
959 }
949
960
950 def flushcommands(self):
961 def flushcommands(self):
951 """Request that all queued commands be sent.
962 """Request that all queued commands be sent.
952
963
953 If any commands are buffered, this will instruct the caller to send
964 If any commands are buffered, this will instruct the caller to send
954 them over the wire. If no commands are buffered it instructs the client
965 them over the wire. If no commands are buffered it instructs the client
955 to no-op.
966 to no-op.
956
967
957 If instances aren't configured for multiple sends, no new command
968 If instances aren't configured for multiple sends, no new command
958 requests are allowed after this is called.
969 requests are allowed after this is called.
959 """
970 """
960 if not self._pendingrequests:
971 if not self._pendingrequests:
961 return 'noop', {}
972 return 'noop', {}
962
973
963 if not self._cansend:
974 if not self._cansend:
964 raise error.ProgrammingError('sends cannot be performed on this '
975 raise error.ProgrammingError('sends cannot be performed on this '
965 'instance')
976 'instance')
966
977
967 # If the instance only allows sending once, mark that we have fired
978 # If the instance only allows sending once, mark that we have fired
968 # our one shot.
979 # our one shot.
969 if not self._hasmultiplesend:
980 if not self._hasmultiplesend:
970 self._canissuecommands = False
981 self._canissuecommands = False
971 self._cansend = False
982 self._cansend = False
972
983
973 def makeframes():
984 def makeframes():
974 while self._pendingrequests:
985 while self._pendingrequests:
975 request = self._pendingrequests.popleft()
986 request = self._pendingrequests.popleft()
976 for frame in self._makecommandframes(request):
987 for frame in self._makecommandframes(request):
977 yield frame
988 yield frame
978
989
979 return 'sendframes', {
990 return 'sendframes', {
980 'framegen': makeframes(),
991 'framegen': makeframes(),
981 }
992 }
982
993
983 def _makecommandframes(self, request):
994 def _makecommandframes(self, request):
984 """Emit frames to issue a command request.
995 """Emit frames to issue a command request.
985
996
986 As a side-effect, update request accounting to reflect its changed
997 As a side-effect, update request accounting to reflect its changed
987 state.
998 state.
988 """
999 """
989 self._activerequests[request.requestid] = request
1000 self._activerequests[request.requestid] = request
990 request.state = 'sending'
1001 request.state = 'sending'
991
1002
992 res = createcommandframes(self._outgoingstream,
1003 res = createcommandframes(self._outgoingstream,
993 request.requestid,
1004 request.requestid,
994 request.name,
1005 request.name,
995 request.args,
1006 request.args,
996 request.datafh)
1007 request.datafh)
997
1008
998 for frame in res:
1009 for frame in res:
999 yield frame
1010 yield frame
1000
1011
1001 request.state = 'sent'
1012 request.state = 'sent'
1002
1013
1003 def onframerecv(self, frame):
1014 def onframerecv(self, frame):
1004 """Process a frame that has been received off the wire.
1015 """Process a frame that has been received off the wire.
1005
1016
1006 Returns a 2-tuple of (action, meta) describing further action the
1017 Returns a 2-tuple of (action, meta) describing further action the
1007 caller needs to take as a result of receiving this frame.
1018 caller needs to take as a result of receiving this frame.
1008 """
1019 """
1009 if frame.streamid % 2:
1020 if frame.streamid % 2:
1010 return 'error', {
1021 return 'error', {
1011 'message': (
1022 'message': (
1012 _('received frame with odd numbered stream ID: %d') %
1023 _('received frame with odd numbered stream ID: %d') %
1013 frame.streamid),
1024 frame.streamid),
1014 }
1025 }
1015
1026
1016 if frame.streamid not in self._incomingstreams:
1027 if frame.streamid not in self._incomingstreams:
1017 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1028 if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM:
1018 return 'error', {
1029 return 'error', {
1019 'message': _('received frame on unknown stream '
1030 'message': _('received frame on unknown stream '
1020 'without beginning of stream flag set'),
1031 'without beginning of stream flag set'),
1021 }
1032 }
1022
1033
1023 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1034 self._incomingstreams[frame.streamid] = stream(frame.streamid)
1024
1035
1025 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1036 if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED:
1026 raise error.ProgrammingError('support for decoding stream '
1037 raise error.ProgrammingError('support for decoding stream '
1027 'payloads not yet implemneted')
1038 'payloads not yet implemneted')
1028
1039
1029 if frame.streamflags & STREAM_FLAG_END_STREAM:
1040 if frame.streamflags & STREAM_FLAG_END_STREAM:
1030 del self._incomingstreams[frame.streamid]
1041 del self._incomingstreams[frame.streamid]
1031
1042
1032 if frame.requestid not in self._activerequests:
1043 if frame.requestid not in self._activerequests:
1033 return 'error', {
1044 return 'error', {
1034 'message': (_('received frame for inactive request ID: %d') %
1045 'message': (_('received frame for inactive request ID: %d') %
1035 frame.requestid),
1046 frame.requestid),
1036 }
1047 }
1037
1048
1038 request = self._activerequests[frame.requestid]
1049 request = self._activerequests[frame.requestid]
1039 request.state = 'receiving'
1050 request.state = 'receiving'
1040
1051
1041 handlers = {
1052 handlers = {
1042 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1053 FRAME_TYPE_COMMAND_RESPONSE: self._oncommandresponseframe,
1043 }
1054 }
1044
1055
1045 meth = handlers.get(frame.typeid)
1056 meth = handlers.get(frame.typeid)
1046 if not meth:
1057 if not meth:
1047 raise error.ProgrammingError('unhandled frame type: %d' %
1058 raise error.ProgrammingError('unhandled frame type: %d' %
1048 frame.typeid)
1059 frame.typeid)
1049
1060
1050 return meth(request, frame)
1061 return meth(request, frame)
1051
1062
1052 def _oncommandresponseframe(self, request, frame):
1063 def _oncommandresponseframe(self, request, frame):
1053 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1064 if frame.flags & FLAG_COMMAND_RESPONSE_EOS:
1054 request.state = 'received'
1065 request.state = 'received'
1055 del self._activerequests[request.requestid]
1066 del self._activerequests[request.requestid]
1056
1067
1057 return 'responsedata', {
1068 return 'responsedata', {
1058 'request': request,
1069 'request': request,
1059 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1070 'expectmore': frame.flags & FLAG_COMMAND_RESPONSE_CONTINUATION,
1060 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1071 'eos': frame.flags & FLAG_COMMAND_RESPONSE_EOS,
1061 'data': frame.payload,
1072 'data': frame.payload,
1062 }
1073 }
@@ -1,175 +1,201 b''
1 # wireprotov2peer.py - client side code for wire protocol version 2
1 # wireprotov2peer.py - client side code for wire protocol version 2
2 #
2 #
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from .i18n import _
10 from .i18n import _
11 from .thirdparty import (
11 from .thirdparty import (
12 cbor,
12 cbor,
13 )
13 )
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 error,
16 error,
17 util,
17 util,
18 wireprotoframing,
18 wireprotoframing,
19 )
19 )
20
20
21 def formatrichmessage(atoms):
22 """Format an encoded message from the framing protocol."""
23
24 chunks = []
25
26 for atom in atoms:
27 msg = _(atom[b'msg'])
28
29 if b'args' in atom:
30 msg = msg % atom[b'args']
31
32 chunks.append(msg)
33
34 return b''.join(chunks)
35
21 class commandresponse(object):
36 class commandresponse(object):
22 """Represents the response to a command request."""
37 """Represents the response to a command request."""
23
38
24 def __init__(self, requestid, command):
39 def __init__(self, requestid, command):
25 self.requestid = requestid
40 self.requestid = requestid
26 self.command = command
41 self.command = command
27
42
28 self.b = util.bytesio()
43 self.b = util.bytesio()
29
44
30 def cborobjects(self):
45 def cborobjects(self):
31 """Obtain decoded CBOR objects from this response."""
46 """Obtain decoded CBOR objects from this response."""
32 size = self.b.tell()
47 size = self.b.tell()
33 self.b.seek(0)
48 self.b.seek(0)
34
49
35 decoder = cbor.CBORDecoder(self.b)
50 decoder = cbor.CBORDecoder(self.b)
36
51
37 while self.b.tell() < size:
52 while self.b.tell() < size:
38 yield decoder.decode()
53 yield decoder.decode()
39
54
40 class clienthandler(object):
55 class clienthandler(object):
41 """Object to handle higher-level client activities.
56 """Object to handle higher-level client activities.
42
57
43 The ``clientreactor`` is used to hold low-level state about the frame-based
58 The ``clientreactor`` is used to hold low-level state about the frame-based
44 protocol, such as which requests and streams are active. This type is used
59 protocol, such as which requests and streams are active. This type is used
45 for higher-level operations, such as reading frames from a socket, exposing
60 for higher-level operations, such as reading frames from a socket, exposing
46 and managing a higher-level primitive for representing command responses,
61 and managing a higher-level primitive for representing command responses,
47 etc. This class is what peers should probably use to bridge wire activity
62 etc. This class is what peers should probably use to bridge wire activity
48 with the higher-level peer API.
63 with the higher-level peer API.
49 """
64 """
50
65
51 def __init__(self, ui, clientreactor):
66 def __init__(self, ui, clientreactor):
52 self._ui = ui
67 self._ui = ui
53 self._reactor = clientreactor
68 self._reactor = clientreactor
54 self._requests = {}
69 self._requests = {}
55 self._futures = {}
70 self._futures = {}
56 self._responses = {}
71 self._responses = {}
57
72
58 def callcommand(self, command, args, f):
73 def callcommand(self, command, args, f):
59 """Register a request to call a command.
74 """Register a request to call a command.
60
75
61 Returns an iterable of frames that should be sent over the wire.
76 Returns an iterable of frames that should be sent over the wire.
62 """
77 """
63 request, action, meta = self._reactor.callcommand(command, args)
78 request, action, meta = self._reactor.callcommand(command, args)
64
79
65 if action != 'noop':
80 if action != 'noop':
66 raise error.ProgrammingError('%s not yet supported' % action)
81 raise error.ProgrammingError('%s not yet supported' % action)
67
82
68 rid = request.requestid
83 rid = request.requestid
69 self._requests[rid] = request
84 self._requests[rid] = request
70 self._futures[rid] = f
85 self._futures[rid] = f
71 self._responses[rid] = commandresponse(rid, command)
86 self._responses[rid] = commandresponse(rid, command)
72
87
73 return iter(())
88 return iter(())
74
89
75 def flushcommands(self):
90 def flushcommands(self):
76 """Flush all queued commands.
91 """Flush all queued commands.
77
92
78 Returns an iterable of frames that should be sent over the wire.
93 Returns an iterable of frames that should be sent over the wire.
79 """
94 """
80 action, meta = self._reactor.flushcommands()
95 action, meta = self._reactor.flushcommands()
81
96
82 if action != 'sendframes':
97 if action != 'sendframes':
83 raise error.ProgrammingError('%s not yet supported' % action)
98 raise error.ProgrammingError('%s not yet supported' % action)
84
99
85 return meta['framegen']
100 return meta['framegen']
86
101
87 def readframe(self, fh):
102 def readframe(self, fh):
88 """Attempt to read and process a frame.
103 """Attempt to read and process a frame.
89
104
90 Returns None if no frame was read. Presumably this means EOF.
105 Returns None if no frame was read. Presumably this means EOF.
91 """
106 """
92 frame = wireprotoframing.readframe(fh)
107 frame = wireprotoframing.readframe(fh)
93 if frame is None:
108 if frame is None:
94 # TODO tell reactor?
109 # TODO tell reactor?
95 return
110 return
96
111
97 self._ui.note(_('received %r\n') % frame)
112 self._ui.note(_('received %r\n') % frame)
98 self._processframe(frame)
113 self._processframe(frame)
99
114
100 return True
115 return True
101
116
102 def _processframe(self, frame):
117 def _processframe(self, frame):
103 """Process a single read frame."""
118 """Process a single read frame."""
104
119
105 action, meta = self._reactor.onframerecv(frame)
120 action, meta = self._reactor.onframerecv(frame)
106
121
107 if action == 'error':
122 if action == 'error':
108 e = error.RepoError(meta['message'])
123 e = error.RepoError(meta['message'])
109
124
110 if frame.requestid in self._futures:
125 if frame.requestid in self._futures:
111 self._futures[frame.requestid].set_exception(e)
126 self._futures[frame.requestid].set_exception(e)
112 else:
127 else:
113 raise e
128 raise e
114
129
115 if frame.requestid not in self._requests:
130 if frame.requestid not in self._requests:
116 raise error.ProgrammingError(
131 raise error.ProgrammingError(
117 'received frame for unknown request; this is either a bug in '
132 'received frame for unknown request; this is either a bug in '
118 'the clientreactor not screening for this or this instance was '
133 'the clientreactor not screening for this or this instance was '
119 'never told about this request: %r' % frame)
134 'never told about this request: %r' % frame)
120
135
121 response = self._responses[frame.requestid]
136 response = self._responses[frame.requestid]
122
137
123 if action == 'responsedata':
138 if action == 'responsedata':
124 response.b.write(meta['data'])
139 response.b.write(meta['data'])
125
140
126 if meta['eos']:
141 if meta['eos']:
127 # If the command has a decoder, resolve the future to the
142 # If the command has a decoder, resolve the future to the
128 # decoded value. Otherwise resolve to the rich response object.
143 # decoded value. Otherwise resolve to the rich response object.
129 decoder = COMMAND_DECODERS.get(response.command)
144 decoder = COMMAND_DECODERS.get(response.command)
130
145
131 result = decoder(response) if decoder else response
146 # TODO consider always resolving the overall status map.
147 if decoder:
148 objs = response.cborobjects()
149
150 overall = next(objs)
132
151
133 self._futures[frame.requestid].set_result(result)
152 if overall['status'] == 'ok':
153 self._futures[frame.requestid].set_result(decoder(objs))
154 else:
155 e = error.RepoError(
156 formatrichmessage(overall['error']['message']))
157 self._futures[frame.requestid].set_exception(e)
158 else:
159 self._futures[frame.requestid].set_result(response)
134
160
135 del self._requests[frame.requestid]
161 del self._requests[frame.requestid]
136 del self._futures[frame.requestid]
162 del self._futures[frame.requestid]
137
163
138 else:
164 else:
139 raise error.ProgrammingError(
165 raise error.ProgrammingError(
140 'unhandled action from clientreactor: %s' % action)
166 'unhandled action from clientreactor: %s' % action)
141
167
142 def decodebranchmap(resp):
168 def decodebranchmap(objs):
143 # Response should be a single CBOR map of branch name to array of nodes.
169 # Response should be a single CBOR map of branch name to array of nodes.
144 bm = next(resp.cborobjects())
170 bm = next(objs)
145
171
146 return {encoding.tolocal(k): v for k, v in bm.items()}
172 return {encoding.tolocal(k): v for k, v in bm.items()}
147
173
148 def decodeheads(resp):
174 def decodeheads(objs):
149 # Array of node bytestrings.
175 # Array of node bytestrings.
150 return next(resp.cborobjects())
176 return next(objs)
151
177
152 def decodeknown(resp):
178 def decodeknown(objs):
153 # Bytestring where each byte is a 0 or 1.
179 # Bytestring where each byte is a 0 or 1.
154 raw = next(resp.cborobjects())
180 raw = next(objs)
155
181
156 return [True if c == '1' else False for c in raw]
182 return [True if c == '1' else False for c in raw]
157
183
158 def decodelistkeys(resp):
184 def decodelistkeys(objs):
159 # Map with bytestring keys and values.
185 # Map with bytestring keys and values.
160 return next(resp.cborobjects())
186 return next(objs)
161
187
162 def decodelookup(resp):
188 def decodelookup(objs):
163 return next(resp.cborobjects())
189 return next(objs)
164
190
165 def decodepushkey(resp):
191 def decodepushkey(objs):
166 return next(resp.cborobjects())
192 return next(objs)
167
193
168 COMMAND_DECODERS = {
194 COMMAND_DECODERS = {
169 'branchmap': decodebranchmap,
195 'branchmap': decodebranchmap,
170 'heads': decodeheads,
196 'heads': decodeheads,
171 'known': decodeknown,
197 'known': decodeknown,
172 'listkeys': decodelistkeys,
198 'listkeys': decodelistkeys,
173 'lookup': decodelookup,
199 'lookup': decodelookup,
174 'pushkey': decodepushkey,
200 'pushkey': decodepushkey,
175 }
201 }
@@ -1,484 +1,484 b''
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10
10
11 from .i18n import _
11 from .i18n import _
12 from .thirdparty import (
12 from .thirdparty import (
13 cbor,
13 cbor,
14 )
14 )
15 from .thirdparty.zope import (
15 from .thirdparty.zope import (
16 interface as zi,
16 interface as zi,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 streamclone,
22 streamclone,
23 util,
23 util,
24 wireproto,
24 wireproto,
25 wireprotoframing,
25 wireprotoframing,
26 wireprototypes,
26 wireprototypes,
27 )
27 )
28
28
29 FRAMINGTYPE = b'application/mercurial-exp-framing-0004'
29 FRAMINGTYPE = b'application/mercurial-exp-framing-0005'
30
30
31 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
31 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
32
32
33 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
33 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
34 from .hgweb import common as hgwebcommon
34 from .hgweb import common as hgwebcommon
35
35
36 # URL space looks like: <permissions>/<command>, where <permission> can
36 # URL space looks like: <permissions>/<command>, where <permission> can
37 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
37 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
38
38
39 # Root URL does nothing meaningful... yet.
39 # Root URL does nothing meaningful... yet.
40 if not urlparts:
40 if not urlparts:
41 res.status = b'200 OK'
41 res.status = b'200 OK'
42 res.headers[b'Content-Type'] = b'text/plain'
42 res.headers[b'Content-Type'] = b'text/plain'
43 res.setbodybytes(_('HTTP version 2 API handler'))
43 res.setbodybytes(_('HTTP version 2 API handler'))
44 return
44 return
45
45
46 if len(urlparts) == 1:
46 if len(urlparts) == 1:
47 res.status = b'404 Not Found'
47 res.status = b'404 Not Found'
48 res.headers[b'Content-Type'] = b'text/plain'
48 res.headers[b'Content-Type'] = b'text/plain'
49 res.setbodybytes(_('do not know how to process %s\n') %
49 res.setbodybytes(_('do not know how to process %s\n') %
50 req.dispatchpath)
50 req.dispatchpath)
51 return
51 return
52
52
53 permission, command = urlparts[0:2]
53 permission, command = urlparts[0:2]
54
54
55 if permission not in (b'ro', b'rw'):
55 if permission not in (b'ro', b'rw'):
56 res.status = b'404 Not Found'
56 res.status = b'404 Not Found'
57 res.headers[b'Content-Type'] = b'text/plain'
57 res.headers[b'Content-Type'] = b'text/plain'
58 res.setbodybytes(_('unknown permission: %s') % permission)
58 res.setbodybytes(_('unknown permission: %s') % permission)
59 return
59 return
60
60
61 if req.method != 'POST':
61 if req.method != 'POST':
62 res.status = b'405 Method Not Allowed'
62 res.status = b'405 Method Not Allowed'
63 res.headers[b'Allow'] = b'POST'
63 res.headers[b'Allow'] = b'POST'
64 res.setbodybytes(_('commands require POST requests'))
64 res.setbodybytes(_('commands require POST requests'))
65 return
65 return
66
66
67 # At some point we'll want to use our own API instead of recycling the
67 # At some point we'll want to use our own API instead of recycling the
68 # behavior of version 1 of the wire protocol...
68 # behavior of version 1 of the wire protocol...
69 # TODO return reasonable responses - not responses that overload the
69 # TODO return reasonable responses - not responses that overload the
70 # HTTP status line message for error reporting.
70 # HTTP status line message for error reporting.
71 try:
71 try:
72 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
72 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
73 except hgwebcommon.ErrorResponse as e:
73 except hgwebcommon.ErrorResponse as e:
74 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
74 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
75 for k, v in e.headers:
75 for k, v in e.headers:
76 res.headers[k] = v
76 res.headers[k] = v
77 res.setbodybytes('permission denied')
77 res.setbodybytes('permission denied')
78 return
78 return
79
79
80 # We have a special endpoint to reflect the request back at the client.
80 # We have a special endpoint to reflect the request back at the client.
81 if command == b'debugreflect':
81 if command == b'debugreflect':
82 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
82 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
83 return
83 return
84
84
85 # Extra commands that we handle that aren't really wire protocol
85 # Extra commands that we handle that aren't really wire protocol
86 # commands. Think extra hard before making this hackery available to
86 # commands. Think extra hard before making this hackery available to
87 # extension.
87 # extension.
88 extracommands = {'multirequest'}
88 extracommands = {'multirequest'}
89
89
90 if command not in wireproto.commandsv2 and command not in extracommands:
90 if command not in wireproto.commandsv2 and command not in extracommands:
91 res.status = b'404 Not Found'
91 res.status = b'404 Not Found'
92 res.headers[b'Content-Type'] = b'text/plain'
92 res.headers[b'Content-Type'] = b'text/plain'
93 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
93 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
94 return
94 return
95
95
96 repo = rctx.repo
96 repo = rctx.repo
97 ui = repo.ui
97 ui = repo.ui
98
98
99 proto = httpv2protocolhandler(req, ui)
99 proto = httpv2protocolhandler(req, ui)
100
100
101 if (not wireproto.commandsv2.commandavailable(command, proto)
101 if (not wireproto.commandsv2.commandavailable(command, proto)
102 and command not in extracommands):
102 and command not in extracommands):
103 res.status = b'404 Not Found'
103 res.status = b'404 Not Found'
104 res.headers[b'Content-Type'] = b'text/plain'
104 res.headers[b'Content-Type'] = b'text/plain'
105 res.setbodybytes(_('invalid wire protocol command: %s') % command)
105 res.setbodybytes(_('invalid wire protocol command: %s') % command)
106 return
106 return
107
107
108 # TODO consider cases where proxies may add additional Accept headers.
108 # TODO consider cases where proxies may add additional Accept headers.
109 if req.headers.get(b'Accept') != FRAMINGTYPE:
109 if req.headers.get(b'Accept') != FRAMINGTYPE:
110 res.status = b'406 Not Acceptable'
110 res.status = b'406 Not Acceptable'
111 res.headers[b'Content-Type'] = b'text/plain'
111 res.headers[b'Content-Type'] = b'text/plain'
112 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
112 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
113 % FRAMINGTYPE)
113 % FRAMINGTYPE)
114 return
114 return
115
115
116 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
116 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
117 res.status = b'415 Unsupported Media Type'
117 res.status = b'415 Unsupported Media Type'
118 # TODO we should send a response with appropriate media type,
118 # TODO we should send a response with appropriate media type,
119 # since client does Accept it.
119 # since client does Accept it.
120 res.headers[b'Content-Type'] = b'text/plain'
120 res.headers[b'Content-Type'] = b'text/plain'
121 res.setbodybytes(_('client MUST send Content-Type header with '
121 res.setbodybytes(_('client MUST send Content-Type header with '
122 'value: %s\n') % FRAMINGTYPE)
122 'value: %s\n') % FRAMINGTYPE)
123 return
123 return
124
124
125 _processhttpv2request(ui, repo, req, res, permission, command, proto)
125 _processhttpv2request(ui, repo, req, res, permission, command, proto)
126
126
127 def _processhttpv2reflectrequest(ui, repo, req, res):
127 def _processhttpv2reflectrequest(ui, repo, req, res):
128 """Reads unified frame protocol request and dumps out state to client.
128 """Reads unified frame protocol request and dumps out state to client.
129
129
130 This special endpoint can be used to help debug the wire protocol.
130 This special endpoint can be used to help debug the wire protocol.
131
131
132 Instead of routing the request through the normal dispatch mechanism,
132 Instead of routing the request through the normal dispatch mechanism,
133 we instead read all frames, decode them, and feed them into our state
133 we instead read all frames, decode them, and feed them into our state
134 tracker. We then dump the log of all that activity back out to the
134 tracker. We then dump the log of all that activity back out to the
135 client.
135 client.
136 """
136 """
137 import json
137 import json
138
138
139 # Reflection APIs have a history of being abused, accidentally disclosing
139 # Reflection APIs have a history of being abused, accidentally disclosing
140 # sensitive data, etc. So we have a config knob.
140 # sensitive data, etc. So we have a config knob.
141 if not ui.configbool('experimental', 'web.api.debugreflect'):
141 if not ui.configbool('experimental', 'web.api.debugreflect'):
142 res.status = b'404 Not Found'
142 res.status = b'404 Not Found'
143 res.headers[b'Content-Type'] = b'text/plain'
143 res.headers[b'Content-Type'] = b'text/plain'
144 res.setbodybytes(_('debugreflect service not available'))
144 res.setbodybytes(_('debugreflect service not available'))
145 return
145 return
146
146
147 # We assume we have a unified framing protocol request body.
147 # We assume we have a unified framing protocol request body.
148
148
149 reactor = wireprotoframing.serverreactor()
149 reactor = wireprotoframing.serverreactor()
150 states = []
150 states = []
151
151
152 while True:
152 while True:
153 frame = wireprotoframing.readframe(req.bodyfh)
153 frame = wireprotoframing.readframe(req.bodyfh)
154
154
155 if not frame:
155 if not frame:
156 states.append(b'received: <no frame>')
156 states.append(b'received: <no frame>')
157 break
157 break
158
158
159 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
159 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
160 frame.requestid,
160 frame.requestid,
161 frame.payload))
161 frame.payload))
162
162
163 action, meta = reactor.onframerecv(frame)
163 action, meta = reactor.onframerecv(frame)
164 states.append(json.dumps((action, meta), sort_keys=True,
164 states.append(json.dumps((action, meta), sort_keys=True,
165 separators=(', ', ': ')))
165 separators=(', ', ': ')))
166
166
167 action, meta = reactor.oninputeof()
167 action, meta = reactor.oninputeof()
168 meta['action'] = action
168 meta['action'] = action
169 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
169 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
170
170
171 res.status = b'200 OK'
171 res.status = b'200 OK'
172 res.headers[b'Content-Type'] = b'text/plain'
172 res.headers[b'Content-Type'] = b'text/plain'
173 res.setbodybytes(b'\n'.join(states))
173 res.setbodybytes(b'\n'.join(states))
174
174
175 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
175 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
176 """Post-validation handler for HTTPv2 requests.
176 """Post-validation handler for HTTPv2 requests.
177
177
178 Called when the HTTP request contains unified frame-based protocol
178 Called when the HTTP request contains unified frame-based protocol
179 frames for evaluation.
179 frames for evaluation.
180 """
180 """
181 # TODO Some HTTP clients are full duplex and can receive data before
181 # TODO Some HTTP clients are full duplex and can receive data before
182 # the entire request is transmitted. Figure out a way to indicate support
182 # the entire request is transmitted. Figure out a way to indicate support
183 # for that so we can opt into full duplex mode.
183 # for that so we can opt into full duplex mode.
184 reactor = wireprotoframing.serverreactor(deferoutput=True)
184 reactor = wireprotoframing.serverreactor(deferoutput=True)
185 seencommand = False
185 seencommand = False
186
186
187 outstream = reactor.makeoutputstream()
187 outstream = reactor.makeoutputstream()
188
188
189 while True:
189 while True:
190 frame = wireprotoframing.readframe(req.bodyfh)
190 frame = wireprotoframing.readframe(req.bodyfh)
191 if not frame:
191 if not frame:
192 break
192 break
193
193
194 action, meta = reactor.onframerecv(frame)
194 action, meta = reactor.onframerecv(frame)
195
195
196 if action == 'wantframe':
196 if action == 'wantframe':
197 # Need more data before we can do anything.
197 # Need more data before we can do anything.
198 continue
198 continue
199 elif action == 'runcommand':
199 elif action == 'runcommand':
200 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
200 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
201 reqcommand, reactor, outstream,
201 reqcommand, reactor, outstream,
202 meta, issubsequent=seencommand)
202 meta, issubsequent=seencommand)
203
203
204 if sentoutput:
204 if sentoutput:
205 return
205 return
206
206
207 seencommand = True
207 seencommand = True
208
208
209 elif action == 'error':
209 elif action == 'error':
210 # TODO define proper error mechanism.
210 # TODO define proper error mechanism.
211 res.status = b'200 OK'
211 res.status = b'200 OK'
212 res.headers[b'Content-Type'] = b'text/plain'
212 res.headers[b'Content-Type'] = b'text/plain'
213 res.setbodybytes(meta['message'] + b'\n')
213 res.setbodybytes(meta['message'] + b'\n')
214 return
214 return
215 else:
215 else:
216 raise error.ProgrammingError(
216 raise error.ProgrammingError(
217 'unhandled action from frame processor: %s' % action)
217 'unhandled action from frame processor: %s' % action)
218
218
219 action, meta = reactor.oninputeof()
219 action, meta = reactor.oninputeof()
220 if action == 'sendframes':
220 if action == 'sendframes':
221 # We assume we haven't started sending the response yet. If we're
221 # We assume we haven't started sending the response yet. If we're
222 # wrong, the response type will raise an exception.
222 # wrong, the response type will raise an exception.
223 res.status = b'200 OK'
223 res.status = b'200 OK'
224 res.headers[b'Content-Type'] = FRAMINGTYPE
224 res.headers[b'Content-Type'] = FRAMINGTYPE
225 res.setbodygen(meta['framegen'])
225 res.setbodygen(meta['framegen'])
226 elif action == 'noop':
226 elif action == 'noop':
227 pass
227 pass
228 else:
228 else:
229 raise error.ProgrammingError('unhandled action from frame processor: %s'
229 raise error.ProgrammingError('unhandled action from frame processor: %s'
230 % action)
230 % action)
231
231
232 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
232 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
233 outstream, command, issubsequent):
233 outstream, command, issubsequent):
234 """Dispatch a wire protocol command made from HTTPv2 requests.
234 """Dispatch a wire protocol command made from HTTPv2 requests.
235
235
236 The authenticated permission (``authedperm``) along with the original
236 The authenticated permission (``authedperm``) along with the original
237 command from the URL (``reqcommand``) are passed in.
237 command from the URL (``reqcommand``) are passed in.
238 """
238 """
239 # We already validated that the session has permissions to perform the
239 # We already validated that the session has permissions to perform the
240 # actions in ``authedperm``. In the unified frame protocol, the canonical
240 # actions in ``authedperm``. In the unified frame protocol, the canonical
241 # command to run is expressed in a frame. However, the URL also requested
241 # command to run is expressed in a frame. However, the URL also requested
242 # to run a specific command. We need to be careful that the command we
242 # to run a specific command. We need to be careful that the command we
243 # run doesn't have permissions requirements greater than what was granted
243 # run doesn't have permissions requirements greater than what was granted
244 # by ``authedperm``.
244 # by ``authedperm``.
245 #
245 #
246 # Our rule for this is we only allow one command per HTTP request and
246 # Our rule for this is we only allow one command per HTTP request and
247 # that command must match the command in the URL. However, we make
247 # that command must match the command in the URL. However, we make
248 # an exception for the ``multirequest`` URL. This URL is allowed to
248 # an exception for the ``multirequest`` URL. This URL is allowed to
249 # execute multiple commands. We double check permissions of each command
249 # execute multiple commands. We double check permissions of each command
250 # as it is invoked to ensure there is no privilege escalation.
250 # as it is invoked to ensure there is no privilege escalation.
251 # TODO consider allowing multiple commands to regular command URLs
251 # TODO consider allowing multiple commands to regular command URLs
252 # iff each command is the same.
252 # iff each command is the same.
253
253
254 proto = httpv2protocolhandler(req, ui, args=command['args'])
254 proto = httpv2protocolhandler(req, ui, args=command['args'])
255
255
256 if reqcommand == b'multirequest':
256 if reqcommand == b'multirequest':
257 if not wireproto.commandsv2.commandavailable(command['command'], proto):
257 if not wireproto.commandsv2.commandavailable(command['command'], proto):
258 # TODO proper error mechanism
258 # TODO proper error mechanism
259 res.status = b'200 OK'
259 res.status = b'200 OK'
260 res.headers[b'Content-Type'] = b'text/plain'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('wire protocol command not available: %s') %
261 res.setbodybytes(_('wire protocol command not available: %s') %
262 command['command'])
262 command['command'])
263 return True
263 return True
264
264
265 # TODO don't use assert here, since it may be elided by -O.
265 # TODO don't use assert here, since it may be elided by -O.
266 assert authedperm in (b'ro', b'rw')
266 assert authedperm in (b'ro', b'rw')
267 wirecommand = wireproto.commandsv2[command['command']]
267 wirecommand = wireproto.commandsv2[command['command']]
268 assert wirecommand.permission in ('push', 'pull')
268 assert wirecommand.permission in ('push', 'pull')
269
269
270 if authedperm == b'ro' and wirecommand.permission != 'pull':
270 if authedperm == b'ro' and wirecommand.permission != 'pull':
271 # TODO proper error mechanism
271 # TODO proper error mechanism
272 res.status = b'403 Forbidden'
272 res.status = b'403 Forbidden'
273 res.headers[b'Content-Type'] = b'text/plain'
273 res.headers[b'Content-Type'] = b'text/plain'
274 res.setbodybytes(_('insufficient permissions to execute '
274 res.setbodybytes(_('insufficient permissions to execute '
275 'command: %s') % command['command'])
275 'command: %s') % command['command'])
276 return True
276 return True
277
277
278 # TODO should we also call checkperm() here? Maybe not if we're going
278 # TODO should we also call checkperm() here? Maybe not if we're going
279 # to overhaul that API. The granted scope from the URL check should
279 # to overhaul that API. The granted scope from the URL check should
280 # be good enough.
280 # be good enough.
281
281
282 else:
282 else:
283 # Don't allow multiple commands outside of ``multirequest`` URL.
283 # Don't allow multiple commands outside of ``multirequest`` URL.
284 if issubsequent:
284 if issubsequent:
285 # TODO proper error mechanism
285 # TODO proper error mechanism
286 res.status = b'200 OK'
286 res.status = b'200 OK'
287 res.headers[b'Content-Type'] = b'text/plain'
287 res.headers[b'Content-Type'] = b'text/plain'
288 res.setbodybytes(_('multiple commands cannot be issued to this '
288 res.setbodybytes(_('multiple commands cannot be issued to this '
289 'URL'))
289 'URL'))
290 return True
290 return True
291
291
292 if reqcommand != command['command']:
292 if reqcommand != command['command']:
293 # TODO define proper error mechanism
293 # TODO define proper error mechanism
294 res.status = b'200 OK'
294 res.status = b'200 OK'
295 res.headers[b'Content-Type'] = b'text/plain'
295 res.headers[b'Content-Type'] = b'text/plain'
296 res.setbodybytes(_('command in frame must match command in URL'))
296 res.setbodybytes(_('command in frame must match command in URL'))
297 return True
297 return True
298
298
299 rsp = wireproto.dispatch(repo, proto, command['command'])
299 rsp = wireproto.dispatch(repo, proto, command['command'])
300
300
301 res.status = b'200 OK'
301 res.status = b'200 OK'
302 res.headers[b'Content-Type'] = FRAMINGTYPE
302 res.headers[b'Content-Type'] = FRAMINGTYPE
303
303
304 if isinstance(rsp, wireprototypes.bytesresponse):
304 if isinstance(rsp, wireprototypes.bytesresponse):
305 action, meta = reactor.oncommandresponseready(outstream,
305 action, meta = reactor.oncommandresponseready(outstream,
306 command['requestid'],
306 command['requestid'],
307 rsp.data)
307 rsp.data)
308 elif isinstance(rsp, wireprototypes.cborresponse):
308 elif isinstance(rsp, wireprototypes.cborresponse):
309 encoded = cbor.dumps(rsp.value, canonical=True)
309 encoded = cbor.dumps(rsp.value, canonical=True)
310 action, meta = reactor.oncommandresponseready(outstream,
310 action, meta = reactor.oncommandresponseready(outstream,
311 command['requestid'],
311 command['requestid'],
312 encoded)
312 encoded)
313 else:
313 else:
314 action, meta = reactor.onapplicationerror(
314 action, meta = reactor.onapplicationerror(
315 _('unhandled response type from wire proto command'))
315 _('unhandled response type from wire proto command'))
316
316
317 if action == 'sendframes':
317 if action == 'sendframes':
318 res.setbodygen(meta['framegen'])
318 res.setbodygen(meta['framegen'])
319 return True
319 return True
320 elif action == 'noop':
320 elif action == 'noop':
321 return False
321 return False
322 else:
322 else:
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
324 action)
324 action)
325
325
326 @zi.implementer(wireprototypes.baseprotocolhandler)
326 @zi.implementer(wireprototypes.baseprotocolhandler)
327 class httpv2protocolhandler(object):
327 class httpv2protocolhandler(object):
328 def __init__(self, req, ui, args=None):
328 def __init__(self, req, ui, args=None):
329 self._req = req
329 self._req = req
330 self._ui = ui
330 self._ui = ui
331 self._args = args
331 self._args = args
332
332
333 @property
333 @property
334 def name(self):
334 def name(self):
335 return HTTP_WIREPROTO_V2
335 return HTTP_WIREPROTO_V2
336
336
337 def getargs(self, args):
337 def getargs(self, args):
338 data = {}
338 data = {}
339 for k, typ in args.items():
339 for k, typ in args.items():
340 if k == '*':
340 if k == '*':
341 raise NotImplementedError('do not support * args')
341 raise NotImplementedError('do not support * args')
342 elif k in self._args:
342 elif k in self._args:
343 # TODO consider validating value types.
343 # TODO consider validating value types.
344 data[k] = self._args[k]
344 data[k] = self._args[k]
345
345
346 return data
346 return data
347
347
348 def getprotocaps(self):
348 def getprotocaps(self):
349 # Protocol capabilities are currently not implemented for HTTP V2.
349 # Protocol capabilities are currently not implemented for HTTP V2.
350 return set()
350 return set()
351
351
352 def getpayload(self):
352 def getpayload(self):
353 raise NotImplementedError
353 raise NotImplementedError
354
354
355 @contextlib.contextmanager
355 @contextlib.contextmanager
356 def mayberedirectstdio(self):
356 def mayberedirectstdio(self):
357 raise NotImplementedError
357 raise NotImplementedError
358
358
359 def client(self):
359 def client(self):
360 raise NotImplementedError
360 raise NotImplementedError
361
361
362 def addcapabilities(self, repo, caps):
362 def addcapabilities(self, repo, caps):
363 return caps
363 return caps
364
364
365 def checkperm(self, perm):
365 def checkperm(self, perm):
366 raise NotImplementedError
366 raise NotImplementedError
367
367
368 def httpv2apidescriptor(req, repo):
368 def httpv2apidescriptor(req, repo):
369 proto = httpv2protocolhandler(req, repo.ui)
369 proto = httpv2protocolhandler(req, repo.ui)
370
370
371 return _capabilitiesv2(repo, proto)
371 return _capabilitiesv2(repo, proto)
372
372
373 def _capabilitiesv2(repo, proto):
373 def _capabilitiesv2(repo, proto):
374 """Obtain the set of capabilities for version 2 transports.
374 """Obtain the set of capabilities for version 2 transports.
375
375
376 These capabilities are distinct from the capabilities for version 1
376 These capabilities are distinct from the capabilities for version 1
377 transports.
377 transports.
378 """
378 """
379 compression = []
379 compression = []
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
381 compression.append({
381 compression.append({
382 b'name': engine.wireprotosupport().name,
382 b'name': engine.wireprotosupport().name,
383 })
383 })
384
384
385 caps = {
385 caps = {
386 'commands': {},
386 'commands': {},
387 'compression': compression,
387 'compression': compression,
388 'framingmediatypes': [FRAMINGTYPE],
388 'framingmediatypes': [FRAMINGTYPE],
389 }
389 }
390
390
391 for command, entry in wireproto.commandsv2.items():
391 for command, entry in wireproto.commandsv2.items():
392 caps['commands'][command] = {
392 caps['commands'][command] = {
393 'args': entry.args,
393 'args': entry.args,
394 'permissions': [entry.permission],
394 'permissions': [entry.permission],
395 }
395 }
396
396
397 if streamclone.allowservergeneration(repo):
397 if streamclone.allowservergeneration(repo):
398 caps['rawrepoformats'] = sorted(repo.requirements &
398 caps['rawrepoformats'] = sorted(repo.requirements &
399 repo.supportedformats)
399 repo.supportedformats)
400
400
401 return proto.addcapabilities(repo, caps)
401 return proto.addcapabilities(repo, caps)
402
402
403 def wireprotocommand(*args, **kwargs):
403 def wireprotocommand(*args, **kwargs):
404 def register(func):
404 def register(func):
405 return wireproto.wireprotocommand(
405 return wireproto.wireprotocommand(
406 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
406 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
407
407
408 return register
408 return register
409
409
410 @wireprotocommand('branchmap', permission='pull')
410 @wireprotocommand('branchmap', permission='pull')
411 def branchmapv2(repo, proto):
411 def branchmapv2(repo, proto):
412 branchmap = {encoding.fromlocal(k): v
412 branchmap = {encoding.fromlocal(k): v
413 for k, v in repo.branchmap().iteritems()}
413 for k, v in repo.branchmap().iteritems()}
414
414
415 return wireprototypes.cborresponse(branchmap)
415 return wireprototypes.cborresponse(branchmap)
416
416
417 @wireprotocommand('capabilities', permission='pull')
417 @wireprotocommand('capabilities', permission='pull')
418 def capabilitiesv2(repo, proto):
418 def capabilitiesv2(repo, proto):
419 caps = _capabilitiesv2(repo, proto)
419 caps = _capabilitiesv2(repo, proto)
420
420
421 return wireprototypes.cborresponse(caps)
421 return wireprototypes.cborresponse(caps)
422
422
423 @wireprotocommand('heads',
423 @wireprotocommand('heads',
424 args={
424 args={
425 'publiconly': False,
425 'publiconly': False,
426 },
426 },
427 permission='pull')
427 permission='pull')
428 def headsv2(repo, proto, publiconly=False):
428 def headsv2(repo, proto, publiconly=False):
429 if publiconly:
429 if publiconly:
430 repo = repo.filtered('immutable')
430 repo = repo.filtered('immutable')
431
431
432 return wireprototypes.cborresponse(repo.heads())
432 return wireprototypes.cborresponse(repo.heads())
433
433
434 @wireprotocommand('known',
434 @wireprotocommand('known',
435 args={
435 args={
436 'nodes': [b'deadbeef'],
436 'nodes': [b'deadbeef'],
437 },
437 },
438 permission='pull')
438 permission='pull')
439 def knownv2(repo, proto, nodes=None):
439 def knownv2(repo, proto, nodes=None):
440 nodes = nodes or []
440 nodes = nodes or []
441 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
441 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
442 return wireprototypes.cborresponse(result)
442 return wireprototypes.cborresponse(result)
443
443
444 @wireprotocommand('listkeys',
444 @wireprotocommand('listkeys',
445 args={
445 args={
446 'namespace': b'ns',
446 'namespace': b'ns',
447 },
447 },
448 permission='pull')
448 permission='pull')
449 def listkeysv2(repo, proto, namespace=None):
449 def listkeysv2(repo, proto, namespace=None):
450 keys = repo.listkeys(encoding.tolocal(namespace))
450 keys = repo.listkeys(encoding.tolocal(namespace))
451 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
451 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
452 for k, v in keys.iteritems()}
452 for k, v in keys.iteritems()}
453
453
454 return wireprototypes.cborresponse(keys)
454 return wireprototypes.cborresponse(keys)
455
455
456 @wireprotocommand('lookup',
456 @wireprotocommand('lookup',
457 args={
457 args={
458 'key': b'foo',
458 'key': b'foo',
459 },
459 },
460 permission='pull')
460 permission='pull')
461 def lookupv2(repo, proto, key):
461 def lookupv2(repo, proto, key):
462 key = encoding.tolocal(key)
462 key = encoding.tolocal(key)
463
463
464 # TODO handle exception.
464 # TODO handle exception.
465 node = repo.lookup(key)
465 node = repo.lookup(key)
466
466
467 return wireprototypes.cborresponse(node)
467 return wireprototypes.cborresponse(node)
468
468
469 @wireprotocommand('pushkey',
469 @wireprotocommand('pushkey',
470 args={
470 args={
471 'namespace': b'ns',
471 'namespace': b'ns',
472 'key': b'key',
472 'key': b'key',
473 'old': b'old',
473 'old': b'old',
474 'new': b'new',
474 'new': b'new',
475 },
475 },
476 permission='push')
476 permission='push')
477 def pushkeyv2(repo, proto, namespace, key, old, new):
477 def pushkeyv2(repo, proto, namespace, key, old, new):
478 # TODO handle ui output redirection
478 # TODO handle ui output redirection
479 r = repo.pushkey(encoding.tolocal(namespace),
479 r = repo.pushkey(encoding.tolocal(namespace),
480 encoding.tolocal(key),
480 encoding.tolocal(key),
481 encoding.tolocal(old),
481 encoding.tolocal(old),
482 encoding.tolocal(new))
482 encoding.tolocal(new))
483
483
484 return wireprototypes.cborresponse(r)
484 return wireprototypes.cborresponse(r)
@@ -1,564 +1,564 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2 $ enabledummycommands
2 $ enabledummycommands
3
3
4 $ hg init server
4 $ hg init server
5 $ cat > server/.hg/hgrc << EOF
5 $ cat > server/.hg/hgrc << EOF
6 > [experimental]
6 > [experimental]
7 > web.apiserver = true
7 > web.apiserver = true
8 > EOF
8 > EOF
9 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
9 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
10 $ cat hg.pid > $DAEMON_PIDS
10 $ cat hg.pid > $DAEMON_PIDS
11
11
12 HTTP v2 protocol not enabled by default
12 HTTP v2 protocol not enabled by default
13
13
14 $ sendhttpraw << EOF
14 $ sendhttpraw << EOF
15 > httprequest GET api/$HTTPV2
15 > httprequest GET api/$HTTPV2
16 > user-agent: test
16 > user-agent: test
17 > EOF
17 > EOF
18 using raw connection to peer
18 using raw connection to peer
19 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
19 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
20 s> Accept-Encoding: identity\r\n
20 s> Accept-Encoding: identity\r\n
21 s> user-agent: test\r\n
21 s> user-agent: test\r\n
22 s> host: $LOCALIP:$HGPORT\r\n (glob)
22 s> host: $LOCALIP:$HGPORT\r\n (glob)
23 s> \r\n
23 s> \r\n
24 s> makefile('rb', None)
24 s> makefile('rb', None)
25 s> HTTP/1.1 404 Not Found\r\n
25 s> HTTP/1.1 404 Not Found\r\n
26 s> Server: testing stub value\r\n
26 s> Server: testing stub value\r\n
27 s> Date: $HTTP_DATE$\r\n
27 s> Date: $HTTP_DATE$\r\n
28 s> Content-Type: text/plain\r\n
28 s> Content-Type: text/plain\r\n
29 s> Content-Length: 33\r\n
29 s> Content-Length: 33\r\n
30 s> \r\n
30 s> \r\n
31 s> API exp-http-v2-0001 not enabled\n
31 s> API exp-http-v2-0001 not enabled\n
32
32
33 Restart server with support for HTTP v2 API
33 Restart server with support for HTTP v2 API
34
34
35 $ killdaemons.py
35 $ killdaemons.py
36 $ enablehttpv2 server
36 $ enablehttpv2 server
37 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
37 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
38 $ cat hg.pid > $DAEMON_PIDS
38 $ cat hg.pid > $DAEMON_PIDS
39
39
40 Request to unknown command yields 404
40 Request to unknown command yields 404
41
41
42 $ sendhttpraw << EOF
42 $ sendhttpraw << EOF
43 > httprequest POST api/$HTTPV2/ro/badcommand
43 > httprequest POST api/$HTTPV2/ro/badcommand
44 > user-agent: test
44 > user-agent: test
45 > EOF
45 > EOF
46 using raw connection to peer
46 using raw connection to peer
47 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
47 s> POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
48 s> Accept-Encoding: identity\r\n
48 s> Accept-Encoding: identity\r\n
49 s> user-agent: test\r\n
49 s> user-agent: test\r\n
50 s> host: $LOCALIP:$HGPORT\r\n (glob)
50 s> host: $LOCALIP:$HGPORT\r\n (glob)
51 s> \r\n
51 s> \r\n
52 s> makefile('rb', None)
52 s> makefile('rb', None)
53 s> HTTP/1.1 404 Not Found\r\n
53 s> HTTP/1.1 404 Not Found\r\n
54 s> Server: testing stub value\r\n
54 s> Server: testing stub value\r\n
55 s> Date: $HTTP_DATE$\r\n
55 s> Date: $HTTP_DATE$\r\n
56 s> Content-Type: text/plain\r\n
56 s> Content-Type: text/plain\r\n
57 s> Content-Length: 42\r\n
57 s> Content-Length: 42\r\n
58 s> \r\n
58 s> \r\n
59 s> unknown wire protocol command: badcommand\n
59 s> unknown wire protocol command: badcommand\n
60
60
61 GET to read-only command yields a 405
61 GET to read-only command yields a 405
62
62
63 $ sendhttpraw << EOF
63 $ sendhttpraw << EOF
64 > httprequest GET api/$HTTPV2/ro/customreadonly
64 > httprequest GET api/$HTTPV2/ro/customreadonly
65 > user-agent: test
65 > user-agent: test
66 > EOF
66 > EOF
67 using raw connection to peer
67 using raw connection to peer
68 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
68 s> GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
69 s> Accept-Encoding: identity\r\n
69 s> Accept-Encoding: identity\r\n
70 s> user-agent: test\r\n
70 s> user-agent: test\r\n
71 s> host: $LOCALIP:$HGPORT\r\n (glob)
71 s> host: $LOCALIP:$HGPORT\r\n (glob)
72 s> \r\n
72 s> \r\n
73 s> makefile('rb', None)
73 s> makefile('rb', None)
74 s> HTTP/1.1 405 Method Not Allowed\r\n
74 s> HTTP/1.1 405 Method Not Allowed\r\n
75 s> Server: testing stub value\r\n
75 s> Server: testing stub value\r\n
76 s> Date: $HTTP_DATE$\r\n
76 s> Date: $HTTP_DATE$\r\n
77 s> Allow: POST\r\n
77 s> Allow: POST\r\n
78 s> Content-Length: 30\r\n
78 s> Content-Length: 30\r\n
79 s> \r\n
79 s> \r\n
80 s> commands require POST requests
80 s> commands require POST requests
81
81
82 Missing Accept header results in 406
82 Missing Accept header results in 406
83
83
84 $ sendhttpraw << EOF
84 $ sendhttpraw << EOF
85 > httprequest POST api/$HTTPV2/ro/customreadonly
85 > httprequest POST api/$HTTPV2/ro/customreadonly
86 > user-agent: test
86 > user-agent: test
87 > EOF
87 > EOF
88 using raw connection to peer
88 using raw connection to peer
89 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
89 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
90 s> Accept-Encoding: identity\r\n
90 s> Accept-Encoding: identity\r\n
91 s> user-agent: test\r\n
91 s> user-agent: test\r\n
92 s> host: $LOCALIP:$HGPORT\r\n (glob)
92 s> host: $LOCALIP:$HGPORT\r\n (glob)
93 s> \r\n
93 s> \r\n
94 s> makefile('rb', None)
94 s> makefile('rb', None)
95 s> HTTP/1.1 406 Not Acceptable\r\n
95 s> HTTP/1.1 406 Not Acceptable\r\n
96 s> Server: testing stub value\r\n
96 s> Server: testing stub value\r\n
97 s> Date: $HTTP_DATE$\r\n
97 s> Date: $HTTP_DATE$\r\n
98 s> Content-Type: text/plain\r\n
98 s> Content-Type: text/plain\r\n
99 s> Content-Length: 85\r\n
99 s> Content-Length: 85\r\n
100 s> \r\n
100 s> \r\n
101 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0004\n
101 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
102
102
103 Bad Accept header results in 406
103 Bad Accept header results in 406
104
104
105 $ sendhttpraw << EOF
105 $ sendhttpraw << EOF
106 > httprequest POST api/$HTTPV2/ro/customreadonly
106 > httprequest POST api/$HTTPV2/ro/customreadonly
107 > accept: invalid
107 > accept: invalid
108 > user-agent: test
108 > user-agent: test
109 > EOF
109 > EOF
110 using raw connection to peer
110 using raw connection to peer
111 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
111 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
112 s> Accept-Encoding: identity\r\n
112 s> Accept-Encoding: identity\r\n
113 s> accept: invalid\r\n
113 s> accept: invalid\r\n
114 s> user-agent: test\r\n
114 s> user-agent: test\r\n
115 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> host: $LOCALIP:$HGPORT\r\n (glob)
116 s> \r\n
116 s> \r\n
117 s> makefile('rb', None)
117 s> makefile('rb', None)
118 s> HTTP/1.1 406 Not Acceptable\r\n
118 s> HTTP/1.1 406 Not Acceptable\r\n
119 s> Server: testing stub value\r\n
119 s> Server: testing stub value\r\n
120 s> Date: $HTTP_DATE$\r\n
120 s> Date: $HTTP_DATE$\r\n
121 s> Content-Type: text/plain\r\n
121 s> Content-Type: text/plain\r\n
122 s> Content-Length: 85\r\n
122 s> Content-Length: 85\r\n
123 s> \r\n
123 s> \r\n
124 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0004\n
124 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0005\n
125
125
126 Bad Content-Type header results in 415
126 Bad Content-Type header results in 415
127
127
128 $ sendhttpraw << EOF
128 $ sendhttpraw << EOF
129 > httprequest POST api/$HTTPV2/ro/customreadonly
129 > httprequest POST api/$HTTPV2/ro/customreadonly
130 > accept: $MEDIATYPE
130 > accept: $MEDIATYPE
131 > user-agent: test
131 > user-agent: test
132 > content-type: badmedia
132 > content-type: badmedia
133 > EOF
133 > EOF
134 using raw connection to peer
134 using raw connection to peer
135 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
135 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
136 s> Accept-Encoding: identity\r\n
136 s> Accept-Encoding: identity\r\n
137 s> accept: application/mercurial-exp-framing-0004\r\n
137 s> accept: application/mercurial-exp-framing-0005\r\n
138 s> content-type: badmedia\r\n
138 s> content-type: badmedia\r\n
139 s> user-agent: test\r\n
139 s> user-agent: test\r\n
140 s> host: $LOCALIP:$HGPORT\r\n (glob)
140 s> host: $LOCALIP:$HGPORT\r\n (glob)
141 s> \r\n
141 s> \r\n
142 s> makefile('rb', None)
142 s> makefile('rb', None)
143 s> HTTP/1.1 415 Unsupported Media Type\r\n
143 s> HTTP/1.1 415 Unsupported Media Type\r\n
144 s> Server: testing stub value\r\n
144 s> Server: testing stub value\r\n
145 s> Date: $HTTP_DATE$\r\n
145 s> Date: $HTTP_DATE$\r\n
146 s> Content-Type: text/plain\r\n
146 s> Content-Type: text/plain\r\n
147 s> Content-Length: 88\r\n
147 s> Content-Length: 88\r\n
148 s> \r\n
148 s> \r\n
149 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0004\n
149 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0005\n
150
150
151 Request to read-only command works out of the box
151 Request to read-only command works out of the box
152
152
153 $ sendhttpraw << EOF
153 $ sendhttpraw << EOF
154 > httprequest POST api/$HTTPV2/ro/customreadonly
154 > httprequest POST api/$HTTPV2/ro/customreadonly
155 > accept: $MEDIATYPE
155 > accept: $MEDIATYPE
156 > content-type: $MEDIATYPE
156 > content-type: $MEDIATYPE
157 > user-agent: test
157 > user-agent: test
158 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
158 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
159 > EOF
159 > EOF
160 using raw connection to peer
160 using raw connection to peer
161 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
161 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
162 s> Accept-Encoding: identity\r\n
162 s> Accept-Encoding: identity\r\n
163 s> *\r\n (glob)
163 s> *\r\n (glob)
164 s> content-type: application/mercurial-exp-framing-0004\r\n
164 s> content-type: application/mercurial-exp-framing-0005\r\n
165 s> user-agent: test\r\n
165 s> user-agent: test\r\n
166 s> content-length: 29\r\n
166 s> content-length: 29\r\n
167 s> host: $LOCALIP:$HGPORT\r\n (glob)
167 s> host: $LOCALIP:$HGPORT\r\n (glob)
168 s> \r\n
168 s> \r\n
169 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
169 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
170 s> makefile('rb', None)
170 s> makefile('rb', None)
171 s> HTTP/1.1 200 OK\r\n
171 s> HTTP/1.1 200 OK\r\n
172 s> Server: testing stub value\r\n
172 s> Server: testing stub value\r\n
173 s> Date: $HTTP_DATE$\r\n
173 s> Date: $HTTP_DATE$\r\n
174 s> Content-Type: application/mercurial-exp-framing-0004\r\n
174 s> Content-Type: application/mercurial-exp-framing-0005\r\n
175 s> Transfer-Encoding: chunked\r\n
175 s> Transfer-Encoding: chunked\r\n
176 s> \r\n
176 s> \r\n
177 s> 27\r\n
177 s> 32\r\n
178 s> \x1f\x00\x00\x01\x00\x02\x012X\x1dcustomreadonly bytes response
178 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
179 s> \r\n
179 s> \r\n
180 s> 0\r\n
180 s> 0\r\n
181 s> \r\n
181 s> \r\n
182
182
183 $ sendhttpv2peer << EOF
183 $ sendhttpv2peer << EOF
184 > command customreadonly
184 > command customreadonly
185 > EOF
185 > EOF
186 creating http peer for wire protocol version 2
186 creating http peer for wire protocol version 2
187 sending customreadonly command
187 sending customreadonly command
188 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
188 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
189 s> Accept-Encoding: identity\r\n
189 s> Accept-Encoding: identity\r\n
190 s> accept: application/mercurial-exp-framing-0004\r\n
190 s> accept: application/mercurial-exp-framing-0005\r\n
191 s> content-type: application/mercurial-exp-framing-0004\r\n
191 s> content-type: application/mercurial-exp-framing-0005\r\n
192 s> content-length: 29\r\n
192 s> content-length: 29\r\n
193 s> host: $LOCALIP:$HGPORT\r\n (glob)
193 s> host: $LOCALIP:$HGPORT\r\n (glob)
194 s> user-agent: Mercurial debugwireproto\r\n
194 s> user-agent: Mercurial debugwireproto\r\n
195 s> \r\n
195 s> \r\n
196 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
196 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
197 s> makefile('rb', None)
197 s> makefile('rb', None)
198 s> HTTP/1.1 200 OK\r\n
198 s> HTTP/1.1 200 OK\r\n
199 s> Server: testing stub value\r\n
199 s> Server: testing stub value\r\n
200 s> Date: $HTTP_DATE$\r\n
200 s> Date: $HTTP_DATE$\r\n
201 s> Content-Type: application/mercurial-exp-framing-0004\r\n
201 s> Content-Type: application/mercurial-exp-framing-0005\r\n
202 s> Transfer-Encoding: chunked\r\n
202 s> Transfer-Encoding: chunked\r\n
203 s> \r\n
203 s> \r\n
204 s> 27\r\n
204 s> 32\r\n
205 s> \x1f\x00\x00\x01\x00\x02\x012
205 s> *\x00\x00\x01\x00\x02\x012
206 s> X\x1dcustomreadonly bytes response
206 s> \xa1FstatusBokX\x1dcustomreadonly bytes response
207 s> \r\n
207 s> \r\n
208 received frame(size=31; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
208 received frame(size=42; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
209 s> 0\r\n
209 s> 0\r\n
210 s> \r\n
210 s> \r\n
211 response: [b'customreadonly bytes response']
211 response: [{b'status': b'ok'}, b'customreadonly bytes response']
212
212
213 Request to read-write command fails because server is read-only by default
213 Request to read-write command fails because server is read-only by default
214
214
215 GET to read-write request yields 405
215 GET to read-write request yields 405
216
216
217 $ sendhttpraw << EOF
217 $ sendhttpraw << EOF
218 > httprequest GET api/$HTTPV2/rw/customreadonly
218 > httprequest GET api/$HTTPV2/rw/customreadonly
219 > user-agent: test
219 > user-agent: test
220 > EOF
220 > EOF
221 using raw connection to peer
221 using raw connection to peer
222 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
222 s> GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
223 s> Accept-Encoding: identity\r\n
223 s> Accept-Encoding: identity\r\n
224 s> user-agent: test\r\n
224 s> user-agent: test\r\n
225 s> host: $LOCALIP:$HGPORT\r\n (glob)
225 s> host: $LOCALIP:$HGPORT\r\n (glob)
226 s> \r\n
226 s> \r\n
227 s> makefile('rb', None)
227 s> makefile('rb', None)
228 s> HTTP/1.1 405 Method Not Allowed\r\n
228 s> HTTP/1.1 405 Method Not Allowed\r\n
229 s> Server: testing stub value\r\n
229 s> Server: testing stub value\r\n
230 s> Date: $HTTP_DATE$\r\n
230 s> Date: $HTTP_DATE$\r\n
231 s> Allow: POST\r\n
231 s> Allow: POST\r\n
232 s> Content-Length: 30\r\n
232 s> Content-Length: 30\r\n
233 s> \r\n
233 s> \r\n
234 s> commands require POST requests
234 s> commands require POST requests
235
235
236 Even for unknown commands
236 Even for unknown commands
237
237
238 $ sendhttpraw << EOF
238 $ sendhttpraw << EOF
239 > httprequest GET api/$HTTPV2/rw/badcommand
239 > httprequest GET api/$HTTPV2/rw/badcommand
240 > user-agent: test
240 > user-agent: test
241 > EOF
241 > EOF
242 using raw connection to peer
242 using raw connection to peer
243 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
243 s> GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
244 s> Accept-Encoding: identity\r\n
244 s> Accept-Encoding: identity\r\n
245 s> user-agent: test\r\n
245 s> user-agent: test\r\n
246 s> host: $LOCALIP:$HGPORT\r\n (glob)
246 s> host: $LOCALIP:$HGPORT\r\n (glob)
247 s> \r\n
247 s> \r\n
248 s> makefile('rb', None)
248 s> makefile('rb', None)
249 s> HTTP/1.1 405 Method Not Allowed\r\n
249 s> HTTP/1.1 405 Method Not Allowed\r\n
250 s> Server: testing stub value\r\n
250 s> Server: testing stub value\r\n
251 s> Date: $HTTP_DATE$\r\n
251 s> Date: $HTTP_DATE$\r\n
252 s> Allow: POST\r\n
252 s> Allow: POST\r\n
253 s> Content-Length: 30\r\n
253 s> Content-Length: 30\r\n
254 s> \r\n
254 s> \r\n
255 s> commands require POST requests
255 s> commands require POST requests
256
256
257 SSL required by default
257 SSL required by default
258
258
259 $ sendhttpraw << EOF
259 $ sendhttpraw << EOF
260 > httprequest POST api/$HTTPV2/rw/customreadonly
260 > httprequest POST api/$HTTPV2/rw/customreadonly
261 > user-agent: test
261 > user-agent: test
262 > EOF
262 > EOF
263 using raw connection to peer
263 using raw connection to peer
264 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
264 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
265 s> Accept-Encoding: identity\r\n
265 s> Accept-Encoding: identity\r\n
266 s> user-agent: test\r\n
266 s> user-agent: test\r\n
267 s> host: $LOCALIP:$HGPORT\r\n (glob)
267 s> host: $LOCALIP:$HGPORT\r\n (glob)
268 s> \r\n
268 s> \r\n
269 s> makefile('rb', None)
269 s> makefile('rb', None)
270 s> HTTP/1.1 403 ssl required\r\n
270 s> HTTP/1.1 403 ssl required\r\n
271 s> Server: testing stub value\r\n
271 s> Server: testing stub value\r\n
272 s> Date: $HTTP_DATE$\r\n
272 s> Date: $HTTP_DATE$\r\n
273 s> Content-Length: 17\r\n
273 s> Content-Length: 17\r\n
274 s> \r\n
274 s> \r\n
275 s> permission denied
275 s> permission denied
276
276
277 Restart server to allow non-ssl read-write operations
277 Restart server to allow non-ssl read-write operations
278
278
279 $ killdaemons.py
279 $ killdaemons.py
280 $ cat > server/.hg/hgrc << EOF
280 $ cat > server/.hg/hgrc << EOF
281 > [experimental]
281 > [experimental]
282 > web.apiserver = true
282 > web.apiserver = true
283 > web.api.http-v2 = true
283 > web.api.http-v2 = true
284 > [web]
284 > [web]
285 > push_ssl = false
285 > push_ssl = false
286 > allow-push = *
286 > allow-push = *
287 > EOF
287 > EOF
288
288
289 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
289 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
290 $ cat hg.pid > $DAEMON_PIDS
290 $ cat hg.pid > $DAEMON_PIDS
291
291
292 Authorized request for valid read-write command works
292 Authorized request for valid read-write command works
293
293
294 $ sendhttpraw << EOF
294 $ sendhttpraw << EOF
295 > httprequest POST api/$HTTPV2/rw/customreadonly
295 > httprequest POST api/$HTTPV2/rw/customreadonly
296 > user-agent: test
296 > user-agent: test
297 > accept: $MEDIATYPE
297 > accept: $MEDIATYPE
298 > content-type: $MEDIATYPE
298 > content-type: $MEDIATYPE
299 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
299 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
300 > EOF
300 > EOF
301 using raw connection to peer
301 using raw connection to peer
302 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
302 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
303 s> Accept-Encoding: identity\r\n
303 s> Accept-Encoding: identity\r\n
304 s> accept: application/mercurial-exp-framing-0004\r\n
304 s> accept: application/mercurial-exp-framing-0005\r\n
305 s> content-type: application/mercurial-exp-framing-0004\r\n
305 s> content-type: application/mercurial-exp-framing-0005\r\n
306 s> user-agent: test\r\n
306 s> user-agent: test\r\n
307 s> content-length: 29\r\n
307 s> content-length: 29\r\n
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
308 s> host: $LOCALIP:$HGPORT\r\n (glob)
309 s> \r\n
309 s> \r\n
310 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
310 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
311 s> makefile('rb', None)
311 s> makefile('rb', None)
312 s> HTTP/1.1 200 OK\r\n
312 s> HTTP/1.1 200 OK\r\n
313 s> Server: testing stub value\r\n
313 s> Server: testing stub value\r\n
314 s> Date: $HTTP_DATE$\r\n
314 s> Date: $HTTP_DATE$\r\n
315 s> Content-Type: application/mercurial-exp-framing-0004\r\n
315 s> Content-Type: application/mercurial-exp-framing-0005\r\n
316 s> Transfer-Encoding: chunked\r\n
316 s> Transfer-Encoding: chunked\r\n
317 s> \r\n
317 s> \r\n
318 s> 27\r\n
318 s> 32\r\n
319 s> \x1f\x00\x00\x01\x00\x02\x012X\x1dcustomreadonly bytes response
319 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
320 s> \r\n
320 s> \r\n
321 s> 0\r\n
321 s> 0\r\n
322 s> \r\n
322 s> \r\n
323
323
324 Authorized request for unknown command is rejected
324 Authorized request for unknown command is rejected
325
325
326 $ sendhttpraw << EOF
326 $ sendhttpraw << EOF
327 > httprequest POST api/$HTTPV2/rw/badcommand
327 > httprequest POST api/$HTTPV2/rw/badcommand
328 > user-agent: test
328 > user-agent: test
329 > accept: $MEDIATYPE
329 > accept: $MEDIATYPE
330 > EOF
330 > EOF
331 using raw connection to peer
331 using raw connection to peer
332 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
332 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
333 s> Accept-Encoding: identity\r\n
333 s> Accept-Encoding: identity\r\n
334 s> accept: application/mercurial-exp-framing-0004\r\n
334 s> accept: application/mercurial-exp-framing-0005\r\n
335 s> user-agent: test\r\n
335 s> user-agent: test\r\n
336 s> host: $LOCALIP:$HGPORT\r\n (glob)
336 s> host: $LOCALIP:$HGPORT\r\n (glob)
337 s> \r\n
337 s> \r\n
338 s> makefile('rb', None)
338 s> makefile('rb', None)
339 s> HTTP/1.1 404 Not Found\r\n
339 s> HTTP/1.1 404 Not Found\r\n
340 s> Server: testing stub value\r\n
340 s> Server: testing stub value\r\n
341 s> Date: $HTTP_DATE$\r\n
341 s> Date: $HTTP_DATE$\r\n
342 s> Content-Type: text/plain\r\n
342 s> Content-Type: text/plain\r\n
343 s> Content-Length: 42\r\n
343 s> Content-Length: 42\r\n
344 s> \r\n
344 s> \r\n
345 s> unknown wire protocol command: badcommand\n
345 s> unknown wire protocol command: badcommand\n
346
346
347 debugreflect isn't enabled by default
347 debugreflect isn't enabled by default
348
348
349 $ sendhttpraw << EOF
349 $ sendhttpraw << EOF
350 > httprequest POST api/$HTTPV2/ro/debugreflect
350 > httprequest POST api/$HTTPV2/ro/debugreflect
351 > user-agent: test
351 > user-agent: test
352 > EOF
352 > EOF
353 using raw connection to peer
353 using raw connection to peer
354 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
354 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
355 s> Accept-Encoding: identity\r\n
355 s> Accept-Encoding: identity\r\n
356 s> user-agent: test\r\n
356 s> user-agent: test\r\n
357 s> host: $LOCALIP:$HGPORT\r\n (glob)
357 s> host: $LOCALIP:$HGPORT\r\n (glob)
358 s> \r\n
358 s> \r\n
359 s> makefile('rb', None)
359 s> makefile('rb', None)
360 s> HTTP/1.1 404 Not Found\r\n
360 s> HTTP/1.1 404 Not Found\r\n
361 s> Server: testing stub value\r\n
361 s> Server: testing stub value\r\n
362 s> Date: $HTTP_DATE$\r\n
362 s> Date: $HTTP_DATE$\r\n
363 s> Content-Type: text/plain\r\n
363 s> Content-Type: text/plain\r\n
364 s> Content-Length: 34\r\n
364 s> Content-Length: 34\r\n
365 s> \r\n
365 s> \r\n
366 s> debugreflect service not available
366 s> debugreflect service not available
367
367
368 Restart server to get debugreflect endpoint
368 Restart server to get debugreflect endpoint
369
369
370 $ killdaemons.py
370 $ killdaemons.py
371 $ cat > server/.hg/hgrc << EOF
371 $ cat > server/.hg/hgrc << EOF
372 > [experimental]
372 > [experimental]
373 > web.apiserver = true
373 > web.apiserver = true
374 > web.api.debugreflect = true
374 > web.api.debugreflect = true
375 > web.api.http-v2 = true
375 > web.api.http-v2 = true
376 > [web]
376 > [web]
377 > push_ssl = false
377 > push_ssl = false
378 > allow-push = *
378 > allow-push = *
379 > EOF
379 > EOF
380
380
381 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
381 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
382 $ cat hg.pid > $DAEMON_PIDS
382 $ cat hg.pid > $DAEMON_PIDS
383
383
384 Command frames can be reflected via debugreflect
384 Command frames can be reflected via debugreflect
385
385
386 $ sendhttpraw << EOF
386 $ sendhttpraw << EOF
387 > httprequest POST api/$HTTPV2/ro/debugreflect
387 > httprequest POST api/$HTTPV2/ro/debugreflect
388 > accept: $MEDIATYPE
388 > accept: $MEDIATYPE
389 > content-type: $MEDIATYPE
389 > content-type: $MEDIATYPE
390 > user-agent: test
390 > user-agent: test
391 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
391 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
392 > EOF
392 > EOF
393 using raw connection to peer
393 using raw connection to peer
394 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
394 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
395 s> Accept-Encoding: identity\r\n
395 s> Accept-Encoding: identity\r\n
396 s> accept: application/mercurial-exp-framing-0004\r\n
396 s> accept: application/mercurial-exp-framing-0005\r\n
397 s> content-type: application/mercurial-exp-framing-0004\r\n
397 s> content-type: application/mercurial-exp-framing-0005\r\n
398 s> user-agent: test\r\n
398 s> user-agent: test\r\n
399 s> content-length: 47\r\n
399 s> content-length: 47\r\n
400 s> host: $LOCALIP:$HGPORT\r\n (glob)
400 s> host: $LOCALIP:$HGPORT\r\n (glob)
401 s> \r\n
401 s> \r\n
402 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1
402 s> \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1
403 s> makefile('rb', None)
403 s> makefile('rb', None)
404 s> HTTP/1.1 200 OK\r\n
404 s> HTTP/1.1 200 OK\r\n
405 s> Server: testing stub value\r\n
405 s> Server: testing stub value\r\n
406 s> Date: $HTTP_DATE$\r\n
406 s> Date: $HTTP_DATE$\r\n
407 s> Content-Type: text/plain\r\n
407 s> Content-Type: text/plain\r\n
408 s> Content-Length: 205\r\n
408 s> Content-Length: 205\r\n
409 s> \r\n
409 s> \r\n
410 s> received: 1 1 1 \xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1\n
410 s> received: 1 1 1 \xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1\n
411 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
411 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
412 s> received: <no frame>\n
412 s> received: <no frame>\n
413 s> {"action": "noop"}
413 s> {"action": "noop"}
414
414
415 Multiple requests to regular command URL are not allowed
415 Multiple requests to regular command URL are not allowed
416
416
417 $ sendhttpraw << EOF
417 $ sendhttpraw << EOF
418 > httprequest POST api/$HTTPV2/ro/customreadonly
418 > httprequest POST api/$HTTPV2/ro/customreadonly
419 > accept: $MEDIATYPE
419 > accept: $MEDIATYPE
420 > content-type: $MEDIATYPE
420 > content-type: $MEDIATYPE
421 > user-agent: test
421 > user-agent: test
422 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
422 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
423 > EOF
423 > EOF
424 using raw connection to peer
424 using raw connection to peer
425 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
425 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
426 s> Accept-Encoding: identity\r\n
426 s> Accept-Encoding: identity\r\n
427 s> accept: application/mercurial-exp-framing-0004\r\n
427 s> accept: application/mercurial-exp-framing-0005\r\n
428 s> content-type: application/mercurial-exp-framing-0004\r\n
428 s> content-type: application/mercurial-exp-framing-0005\r\n
429 s> user-agent: test\r\n
429 s> user-agent: test\r\n
430 s> content-length: 29\r\n
430 s> content-length: 29\r\n
431 s> host: $LOCALIP:$HGPORT\r\n (glob)
431 s> host: $LOCALIP:$HGPORT\r\n (glob)
432 s> \r\n
432 s> \r\n
433 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
433 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
434 s> makefile('rb', None)
434 s> makefile('rb', None)
435 s> HTTP/1.1 200 OK\r\n
435 s> HTTP/1.1 200 OK\r\n
436 s> Server: testing stub value\r\n
436 s> Server: testing stub value\r\n
437 s> Date: $HTTP_DATE$\r\n
437 s> Date: $HTTP_DATE$\r\n
438 s> Content-Type: application/mercurial-exp-framing-0004\r\n
438 s> Content-Type: application/mercurial-exp-framing-0005\r\n
439 s> Transfer-Encoding: chunked\r\n
439 s> Transfer-Encoding: chunked\r\n
440 s> \r\n
440 s> \r\n
441 s> 27\r\n
441 s> 32\r\n
442 s> \x1f\x00\x00\x01\x00\x02\x012X\x1dcustomreadonly bytes response
442 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
443 s> \r\n
443 s> \r\n
444 s> 0\r\n
444 s> 0\r\n
445 s> \r\n
445 s> \r\n
446
446
447 Multiple requests to "multirequest" URL are allowed
447 Multiple requests to "multirequest" URL are allowed
448
448
449 $ sendhttpraw << EOF
449 $ sendhttpraw << EOF
450 > httprequest POST api/$HTTPV2/ro/multirequest
450 > httprequest POST api/$HTTPV2/ro/multirequest
451 > accept: $MEDIATYPE
451 > accept: $MEDIATYPE
452 > content-type: $MEDIATYPE
452 > content-type: $MEDIATYPE
453 > user-agent: test
453 > user-agent: test
454 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
454 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
455 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
455 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
456 > EOF
456 > EOF
457 using raw connection to peer
457 using raw connection to peer
458 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
458 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
459 s> Accept-Encoding: identity\r\n
459 s> Accept-Encoding: identity\r\n
460 s> *\r\n (glob)
460 s> *\r\n (glob)
461 s> *\r\n (glob)
461 s> *\r\n (glob)
462 s> user-agent: test\r\n
462 s> user-agent: test\r\n
463 s> content-length: 58\r\n
463 s> content-length: 58\r\n
464 s> host: $LOCALIP:$HGPORT\r\n (glob)
464 s> host: $LOCALIP:$HGPORT\r\n (glob)
465 s> \r\n
465 s> \r\n
466 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
466 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
467 s> makefile('rb', None)
467 s> makefile('rb', None)
468 s> HTTP/1.1 200 OK\r\n
468 s> HTTP/1.1 200 OK\r\n
469 s> Server: testing stub value\r\n
469 s> Server: testing stub value\r\n
470 s> Date: $HTTP_DATE$\r\n
470 s> Date: $HTTP_DATE$\r\n
471 s> Content-Type: application/mercurial-exp-framing-0004\r\n
471 s> Content-Type: application/mercurial-exp-framing-0005\r\n
472 s> Transfer-Encoding: chunked\r\n
472 s> Transfer-Encoding: chunked\r\n
473 s> \r\n
473 s> \r\n
474 s> 27\r\n
474 s> 32\r\n
475 s> \x1f\x00\x00\x01\x00\x02\x012X\x1dcustomreadonly bytes response
475 s> *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
476 s> \r\n
476 s> \r\n
477 s> 27\r\n
477 s> 32\r\n
478 s> \x1f\x00\x00\x03\x00\x02\x002X\x1dcustomreadonly bytes response
478 s> *\x00\x00\x03\x00\x02\x002\xa1FstatusBokX\x1dcustomreadonly bytes response
479 s> \r\n
479 s> \r\n
480 s> 0\r\n
480 s> 0\r\n
481 s> \r\n
481 s> \r\n
482
482
483 Interleaved requests to "multirequest" are processed
483 Interleaved requests to "multirequest" are processed
484
484
485 $ sendhttpraw << EOF
485 $ sendhttpraw << EOF
486 > httprequest POST api/$HTTPV2/ro/multirequest
486 > httprequest POST api/$HTTPV2/ro/multirequest
487 > accept: $MEDIATYPE
487 > accept: $MEDIATYPE
488 > content-type: $MEDIATYPE
488 > content-type: $MEDIATYPE
489 > user-agent: test
489 > user-agent: test
490 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
490 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
491 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
491 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
492 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
492 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
493 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
493 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
494 > EOF
494 > EOF
495 using raw connection to peer
495 using raw connection to peer
496 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
496 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
497 s> Accept-Encoding: identity\r\n
497 s> Accept-Encoding: identity\r\n
498 s> accept: application/mercurial-exp-framing-0004\r\n
498 s> accept: application/mercurial-exp-framing-0005\r\n
499 s> content-type: application/mercurial-exp-framing-0004\r\n
499 s> content-type: application/mercurial-exp-framing-0005\r\n
500 s> user-agent: test\r\n
500 s> user-agent: test\r\n
501 s> content-length: 115\r\n
501 s> content-length: 115\r\n
502 s> host: $LOCALIP:$HGPORT\r\n (glob)
502 s> host: $LOCALIP:$HGPORT\r\n (glob)
503 s> \r\n
503 s> \r\n
504 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
504 s> \x11\x00\x00\x01\x00\x01\x01\x15\xa2Dargs\xa1Inamespace\x11\x00\x00\x03\x00\x01\x00\x15\xa2Dargs\xa1Inamespace\x19\x00\x00\x03\x00\x01\x00\x12JnamespacesDnameHlistkeys\x18\x00\x00\x01\x00\x01\x00\x12IbookmarksDnameHlistkeys
505 s> makefile('rb', None)
505 s> makefile('rb', None)
506 s> HTTP/1.1 200 OK\r\n
506 s> HTTP/1.1 200 OK\r\n
507 s> Server: testing stub value\r\n
507 s> Server: testing stub value\r\n
508 s> Date: $HTTP_DATE$\r\n
508 s> Date: $HTTP_DATE$\r\n
509 s> Content-Type: application/mercurial-exp-framing-0004\r\n
509 s> Content-Type: application/mercurial-exp-framing-0005\r\n
510 s> Transfer-Encoding: chunked\r\n
510 s> Transfer-Encoding: chunked\r\n
511 s> \r\n
511 s> \r\n
512 s> 28\r\n
512 s> 33\r\n
513 s> \x00\x00\x03\x00\x02\x012\xa3Fphases@Ibookmarks@Jnamespaces@
513 s> +\x00\x00\x03\x00\x02\x012\xa1FstatusBok\xa3Fphases@Ibookmarks@Jnamespaces@
514 s> \r\n
514 s> \r\n
515 s> 9\r\n
515 s> 14\r\n
516 s> \x01\x00\x00\x01\x00\x02\x002\xa0
516 s> \x0c\x00\x00\x01\x00\x02\x002\xa1FstatusBok\xa0
517 s> \r\n
517 s> \r\n
518 s> 0\r\n
518 s> 0\r\n
519 s> \r\n
519 s> \r\n
520
520
521 Restart server to disable read-write access
521 Restart server to disable read-write access
522
522
523 $ killdaemons.py
523 $ killdaemons.py
524 $ cat > server/.hg/hgrc << EOF
524 $ cat > server/.hg/hgrc << EOF
525 > [experimental]
525 > [experimental]
526 > web.apiserver = true
526 > web.apiserver = true
527 > web.api.debugreflect = true
527 > web.api.debugreflect = true
528 > web.api.http-v2 = true
528 > web.api.http-v2 = true
529 > [web]
529 > [web]
530 > push_ssl = false
530 > push_ssl = false
531 > EOF
531 > EOF
532
532
533 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
533 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
534 $ cat hg.pid > $DAEMON_PIDS
534 $ cat hg.pid > $DAEMON_PIDS
535
535
536 Attempting to run a read-write command via multirequest on read-only URL is not allowed
536 Attempting to run a read-write command via multirequest on read-only URL is not allowed
537
537
538 $ sendhttpraw << EOF
538 $ sendhttpraw << EOF
539 > httprequest POST api/$HTTPV2/ro/multirequest
539 > httprequest POST api/$HTTPV2/ro/multirequest
540 > accept: $MEDIATYPE
540 > accept: $MEDIATYPE
541 > content-type: $MEDIATYPE
541 > content-type: $MEDIATYPE
542 > user-agent: test
542 > user-agent: test
543 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
543 > frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
544 > EOF
544 > EOF
545 using raw connection to peer
545 using raw connection to peer
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
547 s> Accept-Encoding: identity\r\n
547 s> Accept-Encoding: identity\r\n
548 s> accept: application/mercurial-exp-framing-0004\r\n
548 s> accept: application/mercurial-exp-framing-0005\r\n
549 s> content-type: application/mercurial-exp-framing-0004\r\n
549 s> content-type: application/mercurial-exp-framing-0005\r\n
550 s> user-agent: test\r\n
550 s> user-agent: test\r\n
551 s> content-length: 22\r\n
551 s> content-length: 22\r\n
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
553 s> \r\n
553 s> \r\n
554 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
554 s> \x0e\x00\x00\x01\x00\x01\x01\x11\xa1DnameGpushkey
555 s> makefile('rb', None)
555 s> makefile('rb', None)
556 s> HTTP/1.1 403 Forbidden\r\n
556 s> HTTP/1.1 403 Forbidden\r\n
557 s> Server: testing stub value\r\n
557 s> Server: testing stub value\r\n
558 s> Date: $HTTP_DATE$\r\n
558 s> Date: $HTTP_DATE$\r\n
559 s> Content-Type: text/plain\r\n
559 s> Content-Type: text/plain\r\n
560 s> Content-Length: 52\r\n
560 s> Content-Length: 52\r\n
561 s> \r\n
561 s> \r\n
562 s> insufficient permissions to execute command: pushkey
562 s> insufficient permissions to execute command: pushkey
563
563
564 $ cat error.log
564 $ cat error.log
@@ -1,335 +1,335 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ cat >> $HGRCPATH << EOF
3 $ cat >> $HGRCPATH << EOF
4 > [web]
4 > [web]
5 > push_ssl = false
5 > push_ssl = false
6 > allow_push = *
6 > allow_push = *
7 > EOF
7 > EOF
8
8
9 $ hg init server
9 $ hg init server
10 $ cd server
10 $ cd server
11 $ touch a
11 $ touch a
12 $ hg -q commit -A -m initial
12 $ hg -q commit -A -m initial
13 $ cd ..
13 $ cd ..
14
14
15 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
15 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
16 $ cat hg.pid >> $DAEMON_PIDS
16 $ cat hg.pid >> $DAEMON_PIDS
17
17
18 compression formats are advertised in compression capability
18 compression formats are advertised in compression capability
19
19
20 #if zstd
20 #if zstd
21 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
21 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zstd,zlib$' > /dev/null
22 #else
22 #else
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
23 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=zlib$' > /dev/null
24 #endif
24 #endif
25
25
26 $ killdaemons.py
26 $ killdaemons.py
27
27
28 server.compressionengines can replace engines list wholesale
28 server.compressionengines can replace engines list wholesale
29
29
30 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
30 $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
31 $ cat hg.pid > $DAEMON_PIDS
31 $ cat hg.pid > $DAEMON_PIDS
32 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
32 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
33
33
34 $ killdaemons.py
34 $ killdaemons.py
35
35
36 Order of engines can also change
36 Order of engines can also change
37
37
38 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
38 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
39 $ cat hg.pid > $DAEMON_PIDS
39 $ cat hg.pid > $DAEMON_PIDS
40 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
40 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
41
41
42 $ killdaemons.py
42 $ killdaemons.py
43
43
44 Start a default server again
44 Start a default server again
45
45
46 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
46 $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
47 $ cat hg.pid > $DAEMON_PIDS
47 $ cat hg.pid > $DAEMON_PIDS
48
48
49 Server should send application/mercurial-0.1 to clients if no Accept is used
49 Server should send application/mercurial-0.1 to clients if no Accept is used
50
50
51 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
51 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
52 200 Script output follows
52 200 Script output follows
53 content-type: application/mercurial-0.1
53 content-type: application/mercurial-0.1
54 date: $HTTP_DATE$
54 date: $HTTP_DATE$
55 server: testing stub value
55 server: testing stub value
56 transfer-encoding: chunked
56 transfer-encoding: chunked
57
57
58 Server should send application/mercurial-0.1 when client says it wants it
58 Server should send application/mercurial-0.1 when client says it wants it
59
59
60 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
60 $ get-with-headers.py --hgproto '0.1' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
61 200 Script output follows
61 200 Script output follows
62 content-type: application/mercurial-0.1
62 content-type: application/mercurial-0.1
63 date: $HTTP_DATE$
63 date: $HTTP_DATE$
64 server: testing stub value
64 server: testing stub value
65 transfer-encoding: chunked
65 transfer-encoding: chunked
66
66
67 Server should send application/mercurial-0.2 when client says it wants it
67 Server should send application/mercurial-0.2 when client says it wants it
68
68
69 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
69 $ get-with-headers.py --hgproto '0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
70 200 Script output follows
70 200 Script output follows
71 content-type: application/mercurial-0.2
71 content-type: application/mercurial-0.2
72 date: $HTTP_DATE$
72 date: $HTTP_DATE$
73 server: testing stub value
73 server: testing stub value
74 transfer-encoding: chunked
74 transfer-encoding: chunked
75
75
76 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
76 $ get-with-headers.py --hgproto '0.1 0.2' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
77 200 Script output follows
77 200 Script output follows
78 content-type: application/mercurial-0.2
78 content-type: application/mercurial-0.2
79 date: $HTTP_DATE$
79 date: $HTTP_DATE$
80 server: testing stub value
80 server: testing stub value
81 transfer-encoding: chunked
81 transfer-encoding: chunked
82
82
83 Requesting a compression format that server doesn't support results will fall back to 0.1
83 Requesting a compression format that server doesn't support results will fall back to 0.1
84
84
85 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
85 $ get-with-headers.py --hgproto '0.2 comp=aa' --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' -
86 200 Script output follows
86 200 Script output follows
87 content-type: application/mercurial-0.1
87 content-type: application/mercurial-0.1
88 date: $HTTP_DATE$
88 date: $HTTP_DATE$
89 server: testing stub value
89 server: testing stub value
90 transfer-encoding: chunked
90 transfer-encoding: chunked
91
91
92 #if zstd
92 #if zstd
93 zstd is used if available
93 zstd is used if available
94
94
95 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
95 $ get-with-headers.py --hgproto '0.2 comp=zstd' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
96 $ f --size --hexdump --bytes 36 --sha1 resp
96 $ f --size --hexdump --bytes 36 --sha1 resp
97 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
97 resp: size=248, sha1=4d8d8f87fb82bd542ce52881fdc94f850748
98 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
98 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
99 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
99 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 73 74 64 |t follows...zstd|
100 0020: 28 b5 2f fd |(./.|
100 0020: 28 b5 2f fd |(./.|
101
101
102 #endif
102 #endif
103
103
104 application/mercurial-0.2 is not yet used on non-streaming responses
104 application/mercurial-0.2 is not yet used on non-streaming responses
105
105
106 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
106 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=heads' -
107 200 Script output follows
107 200 Script output follows
108 content-length: 41
108 content-length: 41
109 content-type: application/mercurial-0.1
109 content-type: application/mercurial-0.1
110 date: $HTTP_DATE$
110 date: $HTTP_DATE$
111 server: testing stub value
111 server: testing stub value
112
112
113 e93700bd72895c5addab234c56d4024b487a362f
113 e93700bd72895c5addab234c56d4024b487a362f
114
114
115 Now test protocol preference usage
115 Now test protocol preference usage
116
116
117 $ killdaemons.py
117 $ killdaemons.py
118 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
118 $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
119 $ cat hg.pid > $DAEMON_PIDS
119 $ cat hg.pid > $DAEMON_PIDS
120
120
121 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
121 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
122
122
123 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
123 $ get-with-headers.py --headeronly $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' Content-Type
124 200 Script output follows
124 200 Script output follows
125 content-type: application/mercurial-0.1
125 content-type: application/mercurial-0.1
126
126
127 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
127 $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
128 $ f --size --hexdump --bytes 28 --sha1 resp
128 $ f --size --hexdump --bytes 28 --sha1 resp
129 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
129 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
130 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
130 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
131 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
131 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
132
132
133 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
133 Explicit 0.1 will send zlib because "none" isn't supported on 0.1
134
134
135 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
135 $ get-with-headers.py --hgproto '0.1' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
136 $ f --size --hexdump --bytes 28 --sha1 resp
136 $ f --size --hexdump --bytes 28 --sha1 resp
137 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
137 resp: size=227, sha1=35a4c074da74f32f5440da3cbf04
138 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
138 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
139 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
139 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 78 |t follows..x|
140
140
141 0.2 with no compression will get "none" because that is server's preference
141 0.2 with no compression will get "none" because that is server's preference
142 (spec says ZL and UN are implicitly supported)
142 (spec says ZL and UN are implicitly supported)
143
143
144 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
144 $ get-with-headers.py --hgproto '0.2' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
145 $ f --size --hexdump --bytes 32 --sha1 resp
145 $ f --size --hexdump --bytes 32 --sha1 resp
146 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
146 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
147 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
147 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
148 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
148 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
149
149
150 Client receives server preference even if local order doesn't match
150 Client receives server preference even if local order doesn't match
151
151
152 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
152 $ get-with-headers.py --hgproto '0.2 comp=zlib,none' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
153 $ f --size --hexdump --bytes 32 --sha1 resp
153 $ f --size --hexdump --bytes 32 --sha1 resp
154 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
154 resp: size=432, sha1=ac931b412ec185a02e0e5bcff98dac83
155 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
155 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
156 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
156 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 6e 6f 6e 65 |t follows...none|
157
157
158 Client receives only supported format even if not server preferred format
158 Client receives only supported format even if not server preferred format
159
159
160 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
160 $ get-with-headers.py --hgproto '0.2 comp=zlib' $LOCALIP:$HGPORT '?cmd=getbundle&heads=e93700bd72895c5addab234c56d4024b487a362f&common=0000000000000000000000000000000000000000' > resp
161 $ f --size --hexdump --bytes 33 --sha1 resp
161 $ f --size --hexdump --bytes 33 --sha1 resp
162 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
162 resp: size=232, sha1=a1c727f0c9693ca15742a75c30419bc36
163 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
163 0000: 32 30 30 20 53 63 72 69 70 74 20 6f 75 74 70 75 |200 Script outpu|
164 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
164 0010: 74 20 66 6f 6c 6c 6f 77 73 0a 0a 04 7a 6c 69 62 |t follows...zlib|
165 0020: 78 |x|
165 0020: 78 |x|
166
166
167 $ killdaemons.py
167 $ killdaemons.py
168 $ cd ..
168 $ cd ..
169
169
170 Test listkeys for listing namespaces
170 Test listkeys for listing namespaces
171
171
172 $ hg init empty
172 $ hg init empty
173 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
173 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
174 $ cat hg.pid > $DAEMON_PIDS
174 $ cat hg.pid > $DAEMON_PIDS
175
175
176 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
176 $ hg --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
177 > command listkeys
177 > command listkeys
178 > namespace namespaces
178 > namespace namespaces
179 > EOF
179 > EOF
180 s> GET /?cmd=capabilities HTTP/1.1\r\n
180 s> GET /?cmd=capabilities HTTP/1.1\r\n
181 s> Accept-Encoding: identity\r\n
181 s> Accept-Encoding: identity\r\n
182 s> accept: application/mercurial-0.1\r\n
182 s> accept: application/mercurial-0.1\r\n
183 s> host: $LOCALIP:$HGPORT\r\n (glob)
183 s> host: $LOCALIP:$HGPORT\r\n (glob)
184 s> user-agent: Mercurial debugwireproto\r\n
184 s> user-agent: Mercurial debugwireproto\r\n
185 s> \r\n
185 s> \r\n
186 s> makefile('rb', None)
186 s> makefile('rb', None)
187 s> HTTP/1.1 200 Script output follows\r\n
187 s> HTTP/1.1 200 Script output follows\r\n
188 s> Server: testing stub value\r\n
188 s> Server: testing stub value\r\n
189 s> Date: $HTTP_DATE$\r\n
189 s> Date: $HTTP_DATE$\r\n
190 s> Content-Type: application/mercurial-0.1\r\n
190 s> Content-Type: application/mercurial-0.1\r\n
191 s> Content-Length: *\r\n (glob)
191 s> Content-Length: *\r\n (glob)
192 s> \r\n
192 s> \r\n
193 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
193 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
194 sending listkeys command
194 sending listkeys command
195 s> GET /?cmd=listkeys HTTP/1.1\r\n
195 s> GET /?cmd=listkeys HTTP/1.1\r\n
196 s> Accept-Encoding: identity\r\n
196 s> Accept-Encoding: identity\r\n
197 s> vary: X-HgArg-1,X-HgProto-1\r\n
197 s> vary: X-HgArg-1,X-HgProto-1\r\n
198 s> x-hgarg-1: namespace=namespaces\r\n
198 s> x-hgarg-1: namespace=namespaces\r\n
199 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
199 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
200 s> accept: application/mercurial-0.1\r\n
200 s> accept: application/mercurial-0.1\r\n
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
201 s> host: $LOCALIP:$HGPORT\r\n (glob)
202 s> user-agent: Mercurial debugwireproto\r\n
202 s> user-agent: Mercurial debugwireproto\r\n
203 s> \r\n
203 s> \r\n
204 s> makefile('rb', None)
204 s> makefile('rb', None)
205 s> HTTP/1.1 200 Script output follows\r\n
205 s> HTTP/1.1 200 Script output follows\r\n
206 s> Server: testing stub value\r\n
206 s> Server: testing stub value\r\n
207 s> Date: $HTTP_DATE$\r\n
207 s> Date: $HTTP_DATE$\r\n
208 s> Content-Type: application/mercurial-0.1\r\n
208 s> Content-Type: application/mercurial-0.1\r\n
209 s> Content-Length: 30\r\n
209 s> Content-Length: 30\r\n
210 s> \r\n
210 s> \r\n
211 s> bookmarks\t\n
211 s> bookmarks\t\n
212 s> namespaces\t\n
212 s> namespaces\t\n
213 s> phases\t
213 s> phases\t
214 response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
214 response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
215
215
216 Same thing, but with "httprequest" command
216 Same thing, but with "httprequest" command
217
217
218 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
218 $ hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT << EOF
219 > httprequest GET ?cmd=listkeys
219 > httprequest GET ?cmd=listkeys
220 > user-agent: test
220 > user-agent: test
221 > x-hgarg-1: namespace=namespaces
221 > x-hgarg-1: namespace=namespaces
222 > EOF
222 > EOF
223 using raw connection to peer
223 using raw connection to peer
224 s> GET /?cmd=listkeys HTTP/1.1\r\n
224 s> GET /?cmd=listkeys HTTP/1.1\r\n
225 s> Accept-Encoding: identity\r\n
225 s> Accept-Encoding: identity\r\n
226 s> user-agent: test\r\n
226 s> user-agent: test\r\n
227 s> x-hgarg-1: namespace=namespaces\r\n
227 s> x-hgarg-1: namespace=namespaces\r\n
228 s> host: $LOCALIP:$HGPORT\r\n (glob)
228 s> host: $LOCALIP:$HGPORT\r\n (glob)
229 s> \r\n
229 s> \r\n
230 s> makefile('rb', None)
230 s> makefile('rb', None)
231 s> HTTP/1.1 200 Script output follows\r\n
231 s> HTTP/1.1 200 Script output follows\r\n
232 s> Server: testing stub value\r\n
232 s> Server: testing stub value\r\n
233 s> Date: $HTTP_DATE$\r\n
233 s> Date: $HTTP_DATE$\r\n
234 s> Content-Type: application/mercurial-0.1\r\n
234 s> Content-Type: application/mercurial-0.1\r\n
235 s> Content-Length: 30\r\n
235 s> Content-Length: 30\r\n
236 s> \r\n
236 s> \r\n
237 s> bookmarks\t\n
237 s> bookmarks\t\n
238 s> namespaces\t\n
238 s> namespaces\t\n
239 s> phases\t
239 s> phases\t
240
240
241 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
241 Client with HTTPv2 enabled advertises that and gets old capabilities response from old server
242
242
243 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
243 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
244 > command heads
244 > command heads
245 > EOF
245 > EOF
246 s> GET /?cmd=capabilities HTTP/1.1\r\n
246 s> GET /?cmd=capabilities HTTP/1.1\r\n
247 s> Accept-Encoding: identity\r\n
247 s> Accept-Encoding: identity\r\n
248 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
248 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
249 s> x-hgproto-1: cbor\r\n
249 s> x-hgproto-1: cbor\r\n
250 s> x-hgupgrade-1: exp-http-v2-0001\r\n
250 s> x-hgupgrade-1: exp-http-v2-0001\r\n
251 s> accept: application/mercurial-0.1\r\n
251 s> accept: application/mercurial-0.1\r\n
252 s> host: $LOCALIP:$HGPORT\r\n (glob)
252 s> host: $LOCALIP:$HGPORT\r\n (glob)
253 s> user-agent: Mercurial debugwireproto\r\n
253 s> user-agent: Mercurial debugwireproto\r\n
254 s> \r\n
254 s> \r\n
255 s> makefile('rb', None)
255 s> makefile('rb', None)
256 s> HTTP/1.1 200 Script output follows\r\n
256 s> HTTP/1.1 200 Script output follows\r\n
257 s> Server: testing stub value\r\n
257 s> Server: testing stub value\r\n
258 s> Date: $HTTP_DATE$\r\n
258 s> Date: $HTTP_DATE$\r\n
259 s> Content-Type: application/mercurial-0.1\r\n
259 s> Content-Type: application/mercurial-0.1\r\n
260 s> Content-Length: 458\r\n
260 s> Content-Length: 458\r\n
261 s> \r\n
261 s> \r\n
262 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
262 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
263 sending heads command
263 sending heads command
264 s> GET /?cmd=heads HTTP/1.1\r\n
264 s> GET /?cmd=heads HTTP/1.1\r\n
265 s> Accept-Encoding: identity\r\n
265 s> Accept-Encoding: identity\r\n
266 s> vary: X-HgProto-1\r\n
266 s> vary: X-HgProto-1\r\n
267 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
267 s> x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull\r\n
268 s> accept: application/mercurial-0.1\r\n
268 s> accept: application/mercurial-0.1\r\n
269 s> host: $LOCALIP:$HGPORT\r\n (glob)
269 s> host: $LOCALIP:$HGPORT\r\n (glob)
270 s> user-agent: Mercurial debugwireproto\r\n
270 s> user-agent: Mercurial debugwireproto\r\n
271 s> \r\n
271 s> \r\n
272 s> makefile('rb', None)
272 s> makefile('rb', None)
273 s> HTTP/1.1 200 Script output follows\r\n
273 s> HTTP/1.1 200 Script output follows\r\n
274 s> Server: testing stub value\r\n
274 s> Server: testing stub value\r\n
275 s> Date: $HTTP_DATE$\r\n
275 s> Date: $HTTP_DATE$\r\n
276 s> Content-Type: application/mercurial-0.1\r\n
276 s> Content-Type: application/mercurial-0.1\r\n
277 s> Content-Length: 41\r\n
277 s> Content-Length: 41\r\n
278 s> \r\n
278 s> \r\n
279 s> 0000000000000000000000000000000000000000\n
279 s> 0000000000000000000000000000000000000000\n
280 response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
280 response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
281
281
282 $ killdaemons.py
282 $ killdaemons.py
283 $ enablehttpv2 empty
283 $ enablehttpv2 empty
284 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
284 $ hg -R empty serve -p $HGPORT -d --pid-file hg.pid
285 $ cat hg.pid > $DAEMON_PIDS
285 $ cat hg.pid > $DAEMON_PIDS
286
286
287 Client with HTTPv2 enabled automatically upgrades if the server supports it
287 Client with HTTPv2 enabled automatically upgrades if the server supports it
288
288
289 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
289 $ hg --config experimental.httppeer.advertise-v2=true --verbose debugwireproto http://$LOCALIP:$HGPORT << EOF
290 > command heads
290 > command heads
291 > EOF
291 > EOF
292 s> GET /?cmd=capabilities HTTP/1.1\r\n
292 s> GET /?cmd=capabilities HTTP/1.1\r\n
293 s> Accept-Encoding: identity\r\n
293 s> Accept-Encoding: identity\r\n
294 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
294 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
295 s> x-hgproto-1: cbor\r\n
295 s> x-hgproto-1: cbor\r\n
296 s> x-hgupgrade-1: exp-http-v2-0001\r\n
296 s> x-hgupgrade-1: exp-http-v2-0001\r\n
297 s> accept: application/mercurial-0.1\r\n
297 s> accept: application/mercurial-0.1\r\n
298 s> host: $LOCALIP:$HGPORT\r\n (glob)
298 s> host: $LOCALIP:$HGPORT\r\n (glob)
299 s> user-agent: Mercurial debugwireproto\r\n
299 s> user-agent: Mercurial debugwireproto\r\n
300 s> \r\n
300 s> \r\n
301 s> makefile('rb', None)
301 s> makefile('rb', None)
302 s> HTTP/1.1 200 OK\r\n
302 s> HTTP/1.1 200 OK\r\n
303 s> Server: testing stub value\r\n
303 s> Server: testing stub value\r\n
304 s> Date: $HTTP_DATE$\r\n
304 s> Date: $HTTP_DATE$\r\n
305 s> Content-Type: application/mercurial-cbor\r\n
305 s> Content-Type: application/mercurial-cbor\r\n
306 s> Content-Length: *\r\n (glob)
306 s> Content-Length: *\r\n (glob)
307 s> \r\n
307 s> \r\n
308 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0004GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
308 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
309 sending heads command
309 sending heads command
310 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
310 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
311 s> Accept-Encoding: identity\r\n
311 s> Accept-Encoding: identity\r\n
312 s> accept: application/mercurial-exp-framing-0004\r\n
312 s> accept: application/mercurial-exp-framing-0005\r\n
313 s> content-type: application/mercurial-exp-framing-0004\r\n
313 s> content-type: application/mercurial-exp-framing-0005\r\n
314 s> content-length: 20\r\n
314 s> content-length: 20\r\n
315 s> host: $LOCALIP:$HGPORT\r\n (glob)
315 s> host: $LOCALIP:$HGPORT\r\n (glob)
316 s> user-agent: Mercurial debugwireproto\r\n
316 s> user-agent: Mercurial debugwireproto\r\n
317 s> \r\n
317 s> \r\n
318 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
318 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
319 s> makefile('rb', None)
319 s> makefile('rb', None)
320 s> HTTP/1.1 200 OK\r\n
320 s> HTTP/1.1 200 OK\r\n
321 s> Server: testing stub value\r\n
321 s> Server: testing stub value\r\n
322 s> Date: $HTTP_DATE$\r\n
322 s> Date: $HTTP_DATE$\r\n
323 s> Content-Type: application/mercurial-exp-framing-0004\r\n
323 s> Content-Type: application/mercurial-exp-framing-0005\r\n
324 s> Transfer-Encoding: chunked\r\n
324 s> Transfer-Encoding: chunked\r\n
325 s> \r\n
325 s> \r\n
326 s> 1e\r\n
326 s> 29\r\n
327 s> \x16\x00\x00\x01\x00\x02\x012
327 s> !\x00\x00\x01\x00\x02\x012
328 s> \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
328 s> \xa1FstatusBok\x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
329 s> \r\n
329 s> \r\n
330 received frame(size=22; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
330 received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
331 s> 0\r\n
331 s> 0\r\n
332 s> \r\n
332 s> \r\n
333 response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
333 response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
334
334
335 $ killdaemons.py
335 $ killdaemons.py
@@ -1,72 +1,72 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg up B
14 $ hg up B
15 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 $ hg branch branch1
16 $ hg branch branch1
17 marked working directory as branch branch1
17 marked working directory as branch branch1
18 (branches are permanent and global, did you want a bookmark?)
18 (branches are permanent and global, did you want a bookmark?)
19 $ echo b1 > foo
19 $ echo b1 > foo
20 $ hg -q commit -A -m 'branch 1'
20 $ hg -q commit -A -m 'branch 1'
21 $ hg up B
21 $ hg up B
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
22 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
23 $ hg branch branch2
23 $ hg branch branch2
24 marked working directory as branch branch2
24 marked working directory as branch branch2
25 $ echo b2 > foo
25 $ echo b2 > foo
26 $ hg -q commit -A -m 'branch 2'
26 $ hg -q commit -A -m 'branch 2'
27
27
28 $ hg log -T '{rev}:{node} {branch} {desc}\n'
28 $ hg log -T '{rev}:{node} {branch} {desc}\n'
29 5:224161c7589aa48fa83a48feff5e95b56ae327fc branch2 branch 2
29 5:224161c7589aa48fa83a48feff5e95b56ae327fc branch2 branch 2
30 4:b5faacdfd2633768cb3152336cc0953381266688 branch1 branch 1
30 4:b5faacdfd2633768cb3152336cc0953381266688 branch1 branch 1
31 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 default D
31 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 default D
32 2:26805aba1e600a82e93661149f2313866a221a7b default C
32 2:26805aba1e600a82e93661149f2313866a221a7b default C
33 1:112478962961147124edd43549aedd1a335e44bf default B
33 1:112478962961147124edd43549aedd1a335e44bf default B
34 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 default A
34 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 default A
35
35
36 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
36 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
37 $ cat hg.pid > $DAEMON_PIDS
37 $ cat hg.pid > $DAEMON_PIDS
38
38
39 No arguments returns something reasonable
39 No arguments returns something reasonable
40
40
41 $ sendhttpv2peer << EOF
41 $ sendhttpv2peer << EOF
42 > command branchmap
42 > command branchmap
43 > EOF
43 > EOF
44 creating http peer for wire protocol version 2
44 creating http peer for wire protocol version 2
45 sending branchmap command
45 sending branchmap command
46 s> POST /api/exp-http-v2-0001/ro/branchmap HTTP/1.1\r\n
46 s> POST /api/exp-http-v2-0001/ro/branchmap HTTP/1.1\r\n
47 s> Accept-Encoding: identity\r\n
47 s> Accept-Encoding: identity\r\n
48 s> accept: application/mercurial-exp-framing-0004\r\n
48 s> accept: application/mercurial-exp-framing-0005\r\n
49 s> content-type: application/mercurial-exp-framing-0004\r\n
49 s> content-type: application/mercurial-exp-framing-0005\r\n
50 s> content-length: 24\r\n
50 s> content-length: 24\r\n
51 s> host: $LOCALIP:$HGPORT\r\n (glob)
51 s> host: $LOCALIP:$HGPORT\r\n (glob)
52 s> user-agent: Mercurial debugwireproto\r\n
52 s> user-agent: Mercurial debugwireproto\r\n
53 s> \r\n
53 s> \r\n
54 s> \x10\x00\x00\x01\x00\x01\x01\x11\xa1DnameIbranchmap
54 s> \x10\x00\x00\x01\x00\x01\x01\x11\xa1DnameIbranchmap
55 s> makefile('rb', None)
55 s> makefile('rb', None)
56 s> HTTP/1.1 200 OK\r\n
56 s> HTTP/1.1 200 OK\r\n
57 s> Server: testing stub value\r\n
57 s> Server: testing stub value\r\n
58 s> Date: $HTTP_DATE$\r\n
58 s> Date: $HTTP_DATE$\r\n
59 s> Content-Type: application/mercurial-exp-framing-0004\r\n
59 s> Content-Type: application/mercurial-exp-framing-0005\r\n
60 s> Transfer-Encoding: chunked\r\n
60 s> Transfer-Encoding: chunked\r\n
61 s> \r\n
61 s> \r\n
62 s> 78\r\n
62 s> 83\r\n
63 s> p\x00\x00\x01\x00\x02\x012
63 s> {\x00\x00\x01\x00\x02\x012
64 s> \xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
64 s> \xa1FstatusBok\xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
65 s> \x82\xe96a\x14\x9f#\x13\x86j"\x1a{T\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82
65 s> \x82\xe96a\x14\x9f#\x13\x86j"\x1a{T\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82
66 s> \r\n
66 s> \r\n
67 received frame(size=112; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
67 received frame(size=123; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
68 s> 0\r\n
68 s> 0\r\n
69 s> \r\n
69 s> \r\n
70 response: {b'branch1': [b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'], b'branch2': [b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'], b'default': [b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{', b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82']}
70 response: {b'branch1': [b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'], b'branch2': [b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'], b'default': [b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{', b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82']}
71
71
72 $ cat error.log
72 $ cat error.log
@@ -1,247 +1,247 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
4 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
5 $ cat hg.pid > $DAEMON_PIDS
5 $ cat hg.pid > $DAEMON_PIDS
6
6
7 A normal capabilities request is serviced for version 1
7 A normal capabilities request is serviced for version 1
8
8
9 $ sendhttpraw << EOF
9 $ sendhttpraw << EOF
10 > httprequest GET ?cmd=capabilities
10 > httprequest GET ?cmd=capabilities
11 > user-agent: test
11 > user-agent: test
12 > EOF
12 > EOF
13 using raw connection to peer
13 using raw connection to peer
14 s> GET /?cmd=capabilities HTTP/1.1\r\n
14 s> GET /?cmd=capabilities HTTP/1.1\r\n
15 s> Accept-Encoding: identity\r\n
15 s> Accept-Encoding: identity\r\n
16 s> user-agent: test\r\n
16 s> user-agent: test\r\n
17 s> host: $LOCALIP:$HGPORT\r\n (glob)
17 s> host: $LOCALIP:$HGPORT\r\n (glob)
18 s> \r\n
18 s> \r\n
19 s> makefile('rb', None)
19 s> makefile('rb', None)
20 s> HTTP/1.1 200 Script output follows\r\n
20 s> HTTP/1.1 200 Script output follows\r\n
21 s> Server: testing stub value\r\n
21 s> Server: testing stub value\r\n
22 s> Date: $HTTP_DATE$\r\n
22 s> Date: $HTTP_DATE$\r\n
23 s> Content-Type: application/mercurial-0.1\r\n
23 s> Content-Type: application/mercurial-0.1\r\n
24 s> Content-Length: 458\r\n
24 s> Content-Length: 458\r\n
25 s> \r\n
25 s> \r\n
26 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
26 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
27
27
28 A proper request without the API server enabled returns the legacy response
28 A proper request without the API server enabled returns the legacy response
29
29
30 $ sendhttpraw << EOF
30 $ sendhttpraw << EOF
31 > httprequest GET ?cmd=capabilities
31 > httprequest GET ?cmd=capabilities
32 > user-agent: test
32 > user-agent: test
33 > x-hgupgrade-1: foo
33 > x-hgupgrade-1: foo
34 > x-hgproto-1: cbor
34 > x-hgproto-1: cbor
35 > EOF
35 > EOF
36 using raw connection to peer
36 using raw connection to peer
37 s> GET /?cmd=capabilities HTTP/1.1\r\n
37 s> GET /?cmd=capabilities HTTP/1.1\r\n
38 s> Accept-Encoding: identity\r\n
38 s> Accept-Encoding: identity\r\n
39 s> user-agent: test\r\n
39 s> user-agent: test\r\n
40 s> x-hgproto-1: cbor\r\n
40 s> x-hgproto-1: cbor\r\n
41 s> x-hgupgrade-1: foo\r\n
41 s> x-hgupgrade-1: foo\r\n
42 s> host: $LOCALIP:$HGPORT\r\n (glob)
42 s> host: $LOCALIP:$HGPORT\r\n (glob)
43 s> \r\n
43 s> \r\n
44 s> makefile('rb', None)
44 s> makefile('rb', None)
45 s> HTTP/1.1 200 Script output follows\r\n
45 s> HTTP/1.1 200 Script output follows\r\n
46 s> Server: testing stub value\r\n
46 s> Server: testing stub value\r\n
47 s> Date: $HTTP_DATE$\r\n
47 s> Date: $HTTP_DATE$\r\n
48 s> Content-Type: application/mercurial-0.1\r\n
48 s> Content-Type: application/mercurial-0.1\r\n
49 s> Content-Length: 458\r\n
49 s> Content-Length: 458\r\n
50 s> \r\n
50 s> \r\n
51 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
51 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
52
52
53 Restart with just API server enabled. This enables serving the new format.
53 Restart with just API server enabled. This enables serving the new format.
54
54
55 $ killdaemons.py
55 $ killdaemons.py
56 $ cat error.log
56 $ cat error.log
57
57
58 $ cat >> server/.hg/hgrc << EOF
58 $ cat >> server/.hg/hgrc << EOF
59 > [experimental]
59 > [experimental]
60 > web.apiserver = true
60 > web.apiserver = true
61 > EOF
61 > EOF
62
62
63 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
63 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
64 $ cat hg.pid > $DAEMON_PIDS
64 $ cat hg.pid > $DAEMON_PIDS
65
65
66 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
66 X-HgUpgrade-<N> without CBOR advertisement uses legacy response
67
67
68 $ sendhttpraw << EOF
68 $ sendhttpraw << EOF
69 > httprequest GET ?cmd=capabilities
69 > httprequest GET ?cmd=capabilities
70 > user-agent: test
70 > user-agent: test
71 > x-hgupgrade-1: foo bar
71 > x-hgupgrade-1: foo bar
72 > EOF
72 > EOF
73 using raw connection to peer
73 using raw connection to peer
74 s> GET /?cmd=capabilities HTTP/1.1\r\n
74 s> GET /?cmd=capabilities HTTP/1.1\r\n
75 s> Accept-Encoding: identity\r\n
75 s> Accept-Encoding: identity\r\n
76 s> user-agent: test\r\n
76 s> user-agent: test\r\n
77 s> x-hgupgrade-1: foo bar\r\n
77 s> x-hgupgrade-1: foo bar\r\n
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
78 s> host: $LOCALIP:$HGPORT\r\n (glob)
79 s> \r\n
79 s> \r\n
80 s> makefile('rb', None)
80 s> makefile('rb', None)
81 s> HTTP/1.1 200 Script output follows\r\n
81 s> HTTP/1.1 200 Script output follows\r\n
82 s> Server: testing stub value\r\n
82 s> Server: testing stub value\r\n
83 s> Date: $HTTP_DATE$\r\n
83 s> Date: $HTTP_DATE$\r\n
84 s> Content-Type: application/mercurial-0.1\r\n
84 s> Content-Type: application/mercurial-0.1\r\n
85 s> Content-Length: 458\r\n
85 s> Content-Length: 458\r\n
86 s> \r\n
86 s> \r\n
87 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
87 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
88
88
89 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
89 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
90
90
91 $ sendhttpraw << EOF
91 $ sendhttpraw << EOF
92 > httprequest GET ?cmd=capabilities
92 > httprequest GET ?cmd=capabilities
93 > user-agent: test
93 > user-agent: test
94 > x-hgupgrade-1: foo bar
94 > x-hgupgrade-1: foo bar
95 > x-hgproto-1: some value
95 > x-hgproto-1: some value
96 > EOF
96 > EOF
97 using raw connection to peer
97 using raw connection to peer
98 s> GET /?cmd=capabilities HTTP/1.1\r\n
98 s> GET /?cmd=capabilities HTTP/1.1\r\n
99 s> Accept-Encoding: identity\r\n
99 s> Accept-Encoding: identity\r\n
100 s> user-agent: test\r\n
100 s> user-agent: test\r\n
101 s> x-hgproto-1: some value\r\n
101 s> x-hgproto-1: some value\r\n
102 s> x-hgupgrade-1: foo bar\r\n
102 s> x-hgupgrade-1: foo bar\r\n
103 s> host: $LOCALIP:$HGPORT\r\n (glob)
103 s> host: $LOCALIP:$HGPORT\r\n (glob)
104 s> \r\n
104 s> \r\n
105 s> makefile('rb', None)
105 s> makefile('rb', None)
106 s> HTTP/1.1 200 Script output follows\r\n
106 s> HTTP/1.1 200 Script output follows\r\n
107 s> Server: testing stub value\r\n
107 s> Server: testing stub value\r\n
108 s> Date: $HTTP_DATE$\r\n
108 s> Date: $HTTP_DATE$\r\n
109 s> Content-Type: application/mercurial-0.1\r\n
109 s> Content-Type: application/mercurial-0.1\r\n
110 s> Content-Length: 458\r\n
110 s> Content-Length: 458\r\n
111 s> \r\n
111 s> \r\n
112 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
112 s> batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
113
113
114 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
114 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
115
115
116 $ sendhttpraw << EOF
116 $ sendhttpraw << EOF
117 > httprequest GET ?cmd=capabilities
117 > httprequest GET ?cmd=capabilities
118 > user-agent: test
118 > user-agent: test
119 > x-hgupgrade-1: foo bar
119 > x-hgupgrade-1: foo bar
120 > x-hgproto-1: cbor
120 > x-hgproto-1: cbor
121 > EOF
121 > EOF
122 using raw connection to peer
122 using raw connection to peer
123 s> GET /?cmd=capabilities HTTP/1.1\r\n
123 s> GET /?cmd=capabilities HTTP/1.1\r\n
124 s> Accept-Encoding: identity\r\n
124 s> Accept-Encoding: identity\r\n
125 s> user-agent: test\r\n
125 s> user-agent: test\r\n
126 s> x-hgproto-1: cbor\r\n
126 s> x-hgproto-1: cbor\r\n
127 s> x-hgupgrade-1: foo bar\r\n
127 s> x-hgupgrade-1: foo bar\r\n
128 s> host: $LOCALIP:$HGPORT\r\n (glob)
128 s> host: $LOCALIP:$HGPORT\r\n (glob)
129 s> \r\n
129 s> \r\n
130 s> makefile('rb', None)
130 s> makefile('rb', None)
131 s> HTTP/1.1 200 OK\r\n
131 s> HTTP/1.1 200 OK\r\n
132 s> Server: testing stub value\r\n
132 s> Server: testing stub value\r\n
133 s> Date: $HTTP_DATE$\r\n
133 s> Date: $HTTP_DATE$\r\n
134 s> Content-Type: application/mercurial-cbor\r\n
134 s> Content-Type: application/mercurial-cbor\r\n
135 s> Content-Length: 496\r\n
135 s> Content-Length: 496\r\n
136 s> \r\n
136 s> \r\n
137 s> \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
137 s> \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
138 cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
138 cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
139
139
140 Restart server to enable HTTPv2
140 Restart server to enable HTTPv2
141
141
142 $ killdaemons.py
142 $ killdaemons.py
143 $ enablehttpv2 server
143 $ enablehttpv2 server
144 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
144 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid -E error.log
145
145
146 Only requested API services are returned
146 Only requested API services are returned
147
147
148 $ sendhttpraw << EOF
148 $ sendhttpraw << EOF
149 > httprequest GET ?cmd=capabilities
149 > httprequest GET ?cmd=capabilities
150 > user-agent: test
150 > user-agent: test
151 > x-hgupgrade-1: foo bar
151 > x-hgupgrade-1: foo bar
152 > x-hgproto-1: cbor
152 > x-hgproto-1: cbor
153 > EOF
153 > EOF
154 using raw connection to peer
154 using raw connection to peer
155 s> GET /?cmd=capabilities HTTP/1.1\r\n
155 s> GET /?cmd=capabilities HTTP/1.1\r\n
156 s> Accept-Encoding: identity\r\n
156 s> Accept-Encoding: identity\r\n
157 s> user-agent: test\r\n
157 s> user-agent: test\r\n
158 s> x-hgproto-1: cbor\r\n
158 s> x-hgproto-1: cbor\r\n
159 s> x-hgupgrade-1: foo bar\r\n
159 s> x-hgupgrade-1: foo bar\r\n
160 s> host: $LOCALIP:$HGPORT\r\n (glob)
160 s> host: $LOCALIP:$HGPORT\r\n (glob)
161 s> \r\n
161 s> \r\n
162 s> makefile('rb', None)
162 s> makefile('rb', None)
163 s> HTTP/1.1 200 OK\r\n
163 s> HTTP/1.1 200 OK\r\n
164 s> Server: testing stub value\r\n
164 s> Server: testing stub value\r\n
165 s> Date: $HTTP_DATE$\r\n
165 s> Date: $HTTP_DATE$\r\n
166 s> Content-Type: application/mercurial-cbor\r\n
166 s> Content-Type: application/mercurial-cbor\r\n
167 s> Content-Length: 496\r\n
167 s> Content-Length: 496\r\n
168 s> \r\n
168 s> \r\n
169 s> \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
169 s> \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
170 cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
170 cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
171
171
172 Request for HTTPv2 service returns information about it
172 Request for HTTPv2 service returns information about it
173
173
174 $ sendhttpraw << EOF
174 $ sendhttpraw << EOF
175 > httprequest GET ?cmd=capabilities
175 > httprequest GET ?cmd=capabilities
176 > user-agent: test
176 > user-agent: test
177 > x-hgupgrade-1: exp-http-v2-0001 foo bar
177 > x-hgupgrade-1: exp-http-v2-0001 foo bar
178 > x-hgproto-1: cbor
178 > x-hgproto-1: cbor
179 > EOF
179 > EOF
180 using raw connection to peer
180 using raw connection to peer
181 s> GET /?cmd=capabilities HTTP/1.1\r\n
181 s> GET /?cmd=capabilities HTTP/1.1\r\n
182 s> Accept-Encoding: identity\r\n
182 s> Accept-Encoding: identity\r\n
183 s> user-agent: test\r\n
183 s> user-agent: test\r\n
184 s> x-hgproto-1: cbor\r\n
184 s> x-hgproto-1: cbor\r\n
185 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
185 s> x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
186 s> host: $LOCALIP:$HGPORT\r\n (glob)
186 s> host: $LOCALIP:$HGPORT\r\n (glob)
187 s> \r\n
187 s> \r\n
188 s> makefile('rb', None)
188 s> makefile('rb', None)
189 s> HTTP/1.1 200 OK\r\n
189 s> HTTP/1.1 200 OK\r\n
190 s> Server: testing stub value\r\n
190 s> Server: testing stub value\r\n
191 s> Date: $HTTP_DATE$\r\n
191 s> Date: $HTTP_DATE$\r\n
192 s> Content-Type: application/mercurial-cbor\r\n
192 s> Content-Type: application/mercurial-cbor\r\n
193 s> Content-Length: *\r\n (glob)
193 s> Content-Length: *\r\n (glob)
194 s> \r\n
194 s> \r\n
195 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0004GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
195 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
196 cbor> {b'apibase': b'api/', b'apis': {b'exp-http-v2-0001': {b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zstd'}, {b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0004'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
196 cbor> {b'apibase': b'api/', b'apis': {b'exp-http-v2-0001': {b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zstd'}, {b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0005'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
197
197
198 capabilities command returns expected info
198 capabilities command returns expected info
199
199
200 $ sendhttpv2peerhandshake << EOF
200 $ sendhttpv2peerhandshake << EOF
201 > command capabilities
201 > command capabilities
202 > EOF
202 > EOF
203 creating http peer for wire protocol version 2
203 creating http peer for wire protocol version 2
204 s> GET /?cmd=capabilities HTTP/1.1\r\n
204 s> GET /?cmd=capabilities HTTP/1.1\r\n
205 s> Accept-Encoding: identity\r\n
205 s> Accept-Encoding: identity\r\n
206 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
206 s> vary: X-HgProto-1,X-HgUpgrade-1\r\n
207 s> x-hgproto-1: cbor\r\n
207 s> x-hgproto-1: cbor\r\n
208 s> x-hgupgrade-1: exp-http-v2-0001\r\n
208 s> x-hgupgrade-1: exp-http-v2-0001\r\n
209 s> accept: application/mercurial-0.1\r\n
209 s> accept: application/mercurial-0.1\r\n
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
210 s> host: $LOCALIP:$HGPORT\r\n (glob)
211 s> user-agent: Mercurial debugwireproto\r\n
211 s> user-agent: Mercurial debugwireproto\r\n
212 s> \r\n
212 s> \r\n
213 s> makefile('rb', None)
213 s> makefile('rb', None)
214 s> HTTP/1.1 200 OK\r\n
214 s> HTTP/1.1 200 OK\r\n
215 s> Server: testing stub value\r\n
215 s> Server: testing stub value\r\n
216 s> Date: $HTTP_DATE$\r\n
216 s> Date: $HTTP_DATE$\r\n
217 s> Content-Type: application/mercurial-cbor\r\n
217 s> Content-Type: application/mercurial-cbor\r\n
218 s> Content-Length: *\r\n (glob)
218 s> Content-Length: *\r\n (glob)
219 s> \r\n
219 s> \r\n
220 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0004GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
220 s> \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xcabatch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
221 sending capabilities command
221 sending capabilities command
222 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
222 s> POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
223 s> Accept-Encoding: identity\r\n
223 s> Accept-Encoding: identity\r\n
224 s> *\r\n (glob)
224 s> *\r\n (glob)
225 s> content-type: application/mercurial-exp-framing-0004\r\n
225 s> content-type: application/mercurial-exp-framing-0005\r\n
226 s> content-length: 27\r\n
226 s> content-length: 27\r\n
227 s> host: $LOCALIP:$HGPORT\r\n (glob)
227 s> host: $LOCALIP:$HGPORT\r\n (glob)
228 s> user-agent: Mercurial debugwireproto\r\n
228 s> user-agent: Mercurial debugwireproto\r\n
229 s> \r\n
229 s> \r\n
230 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
230 s> \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLcapabilities
231 s> makefile('rb', None)
231 s> makefile('rb', None)
232 s> HTTP/1.1 200 OK\r\n
232 s> HTTP/1.1 200 OK\r\n
233 s> Server: testing stub value\r\n
233 s> Server: testing stub value\r\n
234 s> Date: $HTTP_DATE$\r\n
234 s> Date: $HTTP_DATE$\r\n
235 s> Content-Type: application/mercurial-exp-framing-0004\r\n
235 s> Content-Type: application/mercurial-exp-framing-0005\r\n
236 s> Transfer-Encoding: chunked\r\n
236 s> Transfer-Encoding: chunked\r\n
237 s> \r\n
237 s> \r\n
238 s> 1d7\r\n
238 s> 1e2\r\n
239 s> \xcf\x01\x00\x01\x00\x02\x012
239 s> \xda\x01\x00\x01\x00\x02\x012
240 s> \xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0004
240 s> \xa1FstatusBok\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x82\xa1DnameDzstd\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005
241 s> \r\n
241 s> \r\n
242 received frame(size=463; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
242 received frame(size=474; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
243 s> 0\r\n
243 s> 0\r\n
244 s> \r\n
244 s> \r\n
245 response: [{b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zstd'}, {b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0004'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}]
245 response: [{b'status': b'ok'}, {b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zstd'}, {b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0005'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}]
246
246
247 $ cat error.log
247 $ cat error.log
@@ -1,96 +1,96 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > H I J
7 > H I J
8 > | | |
8 > | | |
9 > E F G
9 > E F G
10 > | |/
10 > | |/
11 > C D
11 > C D
12 > |/
12 > |/
13 > B
13 > B
14 > |
14 > |
15 > A
15 > A
16 > EOF
16 > EOF
17
17
18 $ hg phase --force --secret J
18 $ hg phase --force --secret J
19 $ hg phase --public E
19 $ hg phase --public E
20
20
21 $ hg log -r 'E + H + I + G + J' -T '{rev}:{node} {desc} {phase}\n'
21 $ hg log -r 'E + H + I + G + J' -T '{rev}:{node} {desc} {phase}\n'
22 4:78d2dca436b2f5b188ac267e29b81e07266d38fc E public
22 4:78d2dca436b2f5b188ac267e29b81e07266d38fc E public
23 7:ae492e36b0c8339ffaf328d00b85b4525de1165e H draft
23 7:ae492e36b0c8339ffaf328d00b85b4525de1165e H draft
24 8:1d6f6b91d44aaba6d5e580bc30a9948530dbe00b I draft
24 8:1d6f6b91d44aaba6d5e580bc30a9948530dbe00b I draft
25 6:29446d2dc5419c5f97447a8bc062e4cc328bf241 G draft
25 6:29446d2dc5419c5f97447a8bc062e4cc328bf241 G draft
26 9:dec04b246d7cbb670c6689806c05ad17c835284e J secret
26 9:dec04b246d7cbb670c6689806c05ad17c835284e J secret
27
27
28 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
28 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
29 $ cat hg.pid > $DAEMON_PIDS
29 $ cat hg.pid > $DAEMON_PIDS
30
30
31 All non-secret heads returned by default
31 All non-secret heads returned by default
32
32
33 $ sendhttpv2peer << EOF
33 $ sendhttpv2peer << EOF
34 > command heads
34 > command heads
35 > EOF
35 > EOF
36 creating http peer for wire protocol version 2
36 creating http peer for wire protocol version 2
37 sending heads command
37 sending heads command
38 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
38 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
39 s> Accept-Encoding: identity\r\n
39 s> Accept-Encoding: identity\r\n
40 s> accept: application/mercurial-exp-framing-0004\r\n
40 s> accept: application/mercurial-exp-framing-0005\r\n
41 s> content-type: application/mercurial-exp-framing-0004\r\n
41 s> content-type: application/mercurial-exp-framing-0005\r\n
42 s> content-length: 20\r\n
42 s> content-length: 20\r\n
43 s> host: $LOCALIP:$HGPORT\r\n (glob)
43 s> host: $LOCALIP:$HGPORT\r\n (glob)
44 s> user-agent: Mercurial debugwireproto\r\n
44 s> user-agent: Mercurial debugwireproto\r\n
45 s> \r\n
45 s> \r\n
46 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
46 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEheads
47 s> makefile('rb', None)
47 s> makefile('rb', None)
48 s> HTTP/1.1 200 OK\r\n
48 s> HTTP/1.1 200 OK\r\n
49 s> Server: testing stub value\r\n
49 s> Server: testing stub value\r\n
50 s> Date: $HTTP_DATE$\r\n
50 s> Date: $HTTP_DATE$\r\n
51 s> Content-Type: application/mercurial-exp-framing-0004\r\n
51 s> Content-Type: application/mercurial-exp-framing-0005\r\n
52 s> Transfer-Encoding: chunked\r\n
52 s> Transfer-Encoding: chunked\r\n
53 s> \r\n
53 s> \r\n
54 s> 48\r\n
54 s> 53\r\n
55 s> @\x00\x00\x01\x00\x02\x012
55 s> K\x00\x00\x01\x00\x02\x012
56 s> \x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
56 s> \xa1FstatusBok\x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
57 s> \r\n
57 s> \r\n
58 received frame(size=64; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
58 received frame(size=75; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
59 s> 0\r\n
59 s> 0\r\n
60 s> \r\n
60 s> \r\n
61 response: [b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b', b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^', b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A']
61 response: [b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b', b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^', b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A']
62
62
63 Requesting just the public heads works
63 Requesting just the public heads works
64
64
65 $ sendhttpv2peer << EOF
65 $ sendhttpv2peer << EOF
66 > command heads
66 > command heads
67 > publiconly 1
67 > publiconly 1
68 > EOF
68 > EOF
69 creating http peer for wire protocol version 2
69 creating http peer for wire protocol version 2
70 sending heads command
70 sending heads command
71 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
71 s> POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
72 s> Accept-Encoding: identity\r\n
72 s> Accept-Encoding: identity\r\n
73 s> accept: application/mercurial-exp-framing-0004\r\n
73 s> accept: application/mercurial-exp-framing-0005\r\n
74 s> content-type: application/mercurial-exp-framing-0004\r\n
74 s> content-type: application/mercurial-exp-framing-0005\r\n
75 s> content-length: 39\r\n
75 s> content-length: 39\r\n
76 s> host: $LOCALIP:$HGPORT\r\n (glob)
76 s> host: $LOCALIP:$HGPORT\r\n (glob)
77 s> user-agent: Mercurial debugwireproto\r\n
77 s> user-agent: Mercurial debugwireproto\r\n
78 s> \r\n
78 s> \r\n
79 s> \x1f\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1JpubliconlyA1DnameEheads
79 s> \x1f\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1JpubliconlyA1DnameEheads
80 s> makefile('rb', None)
80 s> makefile('rb', None)
81 s> HTTP/1.1 200 OK\r\n
81 s> HTTP/1.1 200 OK\r\n
82 s> Server: testing stub value\r\n
82 s> Server: testing stub value\r\n
83 s> Date: $HTTP_DATE$\r\n
83 s> Date: $HTTP_DATE$\r\n
84 s> Content-Type: application/mercurial-exp-framing-0004\r\n
84 s> Content-Type: application/mercurial-exp-framing-0005\r\n
85 s> Transfer-Encoding: chunked\r\n
85 s> Transfer-Encoding: chunked\r\n
86 s> \r\n
86 s> \r\n
87 s> 1e\r\n
87 s> 29\r\n
88 s> \x16\x00\x00\x01\x00\x02\x012
88 s> !\x00\x00\x01\x00\x02\x012
89 s> \x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
89 s> \xa1FstatusBok\x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
90 s> \r\n
90 s> \r\n
91 received frame(size=22; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
91 received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
92 s> 0\r\n
92 s> 0\r\n
93 s> \r\n
93 s> \r\n
94 response: [b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc']
94 response: [b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc']
95
95
96 $ cat error.log
96 $ cat error.log
@@ -1,121 +1,121 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg log -T '{rev}:{node} {desc}\n'
14 $ hg log -T '{rev}:{node} {desc}\n'
15 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
15 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
16 2:26805aba1e600a82e93661149f2313866a221a7b C
16 2:26805aba1e600a82e93661149f2313866a221a7b C
17 1:112478962961147124edd43549aedd1a335e44bf B
17 1:112478962961147124edd43549aedd1a335e44bf B
18 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
18 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
19
19
20 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
21 $ cat hg.pid > $DAEMON_PIDS
21 $ cat hg.pid > $DAEMON_PIDS
22
22
23 No arguments returns something reasonable
23 No arguments returns something reasonable
24
24
25 $ sendhttpv2peer << EOF
25 $ sendhttpv2peer << EOF
26 > command known
26 > command known
27 > EOF
27 > EOF
28 creating http peer for wire protocol version 2
28 creating http peer for wire protocol version 2
29 sending known command
29 sending known command
30 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
30 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
31 s> Accept-Encoding: identity\r\n
31 s> Accept-Encoding: identity\r\n
32 s> accept: application/mercurial-exp-framing-0004\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0004\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
34 s> content-length: 20\r\n
34 s> content-length: 20\r\n
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
36 s> user-agent: Mercurial debugwireproto\r\n
36 s> user-agent: Mercurial debugwireproto\r\n
37 s> \r\n
37 s> \r\n
38 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEknown
38 s> \x0c\x00\x00\x01\x00\x01\x01\x11\xa1DnameEknown
39 s> makefile('rb', None)
39 s> makefile('rb', None)
40 s> HTTP/1.1 200 OK\r\n
40 s> HTTP/1.1 200 OK\r\n
41 s> Server: testing stub value\r\n
41 s> Server: testing stub value\r\n
42 s> Date: $HTTP_DATE$\r\n
42 s> Date: $HTTP_DATE$\r\n
43 s> Content-Type: application/mercurial-exp-framing-0004\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
44 s> Transfer-Encoding: chunked\r\n
44 s> Transfer-Encoding: chunked\r\n
45 s> \r\n
45 s> \r\n
46 s> 9\r\n
46 s> 14\r\n
47 s> \x01\x00\x00\x01\x00\x02\x012
47 s> \x0c\x00\x00\x01\x00\x02\x012
48 s> @
48 s> \xa1FstatusBok@
49 s> \r\n
49 s> \r\n
50 received frame(size=1; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
50 received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
51 s> 0\r\n
51 s> 0\r\n
52 s> \r\n
52 s> \r\n
53 response: []
53 response: []
54
54
55 Single known node works
55 Single known node works
56
56
57 $ sendhttpv2peer << EOF
57 $ sendhttpv2peer << EOF
58 > command known
58 > command known
59 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0']
59 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0']
60 > EOF
60 > EOF
61 creating http peer for wire protocol version 2
61 creating http peer for wire protocol version 2
62 sending known command
62 sending known command
63 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
63 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
64 s> Accept-Encoding: identity\r\n
64 s> Accept-Encoding: identity\r\n
65 s> accept: application/mercurial-exp-framing-0004\r\n
65 s> accept: application/mercurial-exp-framing-0005\r\n
66 s> content-type: application/mercurial-exp-framing-0004\r\n
66 s> content-type: application/mercurial-exp-framing-0005\r\n
67 s> content-length: 54\r\n
67 s> content-length: 54\r\n
68 s> host: $LOCALIP:$HGPORT\r\n (glob)
68 s> host: $LOCALIP:$HGPORT\r\n (glob)
69 s> user-agent: Mercurial debugwireproto\r\n
69 s> user-agent: Mercurial debugwireproto\r\n
70 s> \r\n
70 s> \r\n
71 s> .\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x81TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0DnameEknown
71 s> .\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x81TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0DnameEknown
72 s> makefile('rb', None)
72 s> makefile('rb', None)
73 s> HTTP/1.1 200 OK\r\n
73 s> HTTP/1.1 200 OK\r\n
74 s> Server: testing stub value\r\n
74 s> Server: testing stub value\r\n
75 s> Date: $HTTP_DATE$\r\n
75 s> Date: $HTTP_DATE$\r\n
76 s> Content-Type: application/mercurial-exp-framing-0004\r\n
76 s> Content-Type: application/mercurial-exp-framing-0005\r\n
77 s> Transfer-Encoding: chunked\r\n
77 s> Transfer-Encoding: chunked\r\n
78 s> \r\n
78 s> \r\n
79 s> a\r\n
79 s> 15\r\n
80 s> \x02\x00\x00\x01\x00\x02\x012
80 s> \r\x00\x00\x01\x00\x02\x012
81 s> A1
81 s> \xa1FstatusBokA1
82 s> \r\n
82 s> \r\n
83 received frame(size=2; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
83 received frame(size=13; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
84 s> 0\r\n
84 s> 0\r\n
85 s> \r\n
85 s> \r\n
86 response: [True]
86 response: [True]
87
87
88 Multiple nodes works
88 Multiple nodes works
89
89
90 $ sendhttpv2peer << EOF
90 $ sendhttpv2peer << EOF
91 > command known
91 > command known
92 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0', b'00000000000000000000', b'\x11\x24\x78\x96\x29\x61\x14\x71\x24\xed\xd4\x35\x49\xae\xdd\x1a\x33\x5e\x44\xbf']
92 > nodes eval:[b'\x42\x6b\xad\xa5\xc6\x75\x98\xca\x65\x03\x6d\x57\xd9\xe4\xb6\x4b\x0c\x1c\xe7\xa0', b'00000000000000000000', b'\x11\x24\x78\x96\x29\x61\x14\x71\x24\xed\xd4\x35\x49\xae\xdd\x1a\x33\x5e\x44\xbf']
93 > EOF
93 > EOF
94 creating http peer for wire protocol version 2
94 creating http peer for wire protocol version 2
95 sending known command
95 sending known command
96 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
96 s> POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
97 s> Accept-Encoding: identity\r\n
97 s> Accept-Encoding: identity\r\n
98 s> accept: application/mercurial-exp-framing-0004\r\n
98 s> accept: application/mercurial-exp-framing-0005\r\n
99 s> content-type: application/mercurial-exp-framing-0004\r\n
99 s> content-type: application/mercurial-exp-framing-0005\r\n
100 s> content-length: 96\r\n
100 s> content-length: 96\r\n
101 s> host: $LOCALIP:$HGPORT\r\n (glob)
101 s> host: $LOCALIP:$HGPORT\r\n (glob)
102 s> user-agent: Mercurial debugwireproto\r\n
102 s> user-agent: Mercurial debugwireproto\r\n
103 s> \r\n
103 s> \r\n
104 s> X\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x83TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0T00000000000000000000T\x11$x\x96)a\x14q$\xed\xd45I\xae\xdd\x1a3^D\xbfDnameEknown
104 s> X\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x83TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0T00000000000000000000T\x11$x\x96)a\x14q$\xed\xd45I\xae\xdd\x1a3^D\xbfDnameEknown
105 s> makefile('rb', None)
105 s> makefile('rb', None)
106 s> HTTP/1.1 200 OK\r\n
106 s> HTTP/1.1 200 OK\r\n
107 s> Server: testing stub value\r\n
107 s> Server: testing stub value\r\n
108 s> Date: $HTTP_DATE$\r\n
108 s> Date: $HTTP_DATE$\r\n
109 s> Content-Type: application/mercurial-exp-framing-0004\r\n
109 s> Content-Type: application/mercurial-exp-framing-0005\r\n
110 s> Transfer-Encoding: chunked\r\n
110 s> Transfer-Encoding: chunked\r\n
111 s> \r\n
111 s> \r\n
112 s> c\r\n
112 s> 17\r\n
113 s> \x04\x00\x00\x01\x00\x02\x012
113 s> \x0f\x00\x00\x01\x00\x02\x012
114 s> C101
114 s> \xa1FstatusBokC101
115 s> \r\n
115 s> \r\n
116 received frame(size=4; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
116 received frame(size=15; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
117 s> 0\r\n
117 s> 0\r\n
118 s> \r\n
118 s> \r\n
119 response: [True, False, True]
119 response: [True, False, True]
120
120
121 $ cat error.log
121 $ cat error.log
@@ -1,125 +1,125 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ hg debugdrawdag << EOF
6 $ hg debugdrawdag << EOF
7 > C D
7 > C D
8 > |/
8 > |/
9 > B
9 > B
10 > |
10 > |
11 > A
11 > A
12 > EOF
12 > EOF
13
13
14 $ hg phase --public -r C
14 $ hg phase --public -r C
15 $ hg book -r C @
15 $ hg book -r C @
16
16
17 $ hg log -T '{rev}:{node} {desc}\n'
17 $ hg log -T '{rev}:{node} {desc}\n'
18 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
18 3:be0ef73c17ade3fc89dc41701eb9fc3a91b58282 D
19 2:26805aba1e600a82e93661149f2313866a221a7b C
19 2:26805aba1e600a82e93661149f2313866a221a7b C
20 1:112478962961147124edd43549aedd1a335e44bf B
20 1:112478962961147124edd43549aedd1a335e44bf B
21 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
21 0:426bada5c67598ca65036d57d9e4b64b0c1ce7a0 A
22
22
23 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
23 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
24 $ cat hg.pid > $DAEMON_PIDS
24 $ cat hg.pid > $DAEMON_PIDS
25
25
26 Request for namespaces works
26 Request for namespaces works
27
27
28 $ sendhttpv2peer << EOF
28 $ sendhttpv2peer << EOF
29 > command listkeys
29 > command listkeys
30 > namespace namespaces
30 > namespace namespaces
31 > EOF
31 > EOF
32 creating http peer for wire protocol version 2
32 creating http peer for wire protocol version 2
33 sending listkeys command
33 sending listkeys command
34 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
34 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
35 s> Accept-Encoding: identity\r\n
35 s> Accept-Encoding: identity\r\n
36 s> accept: application/mercurial-exp-framing-0004\r\n
36 s> accept: application/mercurial-exp-framing-0005\r\n
37 s> content-type: application/mercurial-exp-framing-0004\r\n
37 s> content-type: application/mercurial-exp-framing-0005\r\n
38 s> content-length: 50\r\n
38 s> content-length: 50\r\n
39 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> host: $LOCALIP:$HGPORT\r\n (glob)
40 s> user-agent: Mercurial debugwireproto\r\n
40 s> user-agent: Mercurial debugwireproto\r\n
41 s> \r\n
41 s> \r\n
42 s> *\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceJnamespacesDnameHlistkeys
42 s> *\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceJnamespacesDnameHlistkeys
43 s> makefile('rb', None)
43 s> makefile('rb', None)
44 s> HTTP/1.1 200 OK\r\n
44 s> HTTP/1.1 200 OK\r\n
45 s> Server: testing stub value\r\n
45 s> Server: testing stub value\r\n
46 s> Date: $HTTP_DATE$\r\n
46 s> Date: $HTTP_DATE$\r\n
47 s> Content-Type: application/mercurial-exp-framing-0004\r\n
47 s> Content-Type: application/mercurial-exp-framing-0005\r\n
48 s> Transfer-Encoding: chunked\r\n
48 s> Transfer-Encoding: chunked\r\n
49 s> \r\n
49 s> \r\n
50 s> 28\r\n
50 s> 33\r\n
51 s> \x00\x00\x01\x00\x02\x012
51 s> +\x00\x00\x01\x00\x02\x012
52 s> \xa3Fphases@Ibookmarks@Jnamespaces@
52 s> \xa1FstatusBok\xa3Fphases@Ibookmarks@Jnamespaces@
53 s> \r\n
53 s> \r\n
54 received frame(size=32; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
54 received frame(size=43; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
55 s> 0\r\n
55 s> 0\r\n
56 s> \r\n
56 s> \r\n
57 response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
57 response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
58
58
59 Request for phases works
59 Request for phases works
60
60
61 $ sendhttpv2peer << EOF
61 $ sendhttpv2peer << EOF
62 > command listkeys
62 > command listkeys
63 > namespace phases
63 > namespace phases
64 > EOF
64 > EOF
65 creating http peer for wire protocol version 2
65 creating http peer for wire protocol version 2
66 sending listkeys command
66 sending listkeys command
67 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
67 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
68 s> Accept-Encoding: identity\r\n
68 s> Accept-Encoding: identity\r\n
69 s> accept: application/mercurial-exp-framing-0004\r\n
69 s> accept: application/mercurial-exp-framing-0005\r\n
70 s> content-type: application/mercurial-exp-framing-0004\r\n
70 s> content-type: application/mercurial-exp-framing-0005\r\n
71 s> content-length: 46\r\n
71 s> content-length: 46\r\n
72 s> host: $LOCALIP:$HGPORT\r\n (glob)
72 s> host: $LOCALIP:$HGPORT\r\n (glob)
73 s> user-agent: Mercurial debugwireproto\r\n
73 s> user-agent: Mercurial debugwireproto\r\n
74 s> \r\n
74 s> \r\n
75 s> &\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceFphasesDnameHlistkeys
75 s> &\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceFphasesDnameHlistkeys
76 s> makefile('rb', None)
76 s> makefile('rb', None)
77 s> HTTP/1.1 200 OK\r\n
77 s> HTTP/1.1 200 OK\r\n
78 s> Server: testing stub value\r\n
78 s> Server: testing stub value\r\n
79 s> Date: $HTTP_DATE$\r\n
79 s> Date: $HTTP_DATE$\r\n
80 s> Content-Type: application/mercurial-exp-framing-0004\r\n
80 s> Content-Type: application/mercurial-exp-framing-0005\r\n
81 s> Transfer-Encoding: chunked\r\n
81 s> Transfer-Encoding: chunked\r\n
82 s> \r\n
82 s> \r\n
83 s> 45\r\n
83 s> 50\r\n
84 s> =\x00\x00\x01\x00\x02\x012
84 s> H\x00\x00\x01\x00\x02\x012
85 s> \xa2JpublishingDTrueX(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1
85 s> \xa1FstatusBok\xa2JpublishingDTrueX(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1
86 s> \r\n
86 s> \r\n
87 received frame(size=61; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
87 received frame(size=72; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
88 s> 0\r\n
88 s> 0\r\n
89 s> \r\n
89 s> \r\n
90 response: {b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1', b'publishing': b'True'}
90 response: {b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1', b'publishing': b'True'}
91
91
92 Request for bookmarks works
92 Request for bookmarks works
93
93
94 $ sendhttpv2peer << EOF
94 $ sendhttpv2peer << EOF
95 > command listkeys
95 > command listkeys
96 > namespace bookmarks
96 > namespace bookmarks
97 > EOF
97 > EOF
98 creating http peer for wire protocol version 2
98 creating http peer for wire protocol version 2
99 sending listkeys command
99 sending listkeys command
100 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
100 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
101 s> Accept-Encoding: identity\r\n
101 s> Accept-Encoding: identity\r\n
102 s> accept: application/mercurial-exp-framing-0004\r\n
102 s> accept: application/mercurial-exp-framing-0005\r\n
103 s> content-type: application/mercurial-exp-framing-0004\r\n
103 s> content-type: application/mercurial-exp-framing-0005\r\n
104 s> content-length: 49\r\n
104 s> content-length: 49\r\n
105 s> host: $LOCALIP:$HGPORT\r\n (glob)
105 s> host: $LOCALIP:$HGPORT\r\n (glob)
106 s> user-agent: Mercurial debugwireproto\r\n
106 s> user-agent: Mercurial debugwireproto\r\n
107 s> \r\n
107 s> \r\n
108 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
108 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
109 s> makefile('rb', None)
109 s> makefile('rb', None)
110 s> HTTP/1.1 200 OK\r\n
110 s> HTTP/1.1 200 OK\r\n
111 s> Server: testing stub value\r\n
111 s> Server: testing stub value\r\n
112 s> Date: $HTTP_DATE$\r\n
112 s> Date: $HTTP_DATE$\r\n
113 s> Content-Type: application/mercurial-exp-framing-0004\r\n
113 s> Content-Type: application/mercurial-exp-framing-0005\r\n
114 s> Transfer-Encoding: chunked\r\n
114 s> Transfer-Encoding: chunked\r\n
115 s> \r\n
115 s> \r\n
116 s> 35\r\n
116 s> 40\r\n
117 s> -\x00\x00\x01\x00\x02\x012
117 s> 8\x00\x00\x01\x00\x02\x012
118 s> \xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
118 s> \xa1FstatusBok\xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
119 s> \r\n
119 s> \r\n
120 received frame(size=45; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
120 received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
121 s> 0\r\n
121 s> 0\r\n
122 s> \r\n
122 s> \r\n
123 response: {b'@': b'26805aba1e600a82e93661149f2313866a221a7b'}
123 response: {b'@': b'26805aba1e600a82e93661149f2313866a221a7b'}
124
124
125 $ cat error.log
125 $ cat error.log
@@ -1,55 +1,55 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ cat >> .hg/hgrc << EOF
6 $ cat >> .hg/hgrc << EOF
7 > [web]
7 > [web]
8 > push_ssl = false
8 > push_ssl = false
9 > allow-push = *
9 > allow-push = *
10 > EOF
10 > EOF
11 $ hg debugdrawdag << EOF
11 $ hg debugdrawdag << EOF
12 > C D
12 > C D
13 > |/
13 > |/
14 > B
14 > B
15 > |
15 > |
16 > A
16 > A
17 > EOF
17 > EOF
18
18
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ cat hg.pid > $DAEMON_PIDS
20 $ cat hg.pid > $DAEMON_PIDS
21
21
22 lookup for known node works
22 lookup for known node works
23
23
24 $ sendhttpv2peer << EOF
24 $ sendhttpv2peer << EOF
25 > command lookup
25 > command lookup
26 > key 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
26 > key 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
27 > EOF
27 > EOF
28 creating http peer for wire protocol version 2
28 creating http peer for wire protocol version 2
29 sending lookup command
29 sending lookup command
30 s> *\r\n (glob)
30 s> *\r\n (glob)
31 s> Accept-Encoding: identity\r\n
31 s> Accept-Encoding: identity\r\n
32 s> accept: application/mercurial-exp-framing-0004\r\n
32 s> accept: application/mercurial-exp-framing-0005\r\n
33 s> content-type: application/mercurial-exp-framing-0004\r\n
33 s> content-type: application/mercurial-exp-framing-0005\r\n
34 s> content-length: 73\r\n
34 s> content-length: 73\r\n
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
35 s> host: $LOCALIP:$HGPORT\r\n (glob)
36 s> user-agent: Mercurial debugwireproto\r\n
36 s> user-agent: Mercurial debugwireproto\r\n
37 s> \r\n
37 s> \r\n
38 s> A\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1CkeyX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0DnameFlookup
38 s> A\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1CkeyX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0DnameFlookup
39 s> makefile('rb', None)
39 s> makefile('rb', None)
40 s> HTTP/1.1 200 OK\r\n
40 s> HTTP/1.1 200 OK\r\n
41 s> Server: testing stub value\r\n
41 s> Server: testing stub value\r\n
42 s> Date: $HTTP_DATE$\r\n
42 s> Date: $HTTP_DATE$\r\n
43 s> Content-Type: application/mercurial-exp-framing-0004\r\n
43 s> Content-Type: application/mercurial-exp-framing-0005\r\n
44 s> Transfer-Encoding: chunked\r\n
44 s> Transfer-Encoding: chunked\r\n
45 s> \r\n
45 s> \r\n
46 s> 1d\r\n
46 s> 28\r\n
47 s> \x15\x00\x00\x01\x00\x02\x012
47 s> \x00\x00\x01\x00\x02\x012
48 s> TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
48 s> \xa1FstatusBokTBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
49 s> \r\n
49 s> \r\n
50 received frame(size=21; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
50 received frame(size=32; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
51 s> 0\r\n
51 s> 0\r\n
52 s> \r\n
52 s> \r\n
53 response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0'
53 response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0'
54
54
55 $ cat error.log
55 $ cat error.log
@@ -1,89 +1,89 b''
1 $ . $TESTDIR/wireprotohelpers.sh
1 $ . $TESTDIR/wireprotohelpers.sh
2
2
3 $ hg init server
3 $ hg init server
4 $ enablehttpv2 server
4 $ enablehttpv2 server
5 $ cd server
5 $ cd server
6 $ cat >> .hg/hgrc << EOF
6 $ cat >> .hg/hgrc << EOF
7 > [web]
7 > [web]
8 > push_ssl = false
8 > push_ssl = false
9 > allow-push = *
9 > allow-push = *
10 > EOF
10 > EOF
11 $ hg debugdrawdag << EOF
11 $ hg debugdrawdag << EOF
12 > C D
12 > C D
13 > |/
13 > |/
14 > B
14 > B
15 > |
15 > |
16 > A
16 > A
17 > EOF
17 > EOF
18
18
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
19 $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
20 $ cat hg.pid > $DAEMON_PIDS
20 $ cat hg.pid > $DAEMON_PIDS
21
21
22 pushkey for a bookmark works
22 pushkey for a bookmark works
23
23
24 $ sendhttpv2peer << EOF
24 $ sendhttpv2peer << EOF
25 > command pushkey
25 > command pushkey
26 > namespace bookmarks
26 > namespace bookmarks
27 > key @
27 > key @
28 > old
28 > old
29 > new 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
29 > new 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
30 > EOF
30 > EOF
31 creating http peer for wire protocol version 2
31 creating http peer for wire protocol version 2
32 sending pushkey command
32 sending pushkey command
33 s> *\r\n (glob)
33 s> *\r\n (glob)
34 s> Accept-Encoding: identity\r\n
34 s> Accept-Encoding: identity\r\n
35 s> accept: application/mercurial-exp-framing-0004\r\n
35 s> accept: application/mercurial-exp-framing-0005\r\n
36 s> content-type: application/mercurial-exp-framing-0004\r\n
36 s> content-type: application/mercurial-exp-framing-0005\r\n
37 s> content-length: 105\r\n
37 s> content-length: 105\r\n
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> user-agent: Mercurial debugwireproto\r\n
39 s> user-agent: Mercurial debugwireproto\r\n
40 s> \r\n
40 s> \r\n
41 s> a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@CnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@InamespaceIbookmarksDnameGpushkey
41 s> a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@CnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@InamespaceIbookmarksDnameGpushkey
42 s> makefile('rb', None)
42 s> makefile('rb', None)
43 s> HTTP/1.1 200 OK\r\n
43 s> HTTP/1.1 200 OK\r\n
44 s> Server: testing stub value\r\n
44 s> Server: testing stub value\r\n
45 s> Date: $HTTP_DATE$\r\n
45 s> Date: $HTTP_DATE$\r\n
46 s> Content-Type: application/mercurial-exp-framing-0004\r\n
46 s> Content-Type: application/mercurial-exp-framing-0005\r\n
47 s> Transfer-Encoding: chunked\r\n
47 s> Transfer-Encoding: chunked\r\n
48 s> \r\n
48 s> \r\n
49 s> 9\r\n
49 s> 14\r\n
50 s> \x01\x00\x00\x01\x00\x02\x012
50 s> \x0c\x00\x00\x01\x00\x02\x012
51 s> \xf5
51 s> \xa1FstatusBok\xf5
52 s> \r\n
52 s> \r\n
53 received frame(size=1; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
53 received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
54 s> 0\r\n
54 s> 0\r\n
55 s> \r\n
55 s> \r\n
56 response: True
56 response: True
57
57
58 $ sendhttpv2peer << EOF
58 $ sendhttpv2peer << EOF
59 > command listkeys
59 > command listkeys
60 > namespace bookmarks
60 > namespace bookmarks
61 > EOF
61 > EOF
62 creating http peer for wire protocol version 2
62 creating http peer for wire protocol version 2
63 sending listkeys command
63 sending listkeys command
64 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
64 s> POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
65 s> Accept-Encoding: identity\r\n
65 s> Accept-Encoding: identity\r\n
66 s> accept: application/mercurial-exp-framing-0004\r\n
66 s> accept: application/mercurial-exp-framing-0005\r\n
67 s> content-type: application/mercurial-exp-framing-0004\r\n
67 s> content-type: application/mercurial-exp-framing-0005\r\n
68 s> content-length: 49\r\n
68 s> content-length: 49\r\n
69 s> host: $LOCALIP:$HGPORT\r\n (glob)
69 s> host: $LOCALIP:$HGPORT\r\n (glob)
70 s> user-agent: Mercurial debugwireproto\r\n
70 s> user-agent: Mercurial debugwireproto\r\n
71 s> \r\n
71 s> \r\n
72 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
72 s> )\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1InamespaceIbookmarksDnameHlistkeys
73 s> makefile('rb', None)
73 s> makefile('rb', None)
74 s> HTTP/1.1 200 OK\r\n
74 s> HTTP/1.1 200 OK\r\n
75 s> Server: testing stub value\r\n
75 s> Server: testing stub value\r\n
76 s> Date: $HTTP_DATE$\r\n
76 s> Date: $HTTP_DATE$\r\n
77 s> Content-Type: application/mercurial-exp-framing-0004\r\n
77 s> Content-Type: application/mercurial-exp-framing-0005\r\n
78 s> Transfer-Encoding: chunked\r\n
78 s> Transfer-Encoding: chunked\r\n
79 s> \r\n
79 s> \r\n
80 s> 35\r\n
80 s> 40\r\n
81 s> -\x00\x00\x01\x00\x02\x012
81 s> 8\x00\x00\x01\x00\x02\x012
82 s> \xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
82 s> \xa1FstatusBok\xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
83 s> \r\n
83 s> \r\n
84 received frame(size=45; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
84 received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
85 s> 0\r\n
85 s> 0\r\n
86 s> \r\n
86 s> \r\n
87 response: {b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'}
87 response: {b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'}
88
88
89 $ cat error.log
89 $ cat error.log
@@ -1,485 +1,488 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2
2
3 import unittest
3 import unittest
4
4
5 from mercurial.thirdparty import (
5 from mercurial.thirdparty import (
6 cbor,
6 cbor,
7 )
7 )
8 from mercurial import (
8 from mercurial import (
9 util,
9 util,
10 wireprotoframing as framing,
10 wireprotoframing as framing,
11 )
11 )
12
12
13 ffs = framing.makeframefromhumanstring
13 ffs = framing.makeframefromhumanstring
14
14
15 OK = cbor.dumps({b'status': b'ok'})
16
15 def makereactor(deferoutput=False):
17 def makereactor(deferoutput=False):
16 return framing.serverreactor(deferoutput=deferoutput)
18 return framing.serverreactor(deferoutput=deferoutput)
17
19
18 def sendframes(reactor, gen):
20 def sendframes(reactor, gen):
19 """Send a generator of frame bytearray to a reactor.
21 """Send a generator of frame bytearray to a reactor.
20
22
21 Emits a generator of results from ``onframerecv()`` calls.
23 Emits a generator of results from ``onframerecv()`` calls.
22 """
24 """
23 for frame in gen:
25 for frame in gen:
24 header = framing.parseheader(frame)
26 header = framing.parseheader(frame)
25 payload = frame[framing.FRAME_HEADER_SIZE:]
27 payload = frame[framing.FRAME_HEADER_SIZE:]
26 assert len(payload) == header.length
28 assert len(payload) == header.length
27
29
28 yield reactor.onframerecv(framing.frame(header.requestid,
30 yield reactor.onframerecv(framing.frame(header.requestid,
29 header.streamid,
31 header.streamid,
30 header.streamflags,
32 header.streamflags,
31 header.typeid,
33 header.typeid,
32 header.flags,
34 header.flags,
33 payload))
35 payload))
34
36
35 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
37 def sendcommandframes(reactor, stream, rid, cmd, args, datafh=None):
36 """Generate frames to run a command and send them to a reactor."""
38 """Generate frames to run a command and send them to a reactor."""
37 return sendframes(reactor,
39 return sendframes(reactor,
38 framing.createcommandframes(stream, rid, cmd, args,
40 framing.createcommandframes(stream, rid, cmd, args,
39 datafh))
41 datafh))
40
42
41
43
42 class ServerReactorTests(unittest.TestCase):
44 class ServerReactorTests(unittest.TestCase):
43 def _sendsingleframe(self, reactor, f):
45 def _sendsingleframe(self, reactor, f):
44 results = list(sendframes(reactor, [f]))
46 results = list(sendframes(reactor, [f]))
45 self.assertEqual(len(results), 1)
47 self.assertEqual(len(results), 1)
46
48
47 return results[0]
49 return results[0]
48
50
49 def assertaction(self, res, expected):
51 def assertaction(self, res, expected):
50 self.assertIsInstance(res, tuple)
52 self.assertIsInstance(res, tuple)
51 self.assertEqual(len(res), 2)
53 self.assertEqual(len(res), 2)
52 self.assertIsInstance(res[1], dict)
54 self.assertIsInstance(res[1], dict)
53 self.assertEqual(res[0], expected)
55 self.assertEqual(res[0], expected)
54
56
55 def assertframesequal(self, frames, framestrings):
57 def assertframesequal(self, frames, framestrings):
56 expected = [ffs(s) for s in framestrings]
58 expected = [ffs(s) for s in framestrings]
57 self.assertEqual(list(frames), expected)
59 self.assertEqual(list(frames), expected)
58
60
59 def test1framecommand(self):
61 def test1framecommand(self):
60 """Receiving a command in a single frame yields request to run it."""
62 """Receiving a command in a single frame yields request to run it."""
61 reactor = makereactor()
63 reactor = makereactor()
62 stream = framing.stream(1)
64 stream = framing.stream(1)
63 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
65 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}))
64 self.assertEqual(len(results), 1)
66 self.assertEqual(len(results), 1)
65 self.assertaction(results[0], b'runcommand')
67 self.assertaction(results[0], b'runcommand')
66 self.assertEqual(results[0][1], {
68 self.assertEqual(results[0][1], {
67 b'requestid': 1,
69 b'requestid': 1,
68 b'command': b'mycommand',
70 b'command': b'mycommand',
69 b'args': {},
71 b'args': {},
70 b'data': None,
72 b'data': None,
71 })
73 })
72
74
73 result = reactor.oninputeof()
75 result = reactor.oninputeof()
74 self.assertaction(result, b'noop')
76 self.assertaction(result, b'noop')
75
77
76 def test1argument(self):
78 def test1argument(self):
77 reactor = makereactor()
79 reactor = makereactor()
78 stream = framing.stream(1)
80 stream = framing.stream(1)
79 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
81 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
80 {b'foo': b'bar'}))
82 {b'foo': b'bar'}))
81 self.assertEqual(len(results), 1)
83 self.assertEqual(len(results), 1)
82 self.assertaction(results[0], b'runcommand')
84 self.assertaction(results[0], b'runcommand')
83 self.assertEqual(results[0][1], {
85 self.assertEqual(results[0][1], {
84 b'requestid': 41,
86 b'requestid': 41,
85 b'command': b'mycommand',
87 b'command': b'mycommand',
86 b'args': {b'foo': b'bar'},
88 b'args': {b'foo': b'bar'},
87 b'data': None,
89 b'data': None,
88 })
90 })
89
91
90 def testmultiarguments(self):
92 def testmultiarguments(self):
91 reactor = makereactor()
93 reactor = makereactor()
92 stream = framing.stream(1)
94 stream = framing.stream(1)
93 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
95 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
94 {b'foo': b'bar', b'biz': b'baz'}))
96 {b'foo': b'bar', b'biz': b'baz'}))
95 self.assertEqual(len(results), 1)
97 self.assertEqual(len(results), 1)
96 self.assertaction(results[0], b'runcommand')
98 self.assertaction(results[0], b'runcommand')
97 self.assertEqual(results[0][1], {
99 self.assertEqual(results[0][1], {
98 b'requestid': 1,
100 b'requestid': 1,
99 b'command': b'mycommand',
101 b'command': b'mycommand',
100 b'args': {b'foo': b'bar', b'biz': b'baz'},
102 b'args': {b'foo': b'bar', b'biz': b'baz'},
101 b'data': None,
103 b'data': None,
102 })
104 })
103
105
104 def testsimplecommanddata(self):
106 def testsimplecommanddata(self):
105 reactor = makereactor()
107 reactor = makereactor()
106 stream = framing.stream(1)
108 stream = framing.stream(1)
107 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
109 results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {},
108 util.bytesio(b'data!')))
110 util.bytesio(b'data!')))
109 self.assertEqual(len(results), 2)
111 self.assertEqual(len(results), 2)
110 self.assertaction(results[0], b'wantframe')
112 self.assertaction(results[0], b'wantframe')
111 self.assertaction(results[1], b'runcommand')
113 self.assertaction(results[1], b'runcommand')
112 self.assertEqual(results[1][1], {
114 self.assertEqual(results[1][1], {
113 b'requestid': 1,
115 b'requestid': 1,
114 b'command': b'mycommand',
116 b'command': b'mycommand',
115 b'args': {},
117 b'args': {},
116 b'data': b'data!',
118 b'data': b'data!',
117 })
119 })
118
120
119 def testmultipledataframes(self):
121 def testmultipledataframes(self):
120 frames = [
122 frames = [
121 ffs(b'1 1 stream-begin command-request new|have-data '
123 ffs(b'1 1 stream-begin command-request new|have-data '
122 b"cbor:{b'name': b'mycommand'}"),
124 b"cbor:{b'name': b'mycommand'}"),
123 ffs(b'1 1 0 command-data continuation data1'),
125 ffs(b'1 1 0 command-data continuation data1'),
124 ffs(b'1 1 0 command-data continuation data2'),
126 ffs(b'1 1 0 command-data continuation data2'),
125 ffs(b'1 1 0 command-data eos data3'),
127 ffs(b'1 1 0 command-data eos data3'),
126 ]
128 ]
127
129
128 reactor = makereactor()
130 reactor = makereactor()
129 results = list(sendframes(reactor, frames))
131 results = list(sendframes(reactor, frames))
130 self.assertEqual(len(results), 4)
132 self.assertEqual(len(results), 4)
131 for i in range(3):
133 for i in range(3):
132 self.assertaction(results[i], b'wantframe')
134 self.assertaction(results[i], b'wantframe')
133 self.assertaction(results[3], b'runcommand')
135 self.assertaction(results[3], b'runcommand')
134 self.assertEqual(results[3][1], {
136 self.assertEqual(results[3][1], {
135 b'requestid': 1,
137 b'requestid': 1,
136 b'command': b'mycommand',
138 b'command': b'mycommand',
137 b'args': {},
139 b'args': {},
138 b'data': b'data1data2data3',
140 b'data': b'data1data2data3',
139 })
141 })
140
142
141 def testargumentanddata(self):
143 def testargumentanddata(self):
142 frames = [
144 frames = [
143 ffs(b'1 1 stream-begin command-request new|have-data '
145 ffs(b'1 1 stream-begin command-request new|have-data '
144 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
146 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
145 b"b'foo': b'bar'}}"),
147 b"b'foo': b'bar'}}"),
146 ffs(b'1 1 0 command-data continuation value1'),
148 ffs(b'1 1 0 command-data continuation value1'),
147 ffs(b'1 1 0 command-data eos value2'),
149 ffs(b'1 1 0 command-data eos value2'),
148 ]
150 ]
149
151
150 reactor = makereactor()
152 reactor = makereactor()
151 results = list(sendframes(reactor, frames))
153 results = list(sendframes(reactor, frames))
152
154
153 self.assertaction(results[-1], b'runcommand')
155 self.assertaction(results[-1], b'runcommand')
154 self.assertEqual(results[-1][1], {
156 self.assertEqual(results[-1][1], {
155 b'requestid': 1,
157 b'requestid': 1,
156 b'command': b'command',
158 b'command': b'command',
157 b'args': {
159 b'args': {
158 b'key': b'val',
160 b'key': b'val',
159 b'foo': b'bar',
161 b'foo': b'bar',
160 },
162 },
161 b'data': b'value1value2',
163 b'data': b'value1value2',
162 })
164 })
163
165
164 def testnewandcontinuation(self):
166 def testnewandcontinuation(self):
165 result = self._sendsingleframe(makereactor(),
167 result = self._sendsingleframe(makereactor(),
166 ffs(b'1 1 stream-begin command-request new|continuation '))
168 ffs(b'1 1 stream-begin command-request new|continuation '))
167 self.assertaction(result, b'error')
169 self.assertaction(result, b'error')
168 self.assertEqual(result[1], {
170 self.assertEqual(result[1], {
169 b'message': b'received command request frame with both new and '
171 b'message': b'received command request frame with both new and '
170 b'continuation flags set',
172 b'continuation flags set',
171 })
173 })
172
174
173 def testneithernewnorcontinuation(self):
175 def testneithernewnorcontinuation(self):
174 result = self._sendsingleframe(makereactor(),
176 result = self._sendsingleframe(makereactor(),
175 ffs(b'1 1 stream-begin command-request 0 '))
177 ffs(b'1 1 stream-begin command-request 0 '))
176 self.assertaction(result, b'error')
178 self.assertaction(result, b'error')
177 self.assertEqual(result[1], {
179 self.assertEqual(result[1], {
178 b'message': b'received command request frame with neither new nor '
180 b'message': b'received command request frame with neither new nor '
179 b'continuation flags set',
181 b'continuation flags set',
180 })
182 })
181
183
182 def testunexpectedcommanddata(self):
184 def testunexpectedcommanddata(self):
183 """Command data frame when not running a command is an error."""
185 """Command data frame when not running a command is an error."""
184 result = self._sendsingleframe(makereactor(),
186 result = self._sendsingleframe(makereactor(),
185 ffs(b'1 1 stream-begin command-data 0 ignored'))
187 ffs(b'1 1 stream-begin command-data 0 ignored'))
186 self.assertaction(result, b'error')
188 self.assertaction(result, b'error')
187 self.assertEqual(result[1], {
189 self.assertEqual(result[1], {
188 b'message': b'expected command request frame; got 2',
190 b'message': b'expected command request frame; got 2',
189 })
191 })
190
192
191 def testunexpectedcommanddatareceiving(self):
193 def testunexpectedcommanddatareceiving(self):
192 """Same as above except the command is receiving."""
194 """Same as above except the command is receiving."""
193 results = list(sendframes(makereactor(), [
195 results = list(sendframes(makereactor(), [
194 ffs(b'1 1 stream-begin command-request new|more '
196 ffs(b'1 1 stream-begin command-request new|more '
195 b"cbor:{b'name': b'ignored'}"),
197 b"cbor:{b'name': b'ignored'}"),
196 ffs(b'1 1 0 command-data eos ignored'),
198 ffs(b'1 1 0 command-data eos ignored'),
197 ]))
199 ]))
198
200
199 self.assertaction(results[0], b'wantframe')
201 self.assertaction(results[0], b'wantframe')
200 self.assertaction(results[1], b'error')
202 self.assertaction(results[1], b'error')
201 self.assertEqual(results[1][1], {
203 self.assertEqual(results[1][1], {
202 b'message': b'received command data frame for request that is not '
204 b'message': b'received command data frame for request that is not '
203 b'expecting data: 1',
205 b'expecting data: 1',
204 })
206 })
205
207
206 def testconflictingrequestidallowed(self):
208 def testconflictingrequestidallowed(self):
207 """Multiple fully serviced commands with same request ID is allowed."""
209 """Multiple fully serviced commands with same request ID is allowed."""
208 reactor = makereactor()
210 reactor = makereactor()
209 results = []
211 results = []
210 outstream = reactor.makeoutputstream()
212 outstream = reactor.makeoutputstream()
211 results.append(self._sendsingleframe(
213 results.append(self._sendsingleframe(
212 reactor, ffs(b'1 1 stream-begin command-request new '
214 reactor, ffs(b'1 1 stream-begin command-request new '
213 b"cbor:{b'name': b'command'}")))
215 b"cbor:{b'name': b'command'}")))
214 result = reactor.oncommandresponseready(outstream, 1, b'response1')
216 result = reactor.oncommandresponseready(outstream, 1, b'response1')
215 self.assertaction(result, b'sendframes')
217 self.assertaction(result, b'sendframes')
216 list(result[1][b'framegen'])
218 list(result[1][b'framegen'])
217 results.append(self._sendsingleframe(
219 results.append(self._sendsingleframe(
218 reactor, ffs(b'1 1 stream-begin command-request new '
220 reactor, ffs(b'1 1 stream-begin command-request new '
219 b"cbor:{b'name': b'command'}")))
221 b"cbor:{b'name': b'command'}")))
220 result = reactor.oncommandresponseready(outstream, 1, b'response2')
222 result = reactor.oncommandresponseready(outstream, 1, b'response2')
221 self.assertaction(result, b'sendframes')
223 self.assertaction(result, b'sendframes')
222 list(result[1][b'framegen'])
224 list(result[1][b'framegen'])
223 results.append(self._sendsingleframe(
225 results.append(self._sendsingleframe(
224 reactor, ffs(b'1 1 stream-begin command-request new '
226 reactor, ffs(b'1 1 stream-begin command-request new '
225 b"cbor:{b'name': b'command'}")))
227 b"cbor:{b'name': b'command'}")))
226 result = reactor.oncommandresponseready(outstream, 1, b'response3')
228 result = reactor.oncommandresponseready(outstream, 1, b'response3')
227 self.assertaction(result, b'sendframes')
229 self.assertaction(result, b'sendframes')
228 list(result[1][b'framegen'])
230 list(result[1][b'framegen'])
229
231
230 for i in range(3):
232 for i in range(3):
231 self.assertaction(results[i], b'runcommand')
233 self.assertaction(results[i], b'runcommand')
232 self.assertEqual(results[i][1], {
234 self.assertEqual(results[i][1], {
233 b'requestid': 1,
235 b'requestid': 1,
234 b'command': b'command',
236 b'command': b'command',
235 b'args': {},
237 b'args': {},
236 b'data': None,
238 b'data': None,
237 })
239 })
238
240
239 def testconflictingrequestid(self):
241 def testconflictingrequestid(self):
240 """Request ID for new command matching in-flight command is illegal."""
242 """Request ID for new command matching in-flight command is illegal."""
241 results = list(sendframes(makereactor(), [
243 results = list(sendframes(makereactor(), [
242 ffs(b'1 1 stream-begin command-request new|more '
244 ffs(b'1 1 stream-begin command-request new|more '
243 b"cbor:{b'name': b'command'}"),
245 b"cbor:{b'name': b'command'}"),
244 ffs(b'1 1 0 command-request new '
246 ffs(b'1 1 0 command-request new '
245 b"cbor:{b'name': b'command1'}"),
247 b"cbor:{b'name': b'command1'}"),
246 ]))
248 ]))
247
249
248 self.assertaction(results[0], b'wantframe')
250 self.assertaction(results[0], b'wantframe')
249 self.assertaction(results[1], b'error')
251 self.assertaction(results[1], b'error')
250 self.assertEqual(results[1][1], {
252 self.assertEqual(results[1][1], {
251 b'message': b'request with ID 1 already received',
253 b'message': b'request with ID 1 already received',
252 })
254 })
253
255
254 def testinterleavedcommands(self):
256 def testinterleavedcommands(self):
255 cbor1 = cbor.dumps({
257 cbor1 = cbor.dumps({
256 b'name': b'command1',
258 b'name': b'command1',
257 b'args': {
259 b'args': {
258 b'foo': b'bar',
260 b'foo': b'bar',
259 b'key1': b'val',
261 b'key1': b'val',
260 }
262 }
261 }, canonical=True)
263 }, canonical=True)
262 cbor3 = cbor.dumps({
264 cbor3 = cbor.dumps({
263 b'name': b'command3',
265 b'name': b'command3',
264 b'args': {
266 b'args': {
265 b'biz': b'baz',
267 b'biz': b'baz',
266 b'key': b'val',
268 b'key': b'val',
267 },
269 },
268 }, canonical=True)
270 }, canonical=True)
269
271
270 results = list(sendframes(makereactor(), [
272 results = list(sendframes(makereactor(), [
271 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
273 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
272 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
274 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
273 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
275 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
274 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
276 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
275 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
277 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
276 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
278 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
277 ]))
279 ]))
278
280
279 self.assertEqual([t[0] for t in results], [
281 self.assertEqual([t[0] for t in results], [
280 b'wantframe',
282 b'wantframe',
281 b'wantframe',
283 b'wantframe',
282 b'wantframe',
284 b'wantframe',
283 b'wantframe',
285 b'wantframe',
284 b'runcommand',
286 b'runcommand',
285 b'runcommand',
287 b'runcommand',
286 ])
288 ])
287
289
288 self.assertEqual(results[4][1], {
290 self.assertEqual(results[4][1], {
289 b'requestid': 3,
291 b'requestid': 3,
290 b'command': b'command3',
292 b'command': b'command3',
291 b'args': {b'biz': b'baz', b'key': b'val'},
293 b'args': {b'biz': b'baz', b'key': b'val'},
292 b'data': None,
294 b'data': None,
293 })
295 })
294 self.assertEqual(results[5][1], {
296 self.assertEqual(results[5][1], {
295 b'requestid': 1,
297 b'requestid': 1,
296 b'command': b'command1',
298 b'command': b'command1',
297 b'args': {b'foo': b'bar', b'key1': b'val'},
299 b'args': {b'foo': b'bar', b'key1': b'val'},
298 b'data': None,
300 b'data': None,
299 })
301 })
300
302
301 def testmissingcommanddataframe(self):
303 def testmissingcommanddataframe(self):
302 # The reactor doesn't currently handle partially received commands.
304 # The reactor doesn't currently handle partially received commands.
303 # So this test is failing to do anything with request 1.
305 # So this test is failing to do anything with request 1.
304 frames = [
306 frames = [
305 ffs(b'1 1 stream-begin command-request new|have-data '
307 ffs(b'1 1 stream-begin command-request new|have-data '
306 b"cbor:{b'name': b'command1'}"),
308 b"cbor:{b'name': b'command1'}"),
307 ffs(b'3 1 0 command-request new '
309 ffs(b'3 1 0 command-request new '
308 b"cbor:{b'name': b'command2'}"),
310 b"cbor:{b'name': b'command2'}"),
309 ]
311 ]
310 results = list(sendframes(makereactor(), frames))
312 results = list(sendframes(makereactor(), frames))
311 self.assertEqual(len(results), 2)
313 self.assertEqual(len(results), 2)
312 self.assertaction(results[0], b'wantframe')
314 self.assertaction(results[0], b'wantframe')
313 self.assertaction(results[1], b'runcommand')
315 self.assertaction(results[1], b'runcommand')
314
316
315 def testmissingcommanddataframeflags(self):
317 def testmissingcommanddataframeflags(self):
316 frames = [
318 frames = [
317 ffs(b'1 1 stream-begin command-request new|have-data '
319 ffs(b'1 1 stream-begin command-request new|have-data '
318 b"cbor:{b'name': b'command1'}"),
320 b"cbor:{b'name': b'command1'}"),
319 ffs(b'1 1 0 command-data 0 data'),
321 ffs(b'1 1 0 command-data 0 data'),
320 ]
322 ]
321 results = list(sendframes(makereactor(), frames))
323 results = list(sendframes(makereactor(), frames))
322 self.assertEqual(len(results), 2)
324 self.assertEqual(len(results), 2)
323 self.assertaction(results[0], b'wantframe')
325 self.assertaction(results[0], b'wantframe')
324 self.assertaction(results[1], b'error')
326 self.assertaction(results[1], b'error')
325 self.assertEqual(results[1][1], {
327 self.assertEqual(results[1][1], {
326 b'message': b'command data frame without flags',
328 b'message': b'command data frame without flags',
327 })
329 })
328
330
329 def testframefornonreceivingrequest(self):
331 def testframefornonreceivingrequest(self):
330 """Receiving a frame for a command that is not receiving is illegal."""
332 """Receiving a frame for a command that is not receiving is illegal."""
331 results = list(sendframes(makereactor(), [
333 results = list(sendframes(makereactor(), [
332 ffs(b'1 1 stream-begin command-request new '
334 ffs(b'1 1 stream-begin command-request new '
333 b"cbor:{b'name': b'command1'}"),
335 b"cbor:{b'name': b'command1'}"),
334 ffs(b'3 1 0 command-request new|have-data '
336 ffs(b'3 1 0 command-request new|have-data '
335 b"cbor:{b'name': b'command3'}"),
337 b"cbor:{b'name': b'command3'}"),
336 ffs(b'5 1 0 command-data eos ignored'),
338 ffs(b'5 1 0 command-data eos ignored'),
337 ]))
339 ]))
338 self.assertaction(results[2], b'error')
340 self.assertaction(results[2], b'error')
339 self.assertEqual(results[2][1], {
341 self.assertEqual(results[2][1], {
340 b'message': b'received frame for request that is not receiving: 5',
342 b'message': b'received frame for request that is not receiving: 5',
341 })
343 })
342
344
343 def testsimpleresponse(self):
345 def testsimpleresponse(self):
344 """Bytes response to command sends result frames."""
346 """Bytes response to command sends result frames."""
345 reactor = makereactor()
347 reactor = makereactor()
346 instream = framing.stream(1)
348 instream = framing.stream(1)
347 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
349 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
348
350
349 outstream = reactor.makeoutputstream()
351 outstream = reactor.makeoutputstream()
350 result = reactor.oncommandresponseready(outstream, 1, b'response')
352 result = reactor.oncommandresponseready(outstream, 1, b'response')
351 self.assertaction(result, b'sendframes')
353 self.assertaction(result, b'sendframes')
352 self.assertframesequal(result[1][b'framegen'], [
354 self.assertframesequal(result[1][b'framegen'], [
353 b'1 2 stream-begin command-response eos response',
355 b'1 2 stream-begin command-response eos %sresponse' % OK,
354 ])
356 ])
355
357
356 def testmultiframeresponse(self):
358 def testmultiframeresponse(self):
357 """Bytes response spanning multiple frames is handled."""
359 """Bytes response spanning multiple frames is handled."""
358 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
360 first = b'x' * framing.DEFAULT_MAX_FRAME_SIZE
359 second = b'y' * 100
361 second = b'y' * 100
360
362
361 reactor = makereactor()
363 reactor = makereactor()
362 instream = framing.stream(1)
364 instream = framing.stream(1)
363 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
365 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
364
366
365 outstream = reactor.makeoutputstream()
367 outstream = reactor.makeoutputstream()
366 result = reactor.oncommandresponseready(outstream, 1, first + second)
368 result = reactor.oncommandresponseready(outstream, 1, first + second)
367 self.assertaction(result, b'sendframes')
369 self.assertaction(result, b'sendframes')
368 self.assertframesequal(result[1][b'framegen'], [
370 self.assertframesequal(result[1][b'framegen'], [
369 b'1 2 stream-begin command-response continuation %s' % first,
371 b'1 2 stream-begin command-response continuation %s' % OK,
372 b'1 2 0 command-response continuation %s' % first,
370 b'1 2 0 command-response eos %s' % second,
373 b'1 2 0 command-response eos %s' % second,
371 ])
374 ])
372
375
373 def testapplicationerror(self):
376 def testapplicationerror(self):
374 reactor = makereactor()
377 reactor = makereactor()
375 instream = framing.stream(1)
378 instream = framing.stream(1)
376 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
379 list(sendcommandframes(reactor, instream, 1, b'mycommand', {}))
377
380
378 outstream = reactor.makeoutputstream()
381 outstream = reactor.makeoutputstream()
379 result = reactor.onapplicationerror(outstream, 1, b'some message')
382 result = reactor.onapplicationerror(outstream, 1, b'some message')
380 self.assertaction(result, b'sendframes')
383 self.assertaction(result, b'sendframes')
381 self.assertframesequal(result[1][b'framegen'], [
384 self.assertframesequal(result[1][b'framegen'], [
382 b'1 2 stream-begin error-response application some message',
385 b'1 2 stream-begin error-response application some message',
383 ])
386 ])
384
387
385 def test1commanddeferresponse(self):
388 def test1commanddeferresponse(self):
386 """Responses when in deferred output mode are delayed until EOF."""
389 """Responses when in deferred output mode are delayed until EOF."""
387 reactor = makereactor(deferoutput=True)
390 reactor = makereactor(deferoutput=True)
388 instream = framing.stream(1)
391 instream = framing.stream(1)
389 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
392 results = list(sendcommandframes(reactor, instream, 1, b'mycommand',
390 {}))
393 {}))
391 self.assertEqual(len(results), 1)
394 self.assertEqual(len(results), 1)
392 self.assertaction(results[0], b'runcommand')
395 self.assertaction(results[0], b'runcommand')
393
396
394 outstream = reactor.makeoutputstream()
397 outstream = reactor.makeoutputstream()
395 result = reactor.oncommandresponseready(outstream, 1, b'response')
398 result = reactor.oncommandresponseready(outstream, 1, b'response')
396 self.assertaction(result, b'noop')
399 self.assertaction(result, b'noop')
397 result = reactor.oninputeof()
400 result = reactor.oninputeof()
398 self.assertaction(result, b'sendframes')
401 self.assertaction(result, b'sendframes')
399 self.assertframesequal(result[1][b'framegen'], [
402 self.assertframesequal(result[1][b'framegen'], [
400 b'1 2 stream-begin command-response eos response',
403 b'1 2 stream-begin command-response eos %sresponse' % OK,
401 ])
404 ])
402
405
403 def testmultiplecommanddeferresponse(self):
406 def testmultiplecommanddeferresponse(self):
404 reactor = makereactor(deferoutput=True)
407 reactor = makereactor(deferoutput=True)
405 instream = framing.stream(1)
408 instream = framing.stream(1)
406 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
409 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
407 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
410 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
408
411
409 outstream = reactor.makeoutputstream()
412 outstream = reactor.makeoutputstream()
410 result = reactor.oncommandresponseready(outstream, 1, b'response1')
413 result = reactor.oncommandresponseready(outstream, 1, b'response1')
411 self.assertaction(result, b'noop')
414 self.assertaction(result, b'noop')
412 result = reactor.oncommandresponseready(outstream, 3, b'response2')
415 result = reactor.oncommandresponseready(outstream, 3, b'response2')
413 self.assertaction(result, b'noop')
416 self.assertaction(result, b'noop')
414 result = reactor.oninputeof()
417 result = reactor.oninputeof()
415 self.assertaction(result, b'sendframes')
418 self.assertaction(result, b'sendframes')
416 self.assertframesequal(result[1][b'framegen'], [
419 self.assertframesequal(result[1][b'framegen'], [
417 b'1 2 stream-begin command-response eos response1',
420 b'1 2 stream-begin command-response eos %sresponse1' % OK,
418 b'3 2 0 command-response eos response2'
421 b'3 2 0 command-response eos %sresponse2' % OK,
419 ])
422 ])
420
423
421 def testrequestidtracking(self):
424 def testrequestidtracking(self):
422 reactor = makereactor(deferoutput=True)
425 reactor = makereactor(deferoutput=True)
423 instream = framing.stream(1)
426 instream = framing.stream(1)
424 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
427 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
425 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
428 list(sendcommandframes(reactor, instream, 3, b'command2', {}))
426 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
429 list(sendcommandframes(reactor, instream, 5, b'command3', {}))
427
430
428 # Register results for commands out of order.
431 # Register results for commands out of order.
429 outstream = reactor.makeoutputstream()
432 outstream = reactor.makeoutputstream()
430 reactor.oncommandresponseready(outstream, 3, b'response3')
433 reactor.oncommandresponseready(outstream, 3, b'response3')
431 reactor.oncommandresponseready(outstream, 1, b'response1')
434 reactor.oncommandresponseready(outstream, 1, b'response1')
432 reactor.oncommandresponseready(outstream, 5, b'response5')
435 reactor.oncommandresponseready(outstream, 5, b'response5')
433
436
434 result = reactor.oninputeof()
437 result = reactor.oninputeof()
435 self.assertaction(result, b'sendframes')
438 self.assertaction(result, b'sendframes')
436 self.assertframesequal(result[1][b'framegen'], [
439 self.assertframesequal(result[1][b'framegen'], [
437 b'3 2 stream-begin command-response eos response3',
440 b'3 2 stream-begin command-response eos %sresponse3' % OK,
438 b'1 2 0 command-response eos response1',
441 b'1 2 0 command-response eos %sresponse1' % OK,
439 b'5 2 0 command-response eos response5',
442 b'5 2 0 command-response eos %sresponse5' % OK,
440 ])
443 ])
441
444
442 def testduplicaterequestonactivecommand(self):
445 def testduplicaterequestonactivecommand(self):
443 """Receiving a request ID that matches a request that isn't finished."""
446 """Receiving a request ID that matches a request that isn't finished."""
444 reactor = makereactor()
447 reactor = makereactor()
445 stream = framing.stream(1)
448 stream = framing.stream(1)
446 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
449 list(sendcommandframes(reactor, stream, 1, b'command1', {}))
447 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
450 results = list(sendcommandframes(reactor, stream, 1, b'command1', {}))
448
451
449 self.assertaction(results[0], b'error')
452 self.assertaction(results[0], b'error')
450 self.assertEqual(results[0][1], {
453 self.assertEqual(results[0][1], {
451 b'message': b'request with ID 1 is already active',
454 b'message': b'request with ID 1 is already active',
452 })
455 })
453
456
454 def testduplicaterequestonactivecommandnosend(self):
457 def testduplicaterequestonactivecommandnosend(self):
455 """Same as above but we've registered a response but haven't sent it."""
458 """Same as above but we've registered a response but haven't sent it."""
456 reactor = makereactor()
459 reactor = makereactor()
457 instream = framing.stream(1)
460 instream = framing.stream(1)
458 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
461 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
459 outstream = reactor.makeoutputstream()
462 outstream = reactor.makeoutputstream()
460 reactor.oncommandresponseready(outstream, 1, b'response')
463 reactor.oncommandresponseready(outstream, 1, b'response')
461
464
462 # We've registered the response but haven't sent it. From the
465 # We've registered the response but haven't sent it. From the
463 # perspective of the reactor, the command is still active.
466 # perspective of the reactor, the command is still active.
464
467
465 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
468 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
466 self.assertaction(results[0], b'error')
469 self.assertaction(results[0], b'error')
467 self.assertEqual(results[0][1], {
470 self.assertEqual(results[0][1], {
468 b'message': b'request with ID 1 is already active',
471 b'message': b'request with ID 1 is already active',
469 })
472 })
470
473
471 def testduplicaterequestaftersend(self):
474 def testduplicaterequestaftersend(self):
472 """We can use a duplicate request ID after we've sent the response."""
475 """We can use a duplicate request ID after we've sent the response."""
473 reactor = makereactor()
476 reactor = makereactor()
474 instream = framing.stream(1)
477 instream = framing.stream(1)
475 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
478 list(sendcommandframes(reactor, instream, 1, b'command1', {}))
476 outstream = reactor.makeoutputstream()
479 outstream = reactor.makeoutputstream()
477 res = reactor.oncommandresponseready(outstream, 1, b'response')
480 res = reactor.oncommandresponseready(outstream, 1, b'response')
478 list(res[1][b'framegen'])
481 list(res[1][b'framegen'])
479
482
480 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
483 results = list(sendcommandframes(reactor, instream, 1, b'command1', {}))
481 self.assertaction(results[0], b'runcommand')
484 self.assertaction(results[0], b'runcommand')
482
485
483 if __name__ == '__main__':
486 if __name__ == '__main__':
484 import silenttestrunner
487 import silenttestrunner
485 silenttestrunner.main(__name__)
488 silenttestrunner.main(__name__)
@@ -1,59 +1,59 b''
1 HTTPV2=exp-http-v2-0001
1 HTTPV2=exp-http-v2-0001
2 MEDIATYPE=application/mercurial-exp-framing-0004
2 MEDIATYPE=application/mercurial-exp-framing-0005
3
3
4 sendhttpraw() {
4 sendhttpraw() {
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
5 hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
6 }
6 }
7
7
8 sendhttpv2peer() {
8 sendhttpv2peer() {
9 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
9 hg --verbose debugwireproto --nologhandshake --peer http2 http://$LOCALIP:$HGPORT/
10 }
10 }
11
11
12 sendhttpv2peerhandshake() {
12 sendhttpv2peerhandshake() {
13 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
13 hg --verbose debugwireproto --peer http2 http://$LOCALIP:$HGPORT/
14 }
14 }
15
15
16 cat > dummycommands.py << EOF
16 cat > dummycommands.py << EOF
17 from mercurial import (
17 from mercurial import (
18 wireprototypes,
18 wireprototypes,
19 wireproto,
19 wireproto,
20 )
20 )
21
21
22 @wireproto.wireprotocommand('customreadonly', permission='pull')
22 @wireproto.wireprotocommand('customreadonly', permission='pull')
23 def customreadonlyv1(repo, proto):
23 def customreadonlyv1(repo, proto):
24 return wireprototypes.bytesresponse(b'customreadonly bytes response')
24 return wireprototypes.bytesresponse(b'customreadonly bytes response')
25
25
26 @wireproto.wireprotocommand('customreadonly', permission='pull',
26 @wireproto.wireprotocommand('customreadonly', permission='pull',
27 transportpolicy=wireproto.POLICY_V2_ONLY)
27 transportpolicy=wireproto.POLICY_V2_ONLY)
28 def customreadonlyv2(repo, proto):
28 def customreadonlyv2(repo, proto):
29 return wireprototypes.cborresponse(b'customreadonly bytes response')
29 return wireprototypes.cborresponse(b'customreadonly bytes response')
30
30
31 @wireproto.wireprotocommand('customreadwrite', permission='push')
31 @wireproto.wireprotocommand('customreadwrite', permission='push')
32 def customreadwrite(repo, proto):
32 def customreadwrite(repo, proto):
33 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
33 return wireprototypes.bytesresponse(b'customreadwrite bytes response')
34
34
35 @wireproto.wireprotocommand('customreadwrite', permission='push',
35 @wireproto.wireprotocommand('customreadwrite', permission='push',
36 transportpolicy=wireproto.POLICY_V2_ONLY)
36 transportpolicy=wireproto.POLICY_V2_ONLY)
37 def customreadwritev2(repo, proto):
37 def customreadwritev2(repo, proto):
38 return wireprototypes.cborresponse(b'customreadwrite bytes response')
38 return wireprototypes.cborresponse(b'customreadwrite bytes response')
39 EOF
39 EOF
40
40
41 cat >> $HGRCPATH << EOF
41 cat >> $HGRCPATH << EOF
42 [extensions]
42 [extensions]
43 drawdag = $TESTDIR/drawdag.py
43 drawdag = $TESTDIR/drawdag.py
44 EOF
44 EOF
45
45
46 enabledummycommands() {
46 enabledummycommands() {
47 cat >> $HGRCPATH << EOF
47 cat >> $HGRCPATH << EOF
48 [extensions]
48 [extensions]
49 dummycommands = $TESTTMP/dummycommands.py
49 dummycommands = $TESTTMP/dummycommands.py
50 EOF
50 EOF
51 }
51 }
52
52
53 enablehttpv2() {
53 enablehttpv2() {
54 cat >> $1/.hg/hgrc << EOF
54 cat >> $1/.hg/hgrc << EOF
55 [experimental]
55 [experimental]
56 web.apiserver = true
56 web.apiserver = true
57 web.api.http-v2 = true
57 web.api.http-v2 = true
58 EOF
58 EOF
59 }
59 }
General Comments 0
You need to be logged in to leave comments. Login now