##// END OF EJS Templates
wireproto: convert human output frames to CBOR...
Gregory Szorc -
r37335:36d17f37 default
parent child Browse files
Show More
@@ -699,26 +699,16 b' wire protocol reserve the right to allow'
699 formatting strings with additional formatters, hence why ``%%`` is
699 formatting strings with additional formatters, hence why ``%%`` is
700 required to represent the literal ``%``.
700 required to represent the literal ``%``.
701
701
702 The raw frame consists of a series of data structures representing
702 The frame payload consists of a CBOR array of CBOR maps. Each map
703 textual atoms to print. Each atom begins with a struct defining the
703 defines an *atom* of text data to print. Each *atom* has the following
704 size of the data that follows:
704 bytestring keys:
705
705
706 * A 16-bit little endian unsigned integer denoting the length of the
706 msg
707 formatting string.
707 (bytestring) The formatting string. Content MUST be ASCII.
708 * An 8-bit unsigned integer denoting the number of label strings
708 args (optional)
709 that follow.
709 Array of bytestrings defining arguments to the formatting string.
710 * An 8-bit unsigned integer denoting the number of formatting string
710 labels (optional)
711 arguments strings that follow.
711 Array of bytestrings defining labels to apply to this atom.
712 * An array of 8-bit unsigned integers denoting the lengths of
713 *labels* data.
714 * An array of 16-bit unsigned integers denoting the lengths of
715 formatting strings.
716 * The formatting string, encoded as UTF-8.
717 * 0 or more ASCII strings defining labels to apply to this atom.
718 * 0 or more UTF-8 strings that will be used as arguments to the
719 formatting string.
720
721 TODO use ASCII for formatting string.
722
712
723 All data to be printed MUST be encoded into a single frame: this frame
713 All data to be printed MUST be encoded into a single frame: this frame
724 does not support spanning data across multiple frames.
714 does not support spanning data across multiple frames.
@@ -395,7 +395,8 b' def createerrorframe(stream, requestid, '
395 flags=flags,
395 flags=flags,
396 payload=msg)
396 payload=msg)
397
397
398 def createtextoutputframe(stream, requestid, atoms):
398 def createtextoutputframe(stream, requestid, atoms,
399 maxframesize=DEFAULT_MAX_FRAME_SIZE):
399 """Create a text output frame to render text to people.
400 """Create a text output frame to render text to people.
400
401
401 ``atoms`` is a 3-tuple of (formatting string, args, labels).
402 ``atoms`` is a 3-tuple of (formatting string, args, labels).
@@ -405,15 +406,9 b' def createtextoutputframe(stream, reques'
405 formatters to be applied at rendering time. In terms of the ``ui``
406 formatters to be applied at rendering time. In terms of the ``ui``
406 class, each atom corresponds to a ``ui.write()``.
407 class, each atom corresponds to a ``ui.write()``.
407 """
408 """
408 bytesleft = DEFAULT_MAX_FRAME_SIZE
409 atomdicts = []
409 atomchunks = []
410
410
411 for (formatting, args, labels) in atoms:
411 for (formatting, args, labels) in atoms:
412 if len(args) > 255:
413 raise ValueError('cannot use more than 255 formatting arguments')
414 if len(labels) > 255:
415 raise ValueError('cannot use more than 255 labels')
416
417 # TODO look for localstr, other types here?
412 # TODO look for localstr, other types here?
418
413
419 if not isinstance(formatting, bytes):
414 if not isinstance(formatting, bytes):
@@ -425,8 +420,8 b' def createtextoutputframe(stream, reques'
425 if not isinstance(label, bytes):
420 if not isinstance(label, bytes):
426 raise ValueError('must use bytes for labels')
421 raise ValueError('must use bytes for labels')
427
422
428 # Formatting string must be UTF-8.
423 # Formatting string must be ASCII.
429 formatting = formatting.decode(r'utf-8', r'replace').encode(r'utf-8')
424 formatting = formatting.decode(r'ascii', r'replace').encode(r'ascii')
430
425
431 # Arguments must be UTF-8.
426 # Arguments must be UTF-8.
432 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
427 args = [a.decode(r'utf-8', r'replace').encode(r'utf-8') for a in args]
@@ -435,36 +430,23 b' def createtextoutputframe(stream, reques'
435 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
430 labels = [l.decode(r'ascii', r'strict').encode(r'ascii')
436 for l in labels]
431 for l in labels]
437
432
438 if len(formatting) > 65535:
433 atom = {b'msg': formatting}
439 raise ValueError('formatting string cannot be longer than 64k')
434 if args:
440
435 atom[b'args'] = args
441 if any(len(a) > 65535 for a in args):
436 if labels:
442 raise ValueError('argument string cannot be longer than 64k')
437 atom[b'labels'] = labels
443
444 if any(len(l) > 255 for l in labels):
445 raise ValueError('label string cannot be longer than 255 bytes')
446
438
447 chunks = [
439 atomdicts.append(atom)
448 struct.pack(r'<H', len(formatting)),
449 struct.pack(r'<BB', len(labels), len(args)),
450 struct.pack(r'<' + r'B' * len(labels), *map(len, labels)),
451 struct.pack(r'<' + r'H' * len(args), *map(len, args)),
452 ]
453 chunks.append(formatting)
454 chunks.extend(labels)
455 chunks.extend(args)
456
440
457 atom = b''.join(chunks)
441 payload = cbor.dumps(atomdicts, canonical=True)
458 atomchunks.append(atom)
459 bytesleft -= len(atom)
460
442
461 if bytesleft < 0:
443 if len(payload) > maxframesize:
462 raise ValueError('cannot encode data in a single frame')
444 raise ValueError('cannot encode data in a single frame')
463
445
464 yield stream.makeframe(requestid=requestid,
446 yield stream.makeframe(requestid=requestid,
465 typeid=FRAME_TYPE_TEXT_OUTPUT,
447 typeid=FRAME_TYPE_TEXT_OUTPUT,
466 flags=0,
448 flags=0,
467 payload=b''.join(atomchunks))
449 payload=payload)
468
450
469 class stream(object):
451 class stream(object):
470 """Represents a logical unidirectional series of frames."""
452 """Represents a logical unidirectional series of frames."""
@@ -136,22 +136,6 b' class FrameTests(unittest.TestCase):'
136 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
136 ffs(b'1 1 0 command-data eos %s' % data.getvalue()),
137 ])
137 ])
138
138
139 def testtextoutputexcessiveargs(self):
140 """At most 255 formatting arguments are allowed."""
141 with self.assertRaisesRegexp(ValueError,
142 'cannot use more than 255 formatting'):
143 args = [b'x' for i in range(256)]
144 list(framing.createtextoutputframe(None, 1,
145 [(b'bleh', args, [])]))
146
147 def testtextoutputexcessivelabels(self):
148 """At most 255 labels are allowed."""
149 with self.assertRaisesRegexp(ValueError,
150 'cannot use more than 255 labels'):
151 labels = [b'l' for i in range(256)]
152 list(framing.createtextoutputframe(None, 1,
153 [(b'bleh', [], labels)]))
154
155 def testtextoutputformattingstringtype(self):
139 def testtextoutputformattingstringtype(self):
156 """Formatting string must be bytes."""
140 """Formatting string must be bytes."""
157 with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '):
141 with self.assertRaisesRegexp(ValueError, 'must use bytes formatting '):
@@ -168,31 +152,14 b' class FrameTests(unittest.TestCase):'
168 list(framing.createtextoutputframe(None, 1, [
152 list(framing.createtextoutputframe(None, 1, [
169 (b'foo', [], [b'foo'.decode('ascii')])]))
153 (b'foo', [], [b'foo'.decode('ascii')])]))
170
154
171 def testtextoutputtoolongformatstring(self):
172 with self.assertRaisesRegexp(ValueError,
173 'formatting string cannot be longer than'):
174 list(framing.createtextoutputframe(None, 1, [
175 (b'x' * 65536, [], [])]))
176
177 def testtextoutputtoolongargumentstring(self):
178 with self.assertRaisesRegexp(ValueError,
179 'argument string cannot be longer than'):
180 list(framing.createtextoutputframe(None, 1, [
181 (b'bleh', [b'x' * 65536], [])]))
182
183 def testtextoutputtoolonglabelstring(self):
184 with self.assertRaisesRegexp(ValueError,
185 'label string cannot be longer than'):
186 list(framing.createtextoutputframe(None, 1, [
187 (b'bleh', [], [b'x' * 65536])]))
188
189 def testtextoutput1simpleatom(self):
155 def testtextoutput1simpleatom(self):
190 stream = framing.stream(1)
156 stream = framing.stream(1)
191 val = list(framing.createtextoutputframe(stream, 1, [
157 val = list(framing.createtextoutputframe(stream, 1, [
192 (b'foo', [], [])]))
158 (b'foo', [], [])]))
193
159
194 self.assertEqual(val, [
160 self.assertEqual(val, [
195 ffs(br'1 1 stream-begin text-output 0 \x03\x00\x00\x00foo'),
161 ffs(b'1 1 stream-begin text-output 0 '
162 b"cbor:[{b'msg': b'foo'}]"),
196 ])
163 ])
197
164
198 def testtextoutput2simpleatoms(self):
165 def testtextoutput2simpleatoms(self):
@@ -203,8 +170,8 b' class FrameTests(unittest.TestCase):'
203 ]))
170 ]))
204
171
205 self.assertEqual(val, [
172 self.assertEqual(val, [
206 ffs(br'1 1 stream-begin text-output 0 '
173 ffs(b'1 1 stream-begin text-output 0 '
207 br'\x03\x00\x00\x00foo\x03\x00\x00\x00bar'),
174 b"cbor:[{b'msg': b'foo'}, {b'msg': b'bar'}]")
208 ])
175 ])
209
176
210 def testtextoutput1arg(self):
177 def testtextoutput1arg(self):
@@ -214,8 +181,8 b' class FrameTests(unittest.TestCase):'
214 ]))
181 ]))
215
182
216 self.assertEqual(val, [
183 self.assertEqual(val, [
217 ffs(br'1 1 stream-begin text-output 0 '
184 ffs(b'1 1 stream-begin text-output 0 '
218 br'\x06\x00\x00\x01\x04\x00foo %sval1'),
185 b"cbor:[{b'msg': b'foo %s', b'args': [b'val1']}]")
219 ])
186 ])
220
187
221 def testtextoutput2arg(self):
188 def testtextoutput2arg(self):
@@ -225,8 +192,8 b' class FrameTests(unittest.TestCase):'
225 ]))
192 ]))
226
193
227 self.assertEqual(val, [
194 self.assertEqual(val, [
228 ffs(br'1 1 stream-begin text-output 0 '
195 ffs(b'1 1 stream-begin text-output 0 '
229 br'\x09\x00\x00\x02\x03\x00\x05\x00foo %s %svalvalue'),
196 b"cbor:[{b'msg': b'foo %s %s', b'args': [b'val', b'value']}]")
230 ])
197 ])
231
198
232 def testtextoutput1label(self):
199 def testtextoutput1label(self):
@@ -236,8 +203,8 b' class FrameTests(unittest.TestCase):'
236 ]))
203 ]))
237
204
238 self.assertEqual(val, [
205 self.assertEqual(val, [
239 ffs(br'1 1 stream-begin text-output 0 '
206 ffs(b'1 1 stream-begin text-output 0 '
240 br'\x03\x00\x01\x00\x05foolabel'),
207 b"cbor:[{b'msg': b'foo', b'labels': [b'label']}]")
241 ])
208 ])
242
209
243 def testargandlabel(self):
210 def testargandlabel(self):
@@ -247,8 +214,9 b' class FrameTests(unittest.TestCase):'
247 ]))
214 ]))
248
215
249 self.assertEqual(val, [
216 self.assertEqual(val, [
250 ffs(br'1 1 stream-begin text-output 0 '
217 ffs(b'1 1 stream-begin text-output 0 '
251 br'\x06\x00\x01\x01\x05\x03\x00foo %slabelarg'),
218 b"cbor:[{b'msg': b'foo %s', b'args': [b'arg'], "
219 b"b'labels': [b'label']}]")
252 ])
220 ])
253
221
254 class ServerReactorTests(unittest.TestCase):
222 class ServerReactorTests(unittest.TestCase):
General Comments 0
You need to be logged in to leave comments. Login now