wireproto: use CBOR for command requests
Now that we're using CBOR in the new wire protocol, let's convert
command requests to it.
Before I wrote this patch and was even thinking about CBOR, I was
thinking about how commands should be issued and came to the
conclusion that we didn't need separate frames to represent the
command name from its arguments. I already had a partially
completed patch prepared to merge the frames.
But with CBOR, it makes the implementation a bit simpler because
we don't need to roll our own serialization.
The changes here are a bit invasive. I tried to split this into
multiple commits to make it easier to review. But it was just too
hard.
* "command name" and "command argument" frames have been collapsed
into a "command request" frame.
* The flags for this new frame are totally different.
* Frame processing has been overhauled to reflect the new order
of things.
* Test fallout was significant. A handful of tests were removed.
Altogether, I think the new code is simpler. We don't have
complicated state around receiving commands. We're either receiving
command request frames or command data frames. We /could/
potentially collapse command data frames into command request
frames. Although I'd have to think a bit more about this before
I do it.
Differential Revision: https://phab.mercurial-scm.org/D2951
--- a/mercurial/help/internals/wireprotocol.txt Mon Mar 26 10:50:36 2018 -0700
+++ b/mercurial/help/internals/wireprotocol.txt Mon Mar 26 14:34:32 2018 -0700
@@ -561,8 +561,17 @@
This frame contains a request to run a command.
-The name of the command to run constitutes the entirety of the frame
-payload.
+The payload consists of a CBOR map defining the command request. The
+bytestring keys of that map are:
+
+name
+ Name of the command that should be executed (bytestring).
+args
+ Map of bytestring keys to various value types containing the named
+ arguments to this command.
+
+ Each command defines its own set of argument names and their expected
+ types.
This frame type MUST ONLY be sent from clients to servers: it is illegal
for a server to send this frame to a client.
@@ -570,54 +579,30 @@
The following flag values are defined for this type:
0x01
- End of command data. When set, the client will not send any command
- arguments or additional command data. When set, the command has been
- fully issued and the server has the full context to process the command.
- The next frame issued by the client is not part of this command.
+ New command request. When set, this frame represents the beginning
+ of a new request to run a command. The ``Request ID`` attached to this
+ frame MUST NOT be active.
0x02
- Command argument frames expected. When set, the client will send
- *Command Argument* frames containing command argument data.
+ Command request continuation. When set, this frame is a continuation
+ from a previous command request frame for its ``Request ID``. This
+ flag is set when the CBOR data for a command request does not fit
+ in a single frame.
0x04
- Command data frames expected. When set, the client will send
- *Command Data* frames containing a raw stream of data for this
- command.
-
-The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
-flags.
-
-Command Argument (``0x02``)
----------------------------
-
-This frame contains a named argument for a command.
-
-The frame type MUST ONLY be sent from clients to servers: it is illegal
-for a server to send this frame to a client.
+ Additional frames expected. When set, the command request didn't fit
+ into a single frame and additional CBOR data follows in a subsequent
+ frame.
+0x08
+ Command data frames expected. When set, command data frames are
+ expected to follow the final command request frame for this request.
-The payload consists of:
-
-* A 16-bit little endian integer denoting the length of the
- argument name.
-* A 16-bit little endian integer denoting the length of the
- argument value.
-* N bytes of ASCII data containing the argument name.
-* N bytes of binary data containing the argument value.
-
-The payload MUST hold the entirety of the 32-bit header and the
-argument name. The argument value MAY span multiple frames. If this
-occurs, the appropriate frame flag should be set to indicate this.
+``0x01`` MUST be set on the initial command request frame for a
+``Request ID``.
-The following flag values are defined for this type:
+``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
+a series of command request frames.
-0x01
- Argument data continuation. When set, the data for this argument did
- not fit in a single frame and the next frame will contain additional
- argument data.
-
-0x02
- End of arguments data. When set, the client will not send any more
- command arguments for the command this frame is associated with.
- The next frame issued by the client will be command data or
- belong to a separate request.
+If command data frames are to be sent, ``0x10`` MUST be set on ALL
+command request frames.
Command Data (``0x03``)
-----------------------
@@ -903,8 +888,8 @@
A client can request that a remote run a command by sending it
frames defining that command. This logical stream is composed of
-1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
-and 0 or more ``Command Data`` frames.
+1 or more ``Command Request`` frames and and 0 or more ``Command Data``
+frames.
All frames composing a single command request MUST be associated with
the same ``Request ID``.
@@ -928,14 +913,17 @@
TODO think about whether we should express dependencies between commands
to avoid roundtrip latency.
-Argument frames are the recommended mechanism for transferring fixed
-sets of parameters to a command. Data frames are appropriate for
-transferring variable data. A similar comparison would be to HTTP:
-argument frames are headers and the message body is data frames.
+A command is defined by a command name, 0 or more command arguments,
+and optional command data.
+
+Arguments are the recommended mechanism for transferring fixed sets of
+parameters to a command. Data is appropriate for transferring variable
+data. Thinking in terms of HTTP, arguments would be headers and data
+would be the message body.
It is recommended for servers to delay the dispatch of a command
-until all argument frames for that command have been received. Servers
-MAY impose limits on the maximum argument size.
+until all argument have been received. Servers MAY impose limits on the
+maximum argument size.
TODO define failure mechanism.
Servers MAY dispatch to commands immediately once argument data
--- a/mercurial/wireprotoframing.py Mon Mar 26 10:50:36 2018 -0700
+++ b/mercurial/wireprotoframing.py Mon Mar 26 14:34:32 2018 -0700
@@ -39,8 +39,7 @@
b'encoded': STREAM_FLAG_ENCODING_APPLIED,
}
-FRAME_TYPE_COMMAND_NAME = 0x01
-FRAME_TYPE_COMMAND_ARGUMENT = 0x02
+FRAME_TYPE_COMMAND_REQUEST = 0x01
FRAME_TYPE_COMMAND_DATA = 0x03
FRAME_TYPE_BYTES_RESPONSE = 0x04
FRAME_TYPE_ERROR_RESPONSE = 0x05
@@ -49,8 +48,7 @@
FRAME_TYPE_STREAM_SETTINGS = 0x08
FRAME_TYPES = {
- b'command-name': FRAME_TYPE_COMMAND_NAME,
- b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
+ b'command-request': FRAME_TYPE_COMMAND_REQUEST,
b'command-data': FRAME_TYPE_COMMAND_DATA,
b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
b'error-response': FRAME_TYPE_ERROR_RESPONSE,
@@ -59,22 +57,16 @@
b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
}
-FLAG_COMMAND_NAME_EOS = 0x01
-FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
-FLAG_COMMAND_NAME_HAVE_DATA = 0x04
+FLAG_COMMAND_REQUEST_NEW = 0x01
+FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
+FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
+FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
-FLAGS_COMMAND = {
- b'eos': FLAG_COMMAND_NAME_EOS,
- b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
- b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
-}
-
-FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
-FLAG_COMMAND_ARGUMENT_EOA = 0x02
-
-FLAGS_COMMAND_ARGUMENT = {
- b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
- b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
+FLAGS_COMMAND_REQUEST = {
+ b'new': FLAG_COMMAND_REQUEST_NEW,
+ b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
+ b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
+ b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
}
FLAG_COMMAND_DATA_CONTINUATION = 0x01
@@ -103,8 +95,7 @@
# Maps frame types to their available flags.
FRAME_TYPE_FLAGS = {
- FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
- FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
+ FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
@@ -113,7 +104,7 @@
FRAME_TYPE_STREAM_SETTINGS: {},
}
-ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
+ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
@attr.s(slots=True)
class frameheader(object):
@@ -269,43 +260,48 @@
return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
payload)
-def createcommandframes(stream, requestid, cmd, args, datafh=None):
+def createcommandframes(stream, requestid, cmd, args, datafh=None,
+ maxframesize=DEFAULT_MAX_FRAME_SIZE):
"""Create frames necessary to transmit a request to run a command.
This is a generator of bytearrays. Each item represents a frame
ready to be sent over the wire to a peer.
"""
- flags = 0
+ data = {b'name': cmd}
if args:
- flags |= FLAG_COMMAND_NAME_HAVE_ARGS
- if datafh:
- flags |= FLAG_COMMAND_NAME_HAVE_DATA
+ data[b'args'] = args
- if not flags:
- flags |= FLAG_COMMAND_NAME_EOS
+ data = cbor.dumps(data, canonical=True)
- yield stream.makeframe(requestid=requestid, typeid=FRAME_TYPE_COMMAND_NAME,
- flags=flags, payload=cmd)
+ offset = 0
+
+ while True:
+ flags = 0
- for i, k in enumerate(sorted(args)):
- v = args[k]
- last = i == len(args) - 1
+ # Must set new or continuation flag.
+ if not offset:
+ flags |= FLAG_COMMAND_REQUEST_NEW
+ else:
+ flags |= FLAG_COMMAND_REQUEST_CONTINUATION
- # TODO handle splitting of argument values across frames.
- payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
- offset = 0
- ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
- offset += ARGUMENT_FRAME_HEADER.size
- payload[offset:offset + len(k)] = k
- offset += len(k)
- payload[offset:offset + len(v)] = v
+ # Data frames is set on all frames.
+ if datafh:
+ flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
- flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
+ payload = data[offset:offset + maxframesize]
+ offset += len(payload)
+
+ if len(payload) == maxframesize and offset < len(data):
+ flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
+
yield stream.makeframe(requestid=requestid,
- typeid=FRAME_TYPE_COMMAND_ARGUMENT,
+ typeid=FRAME_TYPE_COMMAND_REQUEST,
flags=flags,
payload=payload)
+ if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
+ break
+
if datafh:
while True:
data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
@@ -673,6 +669,12 @@
def _makeruncommandresult(self, requestid):
entry = self._receivingcommands[requestid]
+
+ if not entry['requestdone']:
+ self._state = 'errored'
+ raise error.ProgrammingError('should not be called without '
+ 'requestdone set')
+
del self._receivingcommands[requestid]
if self._receivingcommands:
@@ -680,13 +682,25 @@
else:
self._state = 'idle'
+ # Decode the payloads as CBOR.
+ entry['payload'].seek(0)
+ request = cbor.load(entry['payload'])
+
+ if b'name' not in request:
+ self._state = 'errored'
+ return self._makeerrorresult(
+ _('command request missing "name" field'))
+
+ if b'args' not in request:
+ request[b'args'] = {}
+
assert requestid not in self._activecommands
self._activecommands.add(requestid)
return 'runcommand', {
'requestid': requestid,
- 'command': entry['command'],
- 'args': entry['args'],
+ 'command': request[b'name'],
+ 'args': request[b'args'],
'data': entry['data'].getvalue() if entry['data'] else None,
}
@@ -695,13 +709,33 @@
'state': self._state,
}
+ def _validatecommandrequestframe(self, frame):
+ new = frame.flags & FLAG_COMMAND_REQUEST_NEW
+ continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
+
+ if new and continuation:
+ self._state = 'errored'
+ return self._makeerrorresult(
+ _('received command request frame with both new and '
+ 'continuation flags set'))
+
+ if not new and not continuation:
+ self._state = 'errored'
+ return self._makeerrorresult(
+ _('received command request frame with neither new nor '
+ 'continuation flags set'))
+
def _onframeidle(self, frame):
# The only frame type that should be received in this state is a
# command request.
- if frame.typeid != FRAME_TYPE_COMMAND_NAME:
+ if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
self._state = 'errored'
return self._makeerrorresult(
- _('expected command frame; got %d') % frame.typeid)
+ _('expected command request frame; got %d') % frame.typeid)
+
+ res = self._validatecommandrequestframe(frame)
+ if res:
+ return res
if frame.requestid in self._receivingcommands:
self._state = 'errored'
@@ -710,35 +744,45 @@
if frame.requestid in self._activecommands:
self._state = 'errored'
- return self._makeerrorresult((
- _('request with ID %d is already active') % frame.requestid))
+ return self._makeerrorresult(
+ _('request with ID %d is already active') % frame.requestid)
+
+ new = frame.flags & FLAG_COMMAND_REQUEST_NEW
+ moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
+ expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
- expectingargs = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_ARGS)
- expectingdata = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_DATA)
+ if not new:
+ self._state = 'errored'
+ return self._makeerrorresult(
+ _('received command request frame without new flag set'))
+
+ payload = util.bytesio()
+ payload.write(frame.payload)
self._receivingcommands[frame.requestid] = {
- 'command': frame.payload,
- 'args': {},
+ 'payload': payload,
'data': None,
- 'expectingargs': expectingargs,
- 'expectingdata': expectingdata,
+ 'requestdone': not moreframes,
+ 'expectingdata': bool(expectingdata),
}
- if frame.flags & FLAG_COMMAND_NAME_EOS:
+ # This is the final frame for this request. Dispatch it.
+ if not moreframes and not expectingdata:
return self._makeruncommandresult(frame.requestid)
- if expectingargs or expectingdata:
- self._state = 'command-receiving'
- return self._makewantframeresult()
- else:
- self._state = 'errored'
- return self._makeerrorresult(_('missing frame flags on '
- 'command frame'))
+ assert moreframes or expectingdata
+ self._state = 'command-receiving'
+ return self._makewantframeresult()
def _onframecommandreceiving(self, frame):
- # It could be a new command request. Process it as such.
- if frame.typeid == FRAME_TYPE_COMMAND_NAME:
- return self._onframeidle(frame)
+ if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
+ # Process new command requests as such.
+ if frame.flags & FLAG_COMMAND_REQUEST_NEW:
+ return self._onframeidle(frame)
+
+ res = self._validatecommandrequestframe(frame)
+ if res:
+ return res
# All other frames should be related to a command that is currently
# receiving but is not active.
@@ -756,14 +800,30 @@
entry = self._receivingcommands[frame.requestid]
- if frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT:
- if not entry['expectingargs']:
+ if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
+ moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
+ expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
+
+ if entry['requestdone']:
+ self._state = 'errored'
+ return self._makeerrorresult(
+ _('received command request frame when request frames '
+ 'were supposedly done'))
+
+ if expectingdata != entry['expectingdata']:
self._state = 'errored'
- return self._makeerrorresult(_(
- 'received command argument frame for request that is not '
- 'expecting arguments: %d') % frame.requestid)
+ return self._makeerrorresult(
+ _('mismatch between expect data flag and previous frame'))
+
+ entry['payload'].write(frame.payload)
- return self._handlecommandargsframe(frame, entry)
+ if not moreframes:
+ entry['requestdone'] = True
+
+ if not moreframes and not expectingdata:
+ return self._makeruncommandresult(frame.requestid)
+
+ return self._makewantframeresult()
elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
if not entry['expectingdata']:
@@ -776,50 +836,10 @@
entry['data'] = util.bytesio()
return self._handlecommanddataframe(frame, entry)
-
- def _handlecommandargsframe(self, frame, entry):
- # The frame and state of command should have already been validated.
- assert frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT
-
- offset = 0
- namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(frame.payload)
- offset += ARGUMENT_FRAME_HEADER.size
-
- # The argument name MUST fit inside the frame.
- argname = bytes(frame.payload[offset:offset + namesize])
- offset += namesize
-
- if len(argname) != namesize:
+ else:
self._state = 'errored'
- return self._makeerrorresult(_('malformed argument frame: '
- 'partial argument name'))
-
- argvalue = bytes(frame.payload[offset:])
-
- # Argument value spans multiple frames. Record our active state
- # and wait for the next frame.
- if frame.flags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
- raise error.ProgrammingError('not yet implemented')
-
- # Common case: the argument value is completely contained in this
- # frame.
-
- if len(argvalue) != valuesize:
- self._state = 'errored'
- return self._makeerrorresult(_('malformed argument frame: '
- 'partial argument value'))
-
- entry['args'][argname] = argvalue
-
- if frame.flags & FLAG_COMMAND_ARGUMENT_EOA:
- if entry['expectingdata']:
- # TODO signal request to run a command once we don't
- # buffer data frames.
- return self._makewantframeresult()
- else:
- return self._makeruncommandresult(frame.requestid)
- else:
- return self._makewantframeresult()
+ return self._makeerrorresult(_(
+ 'received unexpected frame type: %d') % frame.typeid)
def _handlecommanddataframe(self, frame, entry):
assert frame.typeid == FRAME_TYPE_COMMAND_DATA
--- a/mercurial/wireprotoserver.py Mon Mar 26 10:50:36 2018 -0700
+++ b/mercurial/wireprotoserver.py Mon Mar 26 14:34:32 2018 -0700
@@ -36,7 +36,7 @@
HGTYPE = 'application/mercurial-0.1'
HGTYPE2 = 'application/mercurial-0.2'
HGERRTYPE = 'application/hg-error'
-FRAMINGTYPE = b'application/mercurial-exp-framing-0002'
+FRAMINGTYPE = b'application/mercurial-exp-framing-0003'
HTTPV2 = wireprototypes.HTTPV2
SSHV1 = wireprototypes.SSHV1
--- a/tests/test-http-api-httpv2.t Mon Mar 26 10:50:36 2018 -0700
+++ b/tests/test-http-api-httpv2.t Mon Mar 26 14:34:32 2018 -0700
@@ -1,5 +1,5 @@
$ HTTPV2=exp-http-v2-0001
- $ MEDIATYPE=application/mercurial-exp-framing-0002
+ $ MEDIATYPE=application/mercurial-exp-framing-0003
$ send() {
> hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
@@ -122,7 +122,7 @@
s> Content-Type: text/plain\r\n
s> Content-Length: 85\r\n
s> \r\n
- s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
+ s> client MUST specify Accept header with value: application/mercurial-exp-framing-0003\n
Bad Accept header results in 406
@@ -145,7 +145,7 @@
s> Content-Type: text/plain\r\n
s> Content-Length: 85\r\n
s> \r\n
- s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
+ s> client MUST specify Accept header with value: application/mercurial-exp-framing-0003\n
Bad Content-Type header results in 415
@@ -158,7 +158,7 @@
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
s> content-type: badmedia\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
@@ -170,7 +170,7 @@
s> Content-Type: text/plain\r\n
s> Content-Length: 88\r\n
s> \r\n
- s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0002\n
+ s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0003\n
Request to read-only command works out of the box
@@ -179,23 +179,23 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name eos customreadonly
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> *\r\n (glob)
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> *\r\n (glob)
+ s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly
+ s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
- s> Content-Type: application/mercurial-exp-framing-0002\r\n
+ s> Content-Type: application/mercurial-exp-framing-0003\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 25\r\n
@@ -290,23 +290,23 @@
> user-agent: test
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
- > frame 1 1 stream-begin command-name eos customreadonly
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> content-length: 22\r\n
+ s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly
+ s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
- s> Content-Type: application/mercurial-exp-framing-0002\r\n
+ s> Content-Type: application/mercurial-exp-framing-0003\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
s> 25\r\n
@@ -325,7 +325,7 @@
using raw connection to peer
s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
@@ -382,32 +382,26 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name have-args command1
- > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1
- > frame 1 1 0 command-argument eoa \x04\x00\x03\x00bar1val
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> content-length: 54\r\n
+ s> content-length: 47\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x08\x00\x00\x01\x00\x01\x01\x12command1\x0b\x00\x00\x01\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00\x01\x00"\x04\x00\x03\x00bar1val
+ s> '\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
s> Content-Type: text/plain\r\n
- s> Content-Length: 322\r\n
+ s> Content-Length: 205\r\n
s> \r\n
- s> received: 1 2 1 command1\n
- s> ["wantframe", {"state": "command-receiving"}]\n
- s> received: 2 0 1 \x03\x00\x04\x00fooval1\n
- s> ["wantframe", {"state": "command-receiving"}]\n
- s> received: 2 2 1 \x04\x00\x03\x00bar1val\n
+ s> received: 1 1 1 \xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1\n
s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
s> received: <no frame>\n
s> {"action": "noop"}
@@ -419,27 +413,30 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name eos customreadonly
- > frame 3 1 0 command-name eos customreadonly
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> content-length: 44\r\n
+ s> content-length: 29\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
+ s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
- s> Content-Type: text/plain\r\n
- s> Content-Length: 46\r\n
+ s> Content-Type: application/mercurial-exp-framing-0003\r\n
+ s> Transfer-Encoding: chunked\r\n
s> \r\n
- s> multiple commands cannot be issued to this URL
+ s> 25\r\n
+ s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
+ s> \r\n
+ s> 0\r\n
+ s> \r\n
Multiple requests to "multirequest" URL are allowed
@@ -448,27 +445,27 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name eos customreadonly
- > frame 3 1 0 command-name eos customreadonly
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
+ > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> *\r\n (glob)
+ s> *\r\n (glob)
s> user-agent: test\r\n
- s> *\r\n (glob)
+ s> content-length: 58\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
+ s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
- s> Content-Type: application/mercurial-exp-framing-0002\r\n
+ s> Content-Type: application/mercurial-exp-framing-0003\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
- s> *\r\n (glob)
+ s> 25\r\n
s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
s> \r\n
s> 25\r\n
@@ -484,36 +481,35 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name have-args listkeys
- > frame 3 1 0 command-name have-args listkeys
- > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks
- > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
+ > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
+ > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
+ > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
+ > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> content-length: 93\r\n
+ s> content-length: 115\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x08\x00\x00\x01\x00\x01\x01\x12listkeys\x08\x00\x00\x03\x00\x01\x00\x12listkeys\x16\x00\x00\x03\x00\x01\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00\x01\x00" \x00\n
- s> \x00namespacenamespaces
+ 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
s> makefile('rb', None)
s> HTTP/1.1 200 OK\r\n
s> Server: testing stub value\r\n
s> Date: $HTTP_DATE$\r\n
- s> Content-Type: application/mercurial-exp-framing-0002\r\n
+ s> Content-Type: application/mercurial-exp-framing-0003\r\n
s> Transfer-Encoding: chunked\r\n
s> \r\n
+ s> 26\r\n
+ s> \x1e\x00\x00\x03\x00\x02\x01Bbookmarks \n
+ s> namespaces \n
+ s> phases
+ s> \r\n
s> 8\r\n
- s> \x00\x00\x00\x03\x00\x02\x01B
- s> \r\n
- s> 26\r\n
- s> \x1e\x00\x00\x01\x00\x02\x00Bbookmarks \n
- s> namespaces \n
- s> phases
+ s> \x00\x00\x00\x01\x00\x02\x00B
s> \r\n
s> 0\r\n
s> \r\n
@@ -540,18 +536,18 @@
> accept: $MEDIATYPE
> content-type: $MEDIATYPE
> user-agent: test
- > frame 1 1 stream-begin command-name eos unbundle
+ > frame 1 1 stream-begin command-request new cbor:{b'name': b'unbundle'}
> EOF
using raw connection to peer
s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
s> Accept-Encoding: identity\r\n
- s> accept: application/mercurial-exp-framing-0002\r\n
- s> content-type: application/mercurial-exp-framing-0002\r\n
+ s> accept: application/mercurial-exp-framing-0003\r\n
+ s> content-type: application/mercurial-exp-framing-0003\r\n
s> user-agent: test\r\n
- s> content-length: 16\r\n
+ s> content-length: 23\r\n
s> host: $LOCALIP:$HGPORT\r\n (glob)
s> \r\n
- s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle
+ s> \x0f\x00\x00\x01\x00\x01\x01\x11\xa1DnameHunbundle
s> makefile('rb', None)
s> HTTP/1.1 403 Forbidden\r\n
s> Server: testing stub value\r\n
--- a/tests/test-wireproto-serverreactor.py Mon Mar 26 10:50:36 2018 -0700
+++ b/tests/test-wireproto-serverreactor.py Mon Mar 26 14:34:32 2018 -0700
@@ -2,6 +2,9 @@
import unittest
+from mercurial.thirdparty import (
+ cbor,
+)
from mercurial import (
util,
wireprotoframing as framing,
@@ -96,7 +99,8 @@
frames = list(framing.createcommandframes(stream, 1, b'command',
{}, data))
self.assertEqual(frames, [
- ffs(b'1 1 stream-begin command-name have-data command'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command'}"),
ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
ffs(b'1 1 0 command-data eos ')
])
@@ -108,7 +112,8 @@
frames = list(framing.createcommandframes(stream, 1, b'command', {},
data))
self.assertEqual(frames, [
- ffs(b'1 1 stream-begin command-name have-data command'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command'}"),
ffs(b'1 1 0 command-data continuation %s' % (
b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
ffs(b'1 1 0 command-data eos x'),
@@ -125,10 +130,9 @@
}, data))
self.assertEqual(frames, [
- ffs(b'1 1 stream-begin command-name have-args|have-data command'),
- ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'),
- ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'),
- ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command', b'args': {b'key1': b'key1value', "
+ b"b'key2': b'key2value', b'key3': b'key3value'}}"),
ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
])
@@ -286,10 +290,9 @@
stream = framing.stream(1)
results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
{b'foo': b'bar'}))
- self.assertEqual(len(results), 2)
- self.assertaction(results[0], 'wantframe')
- self.assertaction(results[1], 'runcommand')
- self.assertEqual(results[1][1], {
+ self.assertEqual(len(results), 1)
+ self.assertaction(results[0], 'runcommand')
+ self.assertEqual(results[0][1], {
'requestid': 41,
'command': b'mycommand',
'args': {b'foo': b'bar'},
@@ -301,11 +304,9 @@
stream = framing.stream(1)
results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
{b'foo': b'bar', b'biz': b'baz'}))
- self.assertEqual(len(results), 3)
- self.assertaction(results[0], 'wantframe')
- self.assertaction(results[1], 'wantframe')
- self.assertaction(results[2], 'runcommand')
- self.assertEqual(results[2][1], {
+ self.assertEqual(len(results), 1)
+ self.assertaction(results[0], 'runcommand')
+ self.assertEqual(results[0][1], {
'requestid': 1,
'command': b'mycommand',
'args': {b'foo': b'bar', b'biz': b'baz'},
@@ -329,7 +330,8 @@
def testmultipledataframes(self):
frames = [
- ffs(b'1 1 stream-begin command-name have-data mycommand'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'mycommand'}"),
ffs(b'1 1 0 command-data continuation data1'),
ffs(b'1 1 0 command-data continuation data2'),
ffs(b'1 1 0 command-data eos data3'),
@@ -350,9 +352,9 @@
def testargumentanddata(self):
frames = [
- ffs(b'1 1 stream-begin command-name have-args|have-data command'),
- ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'),
- ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
+ b"b'foo': b'bar'}}"),
ffs(b'1 1 0 command-data continuation value1'),
ffs(b'1 1 0 command-data eos value2'),
]
@@ -371,76 +373,68 @@
'data': b'value1value2',
})
- def testunexpectedcommandargument(self):
- """Command argument frame when not running a command is an error."""
- result = self._sendsingleframe(
- makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored'))
+ def testnewandcontinuation(self):
+ result = self._sendsingleframe(makereactor(),
+ ffs(b'1 1 stream-begin command-request new|continuation '))
self.assertaction(result, 'error')
self.assertEqual(result[1], {
- 'message': b'expected command frame; got 2',
+ 'message': b'received command request frame with both new and '
+ b'continuation flags set',
})
- def testunexpectedcommandargumentreceiving(self):
- """Same as above but the command is receiving."""
- results = list(sendframes(makereactor(), [
- ffs(b'1 1 stream-begin command-name have-data command'),
- ffs(b'1 1 0 command-argument eoa ignored'),
- ]))
-
- self.assertaction(results[1], 'error')
- self.assertEqual(results[1][1], {
- 'message': b'received command argument frame for request that is '
- b'not expecting arguments: 1',
+ def testneithernewnorcontinuation(self):
+ result = self._sendsingleframe(makereactor(),
+ ffs(b'1 1 stream-begin command-request 0 '))
+ self.assertaction(result, 'error')
+ self.assertEqual(result[1], {
+ 'message': b'received command request frame with neither new nor '
+ b'continuation flags set',
})
def testunexpectedcommanddata(self):
- """Command argument frame when not running a command is an error."""
- result = self._sendsingleframe(
- makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored'))
+ """Command data frame when not running a command is an error."""
+ result = self._sendsingleframe(makereactor(),
+ ffs(b'1 1 stream-begin command-data 0 ignored'))
self.assertaction(result, 'error')
self.assertEqual(result[1], {
- 'message': b'expected command frame; got 3',
+ 'message': b'expected command request frame; got 3',
})
def testunexpectedcommanddatareceiving(self):
"""Same as above except the command is receiving."""
results = list(sendframes(makereactor(), [
- ffs(b'1 1 stream-begin command-name have-args command'),
+ ffs(b'1 1 stream-begin command-request new|more '
+ b"cbor:{b'name': b'ignored'}"),
ffs(b'1 1 0 command-data eos ignored'),
]))
+ self.assertaction(results[0], 'wantframe')
self.assertaction(results[1], 'error')
self.assertEqual(results[1][1], {
'message': b'received command data frame for request that is not '
b'expecting data: 1',
})
- def testmissingcommandframeflags(self):
- """Command name frame must have flags set."""
- result = self._sendsingleframe(
- makereactor(), ffs(b'1 1 stream-begin command-name 0 command'))
- self.assertaction(result, 'error')
- self.assertEqual(result[1], {
- 'message': b'missing frame flags on command frame',
- })
-
def testconflictingrequestidallowed(self):
"""Multiple fully serviced commands with same request ID is allowed."""
reactor = makereactor()
results = []
outstream = reactor.makeoutputstream()
results.append(self._sendsingleframe(
- reactor, ffs(b'1 1 stream-begin command-name eos command')))
+ reactor, ffs(b'1 1 stream-begin command-request new '
+ b"cbor:{b'name': b'command'}")))
result = reactor.onbytesresponseready(outstream, 1, b'response1')
self.assertaction(result, 'sendframes')
list(result[1]['framegen'])
results.append(self._sendsingleframe(
- reactor, ffs(b'1 1 0 command-name eos command')))
+ reactor, ffs(b'1 1 stream-begin command-request new '
+ b"cbor:{b'name': b'command'}")))
result = reactor.onbytesresponseready(outstream, 1, b'response2')
self.assertaction(result, 'sendframes')
list(result[1]['framegen'])
results.append(self._sendsingleframe(
- reactor, ffs(b'1 1 0 command-name eos command')))
+ reactor, ffs(b'1 1 stream-begin command-request new '
+ b"cbor:{b'name': b'command'}")))
result = reactor.onbytesresponseready(outstream, 1, b'response3')
self.assertaction(result, 'sendframes')
list(result[1]['framegen'])
@@ -457,8 +451,10 @@
def testconflictingrequestid(self):
"""Request ID for new command matching in-flight command is illegal."""
results = list(sendframes(makereactor(), [
- ffs(b'1 1 stream-begin command-name have-args command'),
- ffs(b'1 1 0 command-name eos command'),
+ ffs(b'1 1 stream-begin command-request new|more '
+ b"cbor:{b'name': b'command'}"),
+ ffs(b'1 1 0 command-request new '
+ b"cbor:{b'name': b'command1'}"),
]))
self.assertaction(results[0], 'wantframe')
@@ -468,13 +464,28 @@
})
def testinterleavedcommands(self):
+ cbor1 = cbor.dumps({
+ b'name': b'command1',
+ b'args': {
+ b'foo': b'bar',
+ b'key1': b'val',
+ }
+ }, canonical=True)
+ cbor3 = cbor.dumps({
+ b'name': b'command3',
+ b'args': {
+ b'biz': b'baz',
+ b'key': b'val',
+ },
+ }, canonical=True)
+
results = list(sendframes(makereactor(), [
- ffs(b'1 1 stream-begin command-name have-args command1'),
- ffs(b'3 1 0 command-name have-args command3'),
- ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'),
- ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'),
- ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'),
- ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'),
+ ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
+ ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
+ ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
+ ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
+ ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
+ ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
]))
self.assertEqual([t[0] for t in results], [
@@ -499,53 +510,14 @@
'data': None,
})
- def testmissingargumentframe(self):
- # This test attempts to test behavior when reactor has an incomplete
- # command request waiting on argument data. But it doesn't handle that
- # scenario yet. So this test does nothing of value.
- frames = [
- ffs(b'1 1 stream-begin command-name have-args command'),
- ]
-
- results = list(sendframes(makereactor(), frames))
- self.assertaction(results[0], 'wantframe')
-
- def testincompleteargumentname(self):
- """Argument frame with incomplete name."""
- frames = [
- ffs(b'1 1 stream-begin command-name have-args command1'),
- ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'),
- ]
-
- results = list(sendframes(makereactor(), frames))
- self.assertEqual(len(results), 2)
- self.assertaction(results[0], 'wantframe')
- self.assertaction(results[1], 'error')
- self.assertEqual(results[1][1], {
- 'message': b'malformed argument frame: partial argument name',
- })
-
- def testincompleteargumentvalue(self):
- """Argument frame with incomplete value."""
- frames = [
- ffs(b'1 1 stream-begin command-name have-args command'),
- ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
- ]
-
- results = list(sendframes(makereactor(), frames))
- self.assertEqual(len(results), 2)
- self.assertaction(results[0], 'wantframe')
- self.assertaction(results[1], 'error')
- self.assertEqual(results[1][1], {
- 'message': b'malformed argument frame: partial argument value',
- })
-
def testmissingcommanddataframe(self):
# The reactor doesn't currently handle partially received commands.
# So this test is failing to do anything with request 1.
frames = [
- ffs(b'1 1 stream-begin command-name have-data command1'),
- ffs(b'3 1 0 command-name eos command2'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command1'}"),
+ ffs(b'3 1 0 command-request new '
+ b"cbor:{b'name': b'command2'}"),
]
results = list(sendframes(makereactor(), frames))
self.assertEqual(len(results), 2)
@@ -554,7 +526,8 @@
def testmissingcommanddataframeflags(self):
frames = [
- ffs(b'1 1 stream-begin command-name have-data command1'),
+ ffs(b'1 1 stream-begin command-request new|have-data '
+ b"cbor:{b'name': b'command1'}"),
ffs(b'1 1 0 command-data 0 data'),
]
results = list(sendframes(makereactor(), frames))
@@ -568,9 +541,11 @@
def testframefornonreceivingrequest(self):
"""Receiving a frame for a command that is not receiving is illegal."""
results = list(sendframes(makereactor(), [
- ffs(b'1 1 stream-begin command-name eos command1'),
- ffs(b'3 1 0 command-name have-data command3'),
- ffs(b'5 1 0 command-argument eoa ignored'),
+ ffs(b'1 1 stream-begin command-request new '
+ b"cbor:{b'name': b'command1'}"),
+ ffs(b'3 1 0 command-request new|have-data '
+ b"cbor:{b'name': b'command3'}"),
+ ffs(b'5 1 0 command-data eos ignored'),
]))
self.assertaction(results[2], 'error')
self.assertEqual(results[2][1], {
@@ -705,21 +680,6 @@
'message': b'request with ID 1 is already active',
})
- def testduplicaterequestargumentframe(self):
- """Variant on above except we sent an argument frame instead of name."""
- reactor = makereactor()
- stream = framing.stream(1)
- list(sendcommandframes(reactor, stream, 1, b'command', {}))
- results = list(sendframes(reactor, [
- ffs(b'3 1 stream-begin command-name have-args command'),
- ffs(b'1 1 0 command-argument 0 ignored'),
- ]))
- self.assertaction(results[0], 'wantframe')
- self.assertaction(results[1], 'error')
- self.assertEqual(results[1][1], {
- 'message': 'received frame for request that is still active: 1',
- })
-
def testduplicaterequestaftersend(self):
"""We can use a duplicate request ID after we've sent the response."""
reactor = makereactor()