##// END OF EJS Templates
wireproto: use CBOR for command requests...
Gregory Szorc -
r37308:3d0e2cd8 default
parent child Browse files
Show More
@@ -561,8 +561,17 b' Command Request (``0x01``)'
561
561
562 This frame contains a request to run a command.
562 This frame contains a request to run a command.
563
563
564 The name of the command to run constitutes the entirety of the frame
564 The payload consists of a CBOR map defining the command request. The
565 payload.
565 bytestring keys of that map are:
566
567 name
568 Name of the command that should be executed (bytestring).
569 args
570 Map of bytestring keys to various value types containing the named
571 arguments to this command.
572
573 Each command defines its own set of argument names and their expected
574 types.
566
575
567 This frame type MUST ONLY be sent from clients to servers: it is illegal
576 This frame type MUST ONLY be sent from clients to servers: it is illegal
568 for a server to send this frame to a client.
577 for a server to send this frame to a client.
@@ -570,54 +579,30 b' for a server to send this frame to a cli'
570 The following flag values are defined for this type:
579 The following flag values are defined for this type:
571
580
572 0x01
581 0x01
573 End of command data. When set, the client will not send any command
582 New command request. When set, this frame represents the beginning
574 arguments or additional command data. When set, the command has been
583 of a new request to run a command. The ``Request ID`` attached to this
575 fully issued and the server has the full context to process the command.
584 frame MUST NOT be active.
576 The next frame issued by the client is not part of this command.
577 0x02
585 0x02
578 Command argument frames expected. When set, the client will send
586 Command request continuation. When set, this frame is a continuation
579 *Command Argument* frames containing command argument data.
587 from a previous command request frame for its ``Request ID``. This
588 flag is set when the CBOR data for a command request does not fit
589 in a single frame.
580 0x04
590 0x04
581 Command data frames expected. When set, the client will send
591 Additional frames expected. When set, the command request didn't fit
582 *Command Data* frames containing a raw stream of data for this
592 into a single frame and additional CBOR data follows in a subsequent
583 command.
593 frame.
584
594 0x08
585 The ``0x01`` flag is mutually exclusive with both the ``0x02`` and ``0x04``
595 Command data frames expected. When set, command data frames are
586 flags.
596 expected to follow the final command request frame for this request.
587
588 Command Argument (``0x02``)
589 ---------------------------
590
591 This frame contains a named argument for a command.
592
593 The frame type MUST ONLY be sent from clients to servers: it is illegal
594 for a server to send this frame to a client.
595
597
596 The payload consists of:
598 ``0x01`` MUST be set on the initial command request frame for a
597
599 ``Request ID``.
598 * A 16-bit little endian integer denoting the length of the
599 argument name.
600 * A 16-bit little endian integer denoting the length of the
601 argument value.
602 * N bytes of ASCII data containing the argument name.
603 * N bytes of binary data containing the argument value.
604
605 The payload MUST hold the entirety of the 32-bit header and the
606 argument name. The argument value MAY span multiple frames. If this
607 occurs, the appropriate frame flag should be set to indicate this.
608
600
609 The following flag values are defined for this type:
601 ``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
602 a series of command request frames.
610
603
611 0x01
604 If command data frames are to be sent, ``0x10`` MUST be set on ALL
612 Argument data continuation. When set, the data for this argument did
605 command request frames.
613 not fit in a single frame and the next frame will contain additional
614 argument data.
615
616 0x02
617 End of arguments data. When set, the client will not send any more
618 command arguments for the command this frame is associated with.
619 The next frame issued by the client will be command data or
620 belong to a separate request.
621
606
622 Command Data (``0x03``)
607 Command Data (``0x03``)
623 -----------------------
608 -----------------------
@@ -903,8 +888,8 b' Issuing Commands'
903
888
904 A client can request that a remote run a command by sending it
889 A client can request that a remote run a command by sending it
905 frames defining that command. This logical stream is composed of
890 frames defining that command. This logical stream is composed of
906 1 ``Command Request`` frame, 0 or more ``Command Argument`` frames,
891 1 or more ``Command Request`` frames and and 0 or more ``Command Data``
907 and 0 or more ``Command Data`` frames.
892 frames.
908
893
909 All frames composing a single command request MUST be associated with
894 All frames composing a single command request MUST be associated with
910 the same ``Request ID``.
895 the same ``Request ID``.
@@ -928,14 +913,17 b' been received.'
928 TODO think about whether we should express dependencies between commands
913 TODO think about whether we should express dependencies between commands
929 to avoid roundtrip latency.
914 to avoid roundtrip latency.
930
915
931 Argument frames are the recommended mechanism for transferring fixed
916 A command is defined by a command name, 0 or more command arguments,
932 sets of parameters to a command. Data frames are appropriate for
917 and optional command data.
933 transferring variable data. A similar comparison would be to HTTP:
918
934 argument frames are headers and the message body is data frames.
919 Arguments are the recommended mechanism for transferring fixed sets of
920 parameters to a command. Data is appropriate for transferring variable
921 data. Thinking in terms of HTTP, arguments would be headers and data
922 would be the message body.
935
923
936 It is recommended for servers to delay the dispatch of a command
924 It is recommended for servers to delay the dispatch of a command
937 until all argument frames for that command have been received. Servers
925 until all argument have been received. Servers MAY impose limits on the
938 MAY impose limits on the maximum argument size.
926 maximum argument size.
939 TODO define failure mechanism.
927 TODO define failure mechanism.
940
928
941 Servers MAY dispatch to commands immediately once argument data
929 Servers MAY dispatch to commands immediately once argument data
@@ -39,8 +39,7 b' STREAM_FLAGS = {'
39 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
39 b'encoded': STREAM_FLAG_ENCODING_APPLIED,
40 }
40 }
41
41
42 FRAME_TYPE_COMMAND_NAME = 0x01
42 FRAME_TYPE_COMMAND_REQUEST = 0x01
43 FRAME_TYPE_COMMAND_ARGUMENT = 0x02
44 FRAME_TYPE_COMMAND_DATA = 0x03
43 FRAME_TYPE_COMMAND_DATA = 0x03
45 FRAME_TYPE_BYTES_RESPONSE = 0x04
44 FRAME_TYPE_BYTES_RESPONSE = 0x04
46 FRAME_TYPE_ERROR_RESPONSE = 0x05
45 FRAME_TYPE_ERROR_RESPONSE = 0x05
@@ -49,8 +48,7 b' FRAME_TYPE_PROGRESS = 0x07'
49 FRAME_TYPE_STREAM_SETTINGS = 0x08
48 FRAME_TYPE_STREAM_SETTINGS = 0x08
50
49
51 FRAME_TYPES = {
50 FRAME_TYPES = {
52 b'command-name': FRAME_TYPE_COMMAND_NAME,
51 b'command-request': FRAME_TYPE_COMMAND_REQUEST,
53 b'command-argument': FRAME_TYPE_COMMAND_ARGUMENT,
54 b'command-data': FRAME_TYPE_COMMAND_DATA,
52 b'command-data': FRAME_TYPE_COMMAND_DATA,
55 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
53 b'bytes-response': FRAME_TYPE_BYTES_RESPONSE,
56 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
54 b'error-response': FRAME_TYPE_ERROR_RESPONSE,
@@ -59,22 +57,16 b' FRAME_TYPES = {'
59 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
57 b'stream-settings': FRAME_TYPE_STREAM_SETTINGS,
60 }
58 }
61
59
62 FLAG_COMMAND_NAME_EOS = 0x01
60 FLAG_COMMAND_REQUEST_NEW = 0x01
63 FLAG_COMMAND_NAME_HAVE_ARGS = 0x02
61 FLAG_COMMAND_REQUEST_CONTINUATION = 0x02
64 FLAG_COMMAND_NAME_HAVE_DATA = 0x04
62 FLAG_COMMAND_REQUEST_MORE_FRAMES = 0x04
63 FLAG_COMMAND_REQUEST_EXPECT_DATA = 0x08
65
64
66 FLAGS_COMMAND = {
65 FLAGS_COMMAND_REQUEST = {
67 b'eos': FLAG_COMMAND_NAME_EOS,
66 b'new': FLAG_COMMAND_REQUEST_NEW,
68 b'have-args': FLAG_COMMAND_NAME_HAVE_ARGS,
67 b'continuation': FLAG_COMMAND_REQUEST_CONTINUATION,
69 b'have-data': FLAG_COMMAND_NAME_HAVE_DATA,
68 b'more': FLAG_COMMAND_REQUEST_MORE_FRAMES,
70 }
69 b'have-data': FLAG_COMMAND_REQUEST_EXPECT_DATA,
71
72 FLAG_COMMAND_ARGUMENT_CONTINUATION = 0x01
73 FLAG_COMMAND_ARGUMENT_EOA = 0x02
74
75 FLAGS_COMMAND_ARGUMENT = {
76 b'continuation': FLAG_COMMAND_ARGUMENT_CONTINUATION,
77 b'eoa': FLAG_COMMAND_ARGUMENT_EOA,
78 }
70 }
79
71
80 FLAG_COMMAND_DATA_CONTINUATION = 0x01
72 FLAG_COMMAND_DATA_CONTINUATION = 0x01
@@ -103,8 +95,7 b' FLAGS_ERROR_RESPONSE = {'
103
95
104 # Maps frame types to their available flags.
96 # Maps frame types to their available flags.
105 FRAME_TYPE_FLAGS = {
97 FRAME_TYPE_FLAGS = {
106 FRAME_TYPE_COMMAND_NAME: FLAGS_COMMAND,
98 FRAME_TYPE_COMMAND_REQUEST: FLAGS_COMMAND_REQUEST,
107 FRAME_TYPE_COMMAND_ARGUMENT: FLAGS_COMMAND_ARGUMENT,
108 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
99 FRAME_TYPE_COMMAND_DATA: FLAGS_COMMAND_DATA,
109 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
100 FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE,
110 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
101 FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE,
@@ -113,7 +104,7 b' FRAME_TYPE_FLAGS = {'
113 FRAME_TYPE_STREAM_SETTINGS: {},
104 FRAME_TYPE_STREAM_SETTINGS: {},
114 }
105 }
115
106
116 ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH')
107 ARGUMENT_RECORD_HEADER = struct.Struct(r'<HH')
117
108
118 @attr.s(slots=True)
109 @attr.s(slots=True)
119 class frameheader(object):
110 class frameheader(object):
@@ -269,43 +260,48 b' def readframe(fh):'
269 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
260 return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags,
270 payload)
261 payload)
271
262
272 def createcommandframes(stream, requestid, cmd, args, datafh=None):
263 def createcommandframes(stream, requestid, cmd, args, datafh=None,
264 maxframesize=DEFAULT_MAX_FRAME_SIZE):
273 """Create frames necessary to transmit a request to run a command.
265 """Create frames necessary to transmit a request to run a command.
274
266
275 This is a generator of bytearrays. Each item represents a frame
267 This is a generator of bytearrays. Each item represents a frame
276 ready to be sent over the wire to a peer.
268 ready to be sent over the wire to a peer.
277 """
269 """
278 flags = 0
270 data = {b'name': cmd}
279 if args:
271 if args:
280 flags |= FLAG_COMMAND_NAME_HAVE_ARGS
272 data[b'args'] = args
281 if datafh:
282 flags |= FLAG_COMMAND_NAME_HAVE_DATA
283
273
284 if not flags:
274 data = cbor.dumps(data, canonical=True)
285 flags |= FLAG_COMMAND_NAME_EOS
286
275
287 yield stream.makeframe(requestid=requestid, typeid=FRAME_TYPE_COMMAND_NAME,
276 offset = 0
288 flags=flags, payload=cmd)
277
278 while True:
279 flags = 0
289
280
290 for i, k in enumerate(sorted(args)):
281 # Must set new or continuation flag.
291 v = args[k]
282 if not offset:
292 last = i == len(args) - 1
283 flags |= FLAG_COMMAND_REQUEST_NEW
284 else:
285 flags |= FLAG_COMMAND_REQUEST_CONTINUATION
293
286
294 # TODO handle splitting of argument values across frames.
287 # Data frames is set on all frames.
295 payload = bytearray(ARGUMENT_FRAME_HEADER.size + len(k) + len(v))
288 if datafh:
296 offset = 0
289 flags |= FLAG_COMMAND_REQUEST_EXPECT_DATA
297 ARGUMENT_FRAME_HEADER.pack_into(payload, offset, len(k), len(v))
298 offset += ARGUMENT_FRAME_HEADER.size
299 payload[offset:offset + len(k)] = k
300 offset += len(k)
301 payload[offset:offset + len(v)] = v
302
290
303 flags = FLAG_COMMAND_ARGUMENT_EOA if last else 0
291 payload = data[offset:offset + maxframesize]
292 offset += len(payload)
293
294 if len(payload) == maxframesize and offset < len(data):
295 flags |= FLAG_COMMAND_REQUEST_MORE_FRAMES
296
304 yield stream.makeframe(requestid=requestid,
297 yield stream.makeframe(requestid=requestid,
305 typeid=FRAME_TYPE_COMMAND_ARGUMENT,
298 typeid=FRAME_TYPE_COMMAND_REQUEST,
306 flags=flags,
299 flags=flags,
307 payload=payload)
300 payload=payload)
308
301
302 if not (flags & FLAG_COMMAND_REQUEST_MORE_FRAMES):
303 break
304
309 if datafh:
305 if datafh:
310 while True:
306 while True:
311 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
307 data = datafh.read(DEFAULT_MAX_FRAME_SIZE)
@@ -673,6 +669,12 b' class serverreactor(object):'
673
669
674 def _makeruncommandresult(self, requestid):
670 def _makeruncommandresult(self, requestid):
675 entry = self._receivingcommands[requestid]
671 entry = self._receivingcommands[requestid]
672
673 if not entry['requestdone']:
674 self._state = 'errored'
675 raise error.ProgrammingError('should not be called without '
676 'requestdone set')
677
676 del self._receivingcommands[requestid]
678 del self._receivingcommands[requestid]
677
679
678 if self._receivingcommands:
680 if self._receivingcommands:
@@ -680,13 +682,25 b' class serverreactor(object):'
680 else:
682 else:
681 self._state = 'idle'
683 self._state = 'idle'
682
684
685 # Decode the payloads as CBOR.
686 entry['payload'].seek(0)
687 request = cbor.load(entry['payload'])
688
689 if b'name' not in request:
690 self._state = 'errored'
691 return self._makeerrorresult(
692 _('command request missing "name" field'))
693
694 if b'args' not in request:
695 request[b'args'] = {}
696
683 assert requestid not in self._activecommands
697 assert requestid not in self._activecommands
684 self._activecommands.add(requestid)
698 self._activecommands.add(requestid)
685
699
686 return 'runcommand', {
700 return 'runcommand', {
687 'requestid': requestid,
701 'requestid': requestid,
688 'command': entry['command'],
702 'command': request[b'name'],
689 'args': entry['args'],
703 'args': request[b'args'],
690 'data': entry['data'].getvalue() if entry['data'] else None,
704 'data': entry['data'].getvalue() if entry['data'] else None,
691 }
705 }
692
706
@@ -695,13 +709,33 b' class serverreactor(object):'
695 'state': self._state,
709 'state': self._state,
696 }
710 }
697
711
712 def _validatecommandrequestframe(self, frame):
713 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
714 continuation = frame.flags & FLAG_COMMAND_REQUEST_CONTINUATION
715
716 if new and continuation:
717 self._state = 'errored'
718 return self._makeerrorresult(
719 _('received command request frame with both new and '
720 'continuation flags set'))
721
722 if not new and not continuation:
723 self._state = 'errored'
724 return self._makeerrorresult(
725 _('received command request frame with neither new nor '
726 'continuation flags set'))
727
698 def _onframeidle(self, frame):
728 def _onframeidle(self, frame):
699 # The only frame type that should be received in this state is a
729 # The only frame type that should be received in this state is a
700 # command request.
730 # command request.
701 if frame.typeid != FRAME_TYPE_COMMAND_NAME:
731 if frame.typeid != FRAME_TYPE_COMMAND_REQUEST:
702 self._state = 'errored'
732 self._state = 'errored'
703 return self._makeerrorresult(
733 return self._makeerrorresult(
704 _('expected command frame; got %d') % frame.typeid)
734 _('expected command request frame; got %d') % frame.typeid)
735
736 res = self._validatecommandrequestframe(frame)
737 if res:
738 return res
705
739
706 if frame.requestid in self._receivingcommands:
740 if frame.requestid in self._receivingcommands:
707 self._state = 'errored'
741 self._state = 'errored'
@@ -710,35 +744,45 b' class serverreactor(object):'
710
744
711 if frame.requestid in self._activecommands:
745 if frame.requestid in self._activecommands:
712 self._state = 'errored'
746 self._state = 'errored'
713 return self._makeerrorresult((
747 return self._makeerrorresult(
714 _('request with ID %d is already active') % frame.requestid))
748 _('request with ID %d is already active') % frame.requestid)
749
750 new = frame.flags & FLAG_COMMAND_REQUEST_NEW
751 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
752 expectingdata = frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA
715
753
716 expectingargs = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_ARGS)
754 if not new:
717 expectingdata = bool(frame.flags & FLAG_COMMAND_NAME_HAVE_DATA)
755 self._state = 'errored'
756 return self._makeerrorresult(
757 _('received command request frame without new flag set'))
758
759 payload = util.bytesio()
760 payload.write(frame.payload)
718
761
719 self._receivingcommands[frame.requestid] = {
762 self._receivingcommands[frame.requestid] = {
720 'command': frame.payload,
763 'payload': payload,
721 'args': {},
722 'data': None,
764 'data': None,
723 'expectingargs': expectingargs,
765 'requestdone': not moreframes,
724 'expectingdata': expectingdata,
766 'expectingdata': bool(expectingdata),
725 }
767 }
726
768
727 if frame.flags & FLAG_COMMAND_NAME_EOS:
769 # This is the final frame for this request. Dispatch it.
770 if not moreframes and not expectingdata:
728 return self._makeruncommandresult(frame.requestid)
771 return self._makeruncommandresult(frame.requestid)
729
772
730 if expectingargs or expectingdata:
773 assert moreframes or expectingdata
731 self._state = 'command-receiving'
774 self._state = 'command-receiving'
732 return self._makewantframeresult()
775 return self._makewantframeresult()
733 else:
734 self._state = 'errored'
735 return self._makeerrorresult(_('missing frame flags on '
736 'command frame'))
737
776
738 def _onframecommandreceiving(self, frame):
777 def _onframecommandreceiving(self, frame):
739 # It could be a new command request. Process it as such.
778 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
740 if frame.typeid == FRAME_TYPE_COMMAND_NAME:
779 # Process new command requests as such.
741 return self._onframeidle(frame)
780 if frame.flags & FLAG_COMMAND_REQUEST_NEW:
781 return self._onframeidle(frame)
782
783 res = self._validatecommandrequestframe(frame)
784 if res:
785 return res
742
786
743 # All other frames should be related to a command that is currently
787 # All other frames should be related to a command that is currently
744 # receiving but is not active.
788 # receiving but is not active.
@@ -756,14 +800,30 b' class serverreactor(object):'
756
800
757 entry = self._receivingcommands[frame.requestid]
801 entry = self._receivingcommands[frame.requestid]
758
802
759 if frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT:
803 if frame.typeid == FRAME_TYPE_COMMAND_REQUEST:
760 if not entry['expectingargs']:
804 moreframes = frame.flags & FLAG_COMMAND_REQUEST_MORE_FRAMES
805 expectingdata = bool(frame.flags & FLAG_COMMAND_REQUEST_EXPECT_DATA)
806
807 if entry['requestdone']:
808 self._state = 'errored'
809 return self._makeerrorresult(
810 _('received command request frame when request frames '
811 'were supposedly done'))
812
813 if expectingdata != entry['expectingdata']:
761 self._state = 'errored'
814 self._state = 'errored'
762 return self._makeerrorresult(_(
815 return self._makeerrorresult(
763 'received command argument frame for request that is not '
816 _('mismatch between expect data flag and previous frame'))
764 'expecting arguments: %d') % frame.requestid)
817
818 entry['payload'].write(frame.payload)
765
819
766 return self._handlecommandargsframe(frame, entry)
820 if not moreframes:
821 entry['requestdone'] = True
822
823 if not moreframes and not expectingdata:
824 return self._makeruncommandresult(frame.requestid)
825
826 return self._makewantframeresult()
767
827
768 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
828 elif frame.typeid == FRAME_TYPE_COMMAND_DATA:
769 if not entry['expectingdata']:
829 if not entry['expectingdata']:
@@ -776,50 +836,10 b' class serverreactor(object):'
776 entry['data'] = util.bytesio()
836 entry['data'] = util.bytesio()
777
837
778 return self._handlecommanddataframe(frame, entry)
838 return self._handlecommanddataframe(frame, entry)
779
839 else:
780 def _handlecommandargsframe(self, frame, entry):
781 # The frame and state of command should have already been validated.
782 assert frame.typeid == FRAME_TYPE_COMMAND_ARGUMENT
783
784 offset = 0
785 namesize, valuesize = ARGUMENT_FRAME_HEADER.unpack_from(frame.payload)
786 offset += ARGUMENT_FRAME_HEADER.size
787
788 # The argument name MUST fit inside the frame.
789 argname = bytes(frame.payload[offset:offset + namesize])
790 offset += namesize
791
792 if len(argname) != namesize:
793 self._state = 'errored'
840 self._state = 'errored'
794 return self._makeerrorresult(_('malformed argument frame: '
841 return self._makeerrorresult(_(
795 'partial argument name'))
842 'received unexpected frame type: %d') % frame.typeid)
796
797 argvalue = bytes(frame.payload[offset:])
798
799 # Argument value spans multiple frames. Record our active state
800 # and wait for the next frame.
801 if frame.flags & FLAG_COMMAND_ARGUMENT_CONTINUATION:
802 raise error.ProgrammingError('not yet implemented')
803
804 # Common case: the argument value is completely contained in this
805 # frame.
806
807 if len(argvalue) != valuesize:
808 self._state = 'errored'
809 return self._makeerrorresult(_('malformed argument frame: '
810 'partial argument value'))
811
812 entry['args'][argname] = argvalue
813
814 if frame.flags & FLAG_COMMAND_ARGUMENT_EOA:
815 if entry['expectingdata']:
816 # TODO signal request to run a command once we don't
817 # buffer data frames.
818 return self._makewantframeresult()
819 else:
820 return self._makeruncommandresult(frame.requestid)
821 else:
822 return self._makewantframeresult()
823
843
824 def _handlecommanddataframe(self, frame, entry):
844 def _handlecommanddataframe(self, frame, entry):
825 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
845 assert frame.typeid == FRAME_TYPE_COMMAND_DATA
@@ -36,7 +36,7 b' HTTP_OK = 200'
36 HGTYPE = 'application/mercurial-0.1'
36 HGTYPE = 'application/mercurial-0.1'
37 HGTYPE2 = 'application/mercurial-0.2'
37 HGTYPE2 = 'application/mercurial-0.2'
38 HGERRTYPE = 'application/hg-error'
38 HGERRTYPE = 'application/hg-error'
39 FRAMINGTYPE = b'application/mercurial-exp-framing-0002'
39 FRAMINGTYPE = b'application/mercurial-exp-framing-0003'
40
40
41 HTTPV2 = wireprototypes.HTTPV2
41 HTTPV2 = wireprototypes.HTTPV2
42 SSHV1 = wireprototypes.SSHV1
42 SSHV1 = wireprototypes.SSHV1
@@ -1,5 +1,5 b''
1 $ HTTPV2=exp-http-v2-0001
1 $ HTTPV2=exp-http-v2-0001
2 $ MEDIATYPE=application/mercurial-exp-framing-0002
2 $ MEDIATYPE=application/mercurial-exp-framing-0003
3
3
4 $ send() {
4 $ send() {
5 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
5 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
@@ -122,7 +122,7 b' Missing Accept header results in 406'
122 s> Content-Type: text/plain\r\n
122 s> Content-Type: text/plain\r\n
123 s> Content-Length: 85\r\n
123 s> Content-Length: 85\r\n
124 s> \r\n
124 s> \r\n
125 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
125 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0003\n
126
126
127 Bad Accept header results in 406
127 Bad Accept header results in 406
128
128
@@ -145,7 +145,7 b' Bad Accept header results in 406'
145 s> Content-Type: text/plain\r\n
145 s> Content-Type: text/plain\r\n
146 s> Content-Length: 85\r\n
146 s> Content-Length: 85\r\n
147 s> \r\n
147 s> \r\n
148 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0002\n
148 s> client MUST specify Accept header with value: application/mercurial-exp-framing-0003\n
149
149
150 Bad Content-Type header results in 415
150 Bad Content-Type header results in 415
151
151
@@ -158,7 +158,7 b' Bad Content-Type header results in 415'
158 using raw connection to peer
158 using raw connection to peer
159 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
159 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
160 s> Accept-Encoding: identity\r\n
160 s> Accept-Encoding: identity\r\n
161 s> accept: application/mercurial-exp-framing-0002\r\n
161 s> accept: application/mercurial-exp-framing-0003\r\n
162 s> content-type: badmedia\r\n
162 s> content-type: badmedia\r\n
163 s> user-agent: test\r\n
163 s> user-agent: test\r\n
164 s> host: $LOCALIP:$HGPORT\r\n (glob)
164 s> host: $LOCALIP:$HGPORT\r\n (glob)
@@ -170,7 +170,7 b' Bad Content-Type header results in 415'
170 s> Content-Type: text/plain\r\n
170 s> Content-Type: text/plain\r\n
171 s> Content-Length: 88\r\n
171 s> Content-Length: 88\r\n
172 s> \r\n
172 s> \r\n
173 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0002\n
173 s> client MUST send Content-Type header with value: application/mercurial-exp-framing-0003\n
174
174
175 Request to read-only command works out of the box
175 Request to read-only command works out of the box
176
176
@@ -179,23 +179,23 b' Request to read-only command works out o'
179 > accept: $MEDIATYPE
179 > accept: $MEDIATYPE
180 > content-type: $MEDIATYPE
180 > content-type: $MEDIATYPE
181 > user-agent: test
181 > user-agent: test
182 > frame 1 1 stream-begin command-name eos customreadonly
182 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
183 > EOF
183 > EOF
184 using raw connection to peer
184 using raw connection to peer
185 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
185 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
186 s> Accept-Encoding: identity\r\n
186 s> Accept-Encoding: identity\r\n
187 s> accept: application/mercurial-exp-framing-0002\r\n
187 s> *\r\n (glob)
188 s> content-type: application/mercurial-exp-framing-0002\r\n
188 s> content-type: application/mercurial-exp-framing-0003\r\n
189 s> user-agent: test\r\n
189 s> user-agent: test\r\n
190 s> *\r\n (glob)
190 s> content-length: 29\r\n
191 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 s> host: $LOCALIP:$HGPORT\r\n (glob)
192 s> \r\n
192 s> \r\n
193 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly
193 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
194 s> makefile('rb', None)
194 s> makefile('rb', None)
195 s> HTTP/1.1 200 OK\r\n
195 s> HTTP/1.1 200 OK\r\n
196 s> Server: testing stub value\r\n
196 s> Server: testing stub value\r\n
197 s> Date: $HTTP_DATE$\r\n
197 s> Date: $HTTP_DATE$\r\n
198 s> Content-Type: application/mercurial-exp-framing-0002\r\n
198 s> Content-Type: application/mercurial-exp-framing-0003\r\n
199 s> Transfer-Encoding: chunked\r\n
199 s> Transfer-Encoding: chunked\r\n
200 s> \r\n
200 s> \r\n
201 s> 25\r\n
201 s> 25\r\n
@@ -290,23 +290,23 b' Authorized request for valid read-write '
290 > user-agent: test
290 > user-agent: test
291 > accept: $MEDIATYPE
291 > accept: $MEDIATYPE
292 > content-type: $MEDIATYPE
292 > content-type: $MEDIATYPE
293 > frame 1 1 stream-begin command-name eos customreadonly
293 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
294 > EOF
294 > EOF
295 using raw connection to peer
295 using raw connection to peer
296 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
296 s> POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
297 s> Accept-Encoding: identity\r\n
297 s> Accept-Encoding: identity\r\n
298 s> accept: application/mercurial-exp-framing-0002\r\n
298 s> accept: application/mercurial-exp-framing-0003\r\n
299 s> content-type: application/mercurial-exp-framing-0002\r\n
299 s> content-type: application/mercurial-exp-framing-0003\r\n
300 s> user-agent: test\r\n
300 s> user-agent: test\r\n
301 s> content-length: 22\r\n
301 s> content-length: 29\r\n
302 s> host: $LOCALIP:$HGPORT\r\n (glob)
302 s> host: $LOCALIP:$HGPORT\r\n (glob)
303 s> \r\n
303 s> \r\n
304 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly
304 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
305 s> makefile('rb', None)
305 s> makefile('rb', None)
306 s> HTTP/1.1 200 OK\r\n
306 s> HTTP/1.1 200 OK\r\n
307 s> Server: testing stub value\r\n
307 s> Server: testing stub value\r\n
308 s> Date: $HTTP_DATE$\r\n
308 s> Date: $HTTP_DATE$\r\n
309 s> Content-Type: application/mercurial-exp-framing-0002\r\n
309 s> Content-Type: application/mercurial-exp-framing-0003\r\n
310 s> Transfer-Encoding: chunked\r\n
310 s> Transfer-Encoding: chunked\r\n
311 s> \r\n
311 s> \r\n
312 s> 25\r\n
312 s> 25\r\n
@@ -325,7 +325,7 b' Authorized request for unknown command i'
325 using raw connection to peer
325 using raw connection to peer
326 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
326 s> POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
327 s> Accept-Encoding: identity\r\n
327 s> Accept-Encoding: identity\r\n
328 s> accept: application/mercurial-exp-framing-0002\r\n
328 s> accept: application/mercurial-exp-framing-0003\r\n
329 s> user-agent: test\r\n
329 s> user-agent: test\r\n
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
330 s> host: $LOCALIP:$HGPORT\r\n (glob)
331 s> \r\n
331 s> \r\n
@@ -382,32 +382,26 b' Command frames can be reflected via debu'
382 > accept: $MEDIATYPE
382 > accept: $MEDIATYPE
383 > content-type: $MEDIATYPE
383 > content-type: $MEDIATYPE
384 > user-agent: test
384 > user-agent: test
385 > frame 1 1 stream-begin command-name have-args command1
385 > frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
386 > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1
387 > frame 1 1 0 command-argument eoa \x04\x00\x03\x00bar1val
388 > EOF
386 > EOF
389 using raw connection to peer
387 using raw connection to peer
390 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
388 s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
391 s> Accept-Encoding: identity\r\n
389 s> Accept-Encoding: identity\r\n
392 s> accept: application/mercurial-exp-framing-0002\r\n
390 s> accept: application/mercurial-exp-framing-0003\r\n
393 s> content-type: application/mercurial-exp-framing-0002\r\n
391 s> content-type: application/mercurial-exp-framing-0003\r\n
394 s> user-agent: test\r\n
392 s> user-agent: test\r\n
395 s> content-length: 54\r\n
393 s> content-length: 47\r\n
396 s> host: $LOCALIP:$HGPORT\r\n (glob)
394 s> host: $LOCALIP:$HGPORT\r\n (glob)
397 s> \r\n
395 s> \r\n
398 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
396 s> '\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1
399 s> makefile('rb', None)
397 s> makefile('rb', None)
400 s> HTTP/1.1 200 OK\r\n
398 s> HTTP/1.1 200 OK\r\n
401 s> Server: testing stub value\r\n
399 s> Server: testing stub value\r\n
402 s> Date: $HTTP_DATE$\r\n
400 s> Date: $HTTP_DATE$\r\n
403 s> Content-Type: text/plain\r\n
401 s> Content-Type: text/plain\r\n
404 s> Content-Length: 322\r\n
402 s> Content-Length: 205\r\n
405 s> \r\n
403 s> \r\n
406 s> received: 1 2 1 command1\n
404 s> received: 1 1 1 \xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1\n
407 s> ["wantframe", {"state": "command-receiving"}]\n
408 s> received: 2 0 1 \x03\x00\x04\x00fooval1\n
409 s> ["wantframe", {"state": "command-receiving"}]\n
410 s> received: 2 2 1 \x04\x00\x03\x00bar1val\n
411 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
405 s> ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
412 s> received: <no frame>\n
406 s> received: <no frame>\n
413 s> {"action": "noop"}
407 s> {"action": "noop"}
@@ -419,27 +413,30 b' Multiple requests to regular command URL'
419 > accept: $MEDIATYPE
413 > accept: $MEDIATYPE
420 > content-type: $MEDIATYPE
414 > content-type: $MEDIATYPE
421 > user-agent: test
415 > user-agent: test
422 > frame 1 1 stream-begin command-name eos customreadonly
416 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
423 > frame 3 1 0 command-name eos customreadonly
424 > EOF
417 > EOF
425 using raw connection to peer
418 using raw connection to peer
426 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
419 s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
427 s> Accept-Encoding: identity\r\n
420 s> Accept-Encoding: identity\r\n
428 s> accept: application/mercurial-exp-framing-0002\r\n
421 s> accept: application/mercurial-exp-framing-0003\r\n
429 s> content-type: application/mercurial-exp-framing-0002\r\n
422 s> content-type: application/mercurial-exp-framing-0003\r\n
430 s> user-agent: test\r\n
423 s> user-agent: test\r\n
431 s> content-length: 44\r\n
424 s> content-length: 29\r\n
432 s> host: $LOCALIP:$HGPORT\r\n (glob)
425 s> host: $LOCALIP:$HGPORT\r\n (glob)
433 s> \r\n
426 s> \r\n
434 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
427 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly
435 s> makefile('rb', None)
428 s> makefile('rb', None)
436 s> HTTP/1.1 200 OK\r\n
429 s> HTTP/1.1 200 OK\r\n
437 s> Server: testing stub value\r\n
430 s> Server: testing stub value\r\n
438 s> Date: $HTTP_DATE$\r\n
431 s> Date: $HTTP_DATE$\r\n
439 s> Content-Type: text/plain\r\n
432 s> Content-Type: application/mercurial-exp-framing-0003\r\n
440 s> Content-Length: 46\r\n
433 s> Transfer-Encoding: chunked\r\n
441 s> \r\n
434 s> \r\n
442 s> multiple commands cannot be issued to this URL
435 s> 25\r\n
436 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
437 s> \r\n
438 s> 0\r\n
439 s> \r\n
443
440
444 Multiple requests to "multirequest" URL are allowed
441 Multiple requests to "multirequest" URL are allowed
445
442
@@ -448,27 +445,27 b' Multiple requests to "multirequest" URL '
448 > accept: $MEDIATYPE
445 > accept: $MEDIATYPE
449 > content-type: $MEDIATYPE
446 > content-type: $MEDIATYPE
450 > user-agent: test
447 > user-agent: test
451 > frame 1 1 stream-begin command-name eos customreadonly
448 > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
452 > frame 3 1 0 command-name eos customreadonly
449 > frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
453 > EOF
450 > EOF
454 using raw connection to peer
451 using raw connection to peer
455 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
452 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
456 s> Accept-Encoding: identity\r\n
453 s> Accept-Encoding: identity\r\n
457 s> accept: application/mercurial-exp-framing-0002\r\n
454 s> *\r\n (glob)
458 s> content-type: application/mercurial-exp-framing-0002\r\n
455 s> *\r\n (glob)
459 s> user-agent: test\r\n
456 s> user-agent: test\r\n
460 s> *\r\n (glob)
457 s> content-length: 58\r\n
461 s> host: $LOCALIP:$HGPORT\r\n (glob)
458 s> host: $LOCALIP:$HGPORT\r\n (glob)
462 s> \r\n
459 s> \r\n
463 s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly
460 s> \x15\x00\x00\x01\x00\x01\x01\x11\xa1DnameNcustomreadonly\x15\x00\x00\x03\x00\x01\x00\x11\xa1DnameNcustomreadonly
464 s> makefile('rb', None)
461 s> makefile('rb', None)
465 s> HTTP/1.1 200 OK\r\n
462 s> HTTP/1.1 200 OK\r\n
466 s> Server: testing stub value\r\n
463 s> Server: testing stub value\r\n
467 s> Date: $HTTP_DATE$\r\n
464 s> Date: $HTTP_DATE$\r\n
468 s> Content-Type: application/mercurial-exp-framing-0002\r\n
465 s> Content-Type: application/mercurial-exp-framing-0003\r\n
469 s> Transfer-Encoding: chunked\r\n
466 s> Transfer-Encoding: chunked\r\n
470 s> \r\n
467 s> \r\n
471 s> *\r\n (glob)
468 s> 25\r\n
472 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
469 s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response
473 s> \r\n
470 s> \r\n
474 s> 25\r\n
471 s> 25\r\n
@@ -484,36 +481,35 b' Interleaved requests to "multirequest" a'
484 > accept: $MEDIATYPE
481 > accept: $MEDIATYPE
485 > content-type: $MEDIATYPE
482 > content-type: $MEDIATYPE
486 > user-agent: test
483 > user-agent: test
487 > frame 1 1 stream-begin command-name have-args listkeys
484 > frame 1 1 stream-begin command-request new|more \xa2Dargs\xa1Inamespace
488 > frame 3 1 0 command-name have-args listkeys
485 > frame 3 1 0 command-request new|more \xa2Dargs\xa1Inamespace
489 > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks
486 > frame 3 1 0 command-request continuation JnamespacesDnameHlistkeys
490 > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces
487 > frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
491 > EOF
488 > EOF
492 using raw connection to peer
489 using raw connection to peer
493 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
490 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
494 s> Accept-Encoding: identity\r\n
491 s> Accept-Encoding: identity\r\n
495 s> accept: application/mercurial-exp-framing-0002\r\n
492 s> accept: application/mercurial-exp-framing-0003\r\n
496 s> content-type: application/mercurial-exp-framing-0002\r\n
493 s> content-type: application/mercurial-exp-framing-0003\r\n
497 s> user-agent: test\r\n
494 s> user-agent: test\r\n
498 s> content-length: 93\r\n
495 s> content-length: 115\r\n
499 s> host: $LOCALIP:$HGPORT\r\n (glob)
496 s> host: $LOCALIP:$HGPORT\r\n (glob)
500 s> \r\n
497 s> \r\n
501 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
498 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
502 s> \x00namespacenamespaces
503 s> makefile('rb', None)
499 s> makefile('rb', None)
504 s> HTTP/1.1 200 OK\r\n
500 s> HTTP/1.1 200 OK\r\n
505 s> Server: testing stub value\r\n
501 s> Server: testing stub value\r\n
506 s> Date: $HTTP_DATE$\r\n
502 s> Date: $HTTP_DATE$\r\n
507 s> Content-Type: application/mercurial-exp-framing-0002\r\n
503 s> Content-Type: application/mercurial-exp-framing-0003\r\n
508 s> Transfer-Encoding: chunked\r\n
504 s> Transfer-Encoding: chunked\r\n
509 s> \r\n
505 s> \r\n
506 s> 26\r\n
507 s> \x1e\x00\x00\x03\x00\x02\x01Bbookmarks \n
508 s> namespaces \n
509 s> phases
510 s> \r\n
510 s> 8\r\n
511 s> 8\r\n
511 s> \x00\x00\x00\x03\x00\x02\x01B
512 s> \x00\x00\x00\x01\x00\x02\x00B
512 s> \r\n
513 s> 26\r\n
514 s> \x1e\x00\x00\x01\x00\x02\x00Bbookmarks \n
515 s> namespaces \n
516 s> phases
517 s> \r\n
513 s> \r\n
518 s> 0\r\n
514 s> 0\r\n
519 s> \r\n
515 s> \r\n
@@ -540,18 +536,18 b' Attempting to run a read-write command v'
540 > accept: $MEDIATYPE
536 > accept: $MEDIATYPE
541 > content-type: $MEDIATYPE
537 > content-type: $MEDIATYPE
542 > user-agent: test
538 > user-agent: test
543 > frame 1 1 stream-begin command-name eos unbundle
539 > frame 1 1 stream-begin command-request new cbor:{b'name': b'unbundle'}
544 > EOF
540 > EOF
545 using raw connection to peer
541 using raw connection to peer
546 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
542 s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
547 s> Accept-Encoding: identity\r\n
543 s> Accept-Encoding: identity\r\n
548 s> accept: application/mercurial-exp-framing-0002\r\n
544 s> accept: application/mercurial-exp-framing-0003\r\n
549 s> content-type: application/mercurial-exp-framing-0002\r\n
545 s> content-type: application/mercurial-exp-framing-0003\r\n
550 s> user-agent: test\r\n
546 s> user-agent: test\r\n
551 s> content-length: 16\r\n
547 s> content-length: 23\r\n
552 s> host: $LOCALIP:$HGPORT\r\n (glob)
548 s> host: $LOCALIP:$HGPORT\r\n (glob)
553 s> \r\n
549 s> \r\n
554 s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle
550 s> \x0f\x00\x00\x01\x00\x01\x01\x11\xa1DnameHunbundle
555 s> makefile('rb', None)
551 s> makefile('rb', None)
556 s> HTTP/1.1 403 Forbidden\r\n
552 s> HTTP/1.1 403 Forbidden\r\n
557 s> Server: testing stub value\r\n
553 s> Server: testing stub value\r\n
@@ -2,6 +2,9 b' from __future__ import absolute_import, '
2
2
3 import unittest
3 import unittest
4
4
5 from mercurial.thirdparty import (
6 cbor,
7 )
5 from mercurial import (
8 from mercurial import (
6 util,
9 util,
7 wireprotoframing as framing,
10 wireprotoframing as framing,
@@ -96,7 +99,8 b' class FrameTests(unittest.TestCase):'
96 frames = list(framing.createcommandframes(stream, 1, b'command',
99 frames = list(framing.createcommandframes(stream, 1, b'command',
97 {}, data))
100 {}, data))
98 self.assertEqual(frames, [
101 self.assertEqual(frames, [
99 ffs(b'1 1 stream-begin command-name have-data command'),
102 ffs(b'1 1 stream-begin command-request new|have-data '
103 b"cbor:{b'name': b'command'}"),
100 ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
104 ffs(b'1 1 0 command-data continuation %s' % data.getvalue()),
101 ffs(b'1 1 0 command-data eos ')
105 ffs(b'1 1 0 command-data eos ')
102 ])
106 ])
@@ -108,7 +112,8 b' class FrameTests(unittest.TestCase):'
108 frames = list(framing.createcommandframes(stream, 1, b'command', {},
112 frames = list(framing.createcommandframes(stream, 1, b'command', {},
109 data))
113 data))
110 self.assertEqual(frames, [
114 self.assertEqual(frames, [
111 ffs(b'1 1 stream-begin command-name have-data command'),
115 ffs(b'1 1 stream-begin command-request new|have-data '
116 b"cbor:{b'name': b'command'}"),
112 ffs(b'1 1 0 command-data continuation %s' % (
117 ffs(b'1 1 0 command-data continuation %s' % (
113 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
118 b'x' * framing.DEFAULT_MAX_FRAME_SIZE)),
114 ffs(b'1 1 0 command-data eos x'),
119 ffs(b'1 1 0 command-data eos x'),
@@ -125,10 +130,9 b' class FrameTests(unittest.TestCase):'
125 }, data))
130 }, data))
126
131
127 self.assertEqual(frames, [
132 self.assertEqual(frames, [
128 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
133 ffs(b'1 1 stream-begin command-request new|have-data '
129 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'),
134 b"cbor:{b'name': b'command', b'args': {b'key1': b'key1value', "
130 ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'),
135 b"b'key2': b'key2value', b'key3': b'key3value'}}"),
131 ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'),
132 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
136 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
133 ])
137 ])
134
138
@@ -286,10 +290,9 b' class ServerReactorTests(unittest.TestCa'
286 stream = framing.stream(1)
290 stream = framing.stream(1)
287 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
291 results = list(sendcommandframes(reactor, stream, 41, b'mycommand',
288 {b'foo': b'bar'}))
292 {b'foo': b'bar'}))
289 self.assertEqual(len(results), 2)
293 self.assertEqual(len(results), 1)
290 self.assertaction(results[0], 'wantframe')
294 self.assertaction(results[0], 'runcommand')
291 self.assertaction(results[1], 'runcommand')
295 self.assertEqual(results[0][1], {
292 self.assertEqual(results[1][1], {
293 'requestid': 41,
296 'requestid': 41,
294 'command': b'mycommand',
297 'command': b'mycommand',
295 'args': {b'foo': b'bar'},
298 'args': {b'foo': b'bar'},
@@ -301,11 +304,9 b' class ServerReactorTests(unittest.TestCa'
301 stream = framing.stream(1)
304 stream = framing.stream(1)
302 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
305 results = list(sendcommandframes(reactor, stream, 1, b'mycommand',
303 {b'foo': b'bar', b'biz': b'baz'}))
306 {b'foo': b'bar', b'biz': b'baz'}))
304 self.assertEqual(len(results), 3)
307 self.assertEqual(len(results), 1)
305 self.assertaction(results[0], 'wantframe')
308 self.assertaction(results[0], 'runcommand')
306 self.assertaction(results[1], 'wantframe')
309 self.assertEqual(results[0][1], {
307 self.assertaction(results[2], 'runcommand')
308 self.assertEqual(results[2][1], {
309 'requestid': 1,
310 'requestid': 1,
310 'command': b'mycommand',
311 'command': b'mycommand',
311 'args': {b'foo': b'bar', b'biz': b'baz'},
312 'args': {b'foo': b'bar', b'biz': b'baz'},
@@ -329,7 +330,8 b' class ServerReactorTests(unittest.TestCa'
329
330
330 def testmultipledataframes(self):
331 def testmultipledataframes(self):
331 frames = [
332 frames = [
332 ffs(b'1 1 stream-begin command-name have-data mycommand'),
333 ffs(b'1 1 stream-begin command-request new|have-data '
334 b"cbor:{b'name': b'mycommand'}"),
333 ffs(b'1 1 0 command-data continuation data1'),
335 ffs(b'1 1 0 command-data continuation data1'),
334 ffs(b'1 1 0 command-data continuation data2'),
336 ffs(b'1 1 0 command-data continuation data2'),
335 ffs(b'1 1 0 command-data eos data3'),
337 ffs(b'1 1 0 command-data eos data3'),
@@ -350,9 +352,9 b' class ServerReactorTests(unittest.TestCa'
350
352
351 def testargumentanddata(self):
353 def testargumentanddata(self):
352 frames = [
354 frames = [
353 ffs(b'1 1 stream-begin command-name have-args|have-data command'),
355 ffs(b'1 1 stream-begin command-request new|have-data '
354 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'),
356 b"cbor:{b'name': b'command', b'args': {b'key': b'val',"
355 ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'),
357 b"b'foo': b'bar'}}"),
356 ffs(b'1 1 0 command-data continuation value1'),
358 ffs(b'1 1 0 command-data continuation value1'),
357 ffs(b'1 1 0 command-data eos value2'),
359 ffs(b'1 1 0 command-data eos value2'),
358 ]
360 ]
@@ -371,76 +373,68 b' class ServerReactorTests(unittest.TestCa'
371 'data': b'value1value2',
373 'data': b'value1value2',
372 })
374 })
373
375
374 def testunexpectedcommandargument(self):
376 def testnewandcontinuation(self):
375 """Command argument frame when not running a command is an error."""
377 result = self._sendsingleframe(makereactor(),
376 result = self._sendsingleframe(
378 ffs(b'1 1 stream-begin command-request new|continuation '))
377 makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored'))
378 self.assertaction(result, 'error')
379 self.assertaction(result, 'error')
379 self.assertEqual(result[1], {
380 self.assertEqual(result[1], {
380 'message': b'expected command frame; got 2',
381 'message': b'received command request frame with both new and '
382 b'continuation flags set',
381 })
383 })
382
384
383 def testunexpectedcommandargumentreceiving(self):
385 def testneithernewnorcontinuation(self):
384 """Same as above but the command is receiving."""
386 result = self._sendsingleframe(makereactor(),
385 results = list(sendframes(makereactor(), [
387 ffs(b'1 1 stream-begin command-request 0 '))
386 ffs(b'1 1 stream-begin command-name have-data command'),
388 self.assertaction(result, 'error')
387 ffs(b'1 1 0 command-argument eoa ignored'),
389 self.assertEqual(result[1], {
388 ]))
390 'message': b'received command request frame with neither new nor '
389
391 b'continuation flags set',
390 self.assertaction(results[1], 'error')
391 self.assertEqual(results[1][1], {
392 'message': b'received command argument frame for request that is '
393 b'not expecting arguments: 1',
394 })
392 })
395
393
396 def testunexpectedcommanddata(self):
394 def testunexpectedcommanddata(self):
397 """Command argument frame when not running a command is an error."""
395 """Command data frame when not running a command is an error."""
398 result = self._sendsingleframe(
396 result = self._sendsingleframe(makereactor(),
399 makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored'))
397 ffs(b'1 1 stream-begin command-data 0 ignored'))
400 self.assertaction(result, 'error')
398 self.assertaction(result, 'error')
401 self.assertEqual(result[1], {
399 self.assertEqual(result[1], {
402 'message': b'expected command frame; got 3',
400 'message': b'expected command request frame; got 3',
403 })
401 })
404
402
405 def testunexpectedcommanddatareceiving(self):
403 def testunexpectedcommanddatareceiving(self):
406 """Same as above except the command is receiving."""
404 """Same as above except the command is receiving."""
407 results = list(sendframes(makereactor(), [
405 results = list(sendframes(makereactor(), [
408 ffs(b'1 1 stream-begin command-name have-args command'),
406 ffs(b'1 1 stream-begin command-request new|more '
407 b"cbor:{b'name': b'ignored'}"),
409 ffs(b'1 1 0 command-data eos ignored'),
408 ffs(b'1 1 0 command-data eos ignored'),
410 ]))
409 ]))
411
410
411 self.assertaction(results[0], 'wantframe')
412 self.assertaction(results[1], 'error')
412 self.assertaction(results[1], 'error')
413 self.assertEqual(results[1][1], {
413 self.assertEqual(results[1][1], {
414 'message': b'received command data frame for request that is not '
414 'message': b'received command data frame for request that is not '
415 b'expecting data: 1',
415 b'expecting data: 1',
416 })
416 })
417
417
418 def testmissingcommandframeflags(self):
419 """Command name frame must have flags set."""
420 result = self._sendsingleframe(
421 makereactor(), ffs(b'1 1 stream-begin command-name 0 command'))
422 self.assertaction(result, 'error')
423 self.assertEqual(result[1], {
424 'message': b'missing frame flags on command frame',
425 })
426
427 def testconflictingrequestidallowed(self):
418 def testconflictingrequestidallowed(self):
428 """Multiple fully serviced commands with same request ID is allowed."""
419 """Multiple fully serviced commands with same request ID is allowed."""
429 reactor = makereactor()
420 reactor = makereactor()
430 results = []
421 results = []
431 outstream = reactor.makeoutputstream()
422 outstream = reactor.makeoutputstream()
432 results.append(self._sendsingleframe(
423 results.append(self._sendsingleframe(
433 reactor, ffs(b'1 1 stream-begin command-name eos command')))
424 reactor, ffs(b'1 1 stream-begin command-request new '
425 b"cbor:{b'name': b'command'}")))
434 result = reactor.onbytesresponseready(outstream, 1, b'response1')
426 result = reactor.onbytesresponseready(outstream, 1, b'response1')
435 self.assertaction(result, 'sendframes')
427 self.assertaction(result, 'sendframes')
436 list(result[1]['framegen'])
428 list(result[1]['framegen'])
437 results.append(self._sendsingleframe(
429 results.append(self._sendsingleframe(
438 reactor, ffs(b'1 1 0 command-name eos command')))
430 reactor, ffs(b'1 1 stream-begin command-request new '
431 b"cbor:{b'name': b'command'}")))
439 result = reactor.onbytesresponseready(outstream, 1, b'response2')
432 result = reactor.onbytesresponseready(outstream, 1, b'response2')
440 self.assertaction(result, 'sendframes')
433 self.assertaction(result, 'sendframes')
441 list(result[1]['framegen'])
434 list(result[1]['framegen'])
442 results.append(self._sendsingleframe(
435 results.append(self._sendsingleframe(
443 reactor, ffs(b'1 1 0 command-name eos command')))
436 reactor, ffs(b'1 1 stream-begin command-request new '
437 b"cbor:{b'name': b'command'}")))
444 result = reactor.onbytesresponseready(outstream, 1, b'response3')
438 result = reactor.onbytesresponseready(outstream, 1, b'response3')
445 self.assertaction(result, 'sendframes')
439 self.assertaction(result, 'sendframes')
446 list(result[1]['framegen'])
440 list(result[1]['framegen'])
@@ -457,8 +451,10 b' class ServerReactorTests(unittest.TestCa'
457 def testconflictingrequestid(self):
451 def testconflictingrequestid(self):
458 """Request ID for new command matching in-flight command is illegal."""
452 """Request ID for new command matching in-flight command is illegal."""
459 results = list(sendframes(makereactor(), [
453 results = list(sendframes(makereactor(), [
460 ffs(b'1 1 stream-begin command-name have-args command'),
454 ffs(b'1 1 stream-begin command-request new|more '
461 ffs(b'1 1 0 command-name eos command'),
455 b"cbor:{b'name': b'command'}"),
456 ffs(b'1 1 0 command-request new '
457 b"cbor:{b'name': b'command1'}"),
462 ]))
458 ]))
463
459
464 self.assertaction(results[0], 'wantframe')
460 self.assertaction(results[0], 'wantframe')
@@ -468,13 +464,28 b' class ServerReactorTests(unittest.TestCa'
468 })
464 })
469
465
470 def testinterleavedcommands(self):
466 def testinterleavedcommands(self):
467 cbor1 = cbor.dumps({
468 b'name': b'command1',
469 b'args': {
470 b'foo': b'bar',
471 b'key1': b'val',
472 }
473 }, canonical=True)
474 cbor3 = cbor.dumps({
475 b'name': b'command3',
476 b'args': {
477 b'biz': b'baz',
478 b'key': b'val',
479 },
480 }, canonical=True)
481
471 results = list(sendframes(makereactor(), [
482 results = list(sendframes(makereactor(), [
472 ffs(b'1 1 stream-begin command-name have-args command1'),
483 ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]),
473 ffs(b'3 1 0 command-name have-args command3'),
484 ffs(b'3 1 0 command-request new|more %s' % cbor3[0:10]),
474 ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'),
485 ffs(b'1 1 0 command-request continuation|more %s' % cbor1[6:9]),
475 ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'),
486 ffs(b'3 1 0 command-request continuation|more %s' % cbor3[10:13]),
476 ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'),
487 ffs(b'3 1 0 command-request continuation %s' % cbor3[13:]),
477 ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'),
488 ffs(b'1 1 0 command-request continuation %s' % cbor1[9:]),
478 ]))
489 ]))
479
490
480 self.assertEqual([t[0] for t in results], [
491 self.assertEqual([t[0] for t in results], [
@@ -499,53 +510,14 b' class ServerReactorTests(unittest.TestCa'
499 'data': None,
510 'data': None,
500 })
511 })
501
512
502 def testmissingargumentframe(self):
503 # This test attempts to test behavior when reactor has an incomplete
504 # command request waiting on argument data. But it doesn't handle that
505 # scenario yet. So this test does nothing of value.
506 frames = [
507 ffs(b'1 1 stream-begin command-name have-args command'),
508 ]
509
510 results = list(sendframes(makereactor(), frames))
511 self.assertaction(results[0], 'wantframe')
512
513 def testincompleteargumentname(self):
514 """Argument frame with incomplete name."""
515 frames = [
516 ffs(b'1 1 stream-begin command-name have-args command1'),
517 ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'),
518 ]
519
520 results = list(sendframes(makereactor(), frames))
521 self.assertEqual(len(results), 2)
522 self.assertaction(results[0], 'wantframe')
523 self.assertaction(results[1], 'error')
524 self.assertEqual(results[1][1], {
525 'message': b'malformed argument frame: partial argument name',
526 })
527
528 def testincompleteargumentvalue(self):
529 """Argument frame with incomplete value."""
530 frames = [
531 ffs(b'1 1 stream-begin command-name have-args command'),
532 ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'),
533 ]
534
535 results = list(sendframes(makereactor(), frames))
536 self.assertEqual(len(results), 2)
537 self.assertaction(results[0], 'wantframe')
538 self.assertaction(results[1], 'error')
539 self.assertEqual(results[1][1], {
540 'message': b'malformed argument frame: partial argument value',
541 })
542
543 def testmissingcommanddataframe(self):
513 def testmissingcommanddataframe(self):
544 # The reactor doesn't currently handle partially received commands.
514 # The reactor doesn't currently handle partially received commands.
545 # So this test is failing to do anything with request 1.
515 # So this test is failing to do anything with request 1.
546 frames = [
516 frames = [
547 ffs(b'1 1 stream-begin command-name have-data command1'),
517 ffs(b'1 1 stream-begin command-request new|have-data '
548 ffs(b'3 1 0 command-name eos command2'),
518 b"cbor:{b'name': b'command1'}"),
519 ffs(b'3 1 0 command-request new '
520 b"cbor:{b'name': b'command2'}"),
549 ]
521 ]
550 results = list(sendframes(makereactor(), frames))
522 results = list(sendframes(makereactor(), frames))
551 self.assertEqual(len(results), 2)
523 self.assertEqual(len(results), 2)
@@ -554,7 +526,8 b' class ServerReactorTests(unittest.TestCa'
554
526
555 def testmissingcommanddataframeflags(self):
527 def testmissingcommanddataframeflags(self):
556 frames = [
528 frames = [
557 ffs(b'1 1 stream-begin command-name have-data command1'),
529 ffs(b'1 1 stream-begin command-request new|have-data '
530 b"cbor:{b'name': b'command1'}"),
558 ffs(b'1 1 0 command-data 0 data'),
531 ffs(b'1 1 0 command-data 0 data'),
559 ]
532 ]
560 results = list(sendframes(makereactor(), frames))
533 results = list(sendframes(makereactor(), frames))
@@ -568,9 +541,11 b' class ServerReactorTests(unittest.TestCa'
568 def testframefornonreceivingrequest(self):
541 def testframefornonreceivingrequest(self):
569 """Receiving a frame for a command that is not receiving is illegal."""
542 """Receiving a frame for a command that is not receiving is illegal."""
570 results = list(sendframes(makereactor(), [
543 results = list(sendframes(makereactor(), [
571 ffs(b'1 1 stream-begin command-name eos command1'),
544 ffs(b'1 1 stream-begin command-request new '
572 ffs(b'3 1 0 command-name have-data command3'),
545 b"cbor:{b'name': b'command1'}"),
573 ffs(b'5 1 0 command-argument eoa ignored'),
546 ffs(b'3 1 0 command-request new|have-data '
547 b"cbor:{b'name': b'command3'}"),
548 ffs(b'5 1 0 command-data eos ignored'),
574 ]))
549 ]))
575 self.assertaction(results[2], 'error')
550 self.assertaction(results[2], 'error')
576 self.assertEqual(results[2][1], {
551 self.assertEqual(results[2][1], {
@@ -705,21 +680,6 b' class ServerReactorTests(unittest.TestCa'
705 'message': b'request with ID 1 is already active',
680 'message': b'request with ID 1 is already active',
706 })
681 })
707
682
708 def testduplicaterequestargumentframe(self):
709 """Variant on above except we sent an argument frame instead of name."""
710 reactor = makereactor()
711 stream = framing.stream(1)
712 list(sendcommandframes(reactor, stream, 1, b'command', {}))
713 results = list(sendframes(reactor, [
714 ffs(b'3 1 stream-begin command-name have-args command'),
715 ffs(b'1 1 0 command-argument 0 ignored'),
716 ]))
717 self.assertaction(results[0], 'wantframe')
718 self.assertaction(results[1], 'error')
719 self.assertEqual(results[1][1], {
720 'message': 'received frame for request that is still active: 1',
721 })
722
723 def testduplicaterequestaftersend(self):
683 def testduplicaterequestaftersend(self):
724 """We can use a duplicate request ID after we've sent the response."""
684 """We can use a duplicate request ID after we've sent the response."""
725 reactor = makereactor()
685 reactor = makereactor()
General Comments 0
You need to be logged in to leave comments. Login now