Show More
@@ -2777,22 +2777,23 b' def debugwireproto(ui, repo, path=None, ' | |||||
2777 | syntax. |
|
2777 | syntax. | |
2778 |
|
2778 | |||
2779 | A frame is composed as a type, flags, and payload. These can be parsed |
|
2779 | A frame is composed as a type, flags, and payload. These can be parsed | |
2780 | from a string of the form ``<requestid> <type> <flags> <payload>``. That is, |
|
2780 | from a string of the form: | |
2781 | 4 space-delimited strings. |
|
2781 | ||
2782 |
|
2782 | <request-id> <stream-id> <stream-flags> <type> <flags> <payload> | ||
2783 | ``payload`` is the simplest: it is evaluated as a Python byte string |
|
2783 | ||
2784 | literal. |
|
2784 | ``request-id`` and ``stream-id`` are integers defining the request and | |
2785 |
|
2785 | stream identifiers. | ||
2786 | ``requestid`` is an integer defining the request identifier. |
|
|||
2787 |
|
2786 | |||
2788 | ``type`` can be an integer value for the frame type or the string name |
|
2787 | ``type`` can be an integer value for the frame type or the string name | |
2789 | of the type. The strings are defined in ``wireprotoframing.py``. e.g. |
|
2788 | of the type. The strings are defined in ``wireprotoframing.py``. e.g. | |
2790 | ``command-name``. |
|
2789 | ``command-name``. | |
2791 |
|
2790 | |||
2792 |
``flags`` |
|
2791 | ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag | |
2793 |
(and there can be just one) can be an integer |
|
2792 | components. Each component (and there can be just one) can be an integer | |
2794 | specified frame type. Values are resolved to integers and then bitwise |
|
2793 | or a flag name for stream flags or frame flags, respectively. Values are | |
2795 | OR'd together. |
|
2794 | resolved to integers and then bitwise OR'd together. | |
|
2795 | ||||
|
2796 | ``payload`` is is evaluated as a Python byte string literal. | |||
2796 | """ |
|
2797 | """ | |
2797 | opts = pycompat.byteskwargs(opts) |
|
2798 | opts = pycompat.byteskwargs(opts) | |
2798 |
|
2799 |
@@ -489,28 +489,37 b' To operate the protocol, a bi-directiona' | |||||
489 | ordered sends and receives is required. That is, each peer has one pipe |
|
489 | ordered sends and receives is required. That is, each peer has one pipe | |
490 | for sending data and another for receiving. |
|
490 | for sending data and another for receiving. | |
491 |
|
491 | |||
|
492 | All data is read and written in atomic units called *frames*. These | |||
|
493 | are conceptually similar to TCP packets. Higher-level functionality | |||
|
494 | is built on the exchange and processing of frames. | |||
|
495 | ||||
|
496 | All frames are associated with a *stream*. A *stream* provides a | |||
|
497 | unidirectional grouping of frames. Streams facilitate two goals: | |||
|
498 | content encoding and parallelism. There is a dedicated section on | |||
|
499 | streams below. | |||
|
500 | ||||
492 | The protocol is request-response based: the client issues requests to |
|
501 | The protocol is request-response based: the client issues requests to | |
493 | the server, which issues replies to those requests. Server-initiated |
|
502 | the server, which issues replies to those requests. Server-initiated | |
494 | messaging is not currently supported, but this specification carves |
|
503 | messaging is not currently supported, but this specification carves | |
495 | out room to implement it. |
|
504 | out room to implement it. | |
496 |
|
505 | |||
497 | All data is read and written in atomic units called *frames*. These |
|
|||
498 | are conceptually similar to TCP packets. Higher-level functionality |
|
|||
499 | is built on the exchange and processing of frames. |
|
|||
500 |
|
||||
501 | All frames are associated with a numbered request. Frames can thus |
|
506 | All frames are associated with a numbered request. Frames can thus | |
502 | be logically grouped by their request ID. |
|
507 | be logically grouped by their request ID. | |
503 |
|
508 | |||
504 |
Frames begin with a |
|
509 | Frames begin with an 8 octet header followed by a variable length | |
505 | payload:: |
|
510 | payload:: | |
506 |
|
511 | |||
507 | +-----------------------------------------------+ |
|
512 | +------------------------------------------------+ | |
508 | | Length (24) | |
|
513 | | Length (24) | | |
509 |
+--------------------------------- |
|
514 | +--------------------------------+---------------+ | |
510 | | Request ID (16) | |
|
515 | | Request ID (16) | Stream ID (8) | | |
511 | +----------+-----------+----------+ |
|
516 | +------------------+-------------+---------------+ | |
512 |
| |
|
517 | | Stream Flags (8) | | |
513 | +==========+===========+========================================| |
|
518 | +-----------+------+ | |
|
519 | | Type (4) | | |||
|
520 | +-----------+ | |||
|
521 | | Flags (4) | | |||
|
522 | +===========+===================================================| | |||
514 | | Frame Payload (0...) ... |
|
523 | | Frame Payload (0...) ... | |
515 | +---------------------------------------------------------------+ |
|
524 | +---------------------------------------------------------------+ | |
516 |
|
525 | |||
@@ -518,7 +527,9 b' The length of the frame payload is expre' | |||||
518 | little endian integer. Values larger than 65535 MUST NOT be used unless |
|
527 | little endian integer. Values larger than 65535 MUST NOT be used unless | |
519 | given permission by the server as part of the negotiated capabilities |
|
528 | given permission by the server as part of the negotiated capabilities | |
520 | during the handshake. The frame header is not part of the advertised |
|
529 | during the handshake. The frame header is not part of the advertised | |
521 | frame length. |
|
530 | frame length. The payload length is the over-the-wire length. If there | |
|
531 | is content encoding applied to the payload as part of the frame's stream, | |||
|
532 | the length is the output of that content encoding, not the input. | |||
522 |
|
533 | |||
523 | The 16-bit ``Request ID`` field denotes the integer request identifier, |
|
534 | The 16-bit ``Request ID`` field denotes the integer request identifier, | |
524 | stored as an unsigned little endian integer. Odd numbered requests are |
|
535 | stored as an unsigned little endian integer. Odd numbered requests are | |
@@ -529,7 +540,16 b' response to client-initiated requests. I' | |||||
529 | start ordering request identifiers at ``1`` and ``0``, increment by |
|
540 | start ordering request identifiers at ``1`` and ``0``, increment by | |
530 | ``2``, and wrap around if all available numbers have been exhausted. |
|
541 | ``2``, and wrap around if all available numbers have been exhausted. | |
531 |
|
542 | |||
532 |
The |
|
543 | The 8-bit ``Stream ID`` field denotes the stream that the frame is | |
|
544 | associated with. Frames belonging to a stream may have content | |||
|
545 | encoding applied and the receiver may need to decode the raw frame | |||
|
546 | payload to obtain the original data. Odd numbered IDs are | |||
|
547 | client-initiated. Even numbered IDs are server-initiated. | |||
|
548 | ||||
|
549 | The 8-bit ``Stream Flags`` field defines stream processing semantics. | |||
|
550 | See the section on streams below. | |||
|
551 | ||||
|
552 | The 4-bit ``Type`` field denotes the type of frame being sent. | |||
533 |
|
553 | |||
534 | The 4-bit ``Flags`` field defines special, per-type attributes for |
|
554 | The 4-bit ``Flags`` field defines special, per-type attributes for | |
535 | the frame. |
|
555 | the frame. | |
@@ -720,6 +740,126 b' All textual data encoded in these frames' | |||||
720 | The last atom in the frame SHOULD end with a newline (``\n``). If it |
|
740 | The last atom in the frame SHOULD end with a newline (``\n``). If it | |
721 | doesn't, clients MAY add a newline to facilitate immediate printing. |
|
741 | doesn't, clients MAY add a newline to facilitate immediate printing. | |
722 |
|
742 | |||
|
743 | Stream Encoding Settings (``0x08``) | |||
|
744 | ----------------------------------- | |||
|
745 | ||||
|
746 | This frame type holds information defining the content encoding | |||
|
747 | settings for a *stream*. | |||
|
748 | ||||
|
749 | This frame type is likely consumed by the protocol layer and is not | |||
|
750 | passed on to applications. | |||
|
751 | ||||
|
752 | This frame type MUST ONLY occur on frames having the *Beginning of Stream* | |||
|
753 | ``Stream Flag`` set. | |||
|
754 | ||||
|
755 | The payload of this frame defines what content encoding has (possibly) | |||
|
756 | been applied to the payloads of subsequent frames in this stream. | |||
|
757 | ||||
|
758 | The payload begins with an 8-bit integer defining the length of the | |||
|
759 | encoding *profile*, followed by the string name of that profile, which | |||
|
760 | must be an ASCII string. All bytes that follow can be used by that | |||
|
761 | profile for supplemental settings definitions. See the section below | |||
|
762 | on defined encoding profiles. | |||
|
763 | ||||
|
764 | Stream States and Flags | |||
|
765 | ----------------------- | |||
|
766 | ||||
|
767 | Streams can be in two states: *open* and *closed*. An *open* stream | |||
|
768 | is active and frames attached to that stream could arrive at any time. | |||
|
769 | A *closed* stream is not active. If a frame attached to a *closed* | |||
|
770 | stream arrives, that frame MUST have an appropriate stream flag | |||
|
771 | set indicating beginning of stream. All streams are in the *closed* | |||
|
772 | state by default. | |||
|
773 | ||||
|
774 | The ``Stream Flags`` field denotes a set of bit flags for defining | |||
|
775 | the relationship of this frame within a stream. The following flags | |||
|
776 | are defined: | |||
|
777 | ||||
|
778 | 0x01 | |||
|
779 | Beginning of stream. The first frame in the stream MUST set this | |||
|
780 | flag. When received, the ``Stream ID`` this frame is attached to | |||
|
781 | becomes ``open``. | |||
|
782 | ||||
|
783 | 0x02 | |||
|
784 | End of stream. The last frame in a stream MUST set this flag. When | |||
|
785 | received, the ``Stream ID`` this frame is attached to becomes | |||
|
786 | ``closed``. Any content encoding context associated with this stream | |||
|
787 | can be destroyed after processing the payload of this frame. | |||
|
788 | ||||
|
789 | 0x04 | |||
|
790 | Apply content encoding. When set, any content encoding settings | |||
|
791 | defined by the stream should be applied when attempting to read | |||
|
792 | the frame. When not set, the frame payload isn't encoded. | |||
|
793 | ||||
|
794 | Streams | |||
|
795 | ------- | |||
|
796 | ||||
|
797 | Streams - along with ``Request IDs`` - facilitate grouping of frames. | |||
|
798 | But the purpose of each is quite different and the groupings they | |||
|
799 | constitute are independent. | |||
|
800 | ||||
|
801 | A ``Request ID`` is essentially a tag. It tells you which logical | |||
|
802 | request a frame is associated with. | |||
|
803 | ||||
|
804 | A *stream* is a sequence of frames grouped for the express purpose | |||
|
805 | of applying a stateful encoding or for denoting sub-groups of frames. | |||
|
806 | ||||
|
807 | Unlike ``Request ID``s which span the request and response, a stream | |||
|
808 | is unidirectional and stream IDs are independent from client to | |||
|
809 | server. | |||
|
810 | ||||
|
811 | There is no strict hierarchical relationship between ``Request IDs`` | |||
|
812 | and *streams*. A stream can contain frames having multiple | |||
|
813 | ``Request IDs``. Frames belonging to the same ``Request ID`` can | |||
|
814 | span multiple streams. | |||
|
815 | ||||
|
816 | One goal of streams is to facilitate content encoding. A stream can | |||
|
817 | define an encoding to be applied to frame payloads. For example, the | |||
|
818 | payload transmitted over the wire may contain output from a | |||
|
819 | zstandard compression operation and the receiving end may decompress | |||
|
820 | that payload to obtain the original data. | |||
|
821 | ||||
|
822 | The other goal of streams is to facilitate concurrent execution. For | |||
|
823 | example, a server could spawn 4 threads to service a request that can | |||
|
824 | be easily parallelized. Each of those 4 threads could write into its | |||
|
825 | own stream. Those streams could then in turn be delivered to 4 threads | |||
|
826 | on the receiving end, with each thread consuming its stream in near | |||
|
827 | isolation. The *main* thread on both ends merely does I/O and | |||
|
828 | encodes/decodes frame headers: the bulk of the work is done by worker | |||
|
829 | threads. | |||
|
830 | ||||
|
831 | In addition, since content encoding is defined per stream, each | |||
|
832 | *worker thread* could perform potentially CPU bound work concurrently | |||
|
833 | with other threads. This approach of applying encoding at the | |||
|
834 | sub-protocol / stream level eliminates a potential resource constraint | |||
|
835 | on the protocol stream as a whole (it is common for the throughput of | |||
|
836 | a compression engine to be smaller than the throughput of a network). | |||
|
837 | ||||
|
838 | Having multiple streams - each with their own encoding settings - also | |||
|
839 | facilitates the use of advanced data compression techniques. For | |||
|
840 | example, a transmitter could see that it is generating data faster | |||
|
841 | and slower than the receiving end is consuming it and adjust its | |||
|
842 | compression settings to trade CPU for compression ratio accordingly. | |||
|
843 | ||||
|
844 | While streams can define a content encoding, not all frames within | |||
|
845 | that stream must use that content encoding. This can be useful when | |||
|
846 | data is being served from caches and being derived dynamically. A | |||
|
847 | cache could pre-compressed data so the server doesn't have to | |||
|
848 | recompress it. The ability to pick and choose which frames are | |||
|
849 | compressed allows servers to easily send data to the wire without | |||
|
850 | involving potentially expensive encoding overhead. | |||
|
851 | ||||
|
852 | Content Encoding Profiles | |||
|
853 | ------------------------- | |||
|
854 | ||||
|
855 | Streams can have named content encoding *profiles* associated with | |||
|
856 | them. A profile defines a shared understanding of content encoding | |||
|
857 | settings and behavior. | |||
|
858 | ||||
|
859 | The following profiles are defined: | |||
|
860 | ||||
|
861 | TBD | |||
|
862 | ||||
723 | Issuing Commands |
|
863 | Issuing Commands | |
724 | ---------------- |
|
864 | ---------------- | |
725 |
|
865 |
@@ -25,15 +25,26 b' from .utils import (' | |||||
25 | stringutil, |
|
25 | stringutil, | |
26 | ) |
|
26 | ) | |
27 |
|
27 | |||
28 |
FRAME_HEADER_SIZE = |
|
28 | FRAME_HEADER_SIZE = 8 | |
29 | DEFAULT_MAX_FRAME_SIZE = 32768 |
|
29 | DEFAULT_MAX_FRAME_SIZE = 32768 | |
30 |
|
30 | |||
|
31 | STREAM_FLAG_BEGIN_STREAM = 0x01 | |||
|
32 | STREAM_FLAG_END_STREAM = 0x02 | |||
|
33 | STREAM_FLAG_ENCODING_APPLIED = 0x04 | |||
|
34 | ||||
|
35 | STREAM_FLAGS = { | |||
|
36 | b'stream-begin': STREAM_FLAG_BEGIN_STREAM, | |||
|
37 | b'stream-end': STREAM_FLAG_END_STREAM, | |||
|
38 | b'encoded': STREAM_FLAG_ENCODING_APPLIED, | |||
|
39 | } | |||
|
40 | ||||
31 | FRAME_TYPE_COMMAND_NAME = 0x01 |
|
41 | FRAME_TYPE_COMMAND_NAME = 0x01 | |
32 | FRAME_TYPE_COMMAND_ARGUMENT = 0x02 |
|
42 | FRAME_TYPE_COMMAND_ARGUMENT = 0x02 | |
33 | FRAME_TYPE_COMMAND_DATA = 0x03 |
|
43 | FRAME_TYPE_COMMAND_DATA = 0x03 | |
34 | FRAME_TYPE_BYTES_RESPONSE = 0x04 |
|
44 | FRAME_TYPE_BYTES_RESPONSE = 0x04 | |
35 | FRAME_TYPE_ERROR_RESPONSE = 0x05 |
|
45 | FRAME_TYPE_ERROR_RESPONSE = 0x05 | |
36 | FRAME_TYPE_TEXT_OUTPUT = 0x06 |
|
46 | FRAME_TYPE_TEXT_OUTPUT = 0x06 | |
|
47 | FRAME_TYPE_STREAM_SETTINGS = 0x08 | |||
37 |
|
48 | |||
38 | FRAME_TYPES = { |
|
49 | FRAME_TYPES = { | |
39 | b'command-name': FRAME_TYPE_COMMAND_NAME, |
|
50 | b'command-name': FRAME_TYPE_COMMAND_NAME, | |
@@ -42,6 +53,7 b' FRAME_TYPES = {' | |||||
42 | b'bytes-response': FRAME_TYPE_BYTES_RESPONSE, |
|
53 | b'bytes-response': FRAME_TYPE_BYTES_RESPONSE, | |
43 | b'error-response': FRAME_TYPE_ERROR_RESPONSE, |
|
54 | b'error-response': FRAME_TYPE_ERROR_RESPONSE, | |
44 | b'text-output': FRAME_TYPE_TEXT_OUTPUT, |
|
55 | b'text-output': FRAME_TYPE_TEXT_OUTPUT, | |
|
56 | b'stream-settings': FRAME_TYPE_STREAM_SETTINGS, | |||
45 | } |
|
57 | } | |
46 |
|
58 | |||
47 | FLAG_COMMAND_NAME_EOS = 0x01 |
|
59 | FLAG_COMMAND_NAME_EOS = 0x01 | |
@@ -94,6 +106,7 b' FRAME_TYPE_FLAGS = {' | |||||
94 | FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE, |
|
106 | FRAME_TYPE_BYTES_RESPONSE: FLAGS_BYTES_RESPONSE, | |
95 | FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE, |
|
107 | FRAME_TYPE_ERROR_RESPONSE: FLAGS_ERROR_RESPONSE, | |
96 | FRAME_TYPE_TEXT_OUTPUT: {}, |
|
108 | FRAME_TYPE_TEXT_OUTPUT: {}, | |
|
109 | FRAME_TYPE_STREAM_SETTINGS: {}, | |||
97 | } |
|
110 | } | |
98 |
|
111 | |||
99 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') |
|
112 | ARGUMENT_FRAME_HEADER = struct.Struct(r'<HH') | |
@@ -104,6 +117,8 b' class frameheader(object):' | |||||
104 |
|
117 | |||
105 | length = attr.ib() |
|
118 | length = attr.ib() | |
106 | requestid = attr.ib() |
|
119 | requestid = attr.ib() | |
|
120 | streamid = attr.ib() | |||
|
121 | streamflags = attr.ib() | |||
107 | typeid = attr.ib() |
|
122 | typeid = attr.ib() | |
108 | flags = attr.ib() |
|
123 | flags = attr.ib() | |
109 |
|
124 | |||
@@ -112,25 +127,29 b' class frame(object):' | |||||
112 | """Represents a parsed frame.""" |
|
127 | """Represents a parsed frame.""" | |
113 |
|
128 | |||
114 | requestid = attr.ib() |
|
129 | requestid = attr.ib() | |
|
130 | streamid = attr.ib() | |||
|
131 | streamflags = attr.ib() | |||
115 | typeid = attr.ib() |
|
132 | typeid = attr.ib() | |
116 | flags = attr.ib() |
|
133 | flags = attr.ib() | |
117 | payload = attr.ib() |
|
134 | payload = attr.ib() | |
118 |
|
135 | |||
119 | def makeframe(requestid, typeid, flags, payload): |
|
136 | def makeframe(requestid, streamid, streamflags, typeid, flags, payload): | |
120 | """Assemble a frame into a byte array.""" |
|
137 | """Assemble a frame into a byte array.""" | |
121 | # TODO assert size of payload. |
|
138 | # TODO assert size of payload. | |
122 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) |
|
139 | frame = bytearray(FRAME_HEADER_SIZE + len(payload)) | |
123 |
|
140 | |||
124 | # 24 bits length |
|
141 | # 24 bits length | |
125 | # 16 bits request id |
|
142 | # 16 bits request id | |
|
143 | # 8 bits stream id | |||
|
144 | # 8 bits stream flags | |||
126 | # 4 bits type |
|
145 | # 4 bits type | |
127 | # 4 bits flags |
|
146 | # 4 bits flags | |
128 |
|
147 | |||
129 | l = struct.pack(r'<I', len(payload)) |
|
148 | l = struct.pack(r'<I', len(payload)) | |
130 | frame[0:3] = l[0:3] |
|
149 | frame[0:3] = l[0:3] | |
131 | struct.pack_into(r'<H', frame, 3, requestid) |
|
150 | struct.pack_into(r'<HBB', frame, 3, requestid, streamid, streamflags) | |
132 |
frame[ |
|
151 | frame[7] = (typeid << 4) | flags | |
133 |
frame[ |
|
152 | frame[8:] = payload | |
134 |
|
153 | |||
135 | return frame |
|
154 | return frame | |
136 |
|
155 | |||
@@ -139,20 +158,30 b' def makeframefromhumanstring(s):' | |||||
139 |
|
158 | |||
140 | Strings have the form: |
|
159 | Strings have the form: | |
141 |
|
160 | |||
142 | <request-id> <type> <flags> <payload> |
|
161 | <request-id> <stream-id> <stream-flags> <type> <flags> <payload> | |
143 |
|
162 | |||
144 | This can be used by user-facing applications and tests for creating |
|
163 | This can be used by user-facing applications and tests for creating | |
145 | frames easily without having to type out a bunch of constants. |
|
164 | frames easily without having to type out a bunch of constants. | |
146 |
|
165 | |||
147 |
Request ID |
|
166 | Request ID and stream IDs are integers. | |
148 |
|
167 | |||
149 |
|
|
168 | Stream flags, frame type, and flags can be specified by integer or | |
|
169 | named constant. | |||
150 |
|
170 | |||
151 | Flags can be delimited by `|` to bitwise OR them together. |
|
171 | Flags can be delimited by `|` to bitwise OR them together. | |
152 | """ |
|
172 | """ | |
153 | requestid, frametype, frameflags, payload = s.split(b' ', 3) |
|
173 | fields = s.split(b' ', 5) | |
|
174 | requestid, streamid, streamflags, frametype, frameflags, payload = fields | |||
154 |
|
175 | |||
155 | requestid = int(requestid) |
|
176 | requestid = int(requestid) | |
|
177 | streamid = int(streamid) | |||
|
178 | ||||
|
179 | finalstreamflags = 0 | |||
|
180 | for flag in streamflags.split(b'|'): | |||
|
181 | if flag in STREAM_FLAGS: | |||
|
182 | finalstreamflags |= STREAM_FLAGS[flag] | |||
|
183 | else: | |||
|
184 | finalstreamflags |= int(flag) | |||
156 |
|
185 | |||
157 | if frametype in FRAME_TYPES: |
|
186 | if frametype in FRAME_TYPES: | |
158 | frametype = FRAME_TYPES[frametype] |
|
187 | frametype = FRAME_TYPES[frametype] | |
@@ -169,7 +198,8 b' def makeframefromhumanstring(s):' | |||||
169 |
|
198 | |||
170 | payload = stringutil.unescapestr(payload) |
|
199 | payload = stringutil.unescapestr(payload) | |
171 |
|
200 | |||
172 |
return makeframe(requestid=requestid, |
|
201 | return makeframe(requestid=requestid, streamid=streamid, | |
|
202 | streamflags=finalstreamflags, typeid=frametype, | |||
173 | flags=finalflags, payload=payload) |
|
203 | flags=finalflags, payload=payload) | |
174 |
|
204 | |||
175 | def parseheader(data): |
|
205 | def parseheader(data): | |
@@ -179,17 +209,21 b' def parseheader(data):' | |||||
179 | buffer is expected to be large enough to hold a full header. |
|
209 | buffer is expected to be large enough to hold a full header. | |
180 | """ |
|
210 | """ | |
181 | # 24 bits payload length (little endian) |
|
211 | # 24 bits payload length (little endian) | |
|
212 | # 16 bits request ID | |||
|
213 | # 8 bits stream ID | |||
|
214 | # 8 bits stream flags | |||
182 | # 4 bits frame type |
|
215 | # 4 bits frame type | |
183 | # 4 bits frame flags |
|
216 | # 4 bits frame flags | |
184 | # ... payload |
|
217 | # ... payload | |
185 | framelength = data[0] + 256 * data[1] + 16384 * data[2] |
|
218 | framelength = data[0] + 256 * data[1] + 16384 * data[2] | |
186 |
requestid = struct.unpack_from(r'<H', data, 3) |
|
219 | requestid, streamid, streamflags = struct.unpack_from(r'<HBB', data, 3) | |
187 |
typeflags = data[ |
|
220 | typeflags = data[7] | |
188 |
|
221 | |||
189 | frametype = (typeflags & 0xf0) >> 4 |
|
222 | frametype = (typeflags & 0xf0) >> 4 | |
190 | frameflags = typeflags & 0x0f |
|
223 | frameflags = typeflags & 0x0f | |
191 |
|
224 | |||
192 |
return frameheader(framelength, requestid, |
|
225 | return frameheader(framelength, requestid, streamid, streamflags, | |
|
226 | frametype, frameflags) | |||
193 |
|
227 | |||
194 | def readframe(fh): |
|
228 | def readframe(fh): | |
195 | """Read a unified framing protocol frame from a file object. |
|
229 | """Read a unified framing protocol frame from a file object. | |
@@ -216,7 +250,8 b' def readframe(fh):' | |||||
216 | raise error.Abort(_('frame length error: expected %d; got %d') % |
|
250 | raise error.Abort(_('frame length error: expected %d; got %d') % | |
217 | (h.length, len(payload))) |
|
251 | (h.length, len(payload))) | |
218 |
|
252 | |||
219 |
return frame(h.requestid, h.typeid, h.flags, |
|
253 | return frame(h.requestid, h.streamid, h.streamflags, h.typeid, h.flags, | |
|
254 | payload) | |||
220 |
|
255 | |||
221 | def createcommandframes(stream, requestid, cmd, args, datafh=None): |
|
256 | def createcommandframes(stream, requestid, cmd, args, datafh=None): | |
222 | """Create frames necessary to transmit a request to run a command. |
|
257 | """Create frames necessary to transmit a request to run a command. | |
@@ -398,12 +433,28 b' def createtextoutputframe(stream, reques' | |||||
398 | class stream(object): |
|
433 | class stream(object): | |
399 | """Represents a logical unidirectional series of frames.""" |
|
434 | """Represents a logical unidirectional series of frames.""" | |
400 |
|
435 | |||
|
436 | def __init__(self, streamid, active=False): | |||
|
437 | self.streamid = streamid | |||
|
438 | self._active = False | |||
|
439 | ||||
401 | def makeframe(self, requestid, typeid, flags, payload): |
|
440 | def makeframe(self, requestid, typeid, flags, payload): | |
402 | """Create a frame to be sent out over this stream. |
|
441 | """Create a frame to be sent out over this stream. | |
403 |
|
442 | |||
404 | Only returns the frame instance. Does not actually send it. |
|
443 | Only returns the frame instance. Does not actually send it. | |
405 | """ |
|
444 | """ | |
406 | return makeframe(requestid, typeid, flags, payload) |
|
445 | streamflags = 0 | |
|
446 | if not self._active: | |||
|
447 | streamflags |= STREAM_FLAG_BEGIN_STREAM | |||
|
448 | self._active = True | |||
|
449 | ||||
|
450 | return makeframe(requestid, self.streamid, streamflags, typeid, flags, | |||
|
451 | payload) | |||
|
452 | ||||
|
453 | def ensureserverstream(stream): | |||
|
454 | if stream.streamid % 2: | |||
|
455 | raise error.ProgrammingError('server should only write to even ' | |||
|
456 | 'numbered streams; %d is not even' % | |||
|
457 | stream.streamid) | |||
407 |
|
458 | |||
408 | class serverreactor(object): |
|
459 | class serverreactor(object): | |
409 | """Holds state of a server handling frame-based protocol requests. |
|
460 | """Holds state of a server handling frame-based protocol requests. | |
@@ -483,6 +534,8 b' class serverreactor(object):' | |||||
483 | self._deferoutput = deferoutput |
|
534 | self._deferoutput = deferoutput | |
484 | self._state = 'idle' |
|
535 | self._state = 'idle' | |
485 | self._bufferedframegens = [] |
|
536 | self._bufferedframegens = [] | |
|
537 | # stream id -> stream instance for all active streams from the client. | |||
|
538 | self._incomingstreams = {} | |||
486 | # request id -> dict of commands that are actively being received. |
|
539 | # request id -> dict of commands that are actively being received. | |
487 | self._receivingcommands = {} |
|
540 | self._receivingcommands = {} | |
488 | # Request IDs that have been received and are actively being processed. |
|
541 | # Request IDs that have been received and are actively being processed. | |
@@ -496,6 +549,30 b' class serverreactor(object):' | |||||
496 | Returns a dict with an ``action`` key that details what action, |
|
549 | Returns a dict with an ``action`` key that details what action, | |
497 | if any, the consumer should take next. |
|
550 | if any, the consumer should take next. | |
498 | """ |
|
551 | """ | |
|
552 | if not frame.streamid % 2: | |||
|
553 | self._state = 'errored' | |||
|
554 | return self._makeerrorresult( | |||
|
555 | _('received frame with even numbered stream ID: %d') % | |||
|
556 | frame.streamid) | |||
|
557 | ||||
|
558 | if frame.streamid not in self._incomingstreams: | |||
|
559 | if not frame.streamflags & STREAM_FLAG_BEGIN_STREAM: | |||
|
560 | self._state = 'errored' | |||
|
561 | return self._makeerrorresult( | |||
|
562 | _('received frame on unknown inactive stream without ' | |||
|
563 | 'beginning of stream flag set')) | |||
|
564 | ||||
|
565 | self._incomingstreams[frame.streamid] = stream(frame.streamid) | |||
|
566 | ||||
|
567 | if frame.streamflags & STREAM_FLAG_ENCODING_APPLIED: | |||
|
568 | # TODO handle decoding frames | |||
|
569 | self._state = 'errored' | |||
|
570 | raise error.ProgrammingError('support for decoding stream payloads ' | |||
|
571 | 'not yet implemented') | |||
|
572 | ||||
|
573 | if frame.streamflags & STREAM_FLAG_END_STREAM: | |||
|
574 | del self._incomingstreams[frame.streamid] | |||
|
575 | ||||
499 | handlers = { |
|
576 | handlers = { | |
500 | 'idle': self._onframeidle, |
|
577 | 'idle': self._onframeidle, | |
501 | 'command-receiving': self._onframecommandreceiving, |
|
578 | 'command-receiving': self._onframecommandreceiving, | |
@@ -513,6 +590,8 b' class serverreactor(object):' | |||||
513 |
|
590 | |||
514 | The raw bytes response is passed as an argument. |
|
591 | The raw bytes response is passed as an argument. | |
515 | """ |
|
592 | """ | |
|
593 | ensureserverstream(stream) | |||
|
594 | ||||
516 | def sendframes(): |
|
595 | def sendframes(): | |
517 | for frame in createbytesresponseframesfrombytes(stream, requestid, |
|
596 | for frame in createbytesresponseframesfrombytes(stream, requestid, | |
518 | data): |
|
597 | data): | |
@@ -552,6 +631,8 b' class serverreactor(object):' | |||||
552 | } |
|
631 | } | |
553 |
|
632 | |||
554 | def onapplicationerror(self, stream, requestid, msg): |
|
633 | def onapplicationerror(self, stream, requestid, msg): | |
|
634 | ensureserverstream(stream) | |||
|
635 | ||||
555 | return 'sendframes', { |
|
636 | return 'sendframes', { | |
556 | 'framegen': createerrorframe(stream, requestid, msg, |
|
637 | 'framegen': createerrorframe(stream, requestid, msg, | |
557 | application=True), |
|
638 | application=True), |
@@ -546,7 +546,7 b' def _httpv2runcommand(ui, repo, req, res' | |||||
546 |
|
546 | |||
547 | res.status = b'200 OK' |
|
547 | res.status = b'200 OK' | |
548 | res.headers[b'Content-Type'] = FRAMINGTYPE |
|
548 | res.headers[b'Content-Type'] = FRAMINGTYPE | |
549 | stream = wireprotoframing.stream() |
|
549 | stream = wireprotoframing.stream(2) | |
550 |
|
550 | |||
551 | if isinstance(rsp, wireprototypes.bytesresponse): |
|
551 | if isinstance(rsp, wireprototypes.bytesresponse): | |
552 | action, meta = reactor.onbytesresponseready(stream, |
|
552 | action, meta = reactor.onbytesresponseready(stream, |
@@ -179,7 +179,7 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 command-name eos customreadonly |
|
182 | > frame 1 1 stream-begin command-name eos 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 | |
@@ -190,7 +190,7 b' Request to read-only command works out o' | |||||
190 | s> *\r\n (glob) |
|
190 | s> *\r\n (glob) | |
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\x11customreadonly |
|
193 | s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly | |
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 | |
@@ -198,8 +198,8 b' Request to read-only command works out o' | |||||
198 | s> Content-Type: application/mercurial-exp-framing-0002\r\n |
|
198 | s> Content-Type: application/mercurial-exp-framing-0002\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> 2 |
|
201 | s> 25\r\n | |
202 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response |
|
202 | s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response | |
203 | s> \r\n |
|
203 | s> \r\n | |
204 | s> 0\r\n |
|
204 | s> 0\r\n | |
205 | s> \r\n |
|
205 | s> \r\n | |
@@ -290,7 +290,7 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 command-name eos customreadonly |
|
293 | > frame 1 1 stream-begin command-name eos 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 | |
@@ -298,10 +298,10 b' Authorized request for valid read-write ' | |||||
298 | s> accept: application/mercurial-exp-framing-0002\r\n |
|
298 | s> accept: application/mercurial-exp-framing-0002\r\n | |
299 | s> content-type: application/mercurial-exp-framing-0002\r\n |
|
299 | s> content-type: application/mercurial-exp-framing-0002\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: 22\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\x11customreadonly |
|
304 | s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly | |
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 | |
@@ -309,8 +309,8 b' Authorized request for valid read-write ' | |||||
309 | s> Content-Type: application/mercurial-exp-framing-0002\r\n |
|
309 | s> Content-Type: application/mercurial-exp-framing-0002\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> 2 |
|
312 | s> 25\r\n | |
313 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response |
|
313 | s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response | |
314 | s> \r\n |
|
314 | s> \r\n | |
315 | s> 0\r\n |
|
315 | s> 0\r\n | |
316 | s> \r\n |
|
316 | s> \r\n | |
@@ -382,9 +382,9 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 command-name have-args command1 |
|
385 | > frame 1 1 stream-begin command-name have-args command1 | |
386 | > frame 1 command-argument 0 \x03\x00\x04\x00fooval1 |
|
386 | > frame 1 1 0 command-argument 0 \x03\x00\x04\x00fooval1 | |
387 | > frame 1 command-argument eoa \x04\x00\x03\x00bar1val |
|
387 | > frame 1 1 0 command-argument eoa \x04\x00\x03\x00bar1val | |
388 | > EOF |
|
388 | > EOF | |
389 | using raw connection to peer |
|
389 | using raw connection to peer | |
390 | s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n |
|
390 | s> POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n | |
@@ -392,10 +392,10 b' Command frames can be reflected via debu' | |||||
392 | s> accept: application/mercurial-exp-framing-0002\r\n |
|
392 | s> accept: application/mercurial-exp-framing-0002\r\n | |
393 | s> content-type: application/mercurial-exp-framing-0002\r\n |
|
393 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
394 | s> user-agent: test\r\n |
|
394 | s> user-agent: test\r\n | |
395 |
s> content-length: 4 |
|
395 | s> content-length: 54\r\n | |
396 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
396 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
397 | s> \r\n |
|
397 | s> \r\n | |
398 | s> \x08\x00\x00\x01\x00\x12command1\x0b\x00\x00\x01\x00 \x03\x00\x04\x00fooval1\x0b\x00\x00\x01\x00"\x04\x00\x03\x00bar1val |
|
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 | |
399 | s> makefile('rb', None) |
|
399 | s> makefile('rb', None) | |
400 | s> HTTP/1.1 200 OK\r\n |
|
400 | s> HTTP/1.1 200 OK\r\n | |
401 | s> Server: testing stub value\r\n |
|
401 | s> Server: testing stub value\r\n | |
@@ -419,8 +419,8 b' Multiple requests to regular command URL' | |||||
419 | > accept: $MEDIATYPE |
|
419 | > accept: $MEDIATYPE | |
420 | > content-type: $MEDIATYPE |
|
420 | > content-type: $MEDIATYPE | |
421 | > user-agent: test |
|
421 | > user-agent: test | |
422 | > frame 1 command-name eos customreadonly |
|
422 | > frame 1 1 stream-begin command-name eos customreadonly | |
423 | > frame 3 command-name eos customreadonly |
|
423 | > frame 3 1 0 command-name eos customreadonly | |
424 | > EOF |
|
424 | > EOF | |
425 | using raw connection to peer |
|
425 | using raw connection to peer | |
426 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n |
|
426 | s> POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n | |
@@ -428,10 +428,10 b' Multiple requests to regular command URL' | |||||
428 | s> accept: application/mercurial-exp-framing-0002\r\n |
|
428 | s> accept: application/mercurial-exp-framing-0002\r\n | |
429 | s> content-type: application/mercurial-exp-framing-0002\r\n |
|
429 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
430 | s> user-agent: test\r\n |
|
430 | s> user-agent: test\r\n | |
431 |
s> content-length: 4 |
|
431 | s> content-length: 44\r\n | |
432 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
432 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
433 | s> \r\n |
|
433 | s> \r\n | |
434 | s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly |
|
434 | s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly | |
435 | s> makefile('rb', None) |
|
435 | s> makefile('rb', None) | |
436 | s> HTTP/1.1 200 OK\r\n |
|
436 | s> HTTP/1.1 200 OK\r\n | |
437 | s> Server: testing stub value\r\n |
|
437 | s> Server: testing stub value\r\n | |
@@ -448,8 +448,8 b' Multiple requests to "multirequest" URL ' | |||||
448 | > accept: $MEDIATYPE |
|
448 | > accept: $MEDIATYPE | |
449 | > content-type: $MEDIATYPE |
|
449 | > content-type: $MEDIATYPE | |
450 | > user-agent: test |
|
450 | > user-agent: test | |
451 | > frame 1 command-name eos customreadonly |
|
451 | > frame 1 1 stream-begin command-name eos customreadonly | |
452 | > frame 3 command-name eos customreadonly |
|
452 | > frame 3 1 0 command-name eos customreadonly | |
453 | > EOF |
|
453 | > EOF | |
454 | using raw connection to peer |
|
454 | using raw connection to peer | |
455 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n |
|
455 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n | |
@@ -460,7 +460,7 b' Multiple requests to "multirequest" URL ' | |||||
460 | s> *\r\n (glob) |
|
460 | s> *\r\n (glob) | |
461 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
461 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
462 | s> \r\n |
|
462 | s> \r\n | |
463 | s> \x0e\x00\x00\x01\x00\x11customreadonly\x0e\x00\x00\x03\x00\x11customreadonly |
|
463 | s> \x0e\x00\x00\x01\x00\x01\x01\x11customreadonly\x0e\x00\x00\x03\x00\x01\x00\x11customreadonly | |
464 | s> makefile('rb', None) |
|
464 | s> makefile('rb', None) | |
465 | s> HTTP/1.1 200 OK\r\n |
|
465 | s> HTTP/1.1 200 OK\r\n | |
466 | s> Server: testing stub value\r\n |
|
466 | s> Server: testing stub value\r\n | |
@@ -469,10 +469,10 b' Multiple requests to "multirequest" URL ' | |||||
469 | s> Transfer-Encoding: chunked\r\n |
|
469 | s> Transfer-Encoding: chunked\r\n | |
470 | s> \r\n |
|
470 | s> \r\n | |
471 | s> *\r\n (glob) |
|
471 | s> *\r\n (glob) | |
472 | s> \x1d\x00\x00\x01\x00Bcustomreadonly bytes response |
|
472 | s> \x1d\x00\x00\x01\x00\x02\x01Bcustomreadonly bytes response | |
473 | s> \r\n |
|
473 | s> \r\n | |
474 |
s> 2 |
|
474 | s> 25\r\n | |
475 | s> \x1d\x00\x00\x03\x00Bcustomreadonly bytes response |
|
475 | s> \x1d\x00\x00\x03\x00\x02\x01Bcustomreadonly bytes response | |
476 | s> \r\n |
|
476 | s> \r\n | |
477 | s> 0\r\n |
|
477 | s> 0\r\n | |
478 | s> \r\n |
|
478 | s> \r\n | |
@@ -484,10 +484,10 b' Interleaved requests to "multirequest" a' | |||||
484 | > accept: $MEDIATYPE |
|
484 | > accept: $MEDIATYPE | |
485 | > content-type: $MEDIATYPE |
|
485 | > content-type: $MEDIATYPE | |
486 | > user-agent: test |
|
486 | > user-agent: test | |
487 | > frame 1 command-name have-args listkeys |
|
487 | > frame 1 1 stream-begin command-name have-args listkeys | |
488 | > frame 3 command-name have-args listkeys |
|
488 | > frame 3 1 0 command-name have-args listkeys | |
489 | > frame 3 command-argument eoa \x09\x00\x09\x00namespacebookmarks |
|
489 | > frame 3 1 0 command-argument eoa \x09\x00\x09\x00namespacebookmarks | |
490 | > frame 1 command-argument eoa \x09\x00\x0a\x00namespacenamespaces |
|
490 | > frame 1 1 0 command-argument eoa \x09\x00\x0a\x00namespacenamespaces | |
491 | > EOF |
|
491 | > EOF | |
492 | using raw connection to peer |
|
492 | using raw connection to peer | |
493 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n |
|
493 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n | |
@@ -495,10 +495,10 b' Interleaved requests to "multirequest" a' | |||||
495 | s> accept: application/mercurial-exp-framing-0002\r\n |
|
495 | s> accept: application/mercurial-exp-framing-0002\r\n | |
496 | s> content-type: application/mercurial-exp-framing-0002\r\n |
|
496 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
497 | s> user-agent: test\r\n |
|
497 | s> user-agent: test\r\n | |
498 |
s> content-length: |
|
498 | s> content-length: 93\r\n | |
499 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
499 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
500 | s> \r\n |
|
500 | s> \r\n | |
501 | s> \x08\x00\x00\x01\x00\x12listkeys\x08\x00\x00\x03\x00\x12listkeys\x16\x00\x00\x03\x00" \x00 \x00namespacebookmarks\x17\x00\x00\x01\x00" \x00\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 | |
502 | s> \x00namespacenamespaces |
|
502 | s> \x00namespacenamespaces | |
503 | s> makefile('rb', None) |
|
503 | s> makefile('rb', None) | |
504 | s> HTTP/1.1 200 OK\r\n |
|
504 | s> HTTP/1.1 200 OK\r\n | |
@@ -507,11 +507,11 b' Interleaved requests to "multirequest" a' | |||||
507 | s> Content-Type: application/mercurial-exp-framing-0002\r\n |
|
507 | s> Content-Type: application/mercurial-exp-framing-0002\r\n | |
508 | s> Transfer-Encoding: chunked\r\n |
|
508 | s> Transfer-Encoding: chunked\r\n | |
509 | s> \r\n |
|
509 | s> \r\n | |
510 |
s> |
|
510 | s> 8\r\n | |
511 |
s> \x00\x00\x00\x03\x00 |
|
511 | s> \x00\x00\x00\x03\x00\x02\x01B | |
512 | s> \r\n |
|
512 | s> \r\n | |
513 |
s> 2 |
|
513 | s> 26\r\n | |
514 | s> \x1e\x00\x00\x01\x00Bbookmarks \n |
|
514 | s> \x1e\x00\x00\x01\x00\x02\x01Bbookmarks \n | |
515 | s> namespaces \n |
|
515 | s> namespaces \n | |
516 | s> phases |
|
516 | s> phases | |
517 | s> \r\n |
|
517 | s> \r\n | |
@@ -540,7 +540,7 b' Attempting to run a read-write command v' | |||||
540 | > accept: $MEDIATYPE |
|
540 | > accept: $MEDIATYPE | |
541 | > content-type: $MEDIATYPE |
|
541 | > content-type: $MEDIATYPE | |
542 | > user-agent: test |
|
542 | > user-agent: test | |
543 | > frame 1 command-name eos unbundle |
|
543 | > frame 1 1 stream-begin command-name eos unbundle | |
544 | > EOF |
|
544 | > EOF | |
545 | using raw connection to peer |
|
545 | using raw connection to peer | |
546 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n |
|
546 | s> POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n | |
@@ -548,10 +548,10 b' Attempting to run a read-write command v' | |||||
548 | s> accept: application/mercurial-exp-framing-0002\r\n |
|
548 | s> accept: application/mercurial-exp-framing-0002\r\n | |
549 | s> content-type: application/mercurial-exp-framing-0002\r\n |
|
549 | s> content-type: application/mercurial-exp-framing-0002\r\n | |
550 | s> user-agent: test\r\n |
|
550 | s> user-agent: test\r\n | |
551 |
s> content-length: 1 |
|
551 | s> content-length: 16\r\n | |
552 | s> host: $LOCALIP:$HGPORT\r\n (glob) |
|
552 | s> host: $LOCALIP:$HGPORT\r\n (glob) | |
553 | s> \r\n |
|
553 | s> \r\n | |
554 | s> \x08\x00\x00\x01\x00\x11unbundle |
|
554 | s> \x08\x00\x00\x01\x00\x01\x01\x11unbundle | |
555 | s> makefile('rb', None) |
|
555 | s> makefile('rb', None) | |
556 | s> HTTP/1.1 403 Forbidden\r\n |
|
556 | s> HTTP/1.1 403 Forbidden\r\n | |
557 | s> Server: testing stub value\r\n |
|
557 | s> Server: testing stub value\r\n |
@@ -23,6 +23,8 b' def sendframes(reactor, gen):' | |||||
23 | assert len(payload) == header.length |
|
23 | assert len(payload) == header.length | |
24 |
|
24 | |||
25 | yield reactor.onframerecv(framing.frame(header.requestid, |
|
25 | yield reactor.onframerecv(framing.frame(header.requestid, | |
|
26 | header.streamid, | |||
|
27 | header.streamflags, | |||
26 | header.typeid, |
|
28 | header.typeid, | |
27 | header.flags, |
|
29 | header.flags, | |
28 | payload)) |
|
30 | payload)) | |
@@ -37,32 +39,32 b' class FrameTests(unittest.TestCase):' | |||||
37 | def testdataexactframesize(self): |
|
39 | def testdataexactframesize(self): | |
38 | data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE) |
|
40 | data = util.bytesio(b'x' * framing.DEFAULT_MAX_FRAME_SIZE) | |
39 |
|
41 | |||
40 | stream = framing.stream() |
|
42 | stream = framing.stream(1) | |
41 | frames = list(framing.createcommandframes(stream, 1, b'command', |
|
43 | frames = list(framing.createcommandframes(stream, 1, b'command', | |
42 | {}, data)) |
|
44 | {}, data)) | |
43 | self.assertEqual(frames, [ |
|
45 | self.assertEqual(frames, [ | |
44 | ffs(b'1 command-name have-data command'), |
|
46 | ffs(b'1 1 stream-begin command-name have-data command'), | |
45 | ffs(b'1 command-data continuation %s' % data.getvalue()), |
|
47 | ffs(b'1 1 0 command-data continuation %s' % data.getvalue()), | |
46 | ffs(b'1 command-data eos ') |
|
48 | ffs(b'1 1 0 command-data eos ') | |
47 | ]) |
|
49 | ]) | |
48 |
|
50 | |||
49 | def testdatamultipleframes(self): |
|
51 | def testdatamultipleframes(self): | |
50 | data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1)) |
|
52 | data = util.bytesio(b'x' * (framing.DEFAULT_MAX_FRAME_SIZE + 1)) | |
51 |
|
53 | |||
52 | stream = framing.stream() |
|
54 | stream = framing.stream(1) | |
53 | frames = list(framing.createcommandframes(stream, 1, b'command', {}, |
|
55 | frames = list(framing.createcommandframes(stream, 1, b'command', {}, | |
54 | data)) |
|
56 | data)) | |
55 | self.assertEqual(frames, [ |
|
57 | self.assertEqual(frames, [ | |
56 | ffs(b'1 command-name have-data command'), |
|
58 | ffs(b'1 1 stream-begin command-name have-data command'), | |
57 | ffs(b'1 command-data continuation %s' % ( |
|
59 | ffs(b'1 1 0 command-data continuation %s' % ( | |
58 | b'x' * framing.DEFAULT_MAX_FRAME_SIZE)), |
|
60 | b'x' * framing.DEFAULT_MAX_FRAME_SIZE)), | |
59 | ffs(b'1 command-data eos x'), |
|
61 | ffs(b'1 1 0 command-data eos x'), | |
60 | ]) |
|
62 | ]) | |
61 |
|
63 | |||
62 | def testargsanddata(self): |
|
64 | def testargsanddata(self): | |
63 | data = util.bytesio(b'x' * 100) |
|
65 | data = util.bytesio(b'x' * 100) | |
64 |
|
66 | |||
65 | stream = framing.stream() |
|
67 | stream = framing.stream(1) | |
66 | frames = list(framing.createcommandframes(stream, 1, b'command', { |
|
68 | frames = list(framing.createcommandframes(stream, 1, b'command', { | |
67 | b'key1': b'key1value', |
|
69 | b'key1': b'key1value', | |
68 | b'key2': b'key2value', |
|
70 | b'key2': b'key2value', | |
@@ -70,11 +72,11 b' class FrameTests(unittest.TestCase):' | |||||
70 | }, data)) |
|
72 | }, data)) | |
71 |
|
73 | |||
72 | self.assertEqual(frames, [ |
|
74 | self.assertEqual(frames, [ | |
73 | ffs(b'1 command-name have-args|have-data command'), |
|
75 | ffs(b'1 1 stream-begin command-name have-args|have-data command'), | |
74 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key1key1value'), |
|
76 | ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key1key1value'), | |
75 | ffs(br'1 command-argument 0 \x04\x00\x09\x00key2key2value'), |
|
77 | ffs(br'1 1 0 command-argument 0 \x04\x00\x09\x00key2key2value'), | |
76 | ffs(br'1 command-argument eoa \x04\x00\x09\x00key3key3value'), |
|
78 | ffs(br'1 1 0 command-argument eoa \x04\x00\x09\x00key3key3value'), | |
77 | ffs(b'1 command-data eos %s' % data.getvalue()), |
|
79 | ffs(b'1 1 0 command-data eos %s' % data.getvalue()), | |
78 | ]) |
|
80 | ]) | |
79 |
|
81 | |||
80 | def testtextoutputexcessiveargs(self): |
|
82 | def testtextoutputexcessiveargs(self): | |
@@ -128,64 +130,68 b' class FrameTests(unittest.TestCase):' | |||||
128 | (b'bleh', [], [b'x' * 65536])])) |
|
130 | (b'bleh', [], [b'x' * 65536])])) | |
129 |
|
131 | |||
130 | def testtextoutput1simpleatom(self): |
|
132 | def testtextoutput1simpleatom(self): | |
131 | stream = framing.stream() |
|
133 | stream = framing.stream(1) | |
132 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
134 | val = list(framing.createtextoutputframe(stream, 1, [ | |
133 | (b'foo', [], [])])) |
|
135 | (b'foo', [], [])])) | |
134 |
|
136 | |||
135 | self.assertEqual(val, [ |
|
137 | self.assertEqual(val, [ | |
136 | ffs(br'1 text-output 0 \x03\x00\x00\x00foo'), |
|
138 | ffs(br'1 1 stream-begin text-output 0 \x03\x00\x00\x00foo'), | |
137 | ]) |
|
139 | ]) | |
138 |
|
140 | |||
139 | def testtextoutput2simpleatoms(self): |
|
141 | def testtextoutput2simpleatoms(self): | |
140 | stream = framing.stream() |
|
142 | stream = framing.stream(1) | |
141 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
143 | val = list(framing.createtextoutputframe(stream, 1, [ | |
142 | (b'foo', [], []), |
|
144 | (b'foo', [], []), | |
143 | (b'bar', [], []), |
|
145 | (b'bar', [], []), | |
144 | ])) |
|
146 | ])) | |
145 |
|
147 | |||
146 | self.assertEqual(val, [ |
|
148 | self.assertEqual(val, [ | |
147 | ffs(br'1 text-output 0 \x03\x00\x00\x00foo\x03\x00\x00\x00bar'), |
|
149 | ffs(br'1 1 stream-begin text-output 0 ' | |
|
150 | br'\x03\x00\x00\x00foo\x03\x00\x00\x00bar'), | |||
148 | ]) |
|
151 | ]) | |
149 |
|
152 | |||
150 | def testtextoutput1arg(self): |
|
153 | def testtextoutput1arg(self): | |
151 | stream = framing.stream() |
|
154 | stream = framing.stream(1) | |
152 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
155 | val = list(framing.createtextoutputframe(stream, 1, [ | |
153 | (b'foo %s', [b'val1'], []), |
|
156 | (b'foo %s', [b'val1'], []), | |
154 | ])) |
|
157 | ])) | |
155 |
|
158 | |||
156 | self.assertEqual(val, [ |
|
159 | self.assertEqual(val, [ | |
157 | ffs(br'1 text-output 0 \x06\x00\x00\x01\x04\x00foo %sval1'), |
|
160 | ffs(br'1 1 stream-begin text-output 0 ' | |
|
161 | br'\x06\x00\x00\x01\x04\x00foo %sval1'), | |||
158 | ]) |
|
162 | ]) | |
159 |
|
163 | |||
160 | def testtextoutput2arg(self): |
|
164 | def testtextoutput2arg(self): | |
161 | stream = framing.stream() |
|
165 | stream = framing.stream(1) | |
162 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
166 | val = list(framing.createtextoutputframe(stream, 1, [ | |
163 | (b'foo %s %s', [b'val', b'value'], []), |
|
167 | (b'foo %s %s', [b'val', b'value'], []), | |
164 | ])) |
|
168 | ])) | |
165 |
|
169 | |||
166 | self.assertEqual(val, [ |
|
170 | self.assertEqual(val, [ | |
167 |
ffs(br'1 text-output 0 |
|
171 | ffs(br'1 1 stream-begin text-output 0 ' | |
168 | br'foo %s %svalvalue'), |
|
172 | br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'), | |
169 | ]) |
|
173 | ]) | |
170 |
|
174 | |||
171 | def testtextoutput1label(self): |
|
175 | def testtextoutput1label(self): | |
172 | stream = framing.stream() |
|
176 | stream = framing.stream(1) | |
173 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
177 | val = list(framing.createtextoutputframe(stream, 1, [ | |
174 | (b'foo', [], [b'label']), |
|
178 | (b'foo', [], [b'label']), | |
175 | ])) |
|
179 | ])) | |
176 |
|
180 | |||
177 | self.assertEqual(val, [ |
|
181 | self.assertEqual(val, [ | |
178 |
ffs(br'1 text-output 0 |
|
182 | ffs(br'1 1 stream-begin text-output 0 ' | |
|
183 | br'\x03\x00\x01\x00\x05foolabel'), | |||
179 | ]) |
|
184 | ]) | |
180 |
|
185 | |||
181 | def testargandlabel(self): |
|
186 | def testargandlabel(self): | |
182 | stream = framing.stream() |
|
187 | stream = framing.stream(1) | |
183 | val = list(framing.createtextoutputframe(stream, 1, [ |
|
188 | val = list(framing.createtextoutputframe(stream, 1, [ | |
184 | (b'foo %s', [b'arg'], [b'label']), |
|
189 | (b'foo %s', [b'arg'], [b'label']), | |
185 | ])) |
|
190 | ])) | |
186 |
|
191 | |||
187 | self.assertEqual(val, [ |
|
192 | self.assertEqual(val, [ | |
188 | ffs(br'1 text-output 0 \x06\x00\x01\x01\x05\x03\x00foo %slabelarg'), |
|
193 | ffs(br'1 1 stream-begin text-output 0 ' | |
|
194 | br'\x06\x00\x01\x01\x05\x03\x00foo %slabelarg'), | |||
189 | ]) |
|
195 | ]) | |
190 |
|
196 | |||
191 | class ServerReactorTests(unittest.TestCase): |
|
197 | class ServerReactorTests(unittest.TestCase): | |
@@ -208,7 +214,7 b' class ServerReactorTests(unittest.TestCa' | |||||
208 | def test1framecommand(self): |
|
214 | def test1framecommand(self): | |
209 | """Receiving a command in a single frame yields request to run it.""" |
|
215 | """Receiving a command in a single frame yields request to run it.""" | |
210 | reactor = makereactor() |
|
216 | reactor = makereactor() | |
211 | stream = framing.stream() |
|
217 | stream = framing.stream(1) | |
212 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {})) |
|
218 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {})) | |
213 | self.assertEqual(len(results), 1) |
|
219 | self.assertEqual(len(results), 1) | |
214 | self.assertaction(results[0], 'runcommand') |
|
220 | self.assertaction(results[0], 'runcommand') | |
@@ -224,7 +230,7 b' class ServerReactorTests(unittest.TestCa' | |||||
224 |
|
230 | |||
225 | def test1argument(self): |
|
231 | def test1argument(self): | |
226 | reactor = makereactor() |
|
232 | reactor = makereactor() | |
227 | stream = framing.stream() |
|
233 | stream = framing.stream(1) | |
228 | results = list(sendcommandframes(reactor, stream, 41, b'mycommand', |
|
234 | results = list(sendcommandframes(reactor, stream, 41, b'mycommand', | |
229 | {b'foo': b'bar'})) |
|
235 | {b'foo': b'bar'})) | |
230 | self.assertEqual(len(results), 2) |
|
236 | self.assertEqual(len(results), 2) | |
@@ -239,7 +245,7 b' class ServerReactorTests(unittest.TestCa' | |||||
239 |
|
245 | |||
240 | def testmultiarguments(self): |
|
246 | def testmultiarguments(self): | |
241 | reactor = makereactor() |
|
247 | reactor = makereactor() | |
242 | stream = framing.stream() |
|
248 | stream = framing.stream(1) | |
243 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', |
|
249 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', | |
244 | {b'foo': b'bar', b'biz': b'baz'})) |
|
250 | {b'foo': b'bar', b'biz': b'baz'})) | |
245 | self.assertEqual(len(results), 3) |
|
251 | self.assertEqual(len(results), 3) | |
@@ -255,7 +261,7 b' class ServerReactorTests(unittest.TestCa' | |||||
255 |
|
261 | |||
256 | def testsimplecommanddata(self): |
|
262 | def testsimplecommanddata(self): | |
257 | reactor = makereactor() |
|
263 | reactor = makereactor() | |
258 | stream = framing.stream() |
|
264 | stream = framing.stream(1) | |
259 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}, |
|
265 | results = list(sendcommandframes(reactor, stream, 1, b'mycommand', {}, | |
260 | util.bytesio(b'data!'))) |
|
266 | util.bytesio(b'data!'))) | |
261 | self.assertEqual(len(results), 2) |
|
267 | self.assertEqual(len(results), 2) | |
@@ -270,10 +276,10 b' class ServerReactorTests(unittest.TestCa' | |||||
270 |
|
276 | |||
271 | def testmultipledataframes(self): |
|
277 | def testmultipledataframes(self): | |
272 | frames = [ |
|
278 | frames = [ | |
273 | ffs(b'1 command-name have-data mycommand'), |
|
279 | ffs(b'1 1 stream-begin command-name have-data mycommand'), | |
274 | ffs(b'1 command-data continuation data1'), |
|
280 | ffs(b'1 1 0 command-data continuation data1'), | |
275 | ffs(b'1 command-data continuation data2'), |
|
281 | ffs(b'1 1 0 command-data continuation data2'), | |
276 | ffs(b'1 command-data eos data3'), |
|
282 | ffs(b'1 1 0 command-data eos data3'), | |
277 | ] |
|
283 | ] | |
278 |
|
284 | |||
279 | reactor = makereactor() |
|
285 | reactor = makereactor() | |
@@ -291,11 +297,11 b' class ServerReactorTests(unittest.TestCa' | |||||
291 |
|
297 | |||
292 | def testargumentanddata(self): |
|
298 | def testargumentanddata(self): | |
293 | frames = [ |
|
299 | frames = [ | |
294 | ffs(b'1 command-name have-args|have-data command'), |
|
300 | ffs(b'1 1 stream-begin command-name have-args|have-data command'), | |
295 | ffs(br'1 command-argument 0 \x03\x00\x03\x00keyval'), |
|
301 | ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00keyval'), | |
296 | ffs(br'1 command-argument eoa \x03\x00\x03\x00foobar'), |
|
302 | ffs(br'1 1 0 command-argument eoa \x03\x00\x03\x00foobar'), | |
297 | ffs(b'1 command-data continuation value1'), |
|
303 | ffs(b'1 1 0 command-data continuation value1'), | |
298 | ffs(b'1 command-data eos value2'), |
|
304 | ffs(b'1 1 0 command-data eos value2'), | |
299 | ] |
|
305 | ] | |
300 |
|
306 | |||
301 | reactor = makereactor() |
|
307 | reactor = makereactor() | |
@@ -314,8 +320,8 b' class ServerReactorTests(unittest.TestCa' | |||||
314 |
|
320 | |||
315 | def testunexpectedcommandargument(self): |
|
321 | def testunexpectedcommandargument(self): | |
316 | """Command argument frame when not running a command is an error.""" |
|
322 | """Command argument frame when not running a command is an error.""" | |
317 |
result = self._sendsingleframe( |
|
323 | result = self._sendsingleframe( | |
318 |
|
|
324 | makereactor(), ffs(b'1 1 stream-begin command-argument 0 ignored')) | |
319 | self.assertaction(result, 'error') |
|
325 | self.assertaction(result, 'error') | |
320 | self.assertEqual(result[1], { |
|
326 | self.assertEqual(result[1], { | |
321 | 'message': b'expected command frame; got 2', |
|
327 | 'message': b'expected command frame; got 2', | |
@@ -324,8 +330,8 b' class ServerReactorTests(unittest.TestCa' | |||||
324 | def testunexpectedcommandargumentreceiving(self): |
|
330 | def testunexpectedcommandargumentreceiving(self): | |
325 | """Same as above but the command is receiving.""" |
|
331 | """Same as above but the command is receiving.""" | |
326 | results = list(sendframes(makereactor(), [ |
|
332 | results = list(sendframes(makereactor(), [ | |
327 | ffs(b'1 command-name have-data command'), |
|
333 | ffs(b'1 1 stream-begin command-name have-data command'), | |
328 | ffs(b'1 command-argument eoa ignored'), |
|
334 | ffs(b'1 1 0 command-argument eoa ignored'), | |
329 | ])) |
|
335 | ])) | |
330 |
|
336 | |||
331 | self.assertaction(results[1], 'error') |
|
337 | self.assertaction(results[1], 'error') | |
@@ -336,8 +342,8 b' class ServerReactorTests(unittest.TestCa' | |||||
336 |
|
342 | |||
337 | def testunexpectedcommanddata(self): |
|
343 | def testunexpectedcommanddata(self): | |
338 | """Command argument frame when not running a command is an error.""" |
|
344 | """Command argument frame when not running a command is an error.""" | |
339 |
result = self._sendsingleframe( |
|
345 | result = self._sendsingleframe( | |
340 |
|
|
346 | makereactor(), ffs(b'1 1 stream-begin command-data 0 ignored')) | |
341 | self.assertaction(result, 'error') |
|
347 | self.assertaction(result, 'error') | |
342 | self.assertEqual(result[1], { |
|
348 | self.assertEqual(result[1], { | |
343 | 'message': b'expected command frame; got 3', |
|
349 | 'message': b'expected command frame; got 3', | |
@@ -346,8 +352,8 b' class ServerReactorTests(unittest.TestCa' | |||||
346 | def testunexpectedcommanddatareceiving(self): |
|
352 | def testunexpectedcommanddatareceiving(self): | |
347 | """Same as above except the command is receiving.""" |
|
353 | """Same as above except the command is receiving.""" | |
348 | results = list(sendframes(makereactor(), [ |
|
354 | results = list(sendframes(makereactor(), [ | |
349 | ffs(b'1 command-name have-args command'), |
|
355 | ffs(b'1 1 stream-begin command-name have-args command'), | |
350 | ffs(b'1 command-data eos ignored'), |
|
356 | ffs(b'1 1 0 command-data eos ignored'), | |
351 | ])) |
|
357 | ])) | |
352 |
|
358 | |||
353 | self.assertaction(results[1], 'error') |
|
359 | self.assertaction(results[1], 'error') | |
@@ -358,8 +364,8 b' class ServerReactorTests(unittest.TestCa' | |||||
358 |
|
364 | |||
359 | def testmissingcommandframeflags(self): |
|
365 | def testmissingcommandframeflags(self): | |
360 | """Command name frame must have flags set.""" |
|
366 | """Command name frame must have flags set.""" | |
361 |
result = self._sendsingleframe( |
|
367 | result = self._sendsingleframe( | |
362 |
|
|
368 | makereactor(), ffs(b'1 1 stream-begin command-name 0 command')) | |
363 | self.assertaction(result, 'error') |
|
369 | self.assertaction(result, 'error') | |
364 | self.assertEqual(result[1], { |
|
370 | self.assertEqual(result[1], { | |
365 | 'message': b'missing frame flags on command frame', |
|
371 | 'message': b'missing frame flags on command frame', | |
@@ -369,19 +375,19 b' class ServerReactorTests(unittest.TestCa' | |||||
369 | """Multiple fully serviced commands with same request ID is allowed.""" |
|
375 | """Multiple fully serviced commands with same request ID is allowed.""" | |
370 | reactor = makereactor() |
|
376 | reactor = makereactor() | |
371 | results = [] |
|
377 | results = [] | |
372 | outstream = framing.stream() |
|
378 | outstream = framing.stream(2) | |
373 | results.append(self._sendsingleframe( |
|
379 | results.append(self._sendsingleframe( | |
374 | reactor, ffs(b'1 command-name eos command'))) |
|
380 | reactor, ffs(b'1 1 stream-begin command-name eos command'))) | |
375 | result = reactor.onbytesresponseready(outstream, 1, b'response1') |
|
381 | result = reactor.onbytesresponseready(outstream, 1, b'response1') | |
376 | self.assertaction(result, 'sendframes') |
|
382 | self.assertaction(result, 'sendframes') | |
377 | list(result[1]['framegen']) |
|
383 | list(result[1]['framegen']) | |
378 | results.append(self._sendsingleframe( |
|
384 | results.append(self._sendsingleframe( | |
379 | reactor, ffs(b'1 command-name eos command'))) |
|
385 | reactor, ffs(b'1 1 0 command-name eos command'))) | |
380 | result = reactor.onbytesresponseready(outstream, 1, b'response2') |
|
386 | result = reactor.onbytesresponseready(outstream, 1, b'response2') | |
381 | self.assertaction(result, 'sendframes') |
|
387 | self.assertaction(result, 'sendframes') | |
382 | list(result[1]['framegen']) |
|
388 | list(result[1]['framegen']) | |
383 | results.append(self._sendsingleframe( |
|
389 | results.append(self._sendsingleframe( | |
384 | reactor, ffs(b'1 command-name eos command'))) |
|
390 | reactor, ffs(b'1 1 0 command-name eos command'))) | |
385 | result = reactor.onbytesresponseready(outstream, 1, b'response3') |
|
391 | result = reactor.onbytesresponseready(outstream, 1, b'response3') | |
386 | self.assertaction(result, 'sendframes') |
|
392 | self.assertaction(result, 'sendframes') | |
387 | list(result[1]['framegen']) |
|
393 | list(result[1]['framegen']) | |
@@ -398,8 +404,8 b' class ServerReactorTests(unittest.TestCa' | |||||
398 | def testconflictingrequestid(self): |
|
404 | def testconflictingrequestid(self): | |
399 | """Request ID for new command matching in-flight command is illegal.""" |
|
405 | """Request ID for new command matching in-flight command is illegal.""" | |
400 | results = list(sendframes(makereactor(), [ |
|
406 | results = list(sendframes(makereactor(), [ | |
401 | ffs(b'1 command-name have-args command'), |
|
407 | ffs(b'1 1 stream-begin command-name have-args command'), | |
402 | ffs(b'1 command-name eos command'), |
|
408 | ffs(b'1 1 0 command-name eos command'), | |
403 | ])) |
|
409 | ])) | |
404 |
|
410 | |||
405 | self.assertaction(results[0], 'wantframe') |
|
411 | self.assertaction(results[0], 'wantframe') | |
@@ -410,12 +416,12 b' class ServerReactorTests(unittest.TestCa' | |||||
410 |
|
416 | |||
411 | def testinterleavedcommands(self): |
|
417 | def testinterleavedcommands(self): | |
412 | results = list(sendframes(makereactor(), [ |
|
418 | results = list(sendframes(makereactor(), [ | |
413 | ffs(b'1 command-name have-args command1'), |
|
419 | ffs(b'1 1 stream-begin command-name have-args command1'), | |
414 | ffs(b'3 command-name have-args command3'), |
|
420 | ffs(b'3 1 0 command-name have-args command3'), | |
415 | ffs(br'1 command-argument 0 \x03\x00\x03\x00foobar'), |
|
421 | ffs(br'1 1 0 command-argument 0 \x03\x00\x03\x00foobar'), | |
416 | ffs(br'3 command-argument 0 \x03\x00\x03\x00bizbaz'), |
|
422 | ffs(br'3 1 0 command-argument 0 \x03\x00\x03\x00bizbaz'), | |
417 | ffs(br'3 command-argument eoa \x03\x00\x03\x00keyval'), |
|
423 | ffs(br'3 1 0 command-argument eoa \x03\x00\x03\x00keyval'), | |
418 | ffs(br'1 command-argument eoa \x04\x00\x03\x00key1val'), |
|
424 | ffs(br'1 1 0 command-argument eoa \x04\x00\x03\x00key1val'), | |
419 | ])) |
|
425 | ])) | |
420 |
|
426 | |||
421 | self.assertEqual([t[0] for t in results], [ |
|
427 | self.assertEqual([t[0] for t in results], [ | |
@@ -445,7 +451,7 b' class ServerReactorTests(unittest.TestCa' | |||||
445 | # command request waiting on argument data. But it doesn't handle that |
|
451 | # command request waiting on argument data. But it doesn't handle that | |
446 | # scenario yet. So this test does nothing of value. |
|
452 | # scenario yet. So this test does nothing of value. | |
447 | frames = [ |
|
453 | frames = [ | |
448 | ffs(b'1 command-name have-args command'), |
|
454 | ffs(b'1 1 stream-begin command-name have-args command'), | |
449 | ] |
|
455 | ] | |
450 |
|
456 | |||
451 | results = list(sendframes(makereactor(), frames)) |
|
457 | results = list(sendframes(makereactor(), frames)) | |
@@ -454,8 +460,8 b' class ServerReactorTests(unittest.TestCa' | |||||
454 | def testincompleteargumentname(self): |
|
460 | def testincompleteargumentname(self): | |
455 | """Argument frame with incomplete name.""" |
|
461 | """Argument frame with incomplete name.""" | |
456 | frames = [ |
|
462 | frames = [ | |
457 | ffs(b'1 command-name have-args command1'), |
|
463 | ffs(b'1 1 stream-begin command-name have-args command1'), | |
458 | ffs(br'1 command-argument eoa \x04\x00\xde\xadfoo'), |
|
464 | ffs(br'1 1 0 command-argument eoa \x04\x00\xde\xadfoo'), | |
459 | ] |
|
465 | ] | |
460 |
|
466 | |||
461 | results = list(sendframes(makereactor(), frames)) |
|
467 | results = list(sendframes(makereactor(), frames)) | |
@@ -469,8 +475,8 b' class ServerReactorTests(unittest.TestCa' | |||||
469 | def testincompleteargumentvalue(self): |
|
475 | def testincompleteargumentvalue(self): | |
470 | """Argument frame with incomplete value.""" |
|
476 | """Argument frame with incomplete value.""" | |
471 | frames = [ |
|
477 | frames = [ | |
472 | ffs(b'1 command-name have-args command'), |
|
478 | ffs(b'1 1 stream-begin command-name have-args command'), | |
473 | ffs(br'1 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), |
|
479 | ffs(br'1 1 0 command-argument eoa \x03\x00\xaa\xaafoopartialvalue'), | |
474 | ] |
|
480 | ] | |
475 |
|
481 | |||
476 | results = list(sendframes(makereactor(), frames)) |
|
482 | results = list(sendframes(makereactor(), frames)) | |
@@ -485,8 +491,8 b' class ServerReactorTests(unittest.TestCa' | |||||
485 | # The reactor doesn't currently handle partially received commands. |
|
491 | # The reactor doesn't currently handle partially received commands. | |
486 | # So this test is failing to do anything with request 1. |
|
492 | # So this test is failing to do anything with request 1. | |
487 | frames = [ |
|
493 | frames = [ | |
488 | ffs(b'1 command-name have-data command1'), |
|
494 | ffs(b'1 1 stream-begin command-name have-data command1'), | |
489 | ffs(b'3 command-name eos command2'), |
|
495 | ffs(b'3 1 0 command-name eos command2'), | |
490 | ] |
|
496 | ] | |
491 | results = list(sendframes(makereactor(), frames)) |
|
497 | results = list(sendframes(makereactor(), frames)) | |
492 | self.assertEqual(len(results), 2) |
|
498 | self.assertEqual(len(results), 2) | |
@@ -495,8 +501,8 b' class ServerReactorTests(unittest.TestCa' | |||||
495 |
|
501 | |||
496 | def testmissingcommanddataframeflags(self): |
|
502 | def testmissingcommanddataframeflags(self): | |
497 | frames = [ |
|
503 | frames = [ | |
498 | ffs(b'1 command-name have-data command1'), |
|
504 | ffs(b'1 1 stream-begin command-name have-data command1'), | |
499 | ffs(b'1 command-data 0 data'), |
|
505 | ffs(b'1 1 0 command-data 0 data'), | |
500 | ] |
|
506 | ] | |
501 | results = list(sendframes(makereactor(), frames)) |
|
507 | results = list(sendframes(makereactor(), frames)) | |
502 | self.assertEqual(len(results), 2) |
|
508 | self.assertEqual(len(results), 2) | |
@@ -509,9 +515,9 b' class ServerReactorTests(unittest.TestCa' | |||||
509 | def testframefornonreceivingrequest(self): |
|
515 | def testframefornonreceivingrequest(self): | |
510 | """Receiving a frame for a command that is not receiving is illegal.""" |
|
516 | """Receiving a frame for a command that is not receiving is illegal.""" | |
511 | results = list(sendframes(makereactor(), [ |
|
517 | results = list(sendframes(makereactor(), [ | |
512 | ffs(b'1 command-name eos command1'), |
|
518 | ffs(b'1 1 stream-begin command-name eos command1'), | |
513 | ffs(b'3 command-name have-data command3'), |
|
519 | ffs(b'3 1 0 command-name have-data command3'), | |
514 | ffs(b'5 command-argument eoa ignored'), |
|
520 | ffs(b'5 1 0 command-argument eoa ignored'), | |
515 | ])) |
|
521 | ])) | |
516 | self.assertaction(results[2], 'error') |
|
522 | self.assertaction(results[2], 'error') | |
517 | self.assertEqual(results[2][1], { |
|
523 | self.assertEqual(results[2][1], { | |
@@ -521,14 +527,14 b' class ServerReactorTests(unittest.TestCa' | |||||
521 | def testsimpleresponse(self): |
|
527 | def testsimpleresponse(self): | |
522 | """Bytes response to command sends result frames.""" |
|
528 | """Bytes response to command sends result frames.""" | |
523 | reactor = makereactor() |
|
529 | reactor = makereactor() | |
524 | instream = framing.stream() |
|
530 | instream = framing.stream(1) | |
525 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) |
|
531 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) | |
526 |
|
532 | |||
527 | outstream = framing.stream() |
|
533 | outstream = framing.stream(2) | |
528 | result = reactor.onbytesresponseready(outstream, 1, b'response') |
|
534 | result = reactor.onbytesresponseready(outstream, 1, b'response') | |
529 | self.assertaction(result, 'sendframes') |
|
535 | self.assertaction(result, 'sendframes') | |
530 | self.assertframesequal(result[1]['framegen'], [ |
|
536 | self.assertframesequal(result[1]['framegen'], [ | |
531 | b'1 bytes-response eos response', |
|
537 | b'1 2 stream-begin bytes-response eos response', | |
532 | ]) |
|
538 | ]) | |
533 |
|
539 | |||
534 | def testmultiframeresponse(self): |
|
540 | def testmultiframeresponse(self): | |
@@ -537,54 +543,54 b' class ServerReactorTests(unittest.TestCa' | |||||
537 | second = b'y' * 100 |
|
543 | second = b'y' * 100 | |
538 |
|
544 | |||
539 | reactor = makereactor() |
|
545 | reactor = makereactor() | |
540 | instream = framing.stream() |
|
546 | instream = framing.stream(1) | |
541 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) |
|
547 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) | |
542 |
|
548 | |||
543 | outstream = framing.stream() |
|
549 | outstream = framing.stream(2) | |
544 | result = reactor.onbytesresponseready(outstream, 1, first + second) |
|
550 | result = reactor.onbytesresponseready(outstream, 1, first + second) | |
545 | self.assertaction(result, 'sendframes') |
|
551 | self.assertaction(result, 'sendframes') | |
546 | self.assertframesequal(result[1]['framegen'], [ |
|
552 | self.assertframesequal(result[1]['framegen'], [ | |
547 | b'1 bytes-response continuation %s' % first, |
|
553 | b'1 2 stream-begin bytes-response continuation %s' % first, | |
548 | b'1 bytes-response eos %s' % second, |
|
554 | b'1 2 0 bytes-response eos %s' % second, | |
549 | ]) |
|
555 | ]) | |
550 |
|
556 | |||
551 | def testapplicationerror(self): |
|
557 | def testapplicationerror(self): | |
552 | reactor = makereactor() |
|
558 | reactor = makereactor() | |
553 | instream = framing.stream() |
|
559 | instream = framing.stream(1) | |
554 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) |
|
560 | list(sendcommandframes(reactor, instream, 1, b'mycommand', {})) | |
555 |
|
561 | |||
556 | outstream = framing.stream() |
|
562 | outstream = framing.stream(2) | |
557 | result = reactor.onapplicationerror(outstream, 1, b'some message') |
|
563 | result = reactor.onapplicationerror(outstream, 1, b'some message') | |
558 | self.assertaction(result, 'sendframes') |
|
564 | self.assertaction(result, 'sendframes') | |
559 | self.assertframesequal(result[1]['framegen'], [ |
|
565 | self.assertframesequal(result[1]['framegen'], [ | |
560 | b'1 error-response application some message', |
|
566 | b'1 2 stream-begin error-response application some message', | |
561 | ]) |
|
567 | ]) | |
562 |
|
568 | |||
563 | def test1commanddeferresponse(self): |
|
569 | def test1commanddeferresponse(self): | |
564 | """Responses when in deferred output mode are delayed until EOF.""" |
|
570 | """Responses when in deferred output mode are delayed until EOF.""" | |
565 | reactor = makereactor(deferoutput=True) |
|
571 | reactor = makereactor(deferoutput=True) | |
566 | instream = framing.stream() |
|
572 | instream = framing.stream(1) | |
567 | results = list(sendcommandframes(reactor, instream, 1, b'mycommand', |
|
573 | results = list(sendcommandframes(reactor, instream, 1, b'mycommand', | |
568 | {})) |
|
574 | {})) | |
569 | self.assertEqual(len(results), 1) |
|
575 | self.assertEqual(len(results), 1) | |
570 | self.assertaction(results[0], 'runcommand') |
|
576 | self.assertaction(results[0], 'runcommand') | |
571 |
|
577 | |||
572 | outstream = framing.stream() |
|
578 | outstream = framing.stream(2) | |
573 | result = reactor.onbytesresponseready(outstream, 1, b'response') |
|
579 | result = reactor.onbytesresponseready(outstream, 1, b'response') | |
574 | self.assertaction(result, 'noop') |
|
580 | self.assertaction(result, 'noop') | |
575 | result = reactor.oninputeof() |
|
581 | result = reactor.oninputeof() | |
576 | self.assertaction(result, 'sendframes') |
|
582 | self.assertaction(result, 'sendframes') | |
577 | self.assertframesequal(result[1]['framegen'], [ |
|
583 | self.assertframesequal(result[1]['framegen'], [ | |
578 | b'1 bytes-response eos response', |
|
584 | b'1 2 stream-begin bytes-response eos response', | |
579 | ]) |
|
585 | ]) | |
580 |
|
586 | |||
581 | def testmultiplecommanddeferresponse(self): |
|
587 | def testmultiplecommanddeferresponse(self): | |
582 | reactor = makereactor(deferoutput=True) |
|
588 | reactor = makereactor(deferoutput=True) | |
583 | instream = framing.stream() |
|
589 | instream = framing.stream(1) | |
584 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) |
|
590 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) | |
585 | list(sendcommandframes(reactor, instream, 3, b'command2', {})) |
|
591 | list(sendcommandframes(reactor, instream, 3, b'command2', {})) | |
586 |
|
592 | |||
587 | outstream = framing.stream() |
|
593 | outstream = framing.stream(2) | |
588 | result = reactor.onbytesresponseready(outstream, 1, b'response1') |
|
594 | result = reactor.onbytesresponseready(outstream, 1, b'response1') | |
589 | self.assertaction(result, 'noop') |
|
595 | self.assertaction(result, 'noop') | |
590 | result = reactor.onbytesresponseready(outstream, 3, b'response2') |
|
596 | result = reactor.onbytesresponseready(outstream, 3, b'response2') | |
@@ -592,19 +598,19 b' class ServerReactorTests(unittest.TestCa' | |||||
592 | result = reactor.oninputeof() |
|
598 | result = reactor.oninputeof() | |
593 | self.assertaction(result, 'sendframes') |
|
599 | self.assertaction(result, 'sendframes') | |
594 | self.assertframesequal(result[1]['framegen'], [ |
|
600 | self.assertframesequal(result[1]['framegen'], [ | |
595 | b'1 bytes-response eos response1', |
|
601 | b'1 2 stream-begin bytes-response eos response1', | |
596 | b'3 bytes-response eos response2' |
|
602 | b'3 2 0 bytes-response eos response2' | |
597 | ]) |
|
603 | ]) | |
598 |
|
604 | |||
599 | def testrequestidtracking(self): |
|
605 | def testrequestidtracking(self): | |
600 | reactor = makereactor(deferoutput=True) |
|
606 | reactor = makereactor(deferoutput=True) | |
601 | instream = framing.stream() |
|
607 | instream = framing.stream(1) | |
602 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) |
|
608 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) | |
603 | list(sendcommandframes(reactor, instream, 3, b'command2', {})) |
|
609 | list(sendcommandframes(reactor, instream, 3, b'command2', {})) | |
604 | list(sendcommandframes(reactor, instream, 5, b'command3', {})) |
|
610 | list(sendcommandframes(reactor, instream, 5, b'command3', {})) | |
605 |
|
611 | |||
606 | # Register results for commands out of order. |
|
612 | # Register results for commands out of order. | |
607 | outstream = framing.stream() |
|
613 | outstream = framing.stream(2) | |
608 | reactor.onbytesresponseready(outstream, 3, b'response3') |
|
614 | reactor.onbytesresponseready(outstream, 3, b'response3') | |
609 | reactor.onbytesresponseready(outstream, 1, b'response1') |
|
615 | reactor.onbytesresponseready(outstream, 1, b'response1') | |
610 | reactor.onbytesresponseready(outstream, 5, b'response5') |
|
616 | reactor.onbytesresponseready(outstream, 5, b'response5') | |
@@ -612,15 +618,15 b' class ServerReactorTests(unittest.TestCa' | |||||
612 | result = reactor.oninputeof() |
|
618 | result = reactor.oninputeof() | |
613 | self.assertaction(result, 'sendframes') |
|
619 | self.assertaction(result, 'sendframes') | |
614 | self.assertframesequal(result[1]['framegen'], [ |
|
620 | self.assertframesequal(result[1]['framegen'], [ | |
615 | b'3 bytes-response eos response3', |
|
621 | b'3 2 stream-begin bytes-response eos response3', | |
616 | b'1 bytes-response eos response1', |
|
622 | b'1 2 0 bytes-response eos response1', | |
617 | b'5 bytes-response eos response5', |
|
623 | b'5 2 0 bytes-response eos response5', | |
618 | ]) |
|
624 | ]) | |
619 |
|
625 | |||
620 | def testduplicaterequestonactivecommand(self): |
|
626 | def testduplicaterequestonactivecommand(self): | |
621 | """Receiving a request ID that matches a request that isn't finished.""" |
|
627 | """Receiving a request ID that matches a request that isn't finished.""" | |
622 | reactor = makereactor() |
|
628 | reactor = makereactor() | |
623 | stream = framing.stream() |
|
629 | stream = framing.stream(1) | |
624 | list(sendcommandframes(reactor, stream, 1, b'command1', {})) |
|
630 | list(sendcommandframes(reactor, stream, 1, b'command1', {})) | |
625 | results = list(sendcommandframes(reactor, stream, 1, b'command1', {})) |
|
631 | results = list(sendcommandframes(reactor, stream, 1, b'command1', {})) | |
626 |
|
632 | |||
@@ -632,9 +638,9 b' class ServerReactorTests(unittest.TestCa' | |||||
632 | def testduplicaterequestonactivecommandnosend(self): |
|
638 | def testduplicaterequestonactivecommandnosend(self): | |
633 | """Same as above but we've registered a response but haven't sent it.""" |
|
639 | """Same as above but we've registered a response but haven't sent it.""" | |
634 | reactor = makereactor() |
|
640 | reactor = makereactor() | |
635 | instream = framing.stream() |
|
641 | instream = framing.stream(1) | |
636 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) |
|
642 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) | |
637 | outstream = framing.stream() |
|
643 | outstream = framing.stream(2) | |
638 | reactor.onbytesresponseready(outstream, 1, b'response') |
|
644 | reactor.onbytesresponseready(outstream, 1, b'response') | |
639 |
|
645 | |||
640 | # We've registered the response but haven't sent it. From the |
|
646 | # We've registered the response but haven't sent it. From the | |
@@ -649,11 +655,11 b' class ServerReactorTests(unittest.TestCa' | |||||
649 | def testduplicaterequestargumentframe(self): |
|
655 | def testduplicaterequestargumentframe(self): | |
650 | """Variant on above except we sent an argument frame instead of name.""" |
|
656 | """Variant on above except we sent an argument frame instead of name.""" | |
651 | reactor = makereactor() |
|
657 | reactor = makereactor() | |
652 | stream = framing.stream() |
|
658 | stream = framing.stream(1) | |
653 | list(sendcommandframes(reactor, stream, 1, b'command', {})) |
|
659 | list(sendcommandframes(reactor, stream, 1, b'command', {})) | |
654 | results = list(sendframes(reactor, [ |
|
660 | results = list(sendframes(reactor, [ | |
655 | ffs(b'3 command-name have-args command'), |
|
661 | ffs(b'3 1 stream-begin command-name have-args command'), | |
656 | ffs(b'1 command-argument 0 ignored'), |
|
662 | ffs(b'1 1 0 command-argument 0 ignored'), | |
657 | ])) |
|
663 | ])) | |
658 | self.assertaction(results[0], 'wantframe') |
|
664 | self.assertaction(results[0], 'wantframe') | |
659 | self.assertaction(results[1], 'error') |
|
665 | self.assertaction(results[1], 'error') | |
@@ -664,9 +670,9 b' class ServerReactorTests(unittest.TestCa' | |||||
664 | def testduplicaterequestaftersend(self): |
|
670 | def testduplicaterequestaftersend(self): | |
665 | """We can use a duplicate request ID after we've sent the response.""" |
|
671 | """We can use a duplicate request ID after we've sent the response.""" | |
666 | reactor = makereactor() |
|
672 | reactor = makereactor() | |
667 | instream = framing.stream() |
|
673 | instream = framing.stream(1) | |
668 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) |
|
674 | list(sendcommandframes(reactor, instream, 1, b'command1', {})) | |
669 | outstream = framing.stream() |
|
675 | outstream = framing.stream(2) | |
670 | res = reactor.onbytesresponseready(outstream, 1, b'response') |
|
676 | res = reactor.onbytesresponseready(outstream, 1, b'response') | |
671 | list(res[1]['framegen']) |
|
677 | list(res[1]['framegen']) | |
672 |
|
678 |
General Comments 0
You need to be logged in to leave comments.
Login now