cborutil: implement sans I/O decoder
The vendored CBOR package decodes by calling read(n) on an object.
There are a number of disadvantages to this:
* Uses blocking I/O. If sufficient data is not available, the decoder
will hang until it is.
* No support for partial reads. If the read(n) returns less data than
requested, the decoder raises an error.
* Requires the use of a file like object. If the original data is in
say a buffer, we need to "cast" it to e.g. a BytesIO to appease the
decoder.
In addition, the vendored CBOR decoder doesn't provide flexibility
that we desire. Specifically:
* It buffers indefinite length bytestrings instead of streaming them.
* It doesn't allow limiting the set of types that can be decoded. This
property is useful when implementing a "hardened" decoder that is
less susceptible to abusive input.
* It doesn't provide sufficient "hook points" and introspection to
institute checks around behavior. These are useful for implementing
a "hardened" decoder.
This all adds up to a reasonable set of justifications for writing our
own decoder.
So, this commit implements our own CBOR decoder.
At the heart of the decoder is a function that decodes a single "item"
from a buffer. This item can be a complete simple value or a special
value, such as "start of array." Using this function, we can build a
decoder that effectively iterates over the stream of decoded items and
builds up higher-level values, such as arrays, maps, sets, and indefinite
length bytestrings. And we can do this without performing I/O in the
decoder itself.
The core of the sans I/O decoder will probably not be used directly.
Instead, it is expected that we'll build utility functions for invoking
the decoder given specific input types. This will allow extreme
flexibility in how data is delivered to the decoder.
I'm pretty happy with the state of the decoder modulo the TODO items
to track wanted features to help with a "hardened" decoder. The one
thing I could be convinced to change is the handling of semantic tags.
Since we only support a single semantic tag (sets), I thought it would
be easier to handle them inline in decodeitem(). This is simpler now.
But if we add support for other semantic tags, it will likely be easier
to move semantic tag handling outside of decodeitem(). But, properly
supporting semantic tags opens up a whole can of worms, as many
semantic tags imply new types. I'm optimistic we won't need these in
Mercurial. But who knows.
I'm also pretty happy with the test coverage. Writing comprehensive
tests for partial decoding did flush out a handful of bugs. One
general improvement to testing would be fuzz testing for partial
decoding. I may implement that later. I also anticipate switching the
wire protocol code to this new decoder will flush out any lingering
bugs.
Differential Revision: https://phab.mercurial-scm.org/D4414
This test tries to exercise the ssh functionality with a dummy script
$ checknewrepo()
> {
> name=$1
> if [ -d "$name"/.hg/store ]; then
> echo store created
> fi
> if [ -f "$name"/.hg/00changelog.i ]; then
> echo 00changelog.i created
> fi
> cat "$name"/.hg/requires
> }
creating 'local'
$ hg init local
$ checknewrepo local
store created
00changelog.i created
dotencode
fncache
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
$ echo this > local/foo
$ hg ci --cwd local -A -m "init"
adding foo
test custom revlog chunk cache sizes
$ hg --config format.chunkcachesize=0 log -R local -pv
abort: revlog chunk cache size 0 is not greater than 0!
[255]
$ hg --config format.chunkcachesize=1023 log -R local -pv
abort: revlog chunk cache size 1023 is not a power of 2!
[255]
$ hg --config format.chunkcachesize=1024 log -R local -pv
changeset: 0:08b9e9f63b32
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
files: foo
description:
init
diff -r 000000000000 -r 08b9e9f63b32 foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/foo Thu Jan 01 00:00:00 1970 +0000
@@ -0,0 +1,1 @@
+this
creating repo with format.usestore=false
$ hg --config format.usestore=false init old
$ checknewrepo old
generaldelta
revlogv1
testonly-simplestore (reposimplestore !)
creating repo with format.usefncache=false
$ hg --config format.usefncache=false init old2
$ checknewrepo old2
store created
00changelog.i created
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
creating repo with format.dotencode=false
$ hg --config format.dotencode=false init old3
$ checknewrepo old3
store created
00changelog.i created
fncache
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
creating repo with format.dotencode=false
$ hg --config format.generaldelta=false --config format.usegeneraldelta=false init old4
$ checknewrepo old4
store created
00changelog.i created
dotencode
fncache
revlogv1
store
testonly-simplestore (reposimplestore !)
test failure
$ hg init local
abort: repository local already exists!
[255]
init+push to remote2
$ hg init -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote2
$ hg incoming -R remote2 local
comparing with local
changeset: 0:08b9e9f63b32
tag: tip
user: test
date: Thu Jan 01 00:00:00 1970 +0000
summary: init
$ hg push -R local -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote2
pushing to ssh://user@dummy/remote2
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
clone to remote1
$ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" local ssh://user@dummy/remote1
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
The largefiles extension doesn't crash
$ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" local ssh://user@dummy/remotelf --config extensions.largefiles=
The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
init to existing repo
$ hg init -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote1
abort: repository remote1 already exists!
abort: could not create remote repo!
[255]
clone to existing repo
$ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" local ssh://user@dummy/remote1
abort: repository remote1 already exists!
abort: could not create remote repo!
[255]
output of dummyssh
$ cat dummylog
Got arguments 1:user@dummy 2:hg init remote2
Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio
Got arguments 1:user@dummy 2:hg -R remote2 serve --stdio
Got arguments 1:user@dummy 2:hg init remote1
Got arguments 1:user@dummy 2:hg -R remote1 serve --stdio
Got arguments 1:user@dummy 2:hg init remotelf
Got arguments 1:user@dummy 2:hg -R remotelf serve --stdio
Got arguments 1:user@dummy 2:hg init remote1
Got arguments 1:user@dummy 2:hg init remote1
comparing repositories
$ hg tip -q -R local
0:08b9e9f63b32
$ hg tip -q -R remote1
0:08b9e9f63b32
$ hg tip -q -R remote2
0:08b9e9f63b32
check names for repositories (clashes with URL schemes, special chars)
$ for i in bundle file hg http https old-http ssh static-http "with space"; do
> printf "hg init \"$i\"... "
> hg init "$i"
> test -d "$i" -a -d "$i/.hg" && echo "ok" || echo "failed"
> done
hg init "bundle"... ok
hg init "file"... ok
hg init "hg"... ok
hg init "http"... ok
hg init "https"... ok
hg init "old-http"... ok
hg init "ssh"... ok
hg init "static-http"... ok
hg init "with space"... ok
#if eol-in-paths
/* " " is not a valid name for a directory on Windows */
$ hg init " "
$ test -d " "
$ test -d " /.hg"
#endif
creating 'local/sub/repo'
$ hg init local/sub/repo
$ checknewrepo local/sub/repo
store created
00changelog.i created
dotencode
fncache
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
prepare test of init of url configured from paths
$ echo '[paths]' >> $HGRCPATH
$ echo "somewhere = `pwd`/url from paths" >> $HGRCPATH
$ echo "elsewhere = `pwd`/another paths url" >> $HGRCPATH
init should (for consistency with clone) expand the url
$ hg init somewhere
$ checknewrepo "url from paths"
store created
00changelog.i created
dotencode
fncache
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
verify that clone also expand urls
$ hg clone somewhere elsewhere
updating to branch default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ checknewrepo "another paths url"
store created
00changelog.i created
dotencode
fncache
generaldelta
revlogv1
store
testonly-simplestore (reposimplestore !)
clone bookmarks
$ hg -R local bookmark test
$ hg -R local bookmarks
* test 0:08b9e9f63b32
$ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" local ssh://user@dummy/remote-bookmarks
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
exporting bookmark test
$ hg -R remote-bookmarks bookmarks
test 0:08b9e9f63b32