Mercurial > public > mercurial-scm > hg
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): |