comparison mercurial/wireprotoframing.py @ 37319:36d17f37db91

wireproto: convert human output frames to CBOR This is easier than rolling our own encoding format. As a bonus, some of our artificial limits around lengths of things went away because we are no longer using fixed length fields to hold sizes. Differential Revision: https://phab.mercurial-scm.org/D3067
author Gregory Szorc <gregory.szorc@gmail.com>
date Fri, 30 Mar 2018 14:52:32 -0700
parents e9aadee698cf
children 2f81926c7f89
comparison
equal deleted inserted replaced
37318:9954d0e2ad00 37319:36d17f37db91
393 yield stream.makeframe(requestid=requestid, 393 yield stream.makeframe(requestid=requestid,
394 typeid=FRAME_TYPE_ERROR_RESPONSE, 394 typeid=FRAME_TYPE_ERROR_RESPONSE,
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).
402 403
403 The formatting string contains ``%s`` tokens to be replaced by the 404 The formatting string contains ``%s`` tokens to be replaced by the
404 corresponding indexed entry in ``args``. ``labels`` is an iterable of 405 corresponding indexed entry in ``args``. ``labels`` is an iterable of
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):
420 raise ValueError('must use bytes formatting strings') 415 raise ValueError('must use bytes formatting strings')
421 for arg in args: 416 for arg in args:
423 raise ValueError('must use bytes for arguments') 418 raise ValueError('must use bytes for arguments')
424 for label in labels: 419 for label in labels:
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]
433 428
434 # Labels must be ASCII. 429 # Labels must be ASCII.
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 438
444 if any(len(l) > 255 for l in labels): 439 atomdicts.append(atom)
445 raise ValueError('label string cannot be longer than 255 bytes') 440
446 441 payload = cbor.dumps(atomdicts, canonical=True)
447 chunks = [ 442
448 struct.pack(r'<H', len(formatting)), 443 if len(payload) > maxframesize:
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
457 atom = b''.join(chunks)
458 atomchunks.append(atom)
459 bytesleft -= len(atom)
460
461 if bytesleft < 0:
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."""
471 453
472 def __init__(self, streamid, active=False): 454 def __init__(self, streamid, active=False):