D2639: thirdparty: start vendoring cbor python library

pulkit (Pulkit Goyal) phabricator at mercurial-scm.org
Sun Mar 4 16:11:29 UTC 2018


pulkit created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  CBOR stands for Concise Binary Object Representation, which is a data format
  which is very compact and extensible.
  
  This patch moves the python library which can serilaize and deserialize python
  objects to/from cbor formats. The library is taken from
  https://github.com/brianolson/cbor_py/ and files are taken from version 1.0.0.
  
  The library is not used yet and there are some test-check* fails all of which
  will be fixed in the next patch.
  
  I wanted to keep this commit to just import the library.
  
  One thing is changed while importing things is removing Carriage returns from
  README.md
  
  1. no-check-commit because we are importing a third party library

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2639

AFFECTED FILES
  mercurial/thirdparty/cbor/LICENSE
  mercurial/thirdparty/cbor/Makefile
  mercurial/thirdparty/cbor/README.md
  mercurial/thirdparty/cbor/c/cbor.h
  mercurial/thirdparty/cbor/c/cbormodule.c
  mercurial/thirdparty/cbor/cbor/VERSION.py
  mercurial/thirdparty/cbor/cbor/__init__.py
  mercurial/thirdparty/cbor/cbor/cbor.py
  mercurial/thirdparty/cbor/cbor/tagmap.py
  mercurial/thirdparty/cbor/cbor/tests/__init__.py
  mercurial/thirdparty/cbor/cbor/tests/test_cbor.py
  mercurial/thirdparty/cbor/cbor/tests/test_objects.py
  mercurial/thirdparty/cbor/cbor/tests/test_usage.py
  mercurial/thirdparty/cbor/cbor/tests/test_vectors.py
  mercurial/thirdparty/cbor/setup.py
  mercurial/thirdparty/cbor/utest.sh

CHANGE DETAILS

diff --git a/mercurial/thirdparty/cbor/utest.sh b/mercurial/thirdparty/cbor/utest.sh
new file mode 100755
--- /dev/null
+++ b/mercurial/thirdparty/cbor/utest.sh
@@ -0,0 +1,11 @@
+#!/bin/sh -x
+
+python -m cbor.tests.test_cbor
+python -m cbor.tests.test_objects
+python -m cbor.tests.test_usage
+python -m cbor.tests.test_vectors
+
+#python cbor/tests/test_cbor.py
+#python cbor/tests/test_objects.py
+#python cbor/tests/test_usage.py
+#python cbor/tests/test_vectors.py
diff --git a/mercurial/thirdparty/cbor/setup.py b/mercurial/thirdparty/cbor/setup.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/setup.py
@@ -0,0 +1,129 @@
+#! /usr/bin/env python
+# Copyright 2014 Brian Olson
+# 
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# 
+#     http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Thanks!
+# to Mic Bowman for a bunch of work and impetus on dumps(,sort_keys=)
+
+from distutils.command.build_ext import build_ext
+from distutils.errors import (CCompilerError, DistutilsExecError,
+    DistutilsPlatformError)
+import sys
+
+from setuptools import setup, Extension
+
+
+build_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError)
+if sys.platform == 'win32' and sys.version_info > (2, 6):
+    # 2.6's distutils.msvc9compiler can raise an IOError when failing to
+    # find the compiler
+    build_errors += (IOError,)
+
+
+class BuildError(Exception):
+    """Raised if compiling extensions failed."""
+
+
+class optional_build_ext(build_ext):
+    """build_ext implementation with optional C speedups."""
+
+    def run(self):
+        try:
+            build_ext.run(self)
+        except DistutilsPlatformError:
+            raise BuildError()
+
+    def build_extension(self, ext):
+        try:
+            build_ext.build_extension(self, ext)
+        except build_errors as be:
+            raise BuildError(be)
+        except ValueError as ve:
+            # this can happen on Windows 64 bit, see Python issue 7511
+            if "'path'" in str(sys.exc_info()[1]): # works with Python 2 and 3
+                raise BuildError(ve)
+            raise
+
+
+VERSION = eval(open('cbor/VERSION.py','rb').read())
+
+
+setup_options = dict(
+    name='cbor',
+    version=VERSION,
+    description='RFC 7049 - Concise Binary Object Representation',
+    long_description="""
+An implementation of RFC 7049 - Concise Binary Object Representation (CBOR).
+
+CBOR is comparable to JSON, has a superset of JSON's ability, but serializes to a binary format which is smaller and faster to generate and parse.
+
+The two primary functions are cbor.loads() and cbor.dumps().
+
+This library includes a C implementation which runs 3-5 times faster than the Python standard library's C-accelerated implementanion of JSON. This is also includes a 100% Python implementation.
+""",
+    author='Brian Olson',
+    author_email='bolson at bolson.org',
+    url='https://bitbucket.org/bodhisnarkva/cbor',
+    packages=['cbor'],
+    package_dir={'cbor':'cbor'},
+    ext_modules=[
+        Extension(
+            'cbor._cbor',
+            include_dirs=['c/'],
+            sources=['c/cbormodule.c'],
+            headers=['c/cbor.h'],
+        )
+    ],
+    license='Apache',
+    classifiers=[
+	'Development Status :: 5 - Production/Stable',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: C',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+    ],
+    cmdclass={'build_ext': optional_build_ext},
+)
+
+
+def main():
+    """ Perform setup with optional C speedups.
+
+    Optional extension compilation stolen from markupsafe, which again stole
+    it from simplejson. Creds to Bob Ippolito for the original code.
+    """
+    is_jython = 'java' in sys.platform
+    is_pypy = hasattr(sys, 'pypy_translation_info')
+
+    if is_jython or is_pypy:
+        del setup_options['ext_modules']
+
+    try:
+        setup(**setup_options)
+    except BuildError as be:
+        sys.stderr.write('''
+BUILD ERROR:
+  %s
+RETRYING WITHOUT C EXTENSIONS
+''' % (be,))
+        del setup_options['ext_modules']
+        setup(**setup_options)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/mercurial/thirdparty/cbor/cbor/tests/test_vectors.py b/mercurial/thirdparty/cbor/cbor/tests/test_vectors.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/tests/test_vectors.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+
+"""
+Test CBOR implementation against common "test vectors" set from
+https://github.com/cbor/test-vectors/
+"""
+
+import base64
+import json
+import logging
+import math
+import os
+import sys
+import unittest
+
+
+_IS_PY3 = sys.version_info[0] >= 3
+
+
+logger = logging.getLogger(__name__)
+
+
+#from cbor.cbor import dumps as pydumps
+from cbor.cbor import loads as pyloads
+try:
+    #from cbor._cbor import dumps as cdumps
+    from cbor._cbor import loads as cloads
+except ImportError:
+    # still test what we can without C fast mode
+    logger.warn('testing without C accelerated CBOR', exc_info=True)
+    #cdumps, cloads = None, None
+    cloads = None
+from cbor import Tag
+
+
+# Accomodate several test vectors that have diagnostic descriptors but not JSON
+_DIAGNOSTIC_TESTS = {
+    'Infinity': lambda x: x == float('Inf'),
+    '-Infinity': lambda x: x == float('-Inf'),
+    'NaN': math.isnan,
+    'undefined': lambda x: x is None,
+
+    # TODO: parse into datetime.datetime()
+    '0("2013-03-21T20:04:00Z")': lambda x: isinstance(x, Tag) and (x.tag == 0) and (x.value == '2013-03-21T20:04:00Z'),
+
+    "h''": lambda x: x == b'',
+    "(_ h'0102', h'030405')": lambda x: x == b'\x01\x02\x03\x04\x05',
+    '{1: 2, 3: 4}': lambda x: x == {1: 2, 3: 4},
+    "h'01020304'": lambda x: x == b'\x01\x02\x03\x04',
+}
+
+
+# We expect these to raise exception because they encode reserved/unused codes in the spec.
+# ['hex'] values of tests we expect to raise
+_EXPECT_EXCEPTION = set(['f0', 'f818', 'f8ff'])
+
+
+def _check(row, decoded):
+    cbdata = base64.b64decode(row['cbor'])
+    if cloads is not None:
+        cb = cloads(cbdata)
+        if cb != decoded:
+            anyerr = True
+            sys.stderr.write('expected {0!r} got {1!r} c failed to decode cbor {2}\n'.format(decoded, cb, base64.b16encode(cbdata)))
+
+    cb = pyloads(cbdata)
+    if cb != decoded:
+        anyerr = True
+        sys.stderr.write('expected {0!r} got {1!r} py failed to decode cbor {2}\n'.format(decoded, cb, base64.b16encode(cbdata)))
+
+
+def _check_foo(row, checkf):
+    cbdata = base64.b64decode(row['cbor'])
+    if cloads is not None:
+        cb = cloads(cbdata)
+        if not checkf(cb):
+            anyerr = True
+            sys.stderr.write('expected {0!r} got {1!r} c failed to decode cbor {2}\n'.format(decoded, cb, base64.b16encode(cbdata)))
+
+    cb = pyloads(cbdata)
+    if not checkf(cb):
+        anyerr = True
+        sys.stderr.write('expected {0!r} got {1!r} py failed to decode cbor {2}\n'.format(decoded, cb, base64.b16encode(cbdata)))
+
+
+class TestVectors(unittest.TestCase):
+        def test_vectors(self):
+            here = os.path.dirname(__file__)
+            jf = os.path.abspath(os.path.join(here, '../../../test-vectors/appendix_a.json'))
+            if not os.path.exists(jf):
+                logging.warning('cannot find test-vectors/appendix_a.json, tried: %r', jf)
+                return
+
+            if _IS_PY3:
+                testfile = open(jf, 'r')
+                tv = json.load(testfile)
+            else:
+                testfile = open(jf, 'rb')
+                tv = json.load(testfile)
+            anyerr = False
+            for row in tv:
+                rhex = row.get('hex')
+                if 'decoded' in row:
+                    decoded = row['decoded']
+                    _check(row, decoded)
+                    continue
+                elif 'diagnostic' in row:
+                    diag = row['diagnostic']
+                    checkf = _DIAGNOSTIC_TESTS.get(diag)
+                    if checkf is not None:
+                        _check_foo(row, checkf)
+                        continue
+
+                # variously verbose log of what we're not testing:
+                cbdata = base64.b64decode(row['cbor'])
+                try:
+                    pd = pyloads(cbdata)
+                except:
+                    if rhex and (rhex in _EXPECT_EXCEPTION):
+                        pass
+                    else:
+                        logging.error('failed to py load hex=%s diag=%r', rhex, row.get('diagnostic'), exc_info=True)
+                    pd = ''
+                cd = None
+                if cloads is not None:
+                    try:
+                        cd = cloads(cbdata)
+                    except:
+                        if rhex and (rhex in _EXPECT_EXCEPTION):
+                            pass
+                        else:
+                            logging.error('failed to c load hex=%s diag=%r', rhex, row.get('diagnostic'), exc_info=True)
+                        cd = ''
+                logging.warning('skipping hex=%s diag=%r py=%s c=%s', rhex, row.get('diagnostic'), pd, cd)
+            testfile.close()
+
+            assert not anyerr
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.main()
diff --git a/mercurial/thirdparty/cbor/cbor/tests/test_usage.py b/mercurial/thirdparty/cbor/cbor/tests/test_usage.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/tests/test_usage.py
@@ -0,0 +1,241 @@
+#!python
+from __future__ import absolute_import
+from __future__ import division  # / => float
+import gc
+import logging
+import os
+import resource
+import sys
+import tempfile
+import unittest
+
+from cbor.tests.test_cbor import _randob
+
+
+logger = logging.getLogger(__name__)
+
+
+try:
+    from cbor._cbor import dumps as cdumps
+    from cbor._cbor import loads as cloads
+    from cbor._cbor import dump as cdump
+    from cbor._cbor import load as cload
+except ImportError:
+    # still test what we can without C fast mode
+    logger.warn('testing without C accelerated CBOR', exc_info=True)
+    cdumps, cloads, cdump, cload = None, None, None, None
+
+
+
+_TEST_COUNT = 100000
+_TEST_OUTER = 5
+
+
+_IS_PY3 = sys.version_info[0] >= 3
+
+
+if _IS_PY3:
+    _range = range
+    from io import BytesIO as StringIO
+else:
+    _range = xrange
+    from cStringIO import StringIO
+
+
+class TestUsage(unittest.TestCase):
+    def test_dumps_usage(self):
+        '''
+        repeatedly serialize, check that usage doesn't go up
+        '''
+        if cdumps is None:
+            logger.warn('no C dumps(), skipping test_dumps_usage')
+            return
+        start_usage = resource.getrusage(resource.RUSAGE_SELF)
+        usage_history = [start_usage]
+        for o in _range(_TEST_OUTER):
+            for i in _range(_TEST_COUNT):
+                ob = _randob()
+                blob = cdumps(ob)
+                # and silently drop the result. I hope the garbage collector works!
+            t_usage = resource.getrusage(resource.RUSAGE_SELF)
+            usage_history.append(t_usage)
+        end_usage = usage_history[-1]
+        dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+        didrss = end_usage.ru_idrss - start_usage.ru_idrss
+        dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+        didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+        sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+        sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+        assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+        assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+    def test_loads_usage(self):
+        '''
+        repeatedly serialize, check that usage doesn't go up
+        '''
+        if (cdumps is None) or (cloads is None):
+            logger.warn('no C fast CBOR, skipping test_loads_usage')
+            return
+        ## Just a string passes!
+        #ob = 'sntaoheusnatoheusnaotehuasnoetuhaosentuhaoesnth'
+        ## Just an array passes!
+        #ob = [1,2,3,4,5,6,7,8,9,12,12,13]
+        ## Just a dict passes!
+        #ob = {'a':'b', 'c':'d', 'e':'f', 'g':'h'}
+        # dict of dict is doom!
+        #ob = {'a':{'b':'c', 'd':'e', 'f':'g'}, 'x':'p'}
+        ob = {'aoeu':[1,2,3,4],'foo':'bar','pants':{'foo':0xb44, 'pi':3.14}, 'flubber': [{'x':'y', 'z':[None, 2, []]}, 2, 'hello']}
+        blob = cdumps(ob)
+        start_usage = resource.getrusage(resource.RUSAGE_SELF)
+        usage_history = [start_usage]
+        for o in _range(_TEST_OUTER):
+            for i in _range(_TEST_COUNT):
+                dob = cloads(blob)
+                # and silently drop the result. I hope the garbage collector works!
+            t_usage = resource.getrusage(resource.RUSAGE_SELF)
+            usage_history.append(t_usage)
+        end_usage = usage_history[-1]
+        dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+        didrss = end_usage.ru_idrss - start_usage.ru_idrss
+        dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+        didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+        sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+        sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+        assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+        assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+    def test_tempfile(self):
+        '''repeatedly seralize to temp file, then repeatedly deserialize from
+        it, checking usage all along the way.
+        '''
+        if cdump is None:
+            logger.warn('no C dump(), skipping test_tempfile')
+            return
+        with tempfile.NamedTemporaryFile() as ntf:
+            # first, write a bunch to temp file
+            with open(ntf.name, 'wb') as fout:
+                sys.stderr.write('write {!r} {}\n'.format(ntf.name, fout))
+                start_usage = resource.getrusage(resource.RUSAGE_SELF)
+                usage_history = [start_usage]
+                for o in _range(_TEST_OUTER):
+                    for i in _range(_TEST_COUNT):
+                        ob = _randob()
+                        cdump(ob, fout)
+                    t_usage = resource.getrusage(resource.RUSAGE_SELF)
+                    usage_history.append(t_usage)
+                end_usage = usage_history[-1]
+                dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+                didrss = end_usage.ru_idrss - start_usage.ru_idrss
+                dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+                didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+                sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+                sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+                assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+                assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+            sys.stderr.write('{!r} is {} bytes\n'.format(ntf.name, os.path.getsize(ntf.name)))
+
+            # now, read a bunch back from temp file.
+            with open(ntf.name, 'rb') as fin:
+                sys.stderr.write('read {!r} {}\n'.format(ntf.name, fin))
+                start_usage = resource.getrusage(resource.RUSAGE_SELF)
+                usage_history = [start_usage]
+                for o in _range(_TEST_OUTER):
+                    for i in _range(_TEST_COUNT):
+                        dob = cload(fin)
+                        # and silently drop the result. I hope the garbage collector works!
+                    gc.collect()
+                    t_usage = resource.getrusage(resource.RUSAGE_SELF)
+                    usage_history.append(t_usage)
+                end_usage = usage_history[-1]
+                dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+                didrss = end_usage.ru_idrss - start_usage.ru_idrss
+                dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+                didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+                sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+                sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+                assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+                assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+    def test_stringio_usage(self):
+        '''serialize data to StringIO, read it back'''
+        if cdump is None:
+            logger.warn('no C dump(), skipping test_tempfile')
+            return
+
+        # warmup the rusage, allocate everything!
+        fout = StringIO()
+        sys.stderr.write('write 1 to StringIO\n')
+        oblist = []
+        for o in _range(_TEST_OUTER):
+            for i in _range(_TEST_COUNT):
+                ob = _randob()
+                oblist.append(ob)
+                cdump(ob, fout)
+
+        # position at start to overwrite, but leave allocated
+        fout.seek(0)
+
+        sys.stderr.write('write 2 to StringIO\n')
+        start_usage = resource.getrusage(resource.RUSAGE_SELF)
+        usage_history = [start_usage]
+        pos = 0
+        for o in _range(_TEST_OUTER):
+            for i in _range(_TEST_COUNT):
+                ob = oblist[pos]
+                pos += 1
+                cdump(ob, fout)
+            gc.collect()
+            t_usage = resource.getrusage(resource.RUSAGE_SELF)
+            usage_history.append(t_usage)
+        end_usage = usage_history[-1]
+        dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+        didrss = end_usage.ru_idrss - start_usage.ru_idrss
+        dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+        didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+        sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+        sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+        assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+        assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+        sys.stderr.write('StringIO is {} bytes\n'.format(fout.tell()))
+        fout.seek(0)
+
+        fin = fout
+        sys.stderr.write('read StringIO\n')
+        start_usage = resource.getrusage(resource.RUSAGE_SELF)
+        usage_history = [start_usage]
+        for o in _range(_TEST_OUTER):
+            for i in _range(_TEST_COUNT):
+                dob = cload(fin)
+                # and silently drop the result. I hope the garbage collector works!
+            gc.collect()
+            t_usage = resource.getrusage(resource.RUSAGE_SELF)
+            usage_history.append(t_usage)
+        end_usage = usage_history[-1]
+        dmaxrss = end_usage.ru_maxrss - start_usage.ru_maxrss
+        didrss = end_usage.ru_idrss - start_usage.ru_idrss
+        dmaxrsspct = ((end_usage.ru_maxrss != 0) and (dmaxrss / end_usage.ru_maxrss)) or 0
+        didrsspct = ((end_usage.ru_idrss != 0) and (didrss / end_usage.ru_idrss)) or 0
+
+        sys.stderr.write('maxrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_maxrss, end_usage.ru_maxrss, dmaxrss, dmaxrsspct * 100.0))
+        sys.stderr.write('idrss: {} - {}, d={} ({:.2f}%)\n'.format(start_usage.ru_idrss, end_usage.ru_idrss, didrss, didrsspct * 100.0))
+
+        assert (dmaxrsspct) < 0.05, [x.ru_maxrss for x in usage_history]
+        assert (didrsspct) < 0.05, [x.ru_idrss for x in usage_history]
+
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.DEBUG)
+    unittest.main()
diff --git a/mercurial/thirdparty/cbor/cbor/tests/test_objects.py b/mercurial/thirdparty/cbor/cbor/tests/test_objects.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/tests/test_objects.py
@@ -0,0 +1,82 @@
+import base64
+import sys
+import unittest
+
+
+from cbor.tagmap import ClassTag, TagMapper, Tag, UnknownTagException
+
+#try:
+from cbor.tests.test_cbor import TestPyPy, hexstr
+#except ImportError:
+#    from .test_cbor import TestPyPy, hexstr
+
+
+class SomeType(object):
+    "target type for translator tests"
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    @staticmethod
+    def to_cbor(ob):
+        assert isinstance(ob, SomeType)
+        return (ob.a, ob.b)
+
+    @staticmethod
+    def from_cbor(data):
+        return SomeType(*data)
+
+    def __eq__(self, other):
+        # why isn't this just the default implementation in the object class?
+        return isinstance(other, type(self)) and (self.__dict__ == other.__dict__)
+
+
+class UnknownType(object):
+    pass
+
+
+known_tags = [
+    ClassTag(4325, SomeType, SomeType.to_cbor, SomeType.from_cbor)
+]
+
+
+class TestObjects(unittest.TestCase):
+    def setUp(self):
+        self.tx = TagMapper(known_tags)
+
+    def _oso(self, ob):
+        ser = self.tx.dumps(ob)
+        try:
+            o2 = self.tx.loads(ser)
+            assert ob == o2, '%r != %r from %s' % (ob, o2, base64.b16encode(ser))
+        except Exception as e:
+            sys.stderr.write('failure on buf len={0} {1!r} ob={2!r} {3!r}; {4}\n'.format(len(ser), hexstr(ser), ob, ser, e))
+            raise
+
+    def test_basic(self):
+        self._oso(SomeType(1,2))
+
+    def test_unk_fail(self):
+        ok = False
+        try:
+            self.tx.dumps(UnknownType())
+        except:
+            ok = True
+        assert ok
+
+    def test_tag_passthrough(self):
+        self.tx.raise_on_unknown_tag = False
+        self._oso(Tag(1234, 'aoeu'))
+
+    def test_unk_tag_fail(self):
+        ok = False
+        self.tx.raise_on_unknown_tag = True
+        try:
+            self._oso(Tag(1234, 'aoeu'))
+        except UnknownTagException as ute:
+            ok = True
+        ok = False
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/mercurial/thirdparty/cbor/cbor/tests/test_cbor.py b/mercurial/thirdparty/cbor/cbor/tests/test_cbor.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/tests/test_cbor.py
@@ -0,0 +1,442 @@
+#!python
+# -*- coding: utf-8 -*-
+
+import base64
+import datetime
+import json
+import logging
+import random
+import sys
+import time
+import unittest
+import zlib
+
+
+logger = logging.getLogger(__name__)
+
+
+from cbor.cbor import dumps as pydumps
+from cbor.cbor import loads as pyloads
+from cbor.cbor import dump as pydump
+from cbor.cbor import load as pyload
+from cbor.cbor import Tag
+try:
+    from cbor._cbor import dumps as cdumps
+    from cbor._cbor import loads as cloads
+    from cbor._cbor import dump as cdump
+    from cbor._cbor import load as cload
+except ImportError:
+    # still test what we can without C fast mode
+    logger.warn('testing without C accelerated CBOR', exc_info=True)
+    cdumps, cloads, cdump, cload = None, None, None, None
+
+
+_IS_PY3 = sys.version_info[0] >= 3
+
+
+if _IS_PY3:
+    _range = range
+    from io import BytesIO as StringIO
+else:
+    _range = xrange
+    from cStringIO import StringIO
+
+
+class TestRoot(object):
+    @classmethod
+    def loads(cls, *args):
+        return cls._ld[0](*args)
+    @classmethod
+    def dumps(cls, *args, **kwargs):
+        return cls._ld[1](*args, **kwargs)
+    @classmethod
+    def speediterations(cls):
+        return cls._ld[2]
+    @classmethod
+    def load(cls, *args):
+        return cls._ld[3](*args)
+    @classmethod
+    def dump(cls, *args, **kwargs):
+        return cls._ld[4](*args, **kwargs)
+    @classmethod
+    def testable(cls):
+        ok = (cls._ld[0] is not None) and (cls._ld[1] is not None) and (cls._ld[3] is not None) and (cls._ld[4] is not None)
+        if not ok:
+            logger.warn('non-testable case %s skipped', cls.__name__)
+        return ok
+
+# Can't set class level function pointers, because then they expect a
+# (cls) first argument. So, toss them in a list to hide them.
+class TestPyPy(TestRoot):
+    _ld = [pyloads, pydumps, 1000, pyload, pydump]
+
+class TestPyC(TestRoot):
+    _ld = [pyloads, cdumps, 2000, pyload, cdump]
+
+class TestCPy(TestRoot):
+    _ld = [cloads, pydumps, 2000, cload, pydump]
+
+class TestCC(TestRoot):
+    _ld = [cloads, cdumps, 150000, cload, cdump]
+
+
+if _IS_PY3:
+    def _join_jsers(jsers):
+        return (''.join(jsers)).encode('utf8')
+    def hexstr(bs):
+        return ' '.join(map(lambda x: '{0:02x}'.format(x), bs))
+else:
+    def _join_jsers(jsers):
+        return b''.join(jsers)
+    def hexstr(bs):
+        return ' '.join(map(lambda x: '{0:02x}'.format(ord(x)), bs))
+
+
+class XTestCBOR(object):
+    def _oso(self, ob):
+        ser = self.dumps(ob)
+        try:
+            o2 = self.loads(ser)
+            assert ob == o2, '%r != %r from %s' % (ob, o2, base64.b16encode(ser))
+        except Exception as e:
+            sys.stderr.write('failure on buf len={0} {1!r} ob={2!r} {3!r}; {4}\n'.format(len(ser), hexstr(ser), ob, ser, e))
+            raise
+
+    def _osos(self, ob):
+        obs = self.dumps(ob)
+        o2 = self.loads(obs)
+        o2s = self.dumps(o2)
+        assert obs == o2s
+
+    def _oso_bytearray(self, ob):
+        ser = self.dumps(ob)
+        try:
+            o2 = self.loads(bytearray(ser))
+            assert ob == o2, '%r != %r from %s' % (ob, o2, base64.b16encode(ser))
+        except Exception as e:
+            sys.stderr.write('failure on buf len={0} {1!r} ob={2!r} {3!r}; {4}\n'.format(len(ser), hexstr(ser), ob, ser, e))
+            raise
+
+    test_objects = [
+        1,
+        0,
+        True,
+        False,
+        None,
+        -1,
+        -1.5,
+        1.5,
+        1000,
+        -1000,
+        1000000000,
+        2376030000,
+        -1000000000,
+        1000000000000000,
+        -1000000000000000,
+        [],
+        [1,2,3],
+        {},
+        b'aoeu1234\x00\xff',
+        u'åöéûのかめ亀',
+        b'',
+        u'',
+        Tag(1234, 'aoeu'),
+    ]
+
+    def test_basic(self):
+        if not self.testable(): return
+        for ob in self.test_objects:
+            self._oso(ob)
+
+    def test_basic_bytearray(self):
+        if not self.testable(): return
+        xoso = self._oso
+        self._oso = self._oso_bytearray
+        try:
+            self.test_basic()
+        finally:
+            self._oso = xoso
+
+    def test_random_ints(self):
+        if not self.testable(): return
+        icount = self.speediterations()
+        for i in _range(icount):
+            v = random.randint(-4294967295, 0xffffffff)
+            self._oso(v)
+        oldv = []
+        for i in _range(int(icount / 10)):
+            v = random.randint(-1000000000000000000000, 1000000000000000000000)
+            self._oso(v)
+            oldv.append(v)
+
+    def test_randobs(self):
+        if not self.testable(): return
+        icount = self.speediterations()
+        for i in _range(icount):
+            ob = _randob()
+            self._oso(ob)
+
+    def test_tuple(self):
+        if not self.testable(): return
+        l = [1,2,3]
+        t = tuple(l)
+        ser = self.dumps(t)
+        o2 = self.loads(ser)
+        assert l == o2
+
+    def test_speed_vs_json(self):
+        if not self.testable(): return
+        # It should be noted that the python standard library has a C implementation of key parts of json encoding and decoding
+        icount = self.speediterations()
+        obs = [_randob_notag() for x in _range(icount)]
+        st = time.time()
+        bsers = [self.dumps(o) for o in obs]
+        nt = time.time()
+        cbor_ser_time = nt - st
+        jsers = [json.dumps(o) for o in obs]
+        jt = time.time()
+        json_ser_time = jt - nt
+        cbor_byte_count = sum(map(len, bsers))
+        json_byte_count = sum(map(len, jsers))
+        sys.stderr.write(
+            'serialized {nobs} objects into {cb} cbor bytes in {ct:.2f} seconds ({cops:.2f}/s, {cbps:.1f}B/s) and {jb} json bytes in {jt:.2f} seconds ({jops:.2f}/s, {jbps:.1f}B/s)\n'.format(
+            nobs=len(obs),
+            cb=cbor_byte_count,
+            ct=cbor_ser_time,
+            cops=len(obs) / cbor_ser_time,
+            cbps=cbor_byte_count / cbor_ser_time,
+            jb=json_byte_count,
+            jt=json_ser_time,
+            jops=len(obs) / json_ser_time,
+            jbps=json_byte_count / json_ser_time))
+        bsersz = zlib.compress(b''.join(bsers))
+        jsersz = zlib.compress(_join_jsers(jsers))
+        sys.stderr.write('compress to {0} bytes cbor.gz and {1} bytes json.gz\n'.format(
+            len(bsersz), len(jsersz)))
+
+        st = time.time()
+        bo2 = [self.loads(b) for b in bsers]
+        bt = time.time()
+        cbor_load_time = bt - st
+        jo2 = [json.loads(b) for b in jsers]
+        jt = time.time()
+        json_load_time = jt - bt
+        sys.stderr.write('load {nobs} objects from cbor in {ct:.2f} secs ({cops:.2f}/sec, {cbps:.1f}B/s) and json in {jt:.2f} ({jops:.2f}/sec, {jbps:.1f}B/s)\n'.format(
+            nobs=len(obs),
+            ct=cbor_load_time,
+            cops=len(obs) / cbor_load_time,
+            cbps=cbor_byte_count / cbor_load_time,
+            jt=json_load_time,
+            jops=len(obs) / json_load_time,
+            jbps=json_byte_count / json_load_time
+        ))
+
+    def test_loads_none(self):
+        if not self.testable(): return
+        try:
+            ob = self.loads(None)
+            assert False, "expected ValueError when passing in None"
+        except ValueError:
+            pass
+
+    def test_concat(self):
+        "Test that we can concatenate output and retrieve the objects back out."
+        if not self.testable(): return
+        self._oso(self.test_objects)
+        fob = StringIO()
+
+        for ob in self.test_objects:
+            self.dump(ob, fob)
+        fob.seek(0)
+        obs2 = []
+        try:
+            while True:
+                obs2.append(self.load(fob))
+        except EOFError:
+            pass
+        assert obs2 == self.test_objects
+
+    # TODO: find more bad strings with which to fuzz CBOR
+    def test_badread(self):
+        if not self.testable(): return
+        try:
+            ob = self.loads(b'\xff')
+            assert False, 'badread should have failed'
+        except ValueError as ve:
+            #logger.info('error', exc_info=True)
+            pass
+        except Exception as ex:
+            logger.info('unexpected error!', exc_info=True)
+            assert False, 'unexpected error' + str(ex)
+
+    def test_datetime(self):
+        if not self.testable(): return
+        # right now we're just testing that it's possible to dumps()
+        # Tag(0,...) because there was a bug around that.
+        xb = self.dumps(Tag(0, datetime.datetime(1984,1,24,23,22,21).isoformat()))
+
+    def test_sortkeys(self):
+        if not self.testable(): return
+        obytes = []
+        xbytes = []
+        for n in _range(2, 27):
+            ob = {u'{:02x}'.format(x):x for x in _range(n)}
+            obytes.append(self.dumps(ob, sort_keys=True))
+            xbytes.append(self.dumps(ob, sort_keys=False))
+        allOGood = True
+        someXMiss = False
+        for i, g in enumerate(_GOLDEN_SORTED_KEYS_BYTES):
+            if g != obytes[i]:
+                logger.error('bad sorted result, wanted %r got %r', g, obytes[i])
+                allOGood = False
+            if g != xbytes[i]:
+                someXMiss = True
+
+        assert allOGood
+        assert someXMiss
+
+
+_GOLDEN_SORTED_KEYS_BYTES = [
+b'\xa2b00\x00b01\x01',
+b'\xa3b00\x00b01\x01b02\x02',
+b'\xa4b00\x00b01\x01b02\x02b03\x03',
+b'\xa5b00\x00b01\x01b02\x02b03\x03b04\x04',
+b'\xa6b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05',
+b'\xa7b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06',
+b'\xa8b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07',
+b'\xa9b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08',
+b'\xaab00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\t',
+b'\xabb00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\n',
+b'\xacb00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0b',
+b'\xadb00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0c',
+b'\xaeb00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\r',
+b'\xafb00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0e',
+b'\xb0b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0f',
+b'\xb1b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10',
+b'\xb2b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11',
+b'\xb3b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12',
+b'\xb4b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13',
+b'\xb5b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14',
+b'\xb6b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14b15\x15',
+b'\xb7b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14b15\x15b16\x16',
+b'\xb8\x18b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14b15\x15b16\x16b17\x17',
+b'\xb8\x19b00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14b15\x15b16\x16b17\x17b18\x18\x18',
+b'\xb8\x1ab00\x00b01\x01b02\x02b03\x03b04\x04b05\x05b06\x06b07\x07b08\x08b09\tb0a\nb0b\x0bb0c\x0cb0d\rb0e\x0eb0f\x0fb10\x10b11\x11b12\x12b13\x13b14\x14b15\x15b16\x16b17\x17b18\x18\x18b19\x18\x19',
+]
+
+def gen_sorted_bytes():
+    for n in _range(2, 27):
+        sys.stdout.write(repr(cbor.dumps({u'{:02x}'.format(x):x for x in _range(n)}, sort_keys=True)) + ',\n')
+
+def gen_unsorted_bytes():
+    for n in _range(2, 27):
+        sys.stdout.write(repr(cbor.dumps({u'{:02x}'.format(x):x for x in _range(n)}, sort_keys=False)) + ',\n')
+
+
+class TestCBORPyPy(unittest.TestCase, XTestCBOR, TestPyPy):
+    pass
+
+class TestCBORCPy(unittest.TestCase, XTestCBOR, TestCPy):
+    pass
+
+class TestCBORPyC(unittest.TestCase, XTestCBOR, TestPyC):
+    pass
+
+class TestCBORCC(unittest.TestCase, XTestCBOR, TestCC):
+    pass
+
+
+def _randob():
+    return _randob_x(_randob_probabilities, _randob_probsum, _randob)
+
+def _randob_notag():
+    return _randob_x(_randob_probabilities_notag, _randob_notag_probsum, _randob_notag)
+
+def _randArray(randob=_randob):
+    return [randob() for x in _range(random.randint(0,5))]
+
+_chars = [chr(x) for x in _range(ord(' '), ord('~'))]
+
+def _randStringOrBytes(randob=_randob):
+    tstr = ''.join([random.choice(_chars) for x in _range(random.randint(1,10))])
+    if random.randint(0,1) == 1:
+        if _IS_PY3:
+            # default str is unicode
+            # sometimes squash to bytes
+            return tstr.encode('utf8')
+        else:
+            # default str is bytes
+            # sometimes promote to unicode string
+            return tstr.decode('utf8')
+    return tstr
+
+def _randString(randob=_randob):
+    return ''.join([random.choice(_chars) for x in _range(random.randint(1,10))])
+
+def _randDict(randob=_randob):
+    ob = {}
+    for x in _range(random.randint(0,5)):
+        ob[_randString()] = randob()
+    return ob
+
+
+def _randTag(randob=_randob):
+    t = Tag()
+    # Tags 0..36 are know standard things we might implement special
+    # decoding for. This number will grow over time, and this test
+    # need to be adjusted to only assign unclaimed tags for Tag<->Tag
+    # encode-decode testing.
+    t.tag = random.randint(37, 1000000)
+    t.value = randob()
+    return t
+
+def _randInt(randob=_randob):
+    return random.randint(-4294967295, 4294967295)
+
+def _randBignum(randob=_randob):
+    return random.randint(-1000000000000000000000, 1000000000000000000000)
+
+def _randFloat(randob=_randob):
+    return random.random()
+
+_CONSTANTS = (True, False, None)
+def _randConst(randob=_randob):
+    return random.choice(_CONSTANTS)
+
+_randob_probabilities = [
+    (0.1, _randDict),
+    (0.1, _randTag),
+    (0.2, _randArray),
+    (0.3, _randStringOrBytes),
+    (0.3, _randInt),
+    (0.2, _randBignum),
+    (0.2, _randFloat),
+    (0.2, _randConst),
+]
+
+_randob_probsum = sum([x[0] for x in _randob_probabilities])
+
+_randob_probabilities_notag = [
+    (0.1, _randDict),
+    (0.2, _randArray),
+    (0.3, _randString),
+    (0.3, _randInt),
+    (0.2, _randBignum),
+    (0.2, _randFloat),
+    (0.2, _randConst),
+]
+
+_randob_notag_probsum = sum([x[0] for x in _randob_probabilities_notag])
+
+def _randob_x(probs=_randob_probabilities, probsum=_randob_probsum, randob=_randob):
+    pos = random.uniform(0, probsum)
+    for p, op in probs:
+        if pos < p:
+            return op(randob)
+        pos -= p
+    return None
+
+
+if __name__ == '__main__':
+    logging.basicConfig(level=logging.INFO)
+    unittest.main()
diff --git a/mercurial/thirdparty/cbor/cbor/tests/__init__.py b/mercurial/thirdparty/cbor/cbor/tests/__init__.py
new file mode 100644
diff --git a/mercurial/thirdparty/cbor/cbor/tagmap.py b/mercurial/thirdparty/cbor/cbor/tagmap.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/tagmap.py
@@ -0,0 +1,142 @@
+try:
+    # try C library _cbor.so
+    from ._cbor import loads, dumps, load, dump
+except:
+    # fall back to 100% python implementation
+    from .cbor import loads, dumps, load, dump
+
+from .cbor import Tag, CBOR_TAG_CBOR, _IS_PY3
+
+
+class ClassTag(object):
+    '''
+    For some CBOR tag_number, encode/decode Python class_type.
+    class_type manily used for isintance(foo, class_type)
+    Call encode_function() taking a Python instance and returning CBOR primitive types.
+    Call decode_function() on CBOR primitive types and return an instance of the Python class_type (a factory function).
+    '''
+    def __init__(self, tag_number, class_type, encode_function, decode_function):
+        self.tag_number = tag_number
+        self.class_type = class_type
+        self.encode_function = encode_function
+        self.decode_function = decode_function
+
+
+# TODO: This would be more efficient if it moved into cbor.py and
+# cbormodule.c, happening inline so that there is only one traversal
+# of the objects. But that would require two implementations. When
+# this API has been used more and can be considered settled I should
+# do that. -- Brian Olson 20140917_172229
+class TagMapper(object):
+    '''
+    Translate Python objects and CBOR tagged data.
+    Use the CBOR TAG system to note that some data is of a certain class.
+    Dump while translating Python objects into a CBOR compatible representation.
+    Load and translate CBOR primitives back into Python objects.
+    '''
+    def __init__(self, class_tags=None, raise_on_unknown_tag=False):
+        '''
+        class_tags: list of ClassTag objects
+        '''
+        self.class_tags = class_tags
+        self.raise_on_unknown_tag = raise_on_unknown_tag
+
+    def encode(self, obj):
+        for ct in self.class_tags:
+            if (ct.class_type is None) or (ct.encode_function is None):
+                continue
+            if isinstance(obj, ct.class_type):
+                return Tag(ct.tag_number, ct.encode_function(obj))
+        if isinstance(obj, (list, tuple)):
+            return [self.encode(x) for x in obj]
+        if isinstance(obj, dict):
+            # assume key is a primitive
+            # can't do this in Python 2.6:
+            #return {k:self.encode(v) for k,v in obj.iteritems()}
+            out = {}
+            if _IS_PY3:
+                items = obj.items()
+            else:
+                items = obj.iteritems()
+            for k,v in items:
+                out[k] = self.encode(v)
+            return out
+        # fall through, let underlying cbor.dump decide if it can encode object
+        return obj
+
+    def decode(self, obj):
+        if isinstance(obj, Tag):
+            for ct in self.class_tags:
+                if ct.tag_number == obj.tag:
+                    return ct.decode_function(obj.value)
+            # unknown Tag
+            if self.raise_on_unknown_tag:
+                raise UnknownTagException(str(obj.tag))
+            # otherwise, pass it through
+            return obj
+        if isinstance(obj, list):
+            # update in place. cbor only decodes to list, not tuple
+            for i,v in enumerate(obj):
+                obj[i] = self.decode(v)
+            return obj
+        if isinstance(obj, dict):
+            # update in place
+            if _IS_PY3:
+                items = obj.items()
+            else:
+                items = obj.iteritems()
+            for k,v in items:
+                # assume key is a primitive
+                obj[k] = self.decode(v)
+            return obj
+        # non-recursive object (num,bool,blob,string)
+        return obj
+
+    def dump(self, obj, fp):
+        dump(self.encode(obj), fp)
+
+    def dumps(self, obj):
+        return dumps(self.encode(obj))
+
+    def load(self, fp):
+        return self.decode(load(fp))
+
+    def loads(self, blob):
+        return self.decode(loads(blob))
+
+
+class WrappedCBOR(ClassTag):
+    """Handles Tag 24, where a byte array is sub encoded CBOR.
+    Unpacks sub encoded object on finding such a tag.
+    Does not convert anyting into such a tag.
+
+    Usage:
+>>> import cbor
+>>> import cbor.tagmap
+>>> tm=cbor.TagMapper([cbor.tagmap.WrappedCBOR()])
+>>> x = cbor.dumps(cbor.Tag(24, cbor.dumps({"a":[1,2,3]})))
+>>> x
+'\xd8\x18G\xa1Aa\x83\x01\x02\x03'
+>>> tm.loads(x)
+{'a': [1L, 2L, 3L]}
+>>> cbor.loads(x)
+Tag(24L, '\xa1Aa\x83\x01\x02\x03')
+"""
+    def __init__(self):
+        super(WrappedCBOR, self).__init__(CBOR_TAG_CBOR, None, None, loads)
+
+    @staticmethod
+    def wrap(ob):
+        return Tag(CBOR_TAG_CBOR, dumps(ob))
+
+    @staticmethod
+    def dump(ob, fp):
+        return dump(Tag(CBOR_TAG_CBOR, dumps(ob)), fp)
+
+    @staticmethod
+    def dumps(ob):
+        return dumps(Tag(CBOR_TAG_CBOR, dumps(ob)))
+
+
+class UnknownTagException(BaseException):
+    pass
diff --git a/mercurial/thirdparty/cbor/cbor/cbor.py b/mercurial/thirdparty/cbor/cbor/cbor.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/cbor.py
@@ -0,0 +1,510 @@
+#!python
+# -*- Python -*-
+
+import datetime
+import re
+import struct
+import sys
+
+_IS_PY3 = sys.version_info[0] >= 3
+
+if _IS_PY3:
+    from io import BytesIO as StringIO
+else:
+    try:
+        from cStringIO import StringIO
+    except:
+        from StringIO import StringIO
+
+
+CBOR_TYPE_MASK = 0xE0  # top 3 bits
+CBOR_INFO_BITS = 0x1F  # low 5 bits
+
+
+CBOR_UINT    = 0x00
+CBOR_NEGINT  = 0x20
+CBOR_BYTES   = 0x40
+CBOR_TEXT    = 0x60
+CBOR_ARRAY   = 0x80
+CBOR_MAP     = 0xA0
+CBOR_TAG     = 0xC0
+CBOR_7       = 0xE0  # float and other types
+
+CBOR_UINT8_FOLLOWS  = 24  # 0x18
+CBOR_UINT16_FOLLOWS = 25  # 0x19
+CBOR_UINT32_FOLLOWS = 26  # 0x1a
+CBOR_UINT64_FOLLOWS = 27  # 0x1b
+CBOR_VAR_FOLLOWS    = 31  # 0x1f
+
+CBOR_BREAK  = 0xFF
+
+CBOR_FALSE  = (CBOR_7 | 20)
+CBOR_TRUE   = (CBOR_7 | 21)
+CBOR_NULL   = (CBOR_7 | 22)
+CBOR_UNDEFINED   = (CBOR_7 | 23)  # js 'undefined' value
+
+CBOR_FLOAT16 = (CBOR_7 | 25)
+CBOR_FLOAT32 = (CBOR_7 | 26)
+CBOR_FLOAT64 = (CBOR_7 | 27)
+
+CBOR_TAG_DATE_STRING = 0 # RFC3339
+CBOR_TAG_DATE_ARRAY = 1 # any number type follows, seconds since 1970-01-01T00:00:00 UTC
+CBOR_TAG_BIGNUM = 2 # big endian byte string follows
+CBOR_TAG_NEGBIGNUM = 3 # big endian byte string follows
+CBOR_TAG_DECIMAL = 4 # [ 10^x exponent, number ]
+CBOR_TAG_BIGFLOAT = 5 # [ 2^x exponent, number ]
+CBOR_TAG_BASE64URL = 21
+CBOR_TAG_BASE64 = 22
+CBOR_TAG_BASE16 = 23
+CBOR_TAG_CBOR = 24 # following byte string is embedded CBOR data
+
+CBOR_TAG_URI = 32
+CBOR_TAG_BASE64URL = 33
+CBOR_TAG_BASE64 = 34
+CBOR_TAG_REGEX = 35
+CBOR_TAG_MIME = 36 # following text is MIME message, headers, separators and all
+CBOR_TAG_CBOR_FILEHEADER = 55799 # can open a file with 0xd9d9f7
+
+_CBOR_TAG_BIGNUM_BYTES = struct.pack('B', CBOR_TAG | CBOR_TAG_BIGNUM)
+
+
+def dumps_int(val):
+    "return bytes representing int val in CBOR"
+    if val >= 0:
+        # CBOR_UINT is 0, so I'm lazy/efficient about not OR-ing it in.
+        if val <= 23:
+            return struct.pack('B', val)
+        if val <= 0x0ff:
+            return struct.pack('BB', CBOR_UINT8_FOLLOWS, val)
+        if val <= 0x0ffff:
+            return struct.pack('!BH', CBOR_UINT16_FOLLOWS, val)
+        if val <= 0x0ffffffff:
+            return struct.pack('!BI', CBOR_UINT32_FOLLOWS, val)
+        if val <= 0x0ffffffffffffffff:
+            return struct.pack('!BQ', CBOR_UINT64_FOLLOWS, val)
+        outb = _dumps_bignum_to_bytearray(val)
+        return _CBOR_TAG_BIGNUM_BYTES + _encode_type_num(CBOR_BYTES, len(outb)) + outb
+    val = -1 - val
+    return _encode_type_num(CBOR_NEGINT, val)
+
+
+if _IS_PY3:
+    def _dumps_bignum_to_bytearray(val):
+        out = []
+        while val > 0:
+            out.insert(0, val & 0x0ff)
+            val = val >> 8
+        return bytes(out)
+else:
+    def _dumps_bignum_to_bytearray(val):
+        out = []
+        while val > 0:
+            out.insert(0, chr(val & 0x0ff))
+            val = val >> 8
+        return b''.join(out)
+
+
+def dumps_float(val):
+    return struct.pack("!Bd", CBOR_FLOAT64, val)
+
+
+_CBOR_TAG_NEGBIGNUM_BYTES = struct.pack('B', CBOR_TAG | CBOR_TAG_NEGBIGNUM)
+
+
+def _encode_type_num(cbor_type, val):
+    """For some CBOR primary type [0..7] and an auxiliary unsigned number, return CBOR encoded bytes"""
+    assert val >= 0
+    if val <= 23:
+        return struct.pack('B', cbor_type | val)
+    if val <= 0x0ff:
+        return struct.pack('BB', cbor_type | CBOR_UINT8_FOLLOWS, val)
+    if val <= 0x0ffff:
+        return struct.pack('!BH', cbor_type | CBOR_UINT16_FOLLOWS, val)
+    if val <= 0x0ffffffff:
+        return struct.pack('!BI', cbor_type | CBOR_UINT32_FOLLOWS, val)
+    if (((cbor_type == CBOR_NEGINT) and (val <= 0x07fffffffffffffff)) or
+        ((cbor_type != CBOR_NEGINT) and (val <= 0x0ffffffffffffffff))):
+        return struct.pack('!BQ', cbor_type | CBOR_UINT64_FOLLOWS, val)
+    if cbor_type != CBOR_NEGINT:
+        raise Exception("value too big for CBOR unsigned number: {0!r}".format(val))
+    outb = _dumps_bignum_to_bytearray(val)
+    return _CBOR_TAG_NEGBIGNUM_BYTES + _encode_type_num(CBOR_BYTES, len(outb)) + outb
+
+
+if _IS_PY3:
+    def _is_unicode(val):
+        return isinstance(val, str)
+else:
+    def _is_unicode(val):
+        return isinstance(val, unicode)
+
+
+def dumps_string(val, is_text=None, is_bytes=None):
+    if _is_unicode(val):
+        val = val.encode('utf8')
+        is_text = True
+        is_bytes = False
+    if (is_bytes) or not (is_text == True):
+        return _encode_type_num(CBOR_BYTES, len(val)) + val
+    return _encode_type_num(CBOR_TEXT, len(val)) + val
+
+
+def dumps_array(arr, sort_keys=False):
+    head = _encode_type_num(CBOR_ARRAY, len(arr))
+    parts = [dumps(x, sort_keys=sort_keys) for x in arr]
+    return head + b''.join(parts)
+
+
+if _IS_PY3:
+    def dumps_dict(d, sort_keys=False):
+        head = _encode_type_num(CBOR_MAP, len(d))
+        parts = [head]
+        if sort_keys:
+            for k in sorted(d.keys()):
+                v = d[k]
+                parts.append(dumps(k, sort_keys=sort_keys))
+                parts.append(dumps(v, sort_keys=sort_keys))
+        else:
+            for k,v in d.items():
+                parts.append(dumps(k, sort_keys=sort_keys))
+                parts.append(dumps(v, sort_keys=sort_keys))
+        return b''.join(parts)
+else:
+    def dumps_dict(d, sort_keys=False):
+        head = _encode_type_num(CBOR_MAP, len(d))
+        parts = [head]
+        if sort_keys:
+            for k in sorted(d.iterkeys()):
+                v = d[k]
+                parts.append(dumps(k, sort_keys=sort_keys))
+                parts.append(dumps(v, sort_keys=sort_keys))
+        else:
+            for k,v in d.iteritems():
+                parts.append(dumps(k, sort_keys=sort_keys))
+                parts.append(dumps(v, sort_keys=sort_keys))
+        return b''.join(parts)
+
+
+def dumps_bool(b):
+    if b:
+        return struct.pack('B', CBOR_TRUE)
+    return struct.pack('B', CBOR_FALSE)
+
+
+def dumps_tag(t, sort_keys=False):
+    return _encode_type_num(CBOR_TAG, t.tag) + dumps(t.value, sort_keys=sort_keys)
+    
+
+if _IS_PY3:
+    def _is_stringish(x):
+        return isinstance(x, (str, bytes))
+    def _is_intish(x):
+        return isinstance(x, int)
+else:
+    def _is_stringish(x):
+        return isinstance(x, (str, basestring, bytes, unicode))
+    def _is_intish(x):
+        return isinstance(x, (int, long))
+
+
+def dumps(ob, sort_keys=False):
+    if ob is None:
+        return struct.pack('B', CBOR_NULL)
+    if isinstance(ob, bool):
+        return dumps_bool(ob)
+    if _is_stringish(ob):
+        return dumps_string(ob)
+    if isinstance(ob, (list, tuple)):
+        return dumps_array(ob, sort_keys=sort_keys)
+    # TODO: accept other enumerables and emit a variable length array
+    if isinstance(ob, dict):
+        return dumps_dict(ob, sort_keys=sort_keys)
+    if isinstance(ob, float):
+        return dumps_float(ob)
+    if _is_intish(ob):
+        return dumps_int(ob)
+    if isinstance(ob, Tag):
+        return dumps_tag(ob, sort_keys=sort_keys)
+    raise Exception("don't know how to cbor serialize object of type %s", type(ob))
+
+
+# same basic signature as json.dump, but with no options (yet)
+def dump(obj, fp, sort_keys=False):
+    """
+    obj: Python object to serialize
+    fp: file-like object capable of .write(bytes)
+    """
+    # this is kinda lame, but probably not inefficient for non-huge objects
+    # TODO: .write() to fp as we go as each inner object is serialized
+    blob = dumps(obj, sort_keys=sort_keys)
+    fp.write(blob)
+
+
+class Tag(object):
+    def __init__(self, tag=None, value=None):
+        self.tag = tag
+        self.value = value
+
+    def __repr__(self):
+        return "Tag({0!r}, {1!r})".format(self.tag, self.value)
+
+    def __eq__(self, other):
+        if not isinstance(other, Tag):
+            return False
+        return (self.tag == other.tag) and (self.value == other.value)
+
+
+def loads(data):
+    """
+    Parse CBOR bytes and return Python objects.
+    """
+    if data is None:
+        raise ValueError("got None for buffer to decode in loads")
+    fp = StringIO(data)
+    return _loads(fp)[0]
+
+
+def load(fp):
+    """
+    Parse and return object from fp, a file-like object supporting .read(n)
+    """
+    return _loads(fp)[0]
+
+
+_MAX_DEPTH = 100
+
+
+def _tag_aux(fp, tb):
+    bytes_read = 1
+    tag = tb & CBOR_TYPE_MASK
+    tag_aux = tb & CBOR_INFO_BITS
+    if tag_aux <= 23:
+        aux = tag_aux
+    elif tag_aux == CBOR_UINT8_FOLLOWS:
+        data = fp.read(1)
+        aux = struct.unpack_from("!B", data, 0)[0]
+        bytes_read += 1
+    elif tag_aux == CBOR_UINT16_FOLLOWS:
+        data = fp.read(2)
+        aux = struct.unpack_from("!H", data, 0)[0]
+        bytes_read += 2
+    elif tag_aux == CBOR_UINT32_FOLLOWS:
+        data = fp.read(4)
+        aux = struct.unpack_from("!I", data, 0)[0]
+        bytes_read += 4
+    elif tag_aux == CBOR_UINT64_FOLLOWS:
+        data = fp.read(8)
+        aux = struct.unpack_from("!Q", data, 0)[0]
+        bytes_read += 8
+    else:
+        assert tag_aux == CBOR_VAR_FOLLOWS, "bogus tag {0:02x}".format(tb)
+        aux = None
+
+    return tag, tag_aux, aux, bytes_read
+
+
+def _read_byte(fp):
+    tb = fp.read(1)
+    if len(tb) == 0:
+        # I guess not all file-like objects do this
+        raise EOFError()
+    return ord(tb)
+
+
+def _loads_var_array(fp, limit, depth, returntags, bytes_read):
+    ob = []
+    tb = _read_byte(fp)
+    while tb != CBOR_BREAK:
+        (subob, sub_len) = _loads_tb(fp, tb, limit, depth, returntags)
+        bytes_read += 1 + sub_len
+        ob.append(subob)
+        tb = _read_byte(fp)
+    return (ob, bytes_read + 1)
+
+
+def _loads_var_map(fp, limit, depth, returntags, bytes_read):
+    ob = {}
+    tb = _read_byte(fp)
+    while tb != CBOR_BREAK:
+        (subk, sub_len) = _loads_tb(fp, tb, limit, depth, returntags)
+        bytes_read += 1 + sub_len
+        (subv, sub_len) = _loads(fp, limit, depth, returntags)
+        bytes_read += sub_len
+        ob[subk] = subv
+        tb = _read_byte(fp)
+    return (ob, bytes_read + 1)
+
+
+if _IS_PY3:
+    def _loads_array(fp, limit, depth, returntags, aux, bytes_read):
+        ob = []
+        for i in range(aux):
+            subob, subpos = _loads(fp)
+            bytes_read += subpos
+            ob.append(subob)
+        return ob, bytes_read
+    def _loads_map(fp, limit, depth, returntags, aux, bytes_read):
+        ob = {}
+        for i in range(aux):
+            subk, subpos = _loads(fp)
+            bytes_read += subpos
+            subv, subpos = _loads(fp)
+            bytes_read += subpos
+            ob[subk] = subv
+        return ob, bytes_read
+else:
+    def _loads_array(fp, limit, depth, returntags, aux, bytes_read):
+        ob = []
+        for i in xrange(aux):
+            subob, subpos = _loads(fp)
+            bytes_read += subpos
+            ob.append(subob)
+        return ob, bytes_read
+    def _loads_map(fp, limit, depth, returntags, aux, bytes_read):
+        ob = {}
+        for i in xrange(aux):
+            subk, subpos = _loads(fp)
+            bytes_read += subpos
+            subv, subpos = _loads(fp)
+            bytes_read += subpos
+            ob[subk] = subv
+        return ob, bytes_read
+        
+
+def _loads(fp, limit=None, depth=0, returntags=False):
+    "return (object, bytes read)"
+    if depth > _MAX_DEPTH:
+        raise Exception("hit CBOR loads recursion depth limit")
+
+    tb = _read_byte(fp)
+
+    return _loads_tb(fp, tb, limit, depth, returntags)
+
+def _loads_tb(fp, tb, limit=None, depth=0, returntags=False):
+    # Some special cases of CBOR_7 best handled by special struct.unpack logic here
+    if tb == CBOR_FLOAT16:
+        data = fp.read(2)
+        hibyte, lowbyte = struct.unpack_from("BB", data, 0)
+        exp = (hibyte >> 2) & 0x1F
+        mant = ((hibyte & 0x03) << 8) | lowbyte
+        if exp == 0:
+            val = mant * (2.0 ** -24)
+        elif exp == 31:
+            if mant == 0:
+                val = float('Inf')
+            else:
+                val = float('NaN')
+        else:
+            val = (mant + 1024.0) * (2 ** (exp - 25))
+        if hibyte & 0x80:
+            val = -1.0 * val
+        return (val, 3)
+    elif tb == CBOR_FLOAT32:
+        data = fp.read(4)
+        pf = struct.unpack_from("!f", data, 0)
+        return (pf[0], 5)
+    elif tb == CBOR_FLOAT64:
+        data = fp.read(8)
+        pf = struct.unpack_from("!d", data, 0)
+        return (pf[0], 9)
+
+    tag, tag_aux, aux, bytes_read = _tag_aux(fp, tb)
+
+    if tag == CBOR_UINT:
+        return (aux, bytes_read)
+    elif tag == CBOR_NEGINT:
+        return (-1 - aux, bytes_read)
+    elif tag == CBOR_BYTES:
+        ob, subpos = loads_bytes(fp, aux)
+        return (ob, bytes_read + subpos)
+    elif tag == CBOR_TEXT:
+        raw, subpos = loads_bytes(fp, aux, btag=CBOR_TEXT)
+        ob = raw.decode('utf8')
+        return (ob, bytes_read + subpos)
+    elif tag == CBOR_ARRAY:
+        if aux is None:
+            return _loads_var_array(fp, limit, depth, returntags, bytes_read)
+        return _loads_array(fp, limit, depth, returntags, aux, bytes_read)
+    elif tag == CBOR_MAP:
+        if aux is None:
+            return _loads_var_map(fp, limit, depth, returntags, bytes_read)
+        return _loads_map(fp, limit, depth, returntags, aux, bytes_read)
+    elif tag == CBOR_TAG:
+        ob, subpos = _loads(fp)
+        bytes_read += subpos
+        if returntags:
+            # Don't interpret the tag, return it and the tagged object.
+            ob = Tag(aux, ob)
+        else:
+            # attempt to interpet the tag and the value into a Python object.
+            ob = tagify(ob, aux)
+        return ob, bytes_read
+    elif tag == CBOR_7:
+        if tb == CBOR_TRUE:
+            return (True, bytes_read)
+        if tb == CBOR_FALSE:
+            return (False, bytes_read)
+        if tb == CBOR_NULL:
+            return (None, bytes_read)
+        if tb == CBOR_UNDEFINED:
+            return (None, bytes_read)
+        raise ValueError("unknown cbor tag 7 byte: {:02x}".format(tb))
+
+
+def loads_bytes(fp, aux, btag=CBOR_BYTES):
+    # TODO: limit to some maximum number of chunks and some maximum total bytes
+    if aux is not None:
+        # simple case
+        ob = fp.read(aux)
+        return (ob, aux)
+    # read chunks of bytes
+    chunklist = []
+    total_bytes_read = 0
+    while True:
+        tb = fp.read(1)[0]
+        if not _IS_PY3:
+            tb = ord(tb)
+        if tb == CBOR_BREAK:
+            total_bytes_read += 1
+            break
+        tag, tag_aux, aux, bytes_read = _tag_aux(fp, tb)
+        assert tag == btag, 'variable length value contains unexpected component'
+        ob = fp.read(aux)
+        chunklist.append(ob)
+        total_bytes_read += bytes_read + aux
+    return (b''.join(chunklist), total_bytes_read)
+
+
+if _IS_PY3:
+    def _bytes_to_biguint(bs):
+        out = 0
+        for ch in bs:
+            out = out << 8
+            out = out | ch
+        return out
+else:
+    def _bytes_to_biguint(bs):
+        out = 0
+        for ch in bs:
+            out = out << 8
+            out = out | ord(ch)
+        return out
+
+
+def tagify(ob, aux):
+    # TODO: make this extensible?
+    # cbor.register_tag_handler(tagnumber, tag_handler)
+    # where tag_handler takes (tagnumber, tagged_object)
+    if aux == CBOR_TAG_DATE_STRING:
+        # TODO: parse RFC3339 date string
+        pass
+    if aux == CBOR_TAG_DATE_ARRAY:
+        return datetime.datetime.utcfromtimestamp(ob)
+    if aux == CBOR_TAG_BIGNUM:
+        return _bytes_to_biguint(ob)
+    if aux == CBOR_TAG_NEGBIGNUM:
+        return -1 - _bytes_to_biguint(ob)
+    if aux == CBOR_TAG_REGEX:
+        # Is this actually a good idea? Should we just return the tag and the raw value to the user somehow?
+        return re.compile(ob)
+    return Tag(aux, ob)
diff --git a/mercurial/thirdparty/cbor/cbor/__init__.py b/mercurial/thirdparty/cbor/cbor/__init__.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/__init__.py
@@ -0,0 +1,19 @@
+#!python
+
+try:
+    # try C library _cbor.so
+    from ._cbor import loads, dumps, load, dump
+except:
+    # fall back to 100% python implementation
+    from .cbor import loads, dumps, load, dump
+
+from .cbor import Tag
+from .tagmap import TagMapper, ClassTag, UnknownTagException
+from .VERSION import __doc__ as __version__
+
+__all__ = [
+    'loads', 'dumps', 'load', 'dump',
+    'Tag',
+    'TagMapper', 'ClassTag', 'UnknownTagException',
+    '__version__',
+]
diff --git a/mercurial/thirdparty/cbor/cbor/VERSION.py b/mercurial/thirdparty/cbor/cbor/VERSION.py
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/cbor/VERSION.py
@@ -0,0 +1 @@
+'1.0.0'
diff --git a/mercurial/thirdparty/cbor/c/cbormodule.c b/mercurial/thirdparty/cbor/c/cbormodule.c
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/c/cbormodule.c
@@ -0,0 +1,1491 @@
+#include "Python.h"
+
+#include "cbor.h"
+
+#include <math.h>
+#include <stdint.h>
+
+//#include <stdio.h>
+#include <arpa/inet.h>
+
+
+#ifndef DEBUG_LOGGING
+// causes things to be written to stderr
+#define DEBUG_LOGGING 0
+//#define DEBUG_LOGGING 1
+#endif
+
+
+#ifdef Py_InitModule
+// Python 2.7
+
+#define HAS_FILE_READER 1
+#define IS_PY3 0
+
+#else
+
+#define HAS_FILE_READER 0
+#define IS_PY3 1
+
+#endif
+
+typedef struct {
+    unsigned int sort_keys;
+} EncodeOptions;
+
+// Hey Look! It's a polymorphic object structure in C!
+
+// read(, len): read len bytes and return in buffer, or NULL on error
+// read1(, uint8_t*): read one byte and return 0 on success
+// return_buffer(, *): release result of read(, len)
+// delete(): destructor. free thiz and contents.
+#define READER_FUNCTIONS \
+    void* (*read)(void* self, Py_ssize_t len); \
+    int (*read1)(void* self, uint8_t* oneByte); \
+    void (*return_buffer)(void* self, void* buffer); \
+    void (*delete)(void* self);
+
+#define SET_READER_FUNCTIONS(thiz, clazz) (thiz)->read = clazz##_read;\
+    (thiz)->read1 = clazz##_read1;\
+    (thiz)->return_buffer = clazz##_return_buffer;\
+    (thiz)->delete = clazz##_delete;
+
+typedef struct _Reader {
+    READER_FUNCTIONS;
+} Reader;
+
+static Reader* NewBufferReader(PyObject* ob);
+static Reader* NewObjectReader(PyObject* ob);
+#if HAS_FILE_READER
+static Reader* NewFileReader(PyObject* ob);
+#endif
+
+
+static PyObject* loads_tag(Reader* rin, uint64_t aux);
+static int loads_kv(PyObject* out, Reader* rin);
+
+typedef struct VarBufferPart {
+    void* start;
+    uint64_t len;
+    struct VarBufferPart* next;
+} VarBufferPart;
+
+
+static int logprintf(const char* fmt, ...) {
+    va_list ap;
+    int ret;
+    va_start(ap, fmt);
+#if DEBUG_LOGGING
+    ret = vfprintf(stderr, fmt, ap);
+#else
+    ret = 0;
+#endif
+    va_end(ap);
+    return ret;
+}
+
+// TODO: portably work this out at compile time
+static int _is_big_endian = 0;
+
+static int is_big_endian(void) {
+    uint32_t val = 1234;
+    _is_big_endian = val == htonl(val);
+    //logprintf("is_big_endian=%d\n", _is_big_endian);
+    return _is_big_endian;
+}
+
+
+PyObject* decodeFloat16(Reader* rin) {
+    // float16 parsing adapted from example code in spec
+    uint8_t hibyte, lobyte;// = raw[pos];
+    int err;
+    int exp;
+    int mant;
+    double val;
+
+    err = rin->read1(rin, &hibyte);
+    if (err) { logprintf("fail in float16[0]\n"); return NULL; }
+    err = rin->read1(rin, &lobyte);
+    if (err) { logprintf("fail in float16[1]\n"); return NULL; }
+
+    exp = (hibyte >> 2) & 0x1f;
+    mant = ((hibyte & 0x3) << 8) | lobyte;
+    if (exp == 0) {
+	val = ldexp(mant, -24);
+    } else if (exp != 31) {
+	val = ldexp(mant + 1024, exp - 25);
+    } else {
+	val = mant == 0 ? INFINITY : NAN;
+    }
+    if (hibyte & 0x80) {
+	val = -val;
+    }
+    return PyFloat_FromDouble(val);
+}
+PyObject* decodeFloat32(Reader* rin) {
+    float val;
+    uint8_t* raw = rin->read(rin, 4);
+    if (!raw) { logprintf("fail in float32\n"); return NULL; }
+    if (_is_big_endian) {
+	// easy!
+	val = *((float*)raw);
+    } else {
+	uint8_t* dest = (uint8_t*)(&val);
+	dest[3] = raw[0];
+	dest[2] = raw[1];
+	dest[1] = raw[2];
+	dest[0] = raw[3];
+    }
+    rin->return_buffer(rin, raw);
+    return PyFloat_FromDouble(val);
+}
+PyObject* decodeFloat64(Reader* rin) {
+    int si;
+    uint64_t aux = 0;
+    uint8_t* raw = rin->read(rin, 8);
+    if (!raw) { logprintf("fail in float64\n"); return NULL; }
+    for (si = 0; si < 8; si++) {
+	aux = aux << 8;
+	aux |= raw[si];
+    }
+    rin->return_buffer(rin, raw);
+    return PyFloat_FromDouble(*((double*)(&aux)));
+}
+
+// parse following int value into *auxP
+// return 0 on success, -1 on fail
+static int handle_info_bits(Reader* rin, uint8_t cbor_info, uint64_t* auxP) {
+    uint64_t aux;
+
+    if (cbor_info <= 23) {
+	// literal value <=23
+	aux = cbor_info;
+    } else if (cbor_info == CBOR_UINT8_FOLLOWS) {
+	uint8_t taux;
+	if (rin->read1(rin, &taux)) { logprintf("fail in uint8\n"); return -1; }
+	aux = taux;
+    } else if (cbor_info == CBOR_UINT16_FOLLOWS) {
+	uint8_t hibyte, lobyte;
+	if (rin->read1(rin, &hibyte)) { logprintf("fail in uint16[0]\n"); return -1; }
+	if (rin->read1(rin, &lobyte)) { logprintf("fail in uint16[1]\n"); return -1; }
+	aux = (hibyte << 8) | lobyte;
+    } else if (cbor_info == CBOR_UINT32_FOLLOWS) {
+	uint8_t* raw = (uint8_t*)rin->read(rin, 4);
+	if (!raw) { logprintf("fail in uint32[1]\n"); return -1; }
+	aux = 
+            (((uint64_t)raw[0]) << 24) |
+	    (((uint64_t)raw[1]) << 16) |
+	    (((uint64_t)raw[2]) <<  8) |
+	    ((uint64_t)raw[3]);
+	rin->return_buffer(rin, raw);
+    } else if (cbor_info == CBOR_UINT64_FOLLOWS) {
+        int si;
+	uint8_t* raw = (uint8_t*)rin->read(rin, 8);
+	if (!raw) { logprintf("fail in uint64[1]\n"); return -1; }
+	aux = 0;
+	for (si = 0; si < 8; si++) {
+	    aux = aux << 8;
+	    aux |= raw[si];
+	}
+	rin->return_buffer(rin, raw);
+    } else {
+	aux = 0;
+    }
+    *auxP = aux;
+    return 0;
+}
+
+static PyObject* inner_loads_c(Reader* rin, uint8_t c);
+
+static PyObject* inner_loads(Reader* rin) {
+    uint8_t c;
+    int err;
+
+    err = rin->read1(rin, &c);
+    if (err) { logprintf("fail in loads tag\n"); return NULL; }
+    return inner_loads_c(rin, c);
+}
+
+PyObject* inner_loads_c(Reader* rin, uint8_t c) {
+    uint8_t cbor_type;
+    uint8_t cbor_info;
+    uint64_t aux;
+
+    cbor_type = c & CBOR_TYPE_MASK;
+    cbor_info = c & CBOR_INFO_BITS;
+
+#if 0
+    if (pos > len) {
+	PyErr_SetString(PyExc_ValueError, "misparse, token went longer than buffer");
+	return NULL;
+    }
+
+    pos += 1;
+#endif
+
+    if (cbor_type == CBOR_7) {
+	if (cbor_info == CBOR_UINT16_FOLLOWS) { // float16
+	    return decodeFloat16(rin);
+	} else if (cbor_info == CBOR_UINT32_FOLLOWS) { // float32
+	    return decodeFloat32(rin);
+	} else if (cbor_info == CBOR_UINT64_FOLLOWS) {  // float64
+	    return decodeFloat64(rin);
+	}
+	// not a float, fall through to other CBOR_7 interpretations
+    }
+    if (handle_info_bits(rin, cbor_info, &aux)) { logprintf("info bits failed\n"); return NULL; }
+
+    PyObject* out = NULL;
+    switch (cbor_type) {
+    case CBOR_UINT:
+	out = PyLong_FromUnsignedLongLong(aux);
+        if (out == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "unknown error decoding UINT");
+        }
+        return out;
+    case CBOR_NEGINT:
+	if (aux > 0x7fffffffffffffff) {
+	    PyObject* bignum = PyLong_FromUnsignedLongLong(aux);
+	    PyObject* minusOne = PyLong_FromLong(-1);
+	    out = PyNumber_Subtract(minusOne, bignum);
+	    Py_DECREF(minusOne);
+	    Py_DECREF(bignum);
+	} else {
+	    out = PyLong_FromLongLong((long long)(((long long)-1) - aux));
+	}
+        if (out == NULL) {
+            PyErr_SetString(PyExc_RuntimeError, "unknown error decoding NEGINT");
+        }
+        return out;
+    case CBOR_BYTES:
+	if (cbor_info == CBOR_VAR_FOLLOWS) {
+	    size_t total = 0;
+	    VarBufferPart* parts = NULL;
+	    VarBufferPart* parts_tail = NULL;
+	    uint8_t sc;
+	    if (rin->read1(rin, &sc)) { logprintf("r1 fail in var bytes tag\n"); return NULL; }
+	    while (sc != CBOR_BREAK) {
+		uint8_t scbor_type = sc & CBOR_TYPE_MASK;
+		uint8_t scbor_info = sc & CBOR_INFO_BITS;
+		uint64_t saux;
+		void* blob;
+
+		if (scbor_type != CBOR_BYTES) {
+		    PyErr_Format(PyExc_ValueError, "expected subordinate BYTES block under VAR BYTES, but got %x", scbor_type);
+		    return NULL;
+		}
+		if(handle_info_bits(rin, scbor_info, &saux)) { logprintf("var bytes sub infobits failed\n"); return NULL; }
+		blob = rin->read(rin, saux);
+		if (!blob) { logprintf("var bytes sub bytes read failed\n"); return NULL; }
+		if (parts_tail == NULL) {
+		    parts = parts_tail = (VarBufferPart*)PyMem_Malloc(sizeof(VarBufferPart) + saux);
+		} else {
+		    parts_tail->next = (VarBufferPart*)PyMem_Malloc(sizeof(VarBufferPart) + saux);
+		    parts_tail = parts_tail->next;
+		}
+                parts_tail->start = (void*)(parts_tail + 1);
+                memcpy(parts_tail->start, blob, saux);
+                rin->return_buffer(rin, blob);
+		parts_tail->len = saux;
+		parts_tail->next = NULL;
+		total += saux;
+		if (rin->read1(rin, &sc)) { logprintf("r1 fail in var bytes tag\n"); return NULL; }
+	    }
+	    // Done
+	    {
+		uint8_t* allbytes = (uint8_t*)PyMem_Malloc(total);
+		uintptr_t op = 0;
+		while (parts != NULL) {
+		    VarBufferPart* next;
+		    memcpy(allbytes + op, parts->start, parts->len);
+		    op += parts->len;
+		    next = parts->next;
+		    PyMem_Free(parts);
+		    parts = next;
+		}
+		out = PyBytes_FromStringAndSize((char*)allbytes, total);
+		PyMem_Free(allbytes);
+	    }
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding VAR BYTES");
+            }
+	} else {
+	    void* raw;
+	    if (aux == 0) {
+		static void* empty_string = "";
+		raw = empty_string;
+	    } else {
+		raw = rin->read(rin, aux);
+		if (!raw) { logprintf("bytes read failed\n"); return NULL; }
+	    }
+	    out = PyBytes_FromStringAndSize(raw, (Py_ssize_t)aux);
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding BYTES");
+            }
+            if (aux != 0) {
+                rin->return_buffer(rin, raw);
+            }
+	}
+        return out;
+    case CBOR_TEXT:
+	if (cbor_info == CBOR_VAR_FOLLOWS) {
+	    PyObject* parts = PyList_New(0);
+	    PyObject* joiner = PyUnicode_FromString("");
+	    uint8_t sc;
+	    if (rin->read1(rin, &sc)) { logprintf("r1 fail in var text tag\n"); return NULL; }
+	    while (sc != CBOR_BREAK) {
+		PyObject* subitem = inner_loads_c(rin, sc);
+		if (subitem == NULL) { logprintf("fail in var text subitem\n"); return NULL; }
+		PyList_Append(parts, subitem);
+                Py_DECREF(subitem);
+		if (rin->read1(rin, &sc)) { logprintf("r1 fail in var text tag\n"); return NULL; }
+	    }
+	    // Done
+	    out = PyUnicode_Join(joiner, parts);
+	    Py_DECREF(joiner);
+	    Py_DECREF(parts);
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding VAR TEXT");
+            }
+	} else {
+            void* raw;
+	    if (aux == 0) {
+		static void* empty_string = "";
+		raw = empty_string;
+	    } else {
+                raw = rin->read(rin, aux);
+                if (!raw) { logprintf("read text failed\n"); return NULL; }
+            }
+	    out = PyUnicode_FromStringAndSize((char*)raw, (Py_ssize_t)aux);
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding TEXT");
+            }
+            if (aux != 0) {
+                rin->return_buffer(rin, raw);
+            }
+	}
+        return out;
+    case CBOR_ARRAY:
+	if (cbor_info == CBOR_VAR_FOLLOWS) {
+	    uint8_t sc;
+	    out = PyList_New(0);
+	    if (rin->read1(rin, &sc)) { logprintf("r1 fail in var array tag\n"); return NULL; }
+	    while (sc != CBOR_BREAK) {
+		PyObject* subitem = inner_loads_c(rin, sc);
+		if (subitem == NULL) { logprintf("fail in var array subitem\n"); return NULL; }
+		PyList_Append(out, subitem);
+                Py_DECREF(subitem);
+		if (rin->read1(rin, &sc)) { logprintf("r1 fail in var array tag\n"); return NULL; }
+	    }
+	    // Done
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding VAR ARRAY");
+            }
+	} else {
+            unsigned int i;
+	    out = PyList_New((Py_ssize_t)aux);
+	    for (i = 0; i < aux; i++) {
+		PyObject* subitem = inner_loads(rin);
+		if (subitem == NULL) { logprintf("array subitem[%d] (of %d) failed\n", i, aux); return NULL; }
+		PyList_SetItem(out, (Py_ssize_t)i, subitem);
+                // PyList_SetItem became the owner of the reference count of subitem, we don't need to DECREF it
+	    }
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding ARRAY");
+            }
+	}
+        return out;
+    case CBOR_MAP:
+	out = PyDict_New();
+	if (cbor_info == CBOR_VAR_FOLLOWS) {
+	    uint8_t sc;
+	    if (rin->read1(rin, &sc)) { logprintf("r1 fail in var map tag\n"); return NULL; }
+	    while (sc != CBOR_BREAK) {
+		PyObject* key = inner_loads_c(rin, sc);
+		PyObject* value;
+		if (key == NULL) { logprintf("var map key fail\n"); return NULL; }
+		value = inner_loads(rin);
+		if (value == NULL) { logprintf("var map val vail\n"); return NULL; }
+		PyDict_SetItem(out, key, value);
+                Py_DECREF(key);
+                Py_DECREF(value);
+
+		if (rin->read1(rin, &sc)) { logprintf("r1 fail in var map tag\n"); return NULL; }
+	    }
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding VAR MAP");
+            }
+	} else {
+            unsigned int i;
+	    for (i = 0; i < aux; i++) {
+		if (loads_kv(out, rin) != 0) {
+		    logprintf("map kv[%d] failed\n", i);
+		    return NULL;
+		}
+	    }
+            if (out == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "unknown error decoding MAP");
+            }
+	}
+        return out;
+    case CBOR_TAG:
+	return loads_tag(rin, aux);
+    case CBOR_7:
+	if (aux == 20) {
+	    out = Py_False;
+	    Py_INCREF(out);
+	} else if (aux == 21) {
+	    out = Py_True;
+	    Py_INCREF(out);
+	} else if (aux == 22) {
+	    out = Py_None;
+	    Py_INCREF(out);
+	} else if (aux == 23) {
+            // js `undefined`, closest is py None
+	    out = Py_None;
+	    Py_INCREF(out);
+	}
+        if (out == NULL) {
+            PyErr_Format(PyExc_ValueError, "unknown section 7 marker %02x, aux=%llu", c, aux);
+        }
+        return out;
+    default:
+        PyErr_Format(PyExc_RuntimeError, "unknown cbor marker %02x", c);
+        return NULL;
+    }
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunreachable-code"
+    PyErr_SetString(PyExc_RuntimeError, "cbor library internal error moof!");
+    return NULL;
+#pragma GCC diagnostic pop
+}
+
+static int loads_kv(PyObject* out, Reader* rin) {
+    PyObject* key = inner_loads(rin);
+    PyObject* value;
+    if (key == NULL) { logprintf("map key fail\n"); return -1; }
+    value = inner_loads(rin);
+    if (value == NULL) { logprintf("map val fail\n"); return -1; }
+    PyDict_SetItem(out, key, value);
+    Py_DECREF(key);
+    Py_DECREF(value);
+    return 0;
+}
+
+static PyObject* loads_bignum(Reader* rin, uint8_t c) {
+    PyObject* out = NULL;
+
+    uint8_t bytes_info = c & CBOR_INFO_BITS;
+    if (bytes_info < 24) {
+        int i;
+	PyObject* eight = PyLong_FromLong(8);
+	out = PyLong_FromLong(0);
+	for (i = 0; i < bytes_info; i++) {
+	    // TODO: is this leaking like crazy?
+	    PyObject* curbyte;
+	    PyObject* tout = PyNumber_Lshift(out, eight);
+	    Py_DECREF(out);
+	    out = tout;
+	    uint8_t cb;
+	    if (rin->read1(rin, &cb)) {
+                logprintf("r1 fail in bignum %d/%d\n", i, bytes_info);
+                Py_DECREF(eight);
+                Py_DECREF(out);
+                return NULL;
+            }
+	    curbyte = PyLong_FromLong(cb);
+	    tout = PyNumber_Or(out, curbyte);
+	    Py_DECREF(curbyte);
+	    Py_DECREF(out);
+	    out = tout;
+	}
+        Py_DECREF(eight);
+	return out;
+    } else {
+	PyErr_Format(PyExc_NotImplementedError, "TODO: TAG BIGNUM for bigger bignum bytes_info=%d, len(ull)=%lu\n", bytes_info, sizeof(unsigned long long));
+	return NULL;
+    }
+}
+
+
+// returns a PyObject for cbor.cbor.Tag
+// Returned PyObject* is a BORROWED reference from the module dict
+static PyObject* getCborTagClass(void) {
+    PyObject* cbor_module = PyImport_ImportModule("cbor.cbor");
+    PyObject* moddict = PyModule_GetDict(cbor_module);
+    PyObject* tag_class = PyDict_GetItemString(moddict, "Tag");
+    // moddict and tag_class are 'borrowed reference'
+    Py_DECREF(cbor_module);
+
+    return tag_class;
+}
+
+
+static PyObject* loads_tag(Reader* rin, uint64_t aux) {
+    PyObject* out = NULL;
+    // return an object CBORTag(tagnum, nextob)
+    if (aux == CBOR_TAG_BIGNUM) {
+	// If the next object is bytes, interpret it here without making a PyObject for it.
+	uint8_t sc;
+	if (rin->read1(rin, &sc)) { logprintf("r1 fail in bignum tag\n"); return NULL; }
+	if ((sc & CBOR_TYPE_MASK) == CBOR_BYTES) {
+	    return loads_bignum(rin, sc);
+	} else {
+	    PyErr_Format(PyExc_ValueError, "TAG BIGNUM not followed by bytes but %02x", sc);
+	    return NULL;
+	}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunreachable-code"
+	PyErr_Format(PyExc_ValueError, "TODO: WRITEME CBOR TAG BIGNUM %02x ...\n", sc);
+	return NULL;
+#pragma GCC diagnostic pop
+    } else if (aux == CBOR_TAG_NEGBIGNUM) {
+	// If the next object is bytes, interpret it here without making a PyObject for it.
+	uint8_t sc;
+	if (rin->read1(rin, &sc)) { logprintf("r1 fail in negbignum tag\n"); return NULL; }
+	if ((sc & CBOR_TYPE_MASK) == CBOR_BYTES) {
+	    out = loads_bignum(rin, sc);
+            if (out == NULL) { logprintf("loads_bignum fail inside TAG_NEGBIGNUM\n"); return NULL; }
+            PyObject* minusOne = PyLong_FromLong(-1);
+            PyObject* tout = PyNumber_Subtract(minusOne, out);
+            Py_DECREF(minusOne);
+            Py_DECREF(out);
+            out = tout;
+            return out;
+	} else {
+	    PyErr_Format(PyExc_ValueError, "TAG NEGBIGNUM not followed by bytes but %02x", sc);
+	    return NULL;
+	}
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunreachable-code"
+	PyErr_Format(PyExc_ValueError, "TODO: WRITEME CBOR TAG NEGBIGNUM %02x ...\n", sc);
+	return NULL;
+#pragma GCC diagnostic pop
+    }
+    out = inner_loads(rin);
+    if (out == NULL) { return NULL; }
+    {
+        PyObject* tag_class = getCborTagClass();
+	PyObject* args = Py_BuildValue("(K,O)", aux, out);
+        PyObject* tout = PyObject_CallObject(tag_class, args);
+	Py_DECREF(args);
+	Py_DECREF(out);
+        // tag_class was just a borrowed reference
+	out = tout;
+    }
+    return out;
+}
+
+
+static PyObject*
+cbor_loads(PyObject* noself, PyObject* args) {
+    PyObject* ob;
+    is_big_endian();
+    if (PyType_IsSubtype(Py_TYPE(args), &PyList_Type)) {
+	ob = PyList_GetItem(args, 0);
+    } else if (PyType_IsSubtype(Py_TYPE(args), &PyTuple_Type)) {
+	ob = PyTuple_GetItem(args, 0);
+    } else {
+	PyErr_Format(PyExc_ValueError, "args not list or tuple: %R\n", args);
+	return NULL;
+    }
+
+    if (ob == Py_None) {
+	PyErr_SetString(PyExc_ValueError, "got None for buffer to decode in loads");
+	return NULL;
+    }
+
+    {
+        PyObject* out = NULL;
+	Reader* r = NewBufferReader(ob);
+	if (!r) {
+	    return NULL;
+	}
+	out = inner_loads(r);
+        r->delete(r);
+        return out;
+    }
+}
+
+
+#if HAS_FILE_READER
+
+typedef struct _FileReader {
+    READER_FUNCTIONS;
+    FILE* fin;
+    void* dst;
+    Py_ssize_t dst_size;
+    Py_ssize_t read_count;
+} FileReader;
+
+// read from a python builtin file which contains a C FILE*
+static void* FileReader_read(void* self, Py_ssize_t len) {
+    FileReader* thiz = (FileReader*)self;
+    Py_ssize_t rtotal = 0;
+    uintptr_t opos;
+    //logprintf("file read %d\n", len);
+    if (len > thiz->dst_size) {
+	thiz->dst = PyMem_Realloc(thiz->dst, len);
+	thiz->dst_size = len;
+    } else if ((thiz->dst_size > (128 * 1024)) && (len < 4096)) {
+	PyMem_Free(thiz->dst);
+	thiz->dst = PyMem_Malloc(len);
+	thiz->dst_size = len;
+    }
+    opos = (uintptr_t)(thiz->dst);
+    while (1) {
+	size_t rlen = fread((void*)opos, 1, len, thiz->fin);
+	if (rlen == 0) {
+	    // file isn't going to give any more
+	    PyErr_Format(PyExc_ValueError, "only got %zd bytes with %zd stil to read from file", rtotal, len);
+	    PyMem_Free(thiz->dst);
+	    thiz->dst = NULL;
+            thiz->dst_size = 0;
+	    return NULL;
+	}
+	thiz->read_count += rlen;
+	rtotal += rlen;
+	opos += rlen;
+	len -= rlen;
+	if (rtotal >= len) {
+            if (thiz->dst == NULL) {
+                PyErr_SetString(PyExc_RuntimeError, "known error in file reader, NULL dst");
+                return NULL;
+            }
+	    return thiz->dst;
+	}
+    }
+}
+static int FileReader_read1(void* self, uint8_t* oneByte) {
+    FileReader* thiz = (FileReader*)self;
+    size_t didread = fread((void*)oneByte, 1, 1, thiz->fin);
+    if (didread == 0) {
+	logprintf("failed to read 1 from file\n");
+	PyErr_SetString(PyExc_ValueError, "got nothing reading 1 from file");
+	return -1;
+    }
+    thiz->read_count++;
+    return 0;
+}
+static void FileReader_return_buffer(void* self, void* buffer) {
+    // Nothing to do, we hold onto the buffer and maybe reuse it for next read
+}
+static void FileReader_delete(void* self) {
+    FileReader* thiz = (FileReader*)self;
+    if (thiz->dst) {
+	PyMem_Free(thiz->dst);
+    }
+    PyMem_Free(thiz);
+}
+static Reader* NewFileReader(PyObject* ob) {
+    FileReader* fr = (FileReader*)PyMem_Malloc(sizeof(FileReader));
+    if (fr == NULL) {
+        PyErr_SetString(PyExc_MemoryError, "failed to allocate FileReader");
+        return NULL;
+    }
+    fr->fin = PyFile_AsFile(ob);
+    if (fr->fin == NULL) {
+        PyErr_SetString(PyExc_RuntimeError, "PyFile_AsFile NULL");
+        PyMem_Free(fr);
+        return NULL;
+    }
+    fr->dst = NULL;
+    fr->dst_size = 0;
+    fr->read_count = 0;
+    SET_READER_FUNCTIONS(fr, FileReader);
+    return (Reader*)fr;
+}
+
+#endif /* Python 2.7 FileReader */
+
+
+typedef struct _ObjectReader {
+    READER_FUNCTIONS;
+    PyObject* ob;
+
+    // We got one object with all the bytes neccessary, and need to
+    // DECREF it later.
+    PyObject* retval;
+    void* bytes;
+
+    // OR, we got several objects, we DECREFed them as we went, and
+    // need to Free() this buffer at the end.
+    void* dst;
+
+    Py_ssize_t read_count;
+    int exception_is_external;
+} ObjectReader;
+
+// read from a python file-like object which has a .read(n) method
+static void* ObjectReader_read(void* context, Py_ssize_t len) {
+    ObjectReader* thiz = (ObjectReader*)context;
+    Py_ssize_t rtotal = 0;
+    uintptr_t opos = 0;
+    //logprintf("ob read %d\n", len);
+    assert(!thiz->dst);
+    assert(!thiz->bytes);
+    while (rtotal < len) {
+	PyObject* retval = PyObject_CallMethod(thiz->ob, "read", "n", len - rtotal, NULL);
+	Py_ssize_t rlen;
+	if (retval == NULL) {
+	    thiz->exception_is_external = 1;
+            logprintf("exception in object.read()\n");
+	    return NULL;
+	}
+	if (!PyBytes_Check(retval)) {
+            logprintf("object.read() is not bytes\n");
+	    PyErr_SetString(PyExc_ValueError, "expected ob.read() to return a bytes object\n");
+            Py_DECREF(retval);
+	    return NULL;
+	}
+	rlen = PyBytes_Size(retval);
+	thiz->read_count += rlen;
+	if (rlen > len - rtotal) {
+            logprintf("object.read() is too much!\n");
+            PyErr_Format(PyExc_ValueError, "ob.read() returned %ld bytes but only wanted %lu\n", rlen, len - rtotal);
+            Py_DECREF(retval);
+            return NULL;
+	}
+	if (rlen == len) {
+	    // best case! All in one call to read()
+	    // We _keep_ a reference to retval until later.
+	    thiz->retval = retval;
+	    thiz->bytes = PyBytes_AsString(retval);
+	    assert(thiz->bytes);
+	    thiz->dst = NULL;
+	    opos = 0;
+	    return thiz->bytes;
+	}
+	if (thiz->dst == NULL) {
+	    thiz->dst = PyMem_Malloc(len);
+	    opos = (uintptr_t)thiz->dst;
+	}
+	// else, not enough all in one go
+	memcpy((void*)opos, PyBytes_AsString(retval), rlen);
+	Py_DECREF(retval);
+	opos += rlen;
+	rtotal += rlen;
+    }
+    assert(thiz->dst);
+    return thiz->dst;
+}
+static int ObjectReader_read1(void* self, uint8_t* oneByte) {
+    ObjectReader* thiz = (ObjectReader*)self;
+    PyObject* retval = PyObject_CallMethod(thiz->ob, "read", "i", 1, NULL);
+    Py_ssize_t rlen;
+    if (retval == NULL) {
+	thiz->exception_is_external = 1;
+	//logprintf("call ob read(1) failed\n");
+	return -1;
+    }
+    if (!PyBytes_Check(retval)) {
+	PyErr_SetString(PyExc_ValueError, "expected ob.read() to return a bytes object\n");
+	return -1;
+    }
+    rlen = PyBytes_Size(retval);
+    thiz->read_count += rlen;
+    if (rlen > 1) {
+	PyErr_Format(PyExc_ValueError, "TODO: raise exception: WAT ob.read() returned %ld bytes but only wanted 1\n", rlen);
+	return -1;
+    }
+    if (rlen == 1) {
+	*oneByte = PyBytes_AsString(retval)[0];
+	Py_DECREF(retval);
+	return 0;
+    }
+    PyErr_SetString(PyExc_ValueError, "got nothing reading 1");
+    return -1;
+}
+static void ObjectReader_return_buffer(void* context, void* buffer) {
+    ObjectReader* thiz = (ObjectReader*)context;
+    if (buffer == thiz->bytes) {
+	Py_DECREF(thiz->retval);
+	thiz->retval = NULL;
+	thiz->bytes = NULL;
+    } else if (buffer == thiz->dst) {
+	PyMem_Free(thiz->dst);
+	thiz->dst = NULL;
+    } else {
+	logprintf("TODO: raise exception, could not release buffer %p, wanted dst=%p or bytes=%p\n", buffer, thiz->dst, thiz->bytes);
+    }
+}
+static void ObjectReader_delete(void* context) {
+    ObjectReader* thiz = (ObjectReader*)context;
+    if (thiz->retval != NULL) {
+	Py_DECREF(thiz->retval);
+    }
+    if (thiz->dst != NULL) {
+	PyMem_Free(thiz->dst);
+    }
+    PyMem_Free(thiz);
+}
+static Reader* NewObjectReader(PyObject* ob) {
+    ObjectReader* r = (ObjectReader*)PyMem_Malloc(sizeof(ObjectReader));
+    r->ob = ob;
+    r->retval = NULL;
+    r->bytes = NULL;
+    r->dst = NULL;
+    r->read_count = 0;
+    r->exception_is_external = 0;
+    SET_READER_FUNCTIONS(r, ObjectReader);
+    return (Reader*)r;
+}
+
+typedef struct _BufferReader {
+    READER_FUNCTIONS;
+    uint8_t* raw;
+    Py_ssize_t len;
+    uintptr_t pos;
+} BufferReader;
+
+// read from a buffer, aka loads()
+static void* BufferReader_read(void* context, Py_ssize_t len) {
+    BufferReader* thiz = (BufferReader*)context;
+    //logprintf("br %p %d (%d)\n", thiz, len, thiz->len);
+    if (len <= thiz->len) {
+	void* out = (void*)thiz->pos;
+	thiz->pos += len;
+	thiz->len -= len;
+	assert(out);
+	return out;
+    }
+    PyErr_Format(PyExc_ValueError, "buffer read for %zd but only have %zd\n", len, thiz->len);
+    return NULL;
+}
+static int BufferReader_read1(void* self, uint8_t* oneByte) {
+    BufferReader* thiz = (BufferReader*)self;
+    //logprintf("br %p _1_ (%d)\n", thiz, thiz->len);
+    if (thiz->len <= 0) {
+	PyErr_SetString(PyExc_LookupError, "buffer exhausted");
+	return -1;
+    }
+    *oneByte = *((uint8_t*)thiz->pos);
+    thiz->pos += 1;
+    thiz->len -= 1;
+    return 0;
+}
+static void BufferReader_return_buffer(void* context, void* buffer) {
+    // nothing to do
+}
+static void BufferReader_delete(void* context) {
+    BufferReader* thiz = (BufferReader*)context;
+    PyMem_Free(thiz);
+}
+static Reader* NewBufferReader(PyObject* ob) {
+    BufferReader* r = (BufferReader*)PyMem_Malloc(sizeof(BufferReader));
+    SET_READER_FUNCTIONS(r, BufferReader);
+    if (PyByteArray_Check(ob)) {
+        r->raw = (uint8_t*)PyByteArray_AsString(ob);
+        r->len = PyByteArray_Size(ob);
+    } else if (PyBytes_Check(ob)) {
+        r->raw = (uint8_t*)PyBytes_AsString(ob);
+        r->len = PyBytes_Size(ob);
+    } else {
+        PyErr_SetString(PyExc_ValueError, "input of unknown type not bytes or bytearray");
+        return NULL;
+    }
+    r->pos = (uintptr_t)r->raw;
+    if (r->len == 0) {
+	PyErr_SetString(PyExc_ValueError, "got zero length string in loads");
+	return NULL;
+    }
+    if (r->raw == NULL) {
+	PyErr_SetString(PyExc_ValueError, "got NULL buffer for string");
+	return NULL;
+    }
+    //logprintf("NBR(%llu, %ld)\n", r->pos, r->len);
+    return (Reader*)r;
+}
+
+
+static PyObject*
+cbor_load(PyObject* noself, PyObject* args) {
+    PyObject* ob;
+    Reader* reader;
+    is_big_endian();
+    if (PyType_IsSubtype(Py_TYPE(args), &PyList_Type)) {
+	ob = PyList_GetItem(args, 0);
+    } else if (PyType_IsSubtype(Py_TYPE(args), &PyTuple_Type)) {
+	ob = PyTuple_GetItem(args, 0);
+    } else {
+	PyErr_Format(PyExc_ValueError, "args not list or tuple: %R\n", args);
+	return NULL;
+    }
+
+    if (ob == Py_None) {
+	PyErr_SetString(PyExc_ValueError, "got None for buffer to decode in loads");
+	return NULL;
+    }
+    PyObject* retval;
+#if HAS_FILE_READER
+    if (PyFile_Check(ob)) {
+	reader = NewFileReader(ob);
+        if (reader == NULL) { return NULL; }
+	retval = inner_loads(reader);
+        if ((retval == NULL) &&
+            (((FileReader*)reader)->read_count == 0) &&
+            (feof(((FileReader*)reader)->fin) != 0)) {
+	    // never got anything, started at EOF
+	    PyErr_Clear();
+	    PyErr_SetString(PyExc_EOFError, "read nothing, apparent EOF");
+        }
+        reader->delete(reader);
+    } else
+#endif
+    {
+	reader = NewObjectReader(ob);
+	retval = inner_loads(reader);
+	if ((retval == NULL) &&
+	    (!((ObjectReader*)reader)->exception_is_external) &&
+	    ((ObjectReader*)reader)->read_count == 0) {
+	    // never got anything, assume EOF
+	    PyErr_Clear();
+	    PyErr_SetString(PyExc_EOFError, "read nothing, apparent EOF");
+	}
+        reader->delete(reader);
+    }
+    return retval;
+}
+
+
+static void tag_u64_out(uint8_t cbor_type, uint64_t aux, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = *posp;
+    if (out != NULL) {
+	out[pos] = cbor_type | CBOR_UINT64_FOLLOWS;
+	out[pos+1] = (aux >> 56) & 0x0ff;
+	out[pos+2] = (aux >> 48) & 0x0ff;
+	out[pos+3] = (aux >> 40) & 0x0ff;
+	out[pos+4] = (aux >> 32) & 0x0ff;
+	out[pos+5] = (aux >> 24) & 0x0ff;
+	out[pos+6] = (aux >> 16) & 0x0ff;
+	out[pos+7] = (aux >>  8) & 0x0ff;
+	out[pos+8] = aux & 0x0ff;
+    }
+    pos += 9;
+    *posp = pos;
+}
+
+
+static void tag_aux_out(uint8_t cbor_type, uint64_t aux, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = *posp;
+    if (aux <= 23) {
+	// tiny literal
+	if (out != NULL) {
+	    out[pos] = cbor_type | aux;
+	}
+	pos += 1;
+    } else if (aux <= 0x0ff) {
+	// one byte value
+	if (out != NULL) {
+	    out[pos] = cbor_type | CBOR_UINT8_FOLLOWS;
+	    out[pos+1] = aux;
+	}
+	pos += 2;
+    } else if (aux <= 0x0ffff) {
+	// two byte value
+	if (out != NULL) {
+	    out[pos] = cbor_type | CBOR_UINT16_FOLLOWS;
+	    out[pos+1] = (aux >> 8) & 0x0ff;
+	    out[pos+2] = aux & 0x0ff;
+	}
+	pos += 3;
+    } else if (aux <= 0x0ffffffffL) {
+	// four byte value
+	if (out != NULL) {
+	    out[pos] = cbor_type | CBOR_UINT32_FOLLOWS;
+	    out[pos+1] = (aux >> 24) & 0x0ff;
+	    out[pos+2] = (aux >> 16) & 0x0ff;
+	    out[pos+3] = (aux >>  8) & 0x0ff;
+	    out[pos+4] = aux & 0x0ff;
+	}
+	pos += 5;
+    } else {
+	// eight byte value
+	tag_u64_out(cbor_type, aux, out, posp);
+	return;
+    }
+    *posp = pos;
+    return;
+}
+
+static int inner_dumps(EncodeOptions *optp, PyObject* ob, uint8_t* out, uintptr_t* posp);
+
+static int dumps_dict(EncodeOptions *optp, PyObject* ob, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = *posp;
+    Py_ssize_t dictlen = PyDict_Size(ob);
+    PyObject* key;
+    PyObject* val;
+    int err;
+
+    tag_aux_out(CBOR_MAP, dictlen, out, &pos);
+
+    if (optp->sort_keys) {
+        Py_ssize_t index = 0;
+        PyObject* keylist = PyDict_Keys(ob);
+        PyList_Sort(keylist);
+
+        //fprintf(stderr, "sortking keys\n");
+        for (index = 0; index < PyList_Size(keylist); index++) {
+            key = PyList_GetItem(keylist, index); // Borrowed ref
+            val = PyDict_GetItem(ob, key); // Borrowed ref
+            err = inner_dumps(optp, key, out, &pos);
+            if (err != 0) { return err; }
+            err = inner_dumps(optp, val, out, &pos);
+            if (err != 0) { return err; }
+        }
+        Py_DECREF(keylist);
+    } else {
+        Py_ssize_t dictiter = 0;
+        //fprintf(stderr, "unsorted keys\n");
+        while (PyDict_Next(ob, &dictiter, &key, &val)) {
+            err = inner_dumps(optp, key, out, &pos);
+            if (err != 0) { return err; }
+            err = inner_dumps(optp, val, out, &pos);
+            if (err != 0) { return err; }
+        }
+    }
+
+    *posp = pos;
+    return 0;
+}
+
+
+static void dumps_bignum(EncodeOptions *optp, uint8_t tag, PyObject* val, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = (posp != NULL) ? *posp : 0;
+    PyObject* eight = PyLong_FromLong(8);
+    PyObject* bytemask = NULL;
+    PyObject* nval = NULL;
+    uint8_t* revbytes = NULL;
+    int revbytepos = 0;
+    int val_is_orig = 1;
+    if (out != NULL) {
+	bytemask = PyLong_FromLongLong(0x0ff);
+	revbytes = PyMem_Malloc(23);
+    }
+    while (PyObject_IsTrue(val) && (revbytepos < 23)) {
+	if (revbytes != NULL) {
+	    PyObject* tbyte = PyNumber_And(val, bytemask);
+	    revbytes[revbytepos] = PyLong_AsLong(tbyte);
+	    Py_DECREF(tbyte);
+	}
+	revbytepos++;
+	nval = PyNumber_InPlaceRshift(val, eight);
+        if (val_is_orig) {
+            val_is_orig = 0;
+        } else {
+            Py_DECREF(val);
+        }
+        val = nval;
+    }
+    if (revbytes != NULL) {
+	out[pos] = CBOR_TAG | tag;
+	pos++;
+	out[pos] = CBOR_BYTES | revbytepos;
+	pos++;
+	revbytepos--;
+	while (revbytepos >= 0) {
+	    out[pos] = revbytes[revbytepos];
+	    pos++;
+	    revbytepos--;
+	}
+        PyMem_Free(revbytes);
+	Py_DECREF(bytemask);
+    } else {
+	pos += 2 + revbytepos;
+    }
+    if (!val_is_orig) {
+        Py_DECREF(val);
+    }
+    Py_DECREF(eight);
+    *posp = pos;
+}
+
+static int dumps_tag(EncodeOptions *optp, PyObject* ob, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = (posp != NULL) ? *posp : 0;
+    int err = 0;
+
+
+    PyObject* tag_num;
+    PyObject* tag_value;
+    err = 0;
+
+    tag_num = PyObject_GetAttrString(ob, "tag");
+    if (tag_num != NULL) {
+        tag_value = PyObject_GetAttrString(ob, "value");
+        if (tag_value != NULL) {
+#ifdef Py_INTOBJECT_H
+            if (PyInt_Check(tag_num)) {
+                long val = PyInt_AsLong(tag_num);
+                if (val >= 0) {
+                    tag_aux_out(CBOR_TAG, val, out, &pos);
+                    err = inner_dumps(optp, tag_value, out, &pos);
+                } else {
+                    PyErr_Format(PyExc_ValueError, "tag cannot be a negative int: %ld", val);
+                    err = -1;
+                }
+            } else
+#endif
+            if (PyLong_Check(tag_num)) {
+                int overflow = -1;
+                long long val = PyLong_AsLongLongAndOverflow(tag_num, &overflow);
+                if (overflow == 0) {
+                    if (val >= 0) {
+                        tag_aux_out(CBOR_TAG, val, out, &pos);
+                        err = inner_dumps(optp, tag_value, out, &pos);
+                    } else {
+                        PyErr_Format(PyExc_ValueError, "tag cannot be a negative long: %lld", val);
+                        err = -1;
+                    }
+                } else {
+                    PyErr_SetString(PyExc_ValueError, "tag number too large");
+                    err = -1;
+                }
+            }
+            Py_DECREF(tag_value);
+        } else {
+            PyErr_SetString(PyExc_ValueError, "broken Tag object has .tag but not .value");
+            err = -1;
+        }
+        Py_DECREF(tag_num);
+    } else {
+        PyErr_SetString(PyExc_ValueError, "broken Tag object with no .tag");
+        err = -1;
+    }
+    if (err != 0) { return err; }
+
+    *posp = pos;
+    return err;
+}
+
+
+// With out=NULL it just counts the length.
+// return err, 0=OK
+static int inner_dumps(EncodeOptions *optp, PyObject* ob, uint8_t* out, uintptr_t* posp) {
+    uintptr_t pos = (posp != NULL) ? *posp : 0;
+
+    if (ob == Py_None) {
+	if (out != NULL) {
+	    out[pos] = CBOR_NULL;
+	}
+	pos += 1;
+    } else if (PyBool_Check(ob)) {
+	if (out != NULL) {
+	    if (PyObject_IsTrue(ob)) {
+		out[pos] = CBOR_TRUE;
+	    } else {
+		out[pos] = CBOR_FALSE;
+	    }
+	}
+	pos += 1;
+    } else if (PyDict_Check(ob)) {
+	int err = dumps_dict(optp, ob, out, &pos);
+	if (err != 0) { return err; }
+    } else if (PyList_Check(ob)) {
+        Py_ssize_t i;
+	Py_ssize_t listlen = PyList_Size(ob);
+	tag_aux_out(CBOR_ARRAY, listlen, out, &pos);
+	for (i = 0; i < listlen; i++) {
+	    int err = inner_dumps(optp, PyList_GetItem(ob, i), out, &pos);
+	    if (err != 0) { return err; }
+	}
+    } else if (PyTuple_Check(ob)) {
+        Py_ssize_t i;
+	Py_ssize_t listlen = PyTuple_Size(ob);
+	tag_aux_out(CBOR_ARRAY, listlen, out, &pos);
+	for (i = 0; i < listlen; i++) {
+	    int err = inner_dumps(optp, PyTuple_GetItem(ob, i), out, &pos);
+	    if (err != 0) { return err; }
+	}
+	// TODO: accept other enumerables and emit a variable length array
+#ifdef Py_INTOBJECT_H
+	// PyInt exists in Python 2 but not 3
+    } else if (PyInt_Check(ob)) {
+	long val = PyInt_AsLong(ob);
+	if (val >= 0) {
+	    tag_aux_out(CBOR_UINT, val, out, &pos);
+	} else {
+	    tag_aux_out(CBOR_NEGINT, -1 - val, out, &pos);
+	}
+#endif
+    } else if (PyLong_Check(ob)) {
+	int overflow = 0;
+	long long val = PyLong_AsLongLongAndOverflow(ob, &overflow);
+	if (overflow == 0) {
+	    if (val >= 0) {
+		tag_aux_out(CBOR_UINT, val, out, &pos);
+	    } else {
+		tag_aux_out(CBOR_NEGINT, -1L - val, out, &pos);
+	    }
+	} else {
+	    if (overflow < 0) {
+		// BIG NEGINT
+		PyObject* minusone = PyLong_FromLongLong(-1L);
+		PyObject* val = PyNumber_Subtract(minusone, ob);
+		Py_DECREF(minusone);
+		dumps_bignum(optp, CBOR_TAG_NEGBIGNUM, val, out, &pos);
+		Py_DECREF(val);
+	    } else {
+		// BIG INT
+		dumps_bignum(optp, CBOR_TAG_BIGNUM, ob, out, &pos);
+	    }
+	}
+    } else if (PyFloat_Check(ob)) {
+	double val = PyFloat_AsDouble(ob);
+	tag_u64_out(CBOR_7, *((uint64_t*)(&val)), out, &pos);
+    } else if (PyBytes_Check(ob)) {
+	Py_ssize_t len = PyBytes_Size(ob);
+	tag_aux_out(CBOR_BYTES, len, out, &pos);
+	if (out != NULL) {
+	    memcpy(out + pos, PyBytes_AsString(ob), len);
+	}
+	pos += len;
+    } else if (PyUnicode_Check(ob)) {
+	PyObject* utf8 = PyUnicode_AsUTF8String(ob);
+	Py_ssize_t len = PyBytes_Size(utf8);
+	tag_aux_out(CBOR_TEXT, len, out, &pos);
+	if (out != NULL) {
+	    memcpy(out + pos, PyBytes_AsString(utf8), len);
+	}
+	pos += len;
+	Py_DECREF(utf8);
+    } else {
+        int handled = 0;
+        {
+            PyObject* tag_class = getCborTagClass();
+            if (PyObject_IsInstance(ob, tag_class)) {
+                int err = dumps_tag(optp, ob, out, &pos);
+                if (err != 0) { return err; }
+                handled = 1;
+            }
+            // tag_class was just a borrowed reference
+        }
+
+        // TODO: other special object serializations here
+
+        if (!handled) {
+#if IS_PY3
+            PyErr_Format(PyExc_ValueError, "cannot serialize unknown object: %R", ob);
+#else
+            PyObject* badtype = PyObject_Type(ob);
+            PyObject* badtypename = PyObject_Str(badtype);
+            PyErr_Format(PyExc_ValueError, "cannot serialize unknown object of type %s", PyString_AsString(badtypename));
+            Py_DECREF(badtypename);
+            Py_DECREF(badtype);
+#endif
+            return -1;
+        }
+    }
+    if (posp != NULL) {
+	*posp = pos;
+    }
+    return 0;
+}
+
+static int _dumps_kwargs(EncodeOptions *optp, PyObject* kwargs) {
+    if (kwargs == NULL) {
+    } else if (!PyDict_Check(kwargs)) {
+	PyErr_Format(PyExc_ValueError, "kwargs not dict: %R\n", kwargs);
+	return 0;
+    } else {
+	PyObject* sort_keys = PyDict_GetItemString(kwargs, "sort_keys");  // Borrowed ref
+	if (sort_keys != NULL) {
+            optp->sort_keys = PyObject_IsTrue(sort_keys);
+            //fprintf(stderr, "sort_keys=%d\n", optp->sort_keys);
+	}
+    }
+    return 1;
+}
+
+static PyObject*
+cbor_dumps(PyObject* noself, PyObject* args, PyObject* kwargs) {
+
+    PyObject* ob;
+    EncodeOptions opts = {0};
+    EncodeOptions *optp = &opts;
+    is_big_endian();
+    if (PyType_IsSubtype(Py_TYPE(args), &PyList_Type)) {
+	ob = PyList_GetItem(args, 0);
+    } else if (PyType_IsSubtype(Py_TYPE(args), &PyTuple_Type)) {
+	ob = PyTuple_GetItem(args, 0);
+    } else {
+	PyErr_Format(PyExc_ValueError, "args not list or tuple: %R\n", args);
+	return NULL;
+    }
+    if (ob == NULL) {
+        return NULL;
+    }
+
+    if (!_dumps_kwargs(optp, kwargs)) {
+        return NULL;
+    }
+
+    {
+	Py_ssize_t outlen = 0;
+	uintptr_t pos = 0;
+	void* out = NULL;
+	PyObject* obout = NULL;
+	int err;
+
+	// first pass just to count length
+	err = inner_dumps(optp, ob, NULL, &pos);
+	if (err != 0) {
+	    return NULL;
+	}
+
+	outlen = pos;
+
+	out = PyMem_Malloc(outlen);
+	if (out == NULL) {
+	    PyErr_NoMemory();
+	    return NULL;
+	}
+
+	err = inner_dumps(optp, ob, out, NULL);
+	if (err != 0) {
+	    PyMem_Free(out);
+	    return NULL;
+	}
+
+	// TODO: I wish there was a way to do this without this copy.
+	obout = PyBytes_FromStringAndSize(out, outlen);
+	PyMem_Free(out);
+	return obout;
+    }
+}
+
+static PyObject*
+cbor_dump(PyObject* noself, PyObject* args, PyObject *kwargs) {
+    // args should be (obj, fp)
+    PyObject* ob;
+    PyObject* fp;
+    EncodeOptions opts = {0};
+    EncodeOptions *optp = &opts;
+
+    is_big_endian();
+    if (PyType_IsSubtype(Py_TYPE(args), &PyList_Type)) {
+	ob = PyList_GetItem(args, 0);
+	fp = PyList_GetItem(args, 1);
+    } else if (PyType_IsSubtype(Py_TYPE(args), &PyTuple_Type)) {
+	ob = PyTuple_GetItem(args, 0);
+	fp = PyTuple_GetItem(args, 1);
+    } else {
+	PyErr_Format(PyExc_ValueError, "args not list or tuple: %R\n", args);
+	return NULL;
+    }
+    if ((ob == NULL) || (fp == NULL)) {
+        return NULL;
+    }
+
+    if (!_dumps_kwargs(optp, kwargs)) {
+        return NULL;
+    }
+
+    {
+	// TODO: make this smarter, right now it is justt fp.write(dumps(ob))
+	Py_ssize_t outlen = 0;
+	uintptr_t pos = 0;
+	void* out = NULL;
+	int err;
+
+	// first pass just to count length
+	err = inner_dumps(optp, ob, NULL, &pos);
+	if (err != 0) {
+	    return NULL;
+	}
+
+	outlen = pos;
+
+	out = PyMem_Malloc(outlen);
+	if (out == NULL) {
+	    PyErr_NoMemory();
+	    return NULL;
+	}
+
+	err = inner_dumps(optp, ob, out, NULL);
+	if (err != 0) {
+	    PyMem_Free(out);
+	    return NULL;
+	}
+
+#if HAS_FILE_READER
+	if (PyFile_Check(fp)) {
+	    FILE* fout = PyFile_AsFile(fp);
+	    fwrite(out, 1, outlen, fout);
+	} else
+#endif
+	{
+	    PyObject* ret;
+            PyObject* obout = NULL;
+#if IS_PY3
+	    PyObject* writeStr = PyUnicode_FromString("write");
+#else
+	    PyObject* writeStr = PyString_FromString("write");
+#endif
+	    obout = PyBytes_FromStringAndSize(out, outlen);
+	    //logprintf("write %zd bytes to %p.write() as %p\n", outlen, fp, obout);
+	    ret = PyObject_CallMethodObjArgs(fp, writeStr, obout, NULL);
+	    Py_DECREF(writeStr);
+	    Py_DECREF(obout);
+	    if (ret != NULL) {
+		Py_DECREF(ret);
+	    } else {
+		// exception in fp.write()
+		PyMem_Free(out);
+		return NULL;
+	    }
+	    //logprintf("wrote %zd bytes to %p.write() as %p\n", outlen, fp, obout);
+	}
+	PyMem_Free(out);
+    }
+
+    Py_RETURN_NONE;
+}
+
+
+static PyMethodDef CborMethods[] = {
+    {"loads",  cbor_loads, METH_VARARGS,
+        "parse cbor from data buffer to objects"},
+    {"dumps", (PyCFunction)cbor_dumps, METH_VARARGS|METH_KEYWORDS,
+        "serialize python object to bytes"},
+    {"load",  cbor_load, METH_VARARGS,
+     "Parse cbor from data buffer to objects.\n"
+     "Takes a file-like object capable of .read(N)\n"},
+    {"dump", (PyCFunction)cbor_dump, METH_VARARGS|METH_KEYWORDS,
+     "Serialize python object to bytes.\n"
+     "dump(obj, fp)\n"
+     "obj: object to output; fp: file-like object to .write() to\n"},
+    {NULL, NULL, 0, NULL}        /* Sentinel */
+};
+
+#ifdef Py_InitModule
+// Python 2.7
+PyMODINIT_FUNC
+init_cbor(void)
+{
+    (void) Py_InitModule("cbor._cbor", CborMethods);
+}
+#else
+// Python 3
+PyMODINIT_FUNC
+PyInit__cbor(void)
+{
+    static PyModuleDef modef = {
+	PyModuleDef_HEAD_INIT,
+    };
+    //modef.m_base = PyModuleDef_HEAD_INIT;
+    modef.m_name = "cbor._cbor";
+    modef.m_doc = NULL;
+    modef.m_size = 0;
+    modef.m_methods = CborMethods;
+#ifdef Py_mod_exec
+    modef.m_slots = NULL; // Py >= 3.5
+#else
+    modef.m_reload = NULL; // Py < 3.5
+#endif
+    modef.m_traverse = NULL;
+    modef.m_clear = NULL;
+    modef.m_free = NULL;
+    return PyModule_Create(&modef);
+}
+#endif
+
diff --git a/mercurial/thirdparty/cbor/c/cbor.h b/mercurial/thirdparty/cbor/c/cbor.h
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/c/cbor.h
@@ -0,0 +1,76 @@
+#ifndef CBOR_H
+#define CBOR_H
+
+#define CBOR_TYPE_MASK 0xE0  /* top 3 bits */
+#define CBOR_INFO_BITS 0x1F  /* low 5 bits */
+
+#define CBOR_UINT      0x00
+#define CBOR_NEGINT    0x20
+#define CBOR_BYTES     0x40
+#define CBOR_TEXT      0x60
+#define CBOR_ARRAY     0x80
+#define CBOR_MAP       0xA0
+#define CBOR_TAG       0xC0
+#define CBOR_7         0xE0  /* float and other types */
+
+#define CBOR_ADDITIONAL_INFORMATION 0x1F
+
+/* read the "additional information" of a tag byte which is often a
+ * small literal integer describing the length in bytes of the data
+ * item */
+#define IS_SMALL_LITERAL(n) (((n) & 0x1f) < 24)
+#define SMALL_LITERAL(n) ((n) & 0x1f)
+
+
+#define CBOR_UINT8_FOLLOWS   24 // 0x18
+#define CBOR_UINT16_FOLLOWS  25 // 0x19
+#define CBOR_UINT32_FOLLOWS  26 // 0x1A
+#define CBOR_UINT64_FOLLOWS  27 // 0x1B
+#define CBOR_VAR_FOLLOWS     31 // 0x1F
+
+#define CBOR_UINT8   (CBOR_UINT | CBOR_UINT8_FOLLOWS)
+#define CBOR_UINT16  (CBOR_UINT | CBOR_UINT16_FOLLOWS)
+#define CBOR_UINT32  (CBOR_UINT | CBOR_UINT32_FOLLOWS)
+#define CBOR_UINT64  (CBOR_UINT | CBOR_UINT64_FOLLOWS)
+
+#define CBOR_NEGINT8   (CBOR_NEGINT | CBOR_UINT8_FOLLOWS)
+#define CBOR_NEGINT16  (CBOR_NEGINT | CBOR_UINT16_FOLLOWS)
+#define CBOR_NEGINT32  (CBOR_NEGINT | CBOR_UINT32_FOLLOWS)
+#define CBOR_NEGINT64  (CBOR_NEGINT | CBOR_UINT64_FOLLOWS)
+
+
+#define CBOR_BREAK 0xFF
+
+#define CBOR_FALSE   (CBOR_7 | 20)
+#define CBOR_TRUE    (CBOR_7 | 21)
+#define CBOR_NULL    (CBOR_7 | 22)
+#define CBOR_UNDEFINED (CBOR_7 | 23)
+
+#define CBOR_FLOAT16 (CBOR_7 | 25)
+#define CBOR_FLOAT32 (CBOR_7 | 26)
+#define CBOR_FLOAT64 (CBOR_7 | 27)
+
+
+#define CBOR_TAG_DATE_STRING (0) /* RFC3339 */
+#define CBOR_TAG_DATE_ARRAY (1) /* any number type follows, seconds since 1970-01-01T00:00:00 UTC */
+#define CBOR_TAG_BIGNUM (2)  /* big endian byte string follows */
+#define CBOR_TAG_NEGBIGNUM (3)  /* big endian byte string follows */
+#define CBOR_TAG_DECIMAL (4) /* [ 10^x exponent, number ] */
+#define CBOR_TAG_BIGFLOAT (5) /* [ 2^x exponent, number ] */
+//#define CBOR_TAG_BASE64URL (21)
+//#define CBOR_TAG_BASE64 (22)
+#define CBOR_TAG_BASE16 (23)
+#define CBOR_TAG_CBOR (24) /* following byte string is embedded CBOR data */
+
+#define CBOR_TAG_URI 32
+//#define CBOR_TAG_BASE64URL 33
+//#define CBOR_TAG_BASE64 34
+#define CBOR_TAG_REGEX 35
+#define CBOR_TAG_MIME 36 /* following text is MIME message, headers, separators and all */
+#define CBOR_TAG_CBOR_FILEHEADER 55799  /* can open a file with 0xd9d9f7 */
+
+
+/* Content-Type: application/cbor */
+
+
+#endif /* CBOR_H */
diff --git a/mercurial/thirdparty/cbor/README.md b/mercurial/thirdparty/cbor/README.md
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/README.md
@@ -0,0 +1,33 @@
+Concise Binary Object Representation (CBOR) is a superset of JSON's schema that's faster and more compact.
+
+* http://tools.ietf.org/html/rfc7049
+* http://cbor.io/
+
+This repository contains implementations for Python and Go.
+
+## Python ##
+This Python implementation provides loads()/dumps() like the json standard library.
+
+Compare to Python 2.7.5's standard library implementation of json:
+
+```
+#!
+
+serialized 50000 objects into 1163097 cbor bytes in 0.05 seconds (1036613.48/s) and 1767001 json bytes in 0.22 seconds (224772.48/s)
+compress to 999179 bytes cbor.gz and 1124500 bytes json.gz
+load 50000 objects from cbor in 0.07 secs (763708.80/sec) and json in 0.32 (155348.97/sec)
+```
+
+There is also a pure-python implementation which gets about 1/3 the speed of json's C augmented speed.
+
+Tested in Python 2.7.5, 2,7.6, 3.3.3, and 3.4.0
+
+Available on pypi:
+
+pip install cbor
+
+## Go ##
+
+import cbor "bitbucket.org/bodhisnarkva/cbor/go"
+
+The Go implementation is newer. It attempts to do serialization to/from struct types using reflection, but doesn't do 100% of cases like that right. It _should_ do everything fine serializing `map[string]interface{}` and `[]interface{}` and other basic types. It passes the test of decoding 100% of CBOR common appendix test strings.
\ No newline at end of file
diff --git a/mercurial/thirdparty/cbor/Makefile b/mercurial/thirdparty/cbor/Makefile
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/Makefile
@@ -0,0 +1,4 @@
+test:
+	PYTHONPATH=. python tests/cbor/test_cbor.py
+
+check:	test
diff --git a/mercurial/thirdparty/cbor/LICENSE b/mercurial/thirdparty/cbor/LICENSE
new file mode 100644
--- /dev/null
+++ b/mercurial/thirdparty/cbor/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014-2015 Brian Olson
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.



To: pulkit, #hg-reviewers
Cc: mercurial-devel


More information about the Mercurial-devel mailing list