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 |
|
|
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 |
|
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 |
|
925 | until all argument have been received. Servers MAY impose limits on the | |
938 |
|
|
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_ |
|
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- |
|
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_ |
|
60 | FLAG_COMMAND_REQUEST_NEW = 0x01 | |
63 | FLAG_COMMAND_NAME_HAVE_ARGS = 0x02 |
|
61 | FLAG_COMMAND_REQUEST_CONTINUATION = 0x02 | |
64 |
FLAG_COMMAND_ |
|
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' |
|
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_ |
|
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_ |
|
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_ |
|
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': |
|
702 | 'command': request[b'name'], | |
689 |
'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_ |
|
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 |
' |
|
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 |
|
|
773 | assert moreframes or expectingdata | |
731 |
|
|
774 | self._state = 'command-receiving' | |
732 |
|
|
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_ |
|
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(_( |
|
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-000 |
|
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-000 |
|
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-000 |
|
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-000 |
|
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-000 |
|
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-000 |
|
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 |
|
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-000 |
|
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> \x |
|
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-000 |
|
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 |
|
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-000 |
|
298 | s> accept: application/mercurial-exp-framing-0003\r\n | |
299 |
s> content-type: application/mercurial-exp-framing-000 |
|
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: 2 |
|
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> \x |
|
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-000 |
|
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-000 |
|
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-000 |
|
390 | s> accept: application/mercurial-exp-framing-0003\r\n | |
393 |
s> content-type: application/mercurial-exp-framing-000 |
|
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: |
|
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: |
|
402 | s> Content-Length: 205\r\n | |
405 | s> \r\n |
|
403 | s> \r\n | |
406 |
s> received: 1 |
|
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> {" |
|
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- |
|
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-000 |
|
421 | s> accept: application/mercurial-exp-framing-0003\r\n | |
429 |
s> content-type: application/mercurial-exp-framing-000 |
|
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: |
|
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> \x |
|
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: |
|
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- |
|
448 | > frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'} | |
452 |
> frame 3 1 0 command- |
|
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> \x |
|
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-000 |
|
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> |
|
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- |
|
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- |
|
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-000 |
|
492 | s> accept: application/mercurial-exp-framing-0003\r\n | |
496 |
s> content-type: application/mercurial-exp-framing-000 |
|
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: |
|
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-000 |
|
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\x0 |
|
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- |
|
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-000 |
|
544 | s> accept: application/mercurial-exp-framing-0003\r\n | |
549 |
s> content-type: application/mercurial-exp-framing-000 |
|
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: |
|
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> \x0 |
|
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- |
|
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- |
|
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- |
|
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), |
|
293 | self.assertEqual(len(results), 1) | |
290 |
self.assertaction(results[0], ' |
|
294 | self.assertaction(results[0], 'runcommand') | |
291 |
self.assert |
|
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), |
|
307 | self.assertEqual(len(results), 1) | |
305 |
self.assertaction(results[0], ' |
|
308 | self.assertaction(results[0], 'runcommand') | |
306 |
self.assert |
|
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- |
|
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- |
|
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 test |
|
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' |
|
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 a |
|
395 | """Command data frame when not running a command is an error.""" | |
398 | result = self._sendsingleframe( |
|
396 | result = self._sendsingleframe(makereactor(), | |
399 |
|
|
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- |
|
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- |
|
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 |
|
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 |
|
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- |
|
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- |
|
483 | ffs(b'1 1 stream-begin command-request new|more %s' % cbor1[0:6]), | |
473 |
ffs(b'3 1 0 command- |
|
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(b |
|
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- |
|
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- |
|
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- |
|
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' |
|
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