From 365d96412ab8e30a0e8a6fa2d4dff57f0e3fcb76 Mon Sep 17 00:00:00 2001 From: Felix Mueller-Irion Date: Thu, 26 Feb 2015 15:39:35 +0100 Subject: [PATCH 01/67] Create LICENSE --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb484ba --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) {{{2015}}} {{{Lavaboom GmbH}}} + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 05900e0dd7a610b721c700b5adb2c1dd17e7fc83 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 25 Mar 2015 22:43:38 +0100 Subject: [PATCH 02/67] Godeps --- Godeps/Godeps.json | 163 + Godeps/Readme | 5 + Godeps/_workspace/.gitignore | 2 + .../p/go.crypto/pbkdf2/pbkdf2.go | 77 + .../p/go.crypto/pbkdf2/pbkdf2_test.go | 157 + .../p/go.crypto/scrypt/scrypt.go | 243 ++ .../p/go.crypto/scrypt/scrypt_test.go | 160 + .../p/goprotobuf/proto/Makefile | 40 + .../p/goprotobuf/proto/all_test.go | 1979 ++++++++++++ .../p/goprotobuf/proto/clone.go | 169 ++ .../p/goprotobuf/proto/clone_test.go | 186 ++ .../p/goprotobuf/proto/decode.go | 721 +++++ .../p/goprotobuf/proto/encode.go | 1054 +++++++ .../p/goprotobuf/proto/equal.go | 241 ++ .../p/goprotobuf/proto/equal_test.go | 166 + .../p/goprotobuf/proto/extensions.go | 351 +++ .../p/goprotobuf/proto/extensions_test.go | 60 + .../code.google.com/p/goprotobuf/proto/lib.go | 740 +++++ .../p/goprotobuf/proto/message_set.go | 229 ++ .../p/goprotobuf/proto/message_set_test.go | 66 + .../p/goprotobuf/proto/pointer_reflect.go | 384 +++ .../p/goprotobuf/proto/pointer_unsafe.go | 218 ++ .../p/goprotobuf/proto/properties.go | 658 ++++ .../p/goprotobuf/proto/size2_test.go | 63 + .../p/goprotobuf/proto/size_test.go | 120 + .../p/goprotobuf/proto/testdata/Makefile | 50 + .../goprotobuf/proto/testdata/golden_test.go | 86 + .../p/goprotobuf/proto/testdata/test.pb.go | 2350 ++++++++++++++ .../p/goprotobuf/proto/testdata/test.proto | 428 +++ .../p/goprotobuf/proto/text.go | 701 +++++ .../p/goprotobuf/proto/text_parser.go | 684 +++++ .../p/goprotobuf/proto/text_parser_test.go | 462 +++ .../p/goprotobuf/proto/text_test.go | 408 +++ .../code.google.com/p/rsc/gf256/blog_test.go | 85 + .../src/code.google.com/p/rsc/gf256/gf256.go | 241 ++ .../code.google.com/p/rsc/gf256/gf256_test.go | 194 ++ .../code.google.com/p/rsc/qr/coding/gen.go | 149 + .../src/code.google.com/p/rsc/qr/coding/qr.go | 815 +++++ .../p/rsc/qr/coding/qr_test.go | 133 + .../p/rsc/qr/libqrencode/qrencode.go | 149 + .../src/code.google.com/p/rsc/qr/png.go | 400 +++ .../src/code.google.com/p/rsc/qr/png_test.go | 73 + .../src/code.google.com/p/rsc/qr/qr.go | 116 + .../src/code.google.com/p/rsc/qr/web/pic.go | 505 +++ .../src/code.google.com/p/rsc/qr/web/play.go | 1118 +++++++ .../p/rsc/qr/web/resize/resize.go | 152 + .../p/snappy-go/snappy/decode.go | 124 + .../p/snappy-go/snappy/encode.go | 174 ++ .../p/snappy-go/snappy/snappy.go | 38 + .../p/snappy-go/snappy/snappy_test.go | 261 ++ .../github.com/GeertJohan/yubigo/.gitignore | 5 + .../src/github.com/GeertJohan/yubigo/LICENSE | 22 + .../github.com/GeertJohan/yubigo/readme.md | 84 + .../github.com/GeertJohan/yubigo/yubigo.go | 565 ++++ .../src/github.com/Sirupsen/logrus/.gitignore | 1 + .../github.com/Sirupsen/logrus/.travis.yml | 10 + .../src/github.com/Sirupsen/logrus/LICENSE | 21 + .../src/github.com/Sirupsen/logrus/README.md | 355 +++ .../src/github.com/Sirupsen/logrus/entry.go | 252 ++ .../github.com/Sirupsen/logrus/entry_test.go | 53 + .../Sirupsen/logrus/examples/basic/basic.go | 40 + .../Sirupsen/logrus/examples/hook/hook.go | 35 + .../github.com/Sirupsen/logrus/exported.go | 182 ++ .../github.com/Sirupsen/logrus/formatter.go | 44 + .../Sirupsen/logrus/formatter_bench_test.go | 88 + .../github.com/Sirupsen/logrus/hook_test.go | 122 + .../src/github.com/Sirupsen/logrus/hooks.go | 34 + .../logrus/hooks/airbrake/airbrake.go | 54 + .../logrus/hooks/papertrail/README.md | 28 + .../logrus/hooks/papertrail/papertrail.go | 55 + .../hooks/papertrail/papertrail_test.go | 26 + .../Sirupsen/logrus/hooks/sentry/README.md | 61 + .../Sirupsen/logrus/hooks/sentry/sentry.go | 100 + .../logrus/hooks/sentry/sentry_test.go | 97 + .../Sirupsen/logrus/hooks/syslog/README.md | 20 + .../Sirupsen/logrus/hooks/syslog/syslog.go | 59 + .../logrus/hooks/syslog/syslog_test.go | 26 + .../Sirupsen/logrus/json_formatter.go | 26 + .../src/github.com/Sirupsen/logrus/logger.go | 161 + .../src/github.com/Sirupsen/logrus/logrus.go | 94 + .../github.com/Sirupsen/logrus/logrus_test.go | 283 ++ .../Sirupsen/logrus/terminal_darwin.go | 12 + .../Sirupsen/logrus/terminal_freebsd.go | 20 + .../Sirupsen/logrus/terminal_linux.go | 12 + .../Sirupsen/logrus/terminal_notwindows.go | 21 + .../Sirupsen/logrus/terminal_openbsd.go | 8 + .../Sirupsen/logrus/terminal_windows.go | 27 + .../Sirupsen/logrus/text_formatter.go | 124 + .../Sirupsen/logrus/text_formatter_test.go | 33 + .../src/github.com/ajstarks/svgo/LICENSE | 3 + .../github.com/ajstarks/svgo/README.markdown | 668 ++++ .../ajstarks/svgo/android/android.go | 56 + .../ajstarks/svgo/barchart/barchart.go | 472 +++ .../ajstarks/svgo/benchviz/benchviz.go | 238 ++ .../ajstarks/svgo/bubtrail/bubtrail.go | 68 + .../ajstarks/svgo/bulletgraph/bulletgraph.go | 233 ++ .../ajstarks/svgo/codepic/codepic.go | 183 ++ .../ajstarks/svgo/colortab/colortab.go | 90 + .../ajstarks/svgo/colortab/svgcolors.txt | 147 + .../github.com/ajstarks/svgo/compx/comps.xml | 16 + .../github.com/ajstarks/svgo/compx/compx.go | 894 ++++++ .../ajstarks/svgo/compx/testcomp.xml | 69 + .../src/github.com/ajstarks/svgo/doc.go | 118 + .../src/github.com/ajstarks/svgo/f50/f50.go | 121 + .../src/github.com/ajstarks/svgo/fe/fe.go | 53 + .../github.com/ajstarks/svgo/flower/flower.go | 80 + .../ajstarks/svgo/fontcompare/fontcompare.go | 53 + .../github.com/ajstarks/svgo/funnel/funnel.go | 30 + .../ajstarks/svgo/gophercolor128x128.png | Bin 0 -> 12425 bytes .../ajstarks/svgo/gradient/gradient.go | 59 + .../ajstarks/svgo/html5logo/html5logo.go | 42 + .../svgo/imfade/gophercolor128x128.png | Bin 0 -> 12425 bytes .../github.com/ajstarks/svgo/imfade/imfade.go | 33 + .../github.com/ajstarks/svgo/lewitt/lewitt.go | 83 + .../src/github.com/ajstarks/svgo/ltr/ltr.go | 178 ++ .../github.com/ajstarks/svgo/marker/marker.go | 40 + .../src/github.com/ajstarks/svgo/newsvg | 39 + .../github.com/ajstarks/svgo/paths/paths.go | 31 + .../ajstarks/svgo/pattern/pattern.go | 33 + .../ajstarks/svgo/planets/earth.png | Bin 0 -> 97409 bytes .../ajstarks/svgo/planets/jupiter.png | Bin 0 -> 66878 bytes .../github.com/ajstarks/svgo/planets/mars.png | Bin 0 -> 66839 bytes .../ajstarks/svgo/planets/mercury.png | Bin 0 -> 254995 bytes .../ajstarks/svgo/planets/neptune.png | Bin 0 -> 60626 bytes .../ajstarks/svgo/planets/planets.go | 122 + .../ajstarks/svgo/planets/saturn.png | Bin 0 -> 110922 bytes .../github.com/ajstarks/svgo/planets/sun.png | Bin 0 -> 380224 bytes .../ajstarks/svgo/planets/uranus.png | Bin 0 -> 41964 bytes .../ajstarks/svgo/planets/venus.png | Bin 0 -> 37098 bytes .../src/github.com/ajstarks/svgo/pmap/pmap.go | 244 ++ .../ajstarks/svgo/randcomp/randcomp.go | 59 + .../ajstarks/svgo/richter/richter.go | 35 + .../src/github.com/ajstarks/svgo/rl/rl.go | 34 + .../src/github.com/ajstarks/svgo/rpd/rpd.go | 80 + .../github.com/ajstarks/svgo/rpd/thing.xml | 5 + .../src/github.com/ajstarks/svgo/rr/rr.go | 208 ++ .../ajstarks/svgo/skewabc/skewabc.go | 37 + .../svgo/stockproduct/stockproduct.go | 179 ++ .../src/github.com/ajstarks/svgo/svg.go | 979 ++++++ .../src/github.com/ajstarks/svgo/svgdef.pdf | Bin 0 -> 234105 bytes .../src/github.com/ajstarks/svgo/svgdef.png | Bin 0 -> 434886 bytes .../src/github.com/ajstarks/svgo/svgdef.svg | 395 +++ .../github.com/ajstarks/svgo/svgdef/svgdef.go | 536 ++++ .../ajstarks/svgo/svgopher/svgopher.go | 76 + .../ajstarks/svgo/svgplay/svgplay.go | 320 ++ .../ajstarks/svgo/svgplot/mksvgplotdef | 24 + .../ajstarks/svgo/svgplot/svgplot.go | 301 ++ .../github.com/ajstarks/svgo/svgplot/test.d | 51 + .../github.com/ajstarks/svgo/svgrid/svgrid.go | 107 + .../src/github.com/ajstarks/svgo/tsg/tsg.go | 126 + .../github.com/ajstarks/svgo/tumblrgrid/tlist | 4 + .../ajstarks/svgo/tumblrgrid/tumblrgrid.go | 218 ++ .../ajstarks/svgo/turbulence/turbulence.go | 73 + .../github.com/ajstarks/svgo/vismem/vismem.go | 51 + .../ajstarks/svgo/webfonts/webfonts.go | 62 + .../github.com/ajstarks/svgo/websvg/websvg.go | 74 + .../src/github.com/bitly/go-nsq/.travis.yml | 27 + .../src/github.com/bitly/go-nsq/ChangeLog.md | 191 ++ .../src/github.com/bitly/go-nsq/LICENSE | 17 + .../src/github.com/bitly/go-nsq/README.md | 28 + .../src/github.com/bitly/go-nsq/UPGRADING.md | 180 ++ .../github.com/bitly/go-nsq/api_request.go | 78 + .../src/github.com/bitly/go-nsq/command.go | 214 ++ .../github.com/bitly/go-nsq/command_test.go | 23 + .../src/github.com/bitly/go-nsq/config.go | 562 ++++ .../github.com/bitly/go-nsq/config_test.go | 41 + .../src/github.com/bitly/go-nsq/conn.go | 706 +++++ .../src/github.com/bitly/go-nsq/consumer.go | 1094 +++++++ .../github.com/bitly/go-nsq/consumer_test.go | 225 ++ .../src/github.com/bitly/go-nsq/delegates.go | 143 + .../src/github.com/bitly/go-nsq/errors.go | 41 + .../src/github.com/bitly/go-nsq/message.go | 162 + .../src/github.com/bitly/go-nsq/mock_test.go | 265 ++ .../src/github.com/bitly/go-nsq/producer.go | 326 ++ .../github.com/bitly/go-nsq/producer_test.go | 271 ++ .../src/github.com/bitly/go-nsq/protocol.go | 96 + .../src/github.com/bitly/go-nsq/states.go | 12 + .../src/github.com/bitly/go-nsq/test.sh | 43 + .../src/github.com/bitly/go-nsq/test/ca.pem | 24 + .../github.com/bitly/go-nsq/test/server.key | 28 + .../github.com/bitly/go-nsq/test/server.pem | 24 + .../src/github.com/bitly/go-nsq/version.go | 8 + .../bitly/go-simplejson/.travis.yml | 10 + .../github.com/bitly/go-simplejson/LICENSE | 17 + .../github.com/bitly/go-simplejson/README.md | 13 + .../bitly/go-simplejson/simplejson.go | 421 +++ .../bitly/go-simplejson/simplejson_go10.go | 75 + .../go-simplejson/simplejson_go10_test.go | 86 + .../bitly/go-simplejson/simplejson_go11.go | 89 + .../go-simplejson/simplejson_go11_test.go | 96 + .../bitly/go-simplejson/simplejson_test.go | 235 ++ .../coreos/go-etcd/etcd/add_child.go | 23 + .../coreos/go-etcd/etcd/add_child_test.go | 73 + .../github.com/coreos/go-etcd/etcd/client.go | 435 +++ .../coreos/go-etcd/etcd/client_test.go | 96 + .../github.com/coreos/go-etcd/etcd/cluster.go | 51 + .../coreos/go-etcd/etcd/compare_and_delete.go | 34 + .../go-etcd/etcd/compare_and_delete_test.go | 46 + .../coreos/go-etcd/etcd/compare_and_swap.go | 36 + .../go-etcd/etcd/compare_and_swap_test.go | 57 + .../github.com/coreos/go-etcd/etcd/debug.go | 55 + .../coreos/go-etcd/etcd/debug_test.go | 28 + .../github.com/coreos/go-etcd/etcd/delete.go | 40 + .../coreos/go-etcd/etcd/delete_test.go | 81 + .../github.com/coreos/go-etcd/etcd/error.go | 48 + .../src/github.com/coreos/go-etcd/etcd/get.go | 27 + .../coreos/go-etcd/etcd/get_test.go | 131 + .../github.com/coreos/go-etcd/etcd/options.go | 72 + .../coreos/go-etcd/etcd/requests.go | 398 +++ .../coreos/go-etcd/etcd/requests_test.go | 22 + .../coreos/go-etcd/etcd/response.go | 89 + .../coreos/go-etcd/etcd/set_curl_chan_test.go | 42 + .../coreos/go-etcd/etcd/set_update_create.go | 137 + .../go-etcd/etcd/set_update_create_test.go | 241 ++ .../github.com/coreos/go-etcd/etcd/version.go | 3 + .../github.com/coreos/go-etcd/etcd/watch.go | 103 + .../coreos/go-etcd/etcd/watch_test.go | 119 + .../cupcake/sigil/gen/bench_test.go | 46 + .../src/github.com/cupcake/sigil/gen/sigil.go | 94 + .../github.com/dancannon/gorethink/.gitignore | 23 + .../dancannon/gorethink/CHANGELOG.md | 119 + .../github.com/dancannon/gorethink/LICENSE | 191 ++ .../github.com/dancannon/gorethink/README.md | 228 ++ .../github.com/dancannon/gorethink/buffer.go | 143 + .../dancannon/gorethink/connection.go | 290 ++ .../dancannon/gorethink/connection_helper.go | 91 + .../github.com/dancannon/gorethink/cursor.go | 419 +++ .../dancannon/gorethink/cursor_test.go | 325 ++ .../src/github.com/dancannon/gorethink/doc.go | 6 + .../dancannon/gorethink/encoding/cache.go | 258 ++ .../dancannon/gorethink/encoding/decoder.go | 136 + .../gorethink/encoding/decoder_test.go | 361 +++ .../gorethink/encoding/decoder_types.go | 520 ++++ .../dancannon/gorethink/encoding/encoder.go | 89 + .../gorethink/encoding/encoder_test.go | 224 ++ .../gorethink/encoding/encoder_types.go | 286 ++ .../dancannon/gorethink/encoding/encoding.go | 32 + .../dancannon/gorethink/encoding/errors.go | 102 + .../dancannon/gorethink/encoding/fold.go | 139 + .../dancannon/gorethink/encoding/tags.go | 69 + .../dancannon/gorethink/encoding/utils.go | 60 + .../github.com/dancannon/gorethink/errors.go | 119 + .../gorethink/example_query_select_test.go | 101 + .../gorethink/example_query_table_test.go | 81 + .../dancannon/gorethink/example_test.go | 46 + .../dancannon/gorethink/gorethink.go | 12 + .../dancannon/gorethink/gorethink_test.go | 394 +++ .../github.com/dancannon/gorethink/pool.go | 532 ++++ .../dancannon/gorethink/pool_conn.go | 75 + .../dancannon/gorethink/pseudotypes.go | 219 ++ .../dancannon/gorethink/ql2/ql2.pb.go | 1183 ++++++++ .../dancannon/gorethink/ql2/ql2.proto | 777 +++++ .../github.com/dancannon/gorethink/query.go | 274 ++ .../dancannon/gorethink/query_admin.go | 52 + .../dancannon/gorethink/query_admin_test.go | 91 + .../dancannon/gorethink/query_aggregation.go | 133 + .../gorethink/query_aggregation_test.go | 320 ++ .../dancannon/gorethink/query_control.go | 276 ++ .../dancannon/gorethink/query_control_test.go | 448 +++ .../dancannon/gorethink/query_db.go | 30 + .../dancannon/gorethink/query_db_test.go | 57 + .../dancannon/gorethink/query_geospatial.go | 170 ++ .../gorethink/query_geospatial_test.go | 530 ++++ .../dancannon/gorethink/query_join.go | 45 + .../dancannon/gorethink/query_join_test.go | 177 ++ .../dancannon/gorethink/query_manipulation.go | 111 + .../gorethink/query_manipulation_test.go | 318 ++ .../dancannon/gorethink/query_math.go | 177 ++ .../dancannon/gorethink/query_math_test.go | 252 ++ .../dancannon/gorethink/query_select.go | 108 + .../dancannon/gorethink/query_select_test.go | 467 +++ .../dancannon/gorethink/query_string.go | 35 + .../dancannon/gorethink/query_string_test.go | 91 + .../dancannon/gorethink/query_table.go | 143 + .../dancannon/gorethink/query_table_test.go | 214 ++ .../dancannon/gorethink/query_test.go | 50 + .../dancannon/gorethink/query_time.go | 170 ++ .../dancannon/gorethink/query_time_test.go | 137 + .../gorethink/query_transformation.go | 159 + .../gorethink/query_transformation_test.go | 426 +++ .../dancannon/gorethink/query_write.go | 111 + .../dancannon/gorethink/query_write_test.go | 99 + .../github.com/dancannon/gorethink/session.go | 140 + .../dancannon/gorethink/session_test.go | 70 + .../dancannon/gorethink/types/geometry.go | 214 ++ .../github.com/dancannon/gorethink/utils.go | 246 ++ .../dancannon/gorethink/wercker.yml | 37 + .../src/github.com/dchest/uniuri/.travis.yml | 7 + .../src/github.com/dchest/uniuri/LICENSE | 19 + .../src/github.com/dchest/uniuri/README.md | 83 + .../src/github.com/dchest/uniuri/uniuri.go | 75 + .../github.com/dchest/uniuri/uniuri_test.go | 69 + .../garyburd/redigo/internal/commandinfo.go | 45 + .../redigo/internal/redistest/testdb.go | 65 + .../github.com/garyburd/redigo/redis/conn.go | 455 +++ .../garyburd/redigo/redis/conn_test.go | 542 ++++ .../github.com/garyburd/redigo/redis/doc.go | 169 ++ .../github.com/garyburd/redigo/redis/log.go | 117 + .../github.com/garyburd/redigo/redis/pool.go | 389 +++ .../garyburd/redigo/redis/pool_test.go | 674 ++++ .../garyburd/redigo/redis/pubsub.go | 129 + .../garyburd/redigo/redis/pubsub_test.go | 143 + .../github.com/garyburd/redigo/redis/redis.go | 44 + .../github.com/garyburd/redigo/redis/reply.go | 312 ++ .../garyburd/redigo/redis/reply_test.go | 166 + .../github.com/garyburd/redigo/redis/scan.go | 513 ++++ .../garyburd/redigo/redis/scan_test.go | 412 +++ .../garyburd/redigo/redis/script.go | 86 + .../garyburd/redigo/redis/script_test.go | 93 + .../garyburd/redigo/redis/test_test.go | 38 + .../redigo/redis/zpop_example_test.go | 113 + .../src/github.com/gokyle/hotp/LICENSE | 13 + .../src/github.com/gokyle/hotp/README.md | 192 ++ .../src/github.com/gokyle/hotp/doc.go | 27 + .../src/github.com/gokyle/hotp/hotp.go | 315 ++ .../src/github.com/gokyle/hotp/hotp_test.go | 452 +++ .../src/github.com/gokyle/hotp/hotpcli/README | 43 + .../src/github.com/gokyle/hotp/hotpcli/cli.go | 77 + .../github.com/gokyle/hotp/hotpgen/LICENSE | 13 + .../src/github.com/gokyle/hotp/hotpgen/README | 42 + .../github.com/gokyle/hotp/hotpgen/hotpgen.go | 60 + .../src/github.com/gokyle/hotp/rfc4226.txt | 2075 +++++++++++++ .../gokyle/hotp/testdata/gauth_example.key | Bin 0 -> 30 bytes .../gokyle/hotp/testdata/gauth_example.png | Bin 0 -> 2537 bytes .../gokyle/hotp/testdata/gauth_example.txt | 1 + .../gokyle/hotp/testdata/yubikey.csv | 2 + .../src/github.com/gorilla/schema/.travis.yml | 7 + .../src/github.com/gorilla/schema/LICENSE | 27 + .../src/github.com/gorilla/schema/README.md | 3 + .../src/github.com/gorilla/schema/cache.go | 221 ++ .../github.com/gorilla/schema/converter.go | 145 + .../src/github.com/gorilla/schema/decoder.go | 246 ++ .../github.com/gorilla/schema/decoder_test.go | 1079 +++++++ .../src/github.com/gorilla/schema/doc.go | 123 + .../github.com/gorilla/websocket/.gitignore | 22 + .../github.com/gorilla/websocket/.travis.yml | 6 + .../src/github.com/gorilla/websocket/AUTHORS | 8 + .../src/github.com/gorilla/websocket/LICENSE | 22 + .../github.com/gorilla/websocket/README.md | 59 + .../gorilla/websocket/bench_test.go | 19 + .../github.com/gorilla/websocket/client.go | 235 ++ .../gorilla/websocket/client_server_test.go | 249 ++ .../gorilla/websocket/client_test.go | 63 + .../src/github.com/gorilla/websocket/conn.go | 825 +++++ .../github.com/gorilla/websocket/conn_test.go | 238 ++ .../src/github.com/gorilla/websocket/doc.go | 148 + .../websocket/examples/autobahn/README.md | 13 + .../examples/autobahn/fuzzingclient.json | 14 + .../websocket/examples/autobahn/server.go | 246 ++ .../gorilla/websocket/examples/chat/README.md | 19 + .../gorilla/websocket/examples/chat/conn.go | 106 + .../gorilla/websocket/examples/chat/home.html | 92 + .../gorilla/websocket/examples/chat/hub.go | 51 + .../gorilla/websocket/examples/chat/main.go | 39 + .../websocket/examples/filewatch/README.md | 9 + .../websocket/examples/filewatch/main.go | 193 ++ .../src/github.com/gorilla/websocket/json.go | 57 + .../github.com/gorilla/websocket/json_test.go | 119 + .../github.com/gorilla/websocket/server.go | 247 ++ .../gorilla/websocket/server_test.go | 33 + .../src/github.com/gorilla/websocket/util.go | 44 + .../github.com/gorilla/websocket/util_test.go | 34 + .../src/github.com/gyepisam/mcf/.gitignore | 2 + .../src/github.com/gyepisam/mcf/@build.do | 2 + .../src/github.com/gyepisam/mcf/@clean.do | 1 + .../src/github.com/gyepisam/mcf/@tests.do | 1 + .../src/github.com/gyepisam/mcf/DIRS | 5 + .../src/github.com/gyepisam/mcf/Makefile | 9 + .../src/github.com/gyepisam/mcf/README.md | 80 + .../github.com/gyepisam/mcf/bcrypt/base64.go | 37 + .../github.com/gyepisam/mcf/bcrypt/bcrypt.go | 67 + .../gyepisam/mcf/bcrypt/bcrypt_test.go | 164 + .../github.com/gyepisam/mcf/bridge/bridge.go | 122 + .../src/github.com/gyepisam/mcf/doc.go | 97 + .../gyepisam/mcf/encoder/encoder.go | 27 + .../src/github.com/gyepisam/mcf/encoding.go | 54 + .../src/github.com/gyepisam/mcf/mcf.go | 173 ++ .../gyepisam/mcf/password/password.go | 152 + .../github.com/gyepisam/mcf/pbkdf2/pbkdf2.go | 203 ++ .../gyepisam/mcf/pbkdf2/pbkdf2_test.go | 177 ++ .../github.com/gyepisam/mcf/scrypt/params.txt | 6 + .../github.com/gyepisam/mcf/scrypt/scrypt.go | 148 + .../gyepisam/mcf/scrypt/scrypt_test.go | 138 + .../gyepisam/mcf/scrypt/vectors_test.go | 136 + .../github.com/gyepisam/mcf/test/test_test.go | 90 + .../github.com/johntdyer/slack-go/slack.go | 89 + .../johntdyer/slack-go/slack_test.go | 50 + .../github.com/johntdyer/slackrus/README.md | 57 + .../johntdyer/slackrus/example/example.go | 28 + .../github.com/johntdyer/slackrus/levels.go | 25 + .../github.com/johntdyer/slackrus/slackrus.go | 109 + .../src/github.com/lavab/flag/LICENSE | 27 + .../src/github.com/lavab/flag/README.md | 169 ++ .../src/github.com/lavab/flag/example_test.go | 83 + .../lavab/flag/examples/gopher.conf | 5 + .../github.com/lavab/flag/examples/gopher.go | 29 + .../src/github.com/lavab/flag/export_test.go | 17 + .../src/github.com/lavab/flag/flag.go | 1154 +++++++ .../src/github.com/lavab/flag/flag_test.go | 476 +++ .../github.com/lavab/flag/testdata/test.conf | 11 + .../mreiferson/go-snappystream/.travis.yml | 13 + .../mreiferson/go-snappystream/LICENSE | 17 + .../mreiferson/go-snappystream/README.md | 11 + .../go-snappystream/fixturedata_test.go | 2701 +++++++++++++++++ .../mreiferson/go-snappystream/reader.go | 321 ++ .../mreiferson/go-snappystream/reader_test.go | 650 ++++ .../go-snappystream/readwrite_test.go | 425 +++ .../go-snappystream/snappystream.go | 54 + .../mreiferson/go-snappystream/writer.go | 229 ++ .../mreiferson/go-snappystream/writer_test.go | 126 + .../src/github.com/pzduniak/glogrus/LICENSE | 20 + .../src/github.com/pzduniak/glogrus/README.md | 36 + .../github.com/pzduniak/glogrus/glogrus.go | 80 + .../pzduniak/glogrus/writer_proxy.go | 70 + .../github.com/segmentio/go-loggly/History.md | 71 + .../github.com/segmentio/go-loggly/Readme.md | 17 + .../github.com/segmentio/go-loggly/loggly.go | 306 ++ .../visionmedia/go-debug/History.md | 21 + .../github.com/visionmedia/go-debug/Makefile | 8 + .../github.com/visionmedia/go-debug/Readme.md | 75 + .../github.com/visionmedia/go-debug/debug.go | 128 + .../visionmedia/go-debug/debug_test.go | 152 + .../visionmedia/go-debug/example/multiple.go | 25 + .../visionmedia/go-debug/example/single.go | 16 + .../zenazn/goji/graceful/einhorn.go | 21 + .../zenazn/goji/graceful/graceful.go | 57 + .../zenazn/goji/graceful/listener/conn.go | 149 + .../goji/graceful/listener/conn_test.go | 182 ++ .../goji/graceful/listener/fake_test.go | 123 + .../zenazn/goji/graceful/listener/listener.go | 178 ++ .../goji/graceful/listener/listener_test.go | 156 + .../goji/graceful/listener/race_test.go | 103 + .../zenazn/goji/graceful/listener/shard.go | 98 + .../goji/graceful/listener/shard_test.go | 21 + .../zenazn/goji/graceful/middleware.go | 103 + .../zenazn/goji/graceful/middleware_test.go | 71 + .../github.com/zenazn/goji/graceful/serve.go | 32 + .../zenazn/goji/graceful/serve13.go | 75 + .../github.com/zenazn/goji/graceful/server.go | 82 + .../github.com/zenazn/goji/graceful/signal.go | 197 ++ .../src/github.com/zenazn/goji/web/atomic.go | 18 + .../zenazn/goji/web/atomic_appengine.go | 14 + .../github.com/zenazn/goji/web/bench_test.go | 153 + .../zenazn/goji/web/bytecode_compiler.go | 265 ++ .../zenazn/goji/web/bytecode_runner.go | 85 + .../github.com/zenazn/goji/web/chanpool.go | 31 + .../src/github.com/zenazn/goji/web/cpool.go | 23 + .../github.com/zenazn/goji/web/func_equal.go | 38 + .../zenazn/goji/web/func_equal_test.go | 84 + .../github.com/zenazn/goji/web/middleware.go | 154 + .../zenazn/goji/web/middleware/envinit.go | 26 + .../zenazn/goji/web/middleware/logger.go | 92 + .../zenazn/goji/web/middleware/middleware.go | 4 + .../zenazn/goji/web/middleware/nocache.go | 55 + .../goji/web/middleware/nocache_test.go | 29 + .../zenazn/goji/web/middleware/options.go | 97 + .../goji/web/middleware/options_test.go | 112 + .../zenazn/goji/web/middleware/realip.go | 61 + .../zenazn/goji/web/middleware/recoverer.go | 44 + .../zenazn/goji/web/middleware/request_id.go | 88 + .../zenazn/goji/web/middleware/subrouter.go | 45 + .../zenazn/goji/web/middleware/terminal.go | 60 + .../zenazn/goji/web/middleware12_test.go | 52 + .../zenazn/goji/web/middleware_test.go | 204 ++ .../github.com/zenazn/goji/web/mutil/mutil.go | 3 + .../zenazn/goji/web/mutil/writer_proxy.go | 125 + .../src/github.com/zenazn/goji/web/mux.go | 212 ++ .../github.com/zenazn/goji/web/mux_test.go | 45 + .../src/github.com/zenazn/goji/web/pattern.go | 78 + .../zenazn/goji/web/pattern_test.go | 188 ++ .../zenazn/goji/web/regexp_pattern.go | 145 + .../src/github.com/zenazn/goji/web/router.go | 173 ++ .../github.com/zenazn/goji/web/router_test.go | 326 ++ .../zenazn/goji/web/string_pattern.go | 133 + .../src/github.com/zenazn/goji/web/web.go | 128 + .../src/golang.org/x/crypto/cast5/cast5.go | 526 ++++ .../golang.org/x/crypto/cast5/cast5_test.go | 106 + .../x/crypto/openpgp/armor/armor.go | 219 ++ .../x/crypto/openpgp/armor/armor_test.go | 95 + .../x/crypto/openpgp/armor/encode.go | 160 + .../x/crypto/openpgp/canonical_text.go | 59 + .../x/crypto/openpgp/canonical_text_test.go | 52 + .../x/crypto/openpgp/clearsign/clearsign.go | 366 +++ .../openpgp/clearsign/clearsign_test.go | 187 ++ .../x/crypto/openpgp/elgamal/elgamal.go | 122 + .../x/crypto/openpgp/elgamal/elgamal_test.go | 49 + .../x/crypto/openpgp/errors/errors.go | 72 + .../src/golang.org/x/crypto/openpgp/keys.go | 624 ++++ .../golang.org/x/crypto/openpgp/keys_test.go | 216 ++ .../x/crypto/openpgp/packet/compressed.go | 123 + .../crypto/openpgp/packet/compressed_test.go | 41 + .../x/crypto/openpgp/packet/config.go | 70 + .../x/crypto/openpgp/packet/encrypted_key.go | 168 + .../openpgp/packet/encrypted_key_test.go | 125 + .../x/crypto/openpgp/packet/literal.go | 89 + .../x/crypto/openpgp/packet/ocfb.go | 143 + .../x/crypto/openpgp/packet/ocfb_test.go | 46 + .../openpgp/packet/one_pass_signature.go | 73 + .../x/crypto/openpgp/packet/opaque.go | 161 + .../x/crypto/openpgp/packet/opaque_test.go | 67 + .../x/crypto/openpgp/packet/packet.go | 538 ++++ .../x/crypto/openpgp/packet/packet_test.go | 255 ++ .../x/crypto/openpgp/packet/private_key.go | 310 ++ .../crypto/openpgp/packet/private_key_test.go | 64 + .../x/crypto/openpgp/packet/public_key.go | 687 +++++ .../crypto/openpgp/packet/public_key_test.go | 202 ++ .../x/crypto/openpgp/packet/public_key_v3.go | 275 ++ .../openpgp/packet/public_key_v3_test.go | 82 + .../x/crypto/openpgp/packet/reader.go | 62 + .../x/crypto/openpgp/packet/signature.go | 663 ++++ .../x/crypto/openpgp/packet/signature_test.go | 42 + .../x/crypto/openpgp/packet/signature_v3.go | 146 + .../openpgp/packet/signature_v3_test.go | 92 + .../openpgp/packet/symmetric_key_encrypted.go | 164 + .../packet/symmetric_key_encrypted_test.go | 102 + .../openpgp/packet/symmetrically_encrypted.go | 290 ++ .../packet/symmetrically_encrypted_test.go | 123 + .../x/crypto/openpgp/packet/userattribute.go | 91 + .../openpgp/packet/userattribute_test.go | 109 + .../x/crypto/openpgp/packet/userid.go | 160 + .../x/crypto/openpgp/packet/userid_test.go | 87 + .../src/golang.org/x/crypto/openpgp/read.go | 423 +++ .../golang.org/x/crypto/openpgp/read_test.go | 416 +++ .../golang.org/x/crypto/openpgp/s2k/s2k.go | 218 ++ .../x/crypto/openpgp/s2k/s2k_test.go | 134 + .../src/golang.org/x/crypto/openpgp/write.go | 374 +++ .../golang.org/x/crypto/openpgp/write_test.go | 234 ++ .../x/crypto/ripemd160/ripemd160.go | 120 + .../x/crypto/ripemd160/ripemd160_test.go | 64 + .../x/crypto/ripemd160/ripemd160block.go | 161 + .../igm/sockjs-go.v2/sockjs/.gitignore | 3 + .../sockjs-go.v2/sockjs/benchmarks_test.go | 82 + .../gopkg.in/igm/sockjs-go.v2/sockjs/doc.go | 5 + .../igm/sockjs-go.v2/sockjs/eventsource.go | 32 + .../sockjs-go.v2/sockjs/eventsource_test.go | 70 + .../sockjs/example_handler_test.go | 39 + .../gopkg.in/igm/sockjs-go.v2/sockjs/frame.go | 11 + .../igm/sockjs-go.v2/sockjs/frame_test.go | 10 + .../igm/sockjs-go.v2/sockjs/handler.go | 120 + .../igm/sockjs-go.v2/sockjs/handler_test.go | 77 + .../igm/sockjs-go.v2/sockjs/htmlfile.go | 58 + .../igm/sockjs-go.v2/sockjs/htmlfile_test.go | 60 + .../igm/sockjs-go.v2/sockjs/httpreceiver.go | 99 + .../sockjs-go.v2/sockjs/httpreceiver_test.go | 101 + .../igm/sockjs-go.v2/sockjs/iframe.go | 42 + .../igm/sockjs-go.v2/sockjs/iframe_test.go | 42 + .../gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go | 75 + .../igm/sockjs-go.v2/sockjs/jsonp_test.go | 96 + .../igm/sockjs-go.v2/sockjs/mapping.go | 36 + .../igm/sockjs-go.v2/sockjs/mapping_test.go | 38 + .../igm/sockjs-go.v2/sockjs/options.go | 103 + .../igm/sockjs-go.v2/sockjs/options_test.go | 64 + .../igm/sockjs-go.v2/sockjs/session.go | 216 ++ .../igm/sockjs-go.v2/sockjs/session_test.go | 319 ++ .../igm/sockjs-go.v2/sockjs/sockjs.go | 13 + .../igm/sockjs-go.v2/sockjs/sockjs_test.go | 27 + .../gopkg.in/igm/sockjs-go.v2/sockjs/utils.go | 16 + .../igm/sockjs-go.v2/sockjs/utils_test.go | 19 + .../gopkg.in/igm/sockjs-go.v2/sockjs/web.go | 47 + .../igm/sockjs-go.v2/sockjs/web_test.go | 69 + .../igm/sockjs-go.v2/sockjs/websocket.go | 89 + .../igm/sockjs-go.v2/sockjs/websocket_test.go | 116 + .../gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go | 88 + .../igm/sockjs-go.v2/sockjs/xhr_test.go | 184 ++ 564 files changed, 93538 insertions(+) create mode 100644 Godeps/Godeps.json create mode 100644 Godeps/Readme create mode 100644 Godeps/_workspace/.gitignore create mode 100644 Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go create mode 100644 Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt.go create mode 100644 Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/gf256/blog_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/gen.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/qr.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/qr_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/libqrencode/qrencode.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/png.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/png_test.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/qr.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/web/pic.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/web/play.go create mode 100644 Godeps/_workspace/src/code.google.com/p/rsc/qr/web/resize/resize.go create mode 100644 Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go create mode 100644 Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go create mode 100644 Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go create mode 100644 Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go create mode 100644 Godeps/_workspace/src/github.com/GeertJohan/yubigo/.gitignore create mode 100644 Godeps/_workspace/src/github.com/GeertJohan/yubigo/LICENSE create mode 100644 Godeps/_workspace/src/github.com/GeertJohan/yubigo/readme.md create mode 100644 Godeps/_workspace/src/github.com/GeertJohan/yubigo/yubigo.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go create mode 100644 Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/LICENSE create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/README.markdown create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/android/android.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/barchart/barchart.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/benchviz/benchviz.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/bubtrail/bubtrail.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/bulletgraph/bulletgraph.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/codepic/codepic.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/colortab.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/svgcolors.txt create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/compx/comps.xml create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/compx/compx.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/compx/testcomp.xml create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/doc.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/f50/f50.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/fe/fe.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/flower/flower.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/fontcompare/fontcompare.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/funnel/funnel.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/gophercolor128x128.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/gradient/gradient.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/html5logo/html5logo.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/imfade/gophercolor128x128.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/imfade/imfade.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/lewitt/lewitt.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/ltr/ltr.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/marker/marker.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/newsvg create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/paths/paths.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/pattern/pattern.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/earth.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/jupiter.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mars.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mercury.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/neptune.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/planets.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/saturn.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/sun.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/uranus.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/planets/venus.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/pmap/pmap.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/randcomp/randcomp.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/richter/richter.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/rl/rl.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/rpd.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/thing.xml create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/rr/rr.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/skewabc/skewabc.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/stockproduct/stockproduct.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svg.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.pdf create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.png create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.svg create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef/svgdef.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgopher/svgopher.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgplay/svgplay.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/mksvgplotdef create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/svgplot.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/test.d create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/svgrid/svgrid.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/tsg/tsg.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tlist create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tumblrgrid.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/turbulence/turbulence.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/vismem/vismem.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/webfonts/webfonts.go create mode 100644 Godeps/_workspace/src/github.com/ajstarks/svgo/websvg/websvg.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/LICENSE create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/README.md create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/UPGRADING.md create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/api_request.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/command.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/config.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/message.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/protocol.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/states.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/test/ca.pem create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.key create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.pem create mode 100644 Godeps/_workspace/src/github.com/bitly/go-nsq/version.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/LICENSE create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/README.md create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11_test.go create mode 100644 Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go create mode 100644 Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go create mode 100644 Godeps/_workspace/src/github.com/cupcake/sigil/gen/bench_test.go create mode 100644 Godeps/_workspace/src/github.com/cupcake/sigil/gen/sigil.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/LICENSE create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/README.md create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/buffer.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/cache.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/errors.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/fold.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/tags.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/pool_conn.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/session.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml create mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE create mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/README.md create mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go create mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go create mode 100644 Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/README.md create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/doc.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotp.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotp_test.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/README create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/cli.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/README create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/hotpgen.go create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/rfc4226.txt create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.key create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.png create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.txt create mode 100644 Godeps/_workspace/src/github.com/gokyle/hotp/testdata/yubikey.csv create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/cache.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/converter.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/decoder.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/schema/doc.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/client.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/conn.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/doc.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/json.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/server.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/util.go create mode 100644 Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/.gitignore create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/@build.do create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/@clean.do create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/@tests.do create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/DIRS create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/Makefile create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/README.md create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/base64.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt_test.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/bridge/bridge.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/doc.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/encoder/encoder.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/encoding.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/mcf.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/password/password.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2_test.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/params.txt create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt_test.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/vectors_test.go create mode 100644 Godeps/_workspace/src/github.com/gyepisam/mcf/test/test_test.go create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slack-go/slack.go create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slack-go/slack_test.go create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slackrus/README.md create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slackrus/example/example.go create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slackrus/levels.go create mode 100644 Godeps/_workspace/src/github.com/johntdyer/slackrus/slackrus.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/LICENSE create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/README.md create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/example_test.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.conf create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/export_test.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/flag.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/flag_test.go create mode 100644 Godeps/_workspace/src/github.com/lavab/flag/testdata/test.conf create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/LICENSE create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/README.md create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/fixturedata_test.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader_test.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/readwrite_test.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/snappystream.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer.go create mode 100644 Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer_test.go create mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE create mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md create mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go create mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go create mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md create mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md create mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go create mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/einhorn.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/graceful.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/fake_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/race_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve13.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/server.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/graceful/signal.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/atomic.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/atomic_appengine.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/bench_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_compiler.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_runner.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/chanpool.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/cpool.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/envinit.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/logger.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/middleware.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/realip.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/recoverer.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/request_id.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/subrouter.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/terminal.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware12_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/middleware_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/mutil.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/writer_proxy.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/mux.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/mux_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/pattern.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/pattern_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/regexp_pattern.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/router.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/router_test.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/string_pattern.go create mode 100644 Godeps/_workspace/src/github.com/zenazn/goji/web/web.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/encode.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/errors/errors.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/literal.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/write.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/openpgp/write_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160_test.go create mode 100644 Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160block.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/.gitignore create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/benchmarks_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/doc.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/example_handler_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket_test.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go create mode 100644 Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json new file mode 100644 index 0000000..dce4843 --- /dev/null +++ b/Godeps/Godeps.json @@ -0,0 +1,163 @@ +{ + "ImportPath": "github.com/lavab/api", + "GoVersion": "go1.4", + "Deps": [ + { + "ImportPath": "code.google.com/p/go.crypto/pbkdf2", + "Comment": "null-212", + "Rev": "1064b89a6fb591df0dd65422295b8498916b092f" + }, + { + "ImportPath": "code.google.com/p/go.crypto/scrypt", + "Comment": "null-212", + "Rev": "1064b89a6fb591df0dd65422295b8498916b092f" + }, + { + "ImportPath": "code.google.com/p/goprotobuf/proto", + "Comment": "go.r60-152", + "Rev": "36be16571e14f67e114bb0af619e5de2c1591679" + }, + { + "ImportPath": "code.google.com/p/rsc/gf256", + "Comment": "null-258", + "Rev": "2d8aa6027fab93979a3b4ecdbeb1ba430b489318" + }, + { + "ImportPath": "code.google.com/p/rsc/qr", + "Comment": "null-258", + "Rev": "2d8aa6027fab93979a3b4ecdbeb1ba430b489318" + }, + { + "ImportPath": "code.google.com/p/snappy-go/snappy", + "Comment": "null-15", + "Rev": "12e4b4183793ac4b061921e7980845e750679fd0" + }, + { + "ImportPath": "github.com/GeertJohan/yubigo", + "Rev": "b1764f04aa9ba3c98a15084e7e13c1a69753e1da" + }, + { + "ImportPath": "github.com/Sirupsen/logrus", + "Comment": "v0.6.4", + "Rev": "58f778a886b1e483dccc3b61085ccf347bf1a37e" + }, + { + "ImportPath": "github.com/ajstarks/svgo", + "Comment": "go.weekly.2012-01-27-94-g47ca2bb", + "Rev": "47ca2bbf229311c9b8b96a9188ae37790250757b" + }, + { + "ImportPath": "github.com/bitly/go-nsq", + "Comment": "v1.0.1-8-gba766e5", + "Rev": "ba766e5ff96fe184689976d5424d5b266bec4742" + }, + { + "ImportPath": "github.com/bitly/go-simplejson", + "Comment": "v0.4.3-28-g3378bdc", + "Rev": "3378bdcb5cebedcbf8b5750edee28010f128fe24" + }, + { + "ImportPath": "github.com/coreos/go-etcd/etcd", + "Comment": "v0.2.0-rc1-130-g6aa2da5", + "Rev": "6aa2da5a7a905609c93036b9307185a04a5a84a5" + }, + { + "ImportPath": "github.com/cupcake/sigil/gen", + "Rev": "6bf9722f2ae82f58a0c7d48daeb40ec844b78eae" + }, + { + "ImportPath": "github.com/dancannon/gorethink", + "Comment": "v0.6.1", + "Rev": "441666ad982764e434a151f17dfe399a846cee54" + }, + { + "ImportPath": "github.com/dchest/uniuri", + "Rev": "9eb2cf09193567a3329b188db6e6f924a8e3347a" + }, + { + "ImportPath": "github.com/garyburd/redigo/internal", + "Rev": "535138d7bcd717d6531c701ef5933d98b1866257" + }, + { + "ImportPath": "github.com/garyburd/redigo/redis", + "Rev": "535138d7bcd717d6531c701ef5933d98b1866257" + }, + { + "ImportPath": "github.com/gokyle/hotp", + "Rev": "cab5c36e945092b6fb703b6c785bb06fd0537a0d" + }, + { + "ImportPath": "github.com/gorilla/schema", + "Rev": "c8422571edf3131506bab7df27e18980fe2598d5" + }, + { + "ImportPath": "github.com/gorilla/websocket", + "Rev": "2dbddebb8266b93c5e6b119efb54e89043186f3f" + }, + { + "ImportPath": "github.com/gyepisam/mcf", + "Comment": "v0.02", + "Rev": "805056816f56678a0e70fff55cdc209fb9a21d73" + }, + { + "ImportPath": "github.com/johntdyer/slack-go", + "Rev": "88736fd63eed11c942b478c3182bdd2f152971e5" + }, + { + "ImportPath": "github.com/johntdyer/slackrus", + "Rev": "85cb2fcfa59888a7abe9d16e844501aa3782e259" + }, + { + "ImportPath": "github.com/lavab/flag", + "Rev": "6d5479a9c2f744a1db55e2b5a5f610f50002fdad" + }, + { + "ImportPath": "github.com/mreiferson/go-snappystream", + "Comment": "v0.2.2-8-ga5260a3", + "Rev": "a5260a307b3e7dd583283c1e2717445244d506c7" + }, + { + "ImportPath": "github.com/pzduniak/glogrus", + "Rev": "7a43c02bc2d246e35cf2c88564ddb2ed3ac741d3" + }, + { + "ImportPath": "github.com/segmentio/go-loggly", + "Comment": "v0.4.3-4-ge78f697", + "Rev": "e78f6971ebca5835614673e2f5f6a47ca5f13501" + }, + { + "ImportPath": "github.com/visionmedia/go-debug", + "Comment": "v2.0.0", + "Rev": "ff4a55a20a86994118644bbddc6a216da193cc13" + }, + { + "ImportPath": "github.com/zenazn/goji/graceful", + "Comment": "v0.8.3", + "Rev": "dcb9598cbbcfcc10d2c1ee1e66958e54a2c84b52" + }, + { + "ImportPath": "github.com/zenazn/goji/web", + "Comment": "v0.8.3", + "Rev": "dcb9598cbbcfcc10d2c1ee1e66958e54a2c84b52" + }, + { + "ImportPath": "golang.org/x/crypto/cast5", + "Comment": "null-233", + "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + }, + { + "ImportPath": "golang.org/x/crypto/openpgp", + "Comment": "null-233", + "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + }, + { + "ImportPath": "golang.org/x/crypto/ripemd160", + "Comment": "null-233", + "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + }, + { + "ImportPath": "gopkg.in/igm/sockjs-go.v2/sockjs", + "Rev": "9dbc79efa97d9a6c6b4a515893bd02c9242e9225" + } + ] +} diff --git a/Godeps/Readme b/Godeps/Readme new file mode 100644 index 0000000..4cdaa53 --- /dev/null +++ b/Godeps/Readme @@ -0,0 +1,5 @@ +This directory tree is generated automatically by godep. + +Please do not edit. + +See https://github.com/tools/godep for more information. diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore new file mode 100644 index 0000000..f037d68 --- /dev/null +++ b/Godeps/_workspace/.gitignore @@ -0,0 +1,2 @@ +/pkg +/bin diff --git a/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go b/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go new file mode 100644 index 0000000..c02b4d5 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2.go @@ -0,0 +1,77 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC +2898 / PKCS #5 v2.0. + +A key derivation function is useful when encrypting data based on a password +or any other not-fully-random data. It uses a pseudorandom function to derive +a secure encryption key based on the password. + +While v2.0 of the standard defines only one pseudorandom function to use, +HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved +Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To +choose, you can pass the `New` functions from the different SHA packages to +pbkdf2.Key. +*/ +package pbkdf2 + +import ( + "crypto/hmac" + "hash" +) + +// Key derives a key from the password, salt and iteration count, returning a +// []byte of length keylen that can be used as cryptographic key. The key is +// derived based on the method described as PBKDF2 with the HMAC variant using +// the supplied hash function. +// +// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you +// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by +// doing: +// +// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New) +// +// Remember to get a good random salt. At least 8 bytes is recommended by the +// RFC. +// +// Using a higher iteration count will increase the cost of an exhaustive +// search but will also make derivation proportionally slower. +func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} diff --git a/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go b/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go new file mode 100644 index 0000000..1379240 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go.crypto/pbkdf2/pbkdf2_test.go @@ -0,0 +1,157 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pbkdf2 + +import ( + "bytes" + "crypto/sha1" + "crypto/sha256" + "hash" + "testing" +) + +type testVector struct { + password string + salt string + iter int + output []byte +} + +// Test vectors from RFC 6070, http://tools.ietf.org/html/rfc6070 +var sha1TestVectors = []testVector{ + { + "password", + "salt", + 1, + []byte{ + 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, + 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, + 0x2f, 0xe0, 0x37, 0xa6, + }, + }, + { + "password", + "salt", + 2, + []byte{ + 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, + 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, + 0xd8, 0xde, 0x89, 0x57, + }, + }, + { + "password", + "salt", + 4096, + []byte{ + 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, + 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0, + 0x65, 0xa4, 0x29, 0xc1, + }, + }, + // // This one takes too long + // { + // "password", + // "salt", + // 16777216, + // []byte{ + // 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4, + // 0xe9, 0x94, 0x5b, 0x3d, 0x6b, 0xa2, 0x15, 0x8c, + // 0x26, 0x34, 0xe9, 0x84, + // }, + // }, + { + "passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + []byte{ + 0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b, + 0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a, + 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70, + 0x38, + }, + }, + { + "pass\000word", + "sa\000lt", + 4096, + []byte{ + 0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d, + 0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3, + }, + }, +} + +// Test vectors from +// http://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors +var sha256TestVectors = []testVector{ + { + "password", + "salt", + 1, + []byte{ + 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, + 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, + 0xa8, 0x65, 0x48, 0xc9, + }, + }, + { + "password", + "salt", + 2, + []byte{ + 0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3, + 0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0, + 0x2a, 0x30, 0x3f, 0x8e, + }, + }, + { + "password", + "salt", + 4096, + []byte{ + 0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, + 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, + 0x96, 0x28, 0x93, 0xa0, + }, + }, + { + "passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + []byte{ + 0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f, + 0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf, + 0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18, + 0x1c, + }, + }, + { + "pass\000word", + "sa\000lt", + 4096, + []byte{ + 0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89, + 0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87, + }, + }, +} + +func testHash(t *testing.T, h func() hash.Hash, hashName string, vectors []testVector) { + for i, v := range vectors { + o := Key([]byte(v.password), []byte(v.salt), v.iter, len(v.output), h) + if !bytes.Equal(o, v.output) { + t.Errorf("%s %d: expected %x, got %x", hashName, i, v.output, o) + } + } +} + +func TestWithHMACSHA1(t *testing.T) { + testHash(t, sha1.New, "SHA1", sha1TestVectors) +} + +func TestWithHMACSHA256(t *testing.T) { + testHash(t, sha256.New, "SHA256", sha256TestVectors) +} diff --git a/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt.go b/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt.go new file mode 100644 index 0000000..f7a9fe1 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt.go @@ -0,0 +1,243 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package scrypt implements the scrypt key derivation function as defined in +// Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard +// Functions" (http://www.tarsnap.com/scrypt/scrypt.pdf). +package scrypt + +import ( + "crypto/sha256" + "errors" + + "code.google.com/p/go.crypto/pbkdf2" +) + +const maxInt = int(^uint(0) >> 1) + +// blockCopy copies n numbers from src into dst. +func blockCopy(dst, src []uint32, n int) { + copy(dst, src[:n]) +} + +// blockXOR XORs numbers from dst with n numbers from src. +func blockXOR(dst, src []uint32, n int) { + for i, v := range src[:n] { + dst[i] ^= v + } +} + +// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in, +// and puts the result into both both tmp and out. +func salsaXOR(tmp *[16]uint32, in, out []uint32) { + w0 := tmp[0] ^ in[0] + w1 := tmp[1] ^ in[1] + w2 := tmp[2] ^ in[2] + w3 := tmp[3] ^ in[3] + w4 := tmp[4] ^ in[4] + w5 := tmp[5] ^ in[5] + w6 := tmp[6] ^ in[6] + w7 := tmp[7] ^ in[7] + w8 := tmp[8] ^ in[8] + w9 := tmp[9] ^ in[9] + w10 := tmp[10] ^ in[10] + w11 := tmp[11] ^ in[11] + w12 := tmp[12] ^ in[12] + w13 := tmp[13] ^ in[13] + w14 := tmp[14] ^ in[14] + w15 := tmp[15] ^ in[15] + + x0, x1, x2, x3, x4, x5, x6, x7, x8 := w0, w1, w2, w3, w4, w5, w6, w7, w8 + x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15 + + for i := 0; i < 8; i += 2 { + u := x0 + x12 + x4 ^= u<<7 | u>>(32-7) + u = x4 + x0 + x8 ^= u<<9 | u>>(32-9) + u = x8 + x4 + x12 ^= u<<13 | u>>(32-13) + u = x12 + x8 + x0 ^= u<<18 | u>>(32-18) + + u = x5 + x1 + x9 ^= u<<7 | u>>(32-7) + u = x9 + x5 + x13 ^= u<<9 | u>>(32-9) + u = x13 + x9 + x1 ^= u<<13 | u>>(32-13) + u = x1 + x13 + x5 ^= u<<18 | u>>(32-18) + + u = x10 + x6 + x14 ^= u<<7 | u>>(32-7) + u = x14 + x10 + x2 ^= u<<9 | u>>(32-9) + u = x2 + x14 + x6 ^= u<<13 | u>>(32-13) + u = x6 + x2 + x10 ^= u<<18 | u>>(32-18) + + u = x15 + x11 + x3 ^= u<<7 | u>>(32-7) + u = x3 + x15 + x7 ^= u<<9 | u>>(32-9) + u = x7 + x3 + x11 ^= u<<13 | u>>(32-13) + u = x11 + x7 + x15 ^= u<<18 | u>>(32-18) + + u = x0 + x3 + x1 ^= u<<7 | u>>(32-7) + u = x1 + x0 + x2 ^= u<<9 | u>>(32-9) + u = x2 + x1 + x3 ^= u<<13 | u>>(32-13) + u = x3 + x2 + x0 ^= u<<18 | u>>(32-18) + + u = x5 + x4 + x6 ^= u<<7 | u>>(32-7) + u = x6 + x5 + x7 ^= u<<9 | u>>(32-9) + u = x7 + x6 + x4 ^= u<<13 | u>>(32-13) + u = x4 + x7 + x5 ^= u<<18 | u>>(32-18) + + u = x10 + x9 + x11 ^= u<<7 | u>>(32-7) + u = x11 + x10 + x8 ^= u<<9 | u>>(32-9) + u = x8 + x11 + x9 ^= u<<13 | u>>(32-13) + u = x9 + x8 + x10 ^= u<<18 | u>>(32-18) + + u = x15 + x14 + x12 ^= u<<7 | u>>(32-7) + u = x12 + x15 + x13 ^= u<<9 | u>>(32-9) + u = x13 + x12 + x14 ^= u<<13 | u>>(32-13) + u = x14 + x13 + x15 ^= u<<18 | u>>(32-18) + } + x0 += w0 + x1 += w1 + x2 += w2 + x3 += w3 + x4 += w4 + x5 += w5 + x6 += w6 + x7 += w7 + x8 += w8 + x9 += w9 + x10 += w10 + x11 += w11 + x12 += w12 + x13 += w13 + x14 += w14 + x15 += w15 + + out[0], tmp[0] = x0, x0 + out[1], tmp[1] = x1, x1 + out[2], tmp[2] = x2, x2 + out[3], tmp[3] = x3, x3 + out[4], tmp[4] = x4, x4 + out[5], tmp[5] = x5, x5 + out[6], tmp[6] = x6, x6 + out[7], tmp[7] = x7, x7 + out[8], tmp[8] = x8, x8 + out[9], tmp[9] = x9, x9 + out[10], tmp[10] = x10, x10 + out[11], tmp[11] = x11, x11 + out[12], tmp[12] = x12, x12 + out[13], tmp[13] = x13, x13 + out[14], tmp[14] = x14, x14 + out[15], tmp[15] = x15, x15 +} + +func blockMix(tmp *[16]uint32, in, out []uint32, r int) { + blockCopy(tmp[:], in[(2*r-1)*16:], 16) + for i := 0; i < 2*r; i += 2 { + salsaXOR(tmp, in[i*16:], out[i*8:]) + salsaXOR(tmp, in[i*16+16:], out[i*8+r*16:]) + } +} + +func integer(b []uint32, r int) uint64 { + j := (2*r - 1) * 16 + return uint64(b[j]) | uint64(b[j+1])<<32 +} + +func smix(b []byte, r, N int, v, xy []uint32) { + var tmp [16]uint32 + x := xy + y := xy[32*r:] + + j := 0 + for i := 0; i < 32*r; i++ { + x[i] = uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24 + j += 4 + } + for i := 0; i < N; i += 2 { + blockCopy(v[i*(32*r):], x, 32*r) + blockMix(&tmp, x, y, r) + + blockCopy(v[(i+1)*(32*r):], y, 32*r) + blockMix(&tmp, y, x, r) + } + for i := 0; i < N; i += 2 { + j := int(integer(x, r) & uint64(N-1)) + blockXOR(x, v[j*(32*r):], 32*r) + blockMix(&tmp, x, y, r) + + j = int(integer(y, r) & uint64(N-1)) + blockXOR(y, v[j*(32*r):], 32*r) + blockMix(&tmp, y, x, r) + } + j = 0 + for _, v := range x[:32*r] { + b[j+0] = byte(v >> 0) + b[j+1] = byte(v >> 8) + b[j+2] = byte(v >> 16) + b[j+3] = byte(v >> 24) + j += 4 + } +} + +// Key derives a key from the password, salt, and cost parameters, returning +// a byte slice of length keyLen that can be used as cryptographic key. +// +// N is a CPU/memory cost parameter, which must be a power of two greater than 1. +// r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the +// limits, the function returns a nil byte slice and an error. +// +// For example, you can get a derived key for e.g. AES-256 (which needs a +// 32-byte key) by doing: +// +// dk := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32) +// +// The recommended parameters for interactive logins as of 2009 are N=16384, +// r=8, p=1. They should be increased as memory latency and CPU parallelism +// increases. Remember to get a good random salt. +func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) { + if N <= 1 || N&(N-1) != 0 { + return nil, errors.New("scrypt: N must be > 1 and a power of 2") + } + if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r { + return nil, errors.New("scrypt: parameters are too large") + } + + xy := make([]uint32, 64*r) + v := make([]uint32, 32*N*r) + b := pbkdf2.Key(password, salt, 1, p*128*r, sha256.New) + + for i := 0; i < p; i++ { + smix(b[i*128*r:], r, N, v, xy) + } + + return pbkdf2.Key(password, b, 1, keyLen, sha256.New), nil +} diff --git a/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go b/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go new file mode 100644 index 0000000..e096c3a --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/go.crypto/scrypt/scrypt_test.go @@ -0,0 +1,160 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scrypt + +import ( + "bytes" + "testing" +) + +type testVector struct { + password string + salt string + N, r, p int + output []byte +} + +var good = []testVector{ + { + "password", + "salt", + 2, 10, 10, + []byte{ + 0x48, 0x2c, 0x85, 0x8e, 0x22, 0x90, 0x55, 0xe6, 0x2f, + 0x41, 0xe0, 0xec, 0x81, 0x9a, 0x5e, 0xe1, 0x8b, 0xdb, + 0x87, 0x25, 0x1a, 0x53, 0x4f, 0x75, 0xac, 0xd9, 0x5a, + 0xc5, 0xe5, 0xa, 0xa1, 0x5f, + }, + }, + { + "password", + "salt", + 16, 100, 100, + []byte{ + 0x88, 0xbd, 0x5e, 0xdb, 0x52, 0xd1, 0xdd, 0x0, 0x18, + 0x87, 0x72, 0xad, 0x36, 0x17, 0x12, 0x90, 0x22, 0x4e, + 0x74, 0x82, 0x95, 0x25, 0xb1, 0x8d, 0x73, 0x23, 0xa5, + 0x7f, 0x91, 0x96, 0x3c, 0x37, + }, + }, + { + "this is a long \000 password", + "and this is a long \000 salt", + 16384, 8, 1, + []byte{ + 0xc3, 0xf1, 0x82, 0xee, 0x2d, 0xec, 0x84, 0x6e, 0x70, + 0xa6, 0x94, 0x2f, 0xb5, 0x29, 0x98, 0x5a, 0x3a, 0x09, + 0x76, 0x5e, 0xf0, 0x4c, 0x61, 0x29, 0x23, 0xb1, 0x7f, + 0x18, 0x55, 0x5a, 0x37, 0x07, 0x6d, 0xeb, 0x2b, 0x98, + 0x30, 0xd6, 0x9d, 0xe5, 0x49, 0x26, 0x51, 0xe4, 0x50, + 0x6a, 0xe5, 0x77, 0x6d, 0x96, 0xd4, 0x0f, 0x67, 0xaa, + 0xee, 0x37, 0xe1, 0x77, 0x7b, 0x8a, 0xd5, 0xc3, 0x11, + 0x14, 0x32, 0xbb, 0x3b, 0x6f, 0x7e, 0x12, 0x64, 0x40, + 0x18, 0x79, 0xe6, 0x41, 0xae, + }, + }, + { + "p", + "s", + 2, 1, 1, + []byte{ + 0x48, 0xb0, 0xd2, 0xa8, 0xa3, 0x27, 0x26, 0x11, 0x98, + 0x4c, 0x50, 0xeb, 0xd6, 0x30, 0xaf, 0x52, + }, + }, + + { + "", + "", + 16, 1, 1, + []byte{ + 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, + 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b, + 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, + 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, + 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f, + 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, + 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, + 0x06, + }, + }, + { + "password", + "NaCl", + 1024, 8, 16, + []byte{ + 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, + 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe, 0x7c, 0x6a, + 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, + 0x63, 0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, + 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 0x09, 0x27, 0x9d, + 0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, + 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, + 0x40, + }, + }, + { + "pleaseletmein", "SodiumChloride", + 16384, 8, 1, + []byte{ + 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, + 0x1c, 0x06, 0xcd, 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8, + 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, + 0xf6, 0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, + 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 0x97, 0x05, 0x24, + 0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, + 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58, + 0x87, + }, + }, + /* + // Disabled: needs 1 GiB RAM and takes too long for a simple test. + { + "pleaseletmein", "SodiumChloride", + 1048576, 8, 1, + []byte{ + 0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, + 0xdb, 0xbe, 0x09, 0xcf, 0x70, 0xf8, 0x81, 0xec, 0x56, + 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee, + 0x98, 0x20, 0xad, 0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, + 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c, 0x6d, 0x92, 0x7c, + 0x40, 0xf4, 0xc3, 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, + 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f, 0xa7, 0x7a, 0x41, + 0xa4, + }, + }, + */ +} + +var bad = []testVector{ + {"p", "s", 0, 1, 1, nil}, // N == 0 + {"p", "s", 1, 1, 1, nil}, // N == 1 + {"p", "s", 7, 8, 1, nil}, // N is not power of 2 + {"p", "s", 16, maxInt / 2, maxInt / 2, nil}, // p * r too large +} + +func TestKey(t *testing.T) { + for i, v := range good { + k, err := Key([]byte(v.password), []byte(v.salt), v.N, v.r, v.p, len(v.output)) + if err != nil { + t.Errorf("%d: got unexpected error: %s", i, err) + } + if !bytes.Equal(k, v.output) { + t.Errorf("%d: expected %x, got %x", i, v.output, k) + } + } + for i, v := range bad { + _, err := Key([]byte(v.password), []byte(v.salt), v.N, v.r, v.p, 32) + if err == nil { + t.Errorf("%d: expected error, got nil", i) + } + } +} + +func BenchmarkKey(b *testing.B) { + for i := 0; i < b.N; i++ { + Key([]byte("password"), []byte("salt"), 16384, 8, 1, 64) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile new file mode 100644 index 0000000..e99b839 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/Makefile @@ -0,0 +1,40 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# http://code.google.com/p/goprotobuf/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +install: + go install + +test: install generate-test-pbs + go test + + +generate-test-pbs: + make install && cd testdata && make diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go new file mode 100644 index 0000000..b2a0191 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/all_test.go @@ -0,0 +1,1979 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math" + "math/rand" + "reflect" + "runtime/debug" + "strings" + "testing" + "time" + + . "./testdata" + . "code.google.com/p/goprotobuf/proto" +) + +var globalO *Buffer + +func old() *Buffer { + if globalO == nil { + globalO = NewBuffer(nil) + } + globalO.Reset() + return globalO +} + +func equalbytes(b1, b2 []byte, t *testing.T) { + if len(b1) != len(b2) { + t.Errorf("wrong lengths: 2*%d != %d", len(b1), len(b2)) + return + } + for i := 0; i < len(b1); i++ { + if b1[i] != b2[i] { + t.Errorf("bad byte[%d]:%x %x: %s %s", i, b1[i], b2[i], b1, b2) + } + } +} + +func initGoTestField() *GoTestField { + f := new(GoTestField) + f.Label = String("label") + f.Type = String("type") + return f +} + +// These are all structurally equivalent but the tag numbers differ. +// (It's remarkable that required, optional, and repeated all have +// 8 letters.) +func initGoTest_RequiredGroup() *GoTest_RequiredGroup { + return &GoTest_RequiredGroup{ + RequiredField: String("required"), + } +} + +func initGoTest_OptionalGroup() *GoTest_OptionalGroup { + return &GoTest_OptionalGroup{ + RequiredField: String("optional"), + } +} + +func initGoTest_RepeatedGroup() *GoTest_RepeatedGroup { + return &GoTest_RepeatedGroup{ + RequiredField: String("repeated"), + } +} + +func initGoTest(setdefaults bool) *GoTest { + pb := new(GoTest) + if setdefaults { + pb.F_BoolDefaulted = Bool(Default_GoTest_F_BoolDefaulted) + pb.F_Int32Defaulted = Int32(Default_GoTest_F_Int32Defaulted) + pb.F_Int64Defaulted = Int64(Default_GoTest_F_Int64Defaulted) + pb.F_Fixed32Defaulted = Uint32(Default_GoTest_F_Fixed32Defaulted) + pb.F_Fixed64Defaulted = Uint64(Default_GoTest_F_Fixed64Defaulted) + pb.F_Uint32Defaulted = Uint32(Default_GoTest_F_Uint32Defaulted) + pb.F_Uint64Defaulted = Uint64(Default_GoTest_F_Uint64Defaulted) + pb.F_FloatDefaulted = Float32(Default_GoTest_F_FloatDefaulted) + pb.F_DoubleDefaulted = Float64(Default_GoTest_F_DoubleDefaulted) + pb.F_StringDefaulted = String(Default_GoTest_F_StringDefaulted) + pb.F_BytesDefaulted = Default_GoTest_F_BytesDefaulted + pb.F_Sint32Defaulted = Int32(Default_GoTest_F_Sint32Defaulted) + pb.F_Sint64Defaulted = Int64(Default_GoTest_F_Sint64Defaulted) + } + + pb.Kind = GoTest_TIME.Enum() + pb.RequiredField = initGoTestField() + pb.F_BoolRequired = Bool(true) + pb.F_Int32Required = Int32(3) + pb.F_Int64Required = Int64(6) + pb.F_Fixed32Required = Uint32(32) + pb.F_Fixed64Required = Uint64(64) + pb.F_Uint32Required = Uint32(3232) + pb.F_Uint64Required = Uint64(6464) + pb.F_FloatRequired = Float32(3232) + pb.F_DoubleRequired = Float64(6464) + pb.F_StringRequired = String("string") + pb.F_BytesRequired = []byte("bytes") + pb.F_Sint32Required = Int32(-32) + pb.F_Sint64Required = Int64(-64) + pb.Requiredgroup = initGoTest_RequiredGroup() + + return pb +} + +func fail(msg string, b *bytes.Buffer, s string, t *testing.T) { + data := b.Bytes() + ld := len(data) + ls := len(s) / 2 + + fmt.Printf("fail %s ld=%d ls=%d\n", msg, ld, ls) + + // find the interesting spot - n + n := ls + if ld < ls { + n = ld + } + j := 0 + for i := 0; i < n; i++ { + bs := hex(s[j])*16 + hex(s[j+1]) + j += 2 + if data[i] == bs { + continue + } + n = i + break + } + l := n - 10 + if l < 0 { + l = 0 + } + h := n + 10 + + // find the interesting spot - n + fmt.Printf("is[%d]:", l) + for i := l; i < h; i++ { + if i >= ld { + fmt.Printf(" --") + continue + } + fmt.Printf(" %.2x", data[i]) + } + fmt.Printf("\n") + + fmt.Printf("sb[%d]:", l) + for i := l; i < h; i++ { + if i >= ls { + fmt.Printf(" --") + continue + } + bs := hex(s[j])*16 + hex(s[j+1]) + j += 2 + fmt.Printf(" %.2x", bs) + } + fmt.Printf("\n") + + t.Fail() + + // t.Errorf("%s: \ngood: %s\nbad: %x", msg, s, b.Bytes()) + // Print the output in a partially-decoded format; can + // be helpful when updating the test. It produces the output + // that is pasted, with minor edits, into the argument to verify(). + // data := b.Bytes() + // nesting := 0 + // for b.Len() > 0 { + // start := len(data) - b.Len() + // var u uint64 + // u, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on varint:", err) + // return + // } + // wire := u & 0x7 + // tag := u >> 3 + // switch wire { + // case WireVarint: + // v, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on varint:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireFixed32: + // v, err := DecodeFixed32(b) + // if err != nil { + // fmt.Printf("decode error on fixed32:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireFixed64: + // v, err := DecodeFixed64(b) + // if err != nil { + // fmt.Printf("decode error on fixed64:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" // field %d, encoding %d, value %d\n", + // data[start:len(data)-b.Len()], tag, wire, v) + // case WireBytes: + // nb, err := DecodeVarint(b) + // if err != nil { + // fmt.Printf("decode error on bytes:", err) + // return + // } + // after_tag := len(data) - b.Len() + // str := make([]byte, nb) + // _, err = b.Read(str) + // if err != nil { + // fmt.Printf("decode error on bytes:", err) + // return + // } + // fmt.Printf("\t\t\"%x\" \"%x\" // field %d, encoding %d (FIELD)\n", + // data[start:after_tag], str, tag, wire) + // case WireStartGroup: + // nesting++ + // fmt.Printf("\t\t\"%x\"\t\t// start group field %d level %d\n", + // data[start:len(data)-b.Len()], tag, nesting) + // case WireEndGroup: + // fmt.Printf("\t\t\"%x\"\t\t// end group field %d level %d\n", + // data[start:len(data)-b.Len()], tag, nesting) + // nesting-- + // default: + // fmt.Printf("unrecognized wire type %d\n", wire) + // return + // } + // } +} + +func hex(c uint8) uint8 { + if '0' <= c && c <= '9' { + return c - '0' + } + if 'a' <= c && c <= 'f' { + return 10 + c - 'a' + } + if 'A' <= c && c <= 'F' { + return 10 + c - 'A' + } + return 0 +} + +func equal(b []byte, s string, t *testing.T) bool { + if 2*len(b) != len(s) { + // fail(fmt.Sprintf("wrong lengths: 2*%d != %d", len(b), len(s)), b, s, t) + fmt.Printf("wrong lengths: 2*%d != %d\n", len(b), len(s)) + return false + } + for i, j := 0, 0; i < len(b); i, j = i+1, j+2 { + x := hex(s[j])*16 + hex(s[j+1]) + if b[i] != x { + // fail(fmt.Sprintf("bad byte[%d]:%x %x", i, b[i], x), b, s, t) + fmt.Printf("bad byte[%d]:%x %x", i, b[i], x) + return false + } + } + return true +} + +func overify(t *testing.T, pb *GoTest, expected string) { + o := old() + err := o.Marshal(pb) + if err != nil { + fmt.Printf("overify marshal-1 err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("expected = %s", expected) + } + if !equal(o.Bytes(), expected, t) { + o.DebugPrint("overify neq 1", o.Bytes()) + t.Fatalf("expected = %s", expected) + } + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + err = o.Unmarshal(pbd) + if err != nil { + t.Fatalf("overify unmarshal err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("string = %s", expected) + } + o.Reset() + err = o.Marshal(pbd) + if err != nil { + t.Errorf("overify marshal-2 err = %v", err) + o.DebugPrint("", o.Bytes()) + t.Fatalf("string = %s", expected) + } + if !equal(o.Bytes(), expected, t) { + o.DebugPrint("overify neq 2", o.Bytes()) + t.Fatalf("string = %s", expected) + } +} + +// Simple tests for numeric encode/decode primitives (varint, etc.) +func TestNumericPrimitives(t *testing.T) { + for i := uint64(0); i < 1e6; i += 111 { + o := old() + if o.EncodeVarint(i) != nil { + t.Error("EncodeVarint") + break + } + x, e := o.DecodeVarint() + if e != nil { + t.Fatal("DecodeVarint") + } + if x != i { + t.Fatal("varint decode fail:", i, x) + } + + o = old() + if o.EncodeFixed32(i) != nil { + t.Fatal("encFixed32") + } + x, e = o.DecodeFixed32() + if e != nil { + t.Fatal("decFixed32") + } + if x != i { + t.Fatal("fixed32 decode fail:", i, x) + } + + o = old() + if o.EncodeFixed64(i*1234567) != nil { + t.Error("encFixed64") + break + } + x, e = o.DecodeFixed64() + if e != nil { + t.Error("decFixed64") + break + } + if x != i*1234567 { + t.Error("fixed64 decode fail:", i*1234567, x) + break + } + + o = old() + i32 := int32(i - 12345) + if o.EncodeZigzag32(uint64(i32)) != nil { + t.Fatal("EncodeZigzag32") + } + x, e = o.DecodeZigzag32() + if e != nil { + t.Fatal("DecodeZigzag32") + } + if x != uint64(uint32(i32)) { + t.Fatal("zigzag32 decode fail:", i32, x) + } + + o = old() + i64 := int64(i - 12345) + if o.EncodeZigzag64(uint64(i64)) != nil { + t.Fatal("EncodeZigzag64") + } + x, e = o.DecodeZigzag64() + if e != nil { + t.Fatal("DecodeZigzag64") + } + if x != uint64(i64) { + t.Fatal("zigzag64 decode fail:", i64, x) + } + } +} + +// fakeMarshaler is a simple struct implementing Marshaler and Message interfaces. +type fakeMarshaler struct { + b []byte + err error +} + +func (f fakeMarshaler) Marshal() ([]byte, error) { + return f.b, f.err +} + +func (f fakeMarshaler) String() string { + return fmt.Sprintf("Bytes: %v Error: %v", f.b, f.err) +} + +func (f fakeMarshaler) ProtoMessage() {} + +func (f fakeMarshaler) Reset() {} + +// Simple tests for proto messages that implement the Marshaler interface. +func TestMarshalerEncoding(t *testing.T) { + tests := []struct { + name string + m Message + want []byte + wantErr error + }{ + { + name: "Marshaler that fails", + m: fakeMarshaler{ + err: errors.New("some marshal err"), + b: []byte{5, 6, 7}, + }, + // Since there's an error, nothing should be written to buffer. + want: nil, + wantErr: errors.New("some marshal err"), + }, + { + name: "Marshaler that succeeds", + m: fakeMarshaler{ + b: []byte{0, 1, 2, 3, 4, 127, 255}, + }, + want: []byte{0, 1, 2, 3, 4, 127, 255}, + wantErr: nil, + }, + } + for _, test := range tests { + b := NewBuffer(nil) + err := b.Marshal(test.m) + if !reflect.DeepEqual(test.wantErr, err) { + t.Errorf("%s: got err %v wanted %v", test.name, err, test.wantErr) + } + if !reflect.DeepEqual(test.want, b.Bytes()) { + t.Errorf("%s: got bytes %v wanted %v", test.name, b.Bytes(), test.want) + } + } +} + +// Simple tests for bytes +func TestBytesPrimitives(t *testing.T) { + o := old() + bytes := []byte{'n', 'o', 'w', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 't', 'i', 'm', 'e'} + if o.EncodeRawBytes(bytes) != nil { + t.Error("EncodeRawBytes") + } + decb, e := o.DecodeRawBytes(false) + if e != nil { + t.Error("DecodeRawBytes") + } + equalbytes(bytes, decb, t) +} + +// Simple tests for strings +func TestStringPrimitives(t *testing.T) { + o := old() + s := "now is the time" + if o.EncodeStringBytes(s) != nil { + t.Error("enc_string") + } + decs, e := o.DecodeStringBytes() + if e != nil { + t.Error("dec_string") + } + if s != decs { + t.Error("string encode/decode fail:", s, decs) + } +} + +// Do we catch the "required bit not set" case? +func TestRequiredBit(t *testing.T) { + o := old() + pb := new(GoTest) + err := o.Marshal(pb) + if err == nil { + t.Error("did not catch missing required fields") + } else if strings.Index(err.Error(), "Kind") < 0 { + t.Error("wrong error type:", err) + } +} + +// Check that all fields are nil. +// Clearly silly, and a residue from a more interesting test with an earlier, +// different initialization property, but it once caught a compiler bug so +// it lives. +func checkInitialized(pb *GoTest, t *testing.T) { + if pb.F_BoolDefaulted != nil { + t.Error("New or Reset did not set boolean:", *pb.F_BoolDefaulted) + } + if pb.F_Int32Defaulted != nil { + t.Error("New or Reset did not set int32:", *pb.F_Int32Defaulted) + } + if pb.F_Int64Defaulted != nil { + t.Error("New or Reset did not set int64:", *pb.F_Int64Defaulted) + } + if pb.F_Fixed32Defaulted != nil { + t.Error("New or Reset did not set fixed32:", *pb.F_Fixed32Defaulted) + } + if pb.F_Fixed64Defaulted != nil { + t.Error("New or Reset did not set fixed64:", *pb.F_Fixed64Defaulted) + } + if pb.F_Uint32Defaulted != nil { + t.Error("New or Reset did not set uint32:", *pb.F_Uint32Defaulted) + } + if pb.F_Uint64Defaulted != nil { + t.Error("New or Reset did not set uint64:", *pb.F_Uint64Defaulted) + } + if pb.F_FloatDefaulted != nil { + t.Error("New or Reset did not set float:", *pb.F_FloatDefaulted) + } + if pb.F_DoubleDefaulted != nil { + t.Error("New or Reset did not set double:", *pb.F_DoubleDefaulted) + } + if pb.F_StringDefaulted != nil { + t.Error("New or Reset did not set string:", *pb.F_StringDefaulted) + } + if pb.F_BytesDefaulted != nil { + t.Error("New or Reset did not set bytes:", string(pb.F_BytesDefaulted)) + } + if pb.F_Sint32Defaulted != nil { + t.Error("New or Reset did not set int32:", *pb.F_Sint32Defaulted) + } + if pb.F_Sint64Defaulted != nil { + t.Error("New or Reset did not set int64:", *pb.F_Sint64Defaulted) + } +} + +// Does Reset() reset? +func TestReset(t *testing.T) { + pb := initGoTest(true) + // muck with some values + pb.F_BoolDefaulted = Bool(false) + pb.F_Int32Defaulted = Int32(237) + pb.F_Int64Defaulted = Int64(12346) + pb.F_Fixed32Defaulted = Uint32(32000) + pb.F_Fixed64Defaulted = Uint64(666) + pb.F_Uint32Defaulted = Uint32(323232) + pb.F_Uint64Defaulted = nil + pb.F_FloatDefaulted = nil + pb.F_DoubleDefaulted = Float64(0) + pb.F_StringDefaulted = String("gotcha") + pb.F_BytesDefaulted = []byte("asdfasdf") + pb.F_Sint32Defaulted = Int32(123) + pb.F_Sint64Defaulted = Int64(789) + pb.Reset() + checkInitialized(pb, t) +} + +// All required fields set, no defaults provided. +func TestEncodeDecode1(t *testing.T) { + pb := initGoTest(false) + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 0x20 + "714000000000000000"+ // field 14, encoding 1, value 0x40 + "78a019"+ // field 15, encoding 0, value 0xca0 = 3232 + "8001c032"+ // field 16, encoding 0, value 0x1940 = 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2, string "string" + "b304"+ // field 70, encoding 3, start group + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // field 70, encoding 4, end group + "aa0605"+"6279746573"+ // field 101, encoding 2, string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f") // field 103, encoding 0, 0x7f zigzag64 +} + +// All required fields set, defaults provided. +func TestEncodeDecode2(t *testing.T) { + pb := initGoTest(true) + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All default fields set to their default value by hand +func TestEncodeDecode3(t *testing.T) { + pb := initGoTest(false) + pb.F_BoolDefaulted = Bool(true) + pb.F_Int32Defaulted = Int32(32) + pb.F_Int64Defaulted = Int64(64) + pb.F_Fixed32Defaulted = Uint32(320) + pb.F_Fixed64Defaulted = Uint64(640) + pb.F_Uint32Defaulted = Uint32(3200) + pb.F_Uint64Defaulted = Uint64(6400) + pb.F_FloatDefaulted = Float32(314159) + pb.F_DoubleDefaulted = Float64(271828) + pb.F_StringDefaulted = String("hello, \"world!\"\n") + pb.F_BytesDefaulted = []byte("Bignose") + pb.F_Sint32Defaulted = Int32(-32) + pb.F_Sint64Defaulted = Int64(-64) + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, defaults provided, all non-defaulted optional fields have values. +func TestEncodeDecode4(t *testing.T) { + pb := initGoTest(true) + pb.Table = String("hello") + pb.Param = Int32(7) + pb.OptionalField = initGoTestField() + pb.F_BoolOptional = Bool(true) + pb.F_Int32Optional = Int32(32) + pb.F_Int64Optional = Int64(64) + pb.F_Fixed32Optional = Uint32(3232) + pb.F_Fixed64Optional = Uint64(6464) + pb.F_Uint32Optional = Uint32(323232) + pb.F_Uint64Optional = Uint64(646464) + pb.F_FloatOptional = Float32(32.) + pb.F_DoubleOptional = Float64(64.) + pb.F_StringOptional = String("hello") + pb.F_BytesOptional = []byte("Bignose") + pb.F_Sint32Optional = Int32(-32) + pb.F_Sint64Optional = Int64(-64) + pb.Optionalgroup = initGoTest_OptionalGroup() + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "1205"+"68656c6c6f"+ // field 2, encoding 2, string "hello" + "1807"+ // field 3, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "320d"+"0a056c6162656c120474797065"+ // field 6, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "f00101"+ // field 30, encoding 0, value 1 + "f80120"+ // field 31, encoding 0, value 32 + "800240"+ // field 32, encoding 0, value 64 + "8d02a00c0000"+ // field 33, encoding 5, value 3232 + "91024019000000000000"+ // field 34, encoding 1, value 6464 + "9802a0dd13"+ // field 35, encoding 0, value 323232 + "a002c0ba27"+ // field 36, encoding 0, value 646464 + "ad0200000042"+ // field 37, encoding 5, value 32.0 + "b1020000000000005040"+ // field 38, encoding 1, value 64.0 + "ba0205"+"68656c6c6f"+ // field 39, encoding 2, string "hello" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "d305"+ // start group field 90 level 1 + "da0508"+"6f7074696f6e616c"+ // field 91, encoding 2, string "optional" + "d405"+ // end group field 90 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "ea1207"+"4269676e6f7365"+ // field 301, encoding 2, string "Bignose" + "f0123f"+ // field 302, encoding 0, value 63 + "f8127f"+ // field 303, encoding 0, value 127 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, defaults provided, all repeated fields given two values. +func TestEncodeDecode5(t *testing.T) { + pb := initGoTest(true) + pb.RepeatedField = []*GoTestField{initGoTestField(), initGoTestField()} + pb.F_BoolRepeated = []bool{false, true} + pb.F_Int32Repeated = []int32{32, 33} + pb.F_Int64Repeated = []int64{64, 65} + pb.F_Fixed32Repeated = []uint32{3232, 3333} + pb.F_Fixed64Repeated = []uint64{6464, 6565} + pb.F_Uint32Repeated = []uint32{323232, 333333} + pb.F_Uint64Repeated = []uint64{646464, 656565} + pb.F_FloatRepeated = []float32{32., 33.} + pb.F_DoubleRepeated = []float64{64., 65.} + pb.F_StringRepeated = []string{"hello", "sailor"} + pb.F_BytesRepeated = [][]byte{[]byte("big"), []byte("nose")} + pb.F_Sint32Repeated = []int32{32, -32} + pb.F_Sint64Repeated = []int64{64, -64} + pb.Repeatedgroup = []*GoTest_RepeatedGroup{initGoTest_RepeatedGroup(), initGoTest_RepeatedGroup()} + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "2a0d"+"0a056c6162656c120474797065"+ // field 5, encoding 2 (GoTestField) + "2a0d"+"0a056c6162656c120474797065"+ // field 5, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "a00100"+ // field 20, encoding 0, value 0 + "a00101"+ // field 20, encoding 0, value 1 + "a80120"+ // field 21, encoding 0, value 32 + "a80121"+ // field 21, encoding 0, value 33 + "b00140"+ // field 22, encoding 0, value 64 + "b00141"+ // field 22, encoding 0, value 65 + "bd01a00c0000"+ // field 23, encoding 5, value 3232 + "bd01050d0000"+ // field 23, encoding 5, value 3333 + "c1014019000000000000"+ // field 24, encoding 1, value 6464 + "c101a519000000000000"+ // field 24, encoding 1, value 6565 + "c801a0dd13"+ // field 25, encoding 0, value 323232 + "c80195ac14"+ // field 25, encoding 0, value 333333 + "d001c0ba27"+ // field 26, encoding 0, value 646464 + "d001b58928"+ // field 26, encoding 0, value 656565 + "dd0100000042"+ // field 27, encoding 5, value 32.0 + "dd0100000442"+ // field 27, encoding 5, value 33.0 + "e1010000000000005040"+ // field 28, encoding 1, value 64.0 + "e1010000000000405040"+ // field 28, encoding 1, value 65.0 + "ea0105"+"68656c6c6f"+ // field 29, encoding 2, string "hello" + "ea0106"+"7361696c6f72"+ // field 29, encoding 2, string "sailor" + "c00201"+ // field 40, encoding 0, value 1 + "c80220"+ // field 41, encoding 0, value 32 + "d00240"+ // field 42, encoding 0, value 64 + "dd0240010000"+ // field 43, encoding 5, value 320 + "e1028002000000000000"+ // field 44, encoding 1, value 640 + "e8028019"+ // field 45, encoding 0, value 3200 + "f0028032"+ // field 46, encoding 0, value 6400 + "fd02e0659948"+ // field 47, encoding 5, value 314159.0 + "81030000000050971041"+ // field 48, encoding 1, value 271828.0 + "8a0310"+"68656c6c6f2c2022776f726c6421220a"+ // field 49, encoding 2 string "hello, \"world!\"\n" + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "8305"+ // start group field 80 level 1 + "8a0508"+"7265706561746564"+ // field 81, encoding 2, string "repeated" + "8405"+ // end group field 80 level 1 + "8305"+ // start group field 80 level 1 + "8a0508"+"7265706561746564"+ // field 81, encoding 2, string "repeated" + "8405"+ // end group field 80 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "ca0c03"+"626967"+ // field 201, encoding 2, string "big" + "ca0c04"+"6e6f7365"+ // field 201, encoding 2, string "nose" + "d00c40"+ // field 202, encoding 0, value 32 + "d00c3f"+ // field 202, encoding 0, value -32 + "d80c8001"+ // field 203, encoding 0, value 64 + "d80c7f"+ // field 203, encoding 0, value -64 + "8a1907"+"4269676e6f7365"+ // field 401, encoding 2, string "Bignose" + "90193f"+ // field 402, encoding 0, value 63 + "98197f") // field 403, encoding 0, value 127 + +} + +// All required fields set, all packed repeated fields given two values. +func TestEncodeDecode6(t *testing.T) { + pb := initGoTest(false) + pb.F_BoolRepeatedPacked = []bool{false, true} + pb.F_Int32RepeatedPacked = []int32{32, 33} + pb.F_Int64RepeatedPacked = []int64{64, 65} + pb.F_Fixed32RepeatedPacked = []uint32{3232, 3333} + pb.F_Fixed64RepeatedPacked = []uint64{6464, 6565} + pb.F_Uint32RepeatedPacked = []uint32{323232, 333333} + pb.F_Uint64RepeatedPacked = []uint64{646464, 656565} + pb.F_FloatRepeatedPacked = []float32{32., 33.} + pb.F_DoubleRepeatedPacked = []float64{64., 65.} + pb.F_Sint32RepeatedPacked = []int32{32, -32} + pb.F_Sint64RepeatedPacked = []int64{64, -64} + + overify(t, pb, + "0807"+ // field 1, encoding 0, value 7 + "220d"+"0a056c6162656c120474797065"+ // field 4, encoding 2 (GoTestField) + "5001"+ // field 10, encoding 0, value 1 + "5803"+ // field 11, encoding 0, value 3 + "6006"+ // field 12, encoding 0, value 6 + "6d20000000"+ // field 13, encoding 5, value 32 + "714000000000000000"+ // field 14, encoding 1, value 64 + "78a019"+ // field 15, encoding 0, value 3232 + "8001c032"+ // field 16, encoding 0, value 6464 + "8d0100004a45"+ // field 17, encoding 5, value 3232.0 + "9101000000000040b940"+ // field 18, encoding 1, value 6464.0 + "9a0106"+"737472696e67"+ // field 19, encoding 2 string "string" + "9203020001"+ // field 50, encoding 2, 2 bytes, value 0, value 1 + "9a03022021"+ // field 51, encoding 2, 2 bytes, value 32, value 33 + "a203024041"+ // field 52, encoding 2, 2 bytes, value 64, value 65 + "aa0308"+ // field 53, encoding 2, 8 bytes + "a00c0000050d0000"+ // value 3232, value 3333 + "b20310"+ // field 54, encoding 2, 16 bytes + "4019000000000000a519000000000000"+ // value 6464, value 6565 + "ba0306"+ // field 55, encoding 2, 6 bytes + "a0dd1395ac14"+ // value 323232, value 333333 + "c20306"+ // field 56, encoding 2, 6 bytes + "c0ba27b58928"+ // value 646464, value 656565 + "ca0308"+ // field 57, encoding 2, 8 bytes + "0000004200000442"+ // value 32.0, value 33.0 + "d20310"+ // field 58, encoding 2, 16 bytes + "00000000000050400000000000405040"+ // value 64.0, value 65.0 + "b304"+ // start group field 70 level 1 + "ba0408"+"7265717569726564"+ // field 71, encoding 2, string "required" + "b404"+ // end group field 70 level 1 + "aa0605"+"6279746573"+ // field 101, encoding 2 string "bytes" + "b0063f"+ // field 102, encoding 0, 0x3f zigzag32 + "b8067f"+ // field 103, encoding 0, 0x7f zigzag64 + "b21f02"+ // field 502, encoding 2, 2 bytes + "403f"+ // value 32, value -32 + "ba1f03"+ // field 503, encoding 2, 3 bytes + "80017f") // value 64, value -64 +} + +// Test that we can encode empty bytes fields. +func TestEncodeDecodeBytes1(t *testing.T) { + pb := initGoTest(false) + + // Create our bytes + pb.F_BytesRequired = []byte{} + pb.F_BytesRepeated = [][]byte{{}} + pb.F_BytesOptional = []byte{} + + d, err := Marshal(pb) + if err != nil { + t.Error(err) + } + + pbd := new(GoTest) + if err := Unmarshal(d, pbd); err != nil { + t.Error(err) + } + + if pbd.F_BytesRequired == nil || len(pbd.F_BytesRequired) != 0 { + t.Error("required empty bytes field is incorrect") + } + if pbd.F_BytesRepeated == nil || len(pbd.F_BytesRepeated) == 1 && pbd.F_BytesRepeated[0] == nil { + t.Error("repeated empty bytes field is incorrect") + } + if pbd.F_BytesOptional == nil || len(pbd.F_BytesOptional) != 0 { + t.Error("optional empty bytes field is incorrect") + } +} + +// Test that we encode nil-valued fields of a repeated bytes field correctly. +// Since entries in a repeated field cannot be nil, nil must mean empty value. +func TestEncodeDecodeBytes2(t *testing.T) { + pb := initGoTest(false) + + // Create our bytes + pb.F_BytesRepeated = [][]byte{nil} + + d, err := Marshal(pb) + if err != nil { + t.Error(err) + } + + pbd := new(GoTest) + if err := Unmarshal(d, pbd); err != nil { + t.Error(err) + } + + if len(pbd.F_BytesRepeated) != 1 || pbd.F_BytesRepeated[0] == nil { + t.Error("Unexpected value for repeated bytes field") + } +} + +// All required fields set, defaults provided, all repeated fields given two values. +func TestSkippingUnrecognizedFields(t *testing.T) { + o := old() + pb := initGoTestField() + + // Marshal it normally. + o.Marshal(pb) + + // Now new a GoSkipTest record. + skip := &GoSkipTest{ + SkipInt32: Int32(32), + SkipFixed32: Uint32(3232), + SkipFixed64: Uint64(6464), + SkipString: String("skipper"), + Skipgroup: &GoSkipTest_SkipGroup{ + GroupInt32: Int32(75), + GroupString: String("wxyz"), + }, + } + + // Marshal it into same buffer. + o.Marshal(skip) + + pbd := new(GoTestField) + o.Unmarshal(pbd) + + // The __unrecognized field should be a marshaling of GoSkipTest + skipd := new(GoSkipTest) + + o.SetBuf(pbd.XXX_unrecognized) + o.Unmarshal(skipd) + + if *skipd.SkipInt32 != *skip.SkipInt32 { + t.Error("skip int32", skipd.SkipInt32) + } + if *skipd.SkipFixed32 != *skip.SkipFixed32 { + t.Error("skip fixed32", skipd.SkipFixed32) + } + if *skipd.SkipFixed64 != *skip.SkipFixed64 { + t.Error("skip fixed64", skipd.SkipFixed64) + } + if *skipd.SkipString != *skip.SkipString { + t.Error("skip string", *skipd.SkipString) + } + if *skipd.Skipgroup.GroupInt32 != *skip.Skipgroup.GroupInt32 { + t.Error("skip group int32", skipd.Skipgroup.GroupInt32) + } + if *skipd.Skipgroup.GroupString != *skip.Skipgroup.GroupString { + t.Error("skip group string", *skipd.Skipgroup.GroupString) + } +} + +// Check that unrecognized fields of a submessage are preserved. +func TestSubmessageUnrecognizedFields(t *testing.T) { + nm := &NewMessage{ + Nested: &NewMessage_Nested{ + Name: String("Nigel"), + FoodGroup: String("carbs"), + }, + } + b, err := Marshal(nm) + if err != nil { + t.Fatalf("Marshal of NewMessage: %v", err) + } + + // Unmarshal into an OldMessage. + om := new(OldMessage) + if err := Unmarshal(b, om); err != nil { + t.Fatalf("Unmarshal to OldMessage: %v", err) + } + exp := &OldMessage{ + Nested: &OldMessage_Nested{ + Name: String("Nigel"), + // normal protocol buffer users should not do this + XXX_unrecognized: []byte("\x12\x05carbs"), + }, + } + if !Equal(om, exp) { + t.Errorf("om = %v, want %v", om, exp) + } + + // Clone the OldMessage. + om = Clone(om).(*OldMessage) + if !Equal(om, exp) { + t.Errorf("Clone(om) = %v, want %v", om, exp) + } + + // Marshal the OldMessage, then unmarshal it into an empty NewMessage. + if b, err = Marshal(om); err != nil { + t.Fatalf("Marshal of OldMessage: %v", err) + } + t.Logf("Marshal(%v) -> %q", om, b) + nm2 := new(NewMessage) + if err := Unmarshal(b, nm2); err != nil { + t.Fatalf("Unmarshal to NewMessage: %v", err) + } + if !Equal(nm, nm2) { + t.Errorf("NewMessage round-trip: %v => %v", nm, nm2) + } +} + +// Check that an int32 field can be upgraded to an int64 field. +func TestNegativeInt32(t *testing.T) { + om := &OldMessage{ + Num: Int32(-1), + } + b, err := Marshal(om) + if err != nil { + t.Fatalf("Marshal of OldMessage: %v", err) + } + + // Check the size. It should be 11 bytes; + // 1 for the field/wire type, and 10 for the negative number. + if len(b) != 11 { + t.Errorf("%v marshaled as %q, wanted 11 bytes", om, b) + } + + // Unmarshal into a NewMessage. + nm := new(NewMessage) + if err := Unmarshal(b, nm); err != nil { + t.Fatalf("Unmarshal to NewMessage: %v", err) + } + want := &NewMessage{ + Num: Int64(-1), + } + if !Equal(nm, want) { + t.Errorf("nm = %v, want %v", nm, want) + } +} + +// Check that we can grow an array (repeated field) to have many elements. +// This test doesn't depend only on our encoding; for variety, it makes sure +// we create, encode, and decode the correct contents explicitly. It's therefore +// a bit messier. +// This test also uses (and hence tests) the Marshal/Unmarshal functions +// instead of the methods. +func TestBigRepeated(t *testing.T) { + pb := initGoTest(true) + + // Create the arrays + const N = 50 // Internally the library starts much smaller. + pb.Repeatedgroup = make([]*GoTest_RepeatedGroup, N) + pb.F_Sint64Repeated = make([]int64, N) + pb.F_Sint32Repeated = make([]int32, N) + pb.F_BytesRepeated = make([][]byte, N) + pb.F_StringRepeated = make([]string, N) + pb.F_DoubleRepeated = make([]float64, N) + pb.F_FloatRepeated = make([]float32, N) + pb.F_Uint64Repeated = make([]uint64, N) + pb.F_Uint32Repeated = make([]uint32, N) + pb.F_Fixed64Repeated = make([]uint64, N) + pb.F_Fixed32Repeated = make([]uint32, N) + pb.F_Int64Repeated = make([]int64, N) + pb.F_Int32Repeated = make([]int32, N) + pb.F_BoolRepeated = make([]bool, N) + pb.RepeatedField = make([]*GoTestField, N) + + // Fill in the arrays with checkable values. + igtf := initGoTestField() + igtrg := initGoTest_RepeatedGroup() + for i := 0; i < N; i++ { + pb.Repeatedgroup[i] = igtrg + pb.F_Sint64Repeated[i] = int64(i) + pb.F_Sint32Repeated[i] = int32(i) + s := fmt.Sprint(i) + pb.F_BytesRepeated[i] = []byte(s) + pb.F_StringRepeated[i] = s + pb.F_DoubleRepeated[i] = float64(i) + pb.F_FloatRepeated[i] = float32(i) + pb.F_Uint64Repeated[i] = uint64(i) + pb.F_Uint32Repeated[i] = uint32(i) + pb.F_Fixed64Repeated[i] = uint64(i) + pb.F_Fixed32Repeated[i] = uint32(i) + pb.F_Int64Repeated[i] = int64(i) + pb.F_Int32Repeated[i] = int32(i) + pb.F_BoolRepeated[i] = i%2 == 0 + pb.RepeatedField[i] = igtf + } + + // Marshal. + buf, _ := Marshal(pb) + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + Unmarshal(buf, pbd) + + // Check the checkable values + for i := uint64(0); i < N; i++ { + if pbd.Repeatedgroup[i] == nil { // TODO: more checking? + t.Error("pbd.Repeatedgroup bad") + } + var x uint64 + x = uint64(pbd.F_Sint64Repeated[i]) + if x != i { + t.Error("pbd.F_Sint64Repeated bad", x, i) + } + x = uint64(pbd.F_Sint32Repeated[i]) + if x != i { + t.Error("pbd.F_Sint32Repeated bad", x, i) + } + s := fmt.Sprint(i) + equalbytes(pbd.F_BytesRepeated[i], []byte(s), t) + if pbd.F_StringRepeated[i] != s { + t.Error("pbd.F_Sint32Repeated bad", pbd.F_StringRepeated[i], i) + } + x = uint64(pbd.F_DoubleRepeated[i]) + if x != i { + t.Error("pbd.F_DoubleRepeated bad", x, i) + } + x = uint64(pbd.F_FloatRepeated[i]) + if x != i { + t.Error("pbd.F_FloatRepeated bad", x, i) + } + x = pbd.F_Uint64Repeated[i] + if x != i { + t.Error("pbd.F_Uint64Repeated bad", x, i) + } + x = uint64(pbd.F_Uint32Repeated[i]) + if x != i { + t.Error("pbd.F_Uint32Repeated bad", x, i) + } + x = pbd.F_Fixed64Repeated[i] + if x != i { + t.Error("pbd.F_Fixed64Repeated bad", x, i) + } + x = uint64(pbd.F_Fixed32Repeated[i]) + if x != i { + t.Error("pbd.F_Fixed32Repeated bad", x, i) + } + x = uint64(pbd.F_Int64Repeated[i]) + if x != i { + t.Error("pbd.F_Int64Repeated bad", x, i) + } + x = uint64(pbd.F_Int32Repeated[i]) + if x != i { + t.Error("pbd.F_Int32Repeated bad", x, i) + } + if pbd.F_BoolRepeated[i] != (i%2 == 0) { + t.Error("pbd.F_BoolRepeated bad", x, i) + } + if pbd.RepeatedField[i] == nil { // TODO: more checking? + t.Error("pbd.RepeatedField bad") + } + } +} + +// Verify we give a useful message when decoding to the wrong structure type. +func TestTypeMismatch(t *testing.T) { + pb1 := initGoTest(true) + + // Marshal + o := old() + o.Marshal(pb1) + + // Now Unmarshal it to the wrong type. + pb2 := initGoTestField() + err := o.Unmarshal(pb2) + if err == nil { + t.Error("expected error, got no error") + } else if !strings.Contains(err.Error(), "bad wiretype") { + t.Error("expected bad wiretype error, got", err) + } +} + +func encodeDecode(t *testing.T, in, out Message, msg string) { + buf, err := Marshal(in) + if err != nil { + t.Fatalf("failed marshaling %v: %v", msg, err) + } + if err := Unmarshal(buf, out); err != nil { + t.Fatalf("failed unmarshaling %v: %v", msg, err) + } +} + +func TestPackedNonPackedDecoderSwitching(t *testing.T) { + np, p := new(NonPackedTest), new(PackedTest) + + // non-packed -> packed + np.A = []int32{0, 1, 1, 2, 3, 5} + encodeDecode(t, np, p, "non-packed -> packed") + if !reflect.DeepEqual(np.A, p.B) { + t.Errorf("failed non-packed -> packed; np.A=%+v, p.B=%+v", np.A, p.B) + } + + // packed -> non-packed + np.Reset() + p.B = []int32{3, 1, 4, 1, 5, 9} + encodeDecode(t, p, np, "packed -> non-packed") + if !reflect.DeepEqual(p.B, np.A) { + t.Errorf("failed packed -> non-packed; p.B=%+v, np.A=%+v", p.B, np.A) + } +} + +func TestProto1RepeatedGroup(t *testing.T) { + pb := &MessageList{ + Message: []*MessageList_Message{ + { + Name: String("blah"), + Count: Int32(7), + }, + // NOTE: pb.Message[1] is a nil + nil, + }, + } + + o := old() + if err := o.Marshal(pb); err != ErrRepeatedHasNil { + t.Fatalf("unexpected or no error when marshaling: %v", err) + } +} + +// Test that enums work. Checks for a bug introduced by making enums +// named types instead of int32: newInt32FromUint64 would crash with +// a type mismatch in reflect.PointTo. +func TestEnum(t *testing.T) { + pb := new(GoEnum) + pb.Foo = FOO_FOO1.Enum() + o := old() + if err := o.Marshal(pb); err != nil { + t.Fatal("error encoding enum:", err) + } + pb1 := new(GoEnum) + if err := o.Unmarshal(pb1); err != nil { + t.Fatal("error decoding enum:", err) + } + if *pb1.Foo != FOO_FOO1 { + t.Error("expected 7 but got ", *pb1.Foo) + } +} + +// Enum types have String methods. Check that enum fields can be printed. +// We don't care what the value actually is, just as long as it doesn't crash. +func TestPrintingNilEnumFields(t *testing.T) { + pb := new(GoEnum) + fmt.Sprintf("%+v", pb) +} + +// Verify that absent required fields cause Marshal/Unmarshal to return errors. +func TestRequiredFieldEnforcement(t *testing.T) { + pb := new(GoTestField) + _, err := Marshal(pb) + if err == nil { + t.Error("marshal: expected error, got nil") + } else if strings.Index(err.Error(), "Label") < 0 { + t.Errorf("marshal: bad error type: %v", err) + } + + // A slightly sneaky, yet valid, proto. It encodes the same required field twice, + // so simply counting the required fields is insufficient. + // field 1, encoding 2, value "hi" + buf := []byte("\x0A\x02hi\x0A\x02hi") + err = Unmarshal(buf, pb) + if err == nil { + t.Error("unmarshal: expected error, got nil") + } else if strings.Index(err.Error(), "{Unknown}") < 0 { + t.Errorf("unmarshal: bad error type: %v", err) + } +} + +func TestTypedNilMarshal(t *testing.T) { + // A typed nil should return ErrNil and not crash. + _, err := Marshal((*GoEnum)(nil)) + if err != ErrNil { + t.Errorf("Marshal: got err %v, want ErrNil", err) + } +} + +// A type that implements the Marshaler interface, but is not nillable. +type nonNillableInt uint64 + +func (nni nonNillableInt) Marshal() ([]byte, error) { + return EncodeVarint(uint64(nni)), nil +} + +type NNIMessage struct { + nni nonNillableInt +} + +func (*NNIMessage) Reset() {} +func (*NNIMessage) String() string { return "" } +func (*NNIMessage) ProtoMessage() {} + +// A type that implements the Marshaler interface and is nillable. +type nillableMessage struct { + x uint64 +} + +func (nm *nillableMessage) Marshal() ([]byte, error) { + return EncodeVarint(nm.x), nil +} + +type NMMessage struct { + nm *nillableMessage +} + +func (*NMMessage) Reset() {} +func (*NMMessage) String() string { return "" } +func (*NMMessage) ProtoMessage() {} + +// Verify a type that uses the Marshaler interface, but has a nil pointer. +func TestNilMarshaler(t *testing.T) { + // Try a struct with a Marshaler field that is nil. + // It should be directly marshable. + nmm := new(NMMessage) + if _, err := Marshal(nmm); err != nil { + t.Error("unexpected error marshaling nmm: ", err) + } + + // Try a struct with a Marshaler field that is not nillable. + nnim := new(NNIMessage) + nnim.nni = 7 + var _ Marshaler = nnim.nni // verify it is truly a Marshaler + if _, err := Marshal(nnim); err != nil { + t.Error("unexpected error marshaling nnim: ", err) + } +} + +func TestAllSetDefaults(t *testing.T) { + // Exercise SetDefaults with all scalar field types. + m := &Defaults{ + // NaN != NaN, so override that here. + F_Nan: Float32(1.7), + } + expected := &Defaults{ + F_Bool: Bool(true), + F_Int32: Int32(32), + F_Int64: Int64(64), + F_Fixed32: Uint32(320), + F_Fixed64: Uint64(640), + F_Uint32: Uint32(3200), + F_Uint64: Uint64(6400), + F_Float: Float32(314159), + F_Double: Float64(271828), + F_String: String(`hello, "world!"` + "\n"), + F_Bytes: []byte("Bignose"), + F_Sint32: Int32(-32), + F_Sint64: Int64(-64), + F_Enum: Defaults_GREEN.Enum(), + F_Pinf: Float32(float32(math.Inf(1))), + F_Ninf: Float32(float32(math.Inf(-1))), + F_Nan: Float32(1.7), + StrZero: String(""), + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("SetDefaults failed\n got %v\nwant %v", m, expected) + } +} + +func TestSetDefaultsWithSetField(t *testing.T) { + // Check that a set value is not overridden. + m := &Defaults{ + F_Int32: Int32(12), + } + SetDefaults(m) + if v := m.GetF_Int32(); v != 12 { + t.Errorf("m.FInt32 = %v, want 12", v) + } +} + +func TestSetDefaultsWithSubMessage(t *testing.T) { + m := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("gopher"), + }, + } + expected := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("gopher"), + Port: Int32(4000), + }, + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + +func TestSetDefaultsWithRepeatedSubMessage(t *testing.T) { + m := &MyMessage{ + RepInner: []*InnerMessage{{}}, + } + expected := &MyMessage{ + RepInner: []*InnerMessage{{ + Port: Int32(4000), + }}, + } + SetDefaults(m) + if !Equal(m, expected) { + t.Errorf("\n got %v\nwant %v", m, expected) + } +} + +func TestMaximumTagNumber(t *testing.T) { + m := &MaxTag{ + LastField: String("natural goat essence"), + } + buf, err := Marshal(m) + if err != nil { + t.Fatalf("proto.Marshal failed: %v", err) + } + m2 := new(MaxTag) + if err := Unmarshal(buf, m2); err != nil { + t.Fatalf("proto.Unmarshal failed: %v", err) + } + if got, want := m2.GetLastField(), *m.LastField; got != want { + t.Errorf("got %q, want %q", got, want) + } +} + +func TestJSON(t *testing.T) { + m := &MyMessage{ + Count: Int32(4), + Pet: []string{"bunny", "kitty"}, + Inner: &InnerMessage{ + Host: String("cauchy"), + }, + Bikeshed: MyMessage_GREEN.Enum(), + } + const expected = `{"count":4,"pet":["bunny","kitty"],"inner":{"host":"cauchy"},"bikeshed":1}` + + b, err := json.Marshal(m) + if err != nil { + t.Fatalf("json.Marshal failed: %v", err) + } + s := string(b) + if s != expected { + t.Errorf("got %s\nwant %s", s, expected) + } + + received := new(MyMessage) + if err := json.Unmarshal(b, received); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if !Equal(received, m) { + t.Fatalf("got %s, want %s", received, m) + } + + // Test unmarshalling of JSON with symbolic enum name. + const old = `{"count":4,"pet":["bunny","kitty"],"inner":{"host":"cauchy"},"bikeshed":"GREEN"}` + received.Reset() + if err := json.Unmarshal([]byte(old), received); err != nil { + t.Fatalf("json.Unmarshal failed: %v", err) + } + if !Equal(received, m) { + t.Fatalf("got %s, want %s", received, m) + } +} + +func TestBadWireType(t *testing.T) { + b := []byte{7<<3 | 6} // field 7, wire type 6 + pb := new(OtherMessage) + if err := Unmarshal(b, pb); err == nil { + t.Errorf("Unmarshal did not fail") + } else if !strings.Contains(err.Error(), "unknown wire type") { + t.Errorf("wrong error: %v", err) + } +} + +func TestBytesWithInvalidLength(t *testing.T) { + // If a byte sequence has an invalid (negative) length, Unmarshal should not panic. + b := []byte{2<<3 | WireBytes, 0xff, 0xff, 0xff, 0xff, 0xff, 0} + Unmarshal(b, new(MyMessage)) +} + +func TestLengthOverflow(t *testing.T) { + // Overflowing a length should not panic. + b := []byte{2<<3 | WireBytes, 1, 1, 3<<3 | WireBytes, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x01} + Unmarshal(b, new(MyMessage)) +} + +func TestVarintOverflow(t *testing.T) { + // Overflowing a 64-bit length should not be allowed. + b := []byte{1<<3 | WireVarint, 0x01, 3<<3 | WireBytes, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01} + if err := Unmarshal(b, new(MyMessage)); err == nil { + t.Fatalf("Overflowed uint64 length without error") + } +} + +func TestUnmarshalFuzz(t *testing.T) { + const N = 1000 + seed := time.Now().UnixNano() + t.Logf("RNG seed is %d", seed) + rng := rand.New(rand.NewSource(seed)) + buf := make([]byte, 20) + for i := 0; i < N; i++ { + for j := range buf { + buf[j] = byte(rng.Intn(256)) + } + fuzzUnmarshal(t, buf) + } +} + +func TestMergeMessages(t *testing.T) { + pb := &MessageList{Message: []*MessageList_Message{{Name: String("x"), Count: Int32(1)}}} + data, err := Marshal(pb) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + pb1 := new(MessageList) + if err := Unmarshal(data, pb1); err != nil { + t.Fatalf("first Unmarshal: %v", err) + } + if err := Unmarshal(data, pb1); err != nil { + t.Fatalf("second Unmarshal: %v", err) + } + if len(pb1.Message) != 1 { + t.Errorf("two Unmarshals produced %d Messages, want 1", len(pb1.Message)) + } + + pb2 := new(MessageList) + if err := UnmarshalMerge(data, pb2); err != nil { + t.Fatalf("first UnmarshalMerge: %v", err) + } + if err := UnmarshalMerge(data, pb2); err != nil { + t.Fatalf("second UnmarshalMerge: %v", err) + } + if len(pb2.Message) != 2 { + t.Errorf("two UnmarshalMerges produced %d Messages, want 2", len(pb2.Message)) + } +} + +func TestExtensionMarshalOrder(t *testing.T) { + m := &MyMessage{Count: Int(123)} + if err := SetExtension(m, E_Ext_More, &Ext{Data: String("alpha")}); err != nil { + t.Fatalf("SetExtension: %v", err) + } + if err := SetExtension(m, E_Ext_Text, String("aleph")); err != nil { + t.Fatalf("SetExtension: %v", err) + } + if err := SetExtension(m, E_Ext_Number, Int32(1)); err != nil { + t.Fatalf("SetExtension: %v", err) + } + + // Serialize m several times, and check we get the same bytes each time. + var orig []byte + for i := 0; i < 100; i++ { + b, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if i == 0 { + orig = b + continue + } + if !bytes.Equal(b, orig) { + t.Errorf("Bytes differ on attempt #%d", i) + } + } +} + +// Many extensions, because small maps might not iterate differently on each iteration. +var exts = []*ExtensionDesc{ + E_X201, + E_X202, + E_X203, + E_X204, + E_X205, + E_X206, + E_X207, + E_X208, + E_X209, + E_X210, + E_X211, + E_X212, + E_X213, + E_X214, + E_X215, + E_X216, + E_X217, + E_X218, + E_X219, + E_X220, + E_X221, + E_X222, + E_X223, + E_X224, + E_X225, + E_X226, + E_X227, + E_X228, + E_X229, + E_X230, + E_X231, + E_X232, + E_X233, + E_X234, + E_X235, + E_X236, + E_X237, + E_X238, + E_X239, + E_X240, + E_X241, + E_X242, + E_X243, + E_X244, + E_X245, + E_X246, + E_X247, + E_X248, + E_X249, + E_X250, +} + +func TestMessageSetMarshalOrder(t *testing.T) { + m := &MyMessageSet{} + for _, x := range exts { + if err := SetExtension(m, x, &Empty{}); err != nil { + t.Fatalf("SetExtension: %v", err) + } + } + + buf, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + // Serialize m several times, and check we get the same bytes each time. + for i := 0; i < 10; i++ { + b1, err := Marshal(m) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + if !bytes.Equal(b1, buf) { + t.Errorf("Bytes differ on re-Marshal #%d", i) + } + + m2 := &MyMessageSet{} + if err := Unmarshal(buf, m2); err != nil { + t.Errorf("Unmarshal: %v", err) + } + b2, err := Marshal(m2) + if err != nil { + t.Errorf("re-Marshal: %v", err) + } + if !bytes.Equal(b2, buf) { + t.Errorf("Bytes differ on round-trip #%d", i) + } + } +} + +func TestUnmarshalMergesMessages(t *testing.T) { + // If a nested message occurs twice in the input, + // the fields should be merged when decoding. + a := &OtherMessage{ + Key: Int64(123), + Inner: &InnerMessage{ + Host: String("polhode"), + Port: Int32(1234), + }, + } + aData, err := Marshal(a) + if err != nil { + t.Fatalf("Marshal(a): %v", err) + } + b := &OtherMessage{ + Weight: Float32(1.2), + Inner: &InnerMessage{ + Host: String("herpolhode"), + Connected: Bool(true), + }, + } + bData, err := Marshal(b) + if err != nil { + t.Fatalf("Marshal(b): %v", err) + } + want := &OtherMessage{ + Key: Int64(123), + Weight: Float32(1.2), + Inner: &InnerMessage{ + Host: String("herpolhode"), + Port: Int32(1234), + Connected: Bool(true), + }, + } + got := new(OtherMessage) + if err := Unmarshal(append(aData, bData...), got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if !Equal(got, want) { + t.Errorf("\n got %v\nwant %v", got, want) + } +} + +func TestEncodingSizes(t *testing.T) { + tests := []struct { + m Message + n int + }{ + {&Defaults{F_Int32: Int32(math.MaxInt32)}, 6}, + {&Defaults{F_Int32: Int32(math.MinInt32)}, 11}, + {&Defaults{F_Uint32: Uint32(uint32(math.MaxInt32) + 1)}, 6}, + {&Defaults{F_Uint32: Uint32(math.MaxUint32)}, 6}, + } + for _, test := range tests { + b, err := Marshal(test.m) + if err != nil { + t.Errorf("Marshal(%v): %v", test.m, err) + continue + } + if len(b) != test.n { + t.Errorf("Marshal(%v) yielded %d bytes, want %d bytes", test.m, len(b), test.n) + } + } +} + +func TestRequiredNotSetError(t *testing.T) { + pb := initGoTest(false) + pb.RequiredField.Label = nil + pb.F_Int32Required = nil + pb.F_Int64Required = nil + + expected := "0807" + // field 1, encoding 0, value 7 + "2206" + "120474797065" + // field 4, encoding 2 (GoTestField) + "5001" + // field 10, encoding 0, value 1 + "6d20000000" + // field 13, encoding 5, value 0x20 + "714000000000000000" + // field 14, encoding 1, value 0x40 + "78a019" + // field 15, encoding 0, value 0xca0 = 3232 + "8001c032" + // field 16, encoding 0, value 0x1940 = 6464 + "8d0100004a45" + // field 17, encoding 5, value 3232.0 + "9101000000000040b940" + // field 18, encoding 1, value 6464.0 + "9a0106" + "737472696e67" + // field 19, encoding 2, string "string" + "b304" + // field 70, encoding 3, start group + "ba0408" + "7265717569726564" + // field 71, encoding 2, string "required" + "b404" + // field 70, encoding 4, end group + "aa0605" + "6279746573" + // field 101, encoding 2, string "bytes" + "b0063f" + // field 102, encoding 0, 0x3f zigzag32 + "b8067f" // field 103, encoding 0, 0x7f zigzag64 + + o := old() + bytes, err := Marshal(pb) + if _, ok := err.(*RequiredNotSetError); !ok { + fmt.Printf("marshal-1 err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("expected = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.Label") < 0 { + t.Errorf("marshal-1 wrong err msg: %v", err) + } + if !equal(bytes, expected, t) { + o.DebugPrint("neq 1", bytes) + t.Fatalf("expected = %s", expected) + } + + // Now test Unmarshal by recreating the original buffer. + pbd := new(GoTest) + err = Unmarshal(bytes, pbd) + if _, ok := err.(*RequiredNotSetError); !ok { + t.Fatalf("unmarshal err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("string = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.{Unknown}") < 0 { + t.Errorf("unmarshal wrong err msg: %v", err) + } + bytes, err = Marshal(pbd) + if _, ok := err.(*RequiredNotSetError); !ok { + t.Errorf("marshal-2 err = %v, want *RequiredNotSetError", err) + o.DebugPrint("", bytes) + t.Fatalf("string = %s", expected) + } + if strings.Index(err.Error(), "RequiredField.Label") < 0 { + t.Errorf("marshal-2 wrong err msg: %v", err) + } + if !equal(bytes, expected, t) { + o.DebugPrint("neq 2", bytes) + t.Fatalf("string = %s", expected) + } +} + +func fuzzUnmarshal(t *testing.T, data []byte) { + defer func() { + if e := recover(); e != nil { + t.Errorf("These bytes caused a panic: %+v", data) + t.Logf("Stack:\n%s", debug.Stack()) + t.FailNow() + } + }() + + pb := new(MyMessage) + Unmarshal(data, pb) +} + +// Benchmarks + +func testMsg() *GoTest { + pb := initGoTest(true) + const N = 1000 // Internally the library starts much smaller. + pb.F_Int32Repeated = make([]int32, N) + pb.F_DoubleRepeated = make([]float64, N) + for i := 0; i < N; i++ { + pb.F_Int32Repeated[i] = int32(i) + pb.F_DoubleRepeated[i] = float64(i) + } + return pb +} + +func bytesMsg() *GoTest { + pb := initGoTest(true) + buf := make([]byte, 4000) + for i := range buf { + buf[i] = byte(i) + } + pb.F_BytesDefaulted = buf + return pb +} + +func benchmarkMarshal(b *testing.B, pb Message, marshal func(Message) ([]byte, error)) { + d, _ := marshal(pb) + b.SetBytes(int64(len(d))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + marshal(pb) + } +} + +func benchmarkBufferMarshal(b *testing.B, pb Message) { + p := NewBuffer(nil) + benchmarkMarshal(b, pb, func(pb0 Message) ([]byte, error) { + p.Reset() + err := p.Marshal(pb0) + return p.Bytes(), err + }) +} + +func benchmarkSize(b *testing.B, pb Message) { + benchmarkMarshal(b, pb, func(pb0 Message) ([]byte, error) { + Size(pb) + return nil, nil + }) +} + +func newOf(pb Message) Message { + in := reflect.ValueOf(pb) + if in.IsNil() { + return pb + } + return reflect.New(in.Type().Elem()).Interface().(Message) +} + +func benchmarkUnmarshal(b *testing.B, pb Message, unmarshal func([]byte, Message) error) { + d, _ := Marshal(pb) + b.SetBytes(int64(len(d))) + pbd := newOf(pb) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + unmarshal(d, pbd) + } +} + +func benchmarkBufferUnmarshal(b *testing.B, pb Message) { + p := NewBuffer(nil) + benchmarkUnmarshal(b, pb, func(d []byte, pb0 Message) error { + p.SetBuf(d) + return p.Unmarshal(pb0) + }) +} + +// Benchmark{Marshal,BufferMarshal,Size,Unmarshal,BufferUnmarshal}{,Bytes} + +func BenchmarkMarshal(b *testing.B) { + benchmarkMarshal(b, testMsg(), Marshal) +} + +func BenchmarkBufferMarshal(b *testing.B) { + benchmarkBufferMarshal(b, testMsg()) +} + +func BenchmarkSize(b *testing.B) { + benchmarkSize(b, testMsg()) +} + +func BenchmarkUnmarshal(b *testing.B) { + benchmarkUnmarshal(b, testMsg(), Unmarshal) +} + +func BenchmarkBufferUnmarshal(b *testing.B) { + benchmarkBufferUnmarshal(b, testMsg()) +} + +func BenchmarkMarshalBytes(b *testing.B) { + benchmarkMarshal(b, bytesMsg(), Marshal) +} + +func BenchmarkBufferMarshalBytes(b *testing.B) { + benchmarkBufferMarshal(b, bytesMsg()) +} + +func BenchmarkSizeBytes(b *testing.B) { + benchmarkSize(b, bytesMsg()) +} + +func BenchmarkUnmarshalBytes(b *testing.B) { + benchmarkUnmarshal(b, bytesMsg(), Unmarshal) +} + +func BenchmarkBufferUnmarshalBytes(b *testing.B) { + benchmarkBufferUnmarshal(b, bytesMsg()) +} + +func BenchmarkUnmarshalUnrecognizedFields(b *testing.B) { + b.StopTimer() + pb := initGoTestField() + skip := &GoSkipTest{ + SkipInt32: Int32(32), + SkipFixed32: Uint32(3232), + SkipFixed64: Uint64(6464), + SkipString: String("skipper"), + Skipgroup: &GoSkipTest_SkipGroup{ + GroupInt32: Int32(75), + GroupString: String("wxyz"), + }, + } + + pbd := new(GoTestField) + p := NewBuffer(nil) + p.Marshal(pb) + p.Marshal(skip) + p2 := NewBuffer(nil) + + b.StartTimer() + for i := 0; i < b.N; i++ { + p2.SetBuf(p.Bytes()) + p2.Unmarshal(pbd) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go new file mode 100644 index 0000000..8b94d9e --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go @@ -0,0 +1,169 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Protocol buffer deep copy. +// TODO: MessageSet and RawMessage. + +package proto + +import ( + "log" + "reflect" + "strings" +) + +// Clone returns a deep copy of a protocol buffer. +func Clone(pb Message) Message { + in := reflect.ValueOf(pb) + if in.IsNil() { + return pb + } + + out := reflect.New(in.Type().Elem()) + // out is empty so a merge is a deep copy. + mergeStruct(out.Elem(), in.Elem()) + return out.Interface().(Message) +} + +// Merge merges src into dst. +// Required and optional fields that are set in src will be set to that value in dst. +// Elements of repeated fields will be appended. +// Merge panics if src and dst are not the same type, or if dst is nil. +func Merge(dst, src Message) { + in := reflect.ValueOf(src) + out := reflect.ValueOf(dst) + if out.IsNil() { + panic("proto: nil destination") + } + if in.Type() != out.Type() { + // Explicit test prior to mergeStruct so that mistyped nils will fail + panic("proto: type mismatch") + } + if in.IsNil() { + // Merging nil into non-nil is a quiet no-op + return + } + mergeStruct(out.Elem(), in.Elem()) +} + +func mergeStruct(out, in reflect.Value) { + for i := 0; i < in.NumField(); i++ { + f := in.Type().Field(i) + if strings.HasPrefix(f.Name, "XXX_") { + continue + } + mergeAny(out.Field(i), in.Field(i)) + } + + if emIn, ok := in.Addr().Interface().(extendableProto); ok { + emOut := out.Addr().Interface().(extendableProto) + mergeExtension(emOut.ExtensionMap(), emIn.ExtensionMap()) + } + + uf := in.FieldByName("XXX_unrecognized") + if !uf.IsValid() { + return + } + uin := uf.Bytes() + if len(uin) > 0 { + out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...)) + } +} + +func mergeAny(out, in reflect.Value) { + if in.Type() == protoMessageType { + if !in.IsNil() { + if out.IsNil() { + out.Set(reflect.ValueOf(Clone(in.Interface().(Message)))) + } else { + Merge(out.Interface().(Message), in.Interface().(Message)) + } + } + return + } + switch in.Kind() { + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, + reflect.String, reflect.Uint32, reflect.Uint64: + out.Set(in) + case reflect.Ptr: + if in.IsNil() { + return + } + if out.IsNil() { + out.Set(reflect.New(in.Elem().Type())) + } + mergeAny(out.Elem(), in.Elem()) + case reflect.Slice: + if in.IsNil() { + return + } + n := in.Len() + if out.IsNil() { + out.Set(reflect.MakeSlice(in.Type(), 0, n)) + } + switch in.Type().Elem().Kind() { + case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, + reflect.String, reflect.Uint32, reflect.Uint64: + out.Set(reflect.AppendSlice(out, in)) + case reflect.Uint8: + // []byte is a scalar bytes field. + out.Set(in) + default: + for i := 0; i < n; i++ { + x := reflect.Indirect(reflect.New(in.Type().Elem())) + mergeAny(x, in.Index(i)) + out.Set(reflect.Append(out, x)) + } + } + case reflect.Struct: + mergeStruct(out, in) + default: + // unknown type, so not a protocol buffer + log.Printf("proto: don't know how to copy %v", in) + } +} + +func mergeExtension(out, in map[int32]Extension) { + for extNum, eIn := range in { + eOut := Extension{desc: eIn.desc} + if eIn.value != nil { + v := reflect.New(reflect.TypeOf(eIn.value)).Elem() + mergeAny(v, reflect.ValueOf(eIn.value)) + eOut.value = v.Interface() + } + if eIn.enc != nil { + eOut.enc = make([]byte, len(eIn.enc)) + copy(eOut.enc, eIn.enc) + } + + out[extNum] = eOut + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go new file mode 100644 index 0000000..39aaaf7 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go @@ -0,0 +1,186 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + "code.google.com/p/goprotobuf/proto" + + pb "./testdata" +) + +var cloneTestMessage = &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &pb.InnerMessage{ + Host: proto.String("niles"), + Port: proto.Int32(9099), + Connected: proto.Bool(true), + }, + Others: []*pb.OtherMessage{ + { + Value: []byte("some bytes"), + }, + }, + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham"), []byte("wow")}, +} + +func init() { + ext := &pb.Ext{ + Data: proto.String("extension"), + } + if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil { + panic("SetExtension: " + err.Error()) + } +} + +func TestClone(t *testing.T) { + m := proto.Clone(cloneTestMessage).(*pb.MyMessage) + if !proto.Equal(m, cloneTestMessage) { + t.Errorf("Clone(%v) = %v", cloneTestMessage, m) + } + + // Verify it was a deep copy. + *m.Inner.Port++ + if proto.Equal(m, cloneTestMessage) { + t.Error("Mutating clone changed the original") + } +} + +func TestCloneNil(t *testing.T) { + var m *pb.MyMessage + if c := proto.Clone(m); !proto.Equal(m, c) { + t.Errorf("Clone(%v) = %v", m, c) + } +} + +var mergeTests = []struct { + src, dst, want proto.Message +}{ + { + src: &pb.MyMessage{ + Count: proto.Int32(42), + }, + dst: &pb.MyMessage{ + Name: proto.String("Dave"), + }, + want: &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + }, + }, + { + src: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("hey"), + Connected: proto.Bool(true), + }, + Pet: []string{"horsey"}, + Others: []*pb.OtherMessage{ + { + Value: []byte("some bytes"), + }, + }, + }, + dst: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("niles"), + Port: proto.Int32(9099), + }, + Pet: []string{"bunny", "kitty"}, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(31415926535), + }, + { + // Explicitly test a src=nil field + Inner: nil, + }, + }, + }, + want: &pb.MyMessage{ + Inner: &pb.InnerMessage{ + Host: proto.String("hey"), + Connected: proto.Bool(true), + Port: proto.Int32(9099), + }, + Pet: []string{"bunny", "kitty", "horsey"}, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(31415926535), + }, + {}, + { + Value: []byte("some bytes"), + }, + }, + }, + }, + { + src: &pb.MyMessage{ + RepBytes: [][]byte{[]byte("wow")}, + }, + dst: &pb.MyMessage{ + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham")}, + }, + want: &pb.MyMessage{ + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(6), + }, + RepBytes: [][]byte{[]byte("sham"), []byte("wow")}, + }, + }, + // Check that a scalar bytes field replaces rather than appends. + { + src: &pb.OtherMessage{Value: []byte("foo")}, + dst: &pb.OtherMessage{Value: []byte("bar")}, + want: &pb.OtherMessage{Value: []byte("foo")}, + }, +} + +func TestMerge(t *testing.T) { + for _, m := range mergeTests { + got := proto.Clone(m.dst) + proto.Merge(got, m.src) + if !proto.Equal(got, m.want) { + t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want) + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go new file mode 100644 index 0000000..49a487a --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go @@ -0,0 +1,721 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for decoding protocol buffer data to construct in-memory representations. + */ + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" +) + +// errOverflow is returned when an integer is too large to be represented. +var errOverflow = errors.New("proto: integer overflow") + +// The fundamental decoders that interpret bytes on the wire. +// Those that take integer types all return uint64 and are +// therefore of type valueDecoder. + +// DecodeVarint reads a varint-encoded integer from the slice. +// It returns the integer and the number of bytes consumed, or +// zero if there is not enough. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func DecodeVarint(buf []byte) (x uint64, n int) { + // x, n already 0 + for shift := uint(0); shift < 64; shift += 7 { + if n >= len(buf) { + return 0, 0 + } + b := uint64(buf[n]) + n++ + x |= (b & 0x7F) << shift + if (b & 0x80) == 0 { + return x, n + } + } + + // The number is too large to represent in a 64-bit value. + return 0, 0 +} + +// DecodeVarint reads a varint-encoded integer from the Buffer. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func (p *Buffer) DecodeVarint() (x uint64, err error) { + // x, err already 0 + + i := p.index + l := len(p.buf) + + for shift := uint(0); shift < 64; shift += 7 { + if i >= l { + err = io.ErrUnexpectedEOF + return + } + b := p.buf[i] + i++ + x |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + p.index = i + return + } + } + + // The number is too large to represent in a 64-bit value. + err = errOverflow + return +} + +// DecodeFixed64 reads a 64-bit integer from the Buffer. +// This is the format for the +// fixed64, sfixed64, and double protocol buffer types. +func (p *Buffer) DecodeFixed64() (x uint64, err error) { + // x, err already 0 + i := p.index + 8 + if i < 0 || i > len(p.buf) { + err = io.ErrUnexpectedEOF + return + } + p.index = i + + x = uint64(p.buf[i-8]) + x |= uint64(p.buf[i-7]) << 8 + x |= uint64(p.buf[i-6]) << 16 + x |= uint64(p.buf[i-5]) << 24 + x |= uint64(p.buf[i-4]) << 32 + x |= uint64(p.buf[i-3]) << 40 + x |= uint64(p.buf[i-2]) << 48 + x |= uint64(p.buf[i-1]) << 56 + return +} + +// DecodeFixed32 reads a 32-bit integer from the Buffer. +// This is the format for the +// fixed32, sfixed32, and float protocol buffer types. +func (p *Buffer) DecodeFixed32() (x uint64, err error) { + // x, err already 0 + i := p.index + 4 + if i < 0 || i > len(p.buf) { + err = io.ErrUnexpectedEOF + return + } + p.index = i + + x = uint64(p.buf[i-4]) + x |= uint64(p.buf[i-3]) << 8 + x |= uint64(p.buf[i-2]) << 16 + x |= uint64(p.buf[i-1]) << 24 + return +} + +// DecodeZigzag64 reads a zigzag-encoded 64-bit integer +// from the Buffer. +// This is the format used for the sint64 protocol buffer type. +func (p *Buffer) DecodeZigzag64() (x uint64, err error) { + x, err = p.DecodeVarint() + if err != nil { + return + } + x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63) + return +} + +// DecodeZigzag32 reads a zigzag-encoded 32-bit integer +// from the Buffer. +// This is the format used for the sint32 protocol buffer type. +func (p *Buffer) DecodeZigzag32() (x uint64, err error) { + x, err = p.DecodeVarint() + if err != nil { + return + } + x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31)) + return +} + +// These are not ValueDecoders: they produce an array of bytes or a string. +// bytes, embedded messages + +// DecodeRawBytes reads a count-delimited byte buffer from the Buffer. +// This is the format used for the bytes protocol buffer +// type and for embedded messages. +func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) { + n, err := p.DecodeVarint() + if err != nil { + return + } + + nb := int(n) + if nb < 0 { + return nil, fmt.Errorf("proto: bad byte length %d", nb) + } + end := p.index + nb + if end < p.index || end > len(p.buf) { + return nil, io.ErrUnexpectedEOF + } + + if !alloc { + // todo: check if can get more uses of alloc=false + buf = p.buf[p.index:end] + p.index += nb + return + } + + buf = make([]byte, nb) + copy(buf, p.buf[p.index:]) + p.index += nb + return +} + +// DecodeStringBytes reads an encoded string from the Buffer. +// This is the format used for the proto2 string type. +func (p *Buffer) DecodeStringBytes() (s string, err error) { + buf, err := p.DecodeRawBytes(false) + if err != nil { + return + } + return string(buf), nil +} + +// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. +// If the protocol buffer has extensions, and the field matches, add it as an extension. +// Otherwise, if the XXX_unrecognized field exists, append the skipped data there. +func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error { + oi := o.index + + err := o.skip(t, tag, wire) + if err != nil { + return err + } + + if !unrecField.IsValid() { + return nil + } + + ptr := structPointer_Bytes(base, unrecField) + + // Add the skipped field to struct field + obuf := o.buf + + o.buf = *ptr + o.EncodeVarint(uint64(tag<<3 | wire)) + *ptr = append(o.buf, obuf[oi:o.index]...) + + o.buf = obuf + + return nil +} + +// Skip the next item in the buffer. Its wire type is decoded and presented as an argument. +func (o *Buffer) skip(t reflect.Type, tag, wire int) error { + + var u uint64 + var err error + + switch wire { + case WireVarint: + _, err = o.DecodeVarint() + case WireFixed64: + _, err = o.DecodeFixed64() + case WireBytes: + _, err = o.DecodeRawBytes(false) + case WireFixed32: + _, err = o.DecodeFixed32() + case WireStartGroup: + for { + u, err = o.DecodeVarint() + if err != nil { + break + } + fwire := int(u & 0x7) + if fwire == WireEndGroup { + break + } + ftag := int(u >> 3) + err = o.skip(t, ftag, fwire) + if err != nil { + break + } + } + default: + err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t) + } + return err +} + +// Unmarshaler is the interface representing objects that can +// unmarshal themselves. The method should reset the receiver before +// decoding starts. The argument points to data that may be +// overwritten, so implementations should not keep references to the +// buffer. +type Unmarshaler interface { + Unmarshal([]byte) error +} + +// Unmarshal parses the protocol buffer representation in buf and places the +// decoded result in pb. If the struct underlying pb does not match +// the data in buf, the results can be unpredictable. +// +// Unmarshal resets pb before starting to unmarshal, so any +// existing data in pb is always removed. Use UnmarshalMerge +// to preserve and append to existing data. +func Unmarshal(buf []byte, pb Message) error { + pb.Reset() + return UnmarshalMerge(buf, pb) +} + +// UnmarshalMerge parses the protocol buffer representation in buf and +// writes the decoded result to pb. If the struct underlying pb does not match +// the data in buf, the results can be unpredictable. +// +// UnmarshalMerge merges into existing data in pb. +// Most code should use Unmarshal instead. +func UnmarshalMerge(buf []byte, pb Message) error { + // If the object can unmarshal itself, let it. + if u, ok := pb.(Unmarshaler); ok { + return u.Unmarshal(buf) + } + return NewBuffer(buf).Unmarshal(pb) +} + +// Unmarshal parses the protocol buffer representation in the +// Buffer and places the decoded result in pb. If the struct +// underlying pb does not match the data in the buffer, the results can be +// unpredictable. +func (p *Buffer) Unmarshal(pb Message) error { + // If the object can unmarshal itself, let it. + if u, ok := pb.(Unmarshaler); ok { + err := u.Unmarshal(p.buf[p.index:]) + p.index = len(p.buf) + return err + } + + typ, base, err := getbase(pb) + if err != nil { + return err + } + + err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base) + + if collectStats { + stats.Decode++ + } + + return err +} + +// unmarshalType does the work of unmarshaling a structure. +func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error { + var state errorState + required, reqFields := prop.reqCount, uint64(0) + + var err error + for err == nil && o.index < len(o.buf) { + oi := o.index + var u uint64 + u, err = o.DecodeVarint() + if err != nil { + break + } + wire := int(u & 0x7) + if wire == WireEndGroup { + if is_group { + return nil // input is satisfied + } + return fmt.Errorf("proto: %s: wiretype end group for non-group", st) + } + tag := int(u >> 3) + if tag <= 0 { + return fmt.Errorf("proto: %s: illegal tag %d", st, tag) + } + fieldnum, ok := prop.decoderTags.get(tag) + if !ok { + // Maybe it's an extension? + if prop.extendable { + if e := structPointer_Interface(base, st).(extendableProto); isExtensionField(e, int32(tag)) { + if err = o.skip(st, tag, wire); err == nil { + ext := e.ExtensionMap()[int32(tag)] // may be missing + ext.enc = append(ext.enc, o.buf[oi:o.index]...) + e.ExtensionMap()[int32(tag)] = ext + } + continue + } + } + err = o.skipAndSave(st, tag, wire, base, prop.unrecField) + continue + } + p := prop.Prop[fieldnum] + + if p.dec == nil { + fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name) + continue + } + dec := p.dec + if wire != WireStartGroup && wire != p.WireType { + if wire == WireBytes && p.packedDec != nil { + // a packable field + dec = p.packedDec + } else { + err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType) + continue + } + } + decErr := dec(o, p, base) + if decErr != nil && !state.shouldContinue(decErr, p) { + err = decErr + } + if err == nil && p.Required { + // Successfully decoded a required field. + if tag <= 64 { + // use bitmap for fields 1-64 to catch field reuse. + var mask uint64 = 1 << uint64(tag-1) + if reqFields&mask == 0 { + // new required field + reqFields |= mask + required-- + } + } else { + // This is imprecise. It can be fooled by a required field + // with a tag > 64 that is encoded twice; that's very rare. + // A fully correct implementation would require allocating + // a data structure, which we would like to avoid. + required-- + } + } + } + if err == nil { + if is_group { + return io.ErrUnexpectedEOF + } + if state.err != nil { + return state.err + } + if required > 0 { + // Not enough information to determine the exact field. If we use extra + // CPU, we could determine the field only if the missing required field + // has a tag <= 64 and we check reqFields. + return &RequiredNotSetError{"{Unknown}"} + } + } + return err +} + +// Individual type decoders +// For each, +// u is the decoded value, +// v is a pointer to the field (pointer) in the struct + +// Sizes of the pools to allocate inside the Buffer. +// The goal is modest amortization and allocation +// on at least 16-byte boundaries. +const ( + boolPoolSize = 16 + uint32PoolSize = 8 + uint64PoolSize = 4 +) + +// Decode a bool. +func (o *Buffer) dec_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + if len(o.bools) == 0 { + o.bools = make([]bool, boolPoolSize) + } + o.bools[0] = u != 0 + *structPointer_Bool(base, p.field) = &o.bools[0] + o.bools = o.bools[1:] + return nil +} + +// Decode an int32. +func (o *Buffer) dec_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word32_Set(structPointer_Word32(base, p.field), o, uint32(u)) + return nil +} + +// Decode an int64. +func (o *Buffer) dec_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + word64_Set(structPointer_Word64(base, p.field), o, u) + return nil +} + +// Decode a string. +func (o *Buffer) dec_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + sp := new(string) + *sp = s + *structPointer_String(base, p.field) = sp + return nil +} + +// Decode a slice of bytes ([]byte). +func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error { + b, err := o.DecodeRawBytes(true) + if err != nil { + return err + } + *structPointer_Bytes(base, p.field) = b + return nil +} + +// Decode a slice of bools ([]bool). +func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + v := structPointer_BoolSlice(base, p.field) + *v = append(*v, u != 0) + return nil +} + +// Decode a slice of bools ([]bool) in packed format. +func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error { + v := structPointer_BoolSlice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded bools + + y := *v + for i := 0; i < nb; i++ { + u, err := p.valDec(o) + if err != nil { + return err + } + y = append(y, u != 0) + } + + *v = y + return nil +} + +// Decode a slice of int32s ([]int32). +func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + structPointer_Word32Slice(base, p.field).Append(uint32(u)) + return nil +} + +// Decode a slice of int32s ([]int32) in packed format. +func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error { + v := structPointer_Word32Slice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded int32s + + fin := o.index + nb + if fin < o.index { + return errOverflow + } + for o.index < fin { + u, err := p.valDec(o) + if err != nil { + return err + } + v.Append(uint32(u)) + } + return nil +} + +// Decode a slice of int64s ([]int64). +func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error { + u, err := p.valDec(o) + if err != nil { + return err + } + + structPointer_Word64Slice(base, p.field).Append(u) + return nil +} + +// Decode a slice of int64s ([]int64) in packed format. +func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error { + v := structPointer_Word64Slice(base, p.field) + + nn, err := o.DecodeVarint() + if err != nil { + return err + } + nb := int(nn) // number of bytes of encoded int64s + + fin := o.index + nb + if fin < o.index { + return errOverflow + } + for o.index < fin { + u, err := p.valDec(o) + if err != nil { + return err + } + v.Append(u) + } + return nil +} + +// Decode a slice of strings ([]string). +func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error { + s, err := o.DecodeStringBytes() + if err != nil { + return err + } + v := structPointer_StringSlice(base, p.field) + *v = append(*v, s) + return nil +} + +// Decode a slice of slice of bytes ([][]byte). +func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error { + b, err := o.DecodeRawBytes(true) + if err != nil { + return err + } + v := structPointer_BytesSlice(base, p.field) + *v = append(*v, b) + return nil +} + +// Decode a group. +func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error { + bas := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(bas) { + // allocate new nested message + bas = toStructPointer(reflect.New(p.stype)) + structPointer_SetStructPointer(base, p.field, bas) + } + return o.unmarshalType(p.stype, p.sprop, true, bas) +} + +// Decode an embedded message. +func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) { + raw, e := o.DecodeRawBytes(false) + if e != nil { + return e + } + + bas := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(bas) { + // allocate new nested message + bas = toStructPointer(reflect.New(p.stype)) + structPointer_SetStructPointer(base, p.field, bas) + } + + // If the object can unmarshal itself, let it. + if p.isUnmarshaler { + iv := structPointer_Interface(bas, p.stype) + return iv.(Unmarshaler).Unmarshal(raw) + } + + obuf := o.buf + oi := o.index + o.buf = raw + o.index = 0 + + err = o.unmarshalType(p.stype, p.sprop, false, bas) + o.buf = obuf + o.index = oi + + return err +} + +// Decode a slice of embedded messages. +func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error { + return o.dec_slice_struct(p, false, base) +} + +// Decode a slice of embedded groups. +func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error { + return o.dec_slice_struct(p, true, base) +} + +// Decode a slice of structs ([]*struct). +func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error { + v := reflect.New(p.stype) + bas := toStructPointer(v) + structPointer_StructPointerSlice(base, p.field).Append(bas) + + if is_group { + err := o.unmarshalType(p.stype, p.sprop, is_group, bas) + return err + } + + raw, err := o.DecodeRawBytes(false) + if err != nil { + return err + } + + // If the object can unmarshal itself, let it. + if p.isUnmarshaler { + iv := v.Interface() + return iv.(Unmarshaler).Unmarshal(raw) + } + + obuf := o.buf + oi := o.index + o.buf = raw + o.index = 0 + + err = o.unmarshalType(p.stype, p.sprop, is_group, bas) + + o.buf = obuf + o.index = oi + + return err +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go new file mode 100644 index 0000000..35e5ad5 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go @@ -0,0 +1,1054 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for encoding data into the wire format for protocol buffers. + */ + +import ( + "errors" + "fmt" + "reflect" + "sort" +) + +// RequiredNotSetError is the error returned if Marshal is called with +// a protocol buffer struct whose required fields have not +// all been initialized. It is also the error returned if Unmarshal is +// called with an encoded protocol buffer that does not include all the +// required fields. +// +// When printed, RequiredNotSetError reports the first unset required field in a +// message. If the field cannot be precisely determined, it is reported as +// "{Unknown}". +type RequiredNotSetError struct { + field string +} + +func (e *RequiredNotSetError) Error() string { + return fmt.Sprintf("proto: required field %q not set", e.field) +} + +var ( + // ErrRepeatedHasNil is the error returned if Marshal is called with + // a struct with a repeated field containing a nil element. + ErrRepeatedHasNil = errors.New("proto: repeated field has nil element") + + // ErrNil is the error returned if Marshal is called with nil. + ErrNil = errors.New("proto: Marshal called with nil") +) + +// The fundamental encoders that put bytes on the wire. +// Those that take integer types all accept uint64 and are +// therefore of type valueEncoder. + +const maxVarintBytes = 10 // maximum length of a varint + +// EncodeVarint returns the varint encoding of x. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +// Not used by the package itself, but helpful to clients +// wishing to use the same encoding. +func EncodeVarint(x uint64) []byte { + var buf [maxVarintBytes]byte + var n int + for n = 0; x > 127; n++ { + buf[n] = 0x80 | uint8(x&0x7F) + x >>= 7 + } + buf[n] = uint8(x) + n++ + return buf[0:n] +} + +// EncodeVarint writes a varint-encoded integer to the Buffer. +// This is the format for the +// int32, int64, uint32, uint64, bool, and enum +// protocol buffer types. +func (p *Buffer) EncodeVarint(x uint64) error { + for x >= 1<<7 { + p.buf = append(p.buf, uint8(x&0x7f|0x80)) + x >>= 7 + } + p.buf = append(p.buf, uint8(x)) + return nil +} + +func sizeVarint(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} + +// EncodeFixed64 writes a 64-bit integer to the Buffer. +// This is the format for the +// fixed64, sfixed64, and double protocol buffer types. +func (p *Buffer) EncodeFixed64(x uint64) error { + p.buf = append(p.buf, + uint8(x), + uint8(x>>8), + uint8(x>>16), + uint8(x>>24), + uint8(x>>32), + uint8(x>>40), + uint8(x>>48), + uint8(x>>56)) + return nil +} + +func sizeFixed64(x uint64) int { + return 8 +} + +// EncodeFixed32 writes a 32-bit integer to the Buffer. +// This is the format for the +// fixed32, sfixed32, and float protocol buffer types. +func (p *Buffer) EncodeFixed32(x uint64) error { + p.buf = append(p.buf, + uint8(x), + uint8(x>>8), + uint8(x>>16), + uint8(x>>24)) + return nil +} + +func sizeFixed32(x uint64) int { + return 4 +} + +// EncodeZigzag64 writes a zigzag-encoded 64-bit integer +// to the Buffer. +// This is the format used for the sint64 protocol buffer type. +func (p *Buffer) EncodeZigzag64(x uint64) error { + // use signed number to get arithmetic right shift. + return p.EncodeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} + +func sizeZigzag64(x uint64) int { + return sizeVarint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} + +// EncodeZigzag32 writes a zigzag-encoded 32-bit integer +// to the Buffer. +// This is the format used for the sint32 protocol buffer type. +func (p *Buffer) EncodeZigzag32(x uint64) error { + // use signed number to get arithmetic right shift. + return p.EncodeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) +} + +func sizeZigzag32(x uint64) int { + return sizeVarint(uint64((uint32(x) << 1) ^ uint32((int32(x) >> 31)))) +} + +// EncodeRawBytes writes a count-delimited byte buffer to the Buffer. +// This is the format used for the bytes protocol buffer +// type and for embedded messages. +func (p *Buffer) EncodeRawBytes(b []byte) error { + p.EncodeVarint(uint64(len(b))) + p.buf = append(p.buf, b...) + return nil +} + +func sizeRawBytes(b []byte) int { + return sizeVarint(uint64(len(b))) + + len(b) +} + +// EncodeStringBytes writes an encoded string to the Buffer. +// This is the format used for the proto2 string type. +func (p *Buffer) EncodeStringBytes(s string) error { + p.EncodeVarint(uint64(len(s))) + p.buf = append(p.buf, s...) + return nil +} + +func sizeStringBytes(s string) int { + return sizeVarint(uint64(len(s))) + + len(s) +} + +// Marshaler is the interface representing objects that can marshal themselves. +type Marshaler interface { + Marshal() ([]byte, error) +} + +// Marshal takes the protocol buffer +// and encodes it into the wire format, returning the data. +func Marshal(pb Message) ([]byte, error) { + // Can the object marshal itself? + if m, ok := pb.(Marshaler); ok { + return m.Marshal() + } + p := NewBuffer(nil) + err := p.Marshal(pb) + var state errorState + if err != nil && !state.shouldContinue(err, nil) { + return nil, err + } + if p.buf == nil && err == nil { + // Return a non-nil slice on success. + return []byte{}, nil + } + return p.buf, err +} + +// Marshal takes the protocol buffer +// and encodes it into the wire format, writing the result to the +// Buffer. +func (p *Buffer) Marshal(pb Message) error { + // Can the object marshal itself? + if m, ok := pb.(Marshaler); ok { + data, err := m.Marshal() + if err != nil { + return err + } + p.buf = append(p.buf, data...) + return nil + } + + t, base, err := getbase(pb) + if structPointer_IsNil(base) { + return ErrNil + } + if err == nil { + err = p.enc_struct(t.Elem(), GetProperties(t.Elem()), base) + } + + if collectStats { + stats.Encode++ + } + + return err +} + +// Size returns the encoded size of a protocol buffer. +func Size(pb Message) (n int) { + // Can the object marshal itself? If so, Size is slow. + // TODO: add Size to Marshaler, or add a Sizer interface. + if m, ok := pb.(Marshaler); ok { + b, _ := m.Marshal() + return len(b) + } + + t, base, err := getbase(pb) + if structPointer_IsNil(base) { + return 0 + } + if err == nil { + n = size_struct(t.Elem(), GetProperties(t.Elem()), base) + } + + if collectStats { + stats.Size++ + } + + return +} + +// Individual type encoders. + +// Encode a bool. +func (o *Buffer) enc_bool(p *Properties, base structPointer) error { + v := *structPointer_Bool(base, p.field) + if v == nil { + return ErrNil + } + x := 0 + if *v { + x = 1 + } + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func size_bool(p *Properties, base structPointer) int { + v := *structPointer_Bool(base, p.field) + if v == nil { + return 0 + } + return len(p.tagcode) + 1 // each bool takes exactly one byte +} + +// Encode an int32. +func (o *Buffer) enc_int32(p *Properties, base structPointer) error { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return ErrNil + } + x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func size_int32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return 0 + } + x := int32(word32_Get(v)) // permit sign extension to use full 64-bit range + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +// Encode a uint32. +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_uint32(p *Properties, base structPointer) error { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return ErrNil + } + x := word32_Get(v) + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, uint64(x)) + return nil +} + +func size_uint32(p *Properties, base structPointer) (n int) { + v := structPointer_Word32(base, p.field) + if word32_IsNil(v) { + return 0 + } + x := word32_Get(v) + n += len(p.tagcode) + n += p.valSize(uint64(x)) + return +} + +// Encode an int64. +func (o *Buffer) enc_int64(p *Properties, base structPointer) error { + v := structPointer_Word64(base, p.field) + if word64_IsNil(v) { + return ErrNil + } + x := word64_Get(v) + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, x) + return nil +} + +func size_int64(p *Properties, base structPointer) (n int) { + v := structPointer_Word64(base, p.field) + if word64_IsNil(v) { + return 0 + } + x := word64_Get(v) + n += len(p.tagcode) + n += p.valSize(x) + return +} + +// Encode a string. +func (o *Buffer) enc_string(p *Properties, base structPointer) error { + v := *structPointer_String(base, p.field) + if v == nil { + return ErrNil + } + x := *v + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(x) + return nil +} + +func size_string(p *Properties, base structPointer) (n int) { + v := *structPointer_String(base, p.field) + if v == nil { + return 0 + } + x := *v + n += len(p.tagcode) + n += sizeStringBytes(x) + return +} + +// All protocol buffer fields are nillable, but be careful. +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + return false +} + +// Encode a message struct. +func (o *Buffer) enc_struct_message(p *Properties, base structPointer) error { + var state errorState + structp := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(structp) { + return ErrNil + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, err := m.Marshal() + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(data) + return nil + } + + o.buf = append(o.buf, p.tagcode...) + return o.enc_len_struct(p.stype, p.sprop, structp, &state) +} + +func size_struct_message(p *Properties, base structPointer) int { + structp := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(structp) { + return 0 + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, _ := m.Marshal() + n0 := len(p.tagcode) + n1 := sizeRawBytes(data) + return n0 + n1 + } + + n0 := len(p.tagcode) + n1 := size_struct(p.stype, p.sprop, structp) + n2 := sizeVarint(uint64(n1)) // size of encoded length + return n0 + n1 + n2 +} + +// Encode a group struct. +func (o *Buffer) enc_struct_group(p *Properties, base structPointer) error { + var state errorState + b := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(b) { + return ErrNil + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) + err := o.enc_struct(p.stype, p.sprop, b) + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) + return state.err +} + +func size_struct_group(p *Properties, base structPointer) (n int) { + b := structPointer_GetStructPointer(base, p.field) + if structPointer_IsNil(b) { + return 0 + } + + n += sizeVarint(uint64((p.Tag << 3) | WireStartGroup)) + n += size_struct(p.stype, p.sprop, b) + n += sizeVarint(uint64((p.Tag << 3) | WireEndGroup)) + return +} + +// Encode a slice of bools ([]bool). +func (o *Buffer) enc_slice_bool(p *Properties, base structPointer) error { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return ErrNil + } + for _, x := range s { + o.buf = append(o.buf, p.tagcode...) + v := uint64(0) + if x { + v = 1 + } + p.valEnc(o, v) + } + return nil +} + +func size_slice_bool(p *Properties, base structPointer) int { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return 0 + } + return l * (len(p.tagcode) + 1) // each bool takes exactly one byte +} + +// Encode a slice of bools ([]bool) in packed format. +func (o *Buffer) enc_slice_packed_bool(p *Properties, base structPointer) error { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(l)) // each bool takes exactly one byte + for _, x := range s { + v := uint64(0) + if x { + v = 1 + } + p.valEnc(o, v) + } + return nil +} + +func size_slice_packed_bool(p *Properties, base structPointer) (n int) { + s := *structPointer_BoolSlice(base, p.field) + l := len(s) + if l == 0 { + return 0 + } + n += len(p.tagcode) + n += sizeVarint(uint64(l)) + n += l // each bool takes exactly one byte + return +} + +// Encode a slice of bytes ([]byte). +func (o *Buffer) enc_slice_byte(p *Properties, base structPointer) error { + s := *structPointer_Bytes(base, p.field) + if s == nil { + return ErrNil + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(s) + return nil +} + +func size_slice_byte(p *Properties, base structPointer) (n int) { + s := *structPointer_Bytes(base, p.field) + if s == nil { + return 0 + } + n += len(p.tagcode) + n += sizeRawBytes(s) + return +} + +// Encode a slice of int32s ([]int32). +func (o *Buffer) enc_slice_int32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + p.valEnc(o, uint64(x)) + } + return nil +} + +func size_slice_int32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + n += p.valSize(uint64(x)) + } + return +} + +// Encode a slice of int32s ([]int32) in packed format. +func (o *Buffer) enc_slice_packed_int32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + p.valEnc(buf, uint64(x)) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_int32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + x := int32(s.Index(i)) // permit sign extension to use full 64-bit range + bufSize += p.valSize(uint64(x)) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of uint32s ([]uint32). +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_slice_uint32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + x := s.Index(i) + p.valEnc(o, uint64(x)) + } + return nil +} + +func size_slice_uint32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + x := s.Index(i) + n += p.valSize(uint64(x)) + } + return +} + +// Encode a slice of uint32s ([]uint32) in packed format. +// Exactly the same as int32, except for no sign extension. +func (o *Buffer) enc_slice_packed_uint32(p *Properties, base structPointer) error { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + p.valEnc(buf, uint64(s.Index(i))) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_uint32(p *Properties, base structPointer) (n int) { + s := structPointer_Word32Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + bufSize += p.valSize(uint64(s.Index(i))) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of int64s ([]int64). +func (o *Buffer) enc_slice_int64(p *Properties, base structPointer) error { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + p.valEnc(o, s.Index(i)) + } + return nil +} + +func size_slice_int64(p *Properties, base structPointer) (n int) { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + for i := 0; i < l; i++ { + n += len(p.tagcode) + n += p.valSize(s.Index(i)) + } + return +} + +// Encode a slice of int64s ([]int64) in packed format. +func (o *Buffer) enc_slice_packed_int64(p *Properties, base structPointer) error { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return ErrNil + } + // TODO: Reuse a Buffer. + buf := NewBuffer(nil) + for i := 0; i < l; i++ { + p.valEnc(buf, s.Index(i)) + } + + o.buf = append(o.buf, p.tagcode...) + o.EncodeVarint(uint64(len(buf.buf))) + o.buf = append(o.buf, buf.buf...) + return nil +} + +func size_slice_packed_int64(p *Properties, base structPointer) (n int) { + s := structPointer_Word64Slice(base, p.field) + l := s.Len() + if l == 0 { + return 0 + } + var bufSize int + for i := 0; i < l; i++ { + bufSize += p.valSize(s.Index(i)) + } + + n += len(p.tagcode) + n += sizeVarint(uint64(bufSize)) + n += bufSize + return +} + +// Encode a slice of slice of bytes ([][]byte). +func (o *Buffer) enc_slice_slice_byte(p *Properties, base structPointer) error { + ss := *structPointer_BytesSlice(base, p.field) + l := len(ss) + if l == 0 { + return ErrNil + } + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(ss[i]) + } + return nil +} + +func size_slice_slice_byte(p *Properties, base structPointer) (n int) { + ss := *structPointer_BytesSlice(base, p.field) + l := len(ss) + if l == 0 { + return 0 + } + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + n += sizeRawBytes(ss[i]) + } + return +} + +// Encode a slice of strings ([]string). +func (o *Buffer) enc_slice_string(p *Properties, base structPointer) error { + ss := *structPointer_StringSlice(base, p.field) + l := len(ss) + for i := 0; i < l; i++ { + o.buf = append(o.buf, p.tagcode...) + o.EncodeStringBytes(ss[i]) + } + return nil +} + +func size_slice_string(p *Properties, base structPointer) (n int) { + ss := *structPointer_StringSlice(base, p.field) + l := len(ss) + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + n += sizeStringBytes(ss[i]) + } + return +} + +// Encode a slice of message structs ([]*struct). +func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) error { + var state errorState + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + for i := 0; i < l; i++ { + structp := s.Index(i) + if structPointer_IsNil(structp) { + return ErrRepeatedHasNil + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, err := m.Marshal() + if err != nil && !state.shouldContinue(err, nil) { + return err + } + o.buf = append(o.buf, p.tagcode...) + o.EncodeRawBytes(data) + continue + } + + o.buf = append(o.buf, p.tagcode...) + err := o.enc_len_struct(p.stype, p.sprop, structp, &state) + if err != nil && !state.shouldContinue(err, nil) { + if err == ErrNil { + return ErrRepeatedHasNil + } + return err + } + } + return state.err +} + +func size_slice_struct_message(p *Properties, base structPointer) (n int) { + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + n += l * len(p.tagcode) + for i := 0; i < l; i++ { + structp := s.Index(i) + if structPointer_IsNil(structp) { + return // return the size up to this point + } + + // Can the object marshal itself? + if p.isMarshaler { + m := structPointer_Interface(structp, p.stype).(Marshaler) + data, _ := m.Marshal() + n += len(p.tagcode) + n += sizeRawBytes(data) + continue + } + + n0 := size_struct(p.stype, p.sprop, structp) + n1 := sizeVarint(uint64(n0)) // size of encoded length + n += n0 + n1 + } + return +} + +// Encode a slice of group structs ([]*struct). +func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error { + var state errorState + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + for i := 0; i < l; i++ { + b := s.Index(i) + if structPointer_IsNil(b) { + return ErrRepeatedHasNil + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) + + err := o.enc_struct(p.stype, p.sprop, b) + + if err != nil && !state.shouldContinue(err, nil) { + if err == ErrNil { + return ErrRepeatedHasNil + } + return err + } + + o.EncodeVarint(uint64((p.Tag << 3) | WireEndGroup)) + } + return state.err +} + +func size_slice_struct_group(p *Properties, base structPointer) (n int) { + s := structPointer_StructPointerSlice(base, p.field) + l := s.Len() + + n += l * sizeVarint(uint64((p.Tag<<3)|WireStartGroup)) + n += l * sizeVarint(uint64((p.Tag<<3)|WireEndGroup)) + for i := 0; i < l; i++ { + b := s.Index(i) + if structPointer_IsNil(b) { + return // return size up to this point + } + + n += size_struct(p.stype, p.sprop, b) + } + return +} + +// Encode an extension map. +func (o *Buffer) enc_map(p *Properties, base structPointer) error { + v := *structPointer_ExtMap(base, p.field) + if err := encodeExtensionMap(v); err != nil { + return err + } + // Fast-path for common cases: zero or one extensions. + if len(v) <= 1 { + for _, e := range v { + o.buf = append(o.buf, e.enc...) + } + return nil + } + + // Sort keys to provide a deterministic encoding. + keys := make([]int, 0, len(v)) + for k := range v { + keys = append(keys, int(k)) + } + sort.Ints(keys) + + for _, k := range keys { + o.buf = append(o.buf, v[int32(k)].enc...) + } + return nil +} + +func size_map(p *Properties, base structPointer) int { + v := *structPointer_ExtMap(base, p.field) + return sizeExtensionMap(v) +} + +// Encode a struct. +func (o *Buffer) enc_struct(t reflect.Type, prop *StructProperties, base structPointer) error { + var state errorState + // Encode fields in tag order so that decoders may use optimizations + // that depend on the ordering. + // http://code.google.com/apis/protocolbuffers/docs/encoding.html#order + for _, i := range prop.order { + p := prop.Prop[i] + if p.enc != nil { + err := p.enc(o, p, base) + if err != nil { + if err == ErrNil { + if p.Required && state.err == nil { + state.err = &RequiredNotSetError{p.Name} + } + } else if !state.shouldContinue(err, p) { + return err + } + } + } + } + + // Add unrecognized fields at the end. + if prop.unrecField.IsValid() { + v := *structPointer_Bytes(base, prop.unrecField) + if len(v) > 0 { + o.buf = append(o.buf, v...) + } + } + + return state.err +} + +func size_struct(t reflect.Type, prop *StructProperties, base structPointer) (n int) { + for _, i := range prop.order { + p := prop.Prop[i] + if p.size != nil { + n += p.size(p, base) + } + } + + // Add unrecognized fields at the end. + if prop.unrecField.IsValid() { + v := *structPointer_Bytes(base, prop.unrecField) + n += len(v) + } + + return +} + +var zeroes [20]byte // longer than any conceivable sizeVarint + +// Encode a struct, preceded by its encoded length (as a varint). +func (o *Buffer) enc_len_struct(t reflect.Type, prop *StructProperties, base structPointer, state *errorState) error { + iLen := len(o.buf) + o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length + iMsg := len(o.buf) + err := o.enc_struct(t, prop, base) + if err != nil && !state.shouldContinue(err, nil) { + return err + } + lMsg := len(o.buf) - iMsg + lLen := sizeVarint(uint64(lMsg)) + switch x := lLen - (iMsg - iLen); { + case x > 0: // actual length is x bytes larger than the space we reserved + // Move msg x bytes right. + o.buf = append(o.buf, zeroes[:x]...) + copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) + case x < 0: // actual length is x bytes smaller than the space we reserved + // Move msg x bytes left. + copy(o.buf[iMsg+x:], o.buf[iMsg:iMsg+lMsg]) + o.buf = o.buf[:len(o.buf)+x] // x is negative + } + // Encode the length in the reserved space. + o.buf = o.buf[:iLen] + o.EncodeVarint(uint64(lMsg)) + o.buf = o.buf[:len(o.buf)+lMsg] + return state.err +} + +// errorState maintains the first error that occurs and updates that error +// with additional context. +type errorState struct { + err error +} + +// shouldContinue reports whether encoding should continue upon encountering the +// given error. If the error is RequiredNotSetError, shouldContinue returns true +// and, if this is the first appearance of that error, remembers it for future +// reporting. +// +// If prop is not nil, it may update any error with additional context about the +// field with the error. +func (s *errorState) shouldContinue(err error, prop *Properties) bool { + // Ignore unset required fields. + reqNotSet, ok := err.(*RequiredNotSetError) + if !ok { + return false + } + if s.err == nil { + if prop != nil { + err = &RequiredNotSetError{prop.Name + "." + reqNotSet.field} + } + s.err = err + } + return true +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go new file mode 100644 index 0000000..42542e6 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go @@ -0,0 +1,241 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Protocol buffer comparison. +// TODO: MessageSet. + +package proto + +import ( + "bytes" + "log" + "reflect" + "strings" +) + +/* +Equal returns true iff protocol buffers a and b are equal. +The arguments must both be pointers to protocol buffer structs. + +Equality is defined in this way: + - Two messages are equal iff they are the same type, + corresponding fields are equal, unknown field sets + are equal, and extensions sets are equal. + - Two set scalar fields are equal iff their values are equal. + If the fields are of a floating-point type, remember that + NaN != x for all x, including NaN. + - Two repeated fields are equal iff their lengths are the same, + and their corresponding elements are equal (a "bytes" field, + although represented by []byte, is not a repeated field) + - Two unset fields are equal. + - Two unknown field sets are equal if their current + encoded state is equal. (TODO) + - Two extension sets are equal iff they have corresponding + elements that are pairwise equal. + - Every other combination of things are not equal. + +The return value is undefined if a and b are not protocol buffers. +*/ +func Equal(a, b Message) bool { + if a == nil || b == nil { + return a == b + } + v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b) + if v1.Type() != v2.Type() { + return false + } + if v1.Kind() == reflect.Ptr { + if v1.IsNil() { + return v2.IsNil() + } + if v2.IsNil() { + return false + } + v1, v2 = v1.Elem(), v2.Elem() + } + if v1.Kind() != reflect.Struct { + return false + } + return equalStruct(v1, v2) +} + +// v1 and v2 are known to have the same type. +func equalStruct(v1, v2 reflect.Value) bool { + for i := 0; i < v1.NumField(); i++ { + f := v1.Type().Field(i) + if strings.HasPrefix(f.Name, "XXX_") { + continue + } + f1, f2 := v1.Field(i), v2.Field(i) + if f.Type.Kind() == reflect.Ptr { + if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 { + // both unset + continue + } else if n1 != n2 { + // set/unset mismatch + return false + } + b1, ok := f1.Interface().(raw) + if ok { + b2 := f2.Interface().(raw) + // RawMessage + if !bytes.Equal(b1.Bytes(), b2.Bytes()) { + return false + } + continue + } + f1, f2 = f1.Elem(), f2.Elem() + } + if !equalAny(f1, f2) { + return false + } + } + + if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() { + em2 := v2.FieldByName("XXX_extensions") + if !equalExtensions(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) { + return false + } + } + + uf := v1.FieldByName("XXX_unrecognized") + if !uf.IsValid() { + return true + } + + u1 := uf.Bytes() + u2 := v2.FieldByName("XXX_unrecognized").Bytes() + if !bytes.Equal(u1, u2) { + return false + } + + return true +} + +// v1 and v2 are known to have the same type. +func equalAny(v1, v2 reflect.Value) bool { + if v1.Type() == protoMessageType { + m1, _ := v1.Interface().(Message) + m2, _ := v2.Interface().(Message) + return Equal(m1, m2) + } + switch v1.Kind() { + case reflect.Bool: + return v1.Bool() == v2.Bool() + case reflect.Float32, reflect.Float64: + return v1.Float() == v2.Float() + case reflect.Int32, reflect.Int64: + return v1.Int() == v2.Int() + case reflect.Ptr: + return equalAny(v1.Elem(), v2.Elem()) + case reflect.Slice: + if v1.Type().Elem().Kind() == reflect.Uint8 { + // short circuit: []byte + if v1.IsNil() != v2.IsNil() { + return false + } + return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte)) + } + + if v1.Len() != v2.Len() { + return false + } + for i := 0; i < v1.Len(); i++ { + if !equalAny(v1.Index(i), v2.Index(i)) { + return false + } + } + return true + case reflect.String: + return v1.Interface().(string) == v2.Interface().(string) + case reflect.Struct: + return equalStruct(v1, v2) + case reflect.Uint32, reflect.Uint64: + return v1.Uint() == v2.Uint() + } + + // unknown type, so not a protocol buffer + log.Printf("proto: don't know how to compare %v", v1) + return false +} + +// base is the struct type that the extensions are based on. +// em1 and em2 are extension maps. +func equalExtensions(base reflect.Type, em1, em2 map[int32]Extension) bool { + if len(em1) != len(em2) { + return false + } + + for extNum, e1 := range em1 { + e2, ok := em2[extNum] + if !ok { + return false + } + + m1, m2 := e1.value, e2.value + + if m1 != nil && m2 != nil { + // Both are unencoded. + if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) { + return false + } + continue + } + + // At least one is encoded. To do a semantically correct comparison + // we need to unmarshal them first. + var desc *ExtensionDesc + if m := extensionMaps[base]; m != nil { + desc = m[extNum] + } + if desc == nil { + log.Printf("proto: don't know how to compare extension %d of %v", extNum, base) + continue + } + var err error + if m1 == nil { + m1, err = decodeExtension(e1.enc, desc) + } + if m2 == nil && err == nil { + m2, err = decodeExtension(e2.enc, desc) + } + if err != nil { + // The encoded form is invalid. + log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err) + return false + } + if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2)) { + return false + } + } + + return true +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go new file mode 100644 index 0000000..cdf3fd9 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal_test.go @@ -0,0 +1,166 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2011 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + pb "./testdata" + . "code.google.com/p/goprotobuf/proto" +) + +// Four identical base messages. +// The init function adds extensions to some of them. +var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)} +var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)} + +// Two messages with non-message extensions. +var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)} +var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)} + +func init() { + ext1 := &pb.Ext{Data: String("Kirk")} + ext2 := &pb.Ext{Data: String("Picard")} + + // messageWithExtension1a has ext1, but never marshals it. + if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil { + panic("SetExtension on 1a failed: " + err.Error()) + } + + // messageWithExtension1b is the unmarshaled form of messageWithExtension1a. + if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil { + panic("SetExtension on 1b failed: " + err.Error()) + } + buf, err := Marshal(messageWithExtension1b) + if err != nil { + panic("Marshal of 1b failed: " + err.Error()) + } + messageWithExtension1b.Reset() + if err := Unmarshal(buf, messageWithExtension1b); err != nil { + panic("Unmarshal of 1b failed: " + err.Error()) + } + + // messageWithExtension2 has ext2. + if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil { + panic("SetExtension on 2 failed: " + err.Error()) + } + + if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil { + panic("SetExtension on Int32-1 failed: " + err.Error()) + } + if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil { + panic("SetExtension on Int32-2 failed: " + err.Error()) + } +} + +var EqualTests = []struct { + desc string + a, b Message + exp bool +}{ + {"different types", &pb.GoEnum{}, &pb.GoTestField{}, false}, + {"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true}, + {"nil vs nil", nil, nil, true}, + {"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true}, + {"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false}, + {"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false}, + + {"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false}, + {"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false}, + {"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false}, + {"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true}, + + {"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false}, + {"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false}, + {"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false}, + {"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true}, + {"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true}, + {"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true}, + {"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true}, + + { + "nested, different", + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}}, + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}}, + false, + }, + { + "nested, equal", + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}}, + &pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}}, + true, + }, + + {"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true}, + {"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true}, + {"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false}, + { + "repeated bytes", + &pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}}, + &pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}}, + true, + }, + + {"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false}, + {"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true}, + {"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false}, + + {"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true}, + {"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false}, + + { + "message with group", + &pb.MyMessage{ + Count: Int32(1), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: Int32(5), + }, + }, + &pb.MyMessage{ + Count: Int32(1), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: Int32(5), + }, + }, + true, + }, +} + +func TestEqual(t *testing.T) { + for _, tc := range EqualTests { + if res := Equal(tc.a, tc.b); res != tc.exp { + t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp) + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go new file mode 100644 index 0000000..e592053 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go @@ -0,0 +1,351 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Types and routines for supporting protocol buffer extensions. + */ + +import ( + "errors" + "reflect" + "strconv" + "sync" +) + +// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message. +var ErrMissingExtension = errors.New("proto: missing extension") + +// ExtensionRange represents a range of message extensions for a protocol buffer. +// Used in code generated by the protocol compiler. +type ExtensionRange struct { + Start, End int32 // both inclusive +} + +// extendableProto is an interface implemented by any protocol buffer that may be extended. +type extendableProto interface { + Message + ExtensionRangeArray() []ExtensionRange + ExtensionMap() map[int32]Extension +} + +var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem() + +// ExtensionDesc represents an extension specification. +// Used in generated code from the protocol compiler. +type ExtensionDesc struct { + ExtendedType Message // nil pointer to the type that is being extended + ExtensionType interface{} // nil pointer to the extension type + Field int32 // field number + Name string // fully-qualified name of extension, for text formatting + Tag string // protobuf tag style +} + +func (ed *ExtensionDesc) repeated() bool { + t := reflect.TypeOf(ed.ExtensionType) + return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 +} + +// Extension represents an extension in a message. +type Extension struct { + // When an extension is stored in a message using SetExtension + // only desc and value are set. When the message is marshaled + // enc will be set to the encoded form of the message. + // + // When a message is unmarshaled and contains extensions, each + // extension will have only enc set. When such an extension is + // accessed using GetExtension (or GetExtensions) desc and value + // will be set. + desc *ExtensionDesc + value interface{} + enc []byte +} + +// SetRawExtension is for testing only. +func SetRawExtension(base extendableProto, id int32, b []byte) { + base.ExtensionMap()[id] = Extension{enc: b} +} + +// isExtensionField returns true iff the given field number is in an extension range. +func isExtensionField(pb extendableProto, field int32) bool { + for _, er := range pb.ExtensionRangeArray() { + if er.Start <= field && field <= er.End { + return true + } + } + return false +} + +// checkExtensionTypes checks that the given extension is valid for pb. +func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error { + // Check the extended type. + if a, b := reflect.TypeOf(pb), reflect.TypeOf(extension.ExtendedType); a != b { + return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String()) + } + // Check the range. + if !isExtensionField(pb, extension.Field) { + return errors.New("proto: bad extension number; not in declared ranges") + } + return nil +} + +// extPropKey is sufficient to uniquely identify an extension. +type extPropKey struct { + base reflect.Type + field int32 +} + +var extProp = struct { + sync.RWMutex + m map[extPropKey]*Properties +}{ + m: make(map[extPropKey]*Properties), +} + +func extensionProperties(ed *ExtensionDesc) *Properties { + key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field} + + extProp.RLock() + if prop, ok := extProp.m[key]; ok { + extProp.RUnlock() + return prop + } + extProp.RUnlock() + + extProp.Lock() + defer extProp.Unlock() + // Check again. + if prop, ok := extProp.m[key]; ok { + return prop + } + + prop := new(Properties) + prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil) + extProp.m[key] = prop + return prop +} + +// encodeExtensionMap encodes any unmarshaled (unencoded) extensions in m. +func encodeExtensionMap(m map[int32]Extension) error { + for k, e := range m { + if e.value == nil || e.desc == nil { + // Extension is only in its encoded form. + continue + } + + // We don't skip extensions that have an encoded form set, + // because the extension value may have been mutated after + // the last time this function was called. + + et := reflect.TypeOf(e.desc.ExtensionType) + props := extensionProperties(e.desc) + + p := NewBuffer(nil) + // If e.value has type T, the encoder expects a *struct{ X T }. + // Pass a *T with a zero field and hope it all works out. + x := reflect.New(et) + x.Elem().Set(reflect.ValueOf(e.value)) + if err := props.enc(p, props, toStructPointer(x)); err != nil { + return err + } + e.enc = p.buf + m[k] = e + } + return nil +} + +func sizeExtensionMap(m map[int32]Extension) (n int) { + for _, e := range m { + if e.value == nil || e.desc == nil { + // Extension is only in its encoded form. + n += len(e.enc) + continue + } + + // We don't skip extensions that have an encoded form set, + // because the extension value may have been mutated after + // the last time this function was called. + + et := reflect.TypeOf(e.desc.ExtensionType) + props := extensionProperties(e.desc) + + // If e.value has type T, the encoder expects a *struct{ X T }. + // Pass a *T with a zero field and hope it all works out. + x := reflect.New(et) + x.Elem().Set(reflect.ValueOf(e.value)) + n += props.size(props, toStructPointer(x)) + } + return +} + +// HasExtension returns whether the given extension is present in pb. +func HasExtension(pb extendableProto, extension *ExtensionDesc) bool { + // TODO: Check types, field numbers, etc.? + _, ok := pb.ExtensionMap()[extension.Field] + return ok +} + +// ClearExtension removes the given extension from pb. +func ClearExtension(pb extendableProto, extension *ExtensionDesc) { + // TODO: Check types, field numbers, etc.? + delete(pb.ExtensionMap(), extension.Field) +} + +// GetExtension parses and returns the given extension of pb. +// If the extension is not present it returns ErrMissingExtension. +func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, error) { + if err := checkExtensionTypes(pb, extension); err != nil { + return nil, err + } + + e, ok := pb.ExtensionMap()[extension.Field] + if !ok { + return nil, ErrMissingExtension + } + if e.value != nil { + // Already decoded. Check the descriptor, though. + if e.desc != extension { + // This shouldn't happen. If it does, it means that + // GetExtension was called twice with two different + // descriptors with the same field number. + return nil, errors.New("proto: descriptor conflict") + } + return e.value, nil + } + + v, err := decodeExtension(e.enc, extension) + if err != nil { + return nil, err + } + + // Remember the decoded version and drop the encoded version. + // That way it is safe to mutate what we return. + e.value = v + e.desc = extension + e.enc = nil + return e.value, nil +} + +// decodeExtension decodes an extension encoded in b. +func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) { + o := NewBuffer(b) + + t := reflect.TypeOf(extension.ExtensionType) + rep := extension.repeated() + + props := extensionProperties(extension) + + // t is a pointer to a struct, pointer to basic type or a slice. + // Allocate a "field" to store the pointer/slice itself; the + // pointer/slice will be stored here. We pass + // the address of this field to props.dec. + // This passes a zero field and a *t and lets props.dec + // interpret it as a *struct{ x t }. + value := reflect.New(t).Elem() + + for { + // Discard wire type and field number varint. It isn't needed. + if _, err := o.DecodeVarint(); err != nil { + return nil, err + } + + if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil { + return nil, err + } + + if !rep || o.index >= len(o.buf) { + break + } + } + return value.Interface(), nil +} + +// GetExtensions returns a slice of the extensions present in pb that are also listed in es. +// The returned slice has the same length as es; missing extensions will appear as nil elements. +func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) { + epb, ok := pb.(extendableProto) + if !ok { + err = errors.New("proto: not an extendable proto") + return + } + extensions = make([]interface{}, len(es)) + for i, e := range es { + extensions[i], err = GetExtension(epb, e) + if err == ErrMissingExtension { + err = nil + } + if err != nil { + return + } + } + return +} + +// SetExtension sets the specified extension of pb to the specified value. +func SetExtension(pb extendableProto, extension *ExtensionDesc, value interface{}) error { + if err := checkExtensionTypes(pb, extension); err != nil { + return err + } + typ := reflect.TypeOf(extension.ExtensionType) + if typ != reflect.TypeOf(value) { + return errors.New("proto: bad extension value type") + } + + pb.ExtensionMap()[extension.Field] = Extension{desc: extension, value: value} + return nil +} + +// A global registry of extensions. +// The generated code will register the generated descriptors by calling RegisterExtension. + +var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc) + +// RegisterExtension is called from the generated code. +func RegisterExtension(desc *ExtensionDesc) { + st := reflect.TypeOf(desc.ExtendedType).Elem() + m := extensionMaps[st] + if m == nil { + m = make(map[int32]*ExtensionDesc) + extensionMaps[st] = m + } + if _, ok := m[desc.Field]; ok { + panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field))) + } + m[desc.Field] = desc +} + +// RegisteredExtensions returns a map of the registered extensions of a +// protocol buffer struct, indexed by the extension number. +// The argument pb should be a nil pointer to the struct type. +func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc { + return extensionMaps[reflect.TypeOf(pb).Elem()] +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go new file mode 100644 index 0000000..03f756b --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go @@ -0,0 +1,60 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "testing" + + pb "./testdata" + "code.google.com/p/goprotobuf/proto" +) + +func TestGetExtensionsWithMissingExtensions(t *testing.T) { + msg := &pb.MyMessage{} + ext1 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil { + t.Fatalf("Could not set ext1: %s", ext1) + } + exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{ + pb.E_Ext_More, + pb.E_Ext_Text, + }) + if err != nil { + t.Fatalf("GetExtensions() failed: %s", err) + } + if exts[0] != ext1 { + t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0]) + } + if exts[1] != nil { + t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1]) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go new file mode 100644 index 0000000..46a4416 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/lib.go @@ -0,0 +1,740 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +/* + Package proto converts data structures to and from the wire format of + protocol buffers. It works in concert with the Go source code generated + for .proto files by the protocol compiler. + + A summary of the properties of the protocol buffer interface + for a protocol buffer variable v: + + - Names are turned from camel_case to CamelCase for export. + - There are no methods on v to set fields; just treat + them as structure fields. + - There are getters that return a field's value if set, + and return the field's default value if unset. + The getters work even if the receiver is a nil message. + - The zero value for a struct is its correct initialization state. + All desired fields must be set before marshaling. + - A Reset() method will restore a protobuf struct to its zero state. + - Non-repeated fields are pointers to the values; nil means unset. + That is, optional or required field int32 f becomes F *int32. + - Repeated fields are slices. + - Helper functions are available to aid the setting of fields. + Helpers for getting values are superseded by the + GetFoo methods and their use is deprecated. + msg.Foo = proto.String("hello") // set field + - Constants are defined to hold the default values of all fields that + have them. They have the form Default_StructName_FieldName. + Because the getter methods handle defaulted values, + direct use of these constants should be rare. + - Enums are given type names and maps from names to values. + Enum values are prefixed with the enum's type name. Enum types have + a String method, and a Enum method to assist in message construction. + - Nested groups and enums have type names prefixed with the name of + the surrounding message type. + - Extensions are given descriptor names that start with E_, + followed by an underscore-delimited list of the nested messages + that contain it (if any) followed by the CamelCased name of the + extension field itself. HasExtension, ClearExtension, GetExtension + and SetExtension are functions for manipulating extensions. + - Marshal and Unmarshal are functions to encode and decode the wire format. + + The simplest way to describe this is to see an example. + Given file test.proto, containing + + package example; + + enum FOO { X = 17; }; + + message Test { + required string label = 1; + optional int32 type = 2 [default=77]; + repeated int64 reps = 3; + optional group OptionalGroup = 4 { + required string RequiredField = 5; + } + } + + The resulting file, test.pb.go, is: + + package example + + import "code.google.com/p/goprotobuf/proto" + + type FOO int32 + const ( + FOO_X FOO = 17 + ) + var FOO_name = map[int32]string{ + 17: "X", + } + var FOO_value = map[string]int32{ + "X": 17, + } + + func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p + } + func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) + } + + type Test struct { + Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"` + Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"` + Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"` + Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` + } + func (this *Test) Reset() { *this = Test{} } + func (this *Test) String() string { return proto.CompactTextString(this) } + const Default_Test_Type int32 = 77 + + func (this *Test) GetLabel() string { + if this != nil && this.Label != nil { + return *this.Label + } + return "" + } + + func (this *Test) GetType() int32 { + if this != nil && this.Type != nil { + return *this.Type + } + return Default_Test_Type + } + + func (this *Test) GetOptionalgroup() *Test_OptionalGroup { + if this != nil { + return this.Optionalgroup + } + return nil + } + + type Test_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` + } + func (this *Test_OptionalGroup) Reset() { *this = Test_OptionalGroup{} } + func (this *Test_OptionalGroup) String() string { return proto.CompactTextString(this) } + + func (this *Test_OptionalGroup) GetRequiredField() string { + if this != nil && this.RequiredField != nil { + return *this.RequiredField + } + return "" + } + + func init() { + proto.RegisterEnum("example.FOO", FOO_name, FOO_value) + } + + To create and play with a Test object: + + package main + + import ( + "log" + + "code.google.com/p/goprotobuf/proto" + "./example.pb" + ) + + func main() { + test := &example.Test{ + Label: proto.String("hello"), + Type: proto.Int32(17), + Optionalgroup: &example.Test_OptionalGroup{ + RequiredField: proto.String("good bye"), + }, + } + data, err := proto.Marshal(test) + if err != nil { + log.Fatal("marshaling error: ", err) + } + newTest := new(example.Test) + err = proto.Unmarshal(data, newTest) + if err != nil { + log.Fatal("unmarshaling error: ", err) + } + // Now test and newTest contain the same data. + if test.GetLabel() != newTest.GetLabel() { + log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel()) + } + // etc. + } +*/ +package proto + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "strconv" + "sync" +) + +// Message is implemented by generated protocol buffer messages. +type Message interface { + Reset() + String() string + ProtoMessage() +} + +// Stats records allocation details about the protocol buffer encoders +// and decoders. Useful for tuning the library itself. +type Stats struct { + Emalloc uint64 // mallocs in encode + Dmalloc uint64 // mallocs in decode + Encode uint64 // number of encodes + Decode uint64 // number of decodes + Chit uint64 // number of cache hits + Cmiss uint64 // number of cache misses + Size uint64 // number of sizes +} + +// Set to true to enable stats collection. +const collectStats = false + +var stats Stats + +// GetStats returns a copy of the global Stats structure. +func GetStats() Stats { return stats } + +// A Buffer is a buffer manager for marshaling and unmarshaling +// protocol buffers. It may be reused between invocations to +// reduce memory usage. It is not necessary to use a Buffer; +// the global functions Marshal and Unmarshal create a +// temporary Buffer and are fine for most applications. +type Buffer struct { + buf []byte // encode/decode byte stream + index int // write point + + // pools of basic types to amortize allocation. + bools []bool + uint32s []uint32 + uint64s []uint64 + + // extra pools, only used with pointer_reflect.go + int32s []int32 + int64s []int64 + float32s []float32 + float64s []float64 +} + +// NewBuffer allocates a new Buffer and initializes its internal data to +// the contents of the argument slice. +func NewBuffer(e []byte) *Buffer { + return &Buffer{buf: e} +} + +// Reset resets the Buffer, ready for marshaling a new protocol buffer. +func (p *Buffer) Reset() { + p.buf = p.buf[0:0] // for reading/writing + p.index = 0 // for reading +} + +// SetBuf replaces the internal buffer with the slice, +// ready for unmarshaling the contents of the slice. +func (p *Buffer) SetBuf(s []byte) { + p.buf = s + p.index = 0 +} + +// Bytes returns the contents of the Buffer. +func (p *Buffer) Bytes() []byte { return p.buf } + +/* + * Helper routines for simplifying the creation of optional fields of basic type. + */ + +// Bool is a helper routine that allocates a new bool value +// to store v and returns a pointer to it. +func Bool(v bool) *bool { + return &v +} + +// Int32 is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it. +func Int32(v int32) *int32 { + return &v +} + +// Int is a helper routine that allocates a new int32 value +// to store v and returns a pointer to it, but unlike Int32 +// its argument value is an int. +func Int(v int) *int32 { + p := new(int32) + *p = int32(v) + return p +} + +// Int64 is a helper routine that allocates a new int64 value +// to store v and returns a pointer to it. +func Int64(v int64) *int64 { + return &v +} + +// Float32 is a helper routine that allocates a new float32 value +// to store v and returns a pointer to it. +func Float32(v float32) *float32 { + return &v +} + +// Float64 is a helper routine that allocates a new float64 value +// to store v and returns a pointer to it. +func Float64(v float64) *float64 { + return &v +} + +// Uint32 is a helper routine that allocates a new uint32 value +// to store v and returns a pointer to it. +func Uint32(v uint32) *uint32 { + p := new(uint32) + *p = v + return p +} + +// Uint64 is a helper routine that allocates a new uint64 value +// to store v and returns a pointer to it. +func Uint64(v uint64) *uint64 { + return &v +} + +// String is a helper routine that allocates a new string value +// to store v and returns a pointer to it. +func String(v string) *string { + return &v +} + +// EnumName is a helper function to simplify printing protocol buffer enums +// by name. Given an enum map and a value, it returns a useful string. +func EnumName(m map[int32]string, v int32) string { + s, ok := m[v] + if ok { + return s + } + return strconv.Itoa(int(v)) +} + +// UnmarshalJSONEnum is a helper function to simplify recovering enum int values +// from their JSON-encoded representation. Given a map from the enum's symbolic +// names to its int values, and a byte buffer containing the JSON-encoded +// value, it returns an int32 that can be cast to the enum type by the caller. +// +// The function can deal with both JSON representations, numeric and symbolic. +func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) { + if data[0] == '"' { + // New style: enums are strings. + var repr string + if err := json.Unmarshal(data, &repr); err != nil { + return -1, err + } + val, ok := m[repr] + if !ok { + return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr) + } + return val, nil + } + // Old style: enums are ints. + var val int32 + if err := json.Unmarshal(data, &val); err != nil { + return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName) + } + return val, nil +} + +// DebugPrint dumps the encoded data in b in a debugging format with a header +// including the string s. Used in testing but made available for general debugging. +func (o *Buffer) DebugPrint(s string, b []byte) { + var u uint64 + + obuf := o.buf + index := o.index + o.buf = b + o.index = 0 + depth := 0 + + fmt.Printf("\n--- %s ---\n", s) + +out: + for { + for i := 0; i < depth; i++ { + fmt.Print(" ") + } + + index := o.index + if index == len(o.buf) { + break + } + + op, err := o.DecodeVarint() + if err != nil { + fmt.Printf("%3d: fetching op err %v\n", index, err) + break out + } + tag := op >> 3 + wire := op & 7 + + switch wire { + default: + fmt.Printf("%3d: t=%3d unknown wire=%d\n", + index, tag, wire) + break out + + case WireBytes: + var r []byte + + r, err = o.DecodeRawBytes(false) + if err != nil { + break out + } + fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r)) + if len(r) <= 6 { + for i := 0; i < len(r); i++ { + fmt.Printf(" %.2x", r[i]) + } + } else { + for i := 0; i < 3; i++ { + fmt.Printf(" %.2x", r[i]) + } + fmt.Printf(" ..") + for i := len(r) - 3; i < len(r); i++ { + fmt.Printf(" %.2x", r[i]) + } + } + fmt.Printf("\n") + + case WireFixed32: + u, err = o.DecodeFixed32() + if err != nil { + fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u) + + case WireFixed64: + u, err = o.DecodeFixed64() + if err != nil { + fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u) + break + + case WireVarint: + u, err = o.DecodeVarint() + if err != nil { + fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u) + + case WireStartGroup: + if err != nil { + fmt.Printf("%3d: t=%3d start err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d start\n", index, tag) + depth++ + + case WireEndGroup: + depth-- + if err != nil { + fmt.Printf("%3d: t=%3d end err %v\n", index, tag, err) + break out + } + fmt.Printf("%3d: t=%3d end\n", index, tag) + } + } + + if depth != 0 { + fmt.Printf("%3d: start-end not balanced %d\n", o.index, depth) + } + fmt.Printf("\n") + + o.buf = obuf + o.index = index +} + +// SetDefaults sets unset protocol buffer fields to their default values. +// It only modifies fields that are both unset and have defined defaults. +// It recursively sets default values in any non-nil sub-messages. +func SetDefaults(pb Message) { + setDefaults(reflect.ValueOf(pb), true, false) +} + +// v is a pointer to a struct. +func setDefaults(v reflect.Value, recur, zeros bool) { + v = v.Elem() + + defaultMu.RLock() + dm, ok := defaults[v.Type()] + defaultMu.RUnlock() + if !ok { + dm = buildDefaultMessage(v.Type()) + defaultMu.Lock() + defaults[v.Type()] = dm + defaultMu.Unlock() + } + + for _, sf := range dm.scalars { + f := v.Field(sf.index) + if !f.IsNil() { + // field already set + continue + } + dv := sf.value + if dv == nil && !zeros { + // no explicit default, and don't want to set zeros + continue + } + fptr := f.Addr().Interface() // **T + // TODO: Consider batching the allocations we do here. + switch sf.kind { + case reflect.Bool: + b := new(bool) + if dv != nil { + *b = dv.(bool) + } + *(fptr.(**bool)) = b + case reflect.Float32: + f := new(float32) + if dv != nil { + *f = dv.(float32) + } + *(fptr.(**float32)) = f + case reflect.Float64: + f := new(float64) + if dv != nil { + *f = dv.(float64) + } + *(fptr.(**float64)) = f + case reflect.Int32: + // might be an enum + if ft := f.Type(); ft != int32PtrType { + // enum + f.Set(reflect.New(ft.Elem())) + if dv != nil { + f.Elem().SetInt(int64(dv.(int32))) + } + } else { + // int32 field + i := new(int32) + if dv != nil { + *i = dv.(int32) + } + *(fptr.(**int32)) = i + } + case reflect.Int64: + i := new(int64) + if dv != nil { + *i = dv.(int64) + } + *(fptr.(**int64)) = i + case reflect.String: + s := new(string) + if dv != nil { + *s = dv.(string) + } + *(fptr.(**string)) = s + case reflect.Uint8: + // exceptional case: []byte + var b []byte + if dv != nil { + db := dv.([]byte) + b = make([]byte, len(db)) + copy(b, db) + } else { + b = []byte{} + } + *(fptr.(*[]byte)) = b + case reflect.Uint32: + u := new(uint32) + if dv != nil { + *u = dv.(uint32) + } + *(fptr.(**uint32)) = u + case reflect.Uint64: + u := new(uint64) + if dv != nil { + *u = dv.(uint64) + } + *(fptr.(**uint64)) = u + default: + log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind) + } + } + + for _, ni := range dm.nested { + f := v.Field(ni) + if f.IsNil() { + continue + } + // f is *T or []*T + if f.Kind() == reflect.Ptr { + setDefaults(f, recur, zeros) + } else { + for i := 0; i < f.Len(); i++ { + e := f.Index(i) + if e.IsNil() { + continue + } + setDefaults(e, recur, zeros) + } + } + } +} + +var ( + // defaults maps a protocol buffer struct type to a slice of the fields, + // with its scalar fields set to their proto-declared non-zero default values. + defaultMu sync.RWMutex + defaults = make(map[reflect.Type]defaultMessage) + + int32PtrType = reflect.TypeOf((*int32)(nil)) +) + +// defaultMessage represents information about the default values of a message. +type defaultMessage struct { + scalars []scalarField + nested []int // struct field index of nested messages +} + +type scalarField struct { + index int // struct field index + kind reflect.Kind // element type (the T in *T or []T) + value interface{} // the proto-declared default value, or nil +} + +func ptrToStruct(t reflect.Type) bool { + return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct +} + +// t is a struct type. +func buildDefaultMessage(t reflect.Type) (dm defaultMessage) { + sprop := GetProperties(t) + for _, prop := range sprop.Prop { + fi, ok := sprop.decoderTags.get(prop.Tag) + if !ok { + // XXX_unrecognized + continue + } + ft := t.Field(fi).Type + + // nested messages + if ptrToStruct(ft) || (ft.Kind() == reflect.Slice && ptrToStruct(ft.Elem())) { + dm.nested = append(dm.nested, fi) + continue + } + + sf := scalarField{ + index: fi, + kind: ft.Elem().Kind(), + } + + // scalar fields without defaults + if !prop.HasDefault { + dm.scalars = append(dm.scalars, sf) + continue + } + + // a scalar field: either *T or []byte + switch ft.Elem().Kind() { + case reflect.Bool: + x, err := strconv.ParseBool(prop.Default) + if err != nil { + log.Printf("proto: bad default bool %q: %v", prop.Default, err) + continue + } + sf.value = x + case reflect.Float32: + x, err := strconv.ParseFloat(prop.Default, 32) + if err != nil { + log.Printf("proto: bad default float32 %q: %v", prop.Default, err) + continue + } + sf.value = float32(x) + case reflect.Float64: + x, err := strconv.ParseFloat(prop.Default, 64) + if err != nil { + log.Printf("proto: bad default float64 %q: %v", prop.Default, err) + continue + } + sf.value = x + case reflect.Int32: + x, err := strconv.ParseInt(prop.Default, 10, 32) + if err != nil { + log.Printf("proto: bad default int32 %q: %v", prop.Default, err) + continue + } + sf.value = int32(x) + case reflect.Int64: + x, err := strconv.ParseInt(prop.Default, 10, 64) + if err != nil { + log.Printf("proto: bad default int64 %q: %v", prop.Default, err) + continue + } + sf.value = x + case reflect.String: + sf.value = prop.Default + case reflect.Uint8: + // []byte (not *uint8) + sf.value = []byte(prop.Default) + case reflect.Uint32: + x, err := strconv.ParseUint(prop.Default, 10, 32) + if err != nil { + log.Printf("proto: bad default uint32 %q: %v", prop.Default, err) + continue + } + sf.value = uint32(x) + case reflect.Uint64: + x, err := strconv.ParseUint(prop.Default, 10, 64) + if err != nil { + log.Printf("proto: bad default uint64 %q: %v", prop.Default, err) + continue + } + sf.value = x + default: + log.Printf("proto: unhandled def kind %v", ft.Elem().Kind()) + continue + } + + dm.scalars = append(dm.scalars, sf) + } + + return dm +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go new file mode 100644 index 0000000..1a17809 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go @@ -0,0 +1,229 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Support for message sets. + */ + +import ( + "errors" + "reflect" + "sort" +) + +// ErrNoMessageTypeId occurs when a protocol buffer does not have a message type ID. +// A message type ID is required for storing a protocol buffer in a message set. +var ErrNoMessageTypeId = errors.New("proto does not have a message type ID") + +// The first two types (_MessageSet_Item and MessageSet) +// model what the protocol compiler produces for the following protocol message: +// message MessageSet { +// repeated group Item = 1 { +// required int32 type_id = 2; +// required string message = 3; +// }; +// } +// That is the MessageSet wire format. We can't use a proto to generate these +// because that would introduce a circular dependency between it and this package. +// +// When a proto1 proto has a field that looks like: +// optional message info = 3; +// the protocol compiler produces a field in the generated struct that looks like: +// Info *_proto_.MessageSet `protobuf:"bytes,3,opt,name=info"` +// The package is automatically inserted so there is no need for that proto file to +// import this package. + +type _MessageSet_Item struct { + TypeId *int32 `protobuf:"varint,2,req,name=type_id"` + Message []byte `protobuf:"bytes,3,req,name=message"` +} + +type MessageSet struct { + Item []*_MessageSet_Item `protobuf:"group,1,rep"` + XXX_unrecognized []byte + // TODO: caching? +} + +// Make sure MessageSet is a Message. +var _ Message = (*MessageSet)(nil) + +// messageTypeIder is an interface satisfied by a protocol buffer type +// that may be stored in a MessageSet. +type messageTypeIder interface { + MessageTypeId() int32 +} + +func (ms *MessageSet) find(pb Message) *_MessageSet_Item { + mti, ok := pb.(messageTypeIder) + if !ok { + return nil + } + id := mti.MessageTypeId() + for _, item := range ms.Item { + if *item.TypeId == id { + return item + } + } + return nil +} + +func (ms *MessageSet) Has(pb Message) bool { + if ms.find(pb) != nil { + return true + } + return false +} + +func (ms *MessageSet) Unmarshal(pb Message) error { + if item := ms.find(pb); item != nil { + return Unmarshal(item.Message, pb) + } + if _, ok := pb.(messageTypeIder); !ok { + return ErrNoMessageTypeId + } + return nil // TODO: return error instead? +} + +func (ms *MessageSet) Marshal(pb Message) error { + msg, err := Marshal(pb) + if err != nil { + return err + } + if item := ms.find(pb); item != nil { + // reuse existing item + item.Message = msg + return nil + } + + mti, ok := pb.(messageTypeIder) + if !ok { + return ErrNoMessageTypeId + } + + mtid := mti.MessageTypeId() + ms.Item = append(ms.Item, &_MessageSet_Item{ + TypeId: &mtid, + Message: msg, + }) + return nil +} + +func (ms *MessageSet) Reset() { *ms = MessageSet{} } +func (ms *MessageSet) String() string { return CompactTextString(ms) } +func (*MessageSet) ProtoMessage() {} + +// Support for the message_set_wire_format message option. + +func skipVarint(buf []byte) []byte { + i := 0 + for ; buf[i]&0x80 != 0; i++ { + } + return buf[i+1:] +} + +// MarshalMessageSet encodes the extension map represented by m in the message set wire format. +// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option. +func MarshalMessageSet(m map[int32]Extension) ([]byte, error) { + if err := encodeExtensionMap(m); err != nil { + return nil, err + } + + // Sort extension IDs to provide a deterministic encoding. + // See also enc_map in encode.go. + ids := make([]int, 0, len(m)) + for id := range m { + ids = append(ids, int(id)) + } + sort.Ints(ids) + + ms := &MessageSet{Item: make([]*_MessageSet_Item, 0, len(m))} + for _, id := range ids { + e := m[int32(id)] + // Remove the wire type and field number varint, as well as the length varint. + msg := skipVarint(skipVarint(e.enc)) + + ms.Item = append(ms.Item, &_MessageSet_Item{ + TypeId: Int32(int32(id)), + Message: msg, + }) + } + return Marshal(ms) +} + +// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format. +// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option. +func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error { + ms := new(MessageSet) + if err := Unmarshal(buf, ms); err != nil { + return err + } + for _, item := range ms.Item { + id := *item.TypeId + msg := item.Message + + // Restore wire type and field number varint, plus length varint. + // Be careful to preserve duplicate items. + b := EncodeVarint(uint64(id)<<3 | WireBytes) + if ext, ok := m[id]; ok { + // Existing data; rip off the tag and length varint + // so we join the new data correctly. + // We can assume that ext.enc is set because we are unmarshaling. + o := ext.enc[len(b):] // skip wire type and field number + _, n := DecodeVarint(o) // calculate length of length varint + o = o[n:] // skip length varint + msg = append(o, msg...) // join old data and new data + } + b = append(b, EncodeVarint(uint64(len(msg)))...) + b = append(b, msg...) + + m[id] = Extension{enc: b} + } + return nil +} + +// A global registry of types that can be used in a MessageSet. + +var messageSetMap = make(map[int32]messageSetDesc) + +type messageSetDesc struct { + t reflect.Type // pointer to struct + name string +} + +// RegisterMessageSetType is called from the generated code. +func RegisterMessageSetType(i messageTypeIder, name string) { + messageSetMap[i.MessageTypeId()] = messageSetDesc{ + t: reflect.TypeOf(i), + name: name, + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go new file mode 100644 index 0000000..bb311bc --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set_test.go @@ -0,0 +1,66 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2014 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +import ( + "bytes" + "testing" +) + +func TestUnmarshalMessageSetWithDuplicate(t *testing.T) { + // Check that a repeated message set entry will be concatenated. + in := &MessageSet{ + Item: []*_MessageSet_Item{ + {TypeId: Int32(12345), Message: []byte("hoo")}, + {TypeId: Int32(12345), Message: []byte("hah")}, + }, + } + b, err := Marshal(in) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + t.Logf("Marshaled bytes: %q", b) + + m := make(map[int32]Extension) + if err := UnmarshalMessageSet(b, m); err != nil { + t.Fatalf("UnmarshalMessageSet: %v", err) + } + ext, ok := m[12345] + if !ok { + t.Fatalf("Didn't retrieve extension 12345; map is %v", m) + } + // Skip wire type/field number and length varints. + got := skipVarint(skipVarint(ext.enc)) + if want := []byte("hoohah"); !bytes.Equal(got, want) { + t.Errorf("Combined extension is %q, want %q", got, want) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go new file mode 100644 index 0000000..61141ba --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_reflect.go @@ -0,0 +1,384 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build appengine,!appenginevm + +// This file contains an implementation of proto field accesses using package reflect. +// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can +// be used on App Engine. + +package proto + +import ( + "math" + "reflect" +) + +// A structPointer is a pointer to a struct. +type structPointer struct { + v reflect.Value +} + +// toStructPointer returns a structPointer equivalent to the given reflect value. +// The reflect value must itself be a pointer to a struct. +func toStructPointer(v reflect.Value) structPointer { + return structPointer{v} +} + +// IsNil reports whether p is nil. +func structPointer_IsNil(p structPointer) bool { + return p.v.IsNil() +} + +// Interface returns the struct pointer as an interface value. +func structPointer_Interface(p structPointer, _ reflect.Type) interface{} { + return p.v.Interface() +} + +// A field identifies a field in a struct, accessible from a structPointer. +// In this implementation, a field is identified by the sequence of field indices +// passed to reflect's FieldByIndex. +type field []int + +// toField returns a field equivalent to the given reflect field. +func toField(f *reflect.StructField) field { + return f.Index +} + +// invalidField is an invalid field identifier. +var invalidField = field(nil) + +// IsValid reports whether the field identifier is valid. +func (f field) IsValid() bool { return f != nil } + +// field returns the given field in the struct as a reflect value. +func structPointer_field(p structPointer, f field) reflect.Value { + // Special case: an extension map entry with a value of type T + // passes a *T to the struct-handling code with a zero field, + // expecting that it will be treated as equivalent to *struct{ X T }, + // which has the same memory layout. We have to handle that case + // specially, because reflect will panic if we call FieldByIndex on a + // non-struct. + if f == nil { + return p.v.Elem() + } + + return p.v.Elem().FieldByIndex(f) +} + +// ifield returns the given field in the struct as an interface value. +func structPointer_ifield(p structPointer, f field) interface{} { + return structPointer_field(p, f).Addr().Interface() +} + +// Bytes returns the address of a []byte field in the struct. +func structPointer_Bytes(p structPointer, f field) *[]byte { + return structPointer_ifield(p, f).(*[]byte) +} + +// BytesSlice returns the address of a [][]byte field in the struct. +func structPointer_BytesSlice(p structPointer, f field) *[][]byte { + return structPointer_ifield(p, f).(*[][]byte) +} + +// Bool returns the address of a *bool field in the struct. +func structPointer_Bool(p structPointer, f field) **bool { + return structPointer_ifield(p, f).(**bool) +} + +// BoolSlice returns the address of a []bool field in the struct. +func structPointer_BoolSlice(p structPointer, f field) *[]bool { + return structPointer_ifield(p, f).(*[]bool) +} + +// String returns the address of a *string field in the struct. +func structPointer_String(p structPointer, f field) **string { + return structPointer_ifield(p, f).(**string) +} + +// StringSlice returns the address of a []string field in the struct. +func structPointer_StringSlice(p structPointer, f field) *[]string { + return structPointer_ifield(p, f).(*[]string) +} + +// ExtMap returns the address of an extension map field in the struct. +func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { + return structPointer_ifield(p, f).(*map[int32]Extension) +} + +// SetStructPointer writes a *struct field in the struct. +func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { + structPointer_field(p, f).Set(q.v) +} + +// GetStructPointer reads a *struct field in the struct. +func structPointer_GetStructPointer(p structPointer, f field) structPointer { + return structPointer{structPointer_field(p, f)} +} + +// StructPointerSlice the address of a []*struct field in the struct. +func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice { + return structPointerSlice{structPointer_field(p, f)} +} + +// A structPointerSlice represents the address of a slice of pointers to structs +// (themselves messages or groups). That is, v.Type() is *[]*struct{...}. +type structPointerSlice struct { + v reflect.Value +} + +func (p structPointerSlice) Len() int { return p.v.Len() } +func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} } +func (p structPointerSlice) Append(q structPointer) { + p.v.Set(reflect.Append(p.v, q.v)) +} + +var ( + int32Type = reflect.TypeOf(int32(0)) + uint32Type = reflect.TypeOf(uint32(0)) + float32Type = reflect.TypeOf(float32(0)) + int64Type = reflect.TypeOf(int64(0)) + uint64Type = reflect.TypeOf(uint64(0)) + float64Type = reflect.TypeOf(float64(0)) +) + +// A word32 represents a field of type *int32, *uint32, *float32, or *enum. +// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable. +type word32 struct { + v reflect.Value +} + +// IsNil reports whether p is nil. +func word32_IsNil(p word32) bool { + return p.v.IsNil() +} + +// Set sets p to point at a newly allocated word with bits set to x. +func word32_Set(p word32, o *Buffer, x uint32) { + t := p.v.Type().Elem() + switch t { + case int32Type: + if len(o.int32s) == 0 { + o.int32s = make([]int32, uint32PoolSize) + } + o.int32s[0] = int32(x) + p.v.Set(reflect.ValueOf(&o.int32s[0])) + o.int32s = o.int32s[1:] + return + case uint32Type: + if len(o.uint32s) == 0 { + o.uint32s = make([]uint32, uint32PoolSize) + } + o.uint32s[0] = x + p.v.Set(reflect.ValueOf(&o.uint32s[0])) + o.uint32s = o.uint32s[1:] + return + case float32Type: + if len(o.float32s) == 0 { + o.float32s = make([]float32, uint32PoolSize) + } + o.float32s[0] = math.Float32frombits(x) + p.v.Set(reflect.ValueOf(&o.float32s[0])) + o.float32s = o.float32s[1:] + return + } + + // must be enum + p.v.Set(reflect.New(t)) + p.v.Elem().SetInt(int64(int32(x))) +} + +// Get gets the bits pointed at by p, as a uint32. +func word32_Get(p word32) uint32 { + elem := p.v.Elem() + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32(p structPointer, f field) word32 { + return word32{structPointer_field(p, f)} +} + +// A word32Slice is a slice of 32-bit values. +// That is, v.Type() is []int32, []uint32, []float32, or []enum. +type word32Slice struct { + v reflect.Value +} + +func (p word32Slice) Append(x uint32) { + n, m := p.v.Len(), p.v.Cap() + if n < m { + p.v.SetLen(n + 1) + } else { + t := p.v.Type().Elem() + p.v.Set(reflect.Append(p.v, reflect.Zero(t))) + } + elem := p.v.Index(n) + switch elem.Kind() { + case reflect.Int32: + elem.SetInt(int64(int32(x))) + case reflect.Uint32: + elem.SetUint(uint64(x)) + case reflect.Float32: + elem.SetFloat(float64(math.Float32frombits(x))) + } +} + +func (p word32Slice) Len() int { + return p.v.Len() +} + +func (p word32Slice) Index(i int) uint32 { + elem := p.v.Index(i) + switch elem.Kind() { + case reflect.Int32: + return uint32(elem.Int()) + case reflect.Uint32: + return uint32(elem.Uint()) + case reflect.Float32: + return math.Float32bits(float32(elem.Float())) + } + panic("unreachable") +} + +// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct. +func structPointer_Word32Slice(p structPointer, f field) word32Slice { + return word32Slice{structPointer_field(p, f)} +} + +// word64 is like word32 but for 64-bit values. +type word64 struct { + v reflect.Value +} + +func word64_Set(p word64, o *Buffer, x uint64) { + t := p.v.Type().Elem() + switch t { + case int64Type: + if len(o.int64s) == 0 { + o.int64s = make([]int64, uint64PoolSize) + } + o.int64s[0] = int64(x) + p.v.Set(reflect.ValueOf(&o.int64s[0])) + o.int64s = o.int64s[1:] + return + case uint64Type: + if len(o.uint64s) == 0 { + o.uint64s = make([]uint64, uint64PoolSize) + } + o.uint64s[0] = x + p.v.Set(reflect.ValueOf(&o.uint64s[0])) + o.uint64s = o.uint64s[1:] + return + case float64Type: + if len(o.float64s) == 0 { + o.float64s = make([]float64, uint64PoolSize) + } + o.float64s[0] = math.Float64frombits(x) + p.v.Set(reflect.ValueOf(&o.float64s[0])) + o.float64s = o.float64s[1:] + return + } + panic("unreachable") +} + +func word64_IsNil(p word64) bool { + return p.v.IsNil() +} + +func word64_Get(p word64) uint64 { + elem := p.v.Elem() + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return elem.Uint() + case reflect.Float64: + return math.Float64bits(elem.Float()) + } + panic("unreachable") +} + +func structPointer_Word64(p structPointer, f field) word64 { + return word64{structPointer_field(p, f)} +} + +type word64Slice struct { + v reflect.Value +} + +func (p word64Slice) Append(x uint64) { + n, m := p.v.Len(), p.v.Cap() + if n < m { + p.v.SetLen(n + 1) + } else { + t := p.v.Type().Elem() + p.v.Set(reflect.Append(p.v, reflect.Zero(t))) + } + elem := p.v.Index(n) + switch elem.Kind() { + case reflect.Int64: + elem.SetInt(int64(int64(x))) + case reflect.Uint64: + elem.SetUint(uint64(x)) + case reflect.Float64: + elem.SetFloat(float64(math.Float64frombits(x))) + } +} + +func (p word64Slice) Len() int { + return p.v.Len() +} + +func (p word64Slice) Index(i int) uint64 { + elem := p.v.Index(i) + switch elem.Kind() { + case reflect.Int64: + return uint64(elem.Int()) + case reflect.Uint64: + return uint64(elem.Uint()) + case reflect.Float64: + return math.Float64bits(float64(elem.Float())) + } + panic("unreachable") +} + +func structPointer_Word64Slice(p structPointer, f field) word64Slice { + return word64Slice{structPointer_field(p, f)} +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go new file mode 100644 index 0000000..27a536c --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/pointer_unsafe.go @@ -0,0 +1,218 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// +build !appengine appenginevm + +// This file contains the implementation of the proto field accesses using package unsafe. + +package proto + +import ( + "reflect" + "unsafe" +) + +// NOTE: These type_Foo functions would more idiomatically be methods, +// but Go does not allow methods on pointer types, and we must preserve +// some pointer type for the garbage collector. We use these +// funcs with clunky names as our poor approximation to methods. +// +// An alternative would be +// type structPointer struct { p unsafe.Pointer } +// but that does not registerize as well. + +// A structPointer is a pointer to a struct. +type structPointer unsafe.Pointer + +// toStructPointer returns a structPointer equivalent to the given reflect value. +func toStructPointer(v reflect.Value) structPointer { + return structPointer(unsafe.Pointer(v.Pointer())) +} + +// IsNil reports whether p is nil. +func structPointer_IsNil(p structPointer) bool { + return p == nil +} + +// Interface returns the struct pointer, assumed to have element type t, +// as an interface value. +func structPointer_Interface(p structPointer, t reflect.Type) interface{} { + return reflect.NewAt(t, unsafe.Pointer(p)).Interface() +} + +// A field identifies a field in a struct, accessible from a structPointer. +// In this implementation, a field is identified by its byte offset from the start of the struct. +type field uintptr + +// toField returns a field equivalent to the given reflect field. +func toField(f *reflect.StructField) field { + return field(f.Offset) +} + +// invalidField is an invalid field identifier. +const invalidField = ^field(0) + +// IsValid reports whether the field identifier is valid. +func (f field) IsValid() bool { + return f != ^field(0) +} + +// Bytes returns the address of a []byte field in the struct. +func structPointer_Bytes(p structPointer, f field) *[]byte { + return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// BytesSlice returns the address of a [][]byte field in the struct. +func structPointer_BytesSlice(p structPointer, f field) *[][]byte { + return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// Bool returns the address of a *bool field in the struct. +func structPointer_Bool(p structPointer, f field) **bool { + return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// BoolSlice returns the address of a []bool field in the struct. +func structPointer_BoolSlice(p structPointer, f field) *[]bool { + return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// String returns the address of a *string field in the struct. +func structPointer_String(p structPointer, f field) **string { + return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// StringSlice returns the address of a []string field in the struct. +func structPointer_StringSlice(p structPointer, f field) *[]string { + return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// ExtMap returns the address of an extension map field in the struct. +func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension { + return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// SetStructPointer writes a *struct field in the struct. +func structPointer_SetStructPointer(p structPointer, f field, q structPointer) { + *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q +} + +// GetStructPointer reads a *struct field in the struct. +func structPointer_GetStructPointer(p structPointer, f field) structPointer { + return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// StructPointerSlice the address of a []*struct field in the struct. +func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice { + return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups). +type structPointerSlice []structPointer + +func (v *structPointerSlice) Len() int { return len(*v) } +func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] } +func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) } + +// A word32 is the address of a "pointer to 32-bit value" field. +type word32 **uint32 + +// IsNil reports whether *v is nil. +func word32_IsNil(p word32) bool { + return *p == nil +} + +// Set sets *v to point at a newly allocated word set to x. +func word32_Set(p word32, o *Buffer, x uint32) { + if len(o.uint32s) == 0 { + o.uint32s = make([]uint32, uint32PoolSize) + } + o.uint32s[0] = x + *p = &o.uint32s[0] + o.uint32s = o.uint32s[1:] +} + +// Get gets the value pointed at by *v. +func word32_Get(p word32) uint32 { + return **p +} + +// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct. +func structPointer_Word32(p structPointer, f field) word32 { + return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// A word32Slice is a slice of 32-bit values. +type word32Slice []uint32 + +func (v *word32Slice) Append(x uint32) { *v = append(*v, x) } +func (v *word32Slice) Len() int { return len(*v) } +func (v *word32Slice) Index(i int) uint32 { return (*v)[i] } + +// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct. +func structPointer_Word32Slice(p structPointer, f field) *word32Slice { + return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} + +// word64 is like word32 but for 64-bit values. +type word64 **uint64 + +func word64_Set(p word64, o *Buffer, x uint64) { + if len(o.uint64s) == 0 { + o.uint64s = make([]uint64, uint64PoolSize) + } + o.uint64s[0] = x + *p = &o.uint64s[0] + o.uint64s = o.uint64s[1:] +} + +func word64_IsNil(p word64) bool { + return *p == nil +} + +func word64_Get(p word64) uint64 { + return **p +} + +func structPointer_Word64(p structPointer, f field) word64 { + return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f)))) +} + +// word64Slice is like word32Slice but for 64-bit values. +type word64Slice []uint64 + +func (v *word64Slice) Append(x uint64) { *v = append(*v, x) } +func (v *word64Slice) Len() int { return len(*v) } +func (v *word64Slice) Index(i int) uint64 { return (*v)[i] } + +func structPointer_Word64Slice(p structPointer, f field) *word64Slice { + return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f))) +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go new file mode 100644 index 0000000..f2c1e86 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go @@ -0,0 +1,658 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +/* + * Routines for encoding data into the wire format for protocol buffers. + */ + +import ( + "fmt" + "os" + "reflect" + "sort" + "strconv" + "strings" + "sync" +) + +const debug bool = false + +// Constants that identify the encoding of a value on the wire. +const ( + WireVarint = 0 + WireFixed64 = 1 + WireBytes = 2 + WireStartGroup = 3 + WireEndGroup = 4 + WireFixed32 = 5 +) + +const startSize = 10 // initial slice/string sizes + +// Encoders are defined in encode.go +// An encoder outputs the full representation of a field, including its +// tag and encoder type. +type encoder func(p *Buffer, prop *Properties, base structPointer) error + +// A valueEncoder encodes a single integer in a particular encoding. +type valueEncoder func(o *Buffer, x uint64) error + +// Sizers are defined in encode.go +// A sizer returns the encoded size of a field, including its tag and encoder +// type. +type sizer func(prop *Properties, base structPointer) int + +// A valueSizer returns the encoded size of a single integer in a particular +// encoding. +type valueSizer func(x uint64) int + +// Decoders are defined in decode.go +// A decoder creates a value from its wire representation. +// Unrecognized subelements are saved in unrec. +type decoder func(p *Buffer, prop *Properties, base structPointer) error + +// A valueDecoder decodes a single integer in a particular encoding. +type valueDecoder func(o *Buffer) (x uint64, err error) + +// tagMap is an optimization over map[int]int for typical protocol buffer +// use-cases. Encoded protocol buffers are often in tag order with small tag +// numbers. +type tagMap struct { + fastTags []int + slowTags map[int]int +} + +// tagMapFastLimit is the upper bound on the tag number that will be stored in +// the tagMap slice rather than its map. +const tagMapFastLimit = 1024 + +func (p *tagMap) get(t int) (int, bool) { + if t > 0 && t < tagMapFastLimit { + if t >= len(p.fastTags) { + return 0, false + } + fi := p.fastTags[t] + return fi, fi >= 0 + } + fi, ok := p.slowTags[t] + return fi, ok +} + +func (p *tagMap) put(t int, fi int) { + if t > 0 && t < tagMapFastLimit { + for len(p.fastTags) < t+1 { + p.fastTags = append(p.fastTags, -1) + } + p.fastTags[t] = fi + return + } + if p.slowTags == nil { + p.slowTags = make(map[int]int) + } + p.slowTags[t] = fi +} + +// StructProperties represents properties for all the fields of a struct. +// decoderTags and decoderOrigNames should only be used by the decoder. +type StructProperties struct { + Prop []*Properties // properties for each field + reqCount int // required count + decoderTags tagMap // map from proto tag to struct field number + decoderOrigNames map[string]int // map from original name to struct field number + order []int // list of struct field numbers in tag order + unrecField field // field id of the XXX_unrecognized []byte field + extendable bool // is this an extendable proto +} + +// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec. +// See encode.go, (*Buffer).enc_struct. + +func (sp *StructProperties) Len() int { return len(sp.order) } +func (sp *StructProperties) Less(i, j int) bool { + return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag +} +func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] } + +// Properties represents the protocol-specific behavior of a single struct field. +type Properties struct { + Name string // name of the field, for error messages + OrigName string // original name before protocol compiler (always set) + Wire string + WireType int + Tag int + Required bool + Optional bool + Repeated bool + Packed bool // relevant for repeated primitives only + Enum string // set for enum types only + + Default string // default value + HasDefault bool // whether an explicit default was provided + def_uint64 uint64 + + enc encoder + valEnc valueEncoder // set for bool and numeric types only + field field + tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType) + tagbuf [8]byte + stype reflect.Type // set for struct types only + sprop *StructProperties // set for struct types only + isMarshaler bool + isUnmarshaler bool + + size sizer + valSize valueSizer // set for bool and numeric types only + + dec decoder + valDec valueDecoder // set for bool and numeric types only + + // If this is a packable field, this will be the decoder for the packed version of the field. + packedDec decoder +} + +// String formats the properties in the protobuf struct field tag style. +func (p *Properties) String() string { + s := p.Wire + s = "," + s += strconv.Itoa(p.Tag) + if p.Required { + s += ",req" + } + if p.Optional { + s += ",opt" + } + if p.Repeated { + s += ",rep" + } + if p.Packed { + s += ",packed" + } + if p.OrigName != p.Name { + s += ",name=" + p.OrigName + } + if len(p.Enum) > 0 { + s += ",enum=" + p.Enum + } + if p.HasDefault { + s += ",def=" + p.Default + } + return s +} + +// Parse populates p by parsing a string in the protobuf struct field tag style. +func (p *Properties) Parse(s string) { + // "bytes,49,opt,name=foo,def=hello!" + fields := strings.Split(s, ",") // breaks def=, but handled below. + if len(fields) < 2 { + fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s) + return + } + + p.Wire = fields[0] + switch p.Wire { + case "varint": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeVarint + p.valDec = (*Buffer).DecodeVarint + p.valSize = sizeVarint + case "fixed32": + p.WireType = WireFixed32 + p.valEnc = (*Buffer).EncodeFixed32 + p.valDec = (*Buffer).DecodeFixed32 + p.valSize = sizeFixed32 + case "fixed64": + p.WireType = WireFixed64 + p.valEnc = (*Buffer).EncodeFixed64 + p.valDec = (*Buffer).DecodeFixed64 + p.valSize = sizeFixed64 + case "zigzag32": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeZigzag32 + p.valDec = (*Buffer).DecodeZigzag32 + p.valSize = sizeZigzag32 + case "zigzag64": + p.WireType = WireVarint + p.valEnc = (*Buffer).EncodeZigzag64 + p.valDec = (*Buffer).DecodeZigzag64 + p.valSize = sizeZigzag64 + case "bytes", "group": + p.WireType = WireBytes + // no numeric converter for non-numeric types + default: + fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s) + return + } + + var err error + p.Tag, err = strconv.Atoi(fields[1]) + if err != nil { + return + } + + for i := 2; i < len(fields); i++ { + f := fields[i] + switch { + case f == "req": + p.Required = true + case f == "opt": + p.Optional = true + case f == "rep": + p.Repeated = true + case f == "packed": + p.Packed = true + case strings.HasPrefix(f, "name="): + p.OrigName = f[5:] + case strings.HasPrefix(f, "enum="): + p.Enum = f[5:] + case strings.HasPrefix(f, "def="): + p.HasDefault = true + p.Default = f[4:] // rest of string + if i+1 < len(fields) { + // Commas aren't escaped, and def is always last. + p.Default += "," + strings.Join(fields[i+1:], ",") + break + } + } + } +} + +func logNoSliceEnc(t1, t2 reflect.Type) { + fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2) +} + +var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem() + +// Initialize the fields for encoding and decoding. +func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { + p.enc = nil + p.dec = nil + p.size = nil + + switch t1 := typ; t1.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no coders for %T\n", t1) + + case reflect.Ptr: + switch t2 := t1.Elem(); t2.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no encoder function for %T -> %T\n", t1, t2) + break + case reflect.Bool: + p.enc = (*Buffer).enc_bool + p.dec = (*Buffer).dec_bool + p.size = size_bool + case reflect.Int32: + p.enc = (*Buffer).enc_int32 + p.dec = (*Buffer).dec_int32 + p.size = size_int32 + case reflect.Uint32: + p.enc = (*Buffer).enc_uint32 + p.dec = (*Buffer).dec_int32 // can reuse + p.size = size_uint32 + case reflect.Int64, reflect.Uint64: + p.enc = (*Buffer).enc_int64 + p.dec = (*Buffer).dec_int64 + p.size = size_int64 + case reflect.Float32: + p.enc = (*Buffer).enc_uint32 // can just treat them as bits + p.dec = (*Buffer).dec_int32 + p.size = size_uint32 + case reflect.Float64: + p.enc = (*Buffer).enc_int64 // can just treat them as bits + p.dec = (*Buffer).dec_int64 + p.size = size_int64 + case reflect.String: + p.enc = (*Buffer).enc_string + p.dec = (*Buffer).dec_string + p.size = size_string + case reflect.Struct: + p.stype = t1.Elem() + p.isMarshaler = isMarshaler(t1) + p.isUnmarshaler = isUnmarshaler(t1) + if p.Wire == "bytes" { + p.enc = (*Buffer).enc_struct_message + p.dec = (*Buffer).dec_struct_message + p.size = size_struct_message + } else { + p.enc = (*Buffer).enc_struct_group + p.dec = (*Buffer).dec_struct_group + p.size = size_struct_group + } + } + + case reflect.Slice: + switch t2 := t1.Elem(); t2.Kind() { + default: + logNoSliceEnc(t1, t2) + break + case reflect.Bool: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_bool + p.size = size_slice_packed_bool + } else { + p.enc = (*Buffer).enc_slice_bool + p.size = size_slice_bool + } + p.dec = (*Buffer).dec_slice_bool + p.packedDec = (*Buffer).dec_slice_packed_bool + case reflect.Int32: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int32 + p.size = size_slice_packed_int32 + } else { + p.enc = (*Buffer).enc_slice_int32 + p.size = size_slice_int32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case reflect.Uint32: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_uint32 + p.size = size_slice_packed_uint32 + } else { + p.enc = (*Buffer).enc_slice_uint32 + p.size = size_slice_uint32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case reflect.Int64, reflect.Uint64: + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int64 + p.size = size_slice_packed_int64 + } else { + p.enc = (*Buffer).enc_slice_int64 + p.size = size_slice_int64 + } + p.dec = (*Buffer).dec_slice_int64 + p.packedDec = (*Buffer).dec_slice_packed_int64 + case reflect.Uint8: + p.enc = (*Buffer).enc_slice_byte + p.dec = (*Buffer).dec_slice_byte + p.size = size_slice_byte + case reflect.Float32, reflect.Float64: + switch t2.Bits() { + case 32: + // can just treat them as bits + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_uint32 + p.size = size_slice_packed_uint32 + } else { + p.enc = (*Buffer).enc_slice_uint32 + p.size = size_slice_uint32 + } + p.dec = (*Buffer).dec_slice_int32 + p.packedDec = (*Buffer).dec_slice_packed_int32 + case 64: + // can just treat them as bits + if p.Packed { + p.enc = (*Buffer).enc_slice_packed_int64 + p.size = size_slice_packed_int64 + } else { + p.enc = (*Buffer).enc_slice_int64 + p.size = size_slice_int64 + } + p.dec = (*Buffer).dec_slice_int64 + p.packedDec = (*Buffer).dec_slice_packed_int64 + default: + logNoSliceEnc(t1, t2) + break + } + case reflect.String: + p.enc = (*Buffer).enc_slice_string + p.dec = (*Buffer).dec_slice_string + p.size = size_slice_string + case reflect.Ptr: + switch t3 := t2.Elem(); t3.Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3) + break + case reflect.Struct: + p.stype = t2.Elem() + p.isMarshaler = isMarshaler(t2) + p.isUnmarshaler = isUnmarshaler(t2) + if p.Wire == "bytes" { + p.enc = (*Buffer).enc_slice_struct_message + p.dec = (*Buffer).dec_slice_struct_message + p.size = size_slice_struct_message + } else { + p.enc = (*Buffer).enc_slice_struct_group + p.dec = (*Buffer).dec_slice_struct_group + p.size = size_slice_struct_group + } + } + case reflect.Slice: + switch t2.Elem().Kind() { + default: + fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem()) + break + case reflect.Uint8: + p.enc = (*Buffer).enc_slice_slice_byte + p.dec = (*Buffer).dec_slice_slice_byte + p.size = size_slice_slice_byte + } + } + } + + // precalculate tag code + wire := p.WireType + if p.Packed { + wire = WireBytes + } + x := uint32(p.Tag)<<3 | uint32(wire) + i := 0 + for i = 0; x > 127; i++ { + p.tagbuf[i] = 0x80 | uint8(x&0x7F) + x >>= 7 + } + p.tagbuf[i] = uint8(x) + p.tagcode = p.tagbuf[0 : i+1] + + if p.stype != nil { + if lockGetProp { + p.sprop = GetProperties(p.stype) + } else { + p.sprop = getPropertiesLocked(p.stype) + } + } +} + +var ( + marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem() + unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem() +) + +// isMarshaler reports whether type t implements Marshaler. +func isMarshaler(t reflect.Type) bool { + // We're checking for (likely) pointer-receiver methods + // so if t is not a pointer, something is very wrong. + // The calls above only invoke isMarshaler on pointer types. + if t.Kind() != reflect.Ptr { + panic("proto: misuse of isMarshaler") + } + return t.Implements(marshalerType) +} + +// isUnmarshaler reports whether type t implements Unmarshaler. +func isUnmarshaler(t reflect.Type) bool { + // We're checking for (likely) pointer-receiver methods + // so if t is not a pointer, something is very wrong. + // The calls above only invoke isUnmarshaler on pointer types. + if t.Kind() != reflect.Ptr { + panic("proto: misuse of isUnmarshaler") + } + return t.Implements(unmarshalerType) +} + +// Init populates the properties from a protocol buffer struct tag. +func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) { + p.init(typ, name, tag, f, true) +} + +func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) { + // "bytes,49,opt,def=hello!" + p.Name = name + p.OrigName = name + if f != nil { + p.field = toField(f) + } + if tag == "" { + return + } + p.Parse(tag) + p.setEncAndDec(typ, lockGetProp) +} + +var ( + mutex sync.Mutex + propertiesMap = make(map[reflect.Type]*StructProperties) +) + +// GetProperties returns the list of properties for the type represented by t. +func GetProperties(t reflect.Type) *StructProperties { + mutex.Lock() + sprop := getPropertiesLocked(t) + mutex.Unlock() + return sprop +} + +// getPropertiesLocked requires that mutex is held. +func getPropertiesLocked(t reflect.Type) *StructProperties { + if prop, ok := propertiesMap[t]; ok { + if collectStats { + stats.Chit++ + } + return prop + } + if collectStats { + stats.Cmiss++ + } + + prop := new(StructProperties) + // in case of recursive protos, fill this in now. + propertiesMap[t] = prop + + // build properties + prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) + prop.unrecField = invalidField + prop.Prop = make([]*Properties, t.NumField()) + prop.order = make([]int, t.NumField()) + + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + p := new(Properties) + name := f.Name + p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false) + + if f.Name == "XXX_extensions" { // special case + p.enc = (*Buffer).enc_map + p.dec = nil // not needed + p.size = size_map + } + if f.Name == "XXX_unrecognized" { // special case + prop.unrecField = toField(&f) + } + prop.Prop[i] = p + prop.order[i] = i + if debug { + print(i, " ", f.Name, " ", t.String(), " ") + if p.Tag > 0 { + print(p.String()) + } + print("\n") + } + if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") { + fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]") + } + } + + // Re-order prop.order. + sort.Sort(prop) + + // build required counts + // build tags + reqCount := 0 + prop.decoderOrigNames = make(map[string]int) + for i, p := range prop.Prop { + if strings.HasPrefix(p.Name, "XXX_") { + // Internal fields should not appear in tags/origNames maps. + // They are handled specially when encoding and decoding. + continue + } + if p.Required { + reqCount++ + } + prop.decoderTags.put(p.Tag, i) + prop.decoderOrigNames[p.OrigName] = i + } + prop.reqCount = reqCount + + return prop +} + +// Return the Properties object for the x[0]'th field of the structure. +func propByIndex(t reflect.Type, x []int) *Properties { + if len(x) != 1 { + fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t) + return nil + } + prop := GetProperties(t) + return prop.Prop[x[0]] +} + +// Get the address and type of a pointer to a struct from an interface. +func getbase(pb Message) (t reflect.Type, b structPointer, err error) { + if pb == nil { + err = ErrNil + return + } + // get the reflect type of the pointer to the struct. + t = reflect.TypeOf(pb) + // get the address of the struct. + value := reflect.ValueOf(pb) + b = toStructPointer(value) + return +} + +// A global registry of enum types. +// The generated code will register the generated maps by calling RegisterEnum. + +var enumValueMaps = make(map[string]map[string]int32) + +// RegisterEnum is called from the generated code to install the enum descriptor +// maps into the global table to aid parsing text format protocol buffers. +func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) { + if _, ok := enumValueMaps[typeName]; ok { + panic("proto: duplicate enum registered: " + typeName) + } + enumValueMaps[typeName] = valueMap +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go new file mode 100644 index 0000000..55902a4 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size2_test.go @@ -0,0 +1,63 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +import ( + "testing" +) + +// This is a separate file and package from size_test.go because that one uses +// generated messages and thus may not be in package proto without having a circular +// dependency, whereas this file tests unexported details of size.go. + +func TestVarintSize(t *testing.T) { + // Check the edge cases carefully. + testCases := []struct { + n uint64 + size int + }{ + {0, 1}, + {1, 1}, + {127, 1}, + {128, 2}, + {16383, 2}, + {16384, 3}, + {1<<63 - 1, 9}, + {1 << 63, 10}, + } + for _, tc := range testCases { + size := sizeVarint(tc.n) + if size != tc.size { + t.Errorf("sizeVarint(%d) = %d, want %d", tc.n, size, tc.size) + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go new file mode 100644 index 0000000..6cb9de6 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/size_test.go @@ -0,0 +1,120 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "log" + "testing" + + pb "./testdata" + . "code.google.com/p/goprotobuf/proto" +) + +var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)} + +// messageWithExtension2 is in equal_test.go. +var messageWithExtension3 = &pb.MyMessage{Count: Int32(8)} + +func init() { + if err := SetExtension(messageWithExtension1, pb.E_Ext_More, &pb.Ext{Data: String("Abbott")}); err != nil { + log.Panicf("SetExtension: %v", err) + } + if err := SetExtension(messageWithExtension3, pb.E_Ext_More, &pb.Ext{Data: String("Costello")}); err != nil { + log.Panicf("SetExtension: %v", err) + } + + // Force messageWithExtension3 to have the extension encoded. + Marshal(messageWithExtension3) + +} + +var SizeTests = []struct { + desc string + pb Message +}{ + {"empty", &pb.OtherMessage{}}, + // Basic types. + {"bool", &pb.Defaults{F_Bool: Bool(true)}}, + {"int32", &pb.Defaults{F_Int32: Int32(12)}}, + {"negative int32", &pb.Defaults{F_Int32: Int32(-1)}}, + {"small int64", &pb.Defaults{F_Int64: Int64(1)}}, + {"big int64", &pb.Defaults{F_Int64: Int64(1 << 20)}}, + {"negative int64", &pb.Defaults{F_Int64: Int64(-1)}}, + {"fixed32", &pb.Defaults{F_Fixed32: Uint32(71)}}, + {"fixed64", &pb.Defaults{F_Fixed64: Uint64(72)}}, + {"uint32", &pb.Defaults{F_Uint32: Uint32(123)}}, + {"uint64", &pb.Defaults{F_Uint64: Uint64(124)}}, + {"float", &pb.Defaults{F_Float: Float32(12.6)}}, + {"double", &pb.Defaults{F_Double: Float64(13.9)}}, + {"string", &pb.Defaults{F_String: String("niles")}}, + {"bytes", &pb.Defaults{F_Bytes: []byte("wowsa")}}, + {"bytes, empty", &pb.Defaults{F_Bytes: []byte{}}}, + {"sint32", &pb.Defaults{F_Sint32: Int32(65)}}, + {"sint64", &pb.Defaults{F_Sint64: Int64(67)}}, + {"enum", &pb.Defaults{F_Enum: pb.Defaults_BLUE.Enum()}}, + // Repeated. + {"empty repeated bool", &pb.MoreRepeated{Bools: []bool{}}}, + {"repeated bool", &pb.MoreRepeated{Bools: []bool{false, true, true, false}}}, + {"packed repeated bool", &pb.MoreRepeated{BoolsPacked: []bool{false, true, true, false, true, true, true}}}, + {"repeated int32", &pb.MoreRepeated{Ints: []int32{1, 12203, 1729, -1}}}, + {"repeated int32 packed", &pb.MoreRepeated{IntsPacked: []int32{1, 12203, 1729}}}, + {"repeated int64 packed", &pb.MoreRepeated{Int64SPacked: []int64{ + // Need enough large numbers to verify that the header is counting the number of bytes + // for the field, not the number of elements. + 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, + 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, + }}}, + {"repeated string", &pb.MoreRepeated{Strings: []string{"r", "ken", "gri"}}}, + {"repeated fixed", &pb.MoreRepeated{Fixeds: []uint32{1, 2, 3, 4}}}, + // Nested. + {"nested", &pb.OldMessage{Nested: &pb.OldMessage_Nested{Name: String("whatever")}}}, + {"group", &pb.GroupOld{G: &pb.GroupOld_G{X: Int32(12345)}}}, + // Other things. + {"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}}, + {"extension (unencoded)", messageWithExtension1}, + {"extension (encoded)", messageWithExtension3}, +} + +func TestSize(t *testing.T) { + for _, tc := range SizeTests { + size := Size(tc.pb) + b, err := Marshal(tc.pb) + if err != nil { + t.Errorf("%v: Marshal failed: %v", tc.desc, err) + continue + } + if size != len(b) { + t.Errorf("%v: Size(%v) = %d, want %d", tc.desc, tc.pb, size, len(b)) + t.Logf("%v: bytes: %#v", tc.desc, b) + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile new file mode 100644 index 0000000..9fa10e4 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/Makefile @@ -0,0 +1,50 @@ +# Go support for Protocol Buffers - Google's data interchange format +# +# Copyright 2010 The Go Authors. All rights reserved. +# http://code.google.com/p/goprotobuf/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +include ../../Make.protobuf + +all: regenerate + +regenerate: + rm -f test.pb.go + make test.pb.go + +# The following rules are just aids to development. Not needed for typical testing. + +diff: regenerate + hg diff test.pb.go + +restore: + cp test.pb.go.golden test.pb.go + +preserve: + cp test.pb.go test.pb.go.golden diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go new file mode 100644 index 0000000..f614aa1 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/golden_test.go @@ -0,0 +1,86 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2012 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Verify that the compiler output for test.proto is unchanged. + +package testdata + +import ( + "crypto/sha1" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" +) + +// sum returns in string form (for easy comparison) the SHA-1 hash of the named file. +func sum(t *testing.T, name string) string { + data, err := ioutil.ReadFile(name) + if err != nil { + t.Fatal(err) + } + t.Logf("sum(%q): length is %d", name, len(data)) + hash := sha1.New() + _, err = hash.Write(data) + if err != nil { + t.Fatal(err) + } + return fmt.Sprintf("% x", hash.Sum(nil)) +} + +func run(t *testing.T, name string, args ...string) { + cmd := exec.Command(name, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + t.Fatal(err) + } +} + +func TestGolden(t *testing.T) { + // Compute the original checksum. + goldenSum := sum(t, "test.pb.go") + // Run the proto compiler. + run(t, "protoc", "--go_out="+os.TempDir(), "test.proto") + newFile := filepath.Join(os.TempDir(), "test.pb.go") + defer os.Remove(newFile) + // Compute the new checksum. + newSum := sum(t, newFile) + // Verify + if newSum != goldenSum { + run(t, "diff", "-u", "test.pb.go", newFile) + t.Fatal("Code generated by protoc-gen-go has changed; update test.pb.go") + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go new file mode 100644 index 0000000..5a9a603 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go @@ -0,0 +1,2350 @@ +// Code generated by protoc-gen-go. +// source: test.proto +// DO NOT EDIT! + +/* +Package testdata is a generated protocol buffer package. + +It is generated from these files: + test.proto + +It has these top-level messages: + GoEnum + GoTestField + GoTest + GoSkipTest + NonPackedTest + PackedTest + MaxTag + OldMessage + NewMessage + InnerMessage + OtherMessage + MyMessage + Ext + MyMessageSet + Empty + MessageList + Strings + Defaults + SubDefaults + RepeatedEnum + MoreRepeated + GroupOld + GroupNew + FloatingPoint +*/ +package testdata + +import proto "code.google.com/p/goprotobuf/proto" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +type FOO int32 + +const ( + FOO_FOO1 FOO = 1 +) + +var FOO_name = map[int32]string{ + 1: "FOO1", +} +var FOO_value = map[string]int32{ + "FOO1": 1, +} + +func (x FOO) Enum() *FOO { + p := new(FOO) + *p = x + return p +} +func (x FOO) String() string { + return proto.EnumName(FOO_name, int32(x)) +} +func (x *FOO) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO") + if err != nil { + return err + } + *x = FOO(value) + return nil +} + +// An enum, for completeness. +type GoTest_KIND int32 + +const ( + GoTest_VOID GoTest_KIND = 0 + // Basic types + GoTest_BOOL GoTest_KIND = 1 + GoTest_BYTES GoTest_KIND = 2 + GoTest_FINGERPRINT GoTest_KIND = 3 + GoTest_FLOAT GoTest_KIND = 4 + GoTest_INT GoTest_KIND = 5 + GoTest_STRING GoTest_KIND = 6 + GoTest_TIME GoTest_KIND = 7 + // Groupings + GoTest_TUPLE GoTest_KIND = 8 + GoTest_ARRAY GoTest_KIND = 9 + GoTest_MAP GoTest_KIND = 10 + // Table types + GoTest_TABLE GoTest_KIND = 11 + // Functions + GoTest_FUNCTION GoTest_KIND = 12 +) + +var GoTest_KIND_name = map[int32]string{ + 0: "VOID", + 1: "BOOL", + 2: "BYTES", + 3: "FINGERPRINT", + 4: "FLOAT", + 5: "INT", + 6: "STRING", + 7: "TIME", + 8: "TUPLE", + 9: "ARRAY", + 10: "MAP", + 11: "TABLE", + 12: "FUNCTION", +} +var GoTest_KIND_value = map[string]int32{ + "VOID": 0, + "BOOL": 1, + "BYTES": 2, + "FINGERPRINT": 3, + "FLOAT": 4, + "INT": 5, + "STRING": 6, + "TIME": 7, + "TUPLE": 8, + "ARRAY": 9, + "MAP": 10, + "TABLE": 11, + "FUNCTION": 12, +} + +func (x GoTest_KIND) Enum() *GoTest_KIND { + p := new(GoTest_KIND) + *p = x + return p +} +func (x GoTest_KIND) String() string { + return proto.EnumName(GoTest_KIND_name, int32(x)) +} +func (x *GoTest_KIND) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(GoTest_KIND_value, data, "GoTest_KIND") + if err != nil { + return err + } + *x = GoTest_KIND(value) + return nil +} + +type MyMessage_Color int32 + +const ( + MyMessage_RED MyMessage_Color = 0 + MyMessage_GREEN MyMessage_Color = 1 + MyMessage_BLUE MyMessage_Color = 2 +) + +var MyMessage_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var MyMessage_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x MyMessage_Color) Enum() *MyMessage_Color { + p := new(MyMessage_Color) + *p = x + return p +} +func (x MyMessage_Color) String() string { + return proto.EnumName(MyMessage_Color_name, int32(x)) +} +func (x *MyMessage_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(MyMessage_Color_value, data, "MyMessage_Color") + if err != nil { + return err + } + *x = MyMessage_Color(value) + return nil +} + +type Defaults_Color int32 + +const ( + Defaults_RED Defaults_Color = 0 + Defaults_GREEN Defaults_Color = 1 + Defaults_BLUE Defaults_Color = 2 +) + +var Defaults_Color_name = map[int32]string{ + 0: "RED", + 1: "GREEN", + 2: "BLUE", +} +var Defaults_Color_value = map[string]int32{ + "RED": 0, + "GREEN": 1, + "BLUE": 2, +} + +func (x Defaults_Color) Enum() *Defaults_Color { + p := new(Defaults_Color) + *p = x + return p +} +func (x Defaults_Color) String() string { + return proto.EnumName(Defaults_Color_name, int32(x)) +} +func (x *Defaults_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Defaults_Color_value, data, "Defaults_Color") + if err != nil { + return err + } + *x = Defaults_Color(value) + return nil +} + +type RepeatedEnum_Color int32 + +const ( + RepeatedEnum_RED RepeatedEnum_Color = 1 +) + +var RepeatedEnum_Color_name = map[int32]string{ + 1: "RED", +} +var RepeatedEnum_Color_value = map[string]int32{ + "RED": 1, +} + +func (x RepeatedEnum_Color) Enum() *RepeatedEnum_Color { + p := new(RepeatedEnum_Color) + *p = x + return p +} +func (x RepeatedEnum_Color) String() string { + return proto.EnumName(RepeatedEnum_Color_name, int32(x)) +} +func (x *RepeatedEnum_Color) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(RepeatedEnum_Color_value, data, "RepeatedEnum_Color") + if err != nil { + return err + } + *x = RepeatedEnum_Color(value) + return nil +} + +type GoEnum struct { + Foo *FOO `protobuf:"varint,1,req,name=foo,enum=testdata.FOO" json:"foo,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoEnum) Reset() { *m = GoEnum{} } +func (m *GoEnum) String() string { return proto.CompactTextString(m) } +func (*GoEnum) ProtoMessage() {} + +func (m *GoEnum) GetFoo() FOO { + if m != nil && m.Foo != nil { + return *m.Foo + } + return FOO_FOO1 +} + +type GoTestField struct { + Label *string `protobuf:"bytes,1,req" json:"Label,omitempty"` + Type *string `protobuf:"bytes,2,req" json:"Type,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTestField) Reset() { *m = GoTestField{} } +func (m *GoTestField) String() string { return proto.CompactTextString(m) } +func (*GoTestField) ProtoMessage() {} + +func (m *GoTestField) GetLabel() string { + if m != nil && m.Label != nil { + return *m.Label + } + return "" +} + +func (m *GoTestField) GetType() string { + if m != nil && m.Type != nil { + return *m.Type + } + return "" +} + +type GoTest struct { + // Some typical parameters + Kind *GoTest_KIND `protobuf:"varint,1,req,enum=testdata.GoTest_KIND" json:"Kind,omitempty"` + Table *string `protobuf:"bytes,2,opt" json:"Table,omitempty"` + Param *int32 `protobuf:"varint,3,opt" json:"Param,omitempty"` + // Required, repeated and optional foreign fields. + RequiredField *GoTestField `protobuf:"bytes,4,req" json:"RequiredField,omitempty"` + RepeatedField []*GoTestField `protobuf:"bytes,5,rep" json:"RepeatedField,omitempty"` + OptionalField *GoTestField `protobuf:"bytes,6,opt" json:"OptionalField,omitempty"` + // Required fields of all basic types + F_BoolRequired *bool `protobuf:"varint,10,req,name=F_Bool_required" json:"F_Bool_required,omitempty"` + F_Int32Required *int32 `protobuf:"varint,11,req,name=F_Int32_required" json:"F_Int32_required,omitempty"` + F_Int64Required *int64 `protobuf:"varint,12,req,name=F_Int64_required" json:"F_Int64_required,omitempty"` + F_Fixed32Required *uint32 `protobuf:"fixed32,13,req,name=F_Fixed32_required" json:"F_Fixed32_required,omitempty"` + F_Fixed64Required *uint64 `protobuf:"fixed64,14,req,name=F_Fixed64_required" json:"F_Fixed64_required,omitempty"` + F_Uint32Required *uint32 `protobuf:"varint,15,req,name=F_Uint32_required" json:"F_Uint32_required,omitempty"` + F_Uint64Required *uint64 `protobuf:"varint,16,req,name=F_Uint64_required" json:"F_Uint64_required,omitempty"` + F_FloatRequired *float32 `protobuf:"fixed32,17,req,name=F_Float_required" json:"F_Float_required,omitempty"` + F_DoubleRequired *float64 `protobuf:"fixed64,18,req,name=F_Double_required" json:"F_Double_required,omitempty"` + F_StringRequired *string `protobuf:"bytes,19,req,name=F_String_required" json:"F_String_required,omitempty"` + F_BytesRequired []byte `protobuf:"bytes,101,req,name=F_Bytes_required" json:"F_Bytes_required,omitempty"` + F_Sint32Required *int32 `protobuf:"zigzag32,102,req,name=F_Sint32_required" json:"F_Sint32_required,omitempty"` + F_Sint64Required *int64 `protobuf:"zigzag64,103,req,name=F_Sint64_required" json:"F_Sint64_required,omitempty"` + // Repeated fields of all basic types + F_BoolRepeated []bool `protobuf:"varint,20,rep,name=F_Bool_repeated" json:"F_Bool_repeated,omitempty"` + F_Int32Repeated []int32 `protobuf:"varint,21,rep,name=F_Int32_repeated" json:"F_Int32_repeated,omitempty"` + F_Int64Repeated []int64 `protobuf:"varint,22,rep,name=F_Int64_repeated" json:"F_Int64_repeated,omitempty"` + F_Fixed32Repeated []uint32 `protobuf:"fixed32,23,rep,name=F_Fixed32_repeated" json:"F_Fixed32_repeated,omitempty"` + F_Fixed64Repeated []uint64 `protobuf:"fixed64,24,rep,name=F_Fixed64_repeated" json:"F_Fixed64_repeated,omitempty"` + F_Uint32Repeated []uint32 `protobuf:"varint,25,rep,name=F_Uint32_repeated" json:"F_Uint32_repeated,omitempty"` + F_Uint64Repeated []uint64 `protobuf:"varint,26,rep,name=F_Uint64_repeated" json:"F_Uint64_repeated,omitempty"` + F_FloatRepeated []float32 `protobuf:"fixed32,27,rep,name=F_Float_repeated" json:"F_Float_repeated,omitempty"` + F_DoubleRepeated []float64 `protobuf:"fixed64,28,rep,name=F_Double_repeated" json:"F_Double_repeated,omitempty"` + F_StringRepeated []string `protobuf:"bytes,29,rep,name=F_String_repeated" json:"F_String_repeated,omitempty"` + F_BytesRepeated [][]byte `protobuf:"bytes,201,rep,name=F_Bytes_repeated" json:"F_Bytes_repeated,omitempty"` + F_Sint32Repeated []int32 `protobuf:"zigzag32,202,rep,name=F_Sint32_repeated" json:"F_Sint32_repeated,omitempty"` + F_Sint64Repeated []int64 `protobuf:"zigzag64,203,rep,name=F_Sint64_repeated" json:"F_Sint64_repeated,omitempty"` + // Optional fields of all basic types + F_BoolOptional *bool `protobuf:"varint,30,opt,name=F_Bool_optional" json:"F_Bool_optional,omitempty"` + F_Int32Optional *int32 `protobuf:"varint,31,opt,name=F_Int32_optional" json:"F_Int32_optional,omitempty"` + F_Int64Optional *int64 `protobuf:"varint,32,opt,name=F_Int64_optional" json:"F_Int64_optional,omitempty"` + F_Fixed32Optional *uint32 `protobuf:"fixed32,33,opt,name=F_Fixed32_optional" json:"F_Fixed32_optional,omitempty"` + F_Fixed64Optional *uint64 `protobuf:"fixed64,34,opt,name=F_Fixed64_optional" json:"F_Fixed64_optional,omitempty"` + F_Uint32Optional *uint32 `protobuf:"varint,35,opt,name=F_Uint32_optional" json:"F_Uint32_optional,omitempty"` + F_Uint64Optional *uint64 `protobuf:"varint,36,opt,name=F_Uint64_optional" json:"F_Uint64_optional,omitempty"` + F_FloatOptional *float32 `protobuf:"fixed32,37,opt,name=F_Float_optional" json:"F_Float_optional,omitempty"` + F_DoubleOptional *float64 `protobuf:"fixed64,38,opt,name=F_Double_optional" json:"F_Double_optional,omitempty"` + F_StringOptional *string `protobuf:"bytes,39,opt,name=F_String_optional" json:"F_String_optional,omitempty"` + F_BytesOptional []byte `protobuf:"bytes,301,opt,name=F_Bytes_optional" json:"F_Bytes_optional,omitempty"` + F_Sint32Optional *int32 `protobuf:"zigzag32,302,opt,name=F_Sint32_optional" json:"F_Sint32_optional,omitempty"` + F_Sint64Optional *int64 `protobuf:"zigzag64,303,opt,name=F_Sint64_optional" json:"F_Sint64_optional,omitempty"` + // Default-valued fields of all basic types + F_BoolDefaulted *bool `protobuf:"varint,40,opt,name=F_Bool_defaulted,def=1" json:"F_Bool_defaulted,omitempty"` + F_Int32Defaulted *int32 `protobuf:"varint,41,opt,name=F_Int32_defaulted,def=32" json:"F_Int32_defaulted,omitempty"` + F_Int64Defaulted *int64 `protobuf:"varint,42,opt,name=F_Int64_defaulted,def=64" json:"F_Int64_defaulted,omitempty"` + F_Fixed32Defaulted *uint32 `protobuf:"fixed32,43,opt,name=F_Fixed32_defaulted,def=320" json:"F_Fixed32_defaulted,omitempty"` + F_Fixed64Defaulted *uint64 `protobuf:"fixed64,44,opt,name=F_Fixed64_defaulted,def=640" json:"F_Fixed64_defaulted,omitempty"` + F_Uint32Defaulted *uint32 `protobuf:"varint,45,opt,name=F_Uint32_defaulted,def=3200" json:"F_Uint32_defaulted,omitempty"` + F_Uint64Defaulted *uint64 `protobuf:"varint,46,opt,name=F_Uint64_defaulted,def=6400" json:"F_Uint64_defaulted,omitempty"` + F_FloatDefaulted *float32 `protobuf:"fixed32,47,opt,name=F_Float_defaulted,def=314159" json:"F_Float_defaulted,omitempty"` + F_DoubleDefaulted *float64 `protobuf:"fixed64,48,opt,name=F_Double_defaulted,def=271828" json:"F_Double_defaulted,omitempty"` + F_StringDefaulted *string `protobuf:"bytes,49,opt,name=F_String_defaulted,def=hello, \"world!\"\n" json:"F_String_defaulted,omitempty"` + F_BytesDefaulted []byte `protobuf:"bytes,401,opt,name=F_Bytes_defaulted,def=Bignose" json:"F_Bytes_defaulted,omitempty"` + F_Sint32Defaulted *int32 `protobuf:"zigzag32,402,opt,name=F_Sint32_defaulted,def=-32" json:"F_Sint32_defaulted,omitempty"` + F_Sint64Defaulted *int64 `protobuf:"zigzag64,403,opt,name=F_Sint64_defaulted,def=-64" json:"F_Sint64_defaulted,omitempty"` + // Packed repeated fields (no string or bytes). + F_BoolRepeatedPacked []bool `protobuf:"varint,50,rep,packed,name=F_Bool_repeated_packed" json:"F_Bool_repeated_packed,omitempty"` + F_Int32RepeatedPacked []int32 `protobuf:"varint,51,rep,packed,name=F_Int32_repeated_packed" json:"F_Int32_repeated_packed,omitempty"` + F_Int64RepeatedPacked []int64 `protobuf:"varint,52,rep,packed,name=F_Int64_repeated_packed" json:"F_Int64_repeated_packed,omitempty"` + F_Fixed32RepeatedPacked []uint32 `protobuf:"fixed32,53,rep,packed,name=F_Fixed32_repeated_packed" json:"F_Fixed32_repeated_packed,omitempty"` + F_Fixed64RepeatedPacked []uint64 `protobuf:"fixed64,54,rep,packed,name=F_Fixed64_repeated_packed" json:"F_Fixed64_repeated_packed,omitempty"` + F_Uint32RepeatedPacked []uint32 `protobuf:"varint,55,rep,packed,name=F_Uint32_repeated_packed" json:"F_Uint32_repeated_packed,omitempty"` + F_Uint64RepeatedPacked []uint64 `protobuf:"varint,56,rep,packed,name=F_Uint64_repeated_packed" json:"F_Uint64_repeated_packed,omitempty"` + F_FloatRepeatedPacked []float32 `protobuf:"fixed32,57,rep,packed,name=F_Float_repeated_packed" json:"F_Float_repeated_packed,omitempty"` + F_DoubleRepeatedPacked []float64 `protobuf:"fixed64,58,rep,packed,name=F_Double_repeated_packed" json:"F_Double_repeated_packed,omitempty"` + F_Sint32RepeatedPacked []int32 `protobuf:"zigzag32,502,rep,packed,name=F_Sint32_repeated_packed" json:"F_Sint32_repeated_packed,omitempty"` + F_Sint64RepeatedPacked []int64 `protobuf:"zigzag64,503,rep,packed,name=F_Sint64_repeated_packed" json:"F_Sint64_repeated_packed,omitempty"` + Requiredgroup *GoTest_RequiredGroup `protobuf:"group,70,req,name=RequiredGroup" json:"requiredgroup,omitempty"` + Repeatedgroup []*GoTest_RepeatedGroup `protobuf:"group,80,rep,name=RepeatedGroup" json:"repeatedgroup,omitempty"` + Optionalgroup *GoTest_OptionalGroup `protobuf:"group,90,opt,name=OptionalGroup" json:"optionalgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest) Reset() { *m = GoTest{} } +func (m *GoTest) String() string { return proto.CompactTextString(m) } +func (*GoTest) ProtoMessage() {} + +const Default_GoTest_F_BoolDefaulted bool = true +const Default_GoTest_F_Int32Defaulted int32 = 32 +const Default_GoTest_F_Int64Defaulted int64 = 64 +const Default_GoTest_F_Fixed32Defaulted uint32 = 320 +const Default_GoTest_F_Fixed64Defaulted uint64 = 640 +const Default_GoTest_F_Uint32Defaulted uint32 = 3200 +const Default_GoTest_F_Uint64Defaulted uint64 = 6400 +const Default_GoTest_F_FloatDefaulted float32 = 314159 +const Default_GoTest_F_DoubleDefaulted float64 = 271828 +const Default_GoTest_F_StringDefaulted string = "hello, \"world!\"\n" + +var Default_GoTest_F_BytesDefaulted []byte = []byte("Bignose") + +const Default_GoTest_F_Sint32Defaulted int32 = -32 +const Default_GoTest_F_Sint64Defaulted int64 = -64 + +func (m *GoTest) GetKind() GoTest_KIND { + if m != nil && m.Kind != nil { + return *m.Kind + } + return GoTest_VOID +} + +func (m *GoTest) GetTable() string { + if m != nil && m.Table != nil { + return *m.Table + } + return "" +} + +func (m *GoTest) GetParam() int32 { + if m != nil && m.Param != nil { + return *m.Param + } + return 0 +} + +func (m *GoTest) GetRequiredField() *GoTestField { + if m != nil { + return m.RequiredField + } + return nil +} + +func (m *GoTest) GetRepeatedField() []*GoTestField { + if m != nil { + return m.RepeatedField + } + return nil +} + +func (m *GoTest) GetOptionalField() *GoTestField { + if m != nil { + return m.OptionalField + } + return nil +} + +func (m *GoTest) GetF_BoolRequired() bool { + if m != nil && m.F_BoolRequired != nil { + return *m.F_BoolRequired + } + return false +} + +func (m *GoTest) GetF_Int32Required() int32 { + if m != nil && m.F_Int32Required != nil { + return *m.F_Int32Required + } + return 0 +} + +func (m *GoTest) GetF_Int64Required() int64 { + if m != nil && m.F_Int64Required != nil { + return *m.F_Int64Required + } + return 0 +} + +func (m *GoTest) GetF_Fixed32Required() uint32 { + if m != nil && m.F_Fixed32Required != nil { + return *m.F_Fixed32Required + } + return 0 +} + +func (m *GoTest) GetF_Fixed64Required() uint64 { + if m != nil && m.F_Fixed64Required != nil { + return *m.F_Fixed64Required + } + return 0 +} + +func (m *GoTest) GetF_Uint32Required() uint32 { + if m != nil && m.F_Uint32Required != nil { + return *m.F_Uint32Required + } + return 0 +} + +func (m *GoTest) GetF_Uint64Required() uint64 { + if m != nil && m.F_Uint64Required != nil { + return *m.F_Uint64Required + } + return 0 +} + +func (m *GoTest) GetF_FloatRequired() float32 { + if m != nil && m.F_FloatRequired != nil { + return *m.F_FloatRequired + } + return 0 +} + +func (m *GoTest) GetF_DoubleRequired() float64 { + if m != nil && m.F_DoubleRequired != nil { + return *m.F_DoubleRequired + } + return 0 +} + +func (m *GoTest) GetF_StringRequired() string { + if m != nil && m.F_StringRequired != nil { + return *m.F_StringRequired + } + return "" +} + +func (m *GoTest) GetF_BytesRequired() []byte { + if m != nil { + return m.F_BytesRequired + } + return nil +} + +func (m *GoTest) GetF_Sint32Required() int32 { + if m != nil && m.F_Sint32Required != nil { + return *m.F_Sint32Required + } + return 0 +} + +func (m *GoTest) GetF_Sint64Required() int64 { + if m != nil && m.F_Sint64Required != nil { + return *m.F_Sint64Required + } + return 0 +} + +func (m *GoTest) GetF_BoolRepeated() []bool { + if m != nil { + return m.F_BoolRepeated + } + return nil +} + +func (m *GoTest) GetF_Int32Repeated() []int32 { + if m != nil { + return m.F_Int32Repeated + } + return nil +} + +func (m *GoTest) GetF_Int64Repeated() []int64 { + if m != nil { + return m.F_Int64Repeated + } + return nil +} + +func (m *GoTest) GetF_Fixed32Repeated() []uint32 { + if m != nil { + return m.F_Fixed32Repeated + } + return nil +} + +func (m *GoTest) GetF_Fixed64Repeated() []uint64 { + if m != nil { + return m.F_Fixed64Repeated + } + return nil +} + +func (m *GoTest) GetF_Uint32Repeated() []uint32 { + if m != nil { + return m.F_Uint32Repeated + } + return nil +} + +func (m *GoTest) GetF_Uint64Repeated() []uint64 { + if m != nil { + return m.F_Uint64Repeated + } + return nil +} + +func (m *GoTest) GetF_FloatRepeated() []float32 { + if m != nil { + return m.F_FloatRepeated + } + return nil +} + +func (m *GoTest) GetF_DoubleRepeated() []float64 { + if m != nil { + return m.F_DoubleRepeated + } + return nil +} + +func (m *GoTest) GetF_StringRepeated() []string { + if m != nil { + return m.F_StringRepeated + } + return nil +} + +func (m *GoTest) GetF_BytesRepeated() [][]byte { + if m != nil { + return m.F_BytesRepeated + } + return nil +} + +func (m *GoTest) GetF_Sint32Repeated() []int32 { + if m != nil { + return m.F_Sint32Repeated + } + return nil +} + +func (m *GoTest) GetF_Sint64Repeated() []int64 { + if m != nil { + return m.F_Sint64Repeated + } + return nil +} + +func (m *GoTest) GetF_BoolOptional() bool { + if m != nil && m.F_BoolOptional != nil { + return *m.F_BoolOptional + } + return false +} + +func (m *GoTest) GetF_Int32Optional() int32 { + if m != nil && m.F_Int32Optional != nil { + return *m.F_Int32Optional + } + return 0 +} + +func (m *GoTest) GetF_Int64Optional() int64 { + if m != nil && m.F_Int64Optional != nil { + return *m.F_Int64Optional + } + return 0 +} + +func (m *GoTest) GetF_Fixed32Optional() uint32 { + if m != nil && m.F_Fixed32Optional != nil { + return *m.F_Fixed32Optional + } + return 0 +} + +func (m *GoTest) GetF_Fixed64Optional() uint64 { + if m != nil && m.F_Fixed64Optional != nil { + return *m.F_Fixed64Optional + } + return 0 +} + +func (m *GoTest) GetF_Uint32Optional() uint32 { + if m != nil && m.F_Uint32Optional != nil { + return *m.F_Uint32Optional + } + return 0 +} + +func (m *GoTest) GetF_Uint64Optional() uint64 { + if m != nil && m.F_Uint64Optional != nil { + return *m.F_Uint64Optional + } + return 0 +} + +func (m *GoTest) GetF_FloatOptional() float32 { + if m != nil && m.F_FloatOptional != nil { + return *m.F_FloatOptional + } + return 0 +} + +func (m *GoTest) GetF_DoubleOptional() float64 { + if m != nil && m.F_DoubleOptional != nil { + return *m.F_DoubleOptional + } + return 0 +} + +func (m *GoTest) GetF_StringOptional() string { + if m != nil && m.F_StringOptional != nil { + return *m.F_StringOptional + } + return "" +} + +func (m *GoTest) GetF_BytesOptional() []byte { + if m != nil { + return m.F_BytesOptional + } + return nil +} + +func (m *GoTest) GetF_Sint32Optional() int32 { + if m != nil && m.F_Sint32Optional != nil { + return *m.F_Sint32Optional + } + return 0 +} + +func (m *GoTest) GetF_Sint64Optional() int64 { + if m != nil && m.F_Sint64Optional != nil { + return *m.F_Sint64Optional + } + return 0 +} + +func (m *GoTest) GetF_BoolDefaulted() bool { + if m != nil && m.F_BoolDefaulted != nil { + return *m.F_BoolDefaulted + } + return Default_GoTest_F_BoolDefaulted +} + +func (m *GoTest) GetF_Int32Defaulted() int32 { + if m != nil && m.F_Int32Defaulted != nil { + return *m.F_Int32Defaulted + } + return Default_GoTest_F_Int32Defaulted +} + +func (m *GoTest) GetF_Int64Defaulted() int64 { + if m != nil && m.F_Int64Defaulted != nil { + return *m.F_Int64Defaulted + } + return Default_GoTest_F_Int64Defaulted +} + +func (m *GoTest) GetF_Fixed32Defaulted() uint32 { + if m != nil && m.F_Fixed32Defaulted != nil { + return *m.F_Fixed32Defaulted + } + return Default_GoTest_F_Fixed32Defaulted +} + +func (m *GoTest) GetF_Fixed64Defaulted() uint64 { + if m != nil && m.F_Fixed64Defaulted != nil { + return *m.F_Fixed64Defaulted + } + return Default_GoTest_F_Fixed64Defaulted +} + +func (m *GoTest) GetF_Uint32Defaulted() uint32 { + if m != nil && m.F_Uint32Defaulted != nil { + return *m.F_Uint32Defaulted + } + return Default_GoTest_F_Uint32Defaulted +} + +func (m *GoTest) GetF_Uint64Defaulted() uint64 { + if m != nil && m.F_Uint64Defaulted != nil { + return *m.F_Uint64Defaulted + } + return Default_GoTest_F_Uint64Defaulted +} + +func (m *GoTest) GetF_FloatDefaulted() float32 { + if m != nil && m.F_FloatDefaulted != nil { + return *m.F_FloatDefaulted + } + return Default_GoTest_F_FloatDefaulted +} + +func (m *GoTest) GetF_DoubleDefaulted() float64 { + if m != nil && m.F_DoubleDefaulted != nil { + return *m.F_DoubleDefaulted + } + return Default_GoTest_F_DoubleDefaulted +} + +func (m *GoTest) GetF_StringDefaulted() string { + if m != nil && m.F_StringDefaulted != nil { + return *m.F_StringDefaulted + } + return Default_GoTest_F_StringDefaulted +} + +func (m *GoTest) GetF_BytesDefaulted() []byte { + if m != nil && m.F_BytesDefaulted != nil { + return m.F_BytesDefaulted + } + return append([]byte(nil), Default_GoTest_F_BytesDefaulted...) +} + +func (m *GoTest) GetF_Sint32Defaulted() int32 { + if m != nil && m.F_Sint32Defaulted != nil { + return *m.F_Sint32Defaulted + } + return Default_GoTest_F_Sint32Defaulted +} + +func (m *GoTest) GetF_Sint64Defaulted() int64 { + if m != nil && m.F_Sint64Defaulted != nil { + return *m.F_Sint64Defaulted + } + return Default_GoTest_F_Sint64Defaulted +} + +func (m *GoTest) GetF_BoolRepeatedPacked() []bool { + if m != nil { + return m.F_BoolRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Int32RepeatedPacked() []int32 { + if m != nil { + return m.F_Int32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Int64RepeatedPacked() []int64 { + if m != nil { + return m.F_Int64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Fixed32RepeatedPacked() []uint32 { + if m != nil { + return m.F_Fixed32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Fixed64RepeatedPacked() []uint64 { + if m != nil { + return m.F_Fixed64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Uint32RepeatedPacked() []uint32 { + if m != nil { + return m.F_Uint32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Uint64RepeatedPacked() []uint64 { + if m != nil { + return m.F_Uint64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_FloatRepeatedPacked() []float32 { + if m != nil { + return m.F_FloatRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_DoubleRepeatedPacked() []float64 { + if m != nil { + return m.F_DoubleRepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Sint32RepeatedPacked() []int32 { + if m != nil { + return m.F_Sint32RepeatedPacked + } + return nil +} + +func (m *GoTest) GetF_Sint64RepeatedPacked() []int64 { + if m != nil { + return m.F_Sint64RepeatedPacked + } + return nil +} + +func (m *GoTest) GetRequiredgroup() *GoTest_RequiredGroup { + if m != nil { + return m.Requiredgroup + } + return nil +} + +func (m *GoTest) GetRepeatedgroup() []*GoTest_RepeatedGroup { + if m != nil { + return m.Repeatedgroup + } + return nil +} + +func (m *GoTest) GetOptionalgroup() *GoTest_OptionalGroup { + if m != nil { + return m.Optionalgroup + } + return nil +} + +// Required, repeated, and optional groups. +type GoTest_RequiredGroup struct { + RequiredField *string `protobuf:"bytes,71,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_RequiredGroup) Reset() { *m = GoTest_RequiredGroup{} } +func (m *GoTest_RequiredGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_RequiredGroup) ProtoMessage() {} + +func (m *GoTest_RequiredGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +type GoTest_RepeatedGroup struct { + RequiredField *string `protobuf:"bytes,81,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_RepeatedGroup) Reset() { *m = GoTest_RepeatedGroup{} } +func (m *GoTest_RepeatedGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_RepeatedGroup) ProtoMessage() {} + +func (m *GoTest_RepeatedGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +type GoTest_OptionalGroup struct { + RequiredField *string `protobuf:"bytes,91,req" json:"RequiredField,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoTest_OptionalGroup) Reset() { *m = GoTest_OptionalGroup{} } +func (m *GoTest_OptionalGroup) String() string { return proto.CompactTextString(m) } +func (*GoTest_OptionalGroup) ProtoMessage() {} + +func (m *GoTest_OptionalGroup) GetRequiredField() string { + if m != nil && m.RequiredField != nil { + return *m.RequiredField + } + return "" +} + +// For testing skipping of unrecognized fields. +// Numbers are all big, larger than tag numbers in GoTestField, +// the message used in the corresponding test. +type GoSkipTest struct { + SkipInt32 *int32 `protobuf:"varint,11,req,name=skip_int32" json:"skip_int32,omitempty"` + SkipFixed32 *uint32 `protobuf:"fixed32,12,req,name=skip_fixed32" json:"skip_fixed32,omitempty"` + SkipFixed64 *uint64 `protobuf:"fixed64,13,req,name=skip_fixed64" json:"skip_fixed64,omitempty"` + SkipString *string `protobuf:"bytes,14,req,name=skip_string" json:"skip_string,omitempty"` + Skipgroup *GoSkipTest_SkipGroup `protobuf:"group,15,req,name=SkipGroup" json:"skipgroup,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoSkipTest) Reset() { *m = GoSkipTest{} } +func (m *GoSkipTest) String() string { return proto.CompactTextString(m) } +func (*GoSkipTest) ProtoMessage() {} + +func (m *GoSkipTest) GetSkipInt32() int32 { + if m != nil && m.SkipInt32 != nil { + return *m.SkipInt32 + } + return 0 +} + +func (m *GoSkipTest) GetSkipFixed32() uint32 { + if m != nil && m.SkipFixed32 != nil { + return *m.SkipFixed32 + } + return 0 +} + +func (m *GoSkipTest) GetSkipFixed64() uint64 { + if m != nil && m.SkipFixed64 != nil { + return *m.SkipFixed64 + } + return 0 +} + +func (m *GoSkipTest) GetSkipString() string { + if m != nil && m.SkipString != nil { + return *m.SkipString + } + return "" +} + +func (m *GoSkipTest) GetSkipgroup() *GoSkipTest_SkipGroup { + if m != nil { + return m.Skipgroup + } + return nil +} + +type GoSkipTest_SkipGroup struct { + GroupInt32 *int32 `protobuf:"varint,16,req,name=group_int32" json:"group_int32,omitempty"` + GroupString *string `protobuf:"bytes,17,req,name=group_string" json:"group_string,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GoSkipTest_SkipGroup) Reset() { *m = GoSkipTest_SkipGroup{} } +func (m *GoSkipTest_SkipGroup) String() string { return proto.CompactTextString(m) } +func (*GoSkipTest_SkipGroup) ProtoMessage() {} + +func (m *GoSkipTest_SkipGroup) GetGroupInt32() int32 { + if m != nil && m.GroupInt32 != nil { + return *m.GroupInt32 + } + return 0 +} + +func (m *GoSkipTest_SkipGroup) GetGroupString() string { + if m != nil && m.GroupString != nil { + return *m.GroupString + } + return "" +} + +// For testing packed/non-packed decoder switching. +// A serialized instance of one should be deserializable as the other. +type NonPackedTest struct { + A []int32 `protobuf:"varint,1,rep,name=a" json:"a,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NonPackedTest) Reset() { *m = NonPackedTest{} } +func (m *NonPackedTest) String() string { return proto.CompactTextString(m) } +func (*NonPackedTest) ProtoMessage() {} + +func (m *NonPackedTest) GetA() []int32 { + if m != nil { + return m.A + } + return nil +} + +type PackedTest struct { + B []int32 `protobuf:"varint,1,rep,packed,name=b" json:"b,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *PackedTest) Reset() { *m = PackedTest{} } +func (m *PackedTest) String() string { return proto.CompactTextString(m) } +func (*PackedTest) ProtoMessage() {} + +func (m *PackedTest) GetB() []int32 { + if m != nil { + return m.B + } + return nil +} + +type MaxTag struct { + // Maximum possible tag number. + LastField *string `protobuf:"bytes,536870911,opt,name=last_field" json:"last_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MaxTag) Reset() { *m = MaxTag{} } +func (m *MaxTag) String() string { return proto.CompactTextString(m) } +func (*MaxTag) ProtoMessage() {} + +func (m *MaxTag) GetLastField() string { + if m != nil && m.LastField != nil { + return *m.LastField + } + return "" +} + +type OldMessage struct { + Nested *OldMessage_Nested `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"` + Num *int32 `protobuf:"varint,2,opt,name=num" json:"num,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldMessage) Reset() { *m = OldMessage{} } +func (m *OldMessage) String() string { return proto.CompactTextString(m) } +func (*OldMessage) ProtoMessage() {} + +func (m *OldMessage) GetNested() *OldMessage_Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *OldMessage) GetNum() int32 { + if m != nil && m.Num != nil { + return *m.Num + } + return 0 +} + +type OldMessage_Nested struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OldMessage_Nested) Reset() { *m = OldMessage_Nested{} } +func (m *OldMessage_Nested) String() string { return proto.CompactTextString(m) } +func (*OldMessage_Nested) ProtoMessage() {} + +func (m *OldMessage_Nested) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +// NewMessage is wire compatible with OldMessage; +// imagine it as a future version. +type NewMessage struct { + Nested *NewMessage_Nested `protobuf:"bytes,1,opt,name=nested" json:"nested,omitempty"` + // This is an int32 in OldMessage. + Num *int64 `protobuf:"varint,2,opt,name=num" json:"num,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NewMessage) Reset() { *m = NewMessage{} } +func (m *NewMessage) String() string { return proto.CompactTextString(m) } +func (*NewMessage) ProtoMessage() {} + +func (m *NewMessage) GetNested() *NewMessage_Nested { + if m != nil { + return m.Nested + } + return nil +} + +func (m *NewMessage) GetNum() int64 { + if m != nil && m.Num != nil { + return *m.Num + } + return 0 +} + +type NewMessage_Nested struct { + Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + FoodGroup *string `protobuf:"bytes,2,opt,name=food_group" json:"food_group,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *NewMessage_Nested) Reset() { *m = NewMessage_Nested{} } +func (m *NewMessage_Nested) String() string { return proto.CompactTextString(m) } +func (*NewMessage_Nested) ProtoMessage() {} + +func (m *NewMessage_Nested) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *NewMessage_Nested) GetFoodGroup() string { + if m != nil && m.FoodGroup != nil { + return *m.FoodGroup + } + return "" +} + +type InnerMessage struct { + Host *string `protobuf:"bytes,1,req,name=host" json:"host,omitempty"` + Port *int32 `protobuf:"varint,2,opt,name=port,def=4000" json:"port,omitempty"` + Connected *bool `protobuf:"varint,3,opt,name=connected" json:"connected,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *InnerMessage) Reset() { *m = InnerMessage{} } +func (m *InnerMessage) String() string { return proto.CompactTextString(m) } +func (*InnerMessage) ProtoMessage() {} + +const Default_InnerMessage_Port int32 = 4000 + +func (m *InnerMessage) GetHost() string { + if m != nil && m.Host != nil { + return *m.Host + } + return "" +} + +func (m *InnerMessage) GetPort() int32 { + if m != nil && m.Port != nil { + return *m.Port + } + return Default_InnerMessage_Port +} + +func (m *InnerMessage) GetConnected() bool { + if m != nil && m.Connected != nil { + return *m.Connected + } + return false +} + +type OtherMessage struct { + Key *int64 `protobuf:"varint,1,opt,name=key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value" json:"value,omitempty"` + Weight *float32 `protobuf:"fixed32,3,opt,name=weight" json:"weight,omitempty"` + Inner *InnerMessage `protobuf:"bytes,4,opt,name=inner" json:"inner,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *OtherMessage) Reset() { *m = OtherMessage{} } +func (m *OtherMessage) String() string { return proto.CompactTextString(m) } +func (*OtherMessage) ProtoMessage() {} + +func (m *OtherMessage) GetKey() int64 { + if m != nil && m.Key != nil { + return *m.Key + } + return 0 +} + +func (m *OtherMessage) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *OtherMessage) GetWeight() float32 { + if m != nil && m.Weight != nil { + return *m.Weight + } + return 0 +} + +func (m *OtherMessage) GetInner() *InnerMessage { + if m != nil { + return m.Inner + } + return nil +} + +type MyMessage struct { + Count *int32 `protobuf:"varint,1,req,name=count" json:"count,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` + Quote *string `protobuf:"bytes,3,opt,name=quote" json:"quote,omitempty"` + Pet []string `protobuf:"bytes,4,rep,name=pet" json:"pet,omitempty"` + Inner *InnerMessage `protobuf:"bytes,5,opt,name=inner" json:"inner,omitempty"` + Others []*OtherMessage `protobuf:"bytes,6,rep,name=others" json:"others,omitempty"` + RepInner []*InnerMessage `protobuf:"bytes,12,rep,name=rep_inner" json:"rep_inner,omitempty"` + Bikeshed *MyMessage_Color `protobuf:"varint,7,opt,name=bikeshed,enum=testdata.MyMessage_Color" json:"bikeshed,omitempty"` + Somegroup *MyMessage_SomeGroup `protobuf:"group,8,opt,name=SomeGroup" json:"somegroup,omitempty"` + // This field becomes [][]byte in the generated code. + RepBytes [][]byte `protobuf:"bytes,10,rep,name=rep_bytes" json:"rep_bytes,omitempty"` + Bigfloat *float64 `protobuf:"fixed64,11,opt,name=bigfloat" json:"bigfloat,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessage) Reset() { *m = MyMessage{} } +func (m *MyMessage) String() string { return proto.CompactTextString(m) } +func (*MyMessage) ProtoMessage() {} + +var extRange_MyMessage = []proto.ExtensionRange{ + {100, 536870911}, +} + +func (*MyMessage) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MyMessage +} +func (m *MyMessage) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *MyMessage) GetCount() int32 { + if m != nil && m.Count != nil { + return *m.Count + } + return 0 +} + +func (m *MyMessage) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *MyMessage) GetQuote() string { + if m != nil && m.Quote != nil { + return *m.Quote + } + return "" +} + +func (m *MyMessage) GetPet() []string { + if m != nil { + return m.Pet + } + return nil +} + +func (m *MyMessage) GetInner() *InnerMessage { + if m != nil { + return m.Inner + } + return nil +} + +func (m *MyMessage) GetOthers() []*OtherMessage { + if m != nil { + return m.Others + } + return nil +} + +func (m *MyMessage) GetRepInner() []*InnerMessage { + if m != nil { + return m.RepInner + } + return nil +} + +func (m *MyMessage) GetBikeshed() MyMessage_Color { + if m != nil && m.Bikeshed != nil { + return *m.Bikeshed + } + return MyMessage_RED +} + +func (m *MyMessage) GetSomegroup() *MyMessage_SomeGroup { + if m != nil { + return m.Somegroup + } + return nil +} + +func (m *MyMessage) GetRepBytes() [][]byte { + if m != nil { + return m.RepBytes + } + return nil +} + +func (m *MyMessage) GetBigfloat() float64 { + if m != nil && m.Bigfloat != nil { + return *m.Bigfloat + } + return 0 +} + +type MyMessage_SomeGroup struct { + GroupField *int32 `protobuf:"varint,9,opt,name=group_field" json:"group_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessage_SomeGroup) Reset() { *m = MyMessage_SomeGroup{} } +func (m *MyMessage_SomeGroup) String() string { return proto.CompactTextString(m) } +func (*MyMessage_SomeGroup) ProtoMessage() {} + +func (m *MyMessage_SomeGroup) GetGroupField() int32 { + if m != nil && m.GroupField != nil { + return *m.GroupField + } + return 0 +} + +type Ext struct { + Data *string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Ext) Reset() { *m = Ext{} } +func (m *Ext) String() string { return proto.CompactTextString(m) } +func (*Ext) ProtoMessage() {} + +func (m *Ext) GetData() string { + if m != nil && m.Data != nil { + return *m.Data + } + return "" +} + +var E_Ext_More = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*Ext)(nil), + Field: 103, + Name: "testdata.Ext.more", + Tag: "bytes,103,opt,name=more", +} + +var E_Ext_Text = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*string)(nil), + Field: 104, + Name: "testdata.Ext.text", + Tag: "bytes,104,opt,name=text", +} + +var E_Ext_Number = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: (*int32)(nil), + Field: 105, + Name: "testdata.Ext.number", + Tag: "varint,105,opt,name=number", +} + +type MyMessageSet struct { + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MyMessageSet) Reset() { *m = MyMessageSet{} } +func (m *MyMessageSet) String() string { return proto.CompactTextString(m) } +func (*MyMessageSet) ProtoMessage() {} + +func (m *MyMessageSet) Marshal() ([]byte, error) { + return proto.MarshalMessageSet(m.ExtensionMap()) +} +func (m *MyMessageSet) Unmarshal(buf []byte) error { + return proto.UnmarshalMessageSet(buf, m.ExtensionMap()) +} + +// ensure MyMessageSet satisfies proto.Marshaler and proto.Unmarshaler +var _ proto.Marshaler = (*MyMessageSet)(nil) +var _ proto.Unmarshaler = (*MyMessageSet)(nil) + +var extRange_MyMessageSet = []proto.ExtensionRange{ + {100, 2147483646}, +} + +func (*MyMessageSet) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_MyMessageSet +} +func (m *MyMessageSet) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +type Empty struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *Empty) Reset() { *m = Empty{} } +func (m *Empty) String() string { return proto.CompactTextString(m) } +func (*Empty) ProtoMessage() {} + +type MessageList struct { + Message []*MessageList_Message `protobuf:"group,1,rep" json:"message,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageList) Reset() { *m = MessageList{} } +func (m *MessageList) String() string { return proto.CompactTextString(m) } +func (*MessageList) ProtoMessage() {} + +func (m *MessageList) GetMessage() []*MessageList_Message { + if m != nil { + return m.Message + } + return nil +} + +type MessageList_Message struct { + Name *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"` + Count *int32 `protobuf:"varint,3,req,name=count" json:"count,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MessageList_Message) Reset() { *m = MessageList_Message{} } +func (m *MessageList_Message) String() string { return proto.CompactTextString(m) } +func (*MessageList_Message) ProtoMessage() {} + +func (m *MessageList_Message) GetName() string { + if m != nil && m.Name != nil { + return *m.Name + } + return "" +} + +func (m *MessageList_Message) GetCount() int32 { + if m != nil && m.Count != nil { + return *m.Count + } + return 0 +} + +type Strings struct { + StringField *string `protobuf:"bytes,1,opt,name=string_field" json:"string_field,omitempty"` + BytesField []byte `protobuf:"bytes,2,opt,name=bytes_field" json:"bytes_field,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Strings) Reset() { *m = Strings{} } +func (m *Strings) String() string { return proto.CompactTextString(m) } +func (*Strings) ProtoMessage() {} + +func (m *Strings) GetStringField() string { + if m != nil && m.StringField != nil { + return *m.StringField + } + return "" +} + +func (m *Strings) GetBytesField() []byte { + if m != nil { + return m.BytesField + } + return nil +} + +type Defaults struct { + // Default-valued fields of all basic types. + // Same as GoTest, but copied here to make testing easier. + F_Bool *bool `protobuf:"varint,1,opt,def=1" json:"F_Bool,omitempty"` + F_Int32 *int32 `protobuf:"varint,2,opt,def=32" json:"F_Int32,omitempty"` + F_Int64 *int64 `protobuf:"varint,3,opt,def=64" json:"F_Int64,omitempty"` + F_Fixed32 *uint32 `protobuf:"fixed32,4,opt,def=320" json:"F_Fixed32,omitempty"` + F_Fixed64 *uint64 `protobuf:"fixed64,5,opt,def=640" json:"F_Fixed64,omitempty"` + F_Uint32 *uint32 `protobuf:"varint,6,opt,def=3200" json:"F_Uint32,omitempty"` + F_Uint64 *uint64 `protobuf:"varint,7,opt,def=6400" json:"F_Uint64,omitempty"` + F_Float *float32 `protobuf:"fixed32,8,opt,def=314159" json:"F_Float,omitempty"` + F_Double *float64 `protobuf:"fixed64,9,opt,def=271828" json:"F_Double,omitempty"` + F_String *string `protobuf:"bytes,10,opt,def=hello, \"world!\"\n" json:"F_String,omitempty"` + F_Bytes []byte `protobuf:"bytes,11,opt,def=Bignose" json:"F_Bytes,omitempty"` + F_Sint32 *int32 `protobuf:"zigzag32,12,opt,def=-32" json:"F_Sint32,omitempty"` + F_Sint64 *int64 `protobuf:"zigzag64,13,opt,def=-64" json:"F_Sint64,omitempty"` + F_Enum *Defaults_Color `protobuf:"varint,14,opt,enum=testdata.Defaults_Color,def=1" json:"F_Enum,omitempty"` + // More fields with crazy defaults. + F_Pinf *float32 `protobuf:"fixed32,15,opt,def=inf" json:"F_Pinf,omitempty"` + F_Ninf *float32 `protobuf:"fixed32,16,opt,def=-inf" json:"F_Ninf,omitempty"` + F_Nan *float32 `protobuf:"fixed32,17,opt,def=nan" json:"F_Nan,omitempty"` + // Sub-message. + Sub *SubDefaults `protobuf:"bytes,18,opt,name=sub" json:"sub,omitempty"` + // Redundant but explicit defaults. + StrZero *string `protobuf:"bytes,19,opt,name=str_zero,def=" json:"str_zero,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Defaults) Reset() { *m = Defaults{} } +func (m *Defaults) String() string { return proto.CompactTextString(m) } +func (*Defaults) ProtoMessage() {} + +const Default_Defaults_F_Bool bool = true +const Default_Defaults_F_Int32 int32 = 32 +const Default_Defaults_F_Int64 int64 = 64 +const Default_Defaults_F_Fixed32 uint32 = 320 +const Default_Defaults_F_Fixed64 uint64 = 640 +const Default_Defaults_F_Uint32 uint32 = 3200 +const Default_Defaults_F_Uint64 uint64 = 6400 +const Default_Defaults_F_Float float32 = 314159 +const Default_Defaults_F_Double float64 = 271828 +const Default_Defaults_F_String string = "hello, \"world!\"\n" + +var Default_Defaults_F_Bytes []byte = []byte("Bignose") + +const Default_Defaults_F_Sint32 int32 = -32 +const Default_Defaults_F_Sint64 int64 = -64 +const Default_Defaults_F_Enum Defaults_Color = Defaults_GREEN + +var Default_Defaults_F_Pinf float32 = float32(math.Inf(1)) +var Default_Defaults_F_Ninf float32 = float32(math.Inf(-1)) +var Default_Defaults_F_Nan float32 = float32(math.NaN()) + +func (m *Defaults) GetF_Bool() bool { + if m != nil && m.F_Bool != nil { + return *m.F_Bool + } + return Default_Defaults_F_Bool +} + +func (m *Defaults) GetF_Int32() int32 { + if m != nil && m.F_Int32 != nil { + return *m.F_Int32 + } + return Default_Defaults_F_Int32 +} + +func (m *Defaults) GetF_Int64() int64 { + if m != nil && m.F_Int64 != nil { + return *m.F_Int64 + } + return Default_Defaults_F_Int64 +} + +func (m *Defaults) GetF_Fixed32() uint32 { + if m != nil && m.F_Fixed32 != nil { + return *m.F_Fixed32 + } + return Default_Defaults_F_Fixed32 +} + +func (m *Defaults) GetF_Fixed64() uint64 { + if m != nil && m.F_Fixed64 != nil { + return *m.F_Fixed64 + } + return Default_Defaults_F_Fixed64 +} + +func (m *Defaults) GetF_Uint32() uint32 { + if m != nil && m.F_Uint32 != nil { + return *m.F_Uint32 + } + return Default_Defaults_F_Uint32 +} + +func (m *Defaults) GetF_Uint64() uint64 { + if m != nil && m.F_Uint64 != nil { + return *m.F_Uint64 + } + return Default_Defaults_F_Uint64 +} + +func (m *Defaults) GetF_Float() float32 { + if m != nil && m.F_Float != nil { + return *m.F_Float + } + return Default_Defaults_F_Float +} + +func (m *Defaults) GetF_Double() float64 { + if m != nil && m.F_Double != nil { + return *m.F_Double + } + return Default_Defaults_F_Double +} + +func (m *Defaults) GetF_String() string { + if m != nil && m.F_String != nil { + return *m.F_String + } + return Default_Defaults_F_String +} + +func (m *Defaults) GetF_Bytes() []byte { + if m != nil && m.F_Bytes != nil { + return m.F_Bytes + } + return append([]byte(nil), Default_Defaults_F_Bytes...) +} + +func (m *Defaults) GetF_Sint32() int32 { + if m != nil && m.F_Sint32 != nil { + return *m.F_Sint32 + } + return Default_Defaults_F_Sint32 +} + +func (m *Defaults) GetF_Sint64() int64 { + if m != nil && m.F_Sint64 != nil { + return *m.F_Sint64 + } + return Default_Defaults_F_Sint64 +} + +func (m *Defaults) GetF_Enum() Defaults_Color { + if m != nil && m.F_Enum != nil { + return *m.F_Enum + } + return Default_Defaults_F_Enum +} + +func (m *Defaults) GetF_Pinf() float32 { + if m != nil && m.F_Pinf != nil { + return *m.F_Pinf + } + return Default_Defaults_F_Pinf +} + +func (m *Defaults) GetF_Ninf() float32 { + if m != nil && m.F_Ninf != nil { + return *m.F_Ninf + } + return Default_Defaults_F_Ninf +} + +func (m *Defaults) GetF_Nan() float32 { + if m != nil && m.F_Nan != nil { + return *m.F_Nan + } + return Default_Defaults_F_Nan +} + +func (m *Defaults) GetSub() *SubDefaults { + if m != nil { + return m.Sub + } + return nil +} + +func (m *Defaults) GetStrZero() string { + if m != nil && m.StrZero != nil { + return *m.StrZero + } + return "" +} + +type SubDefaults struct { + N *int64 `protobuf:"varint,1,opt,name=n,def=7" json:"n,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *SubDefaults) Reset() { *m = SubDefaults{} } +func (m *SubDefaults) String() string { return proto.CompactTextString(m) } +func (*SubDefaults) ProtoMessage() {} + +const Default_SubDefaults_N int64 = 7 + +func (m *SubDefaults) GetN() int64 { + if m != nil && m.N != nil { + return *m.N + } + return Default_SubDefaults_N +} + +type RepeatedEnum struct { + Color []RepeatedEnum_Color `protobuf:"varint,1,rep,name=color,enum=testdata.RepeatedEnum_Color" json:"color,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *RepeatedEnum) Reset() { *m = RepeatedEnum{} } +func (m *RepeatedEnum) String() string { return proto.CompactTextString(m) } +func (*RepeatedEnum) ProtoMessage() {} + +func (m *RepeatedEnum) GetColor() []RepeatedEnum_Color { + if m != nil { + return m.Color + } + return nil +} + +type MoreRepeated struct { + Bools []bool `protobuf:"varint,1,rep,name=bools" json:"bools,omitempty"` + BoolsPacked []bool `protobuf:"varint,2,rep,packed,name=bools_packed" json:"bools_packed,omitempty"` + Ints []int32 `protobuf:"varint,3,rep,name=ints" json:"ints,omitempty"` + IntsPacked []int32 `protobuf:"varint,4,rep,packed,name=ints_packed" json:"ints_packed,omitempty"` + Int64SPacked []int64 `protobuf:"varint,7,rep,packed,name=int64s_packed" json:"int64s_packed,omitempty"` + Strings []string `protobuf:"bytes,5,rep,name=strings" json:"strings,omitempty"` + Fixeds []uint32 `protobuf:"fixed32,6,rep,name=fixeds" json:"fixeds,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *MoreRepeated) Reset() { *m = MoreRepeated{} } +func (m *MoreRepeated) String() string { return proto.CompactTextString(m) } +func (*MoreRepeated) ProtoMessage() {} + +func (m *MoreRepeated) GetBools() []bool { + if m != nil { + return m.Bools + } + return nil +} + +func (m *MoreRepeated) GetBoolsPacked() []bool { + if m != nil { + return m.BoolsPacked + } + return nil +} + +func (m *MoreRepeated) GetInts() []int32 { + if m != nil { + return m.Ints + } + return nil +} + +func (m *MoreRepeated) GetIntsPacked() []int32 { + if m != nil { + return m.IntsPacked + } + return nil +} + +func (m *MoreRepeated) GetInt64SPacked() []int64 { + if m != nil { + return m.Int64SPacked + } + return nil +} + +func (m *MoreRepeated) GetStrings() []string { + if m != nil { + return m.Strings + } + return nil +} + +func (m *MoreRepeated) GetFixeds() []uint32 { + if m != nil { + return m.Fixeds + } + return nil +} + +type GroupOld struct { + G *GroupOld_G `protobuf:"group,101,opt" json:"g,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupOld) Reset() { *m = GroupOld{} } +func (m *GroupOld) String() string { return proto.CompactTextString(m) } +func (*GroupOld) ProtoMessage() {} + +func (m *GroupOld) GetG() *GroupOld_G { + if m != nil { + return m.G + } + return nil +} + +type GroupOld_G struct { + X *int32 `protobuf:"varint,2,opt,name=x" json:"x,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupOld_G) Reset() { *m = GroupOld_G{} } +func (m *GroupOld_G) String() string { return proto.CompactTextString(m) } +func (*GroupOld_G) ProtoMessage() {} + +func (m *GroupOld_G) GetX() int32 { + if m != nil && m.X != nil { + return *m.X + } + return 0 +} + +type GroupNew struct { + G *GroupNew_G `protobuf:"group,101,opt" json:"g,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupNew) Reset() { *m = GroupNew{} } +func (m *GroupNew) String() string { return proto.CompactTextString(m) } +func (*GroupNew) ProtoMessage() {} + +func (m *GroupNew) GetG() *GroupNew_G { + if m != nil { + return m.G + } + return nil +} + +type GroupNew_G struct { + X *int32 `protobuf:"varint,2,opt,name=x" json:"x,omitempty"` + Y *int32 `protobuf:"varint,3,opt,name=y" json:"y,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *GroupNew_G) Reset() { *m = GroupNew_G{} } +func (m *GroupNew_G) String() string { return proto.CompactTextString(m) } +func (*GroupNew_G) ProtoMessage() {} + +func (m *GroupNew_G) GetX() int32 { + if m != nil && m.X != nil { + return *m.X + } + return 0 +} + +func (m *GroupNew_G) GetY() int32 { + if m != nil && m.Y != nil { + return *m.Y + } + return 0 +} + +type FloatingPoint struct { + F *float64 `protobuf:"fixed64,1,req,name=f" json:"f,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *FloatingPoint) Reset() { *m = FloatingPoint{} } +func (m *FloatingPoint) String() string { return proto.CompactTextString(m) } +func (*FloatingPoint) ProtoMessage() {} + +func (m *FloatingPoint) GetF() float64 { + if m != nil && m.F != nil { + return *m.F + } + return 0 +} + +var E_Greeting = &proto.ExtensionDesc{ + ExtendedType: (*MyMessage)(nil), + ExtensionType: ([]string)(nil), + Field: 106, + Name: "testdata.greeting", + Tag: "bytes,106,rep,name=greeting", +} + +var E_X201 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 201, + Name: "testdata.x201", + Tag: "bytes,201,opt,name=x201", +} + +var E_X202 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 202, + Name: "testdata.x202", + Tag: "bytes,202,opt,name=x202", +} + +var E_X203 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 203, + Name: "testdata.x203", + Tag: "bytes,203,opt,name=x203", +} + +var E_X204 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 204, + Name: "testdata.x204", + Tag: "bytes,204,opt,name=x204", +} + +var E_X205 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 205, + Name: "testdata.x205", + Tag: "bytes,205,opt,name=x205", +} + +var E_X206 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 206, + Name: "testdata.x206", + Tag: "bytes,206,opt,name=x206", +} + +var E_X207 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 207, + Name: "testdata.x207", + Tag: "bytes,207,opt,name=x207", +} + +var E_X208 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 208, + Name: "testdata.x208", + Tag: "bytes,208,opt,name=x208", +} + +var E_X209 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 209, + Name: "testdata.x209", + Tag: "bytes,209,opt,name=x209", +} + +var E_X210 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 210, + Name: "testdata.x210", + Tag: "bytes,210,opt,name=x210", +} + +var E_X211 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 211, + Name: "testdata.x211", + Tag: "bytes,211,opt,name=x211", +} + +var E_X212 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 212, + Name: "testdata.x212", + Tag: "bytes,212,opt,name=x212", +} + +var E_X213 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 213, + Name: "testdata.x213", + Tag: "bytes,213,opt,name=x213", +} + +var E_X214 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 214, + Name: "testdata.x214", + Tag: "bytes,214,opt,name=x214", +} + +var E_X215 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 215, + Name: "testdata.x215", + Tag: "bytes,215,opt,name=x215", +} + +var E_X216 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 216, + Name: "testdata.x216", + Tag: "bytes,216,opt,name=x216", +} + +var E_X217 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 217, + Name: "testdata.x217", + Tag: "bytes,217,opt,name=x217", +} + +var E_X218 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 218, + Name: "testdata.x218", + Tag: "bytes,218,opt,name=x218", +} + +var E_X219 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 219, + Name: "testdata.x219", + Tag: "bytes,219,opt,name=x219", +} + +var E_X220 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 220, + Name: "testdata.x220", + Tag: "bytes,220,opt,name=x220", +} + +var E_X221 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 221, + Name: "testdata.x221", + Tag: "bytes,221,opt,name=x221", +} + +var E_X222 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 222, + Name: "testdata.x222", + Tag: "bytes,222,opt,name=x222", +} + +var E_X223 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 223, + Name: "testdata.x223", + Tag: "bytes,223,opt,name=x223", +} + +var E_X224 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 224, + Name: "testdata.x224", + Tag: "bytes,224,opt,name=x224", +} + +var E_X225 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 225, + Name: "testdata.x225", + Tag: "bytes,225,opt,name=x225", +} + +var E_X226 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 226, + Name: "testdata.x226", + Tag: "bytes,226,opt,name=x226", +} + +var E_X227 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 227, + Name: "testdata.x227", + Tag: "bytes,227,opt,name=x227", +} + +var E_X228 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 228, + Name: "testdata.x228", + Tag: "bytes,228,opt,name=x228", +} + +var E_X229 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 229, + Name: "testdata.x229", + Tag: "bytes,229,opt,name=x229", +} + +var E_X230 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 230, + Name: "testdata.x230", + Tag: "bytes,230,opt,name=x230", +} + +var E_X231 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 231, + Name: "testdata.x231", + Tag: "bytes,231,opt,name=x231", +} + +var E_X232 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 232, + Name: "testdata.x232", + Tag: "bytes,232,opt,name=x232", +} + +var E_X233 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 233, + Name: "testdata.x233", + Tag: "bytes,233,opt,name=x233", +} + +var E_X234 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 234, + Name: "testdata.x234", + Tag: "bytes,234,opt,name=x234", +} + +var E_X235 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 235, + Name: "testdata.x235", + Tag: "bytes,235,opt,name=x235", +} + +var E_X236 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 236, + Name: "testdata.x236", + Tag: "bytes,236,opt,name=x236", +} + +var E_X237 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 237, + Name: "testdata.x237", + Tag: "bytes,237,opt,name=x237", +} + +var E_X238 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 238, + Name: "testdata.x238", + Tag: "bytes,238,opt,name=x238", +} + +var E_X239 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 239, + Name: "testdata.x239", + Tag: "bytes,239,opt,name=x239", +} + +var E_X240 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 240, + Name: "testdata.x240", + Tag: "bytes,240,opt,name=x240", +} + +var E_X241 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 241, + Name: "testdata.x241", + Tag: "bytes,241,opt,name=x241", +} + +var E_X242 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 242, + Name: "testdata.x242", + Tag: "bytes,242,opt,name=x242", +} + +var E_X243 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 243, + Name: "testdata.x243", + Tag: "bytes,243,opt,name=x243", +} + +var E_X244 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 244, + Name: "testdata.x244", + Tag: "bytes,244,opt,name=x244", +} + +var E_X245 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 245, + Name: "testdata.x245", + Tag: "bytes,245,opt,name=x245", +} + +var E_X246 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 246, + Name: "testdata.x246", + Tag: "bytes,246,opt,name=x246", +} + +var E_X247 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 247, + Name: "testdata.x247", + Tag: "bytes,247,opt,name=x247", +} + +var E_X248 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 248, + Name: "testdata.x248", + Tag: "bytes,248,opt,name=x248", +} + +var E_X249 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 249, + Name: "testdata.x249", + Tag: "bytes,249,opt,name=x249", +} + +var E_X250 = &proto.ExtensionDesc{ + ExtendedType: (*MyMessageSet)(nil), + ExtensionType: (*Empty)(nil), + Field: 250, + Name: "testdata.x250", + Tag: "bytes,250,opt,name=x250", +} + +func init() { + proto.RegisterEnum("testdata.FOO", FOO_name, FOO_value) + proto.RegisterEnum("testdata.GoTest_KIND", GoTest_KIND_name, GoTest_KIND_value) + proto.RegisterEnum("testdata.MyMessage_Color", MyMessage_Color_name, MyMessage_Color_value) + proto.RegisterEnum("testdata.Defaults_Color", Defaults_Color_name, Defaults_Color_value) + proto.RegisterEnum("testdata.RepeatedEnum_Color", RepeatedEnum_Color_name, RepeatedEnum_Color_value) + proto.RegisterExtension(E_Ext_More) + proto.RegisterExtension(E_Ext_Text) + proto.RegisterExtension(E_Ext_Number) + proto.RegisterExtension(E_Greeting) + proto.RegisterExtension(E_X201) + proto.RegisterExtension(E_X202) + proto.RegisterExtension(E_X203) + proto.RegisterExtension(E_X204) + proto.RegisterExtension(E_X205) + proto.RegisterExtension(E_X206) + proto.RegisterExtension(E_X207) + proto.RegisterExtension(E_X208) + proto.RegisterExtension(E_X209) + proto.RegisterExtension(E_X210) + proto.RegisterExtension(E_X211) + proto.RegisterExtension(E_X212) + proto.RegisterExtension(E_X213) + proto.RegisterExtension(E_X214) + proto.RegisterExtension(E_X215) + proto.RegisterExtension(E_X216) + proto.RegisterExtension(E_X217) + proto.RegisterExtension(E_X218) + proto.RegisterExtension(E_X219) + proto.RegisterExtension(E_X220) + proto.RegisterExtension(E_X221) + proto.RegisterExtension(E_X222) + proto.RegisterExtension(E_X223) + proto.RegisterExtension(E_X224) + proto.RegisterExtension(E_X225) + proto.RegisterExtension(E_X226) + proto.RegisterExtension(E_X227) + proto.RegisterExtension(E_X228) + proto.RegisterExtension(E_X229) + proto.RegisterExtension(E_X230) + proto.RegisterExtension(E_X231) + proto.RegisterExtension(E_X232) + proto.RegisterExtension(E_X233) + proto.RegisterExtension(E_X234) + proto.RegisterExtension(E_X235) + proto.RegisterExtension(E_X236) + proto.RegisterExtension(E_X237) + proto.RegisterExtension(E_X238) + proto.RegisterExtension(E_X239) + proto.RegisterExtension(E_X240) + proto.RegisterExtension(E_X241) + proto.RegisterExtension(E_X242) + proto.RegisterExtension(E_X243) + proto.RegisterExtension(E_X244) + proto.RegisterExtension(E_X245) + proto.RegisterExtension(E_X246) + proto.RegisterExtension(E_X247) + proto.RegisterExtension(E_X248) + proto.RegisterExtension(E_X249) + proto.RegisterExtension(E_X250) +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto new file mode 100644 index 0000000..9c0c4c8 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.proto @@ -0,0 +1,428 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// A feature-rich test file for the protocol compiler and libraries. + +syntax = "proto2"; + +package testdata; + +enum FOO { FOO1 = 1; }; + +message GoEnum { + required FOO foo = 1; +} + +message GoTestField { + required string Label = 1; + required string Type = 2; +} + +message GoTest { + // An enum, for completeness. + enum KIND { + VOID = 0; + + // Basic types + BOOL = 1; + BYTES = 2; + FINGERPRINT = 3; + FLOAT = 4; + INT = 5; + STRING = 6; + TIME = 7; + + // Groupings + TUPLE = 8; + ARRAY = 9; + MAP = 10; + + // Table types + TABLE = 11; + + // Functions + FUNCTION = 12; // last tag + }; + + // Some typical parameters + required KIND Kind = 1; + optional string Table = 2; + optional int32 Param = 3; + + // Required, repeated and optional foreign fields. + required GoTestField RequiredField = 4; + repeated GoTestField RepeatedField = 5; + optional GoTestField OptionalField = 6; + + // Required fields of all basic types + required bool F_Bool_required = 10; + required int32 F_Int32_required = 11; + required int64 F_Int64_required = 12; + required fixed32 F_Fixed32_required = 13; + required fixed64 F_Fixed64_required = 14; + required uint32 F_Uint32_required = 15; + required uint64 F_Uint64_required = 16; + required float F_Float_required = 17; + required double F_Double_required = 18; + required string F_String_required = 19; + required bytes F_Bytes_required = 101; + required sint32 F_Sint32_required = 102; + required sint64 F_Sint64_required = 103; + + // Repeated fields of all basic types + repeated bool F_Bool_repeated = 20; + repeated int32 F_Int32_repeated = 21; + repeated int64 F_Int64_repeated = 22; + repeated fixed32 F_Fixed32_repeated = 23; + repeated fixed64 F_Fixed64_repeated = 24; + repeated uint32 F_Uint32_repeated = 25; + repeated uint64 F_Uint64_repeated = 26; + repeated float F_Float_repeated = 27; + repeated double F_Double_repeated = 28; + repeated string F_String_repeated = 29; + repeated bytes F_Bytes_repeated = 201; + repeated sint32 F_Sint32_repeated = 202; + repeated sint64 F_Sint64_repeated = 203; + + // Optional fields of all basic types + optional bool F_Bool_optional = 30; + optional int32 F_Int32_optional = 31; + optional int64 F_Int64_optional = 32; + optional fixed32 F_Fixed32_optional = 33; + optional fixed64 F_Fixed64_optional = 34; + optional uint32 F_Uint32_optional = 35; + optional uint64 F_Uint64_optional = 36; + optional float F_Float_optional = 37; + optional double F_Double_optional = 38; + optional string F_String_optional = 39; + optional bytes F_Bytes_optional = 301; + optional sint32 F_Sint32_optional = 302; + optional sint64 F_Sint64_optional = 303; + + // Default-valued fields of all basic types + optional bool F_Bool_defaulted = 40 [default=true]; + optional int32 F_Int32_defaulted = 41 [default=32]; + optional int64 F_Int64_defaulted = 42 [default=64]; + optional fixed32 F_Fixed32_defaulted = 43 [default=320]; + optional fixed64 F_Fixed64_defaulted = 44 [default=640]; + optional uint32 F_Uint32_defaulted = 45 [default=3200]; + optional uint64 F_Uint64_defaulted = 46 [default=6400]; + optional float F_Float_defaulted = 47 [default=314159.]; + optional double F_Double_defaulted = 48 [default=271828.]; + optional string F_String_defaulted = 49 [default="hello, \"world!\"\n"]; + optional bytes F_Bytes_defaulted = 401 [default="Bignose"]; + optional sint32 F_Sint32_defaulted = 402 [default = -32]; + optional sint64 F_Sint64_defaulted = 403 [default = -64]; + + // Packed repeated fields (no string or bytes). + repeated bool F_Bool_repeated_packed = 50 [packed=true]; + repeated int32 F_Int32_repeated_packed = 51 [packed=true]; + repeated int64 F_Int64_repeated_packed = 52 [packed=true]; + repeated fixed32 F_Fixed32_repeated_packed = 53 [packed=true]; + repeated fixed64 F_Fixed64_repeated_packed = 54 [packed=true]; + repeated uint32 F_Uint32_repeated_packed = 55 [packed=true]; + repeated uint64 F_Uint64_repeated_packed = 56 [packed=true]; + repeated float F_Float_repeated_packed = 57 [packed=true]; + repeated double F_Double_repeated_packed = 58 [packed=true]; + repeated sint32 F_Sint32_repeated_packed = 502 [packed=true]; + repeated sint64 F_Sint64_repeated_packed = 503 [packed=true]; + + // Required, repeated, and optional groups. + required group RequiredGroup = 70 { + required string RequiredField = 71; + }; + + repeated group RepeatedGroup = 80 { + required string RequiredField = 81; + }; + + optional group OptionalGroup = 90 { + required string RequiredField = 91; + }; +} + +// For testing skipping of unrecognized fields. +// Numbers are all big, larger than tag numbers in GoTestField, +// the message used in the corresponding test. +message GoSkipTest { + required int32 skip_int32 = 11; + required fixed32 skip_fixed32 = 12; + required fixed64 skip_fixed64 = 13; + required string skip_string = 14; + required group SkipGroup = 15 { + required int32 group_int32 = 16; + required string group_string = 17; + } +} + +// For testing packed/non-packed decoder switching. +// A serialized instance of one should be deserializable as the other. +message NonPackedTest { + repeated int32 a = 1; +} + +message PackedTest { + repeated int32 b = 1 [packed=true]; +} + +message MaxTag { + // Maximum possible tag number. + optional string last_field = 536870911; +} + +message OldMessage { + message Nested { + optional string name = 1; + } + optional Nested nested = 1; + + optional int32 num = 2; +} + +// NewMessage is wire compatible with OldMessage; +// imagine it as a future version. +message NewMessage { + message Nested { + optional string name = 1; + optional string food_group = 2; + } + optional Nested nested = 1; + + // This is an int32 in OldMessage. + optional int64 num = 2; +} + +// Smaller tests for ASCII formatting. + +message InnerMessage { + required string host = 1; + optional int32 port = 2 [default=4000]; + optional bool connected = 3; +} + +message OtherMessage { + optional int64 key = 1; + optional bytes value = 2; + optional float weight = 3; + optional InnerMessage inner = 4; +} + +message MyMessage { + required int32 count = 1; + optional string name = 2; + optional string quote = 3; + repeated string pet = 4; + optional InnerMessage inner = 5; + repeated OtherMessage others = 6; + repeated InnerMessage rep_inner = 12; + + enum Color { + RED = 0; + GREEN = 1; + BLUE = 2; + }; + optional Color bikeshed = 7; + + optional group SomeGroup = 8 { + optional int32 group_field = 9; + } + + // This field becomes [][]byte in the generated code. + repeated bytes rep_bytes = 10; + + optional double bigfloat = 11; + + extensions 100 to max; +} + +message Ext { + extend MyMessage { + optional Ext more = 103; + optional string text = 104; + optional int32 number = 105; + } + + optional string data = 1; +} + +extend MyMessage { + repeated string greeting = 106; +} + +message MyMessageSet { + option message_set_wire_format = true; + extensions 100 to max; +} + +message Empty { +} + +extend MyMessageSet { + optional Empty x201 = 201; + optional Empty x202 = 202; + optional Empty x203 = 203; + optional Empty x204 = 204; + optional Empty x205 = 205; + optional Empty x206 = 206; + optional Empty x207 = 207; + optional Empty x208 = 208; + optional Empty x209 = 209; + optional Empty x210 = 210; + optional Empty x211 = 211; + optional Empty x212 = 212; + optional Empty x213 = 213; + optional Empty x214 = 214; + optional Empty x215 = 215; + optional Empty x216 = 216; + optional Empty x217 = 217; + optional Empty x218 = 218; + optional Empty x219 = 219; + optional Empty x220 = 220; + optional Empty x221 = 221; + optional Empty x222 = 222; + optional Empty x223 = 223; + optional Empty x224 = 224; + optional Empty x225 = 225; + optional Empty x226 = 226; + optional Empty x227 = 227; + optional Empty x228 = 228; + optional Empty x229 = 229; + optional Empty x230 = 230; + optional Empty x231 = 231; + optional Empty x232 = 232; + optional Empty x233 = 233; + optional Empty x234 = 234; + optional Empty x235 = 235; + optional Empty x236 = 236; + optional Empty x237 = 237; + optional Empty x238 = 238; + optional Empty x239 = 239; + optional Empty x240 = 240; + optional Empty x241 = 241; + optional Empty x242 = 242; + optional Empty x243 = 243; + optional Empty x244 = 244; + optional Empty x245 = 245; + optional Empty x246 = 246; + optional Empty x247 = 247; + optional Empty x248 = 248; + optional Empty x249 = 249; + optional Empty x250 = 250; +} + +message MessageList { + repeated group Message = 1 { + required string name = 2; + required int32 count = 3; + } +} + +message Strings { + optional string string_field = 1; + optional bytes bytes_field = 2; +} + +message Defaults { + enum Color { + RED = 0; + GREEN = 1; + BLUE = 2; + } + + // Default-valued fields of all basic types. + // Same as GoTest, but copied here to make testing easier. + optional bool F_Bool = 1 [default=true]; + optional int32 F_Int32 = 2 [default=32]; + optional int64 F_Int64 = 3 [default=64]; + optional fixed32 F_Fixed32 = 4 [default=320]; + optional fixed64 F_Fixed64 = 5 [default=640]; + optional uint32 F_Uint32 = 6 [default=3200]; + optional uint64 F_Uint64 = 7 [default=6400]; + optional float F_Float = 8 [default=314159.]; + optional double F_Double = 9 [default=271828.]; + optional string F_String = 10 [default="hello, \"world!\"\n"]; + optional bytes F_Bytes = 11 [default="Bignose"]; + optional sint32 F_Sint32 = 12 [default=-32]; + optional sint64 F_Sint64 = 13 [default=-64]; + optional Color F_Enum = 14 [default=GREEN]; + + // More fields with crazy defaults. + optional float F_Pinf = 15 [default=inf]; + optional float F_Ninf = 16 [default=-inf]; + optional float F_Nan = 17 [default=nan]; + + // Sub-message. + optional SubDefaults sub = 18; + + // Redundant but explicit defaults. + optional string str_zero = 19 [default=""]; +} + +message SubDefaults { + optional int64 n = 1 [default=7]; +} + +message RepeatedEnum { + enum Color { + RED = 1; + } + repeated Color color = 1; +} + +message MoreRepeated { + repeated bool bools = 1; + repeated bool bools_packed = 2 [packed=true]; + repeated int32 ints = 3; + repeated int32 ints_packed = 4 [packed=true]; + repeated int64 int64s_packed = 7 [packed=true]; + repeated string strings = 5; + repeated fixed32 fixeds = 6; +} + +// GroupOld and GroupNew have the same wire format. +// GroupNew has a new field inside a group. + +message GroupOld { + optional group G = 101 { + optional int32 x = 2; + } +} + +message GroupNew { + optional group G = 101 { + optional int32 x = 2; + optional int32 y = 3; + } +} + +message FloatingPoint { + required double f = 1; +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go new file mode 100644 index 0000000..5063009 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go @@ -0,0 +1,701 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +// Functions for writing the text protocol buffer format. + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "math" + "os" + "reflect" + "sort" + "strings" +) + +var ( + newline = []byte("\n") + spaces = []byte(" ") + gtNewline = []byte(">\n") + endBraceNewline = []byte("}\n") + backslashN = []byte{'\\', 'n'} + backslashR = []byte{'\\', 'r'} + backslashT = []byte{'\\', 't'} + backslashDQ = []byte{'\\', '"'} + backslashBS = []byte{'\\', '\\'} + posInf = []byte("inf") + negInf = []byte("-inf") + nan = []byte("nan") +) + +type writer interface { + io.Writer + WriteByte(byte) error +} + +// textWriter is an io.Writer that tracks its indentation level. +type textWriter struct { + ind int + complete bool // if the current position is a complete line + compact bool // whether to write out as a one-liner + w writer +} + +// textMarshaler is implemented by Messages that can marshal themsleves. +// It is identical to encoding.TextMarshaler, introduced in go 1.2, +// which will eventually replace it. +type textMarshaler interface { + MarshalText() (text []byte, err error) +} + +func (w *textWriter) WriteString(s string) (n int, err error) { + if !strings.Contains(s, "\n") { + if !w.compact && w.complete { + w.writeIndent() + } + w.complete = false + return io.WriteString(w.w, s) + } + // WriteString is typically called without newlines, so this + // codepath and its copy are rare. We copy to avoid + // duplicating all of Write's logic here. + return w.Write([]byte(s)) +} + +func (w *textWriter) Write(p []byte) (n int, err error) { + newlines := bytes.Count(p, newline) + if newlines == 0 { + if !w.compact && w.complete { + w.writeIndent() + } + n, err = w.w.Write(p) + w.complete = false + return n, err + } + + frags := bytes.SplitN(p, newline, newlines+1) + if w.compact { + for i, frag := range frags { + if i > 0 { + if err := w.w.WriteByte(' '); err != nil { + return n, err + } + n++ + } + nn, err := w.w.Write(frag) + n += nn + if err != nil { + return n, err + } + } + return n, nil + } + + for i, frag := range frags { + if w.complete { + w.writeIndent() + } + nn, err := w.w.Write(frag) + n += nn + if err != nil { + return n, err + } + if i+1 < len(frags) { + if err := w.w.WriteByte('\n'); err != nil { + return n, err + } + n++ + } + } + w.complete = len(frags[len(frags)-1]) == 0 + return n, nil +} + +func (w *textWriter) WriteByte(c byte) error { + if w.compact && c == '\n' { + c = ' ' + } + if !w.compact && w.complete { + w.writeIndent() + } + err := w.w.WriteByte(c) + w.complete = c == '\n' + return err +} + +func (w *textWriter) indent() { w.ind++ } + +func (w *textWriter) unindent() { + if w.ind == 0 { + log.Printf("proto: textWriter unindented too far") + return + } + w.ind-- +} + +func writeName(w *textWriter, props *Properties) error { + if _, err := w.WriteString(props.OrigName); err != nil { + return err + } + if props.Wire != "group" { + return w.WriteByte(':') + } + return nil +} + +var ( + messageSetType = reflect.TypeOf((*MessageSet)(nil)).Elem() +) + +// raw is the interface satisfied by RawMessage. +type raw interface { + Bytes() []byte +} + +func writeStruct(w *textWriter, sv reflect.Value) error { + if sv.Type() == messageSetType { + return writeMessageSet(w, sv.Addr().Interface().(*MessageSet)) + } + + st := sv.Type() + sprops := GetProperties(st) + for i := 0; i < sv.NumField(); i++ { + fv := sv.Field(i) + props := sprops.Prop[i] + name := st.Field(i).Name + + if strings.HasPrefix(name, "XXX_") { + // There are two XXX_ fields: + // XXX_unrecognized []byte + // XXX_extensions map[int32]proto.Extension + // The first is handled here; + // the second is handled at the bottom of this function. + if name == "XXX_unrecognized" && !fv.IsNil() { + if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil { + return err + } + } + continue + } + if fv.Kind() == reflect.Ptr && fv.IsNil() { + // Field not filled in. This could be an optional field or + // a required field that wasn't filled in. Either way, there + // isn't anything we can show for it. + continue + } + if fv.Kind() == reflect.Slice && fv.IsNil() { + // Repeated field that is empty, or a bytes field that is unused. + continue + } + + if props.Repeated && fv.Kind() == reflect.Slice { + // Repeated field. + for j := 0; j < fv.Len(); j++ { + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + v := fv.Index(j) + if v.Kind() == reflect.Ptr && v.IsNil() { + // A nil message in a repeated field is not valid, + // but we can handle that more gracefully than panicking. + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + continue + } + if err := writeAny(w, v, props); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + } + continue + } + + if err := writeName(w, props); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if b, ok := fv.Interface().(raw); ok { + if err := writeRaw(w, b.Bytes()); err != nil { + return err + } + continue + } + + // Enums have a String method, so writeAny will work fine. + if err := writeAny(w, fv, props); err != nil { + return err + } + + if err := w.WriteByte('\n'); err != nil { + return err + } + } + + // Extensions (the XXX_extensions field). + pv := sv.Addr() + if pv.Type().Implements(extendableProtoType) { + if err := writeExtensions(w, pv); err != nil { + return err + } + } + + return nil +} + +// writeRaw writes an uninterpreted raw message. +func writeRaw(w *textWriter, b []byte) error { + if err := w.WriteByte('<'); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + if err := writeUnknownStruct(w, b); err != nil { + return err + } + w.unindent() + if err := w.WriteByte('>'); err != nil { + return err + } + return nil +} + +// writeAny writes an arbitrary field. +func writeAny(w *textWriter, v reflect.Value, props *Properties) error { + v = reflect.Indirect(v) + + // Floats have special cases. + if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 { + x := v.Float() + var b []byte + switch { + case math.IsInf(x, 1): + b = posInf + case math.IsInf(x, -1): + b = negInf + case math.IsNaN(x): + b = nan + } + if b != nil { + _, err := w.Write(b) + return err + } + // Other values are handled below. + } + + // We don't attempt to serialise every possible value type; only those + // that can occur in protocol buffers. + switch v.Kind() { + case reflect.Slice: + // Should only be a []byte; repeated fields are handled in writeStruct. + if err := writeString(w, string(v.Interface().([]byte))); err != nil { + return err + } + case reflect.String: + if err := writeString(w, v.String()); err != nil { + return err + } + case reflect.Struct: + // Required/optional group/message. + var bra, ket byte = '<', '>' + if props != nil && props.Wire == "group" { + bra, ket = '{', '}' + } + if err := w.WriteByte(bra); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte('\n'); err != nil { + return err + } + } + w.indent() + if tm, ok := v.Interface().(textMarshaler); ok { + text, err := tm.MarshalText() + if err != nil { + return err + } + if _, err = w.Write(text); err != nil { + return err + } + } else if err := writeStruct(w, v); err != nil { + return err + } + w.unindent() + if err := w.WriteByte(ket); err != nil { + return err + } + default: + _, err := fmt.Fprint(w, v.Interface()) + return err + } + return nil +} + +// equivalent to C's isprint. +func isprint(c byte) bool { + return c >= 0x20 && c < 0x7f +} + +// writeString writes a string in the protocol buffer text format. +// It is similar to strconv.Quote except we don't use Go escape sequences, +// we treat the string as a byte sequence, and we use octal escapes. +// These differences are to maintain interoperability with the other +// languages' implementations of the text format. +func writeString(w *textWriter, s string) error { + // use WriteByte here to get any needed indent + if err := w.WriteByte('"'); err != nil { + return err + } + // Loop over the bytes, not the runes. + for i := 0; i < len(s); i++ { + var err error + // Divergence from C++: we don't escape apostrophes. + // There's no need to escape them, and the C++ parser + // copes with a naked apostrophe. + switch c := s[i]; c { + case '\n': + _, err = w.w.Write(backslashN) + case '\r': + _, err = w.w.Write(backslashR) + case '\t': + _, err = w.w.Write(backslashT) + case '"': + _, err = w.w.Write(backslashDQ) + case '\\': + _, err = w.w.Write(backslashBS) + default: + if isprint(c) { + err = w.w.WriteByte(c) + } else { + _, err = fmt.Fprintf(w.w, "\\%03o", c) + } + } + if err != nil { + return err + } + } + return w.WriteByte('"') +} + +func writeMessageSet(w *textWriter, ms *MessageSet) error { + for _, item := range ms.Item { + id := *item.TypeId + if msd, ok := messageSetMap[id]; ok { + // Known message set type. + if _, err := fmt.Fprintf(w, "[%s]: <\n", msd.name); err != nil { + return err + } + w.indent() + + pb := reflect.New(msd.t.Elem()) + if err := Unmarshal(item.Message, pb.Interface().(Message)); err != nil { + if _, err := fmt.Fprintf(w, "/* bad message: %v */\n", err); err != nil { + return err + } + } else { + if err := writeStruct(w, pb.Elem()); err != nil { + return err + } + } + } else { + // Unknown type. + if _, err := fmt.Fprintf(w, "[%d]: <\n", id); err != nil { + return err + } + w.indent() + if err := writeUnknownStruct(w, item.Message); err != nil { + return err + } + } + w.unindent() + if _, err := w.Write(gtNewline); err != nil { + return err + } + } + return nil +} + +func writeUnknownStruct(w *textWriter, data []byte) (err error) { + if !w.compact { + if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil { + return err + } + } + b := NewBuffer(data) + for b.index < len(b.buf) { + x, err := b.DecodeVarint() + if err != nil { + _, err := fmt.Fprintf(w, "/* %v */\n", err) + return err + } + wire, tag := x&7, x>>3 + if wire == WireEndGroup { + w.unindent() + if _, err := w.Write(endBraceNewline); err != nil { + return err + } + continue + } + if _, err := fmt.Fprint(w, tag); err != nil { + return err + } + if wire != WireStartGroup { + if err := w.WriteByte(':'); err != nil { + return err + } + } + if !w.compact || wire == WireStartGroup { + if err := w.WriteByte(' '); err != nil { + return err + } + } + switch wire { + case WireBytes: + buf, e := b.DecodeRawBytes(false) + if e == nil { + _, err = fmt.Fprintf(w, "%q", buf) + } else { + _, err = fmt.Fprintf(w, "/* %v */", e) + } + case WireFixed32: + x, err = b.DecodeFixed32() + err = writeUnknownInt(w, x, err) + case WireFixed64: + x, err = b.DecodeFixed64() + err = writeUnknownInt(w, x, err) + case WireStartGroup: + err = w.WriteByte('{') + w.indent() + case WireVarint: + x, err = b.DecodeVarint() + err = writeUnknownInt(w, x, err) + default: + _, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire) + } + if err != nil { + return err + } + if err = w.WriteByte('\n'); err != nil { + return err + } + } + return nil +} + +func writeUnknownInt(w *textWriter, x uint64, err error) error { + if err == nil { + _, err = fmt.Fprint(w, x) + } else { + _, err = fmt.Fprintf(w, "/* %v */", err) + } + return err +} + +type int32Slice []int32 + +func (s int32Slice) Len() int { return len(s) } +func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] } +func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// writeExtensions writes all the extensions in pv. +// pv is assumed to be a pointer to a protocol message struct that is extendable. +func writeExtensions(w *textWriter, pv reflect.Value) error { + emap := extensionMaps[pv.Type().Elem()] + ep := pv.Interface().(extendableProto) + + // Order the extensions by ID. + // This isn't strictly necessary, but it will give us + // canonical output, which will also make testing easier. + m := ep.ExtensionMap() + ids := make([]int32, 0, len(m)) + for id := range m { + ids = append(ids, id) + } + sort.Sort(int32Slice(ids)) + + for _, extNum := range ids { + ext := m[extNum] + var desc *ExtensionDesc + if emap != nil { + desc = emap[extNum] + } + if desc == nil { + // Unknown extension. + if err := writeUnknownStruct(w, ext.enc); err != nil { + return err + } + continue + } + + pb, err := GetExtension(ep, desc) + if err != nil { + if _, err := fmt.Fprintln(os.Stderr, "proto: failed getting extension: ", err); err != nil { + return err + } + continue + } + + // Repeated extensions will appear as a slice. + if !desc.repeated() { + if err := writeExtension(w, desc.Name, pb); err != nil { + return err + } + } else { + v := reflect.ValueOf(pb) + for i := 0; i < v.Len(); i++ { + if err := writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil { + return err + } + } + } + } + return nil +} + +func writeExtension(w *textWriter, name string, pb interface{}) error { + if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil { + return err + } + if !w.compact { + if err := w.WriteByte(' '); err != nil { + return err + } + } + if err := writeAny(w, reflect.ValueOf(pb), nil); err != nil { + return err + } + if err := w.WriteByte('\n'); err != nil { + return err + } + return nil +} + +func (w *textWriter) writeIndent() { + if !w.complete { + return + } + remain := w.ind * 2 + for remain > 0 { + n := remain + if n > len(spaces) { + n = len(spaces) + } + w.w.Write(spaces[:n]) + remain -= n + } + w.complete = false +} + +func marshalText(w io.Writer, pb Message, compact bool) error { + val := reflect.ValueOf(pb) + if pb == nil || val.IsNil() { + w.Write([]byte("")) + return nil + } + var bw *bufio.Writer + ww, ok := w.(writer) + if !ok { + bw = bufio.NewWriter(w) + ww = bw + } + aw := &textWriter{ + w: ww, + complete: true, + compact: compact, + } + + if tm, ok := pb.(textMarshaler); ok { + text, err := tm.MarshalText() + if err != nil { + return err + } + if _, err = aw.Write(text); err != nil { + return err + } + if bw != nil { + return bw.Flush() + } + return nil + } + // Dereference the received pointer so we don't have outer < and >. + v := reflect.Indirect(val) + if err := writeStruct(aw, v); err != nil { + return err + } + if bw != nil { + return bw.Flush() + } + return nil +} + +// MarshalText writes a given protocol buffer in text format. +// The only errors returned are from w. +func MarshalText(w io.Writer, pb Message) error { + return marshalText(w, pb, false) +} + +// MarshalTextString is the same as MarshalText, but returns the string directly. +func MarshalTextString(pb Message) string { + var buf bytes.Buffer + marshalText(&buf, pb, false) + return buf.String() +} + +// CompactText writes a given protocol buffer in compact text format (one line). +func CompactText(w io.Writer, pb Message) error { return marshalText(w, pb, true) } + +// CompactTextString is the same as CompactText, but returns the string directly. +func CompactTextString(pb Message) string { + var buf bytes.Buffer + marshalText(&buf, pb, true) + return buf.String() +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go new file mode 100644 index 0000000..d235ca9 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go @@ -0,0 +1,684 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto + +// Functions for parsing the Text protocol buffer format. +// TODO: message sets. + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "unicode/utf8" +) + +// textUnmarshaler is implemented by Messages that can unmarshal themsleves. +// It is identical to encoding.TextUnmarshaler, introduced in go 1.2, +// which will eventually replace it. +type textUnmarshaler interface { + UnmarshalText(text []byte) error +} + +type ParseError struct { + Message string + Line int // 1-based line number + Offset int // 0-based byte offset from start of input +} + +func (p *ParseError) Error() string { + if p.Line == 1 { + // show offset only for first line + return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message) + } + return fmt.Sprintf("line %d: %v", p.Line, p.Message) +} + +type token struct { + value string + err *ParseError + line int // line number + offset int // byte number from start of input, not start of line + unquoted string // the unquoted version of value, if it was a quoted string +} + +func (t *token) String() string { + if t.err == nil { + return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset) + } + return fmt.Sprintf("parse error: %v", t.err) +} + +type textParser struct { + s string // remaining input + done bool // whether the parsing is finished (success or error) + backed bool // whether back() was called + offset, line int + cur token +} + +func newTextParser(s string) *textParser { + p := new(textParser) + p.s = s + p.line = 1 + p.cur.line = 1 + return p +} + +func (p *textParser) errorf(format string, a ...interface{}) *ParseError { + pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset} + p.cur.err = pe + p.done = true + return pe +} + +// Numbers and identifiers are matched by [-+._A-Za-z0-9] +func isIdentOrNumberChar(c byte) bool { + switch { + case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z': + return true + case '0' <= c && c <= '9': + return true + } + switch c { + case '-', '+', '.', '_': + return true + } + return false +} + +func isWhitespace(c byte) bool { + switch c { + case ' ', '\t', '\n', '\r': + return true + } + return false +} + +func (p *textParser) skipWhitespace() { + i := 0 + for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') { + if p.s[i] == '#' { + // comment; skip to end of line or input + for i < len(p.s) && p.s[i] != '\n' { + i++ + } + if i == len(p.s) { + break + } + } + if p.s[i] == '\n' { + p.line++ + } + i++ + } + p.offset += i + p.s = p.s[i:len(p.s)] + if len(p.s) == 0 { + p.done = true + } +} + +func (p *textParser) advance() { + // Skip whitespace + p.skipWhitespace() + if p.done { + return + } + + // Start of non-whitespace + p.cur.err = nil + p.cur.offset, p.cur.line = p.offset, p.line + p.cur.unquoted = "" + switch p.s[0] { + case '<', '>', '{', '}', ':', '[', ']', ';', ',': + // Single symbol + p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)] + case '"', '\'': + // Quoted string + i := 1 + for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' { + if p.s[i] == '\\' && i+1 < len(p.s) { + // skip escaped char + i++ + } + i++ + } + if i >= len(p.s) || p.s[i] != p.s[0] { + p.errorf("unmatched quote") + return + } + unq, err := unquoteC(p.s[1:i], rune(p.s[0])) + if err != nil { + p.errorf("invalid quoted string %v", p.s[0:i+1]) + return + } + p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)] + p.cur.unquoted = unq + default: + i := 0 + for i < len(p.s) && isIdentOrNumberChar(p.s[i]) { + i++ + } + if i == 0 { + p.errorf("unexpected byte %#x", p.s[0]) + return + } + p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)] + } + p.offset += len(p.cur.value) +} + +var ( + errBadUTF8 = errors.New("proto: bad UTF-8") + errBadHex = errors.New("proto: bad hexadecimal") +) + +func unquoteC(s string, quote rune) (string, error) { + // This is based on C++'s tokenizer.cc. + // Despite its name, this is *not* parsing C syntax. + // For instance, "\0" is an invalid quoted string. + + // Avoid allocation in trivial cases. + simple := true + for _, r := range s { + if r == '\\' || r == quote { + simple = false + break + } + } + if simple { + return s, nil + } + + buf := make([]byte, 0, 3*len(s)/2) + for len(s) > 0 { + r, n := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && n == 1 { + return "", errBadUTF8 + } + s = s[n:] + if r != '\\' { + if r < utf8.RuneSelf { + buf = append(buf, byte(r)) + } else { + buf = append(buf, string(r)...) + } + continue + } + + ch, tail, err := unescape(s) + if err != nil { + return "", err + } + buf = append(buf, ch...) + s = tail + } + return string(buf), nil +} + +func unescape(s string) (ch string, tail string, err error) { + r, n := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && n == 1 { + return "", "", errBadUTF8 + } + s = s[n:] + switch r { + case 'a': + return "\a", s, nil + case 'b': + return "\b", s, nil + case 'f': + return "\f", s, nil + case 'n': + return "\n", s, nil + case 'r': + return "\r", s, nil + case 't': + return "\t", s, nil + case 'v': + return "\v", s, nil + case '?': + return "?", s, nil // trigraph workaround + case '\'', '"', '\\': + return string(r), s, nil + case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X': + if len(s) < 2 { + return "", "", fmt.Errorf(`\%c requires 2 following digits`, r) + } + base := 8 + ss := s[:2] + s = s[2:] + if r == 'x' || r == 'X' { + base = 16 + } else { + ss = string(r) + ss + } + i, err := strconv.ParseUint(ss, base, 8) + if err != nil { + return "", "", err + } + return string([]byte{byte(i)}), s, nil + case 'u', 'U': + n := 4 + if r == 'U' { + n = 8 + } + if len(s) < n { + return "", "", fmt.Errorf(`\%c requires %d digits`, r, n) + } + + bs := make([]byte, n/2) + for i := 0; i < n; i += 2 { + a, ok1 := unhex(s[i]) + b, ok2 := unhex(s[i+1]) + if !ok1 || !ok2 { + return "", "", errBadHex + } + bs[i/2] = a<<4 | b + } + s = s[n:] + return string(bs), s, nil + } + return "", "", fmt.Errorf(`unknown escape \%c`, r) +} + +// Adapted from src/pkg/strconv/quote.go. +func unhex(b byte) (v byte, ok bool) { + switch { + case '0' <= b && b <= '9': + return b - '0', true + case 'a' <= b && b <= 'f': + return b - 'a' + 10, true + case 'A' <= b && b <= 'F': + return b - 'A' + 10, true + } + return 0, false +} + +// Back off the parser by one token. Can only be done between calls to next(). +// It makes the next advance() a no-op. +func (p *textParser) back() { p.backed = true } + +// Advances the parser and returns the new current token. +func (p *textParser) next() *token { + if p.backed || p.done { + p.backed = false + return &p.cur + } + p.advance() + if p.done { + p.cur.value = "" + } else if len(p.cur.value) > 0 && p.cur.value[0] == '"' { + // Look for multiple quoted strings separated by whitespace, + // and concatenate them. + cat := p.cur + for { + p.skipWhitespace() + if p.done || p.s[0] != '"' { + break + } + p.advance() + if p.cur.err != nil { + return &p.cur + } + cat.value += " " + p.cur.value + cat.unquoted += p.cur.unquoted + } + p.done = false // parser may have seen EOF, but we want to return cat + p.cur = cat + } + return &p.cur +} + +// Return an error indicating which required field was not set. +func (p *textParser) missingRequiredFieldError(sv reflect.Value) *ParseError { + st := sv.Type() + sprops := GetProperties(st) + for i := 0; i < st.NumField(); i++ { + if !isNil(sv.Field(i)) { + continue + } + + props := sprops.Prop[i] + if props.Required { + return p.errorf("message %v missing required field %q", st, props.OrigName) + } + } + return p.errorf("message %v missing required field", st) // should not happen +} + +// Returns the index in the struct for the named field, as well as the parsed tag properties. +func structFieldByName(st reflect.Type, name string) (int, *Properties, bool) { + sprops := GetProperties(st) + i, ok := sprops.decoderOrigNames[name] + if ok { + return i, sprops.Prop[i], true + } + return -1, nil, false +} + +// Consume a ':' from the input stream (if the next token is a colon), +// returning an error if a colon is needed but not present. +func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ":" { + // Colon is optional when the field is a group or message. + needColon := true + switch props.Wire { + case "group": + needColon = false + case "bytes": + // A "bytes" field is either a message, a string, or a repeated field; + // those three become *T, *string and []T respectively, so we can check for + // this field being a pointer to a non-string. + if typ.Kind() == reflect.Ptr { + // *T or *string + if typ.Elem().Kind() == reflect.String { + break + } + } else if typ.Kind() == reflect.Slice { + // []T or []*T + if typ.Elem().Kind() != reflect.Ptr { + break + } + } + needColon = false + } + if needColon { + return p.errorf("expected ':', found %q", tok.value) + } + p.back() + } + return nil +} + +func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError { + st := sv.Type() + reqCount := GetProperties(st).reqCount + // A struct is a sequence of "name: value", terminated by one of + // '>' or '}', or the end of the input. A name may also be + // "[extension]". + for { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value == terminator { + break + } + if tok.value == "[" { + // Looks like an extension. + // + // TODO: Check whether we need to handle + // namespace rooted names (e.g. ".something.Foo"). + tok = p.next() + if tok.err != nil { + return tok.err + } + var desc *ExtensionDesc + // This could be faster, but it's functional. + // TODO: Do something smarter than a linear scan. + for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) { + if d.Name == tok.value { + desc = d + break + } + } + if desc == nil { + return p.errorf("unrecognized extension %q", tok.value) + } + // Check the extension terminator. + tok = p.next() + if tok.err != nil { + return tok.err + } + if tok.value != "]" { + return p.errorf("unrecognized extension terminator %q", tok.value) + } + + props := &Properties{} + props.Parse(desc.Tag) + + typ := reflect.TypeOf(desc.ExtensionType) + if err := p.checkForColon(props, typ); err != nil { + return err + } + + rep := desc.repeated() + + // Read the extension structure, and set it in + // the value we're constructing. + var ext reflect.Value + if !rep { + ext = reflect.New(typ).Elem() + } else { + ext = reflect.New(typ.Elem()).Elem() + } + if err := p.readAny(ext, props); err != nil { + return err + } + ep := sv.Addr().Interface().(extendableProto) + if !rep { + SetExtension(ep, desc, ext.Interface()) + } else { + old, err := GetExtension(ep, desc) + var sl reflect.Value + if err == nil { + sl = reflect.ValueOf(old) // existing slice + } else { + sl = reflect.MakeSlice(typ, 0, 1) + } + sl = reflect.Append(sl, ext) + SetExtension(ep, desc, sl.Interface()) + } + } else { + // This is a normal, non-extension field. + fi, props, ok := structFieldByName(st, tok.value) + if !ok { + return p.errorf("unknown field name %q in %v", tok.value, st) + } + + dst := sv.Field(fi) + isDstNil := isNil(dst) + + // Check that it's not already set if it's not a repeated field. + if !props.Repeated && !isDstNil { + return p.errorf("non-repeated field %q was repeated", tok.value) + } + + if err := p.checkForColon(props, st.Field(fi).Type); err != nil { + return err + } + + // Parse into the field. + if err := p.readAny(dst, props); err != nil { + return err + } + + if props.Required { + reqCount-- + } + } + + // For backward compatibility, permit a semicolon or comma after a field. + tok = p.next() + if tok.err != nil { + return tok.err + } + if tok.value != ";" && tok.value != "," { + p.back() + } + } + + if reqCount > 0 { + return p.missingRequiredFieldError(sv) + } + return nil +} + +func (p *textParser) readAny(v reflect.Value, props *Properties) *ParseError { + tok := p.next() + if tok.err != nil { + return tok.err + } + if tok.value == "" { + return p.errorf("unexpected EOF") + } + + switch fv := v; fv.Kind() { + case reflect.Slice: + at := v.Type() + if at.Elem().Kind() == reflect.Uint8 { + // Special case for []byte + if tok.value[0] != '"' && tok.value[0] != '\'' { + // Deliberately written out here, as the error after + // this switch statement would write "invalid []byte: ...", + // which is not as user-friendly. + return p.errorf("invalid string: %v", tok.value) + } + bytes := []byte(tok.unquoted) + fv.Set(reflect.ValueOf(bytes)) + return nil + } + // Repeated field. May already exist. + flen := fv.Len() + if flen == fv.Cap() { + nav := reflect.MakeSlice(at, flen, 2*flen+1) + reflect.Copy(nav, fv) + fv.Set(nav) + } + fv.SetLen(flen + 1) + + // Read one. + p.back() + return p.readAny(fv.Index(flen), props) + case reflect.Bool: + // Either "true", "false", 1 or 0. + switch tok.value { + case "true", "1": + fv.SetBool(true) + return nil + case "false", "0": + fv.SetBool(false) + return nil + } + case reflect.Float32, reflect.Float64: + v := tok.value + // Ignore 'f' for compatibility with output generated by C++, but don't + // remove 'f' when the value is "-inf" or "inf". + if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" { + v = v[:len(v)-1] + } + if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil { + fv.SetFloat(f) + return nil + } + case reflect.Int32: + if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil { + fv.SetInt(x) + return nil + } + + if len(props.Enum) == 0 { + break + } + m, ok := enumValueMaps[props.Enum] + if !ok { + break + } + x, ok := m[tok.value] + if !ok { + break + } + fv.SetInt(int64(x)) + return nil + case reflect.Int64: + if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil { + fv.SetInt(x) + return nil + } + + case reflect.Ptr: + // A basic field (indirected through pointer), or a repeated message/group + p.back() + fv.Set(reflect.New(fv.Type().Elem())) + return p.readAny(fv.Elem(), props) + case reflect.String: + if tok.value[0] == '"' || tok.value[0] == '\'' { + fv.SetString(tok.unquoted) + return nil + } + case reflect.Struct: + var terminator string + switch tok.value { + case "{": + terminator = "}" + case "<": + terminator = ">" + default: + return p.errorf("expected '{' or '<', found %q", tok.value) + } + // TODO: Handle nested messages which implement textUnmarshaler. + return p.readStruct(fv, terminator) + case reflect.Uint32: + if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { + fv.SetUint(uint64(x)) + return nil + } + case reflect.Uint64: + if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil { + fv.SetUint(x) + return nil + } + } + return p.errorf("invalid %v: %v", v.Type(), tok.value) +} + +// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb +// before starting to unmarshal, so any existing data in pb is always removed. +func UnmarshalText(s string, pb Message) error { + if um, ok := pb.(textUnmarshaler); ok { + err := um.UnmarshalText([]byte(s)) + return err + } + pb.Reset() + v := reflect.ValueOf(pb) + if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil { + return pe + } + return nil +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go new file mode 100644 index 0000000..d043769 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go @@ -0,0 +1,462 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "math" + "reflect" + "testing" + + . "./testdata" + . "code.google.com/p/goprotobuf/proto" +) + +type UnmarshalTextTest struct { + in string + err string // if "", no error expected + out *MyMessage +} + +func buildExtStructTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + SetExtension(msg, E_Ext_More, &Ext{ + Data: String("Hello, world!"), + }) + return UnmarshalTextTest{in: text, out: msg} +} + +func buildExtDataTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + SetExtension(msg, E_Ext_Text, String("Hello, world!")) + SetExtension(msg, E_Ext_Number, Int32(1729)) + return UnmarshalTextTest{in: text, out: msg} +} + +func buildExtRepStringTest(text string) UnmarshalTextTest { + msg := &MyMessage{ + Count: Int32(42), + } + if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil { + panic(err) + } + return UnmarshalTextTest{in: text, out: msg} +} + +var unMarshalTextTests = []UnmarshalTextTest{ + // Basic + { + in: " count:42\n name:\"Dave\" ", + out: &MyMessage{ + Count: Int32(42), + Name: String("Dave"), + }, + }, + + // Empty quoted string + { + in: `count:42 name:""`, + out: &MyMessage{ + Count: Int32(42), + Name: String(""), + }, + }, + + // Quoted string concatenation + { + in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`, + out: &MyMessage{ + Count: Int32(42), + Name: String("My name is elsewhere"), + }, + }, + + // Quoted string with escaped apostrophe + { + in: `count:42 name: "HOLIDAY - New Year\'s Day"`, + out: &MyMessage{ + Count: Int32(42), + Name: String("HOLIDAY - New Year's Day"), + }, + }, + + // Quoted string with single quote + { + in: `count:42 name: 'Roger "The Ramster" Ramjet'`, + out: &MyMessage{ + Count: Int32(42), + Name: String(`Roger "The Ramster" Ramjet`), + }, + }, + + // Quoted string with all the accepted special characters from the C++ test + { + in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"", + out: &MyMessage{ + Count: Int32(42), + Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"), + }, + }, + + // Quoted string with quoted backslash + { + in: `count:42 name: "\\'xyz"`, + out: &MyMessage{ + Count: Int32(42), + Name: String(`\'xyz`), + }, + }, + + // Quoted string with UTF-8 bytes. + { + in: "count:42 name: '\303\277\302\201\xAB'", + out: &MyMessage{ + Count: Int32(42), + Name: String("\303\277\302\201\xAB"), + }, + }, + + // Bad quoted string + { + in: `inner: < host: "\0" >` + "\n", + err: `line 1.15: invalid quoted string "\0"`, + }, + + // Number too large for int64 + { + in: "count: 1 others { key: 123456789012345678901 }", + err: "line 1.23: invalid int64: 123456789012345678901", + }, + + // Number too large for int32 + { + in: "count: 1234567890123", + err: "line 1.7: invalid int32: 1234567890123", + }, + + // Number in hexadecimal + { + in: "count: 0x2beef", + out: &MyMessage{ + Count: Int32(0x2beef), + }, + }, + + // Number in octal + { + in: "count: 024601", + out: &MyMessage{ + Count: Int32(024601), + }, + }, + + // Floating point number with "f" suffix + { + in: "count: 4 others:< weight: 17.0f >", + out: &MyMessage{ + Count: Int32(4), + Others: []*OtherMessage{ + { + Weight: Float32(17), + }, + }, + }, + }, + + // Floating point positive infinity + { + in: "count: 4 bigfloat: inf", + out: &MyMessage{ + Count: Int32(4), + Bigfloat: Float64(math.Inf(1)), + }, + }, + + // Floating point negative infinity + { + in: "count: 4 bigfloat: -inf", + out: &MyMessage{ + Count: Int32(4), + Bigfloat: Float64(math.Inf(-1)), + }, + }, + + // Number too large for float32 + { + in: "others:< weight: 12345678901234567890123456789012345678901234567890 >", + err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890", + }, + + // Number posing as a quoted string + { + in: `inner: < host: 12 >` + "\n", + err: `line 1.15: invalid string: 12`, + }, + + // Quoted string posing as int32 + { + in: `count: "12"`, + err: `line 1.7: invalid int32: "12"`, + }, + + // Quoted string posing a float32 + { + in: `others:< weight: "17.4" >`, + err: `line 1.17: invalid float32: "17.4"`, + }, + + // Enum + { + in: `count:42 bikeshed: BLUE`, + out: &MyMessage{ + Count: Int32(42), + Bikeshed: MyMessage_BLUE.Enum(), + }, + }, + + // Repeated field + { + in: `count:42 pet: "horsey" pet:"bunny"`, + out: &MyMessage{ + Count: Int32(42), + Pet: []string{"horsey", "bunny"}, + }, + }, + + // Repeated message with/without colon and <>/{} + { + in: `count:42 others:{} others{} others:<> others:{}`, + out: &MyMessage{ + Count: Int32(42), + Others: []*OtherMessage{ + {}, + {}, + {}, + {}, + }, + }, + }, + + // Missing colon for inner message + { + in: `count:42 inner < host: "cauchy.syd" >`, + out: &MyMessage{ + Count: Int32(42), + Inner: &InnerMessage{ + Host: String("cauchy.syd"), + }, + }, + }, + + // Missing colon for string field + { + in: `name "Dave"`, + err: `line 1.5: expected ':', found "\"Dave\""`, + }, + + // Missing colon for int32 field + { + in: `count 42`, + err: `line 1.6: expected ':', found "42"`, + }, + + // Missing required field + { + in: ``, + err: `line 1.0: message testdata.MyMessage missing required field "count"`, + }, + + // Repeated non-repeated field + { + in: `name: "Rob" name: "Russ"`, + err: `line 1.12: non-repeated field "name" was repeated`, + }, + + // Group + { + in: `count: 17 SomeGroup { group_field: 12 }`, + out: &MyMessage{ + Count: Int32(17), + Somegroup: &MyMessage_SomeGroup{ + GroupField: Int32(12), + }, + }, + }, + + // Semicolon between fields + { + in: `count:3;name:"Calvin"`, + out: &MyMessage{ + Count: Int32(3), + Name: String("Calvin"), + }, + }, + // Comma between fields + { + in: `count:4,name:"Ezekiel"`, + out: &MyMessage{ + Count: Int32(4), + Name: String("Ezekiel"), + }, + }, + + // Extension + buildExtStructTest(`count: 42 [testdata.Ext.more]:`), + buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`), + buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`), + buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`), + + // Big all-in-one + { + in: "count:42 # Meaning\n" + + `name:"Dave" ` + + `quote:"\"I didn't want to go.\"" ` + + `pet:"bunny" ` + + `pet:"kitty" ` + + `pet:"horsey" ` + + `inner:<` + + ` host:"footrest.syd" ` + + ` port:7001 ` + + ` connected:true ` + + `> ` + + `others:<` + + ` key:3735928559 ` + + ` value:"\x01A\a\f" ` + + `> ` + + `others:<` + + " weight:58.9 # Atomic weight of Co\n" + + ` inner:<` + + ` host:"lesha.mtv" ` + + ` port:8002 ` + + ` >` + + `>`, + out: &MyMessage{ + Count: Int32(42), + Name: String("Dave"), + Quote: String(`"I didn't want to go."`), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &InnerMessage{ + Host: String("footrest.syd"), + Port: Int32(7001), + Connected: Bool(true), + }, + Others: []*OtherMessage{ + { + Key: Int64(3735928559), + Value: []byte{0x1, 'A', '\a', '\f'}, + }, + { + Weight: Float32(58.9), + Inner: &InnerMessage{ + Host: String("lesha.mtv"), + Port: Int32(8002), + }, + }, + }, + }, + }, +} + +func TestUnmarshalText(t *testing.T) { + for i, test := range unMarshalTextTests { + pb := new(MyMessage) + err := UnmarshalText(test.in, pb) + if test.err == "" { + // We don't expect failure. + if err != nil { + t.Errorf("Test %d: Unexpected error: %v", i, err) + } else if !reflect.DeepEqual(pb, test.out) { + t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", + i, pb, test.out) + } + } else { + // We do expect failure. + if err == nil { + t.Errorf("Test %d: Didn't get expected error: %v", i, test.err) + } else if err.Error() != test.err { + t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v", + i, err.Error(), test.err) + } + } + } +} + +func TestUnmarshalTextCustomMessage(t *testing.T) { + msg := &textMessage{} + if err := UnmarshalText("custom", msg); err != nil { + t.Errorf("Unexpected error from custom unmarshal: %v", err) + } + if UnmarshalText("not custom", msg) == nil { + t.Errorf("Didn't get expected error from custom unmarshal") + } +} + +// Regression test; this caused a panic. +func TestRepeatedEnum(t *testing.T) { + pb := new(RepeatedEnum) + if err := UnmarshalText("color: RED", pb); err != nil { + t.Fatal(err) + } + exp := &RepeatedEnum{ + Color: []RepeatedEnum_Color{RepeatedEnum_RED}, + } + if !Equal(pb, exp) { + t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp) + } +} + +var benchInput string + +func init() { + benchInput = "count: 4\n" + for i := 0; i < 1000; i++ { + benchInput += "pet: \"fido\"\n" + } + + // Check it is valid input. + pb := new(MyMessage) + err := UnmarshalText(benchInput, pb) + if err != nil { + panic("Bad benchmark input: " + err.Error()) + } +} + +func BenchmarkUnmarshalText(b *testing.B) { + pb := new(MyMessage) + for i := 0; i < b.N; i++ { + UnmarshalText(benchInput, pb) + } + b.SetBytes(int64(len(benchInput))) +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go new file mode 100644 index 0000000..224a484 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_test.go @@ -0,0 +1,408 @@ +// Go support for Protocol Buffers - Google's data interchange format +// +// Copyright 2010 The Go Authors. All rights reserved. +// http://code.google.com/p/goprotobuf/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package proto_test + +import ( + "bytes" + "errors" + "io/ioutil" + "math" + "strings" + "testing" + + "code.google.com/p/goprotobuf/proto" + + pb "./testdata" +) + +// textMessage implements the methods that allow it to marshal and unmarshal +// itself as text. +type textMessage struct { +} + +func (*textMessage) MarshalText() ([]byte, error) { + return []byte("custom"), nil +} + +func (*textMessage) UnmarshalText(bytes []byte) error { + if string(bytes) != "custom" { + return errors.New("expected 'custom'") + } + return nil +} + +func (*textMessage) Reset() {} +func (*textMessage) String() string { return "" } +func (*textMessage) ProtoMessage() {} + +func newTestMessage() *pb.MyMessage { + msg := &pb.MyMessage{ + Count: proto.Int32(42), + Name: proto.String("Dave"), + Quote: proto.String(`"I didn't want to go."`), + Pet: []string{"bunny", "kitty", "horsey"}, + Inner: &pb.InnerMessage{ + Host: proto.String("footrest.syd"), + Port: proto.Int32(7001), + Connected: proto.Bool(true), + }, + Others: []*pb.OtherMessage{ + { + Key: proto.Int64(0xdeadbeef), + Value: []byte{1, 65, 7, 12}, + }, + { + Weight: proto.Float32(6.022), + Inner: &pb.InnerMessage{ + Host: proto.String("lesha.mtv"), + Port: proto.Int32(8002), + }, + }, + }, + Bikeshed: pb.MyMessage_BLUE.Enum(), + Somegroup: &pb.MyMessage_SomeGroup{ + GroupField: proto.Int32(8), + }, + // One normally wouldn't do this. + // This is an undeclared tag 13, as a varint (wire type 0) with value 4. + XXX_unrecognized: []byte{13<<3 | 0, 4}, + } + ext := &pb.Ext{ + Data: proto.String("Big gobs for big rats"), + } + if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil { + panic(err) + } + greetings := []string{"adg", "easy", "cow"} + if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil { + panic(err) + } + + // Add an unknown extension. We marshal a pb.Ext, and fake the ID. + b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")}) + if err != nil { + panic(err) + } + b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...) + proto.SetRawExtension(msg, 201, b) + + // Extensions can be plain fields, too, so let's test that. + b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19) + proto.SetRawExtension(msg, 202, b) + + return msg +} + +const text = `count: 42 +name: "Dave" +quote: "\"I didn't want to go.\"" +pet: "bunny" +pet: "kitty" +pet: "horsey" +inner: < + host: "footrest.syd" + port: 7001 + connected: true +> +others: < + key: 3735928559 + value: "\001A\007\014" +> +others: < + weight: 6.022 + inner: < + host: "lesha.mtv" + port: 8002 + > +> +bikeshed: BLUE +SomeGroup { + group_field: 8 +} +/* 2 unknown bytes */ +13: 4 +[testdata.Ext.more]: < + data: "Big gobs for big rats" +> +[testdata.greeting]: "adg" +[testdata.greeting]: "easy" +[testdata.greeting]: "cow" +/* 13 unknown bytes */ +201: "\t3G skiing" +/* 3 unknown bytes */ +202: 19 +` + +func TestMarshalText(t *testing.T) { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, newTestMessage()); err != nil { + t.Fatalf("proto.MarshalText: %v", err) + } + s := buf.String() + if s != text { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text) + } +} + +func TestMarshalTextCustomMessage(t *testing.T) { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, &textMessage{}); err != nil { + t.Fatalf("proto.MarshalText: %v", err) + } + s := buf.String() + if s != "custom" { + t.Errorf("Got %q, expected %q", s, "custom") + } +} +func TestMarshalTextNil(t *testing.T) { + want := "" + tests := []proto.Message{nil, (*pb.MyMessage)(nil)} + for i, test := range tests { + buf := new(bytes.Buffer) + if err := proto.MarshalText(buf, test); err != nil { + t.Fatal(err) + } + if got := buf.String(); got != want { + t.Errorf("%d: got %q want %q", i, got, want) + } + } +} + +func TestMarshalTextUnknownEnum(t *testing.T) { + // The Color enum only specifies values 0-2. + m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()} + got := m.String() + const want = `bikeshed:3 ` + if got != want { + t.Errorf("\n got %q\nwant %q", got, want) + } +} + +func BenchmarkMarshalTextBuffered(b *testing.B) { + buf := new(bytes.Buffer) + m := newTestMessage() + for i := 0; i < b.N; i++ { + buf.Reset() + proto.MarshalText(buf, m) + } +} + +func BenchmarkMarshalTextUnbuffered(b *testing.B) { + w := ioutil.Discard + m := newTestMessage() + for i := 0; i < b.N; i++ { + proto.MarshalText(w, m) + } +} + +func compact(src string) string { + // s/[ \n]+/ /g; s/ $//; + dst := make([]byte, len(src)) + space, comment := false, false + j := 0 + for i := 0; i < len(src); i++ { + if strings.HasPrefix(src[i:], "/*") { + comment = true + i++ + continue + } + if comment && strings.HasPrefix(src[i:], "*/") { + comment = false + i++ + continue + } + if comment { + continue + } + c := src[i] + if c == ' ' || c == '\n' { + space = true + continue + } + if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') { + space = false + } + if c == '{' { + space = false + } + if space { + dst[j] = ' ' + j++ + space = false + } + dst[j] = c + j++ + } + if space { + dst[j] = ' ' + j++ + } + return string(dst[0:j]) +} + +var compactText = compact(text) + +func TestCompactText(t *testing.T) { + s := proto.CompactTextString(newTestMessage()) + if s != compactText { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText) + } +} + +func TestStringEscaping(t *testing.T) { + testCases := []struct { + in *pb.Strings + out string + }{ + { + // Test data from C++ test (TextFormatTest.StringEscape). + // Single divergence: we don't escape apostrophes. + &pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")}, + "string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n", + }, + { + // Test data from the same C++ test. + &pb.Strings{StringField: proto.String("\350\260\267\346\255\214")}, + "string_field: \"\\350\\260\\267\\346\\255\\214\"\n", + }, + { + // Some UTF-8. + &pb.Strings{StringField: proto.String("\x00\x01\xff\x81")}, + `string_field: "\000\001\377\201"` + "\n", + }, + } + + for i, tc := range testCases { + var buf bytes.Buffer + if err := proto.MarshalText(&buf, tc.in); err != nil { + t.Errorf("proto.MarsalText: %v", err) + continue + } + s := buf.String() + if s != tc.out { + t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out) + continue + } + + // Check round-trip. + pb := new(pb.Strings) + if err := proto.UnmarshalText(s, pb); err != nil { + t.Errorf("#%d: UnmarshalText: %v", i, err) + continue + } + if !proto.Equal(pb, tc.in) { + t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb) + } + } +} + +// A limitedWriter accepts some output before it fails. +// This is a proxy for something like a nearly-full or imminently-failing disk, +// or a network connection that is about to die. +type limitedWriter struct { + b bytes.Buffer + limit int +} + +var outOfSpace = errors.New("proto: insufficient space") + +func (w *limitedWriter) Write(p []byte) (n int, err error) { + var avail = w.limit - w.b.Len() + if avail <= 0 { + return 0, outOfSpace + } + if len(p) <= avail { + return w.b.Write(p) + } + n, _ = w.b.Write(p[:avail]) + return n, outOfSpace +} + +func TestMarshalTextFailing(t *testing.T) { + // Try lots of different sizes to exercise more error code-paths. + for lim := 0; lim < len(text); lim++ { + buf := new(limitedWriter) + buf.limit = lim + err := proto.MarshalText(buf, newTestMessage()) + // We expect a certain error, but also some partial results in the buffer. + if err != outOfSpace { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace) + } + s := buf.b.String() + x := text[:buf.limit] + if s != x { + t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x) + } + } +} + +func TestFloats(t *testing.T) { + tests := []struct { + f float64 + want string + }{ + {0, "0"}, + {4.7, "4.7"}, + {math.Inf(1), "inf"}, + {math.Inf(-1), "-inf"}, + {math.NaN(), "nan"}, + } + for _, test := range tests { + msg := &pb.FloatingPoint{F: &test.f} + got := strings.TrimSpace(msg.String()) + want := `f:` + test.want + if got != want { + t.Errorf("f=%f: got %q, want %q", test.f, got, want) + } + } +} + +func TestRepeatedNilText(t *testing.T) { + m := &pb.MessageList{ + Message: []*pb.MessageList_Message{ + nil, + &pb.MessageList_Message{ + Name: proto.String("Horse"), + }, + nil, + }, + } + want := `Message +Message { + name: "Horse" +} +Message +` + if s := proto.MarshalTextString(m); s != want { + t.Errorf(" got: %s\nwant: %s", s, want) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/gf256/blog_test.go b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/blog_test.go new file mode 100644 index 0000000..4fbf7ec --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/blog_test.go @@ -0,0 +1,85 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains a straightforward implementation of +// Reed-Solomon encoding, along with a benchmark. +// It goes with http://research.swtch.com/field. +// +// For an optimized implementation, see gf256.go. + +package gf256 + +import ( + "bytes" + "fmt" + "testing" +) + +// BlogECC writes to check the error correcting code bytes +// for data using the given Reed-Solomon parameters. +func BlogECC(rs *RSEncoder, m []byte, check []byte) { + if len(check) < rs.c { + panic("gf256: invalid check byte length") + } + if rs.c == 0 { + return + } + + // The check bytes are the remainder after dividing + // data padded with c zeros by the generator polynomial. + + // p = data padded with c zeros. + var p []byte + n := len(m) + rs.c + if len(rs.p) >= n { + p = rs.p + } else { + p = make([]byte, n) + } + copy(p, m) + for i := len(m); i < len(p); i++ { + p[i] = 0 + } + + gen := rs.gen + + // Divide p by gen, leaving the remainder in p[len(data):]. + // p[0] is the most significant term in p, and + // gen[0] is the most significant term in the generator. + for i := 0; i < len(m); i++ { + k := f.Mul(p[i], f.Inv(gen[0])) // k = pi / g0 + // p -= k·g + for j, g := range gen { + p[i+j] = f.Add(p[i+j], f.Mul(k, g)) + } + } + + copy(check, p[len(m):]) + rs.p = p +} + +func BenchmarkBlogECC(b *testing.B) { + data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11} + check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f} + out := make([]byte, len(check)) + rs := NewRSEncoder(f, len(check)) + for i := 0; i < b.N; i++ { + BlogECC(rs, data, out) + } + b.SetBytes(int64(len(data))) + if !bytes.Equal(out, check) { + fmt.Printf("have %#v want %#v\n", out, check) + } +} + +func TestBlogECC(t *testing.T) { + data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11} + check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55} + out := make([]byte, len(check)) + rs := NewRSEncoder(f, len(check)) + BlogECC(rs, data, out) + if !bytes.Equal(out, check) { + t.Errorf("have %x want %x", out, check) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256.go b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256.go new file mode 100644 index 0000000..bfeeeb3 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256.go @@ -0,0 +1,241 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gf256 implements arithmetic over the Galois Field GF(256). +package gf256 + +import "strconv" + +// A Field represents an instance of GF(256) defined by a specific polynomial. +type Field struct { + log [256]byte // log[0] is unused + exp [510]byte +} + +// NewField returns a new field corresponding to the polynomial poly +// and generator α. The Reed-Solomon encoding in QR codes uses +// polynomial 0x11d with generator 2. +// +// The choice of generator α only affects the Exp and Log operations. +func NewField(poly, α int) *Field { + if poly < 0x100 || poly >= 0x200 || reducible(poly) { + panic("gf256: invalid polynomial: " + strconv.Itoa(poly)) + } + + var f Field + x := 1 + for i := 0; i < 255; i++ { + if x == 1 && i != 0 { + panic("gf256: invalid generator " + strconv.Itoa(α) + + " for polynomial " + strconv.Itoa(poly)) + } + f.exp[i] = byte(x) + f.exp[i+255] = byte(x) + f.log[x] = byte(i) + x = mul(x, α, poly) + } + f.log[0] = 255 + for i := 0; i < 255; i++ { + if f.log[f.exp[i]] != byte(i) { + panic("bad log") + } + if f.log[f.exp[i+255]] != byte(i) { + panic("bad log") + } + } + for i := 1; i < 256; i++ { + if f.exp[f.log[i]] != byte(i) { + panic("bad log") + } + } + + return &f +} + +// nbit returns the number of significant in p. +func nbit(p int) uint { + n := uint(0) + for ; p > 0; p >>= 1 { + n++ + } + return n +} + +// polyDiv divides the polynomial p by q and returns the remainder. +func polyDiv(p, q int) int { + np := nbit(p) + nq := nbit(q) + for ; np >= nq; np-- { + if p&(1<<(np-1)) != 0 { + p ^= q << (np - nq) + } + } + return p +} + +// mul returns the product x*y mod poly, a GF(256) multiplication. +func mul(x, y, poly int) int { + z := 0 + for x > 0 { + if x&1 != 0 { + z ^= y + } + x >>= 1 + y <<= 1 + if y&0x100 != 0 { + y ^= poly + } + } + return z +} + +// reducible reports whether p is reducible. +func reducible(p int) bool { + // Multiplying n-bit * n-bit produces (2n-1)-bit, + // so if p is reducible, one of its factors must be + // of np/2+1 bits or fewer. + np := nbit(p) + for q := 2; q < 1<<(np/2+1); q++ { + if polyDiv(p, q) == 0 { + return true + } + } + return false +} + +// Add returns the sum of x and y in the field. +func (f *Field) Add(x, y byte) byte { + return x ^ y +} + +// Exp returns the base-α exponential of e in the field. +// If e < 0, Exp returns 0. +func (f *Field) Exp(e int) byte { + if e < 0 { + return 0 + } + return f.exp[e%255] +} + +// Log returns the base-α logarithm of x in the field. +// If x == 0, Log returns -1. +func (f *Field) Log(x byte) int { + if x == 0 { + return -1 + } + return int(f.log[x]) +} + +// Inv returns the multiplicative inverse of x in the field. +// If x == 0, Inv returns 0. +func (f *Field) Inv(x byte) byte { + if x == 0 { + return 0 + } + return f.exp[255-f.log[x]] +} + +// Mul returns the product of x and y in the field. +func (f *Field) Mul(x, y byte) byte { + if x == 0 || y == 0 { + return 0 + } + return f.exp[int(f.log[x])+int(f.log[y])] +} + +// An RSEncoder implements Reed-Solomon encoding +// over a given field using a given number of error correction bytes. +type RSEncoder struct { + f *Field + c int + gen []byte + lgen []byte + p []byte +} + +func (f *Field) gen(e int) (gen, lgen []byte) { + // p = 1 + p := make([]byte, e+1) + p[e] = 1 + + for i := 0; i < e; i++ { + // p *= (x + Exp(i)) + // p[j] = p[j]*Exp(i) + p[j+1]. + c := f.Exp(i) + for j := 0; j < e; j++ { + p[j] = f.Mul(p[j], c) ^ p[j+1] + } + p[e] = f.Mul(p[e], c) + } + + // lp = log p. + lp := make([]byte, e+1) + for i, c := range p { + if c == 0 { + lp[i] = 255 + } else { + lp[i] = byte(f.Log(c)) + } + } + + return p, lp +} + +// NewRSEncoder returns a new Reed-Solomon encoder +// over the given field and number of error correction bytes. +func NewRSEncoder(f *Field, c int) *RSEncoder { + gen, lgen := f.gen(c) + return &RSEncoder{f: f, c: c, gen: gen, lgen: lgen} +} + +// ECC writes to check the error correcting code bytes +// for data using the given Reed-Solomon parameters. +func (rs *RSEncoder) ECC(data []byte, check []byte) { + if len(check) < rs.c { + panic("gf256: invalid check byte length") + } + if rs.c == 0 { + return + } + + // The check bytes are the remainder after dividing + // data padded with c zeros by the generator polynomial. + + // p = data padded with c zeros. + var p []byte + n := len(data) + rs.c + if len(rs.p) >= n { + p = rs.p + } else { + p = make([]byte, n) + } + copy(p, data) + for i := len(data); i < len(p); i++ { + p[i] = 0 + } + + // Divide p by gen, leaving the remainder in p[len(data):]. + // p[0] is the most significant term in p, and + // gen[0] is the most significant term in the generator, + // which is always 1. + // To avoid repeated work, we store various values as + // lv, not v, where lv = log[v]. + f := rs.f + lgen := rs.lgen[1:] + for i := 0; i < len(data); i++ { + c := p[i] + if c == 0 { + continue + } + q := p[i+1:] + exp := f.exp[f.log[c]:] + for j, lg := range lgen { + if lg != 255 { // lgen uses 255 for log 0 + q[j] ^= exp[lg] + } + } + } + copy(check, p[len(data):]) + rs.p = p +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256_test.go b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256_test.go new file mode 100644 index 0000000..f77fa7d --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/gf256/gf256_test.go @@ -0,0 +1,194 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gf256 + +import ( + "bytes" + "fmt" + "testing" +) + +var f = NewField(0x11d, 2) // x^8 + x^4 + x^3 + x^2 + 1 + +func TestBasic(t *testing.T) { + if f.Exp(0) != 1 || f.Exp(1) != 2 || f.Exp(255) != 1 { + panic("bad Exp") + } +} + +func TestECC(t *testing.T) { + data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11} + check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55} + out := make([]byte, len(check)) + rs := NewRSEncoder(f, len(check)) + rs.ECC(data, out) + if !bytes.Equal(out, check) { + t.Errorf("have %x want %x", out, check) + } +} + +func TestLinear(t *testing.T) { + d1 := []byte{0x00, 0x00} + c1 := []byte{0x00, 0x00} + out := make([]byte, len(c1)) + rs := NewRSEncoder(f, len(c1)) + if rs.ECC(d1, out); !bytes.Equal(out, c1) { + t.Errorf("ECBytes(%x, %d) = %x, want 0", d1, len(c1), out) + } + d2 := []byte{0x00, 0x01} + c2 := make([]byte, 2) + rs.ECC(d2, c2) + d3 := []byte{0x00, 0x02} + c3 := make([]byte, 2) + rs.ECC(d3, c3) + cx := make([]byte, 2) + for i := range cx { + cx[i] = c2[i] ^ c3[i] + } + d4 := []byte{0x00, 0x03} + c4 := make([]byte, 2) + rs.ECC(d4, c4) + if !bytes.Equal(cx, c4) { + t.Errorf("ECBytes(%x, 2) = %x\nECBytes(%x, 2) = %x\nxor = %x\nECBytes(%x, 2) = %x", + d2, c2, d3, c3, cx, d4, c4) + } +} + +func TestGaussJordan(t *testing.T) { + rs := NewRSEncoder(f, 2) + m := make([][]byte, 16) + for i := range m { + m[i] = make([]byte, 4) + m[i][i/8] = 1 << uint(i%8) + rs.ECC(m[i][:2], m[i][2:]) + } + if false { + fmt.Printf("---\n") + for _, row := range m { + fmt.Printf("%x\n", row) + } + } + b := []uint{0, 1, 2, 3, 12, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27} + for i := 0; i < 16; i++ { + bi := b[i] + if m[i][bi/8]&(1<<(7-bi%8)) == 0 { + for j := i + 1; ; j++ { + if j >= len(m) { + t.Errorf("lost track for %d", bi) + break + } + if m[j][bi/8]&(1<<(7-bi%8)) != 0 { + m[i], m[j] = m[j], m[i] + break + } + } + } + for j := i + 1; j < len(m); j++ { + if m[j][bi/8]&(1<<(7-bi%8)) != 0 { + for k := range m[j] { + m[j][k] ^= m[i][k] + } + } + } + } + if false { + fmt.Printf("---\n") + for _, row := range m { + fmt.Printf("%x\n", row) + } + } + for i := 15; i >= 0; i-- { + bi := b[i] + for j := i - 1; j >= 0; j-- { + if m[j][bi/8]&(1<<(7-bi%8)) != 0 { + for k := range m[j] { + m[j][k] ^= m[i][k] + } + } + } + } + if false { + fmt.Printf("---\n") + for _, row := range m { + fmt.Printf("%x", row) + out := make([]byte, 2) + if rs.ECC(row[:2], out); !bytes.Equal(out, row[2:]) { + fmt.Printf(" - want %x", out) + } + fmt.Printf("\n") + } + } +} + +func BenchmarkECC(b *testing.B) { + data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11} + check := []byte{0x29, 0x41, 0xb3, 0x93, 0x8, 0xe8, 0xa3, 0xe7, 0x63, 0x8f} + out := make([]byte, len(check)) + rs := NewRSEncoder(f, len(check)) + for i := 0; i < b.N; i++ { + rs.ECC(data, out) + } + b.SetBytes(int64(len(data))) + if !bytes.Equal(out, check) { + fmt.Printf("have %#v want %#v\n", out, check) + } +} + +func TestGen(t *testing.T) { + for i := 0; i < 256; i++ { + _, lg := f.gen(i) + if lg[0] != 0 { + t.Errorf("#%d: %x", i, lg) + } + } +} + +func TestReducible(t *testing.T) { + var count = []int{1, 2, 3, 6, 9, 18, 30, 56, 99, 186} // oeis.org/A1037 + for i, want := range count { + n := 0 + for p := 1 << uint(i+2); p < 1< 0 { + n := nbit + if n > 8 { + n = 8 + } + if b.nbit%8 == 0 { + b.b = append(b.b, 0) + } else { + m := -b.nbit & 7 + if n > m { + n = m + } + } + b.nbit += n + sh := uint(nbit - n) + b.b[len(b.b)-1] |= uint8(v >> sh << uint(-b.nbit&7)) + v -= v >> sh << sh + nbit -= n + } +} + +// Num is the encoding for numeric data. +// The only valid characters are the decimal digits 0 through 9. +type Num string + +func (s Num) String() string { + return fmt.Sprintf("Num(%#q)", string(s)) +} + +func (s Num) Check() error { + for _, c := range s { + if c < '0' || '9' < c { + return fmt.Errorf("non-numeric string %#q", string(s)) + } + } + return nil +} + +var numLen = [3]int{10, 12, 14} + +func (s Num) Bits(v Version) int { + return 4 + numLen[v.sizeClass()] + (10*len(s)+2)/3 +} + +func (s Num) Encode(b *Bits, v Version) { + b.Write(1, 4) + b.Write(uint(len(s)), numLen[v.sizeClass()]) + var i int + for i = 0; i+3 <= len(s); i += 3 { + w := uint(s[i]-'0')*100 + uint(s[i+1]-'0')*10 + uint(s[i+2]-'0') + b.Write(w, 10) + } + switch len(s) - i { + case 1: + w := uint(s[i] - '0') + b.Write(w, 4) + case 2: + w := uint(s[i]-'0')*10 + uint(s[i+1]-'0') + b.Write(w, 7) + } +} + +// Alpha is the encoding for alphanumeric data. +// The valid characters are 0-9A-Z$%*+-./: and space. +type Alpha string + +const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" + +func (s Alpha) String() string { + return fmt.Sprintf("Alpha(%#q)", string(s)) +} + +func (s Alpha) Check() error { + for _, c := range s { + if strings.IndexRune(alphabet, c) < 0 { + return fmt.Errorf("non-alphanumeric string %#q", string(s)) + } + } + return nil +} + +var alphaLen = [3]int{9, 11, 13} + +func (s Alpha) Bits(v Version) int { + return 4 + alphaLen[v.sizeClass()] + (11*len(s)+1)/2 +} + +func (s Alpha) Encode(b *Bits, v Version) { + b.Write(2, 4) + b.Write(uint(len(s)), alphaLen[v.sizeClass()]) + var i int + for i = 0; i+2 <= len(s); i += 2 { + w := uint(strings.IndexRune(alphabet, rune(s[i])))*45 + + uint(strings.IndexRune(alphabet, rune(s[i+1]))) + b.Write(w, 11) + } + + if i < len(s) { + w := uint(strings.IndexRune(alphabet, rune(s[i]))) + b.Write(w, 6) + } +} + +// String is the encoding for 8-bit data. All bytes are valid. +type String string + +func (s String) String() string { + return fmt.Sprintf("String(%#q)", string(s)) +} + +func (s String) Check() error { + return nil +} + +var stringLen = [3]int{8, 16, 16} + +func (s String) Bits(v Version) int { + return 4 + stringLen[v.sizeClass()] + 8*len(s) +} + +func (s String) Encode(b *Bits, v Version) { + b.Write(4, 4) + b.Write(uint(len(s)), stringLen[v.sizeClass()]) + for i := 0; i < len(s); i++ { + b.Write(uint(s[i]), 8) + } +} + +// A Pixel describes a single pixel in a QR code. +type Pixel uint32 + +const ( + Black Pixel = 1 << iota + Invert +) + +func (p Pixel) Offset() uint { + return uint(p >> 6) +} + +func OffsetPixel(o uint) Pixel { + return Pixel(o << 6) +} + +func (r PixelRole) Pixel() Pixel { + return Pixel(r << 2) +} + +func (p Pixel) Role() PixelRole { + return PixelRole(p>>2) & 15 +} + +func (p Pixel) String() string { + s := p.Role().String() + if p&Black != 0 { + s += "+black" + } + if p&Invert != 0 { + s += "+invert" + } + s += "+" + strconv.FormatUint(uint64(p.Offset()), 10) + return s +} + +// A PixelRole describes the role of a QR pixel. +type PixelRole uint32 + +const ( + _ PixelRole = iota + Position // position squares (large) + Alignment // alignment squares (small) + Timing // timing strip between position squares + Format // format metadata + PVersion // version pattern + Unused // unused pixel + Data // data bit + Check // error correction check bit + Extra +) + +var roles = []string{ + "", + "position", + "alignment", + "timing", + "format", + "pversion", + "unused", + "data", + "check", + "extra", +} + +func (r PixelRole) String() string { + if Position <= r && r <= Check { + return roles[r] + } + return strconv.Itoa(int(r)) +} + +// A Level represents a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota + M + Q + H +) + +func (l Level) String() string { + if L <= l && l <= H { + return "LMQH"[l : l+1] + } + return strconv.Itoa(int(l)) +} + +// A Code is a square pixel grid. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row +} + +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<= pad { + break + } + b.Write(0x11, 8) + } + } +} + +func (b *Bits) AddCheckBytes(v Version, l Level) { + nd := v.DataBytes(l) + if b.nbit < nd*8 { + b.Pad(nd*8 - b.nbit) + } + if b.nbit != nd*8 { + panic("qr: too much data") + } + + dat := b.Bytes() + vt := &vtab[v] + lev := &vt.level[l] + db := nd / lev.nblock + extra := nd % lev.nblock + chk := make([]byte, lev.check) + rs := gf256.NewRSEncoder(Field, lev.check) + for i := 0; i < lev.nblock; i++ { + if i == lev.nblock-extra { + db++ + } + rs.ECC(dat[:db], chk) + b.Append(chk) + dat = dat[db:] + } + + if len(b.Bytes()) != vt.bytes { + panic("qr: internal error") + } +} + +func (p *Plan) Encode(text ...Encoding) (*Code, error) { + var b Bits + for _, t := range text { + if err := t.Check(); err != nil { + return nil, err + } + t.Encode(&b, p.Version) + } + if b.Bits() > p.DataBytes*8 { + return nil, fmt.Errorf("cannot encode %d bits into %d-bit code", b.Bits(), p.DataBytes*8) + } + b.AddCheckBytes(p.Version, p.Level) + bytes := b.Bytes() + + // Now we have the checksum bytes and the data bytes. + // Construct the actual code. + c := &Code{Size: len(p.Pixel), Stride: (len(p.Pixel) + 7) &^ 7} + c.Bitmap = make([]byte, c.Stride*c.Size) + crow := c.Bitmap + for _, row := range p.Pixel { + for x, pix := range row { + switch pix.Role() { + case Data, Check: + o := pix.Offset() + if bytes[o/8]&(1< 40 { + return nil, fmt.Errorf("invalid QR version %d", int(v)) + } + siz := 17 + int(v)*4 + m := grid(siz) + p.Pixel = m + + // Timing markers (overwritten by boxes). + const ti = 6 // timing is in row/column 6 (counting from 0) + for i := range m { + p := Timing.Pixel() + if i&1 == 0 { + p |= Black + } + m[i][ti] = p + m[ti][i] = p + } + + // Position boxes. + posBox(m, 0, 0) + posBox(m, siz-7, 0) + posBox(m, 0, siz-7) + + // Alignment boxes. + info := &vtab[v] + for x := 4; x+5 < siz; { + for y := 4; y+5 < siz; { + // don't overwrite timing markers + if (x < 7 && y < 7) || (x < 7 && y+5 >= siz-7) || (x+5 >= siz-7 && y < 7) { + } else { + alignBox(m, x, y) + } + if y == 4 { + y = info.apos + } else { + y += info.astride + } + } + if x == 4 { + x = info.apos + } else { + x += info.astride + } + } + + // Version pattern. + pat := vtab[v].pattern + if pat != 0 { + v := pat + for x := 0; x < 6; x++ { + for y := 0; y < 3; y++ { + p := PVersion.Pixel() + if v&1 != 0 { + p |= Black + } + m[siz-11+y][x] = p + m[x][siz-11+y] = p + v >>= 1 + } + } + } + + // One lonely black pixel + m[siz-8][8] = Unused.Pixel() | Black + + return p, nil +} + +// fplan adds the format pixels +func fplan(l Level, m Mask, p *Plan) error { + // Format pixels. + fb := uint32(l^1) << 13 // level: L=01, M=00, Q=11, H=10 + fb |= uint32(m) << 10 // mask + const formatPoly = 0x537 + rem := fb + for i := 14; i >= 10; i-- { + if rem&(1<>i)&1 == 1 { + pix |= Black + } + if (invert>>i)&1 == 1 { + pix ^= Invert | Black + } + // top left + switch { + case i < 6: + p.Pixel[i][8] = pix + case i < 8: + p.Pixel[i+1][8] = pix + case i < 9: + p.Pixel[8][7] = pix + default: + p.Pixel[8][14-i] = pix + } + // bottom right + switch { + case i < 8: + p.Pixel[8][siz-1-int(i)] = pix + default: + p.Pixel[siz-1-int(14-i)][8] = pix + } + } + return nil +} + +// lplan edits a version-only Plan to add information +// about the error correction levels. +func lplan(v Version, l Level, p *Plan) error { + p.Level = l + + nblock := vtab[v].level[l].nblock + ne := vtab[v].level[l].check + nde := (vtab[v].bytes - ne*nblock) / nblock + extra := (vtab[v].bytes - ne*nblock) % nblock + dataBits := (nde*nblock + extra) * 8 + checkBits := ne * nblock * 8 + + p.DataBytes = vtab[v].bytes - ne*nblock + p.CheckBytes = ne * nblock + p.Blocks = nblock + + // Make data + checksum pixels. + data := make([]Pixel, dataBits) + for i := range data { + data[i] = Data.Pixel() | OffsetPixel(uint(i)) + } + check := make([]Pixel, checkBits) + for i := range check { + check[i] = Check.Pixel() | OffsetPixel(uint(i+dataBits)) + } + + // Split into blocks. + dataList := make([][]Pixel, nblock) + checkList := make([][]Pixel, nblock) + for i := 0; i < nblock; i++ { + // The last few blocks have an extra data byte (8 pixels). + nd := nde + if i >= nblock-extra { + nd++ + } + dataList[i], data = data[0:nd*8], data[nd*8:] + checkList[i], check = check[0:ne*8], check[ne*8:] + } + if len(data) != 0 || len(check) != 0 { + panic("data/check math") + } + + // Build up bit sequence, taking first byte of each block, + // then second byte, and so on. Then checksums. + bits := make([]Pixel, dataBits+checkBits) + dst := bits + for i := 0; i < nde+1; i++ { + for _, b := range dataList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + for i := 0; i < ne; i++ { + for _, b := range checkList { + if i*8 < len(b) { + copy(dst, b[i*8:(i+1)*8]) + dst = dst[8:] + } + } + } + if len(dst) != 0 { + panic("dst math") + } + + // Sweep up pair of columns, + // then down, assigning to right then left pixel. + // Repeat. + // See Figure 2 of http://www.pclviewer.com/rs2/qrtopology.htm + siz := len(p.Pixel) + rem := make([]Pixel, 7) + for i := range rem { + rem[i] = Extra.Pixel() + } + src := append(bits, rem...) + for x := siz; x > 0; { + for y := siz - 1; y >= 0; y-- { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + if x == 7 { // vertical timing strip + x-- + } + for y := 0; y < siz; y++ { + if p.Pixel[y][x-1].Role() == 0 { + p.Pixel[y][x-1], src = src[0], src[1:] + } + if p.Pixel[y][x-2].Role() == 0 { + p.Pixel[y][x-2], src = src[0], src[1:] + } + } + x -= 2 + } + return nil +} + +// mplan edits a version+level-only Plan to add the mask. +func mplan(m Mask, p *Plan) error { + p.Mask = m + for y, row := range p.Pixel { + for x, pix := range row { + if r := pix.Role(); (r == Data || r == Check || r == Extra) && p.Mask.Invert(y, x) { + row[x] ^= Black | Invert + } + } + } + return nil +} + +// posBox draws a position (large) box at upper left x, y. +func posBox(m [][]Pixel, x, y int) { + pos := Position.Pixel() + // box + for dy := 0; dy < 7; dy++ { + for dx := 0; dx < 7; dx++ { + p := pos + if dx == 0 || dx == 6 || dy == 0 || dy == 6 || 2 <= dx && dx <= 4 && 2 <= dy && dy <= 4 { + p |= Black + } + m[y+dy][x+dx] = p + } + } + // white border + for dy := -1; dy < 8; dy++ { + if 0 <= y+dy && y+dy < len(m) { + if x > 0 { + m[y+dy][x-1] = pos + } + if x+7 < len(m) { + m[y+dy][x+7] = pos + } + } + } + for dx := -1; dx < 8; dx++ { + if 0 <= x+dx && x+dx < len(m) { + if y > 0 { + m[y-1][x+dx] = pos + } + if y+7 < len(m) { + m[y+7][x+dx] = pos + } + } + } +} + +// alignBox draw an alignment (small) box at upper left x, y. +func alignBox(m [][]Pixel, x, y int) { + // box + align := Alignment.Pixel() + for dy := 0; dy < 5; dy++ { + for dx := 0; dx < 5; dx++ { + p := align + if dx == 0 || dx == 4 || dy == 0 || dy == 4 || dx == 2 && dy == 2 { + p |= Black + } + m[y+dy][x+dx] = p + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/qr_test.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/qr_test.go new file mode 100644 index 0000000..6eaf46e --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/coding/qr_test.go @@ -0,0 +1,133 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package coding + +import ( + "bytes" + "testing" + + "code.google.com/p/rsc/gf256" + "code.google.com/p/rsc/qr/libqrencode" +) + +func test(t *testing.T, v Version, l Level, text ...Encoding) bool { + s := "" + ty := libqrencode.EightBit + switch x := text[0].(type) { + case String: + s = string(x) + case Alpha: + s = string(x) + ty = libqrencode.Alphanumeric + case Num: + s = string(x) + ty = libqrencode.Numeric + } + key, err := libqrencode.Encode(libqrencode.Version(v), libqrencode.Level(l), ty, s) + if err != nil { + t.Errorf("libqrencode.Encode(%v, %v, %d, %#q): %v", v, l, ty, s, err) + return false + } + mask := (^key.Pixel[8][2]&1)<<2 | (key.Pixel[8][3]&1)<<1 | (^key.Pixel[8][4] & 1) + p, err := NewPlan(v, l, Mask(mask)) + if err != nil { + t.Errorf("NewPlan(%v, L, %d): %v", v, err, mask) + return false + } + if len(p.Pixel) != len(key.Pixel) { + t.Errorf("%v: NewPlan uses %dx%d, libqrencode uses %dx%d", v, len(p.Pixel), len(p.Pixel), len(key.Pixel), len(key.Pixel)) + return false + } + c, err := p.Encode(text...) + if err != nil { + t.Errorf("Encode: %v", err) + return false + } + badpix := 0 +Pixel: + for y, prow := range p.Pixel { + for x, pix := range prow { + pix &^= Black + if c.Black(x, y) { + pix |= Black + } + + keypix := key.Pixel[y][x] + want := Pixel(0) + switch { + case keypix&libqrencode.Finder != 0: + want = Position.Pixel() + case keypix&libqrencode.Alignment != 0: + want = Alignment.Pixel() + case keypix&libqrencode.Timing != 0: + want = Timing.Pixel() + case keypix&libqrencode.Format != 0: + want = Format.Pixel() + want |= OffsetPixel(pix.Offset()) // sic + want |= pix & Invert + case keypix&libqrencode.PVersion != 0: + want = PVersion.Pixel() + case keypix&libqrencode.DataECC != 0: + if pix.Role() == Check || pix.Role() == Extra { + want = pix.Role().Pixel() + } else { + want = Data.Pixel() + } + want |= OffsetPixel(pix.Offset()) + want |= pix & Invert + default: + want = Unused.Pixel() + } + if keypix&libqrencode.Black != 0 { + want |= Black + } + if pix != want { + t.Errorf("%v/%v: Pixel[%d][%d] = %v, want %v %#x", v, mask, y, x, pix, want, keypix) + if badpix++; badpix >= 100 { + t.Errorf("stopping after %d bad pixels", badpix) + break Pixel + } + } + } + } + return badpix == 0 +} + +var input = []Encoding{ + String("hello"), + Num("1"), + Num("12"), + Num("123"), + Alpha("AB"), + Alpha("ABC"), +} + +func TestVersion(t *testing.T) { + badvers := 0 +Version: + for v := Version(1); v <= 40; v++ { + for l := L; l <= H; l++ { + for _, in := range input { + if !test(t, v, l, in) { + if badvers++; badvers >= 10 { + t.Errorf("stopping after %d bad versions", badvers) + break Version + } + } + } + } + } +} + +func TestEncode(t *testing.T) { + data := []byte{0x10, 0x20, 0x0c, 0x56, 0x61, 0x80, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11, 0xec, 0x11} + check := []byte{0xa5, 0x24, 0xd4, 0xc1, 0xed, 0x36, 0xc7, 0x87, 0x2c, 0x55} + rs := gf256.NewRSEncoder(Field, len(check)) + out := make([]byte, len(check)) + rs.ECC(data, out) + if !bytes.Equal(out, check) { + t.Errorf("have %x want %x", out, check) + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/libqrencode/qrencode.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/libqrencode/qrencode.go new file mode 100644 index 0000000..f4ce3ff --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/libqrencode/qrencode.go @@ -0,0 +1,149 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package libqrencode wraps the C libqrencode library. +// The qr package (in this package's parent directory) +// does not use any C wrapping. This code is here only +// for use during that package's tests. +package libqrencode + +/* +#cgo LDFLAGS: -lqrencode +#include +*/ +import "C" + +import ( + "fmt" + "image" + "image/color" + "unsafe" +) + +type Version int + +type Mode int + +const ( + Numeric Mode = C.QR_MODE_NUM + Alphanumeric Mode = C.QR_MODE_AN + EightBit Mode = C.QR_MODE_8 +) + +type Level int + +const ( + L Level = C.QR_ECLEVEL_L + M Level = C.QR_ECLEVEL_M + Q Level = C.QR_ECLEVEL_Q + H Level = C.QR_ECLEVEL_H +) + +type Pixel int + +const ( + Black Pixel = 1 << iota + DataECC + Format + PVersion + Timing + Alignment + Finder + NonData +) + +type Code struct { + Version int + Width int + Pixel [][]Pixel + Scale int +} + +func (*Code) ColorModel() color.Model { + return color.RGBAModel +} + +func (c *Code) Bounds() image.Rectangle { + d := (c.Width + 8) * c.Scale + return image.Rect(0, 0, d, d) +} + +var ( + white color.Color = color.RGBA{0xFF, 0xFF, 0xFF, 0xFF} + black color.Color = color.RGBA{0x00, 0x00, 0x00, 0xFF} + blue color.Color = color.RGBA{0x00, 0x00, 0x80, 0xFF} + red color.Color = color.RGBA{0xFF, 0x40, 0x40, 0xFF} + yellow color.Color = color.RGBA{0xFF, 0xFF, 0x00, 0xFF} + gray color.Color = color.RGBA{0x80, 0x80, 0x80, 0xFF} + green color.Color = color.RGBA{0x22, 0x8B, 0x22, 0xFF} +) + +func (c *Code) At(x, y int) color.Color { + x = x/c.Scale - 4 + y = y/c.Scale - 4 + if 0 <= x && x < c.Width && 0 <= y && y < c.Width { + switch p := c.Pixel[y][x]; { + case p&Black == 0: + // nothing + case p&DataECC != 0: + return black + case p&Format != 0: + return blue + case p&PVersion != 0: + return red + case p&Timing != 0: + return yellow + case p&Alignment != 0: + return gray + case p&Finder != 0: + return green + } + } + return white +} + +type Chunk struct { + Mode Mode + Text string +} + +func Encode(version Version, level Level, mode Mode, text string) (*Code, error) { + return EncodeChunk(version, level, Chunk{mode, text}) +} + +func EncodeChunk(version Version, level Level, chunk ...Chunk) (*Code, error) { + qi, err := C.QRinput_new2(C.int(version), C.QRecLevel(level)) + if qi == nil { + return nil, fmt.Errorf("QRinput_new2: %v", err) + } + defer C.QRinput_free(qi) + for _, ch := range chunk { + data := []byte(ch.Text) + n, err := C.QRinput_append(qi, C.QRencodeMode(ch.Mode), C.int(len(data)), (*C.uchar)(&data[0])) + if n < 0 { + return nil, fmt.Errorf("QRinput_append %q: %v", data, err) + } + } + + qc, err := C.QRcode_encodeInput(qi) + if qc == nil { + return nil, fmt.Errorf("QRinput_encodeInput: %v", err) + } + + c := &Code{ + Version: int(qc.version), + Width: int(qc.width), + Scale: 16, + } + pix := make([]Pixel, c.Width*c.Width) + cdat := (*[1000 * 1000]byte)(unsafe.Pointer(qc.data))[:len(pix)] + for i := range pix { + pix[i] = Pixel(cdat[i]) + } + c.Pixel = make([][]Pixel, c.Width) + for i := range c.Pixel { + c.Pixel[i] = pix[i*c.Width : (i+1)*c.Width] + } + return c, nil +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/png.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/png.go new file mode 100644 index 0000000..db49d05 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/png.go @@ -0,0 +1,400 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package qr + +// PNG writer for QR codes. + +import ( + "bytes" + "encoding/binary" + "hash" + "hash/crc32" +) + +// PNG returns a PNG image displaying the code. +// +// PNG uses a custom encoder tailored to QR codes. +// Its compressed size is about 2x away from optimal, +// but it runs about 20x faster than calling png.Encode +// on c.Image(). +func (c *Code) PNG() []byte { + var p pngWriter + return p.encode(c) +} + +type pngWriter struct { + tmp [16]byte + wctmp [4]byte + buf bytes.Buffer + zlib bitWriter + crc hash.Hash32 +} + +var pngHeader = []byte("\x89PNG\r\n\x1a\n") + +func (w *pngWriter) encode(c *Code) []byte { + scale := c.Scale + siz := c.Size + + w.buf.Reset() + + // Header + w.buf.Write(pngHeader) + + // Header block + binary.BigEndian.PutUint32(w.tmp[0:4], uint32((siz+8)*scale)) + binary.BigEndian.PutUint32(w.tmp[4:8], uint32((siz+8)*scale)) + w.tmp[8] = 1 // 1-bit + w.tmp[9] = 0 // gray + w.tmp[10] = 0 + w.tmp[11] = 0 + w.tmp[12] = 0 + w.writeChunk("IHDR", w.tmp[:13]) + + // Comment + w.writeChunk("tEXt", comment) + + // Data + w.zlib.writeCode(c) + w.writeChunk("IDAT", w.zlib.bytes.Bytes()) + + // End + w.writeChunk("IEND", nil) + + return w.buf.Bytes() +} + +var comment = []byte("Software\x00QR-PNG http://qr.swtch.com/") + +func (w *pngWriter) writeChunk(name string, data []byte) { + if w.crc == nil { + w.crc = crc32.NewIEEE() + } + binary.BigEndian.PutUint32(w.wctmp[0:4], uint32(len(data))) + w.buf.Write(w.wctmp[0:4]) + w.crc.Reset() + copy(w.wctmp[0:4], name) + w.buf.Write(w.wctmp[0:4]) + w.crc.Write(w.wctmp[0:4]) + w.buf.Write(data) + w.crc.Write(data) + crc := w.crc.Sum32() + binary.BigEndian.PutUint32(w.wctmp[0:4], crc) + w.buf.Write(w.wctmp[0:4]) +} + +func (b *bitWriter) writeCode(c *Code) { + const ftNone = 0 + + b.adler32.Reset() + b.bytes.Reset() + b.nbit = 0 + + scale := c.Scale + siz := c.Size + + // zlib header + b.tmp[0] = 0x78 + b.tmp[1] = 0 + b.tmp[1] += uint8(31 - (uint16(b.tmp[0])<<8+uint16(b.tmp[1]))%31) + b.bytes.Write(b.tmp[0:2]) + + // Start flate block. + b.writeBits(1, 1, false) // final block + b.writeBits(1, 2, false) // compressed, fixed Huffman tables + + // White border. + // First row. + b.byte(ftNone) + n := (scale*(siz+8) + 7) / 8 + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + row := make([]byte, 1+n) + for y := 0; y < siz; y++ { + row[0] = ftNone + j := 1 + var z uint8 + nz := 0 + for x := -4; x < siz+4; x++ { + // Raw data. + for i := 0; i < scale; i++ { + z <<= 1 + if !c.Black(x, y) { + z |= 1 + } + if nz++; nz == 8 { + row[j] = z + j++ + nz = 0 + } + } + } + if j < len(row) { + row[j] = z + } + for _, z := range row { + b.byte(z) + } + + // Scale-1 copies. + b.repeat((scale-1)*(1+n), 1+n) + + b.adler32.WriteN(row, scale) + } + + // White border. + // First row. + b.byte(ftNone) + b.byte(255) + b.repeat(n-1, 1) + // 4*scale rows total. + b.repeat((4*scale-1)*(1+n), 1+n) + + for i := 0; i < 4*scale; i++ { + b.adler32.WriteNByte(ftNone, 1) + b.adler32.WriteNByte(255, n) + } + + // End of block. + b.hcode(256) + b.flushBits() + + // adler32 + binary.BigEndian.PutUint32(b.tmp[0:], b.adler32.Sum32()) + b.bytes.Write(b.tmp[0:4]) +} + +// A bitWriter is a write buffer for bit-oriented data like deflate. +type bitWriter struct { + bytes bytes.Buffer + bit uint32 + nbit uint + + tmp [4]byte + adler32 adigest +} + +func (b *bitWriter) writeBits(bit uint32, nbit uint, rev bool) { + // reverse, for huffman codes + if rev { + br := uint32(0) + for i := uint(0); i < nbit; i++ { + br |= ((bit >> i) & 1) << (nbit - 1 - i) + } + bit = br + } + b.bit |= bit << b.nbit + b.nbit += nbit + for b.nbit >= 8 { + b.bytes.WriteByte(byte(b.bit)) + b.bit >>= 8 + b.nbit -= 8 + } +} + +func (b *bitWriter) flushBits() { + if b.nbit > 0 { + b.bytes.WriteByte(byte(b.bit)) + b.nbit = 0 + b.bit = 0 + } +} + +func (b *bitWriter) hcode(v int) { + /* + Lit Value Bits Codes + --------- ---- ----- + 0 - 143 8 00110000 through + 10111111 + 144 - 255 9 110010000 through + 111111111 + 256 - 279 7 0000000 through + 0010111 + 280 - 287 8 11000000 through + 11000111 + */ + switch { + case v <= 143: + b.writeBits(uint32(v)+0x30, 8, true) + case v <= 255: + b.writeBits(uint32(v-144)+0x190, 9, true) + case v <= 279: + b.writeBits(uint32(v-256)+0, 7, true) + case v <= 287: + b.writeBits(uint32(v-280)+0xc0, 8, true) + default: + panic("invalid hcode") + } +} + +func (b *bitWriter) byte(x byte) { + b.hcode(int(x)) +} + +func (b *bitWriter) codex(c int, val int, nx uint) { + b.hcode(c + val>>nx) + b.writeBits(uint32(val)&(1<= 258+3; n -= 258 { + b.repeat1(258, d) + } + if n > 258 { + // 258 < n < 258+3 + b.repeat1(10, d) + b.repeat1(n-10, d) + return + } + if n < 3 { + panic("invalid flate repeat") + } + b.repeat1(n, d) +} + +func (b *bitWriter) repeat1(n, d int) { + /* + Extra Extra Extra + Code Bits Length(s) Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- + 257 0 3 267 1 15,16 277 4 67-82 + 258 0 4 268 1 17,18 278 4 83-98 + 259 0 5 269 2 19-22 279 4 99-114 + 260 0 6 270 2 23-26 280 4 115-130 + 261 0 7 271 2 27-30 281 5 131-162 + 262 0 8 272 2 31-34 282 5 163-194 + 263 0 9 273 3 35-42 283 5 195-226 + 264 0 10 274 3 43-50 284 5 227-257 + 265 1 11,12 275 3 51-58 285 0 258 + 266 1 13,14 276 3 59-66 + */ + switch { + case n <= 10: + b.codex(257, n-3, 0) + case n <= 18: + b.codex(265, n-11, 1) + case n <= 34: + b.codex(269, n-19, 2) + case n <= 66: + b.codex(273, n-35, 3) + case n <= 130: + b.codex(277, n-67, 4) + case n <= 257: + b.codex(281, n-131, 5) + case n == 258: + b.hcode(285) + default: + panic("invalid repeat length") + } + + /* + Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- + 0 0 1 10 4 33-48 20 9 1025-1536 + 1 0 2 11 4 49-64 21 9 1537-2048 + 2 0 3 12 5 65-96 22 10 2049-3072 + 3 0 4 13 5 97-128 23 10 3073-4096 + 4 1 5,6 14 6 129-192 24 11 4097-6144 + 5 1 7,8 15 6 193-256 25 11 6145-8192 + 6 2 9-12 16 7 257-384 26 12 8193-12288 + 7 2 13-16 17 7 385-512 27 12 12289-16384 + 8 3 17-24 18 8 513-768 28 13 16385-24576 + 9 3 25-32 19 8 769-1024 29 13 24577-32768 + */ + if d <= 4 { + b.writeBits(uint32(d-1), 5, true) + } else if d <= 32768 { + nbit := uint(16) + for d <= 1<<(nbit-1) { + nbit-- + } + v := uint32(d - 1) + v &^= 1 << (nbit - 1) // top bit is implicit + code := uint32(2*nbit - 2) // second bit is low bit of code + code |= v >> (nbit - 2) + v &^= 1 << (nbit - 2) + b.writeBits(code, 5, true) + // rest of bits follow + b.writeBits(uint32(v), nbit-2, false) + } else { + panic("invalid repeat distance") + } +} + +func (b *bitWriter) run(v byte, n int) { + if n == 0 { + return + } + b.byte(v) + if n-1 < 3 { + for i := 0; i < n-1; i++ { + b.byte(v) + } + } else { + b.repeat(n-1, 1) + } +} + +type adigest struct { + a, b uint32 +} + +func (d *adigest) Reset() { d.a, d.b = 1, 0 } + +const amod = 65521 + +func aupdate(a, b uint32, pi byte, n int) (aa, bb uint32) { + // TODO(rsc): 6g doesn't do magic multiplies for b %= amod, + // only for b = b%amod. + + // invariant: a, b < amod + if pi == 0 { + b += uint32(n%amod) * a + b = b % amod + return a, b + } + + // n times: + // a += pi + // b += a + // is same as + // b += n*a + n*(n+1)/2*pi + // a += n*pi + m := uint32(n) + b += (m % amod) * a + b = b % amod + b += (m * (m + 1) / 2) % amod * uint32(pi) + b = b % amod + a += (m % amod) * uint32(pi) + a = a % amod + return a, b +} + +func afinish(a, b uint32) uint32 { + return b<<16 | a +} + +func (d *adigest) WriteN(p []byte, n int) { + for i := 0; i < n; i++ { + for _, pi := range p { + d.a, d.b = aupdate(d.a, d.b, pi, 1) + } + } +} + +func (d *adigest) WriteNByte(pi byte, n int) { + d.a, d.b = aupdate(d.a, d.b, pi, n) +} + +func (d *adigest) Sum32() uint32 { return afinish(d.a, d.b) } diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/png_test.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/png_test.go new file mode 100644 index 0000000..27a6229 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/png_test.go @@ -0,0 +1,73 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package qr + +import ( + "bytes" + "image" + "image/color" + "image/png" + "io/ioutil" + "testing" +) + +func TestPNG(t *testing.T) { + c, err := Encode("hello, world", L) + if err != nil { + t.Fatal(err) + } + pngdat := c.PNG() + if true { + ioutil.WriteFile("x.png", pngdat, 0666) + } + m, err := png.Decode(bytes.NewBuffer(pngdat)) + if err != nil { + t.Fatal(err) + } + gm := m.(*image.Gray) + + scale := c.Scale + siz := c.Size + nbad := 0 + for y := 0; y < scale*(8+siz); y++ { + for x := 0; x < scale*(8+siz); x++ { + v := byte(255) + if c.Black(x/scale-4, y/scale-4) { + v = 0 + } + if gv := gm.At(x, y).(color.Gray).Y; gv != v { + t.Errorf("%d,%d = %d, want %d", x, y, gv, v) + if nbad++; nbad >= 20 { + t.Fatalf("too many bad pixels") + } + } + } + } +} + +func BenchmarkPNG(b *testing.B) { + c, err := Encode("0123456789012345678901234567890123456789", L) + if err != nil { + panic(err) + } + var bytes []byte + for i := 0; i < b.N; i++ { + bytes = c.PNG() + } + b.SetBytes(int64(len(bytes))) +} + +func BenchmarkImagePNG(b *testing.B) { + c, err := Encode("0123456789012345678901234567890123456789", L) + if err != nil { + panic(err) + } + var buf bytes.Buffer + for i := 0; i < b.N; i++ { + buf.Reset() + png.Encode(&buf, c.Image()) + } + b.SetBytes(int64(buf.Len())) +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/qr.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/qr.go new file mode 100644 index 0000000..a38fb8f --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/qr.go @@ -0,0 +1,116 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package qr encodes QR codes. +*/ +package qr + +import ( + "errors" + "image" + "image/color" + + "code.google.com/p/rsc/qr/coding" +) + +// A Level denotes a QR error correction level. +// From least to most tolerant of errors, they are L, M, Q, H. +type Level int + +const ( + L Level = iota // 20% redundant + M // 38% redundant + Q // 55% redundant + H // 65% redundant +) + +// Encode returns an encoding of text at the given error correction level. +func Encode(text string, level Level) (*Code, error) { + // Pick data encoding, smallest first. + // We could split the string and use different encodings + // but that seems like overkill for now. + var enc coding.Encoding + switch { + case coding.Num(text).Check() == nil: + enc = coding.Num(text) + case coding.Alpha(text).Check() == nil: + enc = coding.Alpha(text) + default: + enc = coding.String(text) + } + + // Pick size. + l := coding.Level(level) + var v coding.Version + for v = coding.MinVersion; ; v++ { + if v > coding.MaxVersion { + return nil, errors.New("text too long to encode as QR") + } + if enc.Bits(v) <= v.DataBytes(l)*8 { + break + } + } + + // Build and execute plan. + p, err := coding.NewPlan(v, l, 0) + if err != nil { + return nil, err + } + cc, err := p.Encode(enc) + if err != nil { + return nil, err + } + + // TODO: Pick appropriate mask. + + return &Code{cc.Bitmap, cc.Size, cc.Stride, 8}, nil +} + +// A Code is a square pixel grid. +// It implements image.Image and direct PNG encoding. +type Code struct { + Bitmap []byte // 1 is black, 0 is white + Size int // number of pixels on a side + Stride int // number of bytes per row + Scale int // number of image pixels per QR pixel +} + +// Black returns true if the pixel at (x,y) is black. +func (c *Code) Black(x, y int) bool { + return 0 <= x && x < c.Size && 0 <= y && y < c.Size && + c.Bitmap[y*c.Stride+x/8]&(1<> 24), byte(rgba >> 16), byte(rgba >> 8), byte(rgba)} + draw.Draw(c, r, u, image.ZP, draw.Src) + } + } + + if csize != 0 { + if font == "" { + font = "data/luxisr.ttf" + } + ctxt := fs.NewContext(req) + dat, _, err := ctxt.Read(font) + if err != nil { + panic(err) + } + tfont, err := freetype.ParseFont(dat) + if err != nil { + panic(err) + } + ft := freetype.NewContext() + ft.SetDst(c) + ft.SetDPI(100) + ft.SetFont(tfont) + ft.SetFontSize(float64(pt)) + ft.SetSrc(image.NewUniform(color.Black)) + ft.SetClip(image.Rect(0, 0, 0, 0)) + wid, err := ft.DrawString(caption, freetype.Pt(0, 0)) + if err != nil { + panic(err) + } + p := freetype.Pt(d, d+3*pt/2) + p.X -= wid.X + p.X /= 2 + ft.SetClip(c.Bounds()) + ft.DrawString(caption, p) + } + + return c +} + +func makeFrame(req *http.Request, font string, pt, vers, l, scale, dots int) image.Image { + lev := coding.Level(l) + p, err := coding.NewPlan(coding.Version(vers), lev, 0) + if err != nil { + panic(err) + } + + nd := p.DataBytes / p.Blocks + nc := p.CheckBytes / p.Blocks + extra := p.DataBytes - nd*p.Blocks + + cap := fmt.Sprintf("QR v%d, %s", vers, lev) + if dots > 0 { + cap = fmt.Sprintf("QR v%d order, from bottom right", vers) + } + m := makeImage(req, cap, font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 { + pix := p.Pixel[y][x] + switch pix.Role() { + case coding.Data: + if dots > 0 { + return 0xffffffff + } + off := int(pix.Offset() / 8) + nd := nd + var i int + for i = 0; i < p.Blocks; i++ { + if i == extra { + nd++ + } + if off < nd { + break + } + off -= nd + } + return blockColors[i%len(blockColors)] + case coding.Check: + if dots > 0 { + return 0xffffffff + } + i := (int(pix.Offset()/8) - p.DataBytes) / nc + return dark(blockColors[i%len(blockColors)]) + } + if pix&coding.Black != 0 { + return 0x000000ff + } + return 0xffffffff + }) + + if dots > 0 { + b := m.Bounds() + for y := 0; y <= len(p.Pixel); y++ { + for x := 0; x < b.Dx(); x++ { + m.SetRGBA(x, y*scale-(y/len(p.Pixel)), color.RGBA{127, 127, 127, 255}) + } + } + for x := 0; x <= len(p.Pixel); x++ { + for y := 0; y < b.Dx(); y++ { + m.SetRGBA(x*scale-(x/len(p.Pixel)), y, color.RGBA{127, 127, 127, 255}) + } + } + order := make([]image.Point, (p.DataBytes+p.CheckBytes)*8+1) + for y, row := range p.Pixel { + for x, pix := range row { + if r := pix.Role(); r != coding.Data && r != coding.Check { + continue + } + // draw.Draw(m, m.Bounds().Add(image.Pt(x*scale, y*scale)), dot, image.ZP, draw.Over) + order[pix.Offset()] = image.Point{x*scale + scale/2, y*scale + scale/2} + } + } + + for mode := 0; mode < 2; mode++ { + for i, p := range order { + q := order[i+1] + if q.X == 0 { + break + } + line(m, p, q, mode) + } + } + } + return m +} + +func line(m *image.RGBA, p, q image.Point, mode int) { + x := 0 + y := 0 + dx := q.X - p.X + dy := q.Y - p.Y + xsign := +1 + ysign := +1 + if dx < 0 { + xsign = -1 + dx = -dx + } + if dy < 0 { + ysign = -1 + dy = -dy + } + pt := func() { + switch mode { + case 0: + for dx := -2; dx <= 2; dx++ { + for dy := -2; dy <= 2; dy++ { + if dy*dx <= -4 || dy*dx >= 4 { + continue + } + m.SetRGBA(p.X+x*xsign+dx, p.Y+y*ysign+dy, color.RGBA{255, 192, 192, 255}) + } + } + + case 1: + m.SetRGBA(p.X+x*xsign, p.Y+y*ysign, color.RGBA{128, 0, 0, 255}) + } + } + if dx > dy { + for x < dx || y < dy { + pt() + x++ + if float64(x)*float64(dy)/float64(dx)-float64(y) > 0.5 { + y++ + } + } + } else { + for x < dx || y < dy { + pt() + y++ + if float64(y)*float64(dx)/float64(dy)-float64(x) > 0.5 { + x++ + } + } + } + pt() +} + +func pngEncode(c image.Image) []byte { + var b bytes.Buffer + png.Encode(&b, c) + return b.Bytes() +} + +// Frame handles a request for a single QR frame. +func Frame(w http.ResponseWriter, req *http.Request) { + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + v := arg("v") + scale := arg("scale") + if scale == 0 { + scale = 8 + } + + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(pngEncode(makeFrame(req, req.FormValue("font"), arg("pt"), v, arg("l"), scale, arg("dots")))) +} + +// Frames handles a request for multiple QR frames. +func Frames(w http.ResponseWriter, req *http.Request) { + vs := strings.Split(req.FormValue("v"), ",") + + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + scale := arg("scale") + if scale == 0 { + scale = 8 + } + font := req.FormValue("font") + pt := arg("pt") + dots := arg("dots") + + var images []image.Image + l := arg("l") + for _, v := range vs { + l := l + if i := strings.Index(v, "."); i >= 0 { + l, _ = strconv.Atoi(v[i+1:]) + v = v[:i] + } + vv, _ := strconv.Atoi(v) + images = append(images, makeFrame(req, font, pt, vv, l, scale, dots)) + } + + b := images[len(images)-1].Bounds() + + dx := arg("dx") + if dx == 0 { + dx = b.Dx() + } + x, y := 0, 0 + xmax := 0 + sep := arg("sep") + if sep == 0 { + sep = 10 + } + var points []image.Point + for i, m := range images { + if x > 0 { + x += sep + } + if x > 0 && x+m.Bounds().Dx() > dx { + y += sep + images[i-1].Bounds().Dy() + x = 0 + } + points = append(points, image.Point{x, y}) + x += m.Bounds().Dx() + if x > xmax { + xmax = x + } + + } + + c := image.NewRGBA(image.Rect(0, 0, xmax, y+b.Dy())) + for i, m := range images { + draw.Draw(c, c.Bounds().Add(points[i]), m, image.ZP, draw.Src) + } + + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(pngEncode(c)) +} + +// Mask handles a request for a single QR mask. +func Mask(w http.ResponseWriter, req *http.Request) { + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + v := arg("v") + m := arg("m") + scale := arg("scale") + if scale == 0 { + scale = 8 + } + + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(pngEncode(makeMask(req, req.FormValue("font"), arg("pt"), v, m, scale))) +} + +// Masks handles a request for multiple QR masks. +func Masks(w http.ResponseWriter, req *http.Request) { + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + v := arg("v") + scale := arg("scale") + if scale == 0 { + scale = 8 + } + font := req.FormValue("font") + pt := arg("pt") + var mm []image.Image + for m := 0; m < 8; m++ { + mm = append(mm, makeMask(req, font, pt, v, m, scale)) + } + dx := mm[0].Bounds().Dx() + dy := mm[0].Bounds().Dy() + + sep := arg("sep") + if sep == 0 { + sep = 10 + } + c := image.NewRGBA(image.Rect(0, 0, (dx+sep)*4-sep, (dy+sep)*2-sep)) + for m := 0; m < 8; m++ { + x := (m % 4) * (dx + sep) + y := (m / 4) * (dy + sep) + draw.Draw(c, c.Bounds().Add(image.Pt(x, y)), mm[m], image.ZP, draw.Src) + } + + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(pngEncode(c)) +} + +var maskName = []string{ + "(x+y) % 2", + "y % 2", + "x % 3", + "(x+y) % 3", + "(y/2 + x/3) % 2", + "xy%2 + xy%3", + "(xy%2 + xy%3) % 2", + "(xy%3 + (x+y)%2) % 2", +} + +func makeMask(req *http.Request, font string, pt int, vers, mask, scale int) image.Image { + p, err := coding.NewPlan(coding.Version(vers), coding.L, coding.Mask(mask)) + if err != nil { + panic(err) + } + m := makeImage(req, maskName[mask], font, pt, len(p.Pixel), 0, scale, func(x, y int) uint32 { + pix := p.Pixel[y][x] + switch pix.Role() { + case coding.Data, coding.Check: + if pix&coding.Invert != 0 { + return 0x000000ff + } + } + return 0xffffffff + }) + return m +} + +var blockColors = []uint32{ + 0x7777ffff, + 0xffff77ff, + 0xff7777ff, + 0x77ffffff, + 0x1e90ffff, + 0xffffe0ff, + 0x8b6969ff, + 0x77ff77ff, + 0x9b30ffff, + 0x00bfffff, + 0x90e890ff, + 0xfff68fff, + 0xffec8bff, + 0xffa07aff, + 0xffa54fff, + 0xeee8aaff, + 0x98fb98ff, + 0xbfbfbfff, + 0x54ff9fff, + 0xffaeb9ff, + 0xb23aeeff, + 0xbbffffff, + 0x7fffd4ff, + 0xff7a7aff, + 0x00007fff, +} + +func dark(x uint32) uint32 { + r, g, b, a := byte(x>>24), byte(x>>16), byte(x>>8), byte(x) + r = r/2 + r/4 + g = g/2 + g/4 + b = b/2 + b/4 + return uint32(r)<<24 | uint32(g)<<16 | uint32(b)<<8 | uint32(a) +} + +func clamp(x int) byte { + if x < 0 { + return 0 + } + if x > 255 { + return 255 + } + return byte(x) +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} + +// Arrow handles a request for an arrow pointing in a given direction. +func Arrow(w http.ResponseWriter, req *http.Request) { + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + dir := arg("dir") + size := arg("size") + if size == 0 { + size = 50 + } + del := size / 10 + + m := image.NewRGBA(image.Rect(0, 0, size, size)) + + if dir == 4 { + draw.Draw(m, m.Bounds(), image.Black, image.ZP, draw.Src) + draw.Draw(m, image.Rect(5, 5, size-5, size-5), image.White, image.ZP, draw.Src) + } + + pt := func(x, y int, c color.RGBA) { + switch dir { + case 0: + m.SetRGBA(x, y, c) + case 1: + m.SetRGBA(y, size-1-x, c) + case 2: + m.SetRGBA(size-1-x, size-1-y, c) + case 3: + m.SetRGBA(size-1-y, x, c) + } + } + + for y := 0; y < size/2; y++ { + for x := 0; x < del && x < y; x++ { + pt(x, y, color.RGBA{0, 0, 0, 255}) + } + for x := del; x < y-del; x++ { + pt(x, y, color.RGBA{128, 128, 255, 255}) + } + for x := max(y-del, 0); x <= y; x++ { + pt(x, y, color.RGBA{0, 0, 0, 255}) + } + } + for y := size / 2; y < size; y++ { + for x := 0; x < del && x < size-1-y; x++ { + pt(x, y, color.RGBA{0, 0, 0, 255}) + } + for x := del; x < size-1-y-del; x++ { + pt(x, y, color.RGBA{128, 128, 192, 255}) + } + for x := max(size-1-y-del, 0); x <= size-1-y; x++ { + pt(x, y, color.RGBA{0, 0, 0, 255}) + } + } + + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(pngEncode(m)) +} + +// Encode encodes a string using the given version, level, and mask. +func Encode(w http.ResponseWriter, req *http.Request) { + val := func(s string) int { + v, _ := strconv.Atoi(req.FormValue(s)) + return v + } + + l := coding.Level(val("l")) + v := coding.Version(val("v")) + enc := coding.String(req.FormValue("t")) + m := coding.Mask(val("m")) + + p, err := coding.NewPlan(v, l, m) + if err != nil { + panic(err) + } + cc, err := p.Encode(enc) + if err != nil { + panic(err) + } + + c := &qr.Code{Bitmap: cc.Bitmap, Size: cc.Size, Stride: cc.Stride, Scale: 8} + w.Header().Set("Content-Type", "image/png") + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(c.PNG()) +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/play.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/play.go new file mode 100644 index 0000000..52fdb2e --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/play.go @@ -0,0 +1,1118 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +QR data layout + +qr/ + upload/ + id.png + id.fix + flag/ + id + +*/ +// TODO: Random seed taken from GET for caching, repeatability. +// TODO: Flag for abuse button + some kind of dashboard. +// TODO: +1 button on web page? permalink? +// TODO: Flag for abuse button on permalinks too? +// TODO: Make the page prettier. +// TODO: Cache headers. + +package web + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/json" + "fmt" + "html/template" + "image" + "image/color" + _ "image/gif" + _ "image/jpeg" + "image/png" + "io" + "math/rand" + "net/http" + "net/url" + "os" + "sort" + "strconv" + "strings" + "time" + + "code.google.com/p/rsc/appfs/fs" + "code.google.com/p/rsc/gf256" + "code.google.com/p/rsc/qr" + "code.google.com/p/rsc/qr/coding" + "code.google.com/p/rsc/qr/web/resize" +) + +func runTemplate(c *fs.Context, w http.ResponseWriter, name string, data interface{}) { + t := template.New("main") + + main, _, err := c.Read(name) + if err != nil { + panic(err) + } + style, _, _ := c.Read("style.html") + main = append(main, style...) + _, err = t.Parse(string(main)) + if err != nil { + panic(err) + } + + var buf bytes.Buffer + if err := t.Execute(&buf, &data); err != nil { + panic(err) + } + w.Write(buf.Bytes()) +} + +func isImgName(s string) bool { + if len(s) != 32 { + return false + } + for i := 0; i < len(s); i++ { + if '0' <= s[i] && s[i] <= '9' || 'a' <= s[i] && s[i] <= 'f' { + continue + } + return false + } + return true +} + +func isTagName(s string) bool { + if len(s) != 16 { + return false + } + for i := 0; i < len(s); i++ { + if '0' <= s[i] && s[i] <= '9' || 'a' <= s[i] && s[i] <= 'f' { + continue + } + return false + } + return true +} + +// Draw is the handler for drawing a QR code. +func Draw(w http.ResponseWriter, req *http.Request) { + ctxt := fs.NewContext(req) + + url := req.FormValue("url") + if url == "" { + url = "http://swtch.com/qr" + } + if req.FormValue("upload") == "1" { + upload(w, req, url) + return + } + + t0 := time.Now() + img := req.FormValue("i") + if !isImgName(img) { + img = "pjw" + } + if req.FormValue("show") == "png" { + i := loadSize(ctxt, img, 48) + var buf bytes.Buffer + png.Encode(&buf, i) + w.Write(buf.Bytes()) + return + } + if req.FormValue("flag") == "1" { + flag(w, req, img, ctxt) + return + } + if req.FormValue("x") == "" { + var data = struct { + Name string + URL string + }{ + Name: img, + URL: url, + } + runTemplate(ctxt, w, "qr/main.html", &data) + return + } + + arg := func(s string) int { x, _ := strconv.Atoi(req.FormValue(s)); return x } + targ := makeTarg(ctxt, img, 17+4*arg("v")+arg("z")) + + m := &Image{ + Name: img, + Dx: arg("x"), + Dy: arg("y"), + URL: req.FormValue("u"), + Version: arg("v"), + Mask: arg("m"), + RandControl: arg("r") > 0, + Dither: arg("i") > 0, + OnlyDataBits: arg("d") > 0, + SaveControl: arg("c") > 0, + Scale: arg("scale"), + Target: targ, + Seed: int64(arg("s")), + Rotation: arg("o"), + Size: arg("z"), + } + if m.Version > 8 { + m.Version = 8 + } + + if m.Scale == 0 { + if arg("l") > 1 { + m.Scale = 8 + } else { + m.Scale = 4 + } + } + if m.Version >= 12 && m.Scale >= 4 { + m.Scale /= 2 + } + + if arg("l") == 1 { + data, err := json.Marshal(m) + if err != nil { + panic(err) + } + h := md5.New() + h.Write(data) + tag := fmt.Sprintf("%x", h.Sum(nil))[:16] + if err := ctxt.Write("qrsave/"+tag, data); err != nil { + panic(err) + } + http.Redirect(w, req, "/qr/show/"+tag, http.StatusTemporaryRedirect) + return + } + + if err := m.Encode(req); err != nil { + fmt.Fprintf(w, "%s\n", err) + return + } + + var dat []byte + switch { + case m.SaveControl: + dat = m.Control + default: + dat = m.Code.PNG() + } + + if arg("l") > 0 { + w.Header().Set("Content-Type", "image/png") + w.Write(dat) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprint(w, "

") + fmt.Fprintf(w, "
\n", m.Link()) + fmt.Fprintf(w, "
\n") + fmt.Fprintf(w, "
%v
\n", time.Now().Sub(t0)) +} + +func (m *Image) Small() bool { + return 8*(17+4*int(m.Version)) < 512 +} + +func (m *Image) Link() string { + s := fmt.Sprint + b := func(v bool) string { + if v { + return "1" + } + return "0" + } + val := url.Values{ + "i": {m.Name}, + "x": {s(m.Dx)}, + "y": {s(m.Dy)}, + "z": {s(m.Size)}, + "u": {m.URL}, + "v": {s(m.Version)}, + "m": {s(m.Mask)}, + "r": {b(m.RandControl)}, + "t": {b(m.Dither)}, + "d": {b(m.OnlyDataBits)}, + "c": {b(m.SaveControl)}, + "s": {s(m.Seed)}, + } + return "/qr/draw?" + val.Encode() +} + +// Show is the handler for showing a stored QR code. +func Show(w http.ResponseWriter, req *http.Request) { + ctxt := fs.NewContext(req) + tag := req.URL.Path[len("/qr/show/"):] + png := strings.HasSuffix(tag, ".png") + if png { + tag = tag[:len(tag)-len(".png")] + } + if !isTagName(tag) { + fmt.Fprintf(w, "Sorry, QR code not found\n") + return + } + if req.FormValue("flag") == "1" { + flag(w, req, tag, ctxt) + return + } + data, _, err := ctxt.Read("qrsave/" + tag) + if err != nil { + fmt.Fprintf(w, "Sorry, QR code not found.\n") + return + } + + var m Image + if err := json.Unmarshal(data, &m); err != nil { + panic(err) + } + m.Tag = tag + + switch req.FormValue("size") { + case "big": + m.Scale *= 2 + case "small": + m.Scale /= 2 + } + + if png { + if err := m.Encode(req); err != nil { + panic(err) + return + } + w.Header().Set("Cache-Control", "public, max-age=3600") + w.Write(m.Code.PNG()) + return + } + + w.Header().Set("Cache-Control", "public, max-age=300") + runTemplate(ctxt, w, "qr/permalink.html", &m) +} + +func upload(w http.ResponseWriter, req *http.Request, link string) { + // Upload of a new image. + // Copied from Moustachio demo. + f, _, err := req.FormFile("image") + if err != nil { + fmt.Fprintf(w, "You need to select an image to upload.\n") + return + } + defer f.Close() + + i, _, err := image.Decode(f) + if err != nil { + panic(err) + } + + // Convert image to 128x128 gray+alpha. + b := i.Bounds() + const max = 128 + // If it's gigantic, it's more efficient to downsample first + // and then resize; resizing will smooth out the roughness. + var i1 *image.RGBA + if b.Dx() > 4*max || b.Dy() > 4*max { + w, h := 2*max, 2*max + if b.Dx() > b.Dy() { + h = b.Dy() * h / b.Dx() + } else { + w = b.Dx() * w / b.Dy() + } + i1 = resize.Resample(i, b, w, h) + } else { + // "Resample" to same size, just to convert to RGBA. + i1 = resize.Resample(i, b, b.Dx(), b.Dy()) + } + b = i1.Bounds() + + // Encode to PNG. + dx, dy := 128, 128 + if b.Dx() > b.Dy() { + dy = b.Dy() * dx / b.Dx() + } else { + dx = b.Dx() * dy / b.Dy() + } + i128 := resize.ResizeRGBA(i1, i1.Bounds(), dx, dy) + + var buf bytes.Buffer + if err := png.Encode(&buf, i128); err != nil { + panic(err) + } + + h := md5.New() + h.Write(buf.Bytes()) + tag := fmt.Sprintf("%x", h.Sum(nil))[:32] + + ctxt := fs.NewContext(req) + if err := ctxt.Write("qr/upload/"+tag+".png", buf.Bytes()); err != nil { + panic(err) + } + + // Redirect with new image tag. + // Redirect to draw with new image tag. + http.Redirect(w, req, req.URL.Path+"?"+url.Values{"i": {tag}, "url": {link}}.Encode(), 302) +} + +func flag(w http.ResponseWriter, req *http.Request, img string, ctxt *fs.Context) { + if !isImgName(img) && !isTagName(img) { + fmt.Fprintf(w, "Invalid image.\n") + return + } + data, _, _ := ctxt.Read("qr/flag/" + img) + data = append(data, '!') + ctxt.Write("qr/flag/"+img, data) + + fmt.Fprintf(w, "Thank you. The image has been reported.\n") +} + +func loadSize(ctxt *fs.Context, name string, max int) *image.RGBA { + data, _, err := ctxt.Read("qr/upload/" + name + ".png") + if err != nil { + panic(err) + } + i, _, err := image.Decode(bytes.NewBuffer(data)) + if err != nil { + panic(err) + } + b := i.Bounds() + dx, dy := max, max + if b.Dx() > b.Dy() { + dy = b.Dy() * dx / b.Dx() + } else { + dx = b.Dx() * dy / b.Dy() + } + var irgba *image.RGBA + switch i := i.(type) { + case *image.RGBA: + irgba = resize.ResizeRGBA(i, i.Bounds(), dx, dy) + case *image.NRGBA: + irgba = resize.ResizeNRGBA(i, i.Bounds(), dx, dy) + } + return irgba +} + +func makeTarg(ctxt *fs.Context, name string, max int) [][]int { + i := loadSize(ctxt, name, max) + b := i.Bounds() + dx, dy := b.Dx(), b.Dy() + targ := make([][]int, dy) + arr := make([]int, dx*dy) + for y := 0; y < dy; y++ { + targ[y], arr = arr[:dx], arr[dx:] + row := targ[y] + for x := 0; x < dx; x++ { + p := i.Pix[y*i.Stride+4*x:] + r, g, b, a := p[0], p[1], p[2], p[3] + if a == 0 { + row[x] = -1 + } else { + row[x] = int((299*uint32(r) + 587*uint32(g) + 114*uint32(b) + 500) / 1000) + } + } + } + return targ +} + +type Image struct { + Name string + Target [][]int + Dx int + Dy int + URL string + Tag string + Version int + Mask int + Scale int + Rotation int + Size int + + // RandControl says to pick the pixels randomly. + RandControl bool + Seed int64 + + // Dither says to dither instead of using threshold pixel layout. + Dither bool + + // OnlyDataBits says to use only data bits, not check bits. + OnlyDataBits bool + + // Code is the final QR code. + Code *qr.Code + + // Control is a PNG showing the pixels that we controlled. + // Pixels we don't control are grayed out. + SaveControl bool + Control []byte +} + +type Pixinfo struct { + X int + Y int + Pix coding.Pixel + Targ byte + DTarg int + Contrast int + HardZero bool + Block *BitBlock + Bit uint +} + +type Pixorder struct { + Off int + Priority int +} + +type byPriority []Pixorder + +func (x byPriority) Len() int { return len(x) } +func (x byPriority) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byPriority) Less(i, j int) bool { return x[i].Priority > x[j].Priority } + +func (m *Image) target(x, y int) (targ byte, contrast int) { + tx := x + m.Dx + ty := y + m.Dy + if ty < 0 || ty >= len(m.Target) || tx < 0 || tx >= len(m.Target[ty]) { + return 255, -1 + } + + v0 := m.Target[ty][tx] + if v0 < 0 { + return 255, -1 + } + targ = byte(v0) + + n := 0 + sum := 0 + sumsq := 0 + const del = 5 + for dy := -del; dy <= del; dy++ { + for dx := -del; dx <= del; dx++ { + if 0 <= ty+dy && ty+dy < len(m.Target) && 0 <= tx+dx && tx+dx < len(m.Target[ty+dy]) { + v := m.Target[ty+dy][tx+dx] + sum += v + sumsq += v * v + n++ + } + } + } + + avg := sum / n + contrast = sumsq/n - avg*avg + return +} + +func (m *Image) rotate(p *coding.Plan, rot int) { + if rot == 0 { + return + } + + N := len(p.Pixel) + pix := make([][]coding.Pixel, N) + apix := make([]coding.Pixel, N*N) + for i := range pix { + pix[i], apix = apix[:N], apix[N:] + } + + switch rot { + case 0: + // ok + case 1: + for y := 0; y < N; y++ { + for x := 0; x < N; x++ { + pix[y][x] = p.Pixel[x][N-1-y] + } + } + case 2: + for y := 0; y < N; y++ { + for x := 0; x < N; x++ { + pix[y][x] = p.Pixel[N-1-y][N-1-x] + } + } + case 3: + for y := 0; y < N; y++ { + for x := 0; x < N; x++ { + pix[y][x] = p.Pixel[N-1-x][y] + } + } + } + + p.Pixel = pix +} + +func (m *Image) Encode(req *http.Request) error { + p, err := coding.NewPlan(coding.Version(m.Version), coding.L, coding.Mask(m.Mask)) + if err != nil { + return err + } + + m.rotate(p, m.Rotation) + + rand := rand.New(rand.NewSource(m.Seed)) + + // QR parameters. + nd := p.DataBytes / p.Blocks + nc := p.CheckBytes / p.Blocks + extra := p.DataBytes - nd*p.Blocks + rs := gf256.NewRSEncoder(coding.Field, nc) + + // Build information about pixels, indexed by data/check bit number. + pixByOff := make([]Pixinfo, (p.DataBytes+p.CheckBytes)*8) + expect := make([][]bool, len(p.Pixel)) + for y, row := range p.Pixel { + expect[y] = make([]bool, len(row)) + for x, pix := range row { + targ, contrast := m.target(x, y) + if m.RandControl && contrast >= 0 { + contrast = rand.Intn(128) + 64*((x+y)%2) + 64*((x+y)%3%2) + } + expect[y][x] = pix&coding.Black != 0 + if r := pix.Role(); r == coding.Data || r == coding.Check { + pixByOff[pix.Offset()] = Pixinfo{X: x, Y: y, Pix: pix, Targ: targ, Contrast: contrast} + } + } + } + +Again: + // Count fixed initial data bits, prepare template URL. + url := m.URL + "#" + var b coding.Bits + coding.String(url).Encode(&b, p.Version) + coding.Num("").Encode(&b, p.Version) + bbit := b.Bits() + dbit := p.DataBytes*8 - bbit + if dbit < 0 { + return fmt.Errorf("cannot encode URL into available bits") + } + num := make([]byte, dbit/10*3) + for i := range num { + num[i] = '0' + } + b.Pad(dbit) + b.Reset() + coding.String(url).Encode(&b, p.Version) + coding.Num(num).Encode(&b, p.Version) + b.AddCheckBytes(p.Version, p.Level) + data := b.Bytes() + + doff := 0 // data offset + coff := 0 // checksum offset + mbit := bbit + dbit/10*10 + + // Choose pixels. + bitblocks := make([]*BitBlock, p.Blocks) + for blocknum := 0; blocknum < p.Blocks; blocknum++ { + if blocknum == p.Blocks-extra { + nd++ + } + + bdata := data[doff/8 : doff/8+nd] + cdata := data[p.DataBytes+coff/8 : p.DataBytes+coff/8+nc] + bb := newBlock(nd, nc, rs, bdata, cdata) + bitblocks[blocknum] = bb + + // Determine which bits in this block we can try to edit. + lo, hi := 0, nd*8 + if lo < bbit-doff { + lo = bbit - doff + if lo > hi { + lo = hi + } + } + if hi > mbit-doff { + hi = mbit - doff + if hi < lo { + hi = lo + } + } + + // Preserve [0, lo) and [hi, nd*8). + for i := 0; i < lo; i++ { + if !bb.canSet(uint(i), (bdata[i/8]>>uint(7-i&7))&1) { + return fmt.Errorf("cannot preserve required bits") + } + } + for i := hi; i < nd*8; i++ { + if !bb.canSet(uint(i), (bdata[i/8]>>uint(7-i&7))&1) { + return fmt.Errorf("cannot preserve required bits") + } + } + + // Can edit [lo, hi) and checksum bits to hit target. + // Determine which ones to try first. + order := make([]Pixorder, (hi-lo)+nc*8) + for i := lo; i < hi; i++ { + order[i-lo].Off = doff + i + } + for i := 0; i < nc*8; i++ { + order[hi-lo+i].Off = p.DataBytes*8 + coff + i + } + if m.OnlyDataBits { + order = order[:hi-lo] + } + for i := range order { + po := &order[i] + po.Priority = pixByOff[po.Off].Contrast<<8 | rand.Intn(256) + } + sort.Sort(byPriority(order)) + + const mark = false + for i := range order { + po := &order[i] + pinfo := &pixByOff[po.Off] + bval := pinfo.Targ + if bval < 128 { + bval = 1 + } else { + bval = 0 + } + pix := pinfo.Pix + if pix&coding.Invert != 0 { + bval ^= 1 + } + if pinfo.HardZero { + bval = 0 + } + + var bi int + if pix.Role() == coding.Data { + bi = po.Off - doff + } else { + bi = po.Off - p.DataBytes*8 - coff + nd*8 + } + if bb.canSet(uint(bi), bval) { + pinfo.Block = bb + pinfo.Bit = uint(bi) + if mark { + p.Pixel[pinfo.Y][pinfo.X] = coding.Black + } + } else { + if pinfo.HardZero { + panic("hard zero") + } + if mark { + p.Pixel[pinfo.Y][pinfo.X] = 0 + } + } + } + bb.copyOut() + + const cheat = false + for i := 0; i < nd*8; i++ { + pinfo := &pixByOff[doff+i] + pix := p.Pixel[pinfo.Y][pinfo.X] + if bb.B[i/8]&(1<= 128 { + // want white + pval = 0 + v = 255 + } + + bval := pval // bit value + if pix&coding.Invert != 0 { + bval ^= 1 + } + if pinfo.HardZero && bval != 0 { + bval ^= 1 + pval ^= 1 + v ^= 255 + } + + // Set pixel value as we want it. + pinfo.Block.reset(pinfo.Bit, bval) + + _, _ = x, y + + err := targ - v + if x+1 < len(row) { + addDither(pixByOff, row[x+1], err*7/16) + } + if false && y+1 < len(p.Pixel) { + if x > 0 { + addDither(pixByOff, p.Pixel[y+1][x-1], err*3/16) + } + addDither(pixByOff, p.Pixel[y+1][x], err*5/16) + if x+1 < len(row) { + addDither(pixByOff, p.Pixel[y+1][x+1], err*1/16) + } + } + } + } + + for _, bb := range bitblocks { + bb.copyOut() + } + } + + noops := 0 + // Copy numbers back out. + for i := 0; i < dbit/10; i++ { + // Pull out 10 bits. + v := 0 + for j := 0; j < 10; j++ { + bi := uint(bbit + 10*i + j) + v <<= 1 + v |= int((data[bi/8] >> (7 - bi&7)) & 1) + } + // Turn into 3 digits. + if v >= 1000 { + // Oops - too many 1 bits. + // We know the 512, 256, 128, 64, 32 bits are all set. + // Pick one at random to clear. This will break some + // checksum bits, but so be it. + println("oops", i, v) + pinfo := &pixByOff[bbit+10*i+3] // TODO random + pinfo.Contrast = 1e9 >> 8 + pinfo.HardZero = true + noops++ + } + num[i*3+0] = byte(v/100 + '0') + num[i*3+1] = byte(v/10%10 + '0') + num[i*3+2] = byte(v%10 + '0') + } + if noops > 0 { + goto Again + } + + var b1 coding.Bits + coding.String(url).Encode(&b1, p.Version) + coding.Num(num).Encode(&b1, p.Version) + b1.AddCheckBytes(p.Version, p.Level) + if !bytes.Equal(b.Bytes(), b1.Bytes()) { + fmt.Printf("mismatch\n%d %x\n%d %x\n", len(b.Bytes()), b.Bytes(), len(b1.Bytes()), b1.Bytes()) + panic("byte mismatch") + } + + cc, err := p.Encode(coding.String(url), coding.Num(num)) + if err != nil { + return err + } + + if !m.Dither { + for y, row := range expect { + for x, pix := range row { + if cc.Black(x, y) != pix { + println("mismatch", x, y, p.Pixel[y][x].String()) + } + } + } + } + + m.Code = &qr.Code{Bitmap: cc.Bitmap, Size: cc.Size, Stride: cc.Stride, Scale: m.Scale} + + if m.SaveControl { + m.Control = pngEncode(makeImage(req, "", "", 0, cc.Size, 4, m.Scale, func(x, y int) (rgba uint32) { + pix := p.Pixel[y][x] + if pix.Role() == coding.Data || pix.Role() == coding.Check { + pinfo := &pixByOff[pix.Offset()] + if pinfo.Block != nil { + if cc.Black(x, y) { + return 0x000000ff + } + return 0xffffffff + } + } + if cc.Black(x, y) { + return 0x3f3f3fff + } + return 0xbfbfbfff + })) + } + + return nil +} + +func addDither(pixByOff []Pixinfo, pix coding.Pixel, err int) { + if pix.Role() != coding.Data && pix.Role() != coding.Check { + return + } + pinfo := &pixByOff[pix.Offset()] + println("add", pinfo.X, pinfo.Y, pinfo.DTarg, err) + pinfo.DTarg += err +} + +func readTarget(name string) ([][]int, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + m, err := png.Decode(f) + if err != nil { + return nil, fmt.Errorf("decode %s: %v", name, err) + } + rect := m.Bounds() + target := make([][]int, rect.Dy()) + for i := range target { + target[i] = make([]int, rect.Dx()) + } + for y, row := range target { + for x := range row { + a := int(color.RGBAModel.Convert(m.At(x, y)).(color.RGBA).A) + t := int(color.GrayModel.Convert(m.At(x, y)).(color.Gray).Y) + if a == 0 { + t = -1 + } + row[x] = t + } + } + return target, nil +} + +type BitBlock struct { + DataBytes int + CheckBytes int + B []byte + M [][]byte + Tmp []byte + RS *gf256.RSEncoder + bdata []byte + cdata []byte +} + +func newBlock(nd, nc int, rs *gf256.RSEncoder, dat, cdata []byte) *BitBlock { + b := &BitBlock{ + DataBytes: nd, + CheckBytes: nc, + B: make([]byte, nd+nc), + Tmp: make([]byte, nc), + RS: rs, + bdata: dat, + cdata: cdata, + } + copy(b.B, dat) + rs.ECC(b.B[:nd], b.B[nd:]) + b.check() + if !bytes.Equal(b.Tmp, cdata) { + panic("cdata") + } + + b.M = make([][]byte, nd*8) + for i := range b.M { + row := make([]byte, nd+nc) + b.M[i] = row + for j := range row { + row[j] = 0 + } + row[i/8] = 1 << (7 - uint(i%8)) + rs.ECC(row[:nd], row[nd:]) + } + return b +} + +func (b *BitBlock) check() { + b.RS.ECC(b.B[:b.DataBytes], b.Tmp) + if !bytes.Equal(b.B[b.DataBytes:], b.Tmp) { + fmt.Printf("ecc mismatch\n%x\n%x\n", b.B[b.DataBytes:], b.Tmp) + panic("mismatch") + } +} + +func (b *BitBlock) reset(bi uint, bval byte) { + if (b.B[bi/8]>>(7-bi&7))&1 == bval { + // already has desired bit + return + } + // rows that have already been set + m := b.M[len(b.M):cap(b.M)] + for _, row := range m { + if row[bi/8]&(1<<(7-bi&7)) != 0 { + // Found it. + for j, v := range row { + b.B[j] ^= v + } + return + } + } + panic("reset of unset bit") +} + +func (b *BitBlock) canSet(bi uint, bval byte) bool { + found := false + m := b.M + for j, row := range m { + if row[bi/8]&(1<<(7-bi&7)) == 0 { + continue + } + if !found { + found = true + if j != 0 { + m[0], m[j] = m[j], m[0] + } + continue + } + for k := range row { + row[k] ^= m[0][k] + } + } + if !found { + return false + } + + targ := m[0] + + // Subtract from saved-away rows too. + for _, row := range m[len(m):cap(m)] { + if row[bi/8]&(1<<(7-bi&7)) == 0 { + continue + } + for k := range row { + row[k] ^= targ[k] + } + } + + // Found a row with bit #bi == 1 and cut that bit from all the others. + // Apply to data and remove from m. + if (b.B[bi/8]>>(7-bi&7))&1 != bval { + for j, v := range targ { + b.B[j] ^= v + } + } + b.check() + n := len(m) - 1 + m[0], m[n] = m[n], m[0] + b.M = m[:n] + + for _, row := range b.M { + if row[bi/8]&(1<<(7-bi&7)) != 0 { + panic("did not reduce") + } + } + + return true +} + +func (b *BitBlock) copyOut() { + b.check() + copy(b.bdata, b.B[:b.DataBytes]) + copy(b.cdata, b.B[b.DataBytes:]) +} + +func showtable(w http.ResponseWriter, b *BitBlock, gray func(int) bool) { + nd := b.DataBytes + nc := b.CheckBytes + + fmt.Fprintf(w, "\n") + line := func() { + fmt.Fprintf(w, "\n") + for i := 0; i < (nd+nc)*8; i++ { + fmt.Fprintf(w, "> uint(7-i&7) & 1 + if gray(i) { + fmt.Fprintf(w, " class='gray'") + } + fmt.Fprintf(w, ">") + if v == 1 { + fmt.Fprintf(w, "1") + } + } + line() + } + + m := b.M[len(b.M):cap(b.M)] + for i := len(m) - 1; i >= 0; i-- { + dorow(m[i]) + } + m = b.M + for _, row := range b.M { + dorow(row) + } + + fmt.Fprintf(w, "
\n", (nd+nc)*8) + } + line() + dorow := func(row []byte) { + fmt.Fprintf(w, "
\n") +} + +func BitsTable(w http.ResponseWriter, req *http.Request) { + nd := 2 + nc := 2 + fmt.Fprintf(w, ` + + `) + rs := gf256.NewRSEncoder(coding.Field, nc) + dat := make([]byte, nd+nc) + b := newBlock(nd, nc, rs, dat[:nd], dat[nd:]) + for i := 0; i < nd*8; i++ { + b.canSet(uint(i), 0) + } + showtable(w, b, func(i int) bool { return i < nd*8 }) + + b = newBlock(nd, nc, rs, dat[:nd], dat[nd:]) + for j := 0; j < (nd+nc)*8; j += 2 { + b.canSet(uint(j), 0) + } + showtable(w, b, func(i int) bool { return i%2 == 0 }) + +} diff --git a/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/resize/resize.go b/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/resize/resize.go new file mode 100644 index 0000000..02c8b00 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/rsc/qr/web/resize/resize.go @@ -0,0 +1,152 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package resize + +import ( + "image" + "image/color" +) + +// average convert the sums to averages and returns the result. +func average(sum []uint64, w, h int, n uint64) *image.RGBA { + ret := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + index := 4 * (y*w + x) + pix := ret.Pix[y*ret.Stride+x*4:] + pix[0] = uint8(sum[index+0] / n) + pix[1] = uint8(sum[index+1] / n) + pix[2] = uint8(sum[index+2] / n) + pix[3] = uint8(sum[index+3] / n) + } + } + return ret +} + +// ResizeRGBA returns a scaled copy of the RGBA image slice r of m. +// The returned image has width w and height h. +func ResizeRGBA(m *image.RGBA, r image.Rectangle, w, h int) *image.RGBA { + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + pix := m.Pix[(y-r.Min.Y)*m.Stride:] + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + p := pix[(x-r.Min.X)*4:] + r64 := uint64(p[0]) + g64 := uint64(p[1]) + b64 := uint64(p[2]) + a64 := uint64(p[3]) + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n) +} + +// ResizeNRGBA returns a scaled copy of the RGBA image slice r of m. +// The returned image has width w and height h. +func ResizeNRGBA(m *image.NRGBA, r image.Rectangle, w, h int) *image.RGBA { + ww, hh := uint64(w), uint64(h) + dx, dy := uint64(r.Dx()), uint64(r.Dy()) + // See comment in Resize. + n, sum := dx*dy, make([]uint64, 4*w*h) + for y := r.Min.Y; y < r.Max.Y; y++ { + pix := m.Pix[(y-r.Min.Y)*m.Stride:] + for x := r.Min.X; x < r.Max.X; x++ { + // Get the source pixel. + p := pix[(x-r.Min.X)*4:] + r64 := uint64(p[0]) + g64 := uint64(p[1]) + b64 := uint64(p[2]) + a64 := uint64(p[3]) + r64 = (r64 * a64) / 255 + g64 = (g64 * a64) / 255 + b64 = (b64 * a64) / 255 + // Spread the source pixel over 1 or more destination rows. + py := uint64(y) * hh + for remy := hh; remy > 0; { + qy := dy - (py % dy) + if qy > remy { + qy = remy + } + // Spread the source pixel over 1 or more destination columns. + px := uint64(x) * ww + index := 4 * ((py/dy)*ww + (px / dx)) + for remx := ww; remx > 0; { + qx := dx - (px % dx) + if qx > remx { + qx = remx + } + qxy := qx * qy + sum[index+0] += r64 * qxy + sum[index+1] += g64 * qxy + sum[index+2] += b64 * qxy + sum[index+3] += a64 * qxy + index += 4 + px += qx + remx -= qx + } + py += qy + remy -= qy + } + } + } + return average(sum, w, h, n) +} + +// Resample returns a resampled copy of the image slice r of m. +// The returned image has width w and height h. +func Resample(m image.Image, r image.Rectangle, w, h int) *image.RGBA { + if w < 0 || h < 0 { + return nil + } + if w == 0 || h == 0 || r.Dx() <= 0 || r.Dy() <= 0 { + return image.NewRGBA(image.Rect(0, 0, w, h)) + } + curw, curh := r.Dx(), r.Dy() + img := image.NewRGBA(image.Rect(0, 0, w, h)) + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + // Get a source pixel. + subx := x * curw / w + suby := y * curh / h + r32, g32, b32, a32 := m.At(subx, suby).RGBA() + r := uint8(r32 >> 8) + g := uint8(g32 >> 8) + b := uint8(b32 >> 8) + a := uint8(a32 >> 8) + img.SetRGBA(x, y, color.RGBA{r, g, b, a}) + } + } + return img +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go new file mode 100644 index 0000000..d93c1b9 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go @@ -0,0 +1,124 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "encoding/binary" + "errors" +) + +// ErrCorrupt reports that the input is invalid. +var ErrCorrupt = errors.New("snappy: corrupt input") + +// DecodedLen returns the length of the decoded block. +func DecodedLen(src []byte) (int, error) { + v, _, err := decodedLen(src) + return v, err +} + +// decodedLen returns the length of the decoded block and the number of bytes +// that the length header occupied. +func decodedLen(src []byte) (blockLen, headerLen int, err error) { + v, n := binary.Uvarint(src) + if n == 0 { + return 0, 0, ErrCorrupt + } + if uint64(int(v)) != v { + return 0, 0, errors.New("snappy: decoded block is too large") + } + return int(v), n, nil +} + +// Decode returns the decoded form of src. The returned slice may be a sub- +// slice of dst if dst was large enough to hold the entire decoded block. +// Otherwise, a newly allocated slice will be returned. +// It is valid to pass a nil dst. +func Decode(dst, src []byte) ([]byte, error) { + dLen, s, err := decodedLen(src) + if err != nil { + return nil, err + } + if len(dst) < dLen { + dst = make([]byte, dLen) + } + + var d, offset, length int + for s < len(src) { + switch src[s] & 0x03 { + case tagLiteral: + x := uint(src[s] >> 2) + switch { + case x < 60: + s += 1 + case x == 60: + s += 2 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-1]) + case x == 61: + s += 3 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-2]) | uint(src[s-1])<<8 + case x == 62: + s += 4 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-3]) | uint(src[s-2])<<8 | uint(src[s-1])<<16 + case x == 63: + s += 5 + if s > len(src) { + return nil, ErrCorrupt + } + x = uint(src[s-4]) | uint(src[s-3])<<8 | uint(src[s-2])<<16 | uint(src[s-1])<<24 + } + length = int(x + 1) + if length <= 0 { + return nil, errors.New("snappy: unsupported literal length") + } + if length > len(dst)-d || length > len(src)-s { + return nil, ErrCorrupt + } + copy(dst[d:], src[s:s+length]) + d += length + s += length + continue + + case tagCopy1: + s += 2 + if s > len(src) { + return nil, ErrCorrupt + } + length = 4 + int(src[s-2])>>2&0x7 + offset = int(src[s-2])&0xe0<<3 | int(src[s-1]) + + case tagCopy2: + s += 3 + if s > len(src) { + return nil, ErrCorrupt + } + length = 1 + int(src[s-3])>>2 + offset = int(src[s-2]) | int(src[s-1])<<8 + + case tagCopy4: + return nil, errors.New("snappy: unsupported COPY_4 tag") + } + + end := d + length + if offset > d || end > len(dst) { + return nil, ErrCorrupt + } + for ; d < end; d++ { + dst[d] = dst[d-offset] + } + } + if d != dLen { + return nil, ErrCorrupt + } + return dst[:d], nil +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go new file mode 100644 index 0000000..b2371db --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go @@ -0,0 +1,174 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "encoding/binary" +) + +// We limit how far copy back-references can go, the same as the C++ code. +const maxOffset = 1 << 15 + +// emitLiteral writes a literal chunk and returns the number of bytes written. +func emitLiteral(dst, lit []byte) int { + i, n := 0, uint(len(lit)-1) + switch { + case n < 60: + dst[0] = uint8(n)<<2 | tagLiteral + i = 1 + case n < 1<<8: + dst[0] = 60<<2 | tagLiteral + dst[1] = uint8(n) + i = 2 + case n < 1<<16: + dst[0] = 61<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + i = 3 + case n < 1<<24: + dst[0] = 62<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + dst[3] = uint8(n >> 16) + i = 4 + case int64(n) < 1<<32: + dst[0] = 63<<2 | tagLiteral + dst[1] = uint8(n) + dst[2] = uint8(n >> 8) + dst[3] = uint8(n >> 16) + dst[4] = uint8(n >> 24) + i = 5 + default: + panic("snappy: source buffer is too long") + } + if copy(dst[i:], lit) != len(lit) { + panic("snappy: destination buffer is too short") + } + return i + len(lit) +} + +// emitCopy writes a copy chunk and returns the number of bytes written. +func emitCopy(dst []byte, offset, length int) int { + i := 0 + for length > 0 { + x := length - 4 + if 0 <= x && x < 1<<3 && offset < 1<<11 { + dst[i+0] = uint8(offset>>8)&0x07<<5 | uint8(x)<<2 | tagCopy1 + dst[i+1] = uint8(offset) + i += 2 + break + } + + x = length + if x > 1<<6 { + x = 1 << 6 + } + dst[i+0] = uint8(x-1)<<2 | tagCopy2 + dst[i+1] = uint8(offset) + dst[i+2] = uint8(offset >> 8) + i += 3 + length -= x + } + return i +} + +// Encode returns the encoded form of src. The returned slice may be a sub- +// slice of dst if dst was large enough to hold the entire encoded block. +// Otherwise, a newly allocated slice will be returned. +// It is valid to pass a nil dst. +func Encode(dst, src []byte) ([]byte, error) { + if n := MaxEncodedLen(len(src)); len(dst) < n { + dst = make([]byte, n) + } + + // The block starts with the varint-encoded length of the decompressed bytes. + d := binary.PutUvarint(dst, uint64(len(src))) + + // Return early if src is short. + if len(src) <= 4 { + if len(src) != 0 { + d += emitLiteral(dst[d:], src) + } + return dst[:d], nil + } + + // Initialize the hash table. Its size ranges from 1<<8 to 1<<14 inclusive. + const maxTableSize = 1 << 14 + shift, tableSize := uint(32-8), 1<<8 + for tableSize < maxTableSize && tableSize < len(src) { + shift-- + tableSize *= 2 + } + var table [maxTableSize]int + + // Iterate over the source bytes. + var ( + s int // The iterator position. + t int // The last position with the same hash as s. + lit int // The start position of any pending literal bytes. + ) + for s+3 < len(src) { + // Update the hash table. + b0, b1, b2, b3 := src[s], src[s+1], src[s+2], src[s+3] + h := uint32(b0) | uint32(b1)<<8 | uint32(b2)<<16 | uint32(b3)<<24 + p := &table[(h*0x1e35a7bd)>>shift] + // We need to to store values in [-1, inf) in table. To save + // some initialization time, (re)use the table's zero value + // and shift the values against this zero: add 1 on writes, + // subtract 1 on reads. + t, *p = *p-1, s+1 + // If t is invalid or src[s:s+4] differs from src[t:t+4], accumulate a literal byte. + if t < 0 || s-t >= maxOffset || b0 != src[t] || b1 != src[t+1] || b2 != src[t+2] || b3 != src[t+3] { + s++ + continue + } + // Otherwise, we have a match. First, emit any pending literal bytes. + if lit != s { + d += emitLiteral(dst[d:], src[lit:s]) + } + // Extend the match to be as long as possible. + s0 := s + s, t = s+4, t+4 + for s < len(src) && src[s] == src[t] { + s++ + t++ + } + // Emit the copied bytes. + d += emitCopy(dst[d:], s-t, s-s0) + lit = s + } + + // Emit any final pending literal bytes and return. + if lit != len(src) { + d += emitLiteral(dst[d:], src[lit:]) + } + return dst[:d], nil +} + +// MaxEncodedLen returns the maximum length of a snappy block, given its +// uncompressed length. +func MaxEncodedLen(srcLen int) int { + // Compressed data can be defined as: + // compressed := item* literal* + // item := literal* copy + // + // The trailing literal sequence has a space blowup of at most 62/60 + // since a literal of length 60 needs one tag byte + one extra byte + // for length information. + // + // Item blowup is trickier to measure. Suppose the "copy" op copies + // 4 bytes of data. Because of a special check in the encoding code, + // we produce a 4-byte copy only if the offset is < 65536. Therefore + // the copy op takes 3 bytes to encode, and this type of item leads + // to at most the 62/60 blowup for representing literals. + // + // Suppose the "copy" op copies 5 bytes of data. If the offset is big + // enough, it will take 5 bytes to encode the copy op. Therefore the + // worst case here is a one-byte literal followed by a five-byte copy. + // That is, 6 bytes of input turn into 7 bytes of "compressed" data. + // + // This last factor dominates the blowup, so the final estimate is: + return 32 + srcLen + srcLen/6 +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go new file mode 100644 index 0000000..2f1b790 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go @@ -0,0 +1,38 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package snappy implements the snappy block-based compression format. +// It aims for very high speeds and reasonable compression. +// +// The C++ snappy implementation is at http://code.google.com/p/snappy/ +package snappy + +/* +Each encoded block begins with the varint-encoded length of the decoded data, +followed by a sequence of chunks. Chunks begin and end on byte boundaries. The +first byte of each chunk is broken into its 2 least and 6 most significant bits +called l and m: l ranges in [0, 4) and m ranges in [0, 64). l is the chunk tag. +Zero means a literal tag. All other values mean a copy tag. + +For literal tags: + - If m < 60, the next 1 + m bytes are literal bytes. + - Otherwise, let n be the little-endian unsigned integer denoted by the next + m - 59 bytes. The next 1 + n bytes after that are literal bytes. + +For copy tags, length bytes are copied from offset bytes ago, in the style of +Lempel-Ziv compression algorithms. In particular: + - For l == 1, the offset ranges in [0, 1<<11) and the length in [4, 12). + The length is 4 + the low 3 bits of m. The high 3 bits of m form bits 8-10 + of the offset. The next byte is bits 0-7 of the offset. + - For l == 2, the offset ranges in [0, 1<<16) and the length in [1, 65). + The length is 1 + m. The offset is the little-endian unsigned integer + denoted by the next 2 bytes. + - For l == 3, this tag is a legacy format that is no longer supported. +*/ +const ( + tagLiteral = 0x00 + tagCopy1 = 0x01 + tagCopy2 = 0x02 + tagCopy4 = 0x03 +) diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go new file mode 100644 index 0000000..7ba8392 --- /dev/null +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go @@ -0,0 +1,261 @@ +// Copyright 2011 The Snappy-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package snappy + +import ( + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "os" + "path/filepath" + "strings" + "testing" +) + +var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks") + +func roundtrip(b, ebuf, dbuf []byte) error { + e, err := Encode(ebuf, b) + if err != nil { + return fmt.Errorf("encoding error: %v", err) + } + d, err := Decode(dbuf, e) + if err != nil { + return fmt.Errorf("decoding error: %v", err) + } + if !bytes.Equal(b, d) { + return fmt.Errorf("roundtrip mismatch:\n\twant %v\n\tgot %v", b, d) + } + return nil +} + +func TestEmpty(t *testing.T) { + if err := roundtrip(nil, nil, nil); err != nil { + t.Fatal(err) + } +} + +func TestSmallCopy(t *testing.T) { + for _, ebuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} { + for _, dbuf := range [][]byte{nil, make([]byte, 20), make([]byte, 64)} { + for i := 0; i < 32; i++ { + s := "aaaa" + strings.Repeat("b", i) + "aaaabbbb" + if err := roundtrip([]byte(s), ebuf, dbuf); err != nil { + t.Errorf("len(ebuf)=%d, len(dbuf)=%d, i=%d: %v", len(ebuf), len(dbuf), i, err) + } + } + } + } +} + +func TestSmallRand(t *testing.T) { + rand.Seed(27354294) + for n := 1; n < 20000; n += 23 { + b := make([]byte, n) + for i, _ := range b { + b[i] = uint8(rand.Uint32()) + } + if err := roundtrip(b, nil, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestSmallRegular(t *testing.T) { + for n := 1; n < 20000; n += 23 { + b := make([]byte, n) + for i, _ := range b { + b[i] = uint8(i%10 + 'a') + } + if err := roundtrip(b, nil, nil); err != nil { + t.Fatal(err) + } + } +} + +func benchDecode(b *testing.B, src []byte) { + encoded, err := Encode(nil, src) + if err != nil { + b.Fatal(err) + } + // Bandwidth is in amount of uncompressed data. + b.SetBytes(int64(len(src))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Decode(src, encoded) + } +} + +func benchEncode(b *testing.B, src []byte) { + // Bandwidth is in amount of uncompressed data. + b.SetBytes(int64(len(src))) + dst := make([]byte, MaxEncodedLen(len(src))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + Encode(dst, src) + } +} + +func readFile(b *testing.B, filename string) []byte { + src, err := ioutil.ReadFile(filename) + if err != nil { + b.Fatalf("failed reading %s: %s", filename, err) + } + if len(src) == 0 { + b.Fatalf("%s has zero length", filename) + } + return src +} + +// expand returns a slice of length n containing repeated copies of src. +func expand(src []byte, n int) []byte { + dst := make([]byte, n) + for x := dst; len(x) > 0; { + i := copy(x, src) + x = x[i:] + } + return dst +} + +func benchWords(b *testing.B, n int, decode bool) { + // Note: the file is OS-language dependent so the resulting values are not + // directly comparable for non-US-English OS installations. + data := expand(readFile(b, "/usr/share/dict/words"), n) + if decode { + benchDecode(b, data) + } else { + benchEncode(b, data) + } +} + +func BenchmarkWordsDecode1e3(b *testing.B) { benchWords(b, 1e3, true) } +func BenchmarkWordsDecode1e4(b *testing.B) { benchWords(b, 1e4, true) } +func BenchmarkWordsDecode1e5(b *testing.B) { benchWords(b, 1e5, true) } +func BenchmarkWordsDecode1e6(b *testing.B) { benchWords(b, 1e6, true) } +func BenchmarkWordsEncode1e3(b *testing.B) { benchWords(b, 1e3, false) } +func BenchmarkWordsEncode1e4(b *testing.B) { benchWords(b, 1e4, false) } +func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) } +func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) } + +// testFiles' values are copied directly from +// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc. +// The label field is unused in snappy-go. +var testFiles = []struct { + label string + filename string +}{ + {"html", "html"}, + {"urls", "urls.10K"}, + {"jpg", "house.jpg"}, + {"pdf", "mapreduce-osdi-1.pdf"}, + {"html4", "html_x_4"}, + {"cp", "cp.html"}, + {"c", "fields.c"}, + {"lsp", "grammar.lsp"}, + {"xls", "kennedy.xls"}, + {"txt1", "alice29.txt"}, + {"txt2", "asyoulik.txt"}, + {"txt3", "lcet10.txt"}, + {"txt4", "plrabn12.txt"}, + {"bin", "ptt5"}, + {"sum", "sum"}, + {"man", "xargs.1"}, + {"pb", "geo.protodata"}, + {"gaviota", "kppkn.gtb"}, +} + +// The test data files are present at this canonical URL. +const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/" + +func downloadTestdata(basename string) (errRet error) { + filename := filepath.Join("testdata", basename) + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("failed to create %s: %s", filename, err) + } + defer f.Close() + defer func() { + if errRet != nil { + os.Remove(filename) + } + }() + resp, err := http.Get(baseURL + basename) + if err != nil { + return fmt.Errorf("failed to download %s: %s", baseURL+basename, err) + } + defer resp.Body.Close() + _, err = io.Copy(f, resp.Body) + if err != nil { + return fmt.Errorf("failed to write %s: %s", filename, err) + } + return nil +} + +func benchFile(b *testing.B, n int, decode bool) { + filename := filepath.Join("testdata", testFiles[n].filename) + if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 { + if !*download { + b.Fatal("test data not found; skipping benchmark without the -download flag") + } + // Download the official snappy C++ implementation reference test data + // files for benchmarking. + if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) { + b.Fatalf("failed to create testdata: %s", err) + } + for _, tf := range testFiles { + if err := downloadTestdata(tf.filename); err != nil { + b.Fatalf("failed to download testdata: %s", err) + } + } + } + data := readFile(b, filename) + if decode { + benchDecode(b, data) + } else { + benchEncode(b, data) + } +} + +// Naming convention is kept similar to what snappy's C++ implementation uses. +func Benchmark_UFlat0(b *testing.B) { benchFile(b, 0, true) } +func Benchmark_UFlat1(b *testing.B) { benchFile(b, 1, true) } +func Benchmark_UFlat2(b *testing.B) { benchFile(b, 2, true) } +func Benchmark_UFlat3(b *testing.B) { benchFile(b, 3, true) } +func Benchmark_UFlat4(b *testing.B) { benchFile(b, 4, true) } +func Benchmark_UFlat5(b *testing.B) { benchFile(b, 5, true) } +func Benchmark_UFlat6(b *testing.B) { benchFile(b, 6, true) } +func Benchmark_UFlat7(b *testing.B) { benchFile(b, 7, true) } +func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) } +func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) } +func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) } +func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) } +func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) } +func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) } +func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) } +func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) } +func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) } +func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) } +func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) } +func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) } +func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) } +func Benchmark_ZFlat3(b *testing.B) { benchFile(b, 3, false) } +func Benchmark_ZFlat4(b *testing.B) { benchFile(b, 4, false) } +func Benchmark_ZFlat5(b *testing.B) { benchFile(b, 5, false) } +func Benchmark_ZFlat6(b *testing.B) { benchFile(b, 6, false) } +func Benchmark_ZFlat7(b *testing.B) { benchFile(b, 7, false) } +func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) } +func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) } +func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) } +func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) } +func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) } +func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) } +func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) } +func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) } +func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) } +func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) } diff --git a/Godeps/_workspace/src/github.com/GeertJohan/yubigo/.gitignore b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/.gitignore new file mode 100644 index 0000000..e6d7d4e --- /dev/null +++ b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/.gitignore @@ -0,0 +1,5 @@ +/yubigo +/yubigo.sublime-project +/yubigo.sublime-workspace +/test +.apikey \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/GeertJohan/yubigo/LICENSE b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/LICENSE new file mode 100644 index 0000000..cb489ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012, Geert-Johan Riemer +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/GeertJohan/yubigo/readme.md b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/readme.md new file mode 100644 index 0000000..73b2f7e --- /dev/null +++ b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/readme.md @@ -0,0 +1,84 @@ + +## yubigo + +Yubigo is a Yubikey client API library that provides an easy way to integrate the Yubikey into any Go application. + +## Installation + +Installation is simple. Use go get: +`go get github.com/GeertJohan/yubigo` + +## Usage + +Make sure to import the library: `import "github.com/GeertJohan/yubigo"` + +For use with the default Yubico servers, make sure you have an API key. [Request a key][getapikey]. + +**Basic OTP checking usage:** +```go + +// create a new yubiAuth instance with id and key +yubiAuth, err := yubigo.NewYubiAuth("1234", "fdsaffqaf4vrc2q3cds=") +if err != nil { + // probably an invalid key was given + log.Fatalln(err) +} + +// verify an OTP string +result, ok, err := yubiAuth.Verify("ccccccbetgjevivbklihljgtbenbfrefccveiglnjfbc") +if err != nil { + log.Fatalln(err) +} + +if ok { + // succes!! The OTP is valid! + log.Printf("Used query was: %s\n", result.GetRequestQuery()) // this query string includes the url of the api-server that responded first. +} else { + // fail! The OTP is invalid or has been used before. + log.Println("The given OTP is invalid!!!") +} +``` + + +**Do not verify HTTPS certificate:** +```go +// Disable HTTPS cert verification. Use true to enable again. +yubiAuth.HttpsVerifyCertificate(false) +``` + + +**HTTP instead of HTTPS:** +```go +// Disable HTTPS. Use true to enable again. +yubiAuth.UseHttps(false) +``` + + +**Custom API server:** +```go +// Set a list of n servers, each server as host + path. +// Do not prepend with protocol +yubiAuth.SetApiServerList("api0.server.com/api/verify", "api1.server.com/api/verify", "otherserver.com/api/verify") +``` + +## Licence + +This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license]. + + +## Todo + - Test files + - More documentation + - Getters/Setters for some options on the YubiAuth object. + +## Protocol & Package documentation + +This project is implementing a pure-Go Yubico OTP Validation Client and is following the [Yubico Validation Protocol Version 2.0][validationProtocolV20]. + +You will find "go doc"-like [package documentation at go.pkgdoc.org][pkgdoc]. + + + [license]: https://github.com/GeertJohan/yubigo/blob/master/LICENSE + [getapikey]: https://upgrade.yubico.com/getapikey/ + [pkgdoc]: http://go.pkgdoc.org/github.com/GeertJohan/yubigo + [validationProtocolV20]: http://code.google.com/p/yubikey-val-server-php/wiki/ValidationProtocolV20 \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/GeertJohan/yubigo/yubigo.go b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/yubigo.go new file mode 100644 index 0000000..f317679 --- /dev/null +++ b/Godeps/_workspace/src/github.com/GeertJohan/yubigo/yubigo.go @@ -0,0 +1,565 @@ +package yubigo + +import ( + "bufio" + "crypto/hmac" + "crypto/sha1" + "crypto/tls" + "encoding/base64" + "errors" + "fmt" + "io" + "log" + "math/rand" + "net/http" + "regexp" + "sort" + "strings" + "sync" + "time" +) + +var ( + dvorakToQwerty = strings.NewReplacer( + "j", "c", "x", "b", "e", "d", ".", "e", "u", "f", "i", "g", "d", "h", "c", "i", + "h", "j", "t", "k", "n", "l", "b", "n", "p", "r", "y", "t", "g", "u", "k", "v", + "J", "C", "X", "B", "E", "D", ".", "E", "U", "F", "I", "G", "D", "H", "C", "I", + "H", "J", "T", "K", "N", "L", "B", "N", "P", "R", "Y", "T", "G", "U", "K", "V") + matchDvorak = regexp.MustCompile(`^[jxe.uidchtnbpygkJXE.UIDCHTNBPYGK]{32,48}$`) + matchQwerty = regexp.MustCompile(`^[cbdefghijklnrtuvCBDEFGHIJKLNRTUV]{32,48}$`) + signatureUrlFix = regexp.MustCompile(`\+`) +) + +// Package variable used to override the http client used for communication +// with Yubico. If nil the standard http.Client will be used - if overriding +// you need to ensure the transport options are set. +var HTTPClient *http.Client = nil + +// Parse and verify the given OTP string into prefix (identity) and ciphertext. +// Function returns a non-nil error when given OTP is not in valid format. +// NOTE: This function does NOT verify if the OTP is correct and unused/unique. +func ParseOTP(otp string) (prefix string, ciphertext string, err error) { + if len(otp) < 32 || len(otp) > 48 { + err = errors.New("OTP has wrong length.") + return + } + + // When otp matches dvorak-otp, then translate to qwerty. + if matchDvorak.MatchString(otp) { + otp = dvorakToQwerty.Replace(otp) + } + + // Verify that otp matches qwerty expectations + if !matchQwerty.MatchString(otp) { + err = errors.New("Given string is not a valid Yubikey OTP. It contains invalid characters and/or the length is wrong.") + return + } + + l := len(otp) + prefix = otp[0 : l-32] + ciphertext = otp[l-32 : l] + return +} + +type YubiAuth struct { + id string + key []byte + apiServerList []string + protocol string + verifyCertificate bool + workers []*verifyWorker + use sync.Mutex + debug bool +} + +type verifyWorker struct { + ya *YubiAuth // YubiAuth this worker belongs to + id int // Worker id + client *http.Client // http client standing by ready for work + apiServer string // API server URL + work chan *workRequest // Channel on which the worker receives work + stop chan bool // Channel for stop signal +} + +type workRequest struct { + paramString *string + resultChan chan *workResult +} + +type workResult struct { + response *http.Response + requestQuery string + err error // indicates a failing server/network. This doesn't mean the OTP is invalid. +} + +func (vw *verifyWorker) process() { + if vw.ya.debug { + log.Printf("worker[%d]: Started.\n", vw.id) + } + for { + select { + case w := <-vw.work: + + // Create url + url := vw.ya.protocol + vw.apiServer + *w.paramString + + if vw.ya.debug { + log.Printf("worker[%d]: Have work. Requesting: %s\n", vw.id, url) + } + + // Create request + request, err := http.NewRequest("GET", url, nil) + if err != nil { + w.resultChan <- &workResult{ + response: nil, + requestQuery: url, + err: fmt.Errorf("Could not create http request. Error: %s\n", err), + } + continue + } + request.Header.Add("User-Agent", "github.com/GeertJohan/yubigo") + + // Call server + response, err := vw.client.Do(request) + + // If we received an error from the client, return that (wrapped) on the channel. + if err != nil { + w.resultChan <- &workResult{ + response: nil, + requestQuery: url, + err: fmt.Errorf("Http client error: %s\n", err), + } + if vw.ya.debug { + log.Printf("worker[%d]: Http client error: %s", vw.id, err) + } + continue + } + + // It seems everything is ok! return the response (wrapped) on the channel. + if vw.ya.debug { + log.Printf("worker[%d] Received result from api server. Sending on channel.", vw.id) + } + w.resultChan <- &workResult{ + response: response, + requestQuery: url, + err: nil, + } + continue + case <-vw.stop: + if vw.ya.debug { + log.Printf("worker[%d]: received stop signal.\n", vw.id) + } + return + } + } +} + +// Create a yubiAuth instance with given API-id and API-key. +// Returns an error when the key could not be base64 decoded. +// To use yubigo with the Yubico Web Service (default api servers), create an API id+key here: https://upgrade.yubico.com/getapikey/ +// Debugging is disabled. For debugging: use NewYubiAuthDebug(..) +func NewYubiAuth(id string, key string) (auth *YubiAuth, err error) { + return NewYubiAuthDebug(id, key, false) +} + +// Create a yubiAuth instance for given API-id and API-key. +// Has third parameter `debug`. When debug is true this YubiAuth instance will spam the console with logging messages. +// Returns an error when the key could not be base64 decoded. +// To use yubigo with the Yubico Web Service (default api servers), create an API id+key here: https://upgrade.yubico.com/getapikey/ +func NewYubiAuthDebug(id string, key string, debug bool) (auth *YubiAuth, err error) { + keyBytes, err := base64.StdEncoding.DecodeString(key) + if err != nil { + err = fmt.Errorf("Given key seems to be invalid. Could not base64_decode. Error: %s\n", err) + return + } + + if debug { + log.Printf("NewYubiAuthDebug: Given key is base64 decodable. Creating new YubiAuth instance with api id '%s'.\n", id) + } + + auth = &YubiAuth{ + id: id, + key: keyBytes, + + apiServerList: []string{"api.yubico.com/wsapi/2.0/verify", + "api2.yubico.com/wsapi/2.0/verify", + "api3.yubico.com/wsapi/2.0/verify", + "api4.yubico.com/wsapi/2.0/verify", + "api5.yubico.com/wsapi/2.0/verify"}, + + protocol: "https://", + verifyCertificate: true, + + debug: debug, + } + + if debug { + log.Printf("NewYubiAuthDebug: Using yubico web servers: %#v\n", auth.apiServerList) + log.Println("NewYubiAuthDebug: Going to build workers.") + } + + // Build workers + auth.buildWorkers() + + // All done :) + return +} + +// Stops existing workers and creates new ones. +func (ya *YubiAuth) buildWorkers() { + // Unexported (internal) method, so no locking. + + // create tls config + tlsConfig := &tls.Config{} + if !ya.verifyCertificate { + tlsConfig.InsecureSkipVerify = true + } + + // stop all existing workers + for _, worker := range ya.workers { + worker.stop <- true + } + + // create new (empty) slice with exact capacity + ya.workers = make([]*verifyWorker, 0, len(ya.apiServerList)) + + // start new workers. One for each apiServerString + for id, apiServer := range ya.apiServerList { + // create worker instance with new http.Client instance + worker := &verifyWorker{ + ya: ya, + id: id, + apiServer: apiServer + "?", + work: make(chan *workRequest), + stop: make(chan bool), + } + + if HTTPClient == nil { + worker.client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConfig, + }, + } + } else { + worker.client = HTTPClient + } + + ya.workers = append(ya.workers, worker) + + // start worker process in new goroutine + go worker.process() + } +} + +// Use this method to specify a list of servers for verification. +// Each server string should contain host + path. +// Example: "api.yubico.com/wsapi/2.0/verify". +func (ya *YubiAuth) SetApiServerList(urls ...string) { + // Lock + ya.use.Lock() + defer ya.use.Unlock() + + // save setting + ya.apiServerList = urls + + // rebuild workers (api server url's have changed) + ya.buildWorkers() +} + +// Retrieve the the ist of servers that are being used for verification. +func (ya *YubiAuth) GetApiServerList() []string { + return ya.apiServerList +} + +// Enable or disable the use of https +func (ya *YubiAuth) UseHttps(useHttps bool) { + // Lock + ya.use.Lock() + defer ya.use.Unlock() + + // change setting + if useHttps { + ya.protocol = "https://" + } else { + ya.protocol = "http://" + } + + // no need to rebuild workers, they re-read ya.protocol on each request. +} + +// Enable or disable https certificate verification +// Disable this at your own risk. +func (ya *YubiAuth) HttpsVerifyCertificate(verifyCertificate bool) { + // Lock + ya.use.Lock() + defer ya.use.Unlock() + + // save setting + ya.verifyCertificate = verifyCertificate + + // rebuild workers (client has to be changed) + ya.buildWorkers() +} + +// The verify method calls the API with given OTP and returns if the OTP is valid or not. +// This method will return an error if something unexpected happens +// If no error was returned, the returned 'ok bool' indicates if the OTP is valid +// if the 'ok bool' is true, additional informtion can be found in the returned YubiResponse object +func (ya *YubiAuth) Verify(otp string) (yr *YubiResponse, ok bool, err error) { + // Lock + ya.use.Lock() + defer ya.use.Unlock() + + // check the OTP + _, _, err = ParseOTP(otp) + if err != nil { + return nil, false, err + } + + // create slice to store parameters for this verification request + paramSlice := make([]string, 0) + paramSlice = append(paramSlice, "id="+ya.id) + paramSlice = append(paramSlice, "otp="+otp) + + // Create 40 characters nonce + rand.Seed(time.Now().UnixNano()) + k := make([]rune, 40) + for i := 0; i < 40; i++ { + c := rand.Intn(35) + if c < 10 { + c += 48 // numbers (0-9) (0+48 == 48 == '0', 9+48 == 57 == '9') + } else { + c += 87 // lower case alphabets (a-z) (10+87 == 97 == 'a', 35+87 == 122 = 'z') + } + k[i] = rune(c) + } + nonce := string(k) + paramSlice = append(paramSlice, "nonce="+nonce) + + // These settings are hardcoded in the library for now. + //++ TODO(GeertJohan): add these values to the yubiAuth object and create getters/setters + // paramSlice = append(paramSlice, "timestamp=1") + paramSlice = append(paramSlice, "sl=secure") + + //++ TODO(GeertJohan): Add timeout support? + //++ //paramSlice = append(paramSlice, "timeout=") + + // sort the slice + sort.Strings(paramSlice) + + // create parameter string + paramString := strings.Join(paramSlice, "&") + + // generate signature + if len(ya.key) > 0 { + hmacenc := hmac.New(sha1.New, ya.key) + _, err := hmacenc.Write([]byte(paramString)) + if err != nil { + return nil, false, fmt.Errorf("Could not calculate signature. Error: %s\n", err) + } + signature := base64.StdEncoding.EncodeToString(hmacenc.Sum([]byte{})) + signature = signatureUrlFix.ReplaceAllString(signature, `%2B`) + paramString = paramString + "&h=" + signature + } + + // create result channel, buffersize equals the amount of workers. + resultChan := make(chan *workResult, len(ya.workers)) + + // create workRequest instance + wr := &workRequest{ + paramString: ¶mString, + resultChan: resultChan, + } + + // send workRequest to each worker + for _, worker := range ya.workers { + worker.work <- wr + } + + // count the errors so we can handle when all servers fail (network fail for instance) + errCount := 0 + + // local result var, will contain the first result we have + var result *workResult + + // keep looping until we have a good result + for { + // listen for result from a worker + result = <-resultChan + + // check for error + if result.err != nil { + // increment error counter + errCount++ + + if ya.debug { + // debug logging + log.Printf("A server (%s) gave error back: %s\n", result.requestQuery, result.err) + } + + if errCount == len(ya.apiServerList) { + // All workers are done, there's nothing left to try. we return an error. + return nil, false, errors.New("None of the servers responded properly.") + } + + // we have an error, but not all workers responded yet, so lets wait for the next result. + continue + } + + // create a yubiResult from the workers response. + yr, err = newYubiResponse(result) + if err != nil { + return nil, false, err + } + + // Check for "REPLAYED_REQUEST" result. + if status, _ := yr.resultParameters["status"]; status == "REPLAYED_REQUEST" { + // The result status is "REPLAYED_REQUEST". + // This means that the server for this request got sync with an other server before our request. + // Lets wait for the result from the other server. + // See: http://forum.yubico.com/viewtopic.php?f=3&t=701 + + // increment error counter + errCount++ + + if ya.debug { + // debug logging + log.Println("Got replayed request: ", result.response.Body) + } + + if errCount == len(ya.apiServerList) { + // All workers are done, there' is nothing left to try. We return an error. + return nil, false, errors.New("None of the servers responded properly.") + } + + // We have a replayed request, but not all workers responded yet, so lets wait for the next result. + continue + } + + // No error or REPLAYED_REQUEST. Seems like we have a proper result. + break + } + + // check status + status, ok := yr.resultParameters["status"] + if !ok || status != "OK" { + switch status { + case "BAD_OTP": + return yr, false, nil + case "REPLAYED_OTP": + return yr, false, errors.New("The OTP is valid, but has been used before. If you receive this error, you might be the victim of a man-in-the-middle attack.") + case "BAD_SIGNATURE": + return yr, false, errors.New("Signature verification at the api server failed. The used id/key combination could be invalid or is not activated (yet).") + case "NO_SUCH_CLIENT": + return yr, false, errors.New("The api server does not accept the given id. It might be invalid or is not activated (yet).") + case "OPERATION_NOT_ALLOWED": + return yr, false, errors.New("The api server does not allow the given api id to verify OTPs.") + case "BACKEND_ERROR": + return yr, false, errors.New("The api server seems to be broken. Please contact the api servers system administration (yubico servers? contact yubico).") + case "NOT_ENOUGH_ANSWERS": + return yr, false, errors.New("The api server could not get requested number of syncs during before timeout") + case "REPLAYED_REQUEST": + panic("Unexpected. This status should've been catched in the worker response loop.") + return yr, false, errors.New("The api server has seen this unique request before. If you receive this error, you might be the victim of a man-in-the-middle attack.") + default: + return yr, false, fmt.Errorf("Unknown status parameter (%s) sent by api server.", status) + } + } + + // check otp + otpCheck, ok := yr.resultParameters["otp"] + if !ok || otp != otpCheck { + return nil, false, errors.New("Could not validate otp value from server response.") + } + + // check nonce + nonceCheck, ok := yr.resultParameters["nonce"] + if !ok || nonce != nonceCheck { + return nil, false, errors.New("Could not validate nonce value from server response.") + } + + // check attached signature with remake of that signature, if key is actually in use. + if len(ya.key) > 0 { + receivedSignature, ok := yr.resultParameters["h"] + if !ok || len(receivedSignature) == 0 { + return nil, false, errors.New("No signature hash was attached by the api server, we do expect one though. This might be a hacking attempt.") + } + + // create a slice with the same size-1 as the parameters map (we're leaving the hash itself out of it's replica calculation) + receivedValuesSlice := make([]string, 0, len(yr.resultParameters)-1) + for key, value := range yr.resultParameters { + if key != "h" { + receivedValuesSlice = append(receivedValuesSlice, key+"="+value) + } + } + sort.Strings(receivedValuesSlice) + receivedValuesString := strings.Join(receivedValuesSlice, "&") + hmacenc := hmac.New(sha1.New, ya.key) + _, err := hmacenc.Write([]byte(receivedValuesString)) + if err != nil { + return nil, false, fmt.Errorf("Could not calculate signature replica. Error: %s\n", err) + } + recievedSignatureReplica := base64.StdEncoding.EncodeToString(hmacenc.Sum([]byte{})) + + if receivedSignature != recievedSignatureReplica { + return nil, false, errors.New("The received signature hash is not valid. This might be a hacking attempt.") + } + } + + // we're done! + yr.validOTP = true + return yr, true, nil + +} + +// Contains details about yubikey OTP verification. +type YubiResponse struct { + requestQuery string + resultParameters map[string]string + validOTP bool +} + +func newYubiResponse(result *workResult) (*YubiResponse, error) { + bodyReader := bufio.NewReader(result.response.Body) + yr := &YubiResponse{} + yr.resultParameters = make(map[string]string) + yr.requestQuery = result.requestQuery + for { + // read through the response lines + line, err := bodyReader.ReadString('\n') + + // handle error, which at one point should be an expected io.EOF (end of file) + if err != nil { + if err == io.EOF { + break // successfully done with reading lines, lets break this for loop + } + return nil, fmt.Errorf("Could not read result body from the server. Error: %s\n", err) + } + + // parse result lines, split on first '=', trim \n and \r + keyvalue := strings.SplitN(line, "=", 2) + if len(keyvalue) == 2 { + yr.resultParameters[keyvalue[0]] = strings.Trim(keyvalue[1], "\n\r") + } + } + return yr, nil +} + +// Returns wether the verification was successful +func (yr *YubiResponse) IsValidOTP() bool { + return yr.validOTP +} + +// Get the requestQuery that was used during verification. +func (yr *YubiResponse) GetRequestQuery() string { + return yr.requestQuery +} + +// Retrieve a parameter from the api's response +func (yr *YubiResponse) GetResultParameter(key string) (value string) { + value, ok := yr.resultParameters[key] + if !ok { + value = "" + } + return value +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore new file mode 100644 index 0000000..66be63a --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.gitignore @@ -0,0 +1 @@ +logrus diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml new file mode 100644 index 0000000..c3af3ce --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.2 + - 1.3 + - tip +install: + - go get github.com/stretchr/testify + - go get github.com/stvp/go-udp-testing + - go get github.com/tobi/airbrake-go + - go get github.com/getsentry/raven-go diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE b/Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE new file mode 100644 index 0000000..f090cb4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md new file mode 100644 index 0000000..abccd84 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/README.md @@ -0,0 +1,355 @@ +# Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc] + +Logrus is a structured logger for Go (golang), completely API compatible with +the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not +yet stable (pre 1.0), the core API is unlikely change much but please version +control your Logrus to make sure you aren't fetching latest `master` on every +build.** + +Nicely color-coded in development (when a TTY is attached, otherwise just +plain text): + +![Colored](http://i.imgur.com/PY7qMwd.png) + +With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash +or Splunk: + +```json +{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the +ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} + +{"level":"warning","msg":"The group's number increased tremendously!", +"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"A giant walrus appears!", +"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", +"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} + +{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, +"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} +``` + +With the default `log.Formatter = new(logrus.TextFormatter)` when a TTY is not +attached, the output is compatible with the +[logfmt](http://godoc.org/github.com/kr/logfmt) format: + +```text +time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 +time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 +time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 +time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 +time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 +``` + +#### Example + +The simplest way to use Logrus is simply the package-level exported logger: + +```go +package main + +import ( + log "github.com/Sirupsen/logrus" +) + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + }).Info("A walrus appears") +} +``` + +Note that it's completely api-compatible with the stdlib logger, so you can +replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"` +and you'll now have the flexibility of Logrus. You can customize it all you +want: + +```go +package main + +import ( + "os" + log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" +) + +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) + + // Use the Airbrake hook to report errors that have Error severity or above to + // an exception tracker. You can create custom hooks, see the Hooks section. + log.AddHook(&logrus_airbrake.AirbrakeHook{}) + + // Output to stderr instead of stdout, could also be a file. + log.SetOutput(os.Stderr) + + // Only log the warning severity or above. + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") +} +``` + +For more advanced usage such as logging to multiple locations from the same +application, you can also create an instance of the `logrus` Logger: + +```go +package main + +import ( + "github.com/Sirupsen/logrus" +) + +// Create a new instance of the logger. You can have any number of instances. +var log = logrus.New() + +func main() { + // The API for setting attributes is a little different than the package level + // exported logger. See Godoc. + log.Out = os.Stderr + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") +} +``` + +#### Fields + +Logrus encourages careful, structured logging though logging fields instead of +long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +to send event %s to topic %s with key %d")`, you should log the much more +discoverable: + +```go +log.WithFields(log.Fields{ + "event": event, + "topic": topic, + "key": key, +}).Fatal("Failed to send event") +``` + +We've found this API forces you to think about logging in a way that produces +much more useful logging messages. We've been in countless situations where just +a single added field to a log statement that was already there would've saved us +hours. The `WithFields` call is optional. + +In general, with Logrus using any of the `printf`-family functions should be +seen as a hint you should add a field, however, you can still use the +`printf`-family functions with Logrus. + +#### Hooks + +You can add hooks for logging levels. For example to send errors to an exception +tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to +multiple places simultaneously, e.g. syslog. + +```go +// Not the real implementation of the Airbrake hook. Just a simple sample. +import ( + log "github.com/Sirupsen/logrus" +) + +func init() { + log.AddHook(new(AirbrakeHook)) +} + +type AirbrakeHook struct{} + +// `Fire()` takes the entry that the hook is fired for. `entry.Data[]` contains +// the fields for the entry. See the Fields section of the README. +func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { + err := airbrake.Notify(entry.Data["error"].(error)) + if err != nil { + log.WithFields(log.Fields{ + "source": "airbrake", + "endpoint": airbrake.Endpoint, + }).Info("Failed to send error to Airbrake") + } + + return nil +} + +// `Levels()` returns a slice of `Levels` the hook is fired for. +func (hook *AirbrakeHook) Levels() []log.Level { + return []log.Level{ + log.ErrorLevel, + log.FatalLevel, + log.PanicLevel, + } +} +``` + +Logrus comes with built-in hooks. Add those, or your custom hook, in `init`: + +```go +import ( + log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" + "github.com/Sirupsen/logrus/hooks/syslog" + "log/syslog" +) + +func init() { + log.AddHook(new(logrus_airbrake.AirbrakeHook)) + + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + if err != nil { + log.Error("Unable to connect to local syslog daemon") + } else { + log.AddHook(hook) + } +} +``` + +* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) + Send errors to an exception tracking service compatible with the Airbrake API. + Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. + +* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) + Send errors to the Papertrail hosted logging service via UDP. + +* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) + Send errors to remote syslog server. + Uses standard library `log/syslog` behind the scenes. + +* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) + Send errors to a channel in hipchat. + +* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly) + Send logs to Loggly (https://www.loggly.com/) + +* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus) + Hook for Slack chat. + +#### Level logging + +Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. + +```go +log.Debug("Useful debugging information.") +log.Info("Something noteworthy happened!") +log.Warn("You should probably take a look at this.") +log.Error("Something failed but I'm not quitting.") +// Calls os.Exit(1) after logging +log.Fatal("Bye.") +// Calls panic() after logging +log.Panic("I'm bailing.") +``` + +You can set the logging level on a `Logger`, then it will only log entries with +that severity or anything above it: + +```go +// Will log anything that is info or above (warn, error, fatal, panic). Default. +log.SetLevel(log.InfoLevel) +``` + +It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +environment if your application has that. + +#### Entries + +Besides the fields added with `WithField` or `WithFields` some fields are +automatically added to all logging events: + +1. `time`. The timestamp when the entry was created. +2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after + the `AddFields` call. E.g. `Failed to send event.` +3. `level`. The logging level. E.g. `info`. + +#### Environments + +Logrus has no notion of environment. + +If you wish for hooks and formatters to only be used in specific environments, +you should handle that yourself. For example, if your application has a global +variable `Environment`, which is a string representation of the environment you +could do: + +```go +import ( + log "github.com/Sirupsen/logrus" +) + +init() { + // do something here to set environment depending on an environment variable + // or command-line flag + if Environment == "production" { + log.SetFormatter(logrus.JSONFormatter) + } else { + // The TextFormatter is default, you don't actually have to do this. + log.SetFormatter(logrus.TextFormatter) + } +} +``` + +This configuration is how `logrus` was intended to be used, but JSON in +production is mostly only useful if you do log aggregation with tools like +Splunk or Logstash. + +#### Formatters + +The built-in logging formatters are: + +* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise + without colors. + * *Note:* to force colored output when there is no TTY, set the `ForceColors` + field to `true`. To force no colored output even if there is a TTY set the + `DisableColors` field to `true` +* `logrus.JSONFormatter`. Logs fields as JSON. + +Third party logging formatters: + +* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. + +You can define your formatter by implementing the `Formatter` interface, +requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a +`Fields` type (`map[string]interface{}`) with all your fields as well as the +default ones (see Entries section above): + +```go +type MyJSONFormatter struct { +} + +log.SetFormatter(new(MyJSONFormatter)) + +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + // Note this doesn't include Time, Level and Message which are available on + // the Entry. Consult `godoc` on information about those fields or read the + // source of the official loggers. + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} +``` + +#### Rotation + +Log rotation is not provided with Logrus. Log rotation should be done by an +external program (like `logrotated(8)`) that can compress and delete old log +entries. It should not be a feature of the application-level logger. + + +[godoc]: https://godoc.org/github.com/Sirupsen/logrus diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go new file mode 100644 index 0000000..17fe6f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry.go @@ -0,0 +1,252 @@ +package logrus + +import ( + "bytes" + "fmt" + "io" + "os" + "time" +) + +// An entry is the final or intermediate Logrus logging entry. It contains all +// the fields passed with WithField{,s}. It's finally logged when Debug, Info, +// Warn, Error, Fatal or Panic is called on it. These objects can be reused and +// passed around as much as you wish to avoid field duplication. +type Entry struct { + Logger *Logger + + // Contains all the fields set by the user. + Data Fields + + // Time at which the log entry was created + Time time.Time + + // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic + Level Level + + // Message passed to Debug, Info, Warn, Error, Fatal or Panic + Message string +} + +func NewEntry(logger *Logger) *Entry { + return &Entry{ + Logger: logger, + // Default is three fields, give a little extra room + Data: make(Fields, 5), + } +} + +// Returns a reader for the entry, which is a proxy to the formatter. +func (entry *Entry) Reader() (*bytes.Buffer, error) { + serialized, err := entry.Logger.Formatter.Format(entry) + return bytes.NewBuffer(serialized), err +} + +// Returns the string representation from the reader and ultimately the +// formatter. +func (entry *Entry) String() (string, error) { + reader, err := entry.Reader() + if err != nil { + return "", err + } + + return reader.String(), err +} + +// Add a single field to the Entry. +func (entry *Entry) WithField(key string, value interface{}) *Entry { + return entry.WithFields(Fields{key: value}) +} + +// Add a map of fields to the Entry. +func (entry *Entry) WithFields(fields Fields) *Entry { + data := Fields{} + for k, v := range entry.Data { + data[k] = v + } + for k, v := range fields { + data[k] = v + } + return &Entry{Logger: entry.Logger, Data: data} +} + +func (entry *Entry) log(level Level, msg string) { + entry.Time = time.Now() + entry.Level = level + entry.Message = msg + + if err := entry.Logger.Hooks.Fire(level, entry); err != nil { + entry.Logger.mu.Lock() + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + entry.Logger.mu.Unlock() + } + + reader, err := entry.Reader() + if err != nil { + entry.Logger.mu.Lock() + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + entry.Logger.mu.Unlock() + } + + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + + _, err = io.Copy(entry.Logger.Out, reader) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(entry) + } +} + +func (entry *Entry) Debug(args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.log(DebugLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Print(args ...interface{}) { + entry.Info(args...) +} + +func (entry *Entry) Info(args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.log(InfoLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warn(args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.log(WarnLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warning(args ...interface{}) { + entry.Warn(args...) +} + +func (entry *Entry) Error(args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.log(ErrorLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Fatal(args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.log(FatalLevel, fmt.Sprint(args...)) + } + os.Exit(1) +} + +func (entry *Entry) Panic(args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.log(PanicLevel, fmt.Sprint(args...)) + } + panic(fmt.Sprint(args...)) +} + +// Entry Printf family functions + +func (entry *Entry) Debugf(format string, args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.Debug(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Infof(format string, args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.Info(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Printf(format string, args ...interface{}) { + entry.Infof(format, args...) +} + +func (entry *Entry) Warnf(format string, args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.Warn(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Warningf(format string, args ...interface{}) { + entry.Warnf(format, args...) +} + +func (entry *Entry) Errorf(format string, args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.Error(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Fatalf(format string, args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.Fatal(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Panicf(format string, args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.Panic(fmt.Sprintf(format, args...)) + } +} + +// Entry Println family functions + +func (entry *Entry) Debugln(args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.Debug(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Infoln(args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.Info(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Println(args ...interface{}) { + entry.Infoln(args...) +} + +func (entry *Entry) Warnln(args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.Warn(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Warningln(args ...interface{}) { + entry.Warnln(args...) +} + +func (entry *Entry) Errorln(args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.Error(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Fatalln(args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.Fatal(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Panicln(args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.Panic(entry.sprintlnn(args...)) + } +} + +// Sprintlnn => Sprint no newline. This is to get the behavior of how +// fmt.Sprintln where spaces are always added between operands, regardless of +// their type. Instead of vendoring the Sprintln implementation to spare a +// string allocation, we do the simplest thing. +func (entry *Entry) sprintlnn(args ...interface{}) string { + msg := fmt.Sprintln(args...) + return msg[:len(msg)-1] +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go new file mode 100644 index 0000000..98717df --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/entry_test.go @@ -0,0 +1,53 @@ +package logrus + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEntryPanicln(t *testing.T) { + errBoom := fmt.Errorf("boom time") + + defer func() { + p := recover() + assert.NotNil(t, p) + + switch pVal := p.(type) { + case *Entry: + assert.Equal(t, "kaboom", pVal.Message) + assert.Equal(t, errBoom, pVal.Data["err"]) + default: + t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) + } + }() + + logger := New() + logger.Out = &bytes.Buffer{} + entry := NewEntry(logger) + entry.WithField("err", errBoom).Panicln("kaboom") +} + +func TestEntryPanicf(t *testing.T) { + errBoom := fmt.Errorf("boom again") + + defer func() { + p := recover() + assert.NotNil(t, p) + + switch pVal := p.(type) { + case *Entry: + assert.Equal(t, "kaboom true", pVal.Message) + assert.Equal(t, errBoom, pVal.Data["err"]) + default: + t.Fatalf("want type *Entry, got %T: %#v", pVal, pVal) + } + }() + + logger := New() + logger.Out = &bytes.Buffer{} + entry := NewEntry(logger) + entry.WithField("err", errBoom).Panicf("kaboom %v", true) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go new file mode 100644 index 0000000..a62ba45 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/basic/basic.go @@ -0,0 +1,40 @@ +package main + +import ( + "github.com/Sirupsen/logrus" +) + +var log = logrus.New() + +func init() { + log.Formatter = new(logrus.JSONFormatter) + log.Formatter = new(logrus.TextFormatter) // default +} + +func main() { + defer func() { + err := recover() + if err != nil { + log.WithFields(logrus.Fields{ + "omg": true, + "err": err, + "number": 100, + }).Fatal("The ice breaks!") + } + }() + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(logrus.Fields{ + "animal": "orca", + "size": 9009, + }).Panic("It's over 9000!") +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go new file mode 100644 index 0000000..42e7a4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/examples/hook/hook.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" + "github.com/tobi/airbrake-go" +) + +var log = logrus.New() + +func init() { + log.Formatter = new(logrus.TextFormatter) // default + log.Hooks.Add(new(logrus_airbrake.AirbrakeHook)) +} + +func main() { + airbrake.Endpoint = "https://exceptions.whatever.com/notifier_api/v2/notices.xml" + airbrake.ApiKey = "whatever" + airbrake.Environment = "production" + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go new file mode 100644 index 0000000..d087124 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/exported.go @@ -0,0 +1,182 @@ +package logrus + +import ( + "io" +) + +var ( + // std is the name of the standard logger in stdlib `log` + std = New() +) + +// SetOutput sets the standard logger output. +func SetOutput(out io.Writer) { + std.mu.Lock() + defer std.mu.Unlock() + std.Out = out +} + +// SetFormatter sets the standard logger formatter. +func SetFormatter(formatter Formatter) { + std.mu.Lock() + defer std.mu.Unlock() + std.Formatter = formatter +} + +// SetLevel sets the standard logger level. +func SetLevel(level Level) { + std.mu.Lock() + defer std.mu.Unlock() + std.Level = level +} + +// GetLevel returns the standard logger level. +func GetLevel() Level { + return std.Level +} + +// AddHook adds a hook to the standard logger hooks. +func AddHook(hook Hook) { + std.mu.Lock() + defer std.mu.Unlock() + std.Hooks.Add(hook) +} + +// WithField creates an entry from the standard logger and adds a field to +// it. If you want multiple fields, use `WithFields`. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithField(key string, value interface{}) *Entry { + return std.WithField(key, value) +} + +// WithFields creates an entry from the standard logger and adds multiple +// fields to it. This is simply a helper for `WithField`, invoking it +// once for each field. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithFields(fields Fields) *Entry { + return std.WithFields(fields) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + std.Debug(args...) +} + +// Print logs a message at level Info on the standard logger. +func Print(args ...interface{}) { + std.Print(args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + std.Info(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + std.Warn(args...) +} + +// Warning logs a message at level Warn on the standard logger. +func Warning(args ...interface{}) { + std.Warning(args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + std.Error(args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...interface{}) { + std.Panic(args...) +} + +// Fatal logs a message at level Fatal on the standard logger. +func Fatal(args ...interface{}) { + std.Fatal(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + std.Debugf(format, args...) +} + +// Printf logs a message at level Info on the standard logger. +func Printf(format string, args ...interface{}) { + std.Printf(format, args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + std.Infof(format, args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + std.Warnf(format, args...) +} + +// Warningf logs a message at level Warn on the standard logger. +func Warningf(format string, args ...interface{}) { + std.Warningf(format, args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + std.Errorf(format, args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...interface{}) { + std.Panicf(format, args...) +} + +// Fatalf logs a message at level Fatal on the standard logger. +func Fatalf(format string, args ...interface{}) { + std.Fatalf(format, args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + std.Debugln(args...) +} + +// Println logs a message at level Info on the standard logger. +func Println(args ...interface{}) { + std.Println(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + std.Infoln(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + std.Warnln(args...) +} + +// Warningln logs a message at level Warn on the standard logger. +func Warningln(args ...interface{}) { + std.Warningln(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + std.Errorln(args...) +} + +// Panicln logs a message at level Panic on the standard logger. +func Panicln(args ...interface{}) { + std.Panicln(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger. +func Fatalln(args ...interface{}) { + std.Fatalln(args...) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go new file mode 100644 index 0000000..038ce9f --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter.go @@ -0,0 +1,44 @@ +package logrus + +// The Formatter interface is used to implement a custom Formatter. It takes an +// `Entry`. It exposes all the fields, including the default ones: +// +// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. +// * `entry.Data["time"]`. The timestamp. +// * `entry.Data["level"]. The level the entry was logged at. +// +// Any additional fields added with `WithField` or `WithFields` are also in +// `entry.Data`. Format is expected to return an array of bytes which are then +// logged to `logger.Out`. +type Formatter interface { + Format(*Entry) ([]byte, error) +} + +// This is to not silently overwrite `time`, `msg` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data Fields) { + _, ok := data["time"] + if ok { + data["fields.time"] = data["time"] + } + + _, ok = data["msg"] + if ok { + data["fields.msg"] = data["msg"] + } + + _, ok = data["level"] + if ok { + data["fields.level"] = data["level"] + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go new file mode 100644 index 0000000..77989da --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/formatter_bench_test.go @@ -0,0 +1,88 @@ +package logrus + +import ( + "testing" + "time" +) + +// smallFields is a small size data set for benchmarking +var smallFields = Fields{ + "foo": "bar", + "baz": "qux", + "one": "two", + "three": "four", +} + +// largeFields is a large size data set for benchmarking +var largeFields = Fields{ + "foo": "bar", + "baz": "qux", + "one": "two", + "three": "four", + "five": "six", + "seven": "eight", + "nine": "ten", + "eleven": "twelve", + "thirteen": "fourteen", + "fifteen": "sixteen", + "seventeen": "eighteen", + "nineteen": "twenty", + "a": "b", + "c": "d", + "e": "f", + "g": "h", + "i": "j", + "k": "l", + "m": "n", + "o": "p", + "q": "r", + "s": "t", + "u": "v", + "w": "x", + "y": "z", + "this": "will", + "make": "thirty", + "entries": "yeah", +} + +func BenchmarkSmallTextFormatter(b *testing.B) { + doBenchmark(b, &TextFormatter{DisableColors: true}, smallFields) +} + +func BenchmarkLargeTextFormatter(b *testing.B) { + doBenchmark(b, &TextFormatter{DisableColors: true}, largeFields) +} + +func BenchmarkSmallColoredTextFormatter(b *testing.B) { + doBenchmark(b, &TextFormatter{ForceColors: true}, smallFields) +} + +func BenchmarkLargeColoredTextFormatter(b *testing.B) { + doBenchmark(b, &TextFormatter{ForceColors: true}, largeFields) +} + +func BenchmarkSmallJSONFormatter(b *testing.B) { + doBenchmark(b, &JSONFormatter{}, smallFields) +} + +func BenchmarkLargeJSONFormatter(b *testing.B) { + doBenchmark(b, &JSONFormatter{}, largeFields) +} + +func doBenchmark(b *testing.B, formatter Formatter, fields Fields) { + entry := &Entry{ + Time: time.Time{}, + Level: InfoLevel, + Message: "message", + Data: fields, + } + var d []byte + var err error + for i := 0; i < b.N; i++ { + d, err = formatter.Format(entry) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(len(d))) + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go new file mode 100644 index 0000000..13f34cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hook_test.go @@ -0,0 +1,122 @@ +package logrus + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type TestHook struct { + Fired bool +} + +func (hook *TestHook) Fire(entry *Entry) error { + hook.Fired = true + return nil +} + +func (hook *TestHook) Levels() []Level { + return []Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + PanicLevel, + } +} + +func TestHookFires(t *testing.T) { + hook := new(TestHook) + + LogAndAssertJSON(t, func(log *Logger) { + log.Hooks.Add(hook) + assert.Equal(t, hook.Fired, false) + + log.Print("test") + }, func(fields Fields) { + assert.Equal(t, hook.Fired, true) + }) +} + +type ModifyHook struct { +} + +func (hook *ModifyHook) Fire(entry *Entry) error { + entry.Data["wow"] = "whale" + return nil +} + +func (hook *ModifyHook) Levels() []Level { + return []Level{ + DebugLevel, + InfoLevel, + WarnLevel, + ErrorLevel, + FatalLevel, + PanicLevel, + } +} + +func TestHookCanModifyEntry(t *testing.T) { + hook := new(ModifyHook) + + LogAndAssertJSON(t, func(log *Logger) { + log.Hooks.Add(hook) + log.WithField("wow", "elephant").Print("test") + }, func(fields Fields) { + assert.Equal(t, fields["wow"], "whale") + }) +} + +func TestCanFireMultipleHooks(t *testing.T) { + hook1 := new(ModifyHook) + hook2 := new(TestHook) + + LogAndAssertJSON(t, func(log *Logger) { + log.Hooks.Add(hook1) + log.Hooks.Add(hook2) + + log.WithField("wow", "elephant").Print("test") + }, func(fields Fields) { + assert.Equal(t, fields["wow"], "whale") + assert.Equal(t, hook2.Fired, true) + }) +} + +type ErrorHook struct { + Fired bool +} + +func (hook *ErrorHook) Fire(entry *Entry) error { + hook.Fired = true + return nil +} + +func (hook *ErrorHook) Levels() []Level { + return []Level{ + ErrorLevel, + } +} + +func TestErrorHookShouldntFireOnInfo(t *testing.T) { + hook := new(ErrorHook) + + LogAndAssertJSON(t, func(log *Logger) { + log.Hooks.Add(hook) + log.Info("test") + }, func(fields Fields) { + assert.Equal(t, hook.Fired, false) + }) +} + +func TestErrorHookShouldFireOnError(t *testing.T) { + hook := new(ErrorHook) + + LogAndAssertJSON(t, func(log *Logger) { + log.Hooks.Add(hook) + log.Error("test") + }, func(fields Fields) { + assert.Equal(t, hook.Fired, true) + }) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go new file mode 100644 index 0000000..0da2b36 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks.go @@ -0,0 +1,34 @@ +package logrus + +// A hook to be fired when logging on the logging levels returned from +// `Levels()` on your implementation of the interface. Note that this is not +// fired in a goroutine or a channel with workers, you should handle such +// functionality yourself if your call is non-blocking and you don't wish for +// the logging calls for levels returned from `Levels()` to block. +type Hook interface { + Levels() []Level + Fire(*Entry) error +} + +// Internal type for storing the hooks on a logger instance. +type levelHooks map[Level][]Hook + +// Add a hook to an instance of logger. This is called with +// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. +func (hooks levelHooks) Add(hook Hook) { + for _, level := range hook.Levels() { + hooks[level] = append(hooks[level], hook) + } +} + +// Fire all the hooks for the passed level. Used by `entry.log` to fire +// appropriate hooks for a log entry. +func (hooks levelHooks) Fire(level Level, entry *Entry) error { + for _, hook := range hooks[level] { + if err := hook.Fire(entry); err != nil { + return err + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go new file mode 100644 index 0000000..880d21e --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go @@ -0,0 +1,54 @@ +package logrus_airbrake + +import ( + "github.com/Sirupsen/logrus" + "github.com/tobi/airbrake-go" +) + +// AirbrakeHook to send exceptions to an exception-tracking service compatible +// with the Airbrake API. You must set: +// * airbrake.Endpoint +// * airbrake.ApiKey +// * airbrake.Environment (only sends exceptions when set to "production") +// +// Before using this hook, to send an error. Entries that trigger an Error, +// Fatal or Panic should now include an "error" field to send to Airbrake. +type AirbrakeHook struct{} + +func (hook *AirbrakeHook) Fire(entry *logrus.Entry) error { + if entry.Data["error"] == nil { + entry.Logger.WithFields(logrus.Fields{ + "source": "airbrake", + "endpoint": airbrake.Endpoint, + }).Warn("Exceptions sent to Airbrake must have an 'error' key with the error") + return nil + } + + err, ok := entry.Data["error"].(error) + if !ok { + entry.Logger.WithFields(logrus.Fields{ + "source": "airbrake", + "endpoint": airbrake.Endpoint, + }).Warn("Exceptions sent to Airbrake must have an `error` key of type `error`") + return nil + } + + airErr := airbrake.Notify(err) + if airErr != nil { + entry.Logger.WithFields(logrus.Fields{ + "source": "airbrake", + "endpoint": airbrake.Endpoint, + "error": airErr, + }).Warn("Failed to send error to Airbrake") + } + + return nil +} + +func (hook *AirbrakeHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md new file mode 100644 index 0000000..ae61e92 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/README.md @@ -0,0 +1,28 @@ +# Papertrail Hook for Logrus :walrus: + +[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts). + +In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible. + +## Usage + +You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`. + +For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs. + +```go +import ( + "log/syslog" + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/papertrail" +) + +func main() { + log := logrus.New() + hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME) + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go new file mode 100644 index 0000000..c0f10c1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go @@ -0,0 +1,55 @@ +package logrus_papertrail + +import ( + "fmt" + "net" + "os" + "time" + + "github.com/Sirupsen/logrus" +) + +const ( + format = "Jan 2 15:04:05" +) + +// PapertrailHook to send logs to a logging service compatible with the Papertrail API. +type PapertrailHook struct { + Host string + Port int + AppName string + UDPConn net.Conn +} + +// NewPapertrailHook creates a hook to be added to an instance of logger. +func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) { + conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port)) + return &PapertrailHook{host, port, appName, conn}, err +} + +// Fire is called when a log event is fired. +func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { + date := time.Now().Format(format) + msg, _ := entry.String() + payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg) + + bytesWritten, err := hook.UDPConn.Write([]byte(payload)) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err) + return err + } + + return nil +} + +// Levels returns the available logging levels. +func (hook *PapertrailHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go new file mode 100644 index 0000000..96318d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/papertrail/papertrail_test.go @@ -0,0 +1,26 @@ +package logrus_papertrail + +import ( + "fmt" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/stvp/go-udp-testing" +) + +func TestWritingToUDP(t *testing.T) { + port := 16661 + udp.SetAddr(fmt.Sprintf(":%d", port)) + + hook, err := NewPapertrailHook("localhost", port, "test") + if err != nil { + t.Errorf("Unable to connect to local UDP server.") + } + + log := logrus.New() + log.Hooks.Add(hook) + + udp.ShouldReceive(t, "foo", func() { + log.Info("foo") + }) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md new file mode 100644 index 0000000..a409f3b --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/README.md @@ -0,0 +1,61 @@ +# Sentry Hook for Logrus :walrus: + +[Sentry](https://getsentry.com) provides both self-hosted and hosted +solutions for exception tracking. +Both client and server are +[open source](https://github.com/getsentry/sentry). + +## Usage + +Every sentry application defined on the server gets a different +[DSN](https://www.getsentry.com/docs/). In the example below replace +`YOUR_DSN` with the one created for your application. + +```go +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/sentry" +) + +func main() { + log := logrus.New() + hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + }) + + if err == nil { + log.Hooks.Add(hook) + } +} +``` + +## Special fields + +Some logrus fields have a special meaning in this hook, +these are server_name and logger. +When logs are sent to sentry these fields are treated differently. +- server_name (also known as hostname) is the name of the server which +is logging the event (hostname.example.com) +- logger is the part of the application which is logging the event. +In go this usually means setting it to the name of the package. + +## Timeout + +`Timeout` is the time the sentry hook will wait for a response +from the sentry server. + +If this time elapses with no response from +the server an error will be returned. + +If `Timeout` is set to 0 the SentryHook will not wait for a reply +and will assume a correct delivery. + +The SentryHook has a default timeout of `100 milliseconds` when created +with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field: + +```go +hook, _ := logrus_sentry.NewSentryHook(...) +hook.Timeout = 20*time.Seconds +``` diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go new file mode 100644 index 0000000..379f281 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry.go @@ -0,0 +1,100 @@ +package logrus_sentry + +import ( + "fmt" + "time" + + "github.com/Sirupsen/logrus" + "github.com/getsentry/raven-go" +) + +var ( + severityMap = map[logrus.Level]raven.Severity{ + logrus.DebugLevel: raven.DEBUG, + logrus.InfoLevel: raven.INFO, + logrus.WarnLevel: raven.WARNING, + logrus.ErrorLevel: raven.ERROR, + logrus.FatalLevel: raven.FATAL, + logrus.PanicLevel: raven.FATAL, + } +) + +func getAndDel(d logrus.Fields, key string) (string, bool) { + var ( + ok bool + v interface{} + val string + ) + if v, ok = d[key]; !ok { + return "", false + } + + if val, ok = v.(string); !ok { + return "", false + } + delete(d, key) + return val, true +} + +// SentryHook delivers logs to a sentry server. +type SentryHook struct { + // Timeout sets the time to wait for a delivery error from the sentry server. + // If this is set to zero the server will not wait for any response and will + // consider the message correctly sent + Timeout time.Duration + + client *raven.Client + levels []logrus.Level +} + +// NewSentryHook creates a hook to be added to an instance of logger +// and initializes the raven client. +// This method sets the timeout to 100 milliseconds. +func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) { + client, err := raven.NewClient(DSN, nil) + if err != nil { + return nil, err + } + return &SentryHook{100 * time.Millisecond, client, levels}, nil +} + +// Called when an event should be sent to sentry +// Special fields that sentry uses to give more information to the server +// are extracted from entry.Data (if they are found) +// These fields are: logger and server_name +func (hook *SentryHook) Fire(entry *logrus.Entry) error { + packet := &raven.Packet{ + Message: entry.Message, + Timestamp: raven.Timestamp(entry.Time), + Level: severityMap[entry.Level], + Platform: "go", + } + + d := entry.Data + + if logger, ok := getAndDel(d, "logger"); ok { + packet.Logger = logger + } + if serverName, ok := getAndDel(d, "server_name"); ok { + packet.ServerName = serverName + } + packet.Extra = map[string]interface{}(d) + + _, errCh := hook.client.Capture(packet, nil) + timeout := hook.Timeout + if timeout != 0 { + timeoutCh := time.After(timeout) + select { + case err := <-errCh: + return err + case <-timeoutCh: + return fmt.Errorf("no response from sentry server in %s", timeout) + } + } + return nil +} + +// Levels returns the available logging levels. +func (hook *SentryHook) Levels() []logrus.Level { + return hook.levels +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go new file mode 100644 index 0000000..45f18d1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/sentry/sentry_test.go @@ -0,0 +1,97 @@ +package logrus_sentry + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/Sirupsen/logrus" + "github.com/getsentry/raven-go" +) + +const ( + message = "error message" + server_name = "testserver.internal" + logger_name = "test.logger" +) + +func getTestLogger() *logrus.Logger { + l := logrus.New() + l.Out = ioutil.Discard + return l +} + +func WithTestDSN(t *testing.T, tf func(string, <-chan *raven.Packet)) { + pch := make(chan *raven.Packet, 1) + s := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + d := json.NewDecoder(req.Body) + p := &raven.Packet{} + err := d.Decode(p) + if err != nil { + t.Fatal(err.Error()) + } + + pch <- p + })) + defer s.Close() + + fragments := strings.SplitN(s.URL, "://", 2) + dsn := fmt.Sprintf( + "%s://public:secret@%s/sentry/project-id", + fragments[0], + fragments[1], + ) + tf(dsn, pch) +} + +func TestSpecialFields(t *testing.T) { + WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) { + logger := getTestLogger() + + hook, err := NewSentryHook(dsn, []logrus.Level{ + logrus.ErrorLevel, + }) + + if err != nil { + t.Fatal(err.Error()) + } + logger.Hooks.Add(hook) + logger.WithFields(logrus.Fields{ + "server_name": server_name, + "logger": logger_name, + }).Error(message) + + packet := <-pch + if packet.Logger != logger_name { + t.Errorf("logger should have been %s, was %s", logger_name, packet.Logger) + } + + if packet.ServerName != server_name { + t.Errorf("server_name should have been %s, was %s", server_name, packet.ServerName) + } + }) +} + +func TestSentryHandler(t *testing.T) { + WithTestDSN(t, func(dsn string, pch <-chan *raven.Packet) { + logger := getTestLogger() + hook, err := NewSentryHook(dsn, []logrus.Level{ + logrus.ErrorLevel, + }) + if err != nil { + t.Fatal(err.Error()) + } + logger.Hooks.Add(hook) + + logger.Error(message) + packet := <-pch + if packet.Message != message { + t.Errorf("message should have been %s, was %s", message, packet.Message) + } + }) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md new file mode 100644 index 0000000..4dbb8e7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/README.md @@ -0,0 +1,20 @@ +# Syslog Hooks for Logrus :walrus: + +## Usage + +```go +import ( + "log/syslog" + "github.com/Sirupsen/logrus" + logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" +) + +func main() { + log := logrus.New() + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go new file mode 100644 index 0000000..b6fa374 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog.go @@ -0,0 +1,59 @@ +package logrus_syslog + +import ( + "fmt" + "github.com/Sirupsen/logrus" + "log/syslog" + "os" +) + +// SyslogHook to send logs via syslog. +type SyslogHook struct { + Writer *syslog.Writer + SyslogNetwork string + SyslogRaddr string +} + +// Creates a hook to be added to an instance of logger. This is called with +// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` +// `if err == nil { log.Hooks.Add(hook) }` +func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { + w, err := syslog.Dial(network, raddr, priority, tag) + return &SyslogHook{w, network, raddr}, err +} + +func (hook *SyslogHook) Fire(entry *logrus.Entry) error { + line, err := entry.String() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) + return err + } + + switch entry.Level { + case logrus.PanicLevel: + return hook.Writer.Crit(line) + case logrus.FatalLevel: + return hook.Writer.Crit(line) + case logrus.ErrorLevel: + return hook.Writer.Err(line) + case logrus.WarnLevel: + return hook.Writer.Warning(line) + case logrus.InfoLevel: + return hook.Writer.Info(line) + case logrus.DebugLevel: + return hook.Writer.Debug(line) + default: + return nil + } +} + +func (hook *SyslogHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go new file mode 100644 index 0000000..42762dc --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/hooks/syslog/syslog_test.go @@ -0,0 +1,26 @@ +package logrus_syslog + +import ( + "github.com/Sirupsen/logrus" + "log/syslog" + "testing" +) + +func TestLocalhostAddAndPrint(t *testing.T) { + log := logrus.New() + hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + + if err != nil { + t.Errorf("Unable to connect to local syslog.") + } + + log.Hooks.Add(hook) + + for _, level := range hook.Levels() { + if len(log.Hooks[level]) != 1 { + t.Errorf("SyslogHook was not added. The length of log.Hooks[%v]: %v", level, len(log.Hooks[level])) + } + } + + log.Info("Congratulations!") +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go new file mode 100644 index 0000000..b09227c --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/json_formatter.go @@ -0,0 +1,26 @@ +package logrus + +import ( + "encoding/json" + "fmt" + "time" +) + +type JSONFormatter struct{} + +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields, len(entry.Data)+3) + for k, v := range entry.Data { + data[k] = v + } + prefixFieldClashes(data) + data["time"] = entry.Time.Format(time.RFC3339) + data["msg"] = entry.Message + data["level"] = entry.Level.String() + + serialized, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go new file mode 100644 index 0000000..b392e54 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logger.go @@ -0,0 +1,161 @@ +package logrus + +import ( + "io" + "os" + "sync" +) + +type Logger struct { + // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a + // file, or leave it default which is `os.Stdout`. You can also set this to + // something more adventorous, such as logging to Kafka. + Out io.Writer + // Hooks for the logger instance. These allow firing events based on logging + // levels and log entries. For example, to send errors to an error tracking + // service, log to StatsD or dump the core on fatal errors. + Hooks levelHooks + // All log entries pass through the formatter before logged to Out. The + // included formatters are `TextFormatter` and `JSONFormatter` for which + // TextFormatter is the default. In development (when a TTY is attached) it + // logs with colors, but to a file it wouldn't. You can easily implement your + // own that implements the `Formatter` interface, see the `README` or included + // formatters for examples. + Formatter Formatter + // The logging level the logger should log at. This is typically (and defaults + // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be + // logged. `logrus.Debug` is useful in + Level Level + // Used to sync writing to the log. + mu sync.Mutex +} + +// Creates a new logger. Configuration should be set by changing `Formatter`, +// `Out` and `Hooks` directly on the default logger instance. You can also just +// instantiate your own: +// +// var log = &Logger{ +// Out: os.Stderr, +// Formatter: new(JSONFormatter), +// Hooks: make(levelHooks), +// Level: logrus.DebugLevel, +// } +// +// It's recommended to make this a global instance called `log`. +func New() *Logger { + return &Logger{ + Out: os.Stdout, + Formatter: new(TextFormatter), + Hooks: make(levelHooks), + Level: InfoLevel, + } +} + +// Adds a field to the log entry, note that you it doesn't log until you call +// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. +// Ff you want multiple fields, use `WithFields`. +func (logger *Logger) WithField(key string, value interface{}) *Entry { + return NewEntry(logger).WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func (logger *Logger) WithFields(fields Fields) *Entry { + return NewEntry(logger).WithFields(fields) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + NewEntry(logger).Debugf(format, args...) +} + +func (logger *Logger) Infof(format string, args ...interface{}) { + NewEntry(logger).Infof(format, args...) +} + +func (logger *Logger) Printf(format string, args ...interface{}) { + NewEntry(logger).Printf(format, args...) +} + +func (logger *Logger) Warnf(format string, args ...interface{}) { + NewEntry(logger).Warnf(format, args...) +} + +func (logger *Logger) Warningf(format string, args ...interface{}) { + NewEntry(logger).Warnf(format, args...) +} + +func (logger *Logger) Errorf(format string, args ...interface{}) { + NewEntry(logger).Errorf(format, args...) +} + +func (logger *Logger) Fatalf(format string, args ...interface{}) { + NewEntry(logger).Fatalf(format, args...) +} + +func (logger *Logger) Panicf(format string, args ...interface{}) { + NewEntry(logger).Panicf(format, args...) +} + +func (logger *Logger) Debug(args ...interface{}) { + NewEntry(logger).Debug(args...) +} + +func (logger *Logger) Info(args ...interface{}) { + NewEntry(logger).Info(args...) +} + +func (logger *Logger) Print(args ...interface{}) { + NewEntry(logger).Info(args...) +} + +func (logger *Logger) Warn(args ...interface{}) { + NewEntry(logger).Warn(args...) +} + +func (logger *Logger) Warning(args ...interface{}) { + NewEntry(logger).Warn(args...) +} + +func (logger *Logger) Error(args ...interface{}) { + NewEntry(logger).Error(args...) +} + +func (logger *Logger) Fatal(args ...interface{}) { + NewEntry(logger).Fatal(args...) +} + +func (logger *Logger) Panic(args ...interface{}) { + NewEntry(logger).Panic(args...) +} + +func (logger *Logger) Debugln(args ...interface{}) { + NewEntry(logger).Debugln(args...) +} + +func (logger *Logger) Infoln(args ...interface{}) { + NewEntry(logger).Infoln(args...) +} + +func (logger *Logger) Println(args ...interface{}) { + NewEntry(logger).Println(args...) +} + +func (logger *Logger) Warnln(args ...interface{}) { + NewEntry(logger).Warnln(args...) +} + +func (logger *Logger) Warningln(args ...interface{}) { + NewEntry(logger).Warnln(args...) +} + +func (logger *Logger) Errorln(args ...interface{}) { + NewEntry(logger).Errorln(args...) +} + +func (logger *Logger) Fatalln(args ...interface{}) { + NewEntry(logger).Fatalln(args...) +} + +func (logger *Logger) Panicln(args ...interface{}) { + NewEntry(logger).Panicln(args...) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go new file mode 100644 index 0000000..43ee12e --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus.go @@ -0,0 +1,94 @@ +package logrus + +import ( + "fmt" + "log" +) + +// Fields type, used to pass to `WithFields`. +type Fields map[string]interface{} + +// Level type +type Level uint8 + +// Convert the Level to a string. E.g. PanicLevel becomes "panic". +func (level Level) String() string { + switch level { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warning" + case ErrorLevel: + return "error" + case FatalLevel: + return "fatal" + case PanicLevel: + return "panic" + } + + return "unknown" +} + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) (Level, error) { + switch lvl { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + } + + var l Level + return l, fmt.Errorf("not a valid logrus Level: %q", lvl) +} + +// These are the different logging levels. You can set the logging level to log +// on your instance of logger, obtained with `logrus.New()`. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel +) + +// Won't compile if StdLogger can't be realized by a log.Logger +var _ StdLogger = &log.Logger{} + +// StdLogger is what your logrus-enabled library should take, that way +// it'll accept a stdlib logger and a logrus logger. There's no standard +// interface, this is the closest we get, unfortunately. +type StdLogger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go new file mode 100644 index 0000000..5302542 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/logrus_test.go @@ -0,0 +1,283 @@ +package logrus + +import ( + "bytes" + "encoding/json" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func LogAndAssertJSON(t *testing.T, log func(*Logger), assertions func(fields Fields)) { + var buffer bytes.Buffer + var fields Fields + + logger := New() + logger.Out = &buffer + logger.Formatter = new(JSONFormatter) + + log(logger) + + err := json.Unmarshal(buffer.Bytes(), &fields) + assert.Nil(t, err) + + assertions(fields) +} + +func LogAndAssertText(t *testing.T, log func(*Logger), assertions func(fields map[string]string)) { + var buffer bytes.Buffer + + logger := New() + logger.Out = &buffer + logger.Formatter = &TextFormatter{ + DisableColors: true, + } + + log(logger) + + fields := make(map[string]string) + for _, kv := range strings.Split(buffer.String(), " ") { + if !strings.Contains(kv, "=") { + continue + } + kvArr := strings.Split(kv, "=") + key := strings.TrimSpace(kvArr[0]) + val := kvArr[1] + if kvArr[1][0] == '"' { + var err error + val, err = strconv.Unquote(val) + assert.NoError(t, err) + } + fields[key] = val + } + assertions(fields) +} + +func TestPrint(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Print("test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test") + assert.Equal(t, fields["level"], "info") + }) +} + +func TestInfo(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Info("test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test") + assert.Equal(t, fields["level"], "info") + }) +} + +func TestWarn(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Warn("test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test") + assert.Equal(t, fields["level"], "warning") + }) +} + +func TestInfolnShouldAddSpacesBetweenStrings(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Infoln("test", "test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test test") + }) +} + +func TestInfolnShouldAddSpacesBetweenStringAndNonstring(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Infoln("test", 10) + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test 10") + }) +} + +func TestInfolnShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Infoln(10, 10) + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "10 10") + }) +} + +func TestInfoShouldAddSpacesBetweenTwoNonStrings(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Infoln(10, 10) + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "10 10") + }) +} + +func TestInfoShouldNotAddSpacesBetweenStringAndNonstring(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Info("test", 10) + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test10") + }) +} + +func TestInfoShouldNotAddSpacesBetweenStrings(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.Info("test", "test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "testtest") + }) +} + +func TestWithFieldsShouldAllowAssignments(t *testing.T) { + var buffer bytes.Buffer + var fields Fields + + logger := New() + logger.Out = &buffer + logger.Formatter = new(JSONFormatter) + + localLog := logger.WithFields(Fields{ + "key1": "value1", + }) + + localLog.WithField("key2", "value2").Info("test") + err := json.Unmarshal(buffer.Bytes(), &fields) + assert.Nil(t, err) + + assert.Equal(t, "value2", fields["key2"]) + assert.Equal(t, "value1", fields["key1"]) + + buffer = bytes.Buffer{} + fields = Fields{} + localLog.Info("test") + err = json.Unmarshal(buffer.Bytes(), &fields) + assert.Nil(t, err) + + _, ok := fields["key2"] + assert.Equal(t, false, ok) + assert.Equal(t, "value1", fields["key1"]) +} + +func TestUserSuppliedFieldDoesNotOverwriteDefaults(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.WithField("msg", "hello").Info("test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test") + }) +} + +func TestUserSuppliedMsgFieldHasPrefix(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.WithField("msg", "hello").Info("test") + }, func(fields Fields) { + assert.Equal(t, fields["msg"], "test") + assert.Equal(t, fields["fields.msg"], "hello") + }) +} + +func TestUserSuppliedTimeFieldHasPrefix(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.WithField("time", "hello").Info("test") + }, func(fields Fields) { + assert.Equal(t, fields["fields.time"], "hello") + }) +} + +func TestUserSuppliedLevelFieldHasPrefix(t *testing.T) { + LogAndAssertJSON(t, func(log *Logger) { + log.WithField("level", 1).Info("test") + }, func(fields Fields) { + assert.Equal(t, fields["level"], "info") + assert.Equal(t, fields["fields.level"], 1) + }) +} + +func TestDefaultFieldsAreNotPrefixed(t *testing.T) { + LogAndAssertText(t, func(log *Logger) { + ll := log.WithField("herp", "derp") + ll.Info("hello") + ll.Info("bye") + }, func(fields map[string]string) { + for _, fieldName := range []string{"fields.level", "fields.time", "fields.msg"} { + if _, ok := fields[fieldName]; ok { + t.Fatalf("should not have prefixed %q: %v", fieldName, fields) + } + } + }) +} + +func TestDoubleLoggingDoesntPrefixPreviousFields(t *testing.T) { + + var buffer bytes.Buffer + var fields Fields + + logger := New() + logger.Out = &buffer + logger.Formatter = new(JSONFormatter) + + llog := logger.WithField("context", "eating raw fish") + + llog.Info("looks delicious") + + err := json.Unmarshal(buffer.Bytes(), &fields) + assert.NoError(t, err, "should have decoded first message") + assert.Len(t, fields, 4, "should only have msg/time/level/context fields") + assert.Equal(t, fields["msg"], "looks delicious") + assert.Equal(t, fields["context"], "eating raw fish") + + buffer.Reset() + + llog.Warn("omg it is!") + + err = json.Unmarshal(buffer.Bytes(), &fields) + assert.NoError(t, err, "should have decoded second message") + assert.Len(t, fields, 4, "should only have msg/time/level/context fields") + assert.Equal(t, fields["msg"], "omg it is!") + assert.Equal(t, fields["context"], "eating raw fish") + assert.Nil(t, fields["fields.msg"], "should not have prefixed previous `msg` entry") + +} + +func TestConvertLevelToString(t *testing.T) { + assert.Equal(t, "debug", DebugLevel.String()) + assert.Equal(t, "info", InfoLevel.String()) + assert.Equal(t, "warning", WarnLevel.String()) + assert.Equal(t, "error", ErrorLevel.String()) + assert.Equal(t, "fatal", FatalLevel.String()) + assert.Equal(t, "panic", PanicLevel.String()) +} + +func TestParseLevel(t *testing.T) { + l, err := ParseLevel("panic") + assert.Nil(t, err) + assert.Equal(t, PanicLevel, l) + + l, err = ParseLevel("fatal") + assert.Nil(t, err) + assert.Equal(t, FatalLevel, l) + + l, err = ParseLevel("error") + assert.Nil(t, err) + assert.Equal(t, ErrorLevel, l) + + l, err = ParseLevel("warn") + assert.Nil(t, err) + assert.Equal(t, WarnLevel, l) + + l, err = ParseLevel("warning") + assert.Nil(t, err) + assert.Equal(t, WarnLevel, l) + + l, err = ParseLevel("info") + assert.Nil(t, err) + assert.Equal(t, InfoLevel, l) + + l, err = ParseLevel("debug") + assert.Nil(t, err) + assert.Equal(t, DebugLevel, l) + + l, err = ParseLevel("invalid") + assert.Equal(t, "not a valid logrus Level: \"invalid\"", err.Error()) +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go new file mode 100644 index 0000000..8fe02a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_darwin.go @@ -0,0 +1,12 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package logrus + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go new file mode 100644 index 0000000..0428ee5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_freebsd.go @@ -0,0 +1,20 @@ +/* + Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin. +*/ +package logrus + +import ( + "syscall" +) + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]uint8 + Ispeed uint32 + Ospeed uint32 +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go new file mode 100644 index 0000000..a2c0b40 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_linux.go @@ -0,0 +1,12 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package logrus + +import "syscall" + +const ioctlReadTermios = syscall.TCGETS + +type Termios syscall.Termios diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go new file mode 100644 index 0000000..80edd32 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_notwindows.go @@ -0,0 +1,21 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux,!appengine darwin freebsd openbsd + +package logrus + +import ( + "syscall" + "unsafe" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal() bool { + fd := syscall.Stdout + var termios Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go new file mode 100644 index 0000000..d238bfa --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_openbsd.go @@ -0,0 +1,8 @@ + +package logrus + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go new file mode 100644 index 0000000..2e09f6f --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/terminal_windows.go @@ -0,0 +1,27 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package logrus + +import ( + "syscall" + "unsafe" +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal() bool { + fd := syscall.Stdout + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go new file mode 100644 index 0000000..78e7889 --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter.go @@ -0,0 +1,124 @@ +package logrus + +import ( + "bytes" + "fmt" + "regexp" + "sort" + "strings" + "time" +) + +const ( + nocolor = 0 + red = 31 + green = 32 + yellow = 33 + blue = 34 +) + +var ( + baseTimestamp time.Time + isTerminal bool + noQuoteNeeded *regexp.Regexp +) + +func init() { + baseTimestamp = time.Now() + isTerminal = IsTerminal() +} + +func miniTS() int { + return int(time.Since(baseTimestamp) / time.Second) +} + +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + DisableColors bool + // Set to true to disable timestamp logging (useful when the output + // is redirected to a logging system already adding a timestamp) + DisableTimestamp bool +} + +func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { + + var keys []string + for k := range entry.Data { + keys = append(keys, k) + } + sort.Strings(keys) + + b := &bytes.Buffer{} + + prefixFieldClashes(entry.Data) + + isColored := (f.ForceColors || isTerminal) && !f.DisableColors + + if isColored { + printColored(b, entry, keys) + } else { + if !f.DisableTimestamp { + f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) + } + f.appendKeyValue(b, "level", entry.Level.String()) + f.appendKeyValue(b, "msg", entry.Message) + for _, key := range keys { + f.appendKeyValue(b, key, entry.Data[key]) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func printColored(b *bytes.Buffer, entry *Entry, keys []string) { + var levelColor int + switch entry.Level { + case WarnLevel: + levelColor = yellow + case ErrorLevel, FatalLevel, PanicLevel: + levelColor = red + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String())[0:4] + + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) + for _, k := range keys { + v := entry.Data[k] + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%v", levelColor, k, v) + } +} + +func needsQuoting(text string) bool { + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch < '9') || + ch == '-' || ch == '.') { + return false + } + } + return true +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key, value interface{}) { + switch value.(type) { + case string: + if needsQuoting(value.(string)) { + fmt.Fprintf(b, "%v=%s ", key, value) + } else { + fmt.Fprintf(b, "%v=%q ", key, value) + } + case error: + if needsQuoting(value.(error).Error()) { + fmt.Fprintf(b, "%v=%s ", key, value) + } else { + fmt.Fprintf(b, "%v=%q ", key, value) + } + default: + fmt.Fprintf(b, "%v=%v ", key, value) + } +} diff --git a/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go new file mode 100644 index 0000000..f604f1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/Sirupsen/logrus/text_formatter_test.go @@ -0,0 +1,33 @@ +package logrus + +import ( + "bytes" + "errors" + + "testing" +) + +func TestQuoting(t *testing.T) { + tf := &TextFormatter{DisableColors: true} + + checkQuoting := func(q bool, value interface{}) { + b, _ := tf.Format(WithField("test", value)) + idx := bytes.Index(b, ([]byte)("test=")) + cont := bytes.Contains(b[idx+5:], []byte{'"'}) + if cont != q { + if q { + t.Errorf("quoting expected for: %#v", value) + } else { + t.Errorf("quoting not expected for: %#v", value) + } + } + } + + checkQuoting(false, "abcd") + checkQuoting(false, "v1.0") + checkQuoting(true, "/foobar") + checkQuoting(true, "x y") + checkQuoting(true, "x,y") + checkQuoting(false, errors.New("invalid")) + checkQuoting(true, errors.New("invalid argument")) +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/LICENSE b/Godeps/_workspace/src/github.com/ajstarks/svgo/LICENSE new file mode 100644 index 0000000..ea5a772 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/LICENSE @@ -0,0 +1,3 @@ +The contents of this repository are Licensed under +the Creative Commons Attribution 3.0 license as described in +http://creativecommons.org/licenses/by/3.0/us/ diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/README.markdown b/Godeps/_workspace/src/github.com/ajstarks/svgo/README.markdown new file mode 100644 index 0000000..2e5fadb --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/README.markdown @@ -0,0 +1,668 @@ +#SVGo: A Go library for SVG generation# + +The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). +Output goes to the specified io.Writer. + +## Supported SVG elements and functions ## + +### Shapes, lines, text + + circle, ellipse, polygon, polyline, rect (including roundrects), line, text + +### Paths + + general, arc, cubic and quadratic bezier paths, + +### Image and Gradients + + image, linearGradient, radialGradient, + +### Transforms ### + + translate, rotate, scale, skewX, skewY + +### Filter Effects + + filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, + feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, + feSpecularLighting, feSpotLight,feTile, feTurbulence + + +### Metadata elements ### + + desc, defs, g (style, transform, id), marker, mask, pattern, title, (a)ddress, link, script, use + +## Building and Usage ## + +See svgdef.[svg|png|pdf] for a graphical view of the function calls + + +Usage: (assuming GOPATH is set) + + go get github.com/ajstarks/svgo + go install github.com/ajstarks/svgo/... + + +You can use godoc to browse the documentation from the command line: + + $ godoc github.com/ajstarks/svgo + + +a minimal program, to generate SVG to standard output. + + package main + + import ( + "github.com/ajstarks/svgo" + "os" + ) + + func main() { + width := 500 + height := 500 + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Circle(width/2, height/2, 100) + canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") + canvas.End() + } + +Drawing in a web server: (http://localhost:2003/circle) + + package main + + import ( + "log" + "github.com/ajstarks/svgo" + "net/http" + ) + + func main() { + http.Handle("/circle", http.HandlerFunc(circle)) + err := http.ListenAndServe(":2003", nil) + if err != nil { + log.Fatal("ListenAndServe:", err) + } + } + + func circle(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Circle(250, 250, 125, "fill:none;stroke:black") + s.End() + } + +You may view the SVG output with a browser that supports SVG (tested on Chrome, Opera, Firefox and Safari), or any other SVG user-agent such as Batik Squiggle. + +### Graphics Sketching with SVGo and svgplay ### + +Combined with the svgplay command, SVGo can be used to "sketch" with code in a browser. + +To use svgplay and SVGo, first go to a directory with your code, and run: + + $ svgplay + 2014/06/25 22:05:28 ☠ ☠ ☠ Warning: this server allows a client connecting to 127.0.0.1:1999 to execute code on this computer ☠ ☠ ☠ + +Next open your browser to the svgplay server you just started. +svgplay only listens on localhost, and uses port 1999 (guess which year SVG was first introduced) by default + + http://localhost:1999/ + +Enter your code in the textarea, and when you are ready to run press Shift--Enter. The code will be compiled, with the results +on the right. To update, change the code and repeat. Note that compilation errors are shown in red under the code. In order for svgplay/SVGo to work, make sure that the io.Writer specified with the New function is os.Stdout. + + +If you want to sketch with an existing file, enter its URL: + + http://localhost:1999/foo.go + +![SVGplay](https://farm4.staticflickr.com/3859/14322978157_31c0114850.jpg) + + +### SVGo Papers and presentations ### + +* SVGo paper from SVGOpen 2011 + +* Programming Pictures with SVGo + +* SVGo Workshop + + +### Tutorial Video ### + +A video describing how to use the package can be seen on YouTube at + +## Package contents ## + +* svg.go: Library +* newsvg: Coding template command +* svgdef: Creates a SVG representation of the API +* android: The Android logo +* bubtrail: Bubble trails +* bulletgraph: Bullet Graphs (via Stephen Few) +* colortab: Display SVG named colors with RGB values +* compx: Component diagrams +* flower: Random "flowers" +* fontcompare: Compare two fonts +* f50: Get 50 photos from Flickr based on a query +* fe: Filter effects +* funnel: Funnel from transparent circles +* gradient: Linear and radial gradients +* html5logo: HTML5 logo with draggable elements +* imfade: Show image fading +* lewitt: Version of Sol Lewitt's Wall Drawing 91 +* ltr: Layer Tennis Remixes +* marker: Test markers +* paths: Demonstrate SVG paths +* pattern: Test patterns +* planets: Show the scale of the Solar system +* pmap: Proportion maps +* randcomp: Compare random number generators +* richter: Gerhard Richter's 256 colors +* rl: Random lines (port of a Processing demo) +* skewabc: Skew ABC +* stockproduct: Visualize product and stock prices +* svgopher: SVGo Mascot +* svgplay: SVGo sketching server +* svgplot: Plot data +* svgrid: Compose SVG files in a grid +* tsg: Twitter Search Grid +* tumblrgrid: Tumblr picture grid +* turbulence: Turbulence filter effect +* vismem: Visualize data from files +* webfonts: "Hello, World" with Google Web Fonts +* websvg: Generate SVG as a web server + + +## Functions and types ## + +Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. +Where applicable, a final optional argument specifies the style to be applied to the object. +The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a +series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) + +The Offcolor type: + + type Offcolor struct { + Offset uint8 + Color string + Opacity float + } + +is used to specify the offset, color, and opacity of stop colors in linear and radial gradients + +The Filterspec type: + + type Filterspec struct { + In string + In2 string + Result string + } + +is used to specify inputs and results for filter effects + + +### Structure, Scripting, Metadata, Transformation and Links ### + + New(w io.Writer) *SVG + Constructor, Specify the output destination. + + Start(w int, h int, attributes ...string) + begin the SVG document with the width w and height h. Optionally add additional elememts + (such as additional namespaces or scripting events) + + + Startview(w, h, minx, miny, vw, vh int) + begin the SVG document with the width w, height h, with a viewBox at minx, miny, vw, vh. + + + Startunit(w int, h int, unit string, ns ...string) + begin the SVG document, with width and height in the specified units. Optionally add additional elememts + (such as additional namespaces or scripting events) + + + + Startpercent(w int, h int, ns ...string) + begin the SVG document, with width and height in percent. Optionally add additional elememts + (such as additional namespaces or scripting events) + + + + StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) + begin the SVG document with the width w, height h, in the specified unit, with a viewBox at minx, miny, vw, vh. + + + End() + end the SVG document + + Script(scriptype string, data ...string) + Script defines a script with a specified type, (for example "application/javascript"). + if the first variadic argument is a link, use only the link reference. + Otherwise, treat variadic arguments as the text of the script (marked up as CDATA). + if no data is specified, simply close the script element. + + + Group(s ...string) + begin a group, with arbitrary attributes + + + Gstyle(s string) + begin a group, with the specified style. + + + Gid(s string) + begin a group, with the specified id. + + Gtransform(s string) + begin a group, with the specified transform, end with Gend(). + + + Translate(x, y int) + begins coordinate translation to (x,y), end with Gend(). + + + Scale(n float64) + scales the coordinate system by n, end with Gend(). + + + ScaleXY(x, y float64) + scales the coordinate system by x, y. End with Gend(). + + + SkewX(a float64) + SkewX skews the x coordinate system by angle a, end with Gend(). + + + SkewY(a float64) + SkewY skews the y coordinate system by angle a, end with Gend(). + + + SkewXY(ax, ay float64) + SkewXY skews x and y coordinate systems by ax, ay respectively, end with Gend(). + + + Rotate(r float64) + rotates the coordinate system by r degrees, end with Gend(). + + + TranslateRotate(x, y int, r float64) + translates the coordinate system to (x,y), then rotates to r degrees, end with Gend(). + + RotateTranslate(x, y int, r float64) + rotates the coordinate system r degrees, then translates to (x,y), end with Gend(). + + Gend() + end the group (must be paired with Gstyle, Gtransform, Gid). + + ClipPath(s ...string) + Begin a ClipPath + + + ClipEnd() + End a ClipPath + + + Def() + begin a definition block. + + + DefEnd() + end a definition block. + + Marker(id string, x, y, w, h int, s ...string) + define a marker + + + + MarkerEnd() + end a marker + + + Mask(id string, x int, y int, w int, h int, s ...string) + creates a mask with a specified id, dimension, and optional style. + + + MaskEnd() + ends the Mask element. + + + Pattern(id string, x, y, width, height int, putype string, s ...string) + define a Pattern with the specified dimensions, the putype can be either "user" or "obj", which sets the patternUnits + attribute to be either userSpaceOnUse or objectBoundingBox. + + + Desc(s string) + specify the text of the description. + + + Title(s string) + specify the text of the title. + + + Link(href string, title string) + begin a link named "href", with the specified title. + + + LinkEnd() + end the link. + + Use(x int, y int, link string, s ...string) + place the object referenced at link at the location x, y. + + +### Shapes ### + + Circle(x int, y int, r int, s ...string) + draw a circle, centered at x,y with radius r. + + + ![Circle](http://farm5.static.flickr.com/4144/5187953823_01a1741489_m.jpg) + + Ellipse(x int, y int, w int, h int, s ...string) + draw an ellipse, centered at x,y with radii w, and h. + + + ![Ellipse](http://farm2.static.flickr.com/1271/5187953773_a9d1fc406c_m.jpg) + + Polygon(x []int, y []int, s ...string) + draw a series of line segments using an array of x, y coordinates. + + + ![Polygon](http://farm2.static.flickr.com/1006/5187953873_337dc26597_m.jpg) + + Rect(x int, y int, w int, h int, s ...string) + draw a rectangle with upper left-hand corner at x,y, with width w, and height h. + + + ![Rect](http://farm2.static.flickr.com/1233/5188556032_86c90e354b_m.jpg) + + CenterRect(x int, y int, w int, h int, s ...string) + draw a rectangle with its center at x,y, with width w, and height h. + + Roundrect(x int, y int, w int, h int, rx int, ry int, s ...string) + draw a rounded rectangle with upper the left-hand corner at x,y, + with width w, and height h. The radii for the rounded portion + is specified by rx (width), and ry (height). + + ![Roundrect](http://farm2.static.flickr.com/1275/5188556120_e2a9998fee_m.jpg) + + Square(x int, y int, s int, style ...string) + draw a square with upper left corner at x,y with sides of length s. + + ![Square](http://farm5.static.flickr.com/4110/5187953659_54dcce242e_m.jpg) + +### Paths ### + + Path(p string, s ...style) + draw the arbitrary path as specified in p, according to the style specified in s. + + + Arc(sx int, sy int, ax int, ay int, r int, large bool, sweep bool, ex int, ey int, s ...string) + draw an elliptical arc beginning coordinate at sx,sy, ending coordinate at ex, ey + width and height of the arc are specified by ax, ay, the x axis rotation is r + + if sweep is true, then the arc will be drawn in a "positive-angle" direction (clockwise), + if false, the arc is drawn counterclockwise. + + if large is true, the arc sweep angle is greater than or equal to 180 degrees, + otherwise the arc sweep is less than 180 degrees. + + + ![Arc](http://farm2.static.flickr.com/1300/5188556148_df1a176074_m.jpg) + + + + Bezier(sx int, sy int, cx int, cy int, px int, py int, ex int, ey int, s ...string) + draw a cubic bezier curve, beginning at sx,sy, ending at ex,ey + with control points at cx,cy and px,py. + + + ![Bezier](http://farm2.static.flickr.com/1233/5188556246_a03e67d013.jpg) + + + + Qbezier(sx int, sy int, cx int, cy int, ex int, ey int, tx int, ty int, s ...string) + draw a quadratic bezier curve, beginning at sx, sy, ending at tx,ty + with control points are at cx,cy, ex,ey. + + + ![Qbezier](http://farm2.static.flickr.com/1018/5187953917_9a43cf64fb.jpg) + + + Qbez(sx int, sy int, cx int, cy int, ex int, ey int, s...string) + draws a quadratic bezier curver, with optional style beginning at sx,sy, ending at ex, sy + with the control point at cx, cy. + + + ![Qbez](http://farm6.static.flickr.com/5176/5569879349_5f726aab5e.jpg) + +### Lines ### + + Line(x1 int, y1 int, x2 int, y2 int, s ...string) + draw a line segment between x1,y1 and x2,y2. + + + ![Line](http://farm5.static.flickr.com/4154/5188556080_0be19da0bc.jpg) + + + Polyline(x []int, y []int, s ...string) + draw a polygon using coordinates specified in x,y arrays. + + + ![Polyline](http://farm2.static.flickr.com/1266/5188556384_a863273a69.jpg) + +### Image and Text ### + + Image(x int, y int, w int, h int, link string, s ...string) + place at x,y (upper left hand corner), the image with width w, and height h, referenced at link. + + + ![Image](http://farm5.static.flickr.com/4058/5188556346_e5ce3dcbc2_m.jpg) + + Text(x int, y int, t string, s ...string) + Place the specified text, t at x,y according to the style specified in s. + + + Textlines(x, y int, s []string, size, spacing int, fill, align string) + Places lines of text in s, starting at x,y, at the specified size, fill, and alignment, and spacing. + + Textpath(t string, pathid string, s ...string) + places optionally styled text along a previously defined path. + + ![Image](http://farm4.static.flickr.com/3149/5694580737_4b291df768_m.jpg) + +### Color ### + + RGB(r int, g int, b int) string + creates a style string for the fill color designated + by the (r)ed, g(reen), (b)lue components. + + + RGBA(r int, g int, b int, a float64) string + as above, but includes the color's opacity as a value + between 0.0 (fully transparent) and 1.0 (opaque). + +### Gradients ### + + LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) + constructs a linear color gradient identified by id, + along the vector defined by (x1,y1), and (x2,y2). + The stop color sequence defined in sc. Coordinates are expressed as percentages. + + ![LinearGradient](http://farm5.static.flickr.com/4153/5187954033_3972f63fa9.jpg) + + RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) + constructs a radial color gradient identified by id, + centered at (cx,cy), with a radius of r. + (fx, fy) define the location of the focal point of the light source. + The stop color sequence defined in sc. + Coordinates are expressed as percentages. + + + ![RadialGradient](http://farm2.static.flickr.com/1302/5187954065_7ddba7b819.jpg) + +### Filter Effects ### + + Filter(id string, s ...string) + Filter begins a filter set +Standard reference: + + Fend() +Fend ends a filter set +Standard reference: + + FeBlend(fs Filterspec, mode string, s ...string) +FeBlend specifies a Blend filter primitive +Standard reference: + + FeColorMatrix(fs Filterspec, values [20]float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with matrix values +Standard reference: + + FeColorMatrixHue(fs Filterspec, value float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with hue values +Standard reference: + + FeColorMatrixSaturate(fs Filterspec, value float64, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with saturation values +Standard reference: + + FeColorMatrixLuminence(fs Filterspec, s ...string) +FeColorMatrix specifies a color matrix filter primitive, with luminence values +Standard reference: + + FeComponentTransfer() +FeComponentTransfer begins a feComponent filter Element> +Standard reference: + + FeCompEnd() +FeCompEnd ends a feComponent filter Element> + + FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) +FeComposite specifies a feComposite filter primitive +Standard reference: + + FeConvolveMatrix(fs Filterspec, matrix [9]int, s ...string) +FeConvolveMatrix specifies a feConvolveMatrix filter primitive +Standard referencd: + + + FeDiffuseLighting(fs Filterspec, scale, constant float64, s ...string) +FeDiffuseLighting specifies a diffuse lighting filter primitive, +a container for light source Element>s, end with DiffuseEnd() + + FeDiffEnd() +FeDiffuseEnd ends a diffuse lighting filter primitive container +Standard reference: + + + FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) +FeDisplacementMap specifies a feDisplacementMap filter primitive +Standard reference: + + FeDistantLight(fs Filterspec, azimuth, elevation float64, s ...string) +FeDistantLight specifies a feDistantLight filter primitive +Standard reference: + + FeFlood(fs Filterspec, color string, opacity float64, s ...string) +FeFlood specifies a flood filter primitive +Standard reference: + + FeFuncLinear(channel string, slope, intercept float64) +FeFuncLinear is the linear form of feFunc +Standard reference: + + FeFuncGamma(channel, amplitude, exponent, offset float64) +FeFuncGamma is the gamma curve form of feFunc +Standard reference: + + FeFuncTable(channel string, tv []float64) +FeFuncGamma is the form of feFunc using a table of values +Standard reference: + + FeFuncDiscrete(channel string, tv []float64) +FeFuncGamma is the form of feFunc using discrete values +Standard reference: + + FeGaussianBlur(fs Filterspec, stdx, stdy float64, s ...string) +FeGaussianBlur specifies a Gaussian Blur filter primitive +Standard reference: + + FeImage(href string, result string, s ...string) +FeImage specifies a feImage filter primitive +Standard reference: + + FeMerge(nodes []string, s ...string) +FeMerge specifies a feMerge filter primitive, containing feMerge Element>s +Standard reference: + + FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) +FeMorphologyLight specifies a feMorphologyLight filter primitive +Standard reference: + + FeOffset(fs Filterspec, dx, dy int, s ...string) +FeOffset specifies the feOffset filter primitive +Standard reference: + + FePointLight(x, y, z float64, s ...string) +FePointLight specifies a fePpointLight filter primitive +Standard reference: + + FeSpecularLighting(fs Filterspec, scale, constant float64, exponent int, color string, s ...string) +FeSpecularLighting specifies a specular lighting filter primitive, +a container for light source elements, end with SpecularEnd() + + + FeSpecEnd() +FeSpecularEnd ends a specular lighting filter primitive container +Standard reference: + + + FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) +FeSpotLight specifies a feSpotLight filter primitive +Standard reference: + + FeTile(fs Filterspec, in string, s ...string) +FeTile specifies the tile utility filter primitive +Standard reference: + + + FeTurbulence(fs Filterspec, ftype string, bfx, bfy float64, octaves int, seed int64, stitch bool, s ...string) +FeTurbulence specifies a turbulence filter primitive +Standard reference: + +### Filter convenience functions (modeled on CSS filter effects) ### + + Blur(p float64) +Blur function by standard deviation + + Brightness(p float64) +Brightness function (0-100) + + Grayscale() +Apply a grayscale filter to the image + + HueRotate(a float64) +Rotate Hues (0-360 degrees) + + Invert() +Invert the image's colors + + Saturate(p float64) +Percent saturation, 0 is grayscale + + Sepia() +Apply sepia tone + + +### Utility ### + + Grid(x int, y int, w int, h int, n int, s ...string) + draws a grid of straight lines starting at x,y, with a width w, and height h, and a size of n. + + ![Grid](http://farm5.static.flickr.com/4133/5190957924_7a31d0db34.jpg) + +### Credits ### + +Thanks to Jonathan Wright for the io.Writer update. diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/android/android.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/android/android.go new file mode 100644 index 0000000..672ca1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/android/android.go @@ -0,0 +1,56 @@ +// android draws bugdroid, the Android mascot +// +build !appengine + +package main + + +import ( + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +var ( + width = 500 + height = 500 + canvas = svg.New(os.Stdout) +) + +const androidcolor = "rgb(164,198,57)" + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + +func android(x, y int, fill string, opacity float64) { + var linestyle = []string{`stroke="` + fill + `"`, `stroke-linecap="round"`, `stroke-width="5"`} + globalstyle := fmt.Sprintf("fill:%s;opacity:%.2f", fill, opacity) + canvas.Gstyle(globalstyle) + canvas.Arc(x+30, y+70, 35, 35, 0, false, true, x+130, y+70) // head + canvas.Line(x+60, y+25, x+50, y+10, linestyle[0], linestyle[1], linestyle[2]) // left antenna + canvas.Line(x+100, y+25, x+110, y+10, linestyle[0], linestyle[1], linestyle[2]) // right antenna + canvas.Circle(x+60, y+45, 5, "fill:white") // left eye + canvas.Circle(x+100, y+45, 5, `fill="white"`) // right eye + canvas.Roundrect(x+30, y+75, 100, 90, 10, 10) // body + canvas.Rect(x+30, y+75, 100, 80) + canvas.Roundrect(x+5, y+80, 20, 70, 10, 10) // left arm + canvas.Roundrect(x+135, y+80, 20, 70, 10, 10) // right arm + canvas.Roundrect(x+50, y+150, 20, 50, 10, 10) // left leg + canvas.Roundrect(x+90, y+150, 20, 50, 10, 10) // right leg + canvas.Gend() +} + +func main() { + canvas.Start(width, height) + canvas.Title("Android") + background(255) + + android(100, 100, androidcolor, 1.0) + canvas.Scale(3.0) + android(50, 50, "gray", 0.5) + canvas.Gend() + + canvas.Scale(0.5) + android(100, 100, "red", 1.0) + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/barchart/barchart.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/barchart/barchart.go new file mode 100644 index 0000000..7c0e767 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/barchart/barchart.go @@ -0,0 +1,472 @@ +// barchart - bar chart +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "github.com/ajstarks/svgo" + "io" + "math" + "os" + "strconv" + "strings" +) + +var ( + width, height, iscale, fontsize, barheight, gutter, cornerRadius, labelimit int + bgcolor, barcolor, title, inbar, valformat string + showtitle, showdata, showgrid, showscale, endtitle, trace bool +) + +const ( + gstyle = "font-family:Calibri,sans-serif;font-size:%dpx" + borderstyle = "stroke:lightgray;stroke-width:1px" + scalestyle = "text-anchor:middle;font-size:75%" + btitlestyle = "font-style:italic;font-size:150%;text-anchor:" + notestyle = "font-style:italic;text-anchor:" + datastyle = "text-anchor:end;fill:" + titlestyle = "text-anchor:start;font-size:300%" + labelstyle = "fill:black;baseline-shift:-25%" +) + +// a Barchart Defintion +// +// This is a note +// More expository text +// +// +// +// +// +// + +type Barchart struct { + Top int `xml:"top,attr"` + Left int `xml:"left,attr"` + Right int `xml:"right,attr"` + Title string `xml:"title,attr"` + Bdata []bdata `xml:"bdata"` + Note []note `xml:"note"` +} + +type bdata struct { + Title string `xml:"title,attr"` + Scale string `xml:"scale,attr"` + Color string `xml:"color,attr"` + Unit string `xml:"unit,attr"` + Showdata bool `xml:"showdata,attr"` + Showgrid bool `xml:"showgrid,attr"` + Samebar bool `xml:"samebar,attr"` + Bitem []bitem `xml:"bitem"` + Bstack []bstack `xml:"bstack"` + Note []note `xml:"note"` +} + +type bitem struct { + Name string `xml:"name,attr"` + Value float64 `xml:"value,attr"` + Color string `xml:"color,attr"` + Samebar bool `xml:"samebar,attr"` +} + +type bstack struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` + Color string `xml:"color,attr"` +} + +type note struct { + Text string `xml:",chardata"` +} + +// dobc does file i/o +func dobc(location string, s *svg.SVG) { + var f *os.File + var err error + if len(location) > 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err == nil { + readbc(f, s) + f.Close() + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// readbc reads and parses the XML specification +func readbc(r io.Reader, s *svg.SVG) { + var bc Barchart + if err := xml.NewDecoder(r).Decode(&bc); err == nil { + drawbc(bc, s) + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// drawbc draws the bar chart +func drawbc(bg Barchart, canvas *svg.SVG) { + + if bg.Left == 0 { + bg.Left = 250 + } + if bg.Right == 0 { + bg.Right = 50 + } + if bg.Top == 0 { + bg.Top = 50 + } + if len(title) > 0 { + bg.Title = title + } + labelimit = bg.Left/8 + cr := cornerRadius + maxwidth := width - (bg.Left + bg.Right) + x := bg.Left + y := bg.Top + sep := 10 + color := barcolor + scfmt := "%v" + canvas.Title(bg.Title) + + // for each bdata element... + for _, b := range bg.Bdata { + if trace { + fmt.Fprintf(os.Stderr, "# %s\n", b.Title) + } + // overide the color if specified + if len(b.Color) > 0 { + color = b.Color + } else { + color = barcolor + } + // extract the scale data from the XML attributes + // if not specified, compute the scale factors + sc := strings.Split(b.Scale, ",") + var scalemin, scalemax, scaleincr float64 + + if len(sc) != 3 { + if len(b.Bitem) > 0 { + scalemin, scalemax, scaleincr = scalevalues(b.Bitem) + } + + if len(b.Bstack) > 0 { + scalemin, scalemax, scaleincr = scalestack(b.Bstack) + } + } else { + scalemin, _ = strconv.ParseFloat(sc[0], 64) + scalemax, _ = strconv.ParseFloat(sc[1], 64) + scaleincr, _ = strconv.ParseFloat(sc[2], 64) + } + // label the graph + canvas.Text(x, y, b.Title, btitlestyle+anchor()) + + y += sep * 2 + chartop := y + + // draw the data items + canvas.Gstyle(datastyle + color) + + // stacked bars + for _, stack := range b.Bstack { + if trace { + fmt.Fprintf(os.Stderr, "%s~%s\n", stack.Value, stack.Name) + } + stackdata := stackvalues(stack.Value) + if len(stackdata) < 1 { + continue + } + sx := x + canvas.Text(x-sep, y+barheight/2, textlimit(stack.Name, labelimit), labelstyle) + barop := colorange(1.0, 0.3, len(stackdata)) + for ns, sd := range stackdata { + dw := vmap(sd, scalemin, scalemax, 0, float64(maxwidth)) + if len(stack.Color) > 0 { + canvas.Roundrect(sx, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill:%s;fill-opacity:%.2f", stack.Color, barop[ns])) + } else { + canvas.Roundrect(sx, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill-opacity:%.2f", barop[ns])) + } + + + if (showdata || b.Showdata) && sd > 0 { + var valuestyle = "fill-opacity:1;font-style:italic;font-size:75%;text-anchor:middle;baseline-shift:-25%;" + var ditem string + var datax int + if len(b.Unit) > 0 { + ditem = fmt.Sprintf(valformat+"%s", sd, b.Unit) + } else { + ditem = fmt.Sprintf(valformat, sd) + } + if len(inbar) > 0 { + valuestyle += inbar + } else { + valuestyle += "fill:black" + } + datax = sx + int(dw)/2 + canvas.Text(datax, y+barheight/2, ditem, valuestyle) + } + sx += int(dw) + } + y += barheight + gutter + } + + // plain bars + for _, d := range b.Bitem { + if trace { + fmt.Fprintf(os.Stderr, "%.2f~%s\n", d.Value, d.Name) + } + canvas.Text(x-sep, y+barheight/2, textlimit(d.Name, labelimit), labelstyle) + dw := vmap(d.Value, scalemin, scalemax, 0, float64(maxwidth)) + var barop float64 + if b.Samebar { + barop = 0.3 + } else { + barop = 1.0 + } + if len(d.Color) > 0 { + canvas.Roundrect(x, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill:%s;fill-opacity:%.2f", d.Color, barop)) + } else { + canvas.Roundrect(x, y, int(dw), barheight, cr, cr, fmt.Sprintf("fill-opacity:%.2f", barop)) + } + if showdata || b.Showdata { + var valuestyle = "fill-opacity:1;font-style:italic;font-size:75%;text-anchor:start;baseline-shift:-25%;" + var ditem string + var datax int + if len(b.Unit) > 0 { + ditem = fmt.Sprintf(valformat+"%s", d.Value, b.Unit) + } else { + ditem = fmt.Sprintf(valformat, d.Value) + } + if len(inbar) > 0 { + valuestyle += inbar + datax = x + fontsize/2 + } else { + valuestyle += "fill:black" + datax = x + int(dw) + fontsize/2 + } + canvas.Text(datax, y+barheight/2, ditem, valuestyle) + } + if !d.Samebar { + y += barheight + gutter + } + } + canvas.Gend() + + // draw the scale and borders + chartbot := y + gutter + if showgrid || b.Showgrid { + canvas.Line(x, chartop, x+maxwidth, chartop, borderstyle) // top border + canvas.Line(x, chartbot-gutter, x+maxwidth, chartbot-gutter, borderstyle) // bottom border + } + if showscale { + if scaleincr < 1 { + scfmt = "%.1f" + } else { + scfmt = "%0.f" + } + canvas.Gstyle(scalestyle) + for sc := scalemin; sc <= scalemax; sc += scaleincr { + scx := vmap(sc, scalemin, scalemax, 0, float64(maxwidth)) + canvas.Text(x+int(scx), chartbot+fontsize, fmt.Sprintf(scfmt, sc)) + if showgrid || b.Showgrid { + canvas.Line(x+int(scx), chartbot, x+int(scx), chartop, borderstyle) // grid line + } + } + canvas.Gend() + } + + // apply the note if present + if len(b.Note) > 0 { + canvas.Gstyle(notestyle + anchor()) + y += fontsize * 2 + leading := 3 + for _, note := range b.Note { + canvas.Text(bg.Left, y, note.Text) + y += fontsize + leading + } + canvas.Gend() + } + y += sep * 7 // advance vertically for the next chart + } + // if requested, place the title below the last chart + if showtitle && len(bg.Title) > 0 { + y += fontsize * 2 + canvas.Text(bg.Left, y, bg.Title, titlestyle) + } + // apply overall note if present + if len(bg.Note) > 0 { + canvas.Gstyle(notestyle + anchor()) + y += fontsize * 2 + leading := 3 + for _, note := range bg.Note { + canvas.Text(bg.Left, y, note.Text) + y += fontsize + leading + } + canvas.Gend() + } +} + +func anchor() string { + if endtitle { + return "end" + } + return "start" +} + +// vmap maps one interval to another +func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) +} + +// maxitem finds the maxima is a collection of bar items +func maxitem(data []bitem) float64 { + max := -math.SmallestNonzeroFloat64 + for _, d := range data { + if d.Value > max { + max = d.Value + } + } + return max +} + +// maxstack finds the maxima is a stack of bars +func maxstack(stacks []bstack) float64 { + max := -math.SmallestNonzeroFloat64 + for _, s := range stacks { + sv := stackvalues(s.Value) + sum := 0.0 + for _, d := range sv { + sum += d + } + if sum > max { + max = sum + } + } + return max +} + +// scale values returns the min, max, increment from a set of bar items +func scalevalues(data []bitem) (float64, float64, float64) { + var m, max, increment float64 + rui := 5 + m = maxitem(data) + max = roundup(m, 100) + if max > 2 { + increment = roundup(max/float64(rui), 10) + } else { + increment = 0.4 + } + return 0, max, increment +} + +// scalestack returns the min, max, increment from a stack of bars +func scalestack(data []bstack) (float64, float64, float64) { + var m, max, increment float64 + rui := 5 + m = maxstack(data) + max = roundup(m, 100) + if max > 2 { + increment = roundup(max/float64(rui), 10) + } else { + increment = 0.4 + } + return 0, max, increment +} + +// roundup rouds a floating point number up +func roundup(n float64, m int) float64 { + i := int(n) + if i <= 2 { + return 2 + } + for ; i%m != 0; i++ { + } + return float64(i) +} + +// stack value returns the values from the value string of a stack +func stackvalues(s string) []float64 { + v := strings.Split(s, "/") + if len(v) <= 0 { + return nil + } + vals := make([]float64, len(v)) + for i, x := range v { + f, err := strconv.ParseFloat(x, 64) + if err != nil { + vals[i] = 0 + } else { + vals[i] = f + } + } + return vals +} + + +// colorange evenly distributes opacity across a range of values +func colorange(start, end float64, n int) []float64 { + v := make([]float64, n) + v[0] = start + v[n-1] = end + if n == 2 { + return v + } + incr := (end-start)/float64(n-1) + for i:=1; i < n-1; i++ { + v[i] = v[i-1] + incr + } + return v +} + + +func textlimit(s string, n int) string { + l := len(s) + if l <= n { + return s + } + + return s[0:n-3]+"..." +} + +// init sets up the command flags +func init() { + flag.StringVar(&bgcolor, "bg", "white", "background color") + flag.StringVar(&barcolor, "bc", "rgb(200,200,200)", "bar color") + flag.StringVar(&valformat, "vfmt", "%v", "value format") + flag.IntVar(&width, "w", 1024, "width") + flag.IntVar(&height, "h", 800, "height") + flag.IntVar(&barheight, "bh", 20, "bar height") + flag.IntVar(&gutter, "g", 5, "gutter") + flag.IntVar(&cornerRadius, "cr", 0, "corner radius") + flag.IntVar(&fontsize, "f", 18, "fontsize (px)") + flag.BoolVar(&showscale, "showscale", true, "show scale") + flag.BoolVar(&showgrid, "showgrid", false, "show grid") + flag.BoolVar(&showdata, "showdata", false, "show data values") + flag.BoolVar(&showtitle, "showtitle", false, "show title") + flag.BoolVar(&endtitle, "endtitle", false, "align title to the end") + flag.BoolVar(&trace, "trace", false, "show name/value pairs") + flag.StringVar(&inbar, "inbar", "", "data in bar format") + flag.StringVar(&title, "t", "", "title") +} + +// for every input file (or stdin), draw a bar graph +// as specified by command flags +func main() { + flag.Parse() + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:"+bgcolor) + canvas.Gstyle(fmt.Sprintf(gstyle, fontsize)) + if len(flag.Args()) == 0 { + dobc("", canvas) + } else { + for _, f := range flag.Args() { + dobc(f, canvas) + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/benchviz/benchviz.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/benchviz/benchviz.go new file mode 100644 index 0000000..5f2bb07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/benchviz/benchviz.go @@ -0,0 +1,238 @@ +// benchviz: visualize benchmark data from benchcmp +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "io" + "math" + "os" + "strconv" + "strings" + + "github.com/ajstarks/svgo" +) + +// geometry defines the layout of the visualization +type geometry struct { + top, left, width, height, vwidth, vp, barHeight int + dolines, coldata bool + title, rcolor, scolor, style string + deltamax, speedupmax float64 +} + +// process reads the input and calls the visualization function +func process(canvas *svg.SVG, filename string, g geometry) int { + if filename == "" { + return g.visualize(canvas, filename, os.Stdin) + } + f, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return 0 + } + defer f.Close() + return g.visualize(canvas, filename, f) +} + +// vmap maps world to canvas coordinates +func vmap(value, low1, high1, low2, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) +} + +// visualize performs the visualization of the input, reading a line a time +func (g *geometry) visualize(canvas *svg.SVG, filename string, f io.Reader) int { + var ( + err error + line, vs, bmtitle string + dmin, dmax float64 + ) + + bh := g.barHeight + vizwidth := g.vwidth + vspacing := g.barHeight + (g.barHeight / 3) // vertical spacing + bmtype := "delta" + + in := bufio.NewReader(f) + canvas.Gstyle(fmt.Sprintf("font-size:%dpx;font-family:sans-serif", bh)) + if g.title == "" { + bmtitle = filename + } else { + bmtitle = g.title + } + canvas.Text(g.left, g.top, bmtitle, "font-size:150%") + + height := 0 + for x, y, nr := g.left+g.vp, g.top+vspacing, 0; err == nil; nr++ { + line, err = in.ReadString('\n') + fields := strings.Split(strings.TrimSpace(line), ` `) + + if len(fields) <= 1 || len(line) < 2 { + continue + } + name := fields[0] + value := fields[len(fields)-1] + if len(value) > 2 { + vs = value[:len(value)-1] + } + v, _ := strconv.ParseFloat(vs, 64) + av := math.Abs(v) + + switch { + case strings.HasPrefix(value, "delt"): + bmtype = "delta" + dmin = 0.0 + dmax = g.deltamax // 100.0 + y += vspacing * 2 + continue + + case strings.HasPrefix(value, "speed"): + bmtype = "speedup" + dmin = 0.0 + dmax = g.speedupmax // 10.0 + y += vspacing * 2 + continue + + case strings.HasPrefix(name, "#"): + y += vspacing + canvas.Text(g.left, y, line[1:], "font-style:italic;fill:gray") + continue + } + + bw := int(vmap(av, dmin, dmax, 0, float64(vizwidth))) + switch g.style { + case "bar": + g.bars(canvas, x, y, bw, bh, vspacing/2, bmtype, name, value, v) + case "inline": + g.inline(canvas, g.left, y, bw, bh, bmtype, name, value, v) + default: + g.bars(canvas, x, y, bw, bh, vspacing/2, bmtype, name, value, v) + } + y += vspacing + height = y + } + canvas.Gend() + return height +} + +// inline makes the inline style pf visualization +func (g *geometry) inline(canvas *svg.SVG, x, y, w, h int, bmtype, name, value string, v float64) { + var color string + switch bmtype { + case "delta": + if v > 0 { + color = g.rcolor + } else { + color = g.scolor + } + case "speedup": + if v < 1.0 { + color = g.rcolor + } else { + color = g.scolor + } + } + canvas.Text(x-10, y, value, "text-anchor:end") + canvas.Text(x, y, name) + canvas.Rect(x, y-h, w, h, "fill-opacity:0.3;fill:"+color) +} + +// bars creates barchart style visualization +func (g *geometry) bars(canvas *svg.SVG, x, y, w, h, vs int, bmtype, name, value string, v float64) { + canvas.Gstyle("font-style:italic;font-size:75%") + toffset := h / 4 + var tx int + var tstyle string + switch bmtype { + case "delta": + if v > 0 { + canvas.Rect(x-w, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.rcolor) + tx = x - w - toffset + tstyle = "text-anchor:end" + } else { + canvas.Rect(x, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.scolor) + tx = x + w + toffset + tstyle = "text-anchor:start" + } + case "speedup": + if v < 1.0 { + canvas.Rect(x-w, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.rcolor) + tx = x - w - toffset + tstyle = "text-anchor:end" + } else { + canvas.Rect(x, y-h/2, w, h, "fill-opacity:0.3;fill:"+g.scolor) + tx = x + w + toffset + tstyle = "text-anchor:start" + } + } + if g.coldata { + canvas.Text(x-toffset, y+toffset, value, "text-anchor:end") + } else { + canvas.Text(tx, y+toffset, value, tstyle) + } + canvas.Gend() + canvas.Text(g.left, y+(h/2), name, "text-anchor:start") + if g.dolines { + canvas.Line(g.left, y+vs, g.left+(g.width-g.left), y+vs, "stroke:lightgray;stroke-width:1") + } +} + +func main() { + var ( + width = flag.Int("w", 1024, "width") + top = flag.Int("top", 50, "top") + left = flag.Int("left", 100, "left margin") + vp = flag.Int("vp", 512, "visualization point") + vw = flag.Int("vw", 300, "visual area width") + bh = flag.Int("bh", 20, "bar height") + smax = flag.Float64("sm", 10, "maximum speedup") + dmax = flag.Float64("dm", 100, "maximum delta") + title = flag.String("title", "", "title") + speedcolor = flag.String("scolor", "green", "speedup color") + regresscolor = flag.String("rcolor", "red", "regression color") + style = flag.String("style", "bar", "set the style (bar or inline)") + lines = flag.Bool("line", false, "show lines between entries") + coldata = flag.Bool("col", false, "show data in a single column") + ) + flag.Parse() + + g := geometry{ + width: *width, + top: *top, + left: *left, + vp: *vp, + vwidth: *vw, + barHeight: *bh, + title: *title, + scolor: *speedcolor, + rcolor: *regresscolor, + style: *style, + dolines: *lines, + coldata: *coldata, + speedupmax: *smax, + deltamax: *dmax, + } + + // For every named file or stdin, render the SVG in memory, accumulating the height. + var b bytes.Buffer + canvas := svg.New(&b) + height := 0 + if len(flag.Args()) > 0 { + for _, f := range flag.Args() { + height = process(canvas, f, g) + g.top = height + 50 + } + } else { + height = process(canvas, "", g) + } + g.height = height + 15 + + // Write the rendered SVG to stdout + out := svg.New(os.Stdout) + out.Start(g.width, g.height) + out.Rect(0, 0, g.width, g.height, "fill:white;stroke-width:2px;stroke:lightgray") + b.WriteTo(os.Stdout) + out.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/bubtrail/bubtrail.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/bubtrail/bubtrail.go new file mode 100644 index 0000000..27b4fac --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/bubtrail/bubtrail.go @@ -0,0 +1,68 @@ +// bubtrail draws a randmonized trail of bubbles +// +build !appengine + +package main + + +import ( + "flag" + "fmt" + "math/rand" + "os" + "time" + + "github.com/ajstarks/svgo" +) + +var ( + width = 1200 + height = 600 + opacity = 0.5 + size = 40 + niter = 200 + canvas = svg.New(os.Stdout) +) + +func init() { + flag.IntVar(&size, "s", 40, "bubble size") + flag.IntVar(&niter, "n", 200, "number of iterations") + flag.Float64Var(&opacity, "o", 0.5, "opacity") + flag.Parse() + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) +} + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + +func random(howsmall, howbig int) int { + if howsmall >= howbig { + return howsmall + } + return rand.Intn(howbig-howsmall) + howsmall +} + +func main() { + var style string + + canvas.Start(width, height) + canvas.Title("Bubble Trail") + background(200) + canvas.Gstyle(fmt.Sprintf("fill-opacity:%.2f;stroke:none", opacity)) + for i := 0; i < niter; i++ { + x := random(0, width) + y := random(height/3, (height*2)/3) + r := random(0, 10000) + switch { + case r >= 0 && r <= 2500: + style = "fill:rgb(255,255,255)" + case r > 2500 && r <= 5000: + style = "fill:rgb(127,0,0)" + case r > 5000 && r <= 7500: + style = "fill:rgb(127,127,127)" + case r > 7500 && r <= 10000: + style = "fill:rgb(0,0,0)" + } + canvas.Circle(x, y, size, style) + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/bulletgraph/bulletgraph.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/bulletgraph/bulletgraph.go new file mode 100644 index 0000000..d4269e0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/bulletgraph/bulletgraph.go @@ -0,0 +1,233 @@ +// bulletgraph - bullet graphs +// (Design Specification http://www.perceptualedge.com/articles/misc/Bullet_Graph_Design_Spec.pdf) +// +build !appengine + +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/ajstarks/svgo" +) + +var ( + width, height, fontsize, barheight, gutter, circleradius int + bgcolor, barcolor, datacolor, compcolor, title, font string + showtitle, circlemark bool + gstyle = "font-family:'%s',sans-serif;font-size:%dpx" +) + +// a Bulletgraph Defintion +// +// This is a note +// More expository text +// +// +// +// +// +// + +// Bulletgraph is the top-level drawing +type Bulletgraph struct { + Top int `xml:"top,attr"` + Left int `xml:"left,attr"` + Right int `xml:"right,attr"` + Title string `xml:"title,attr"` + Bdata []bdata `xml:"bdata"` + Note []note `xml:"note"` +} + +type bdata struct { + Title string `xml:"title,attr"` + Subtitle string `xml:"subtitle,attr"` + Scale string `xml:"scale,attr"` + Qmeasure string `xml:"qmeasure,attr"` + Cmeasure float64 `xml:"cmeasure,attr"` + Measure float64 `xml:"measure,attr"` +} + +type note struct { + Text string `xml:",chardata"` +} + +// dobg does file i/o +func dobg(location string, s *svg.SVG) { + var f *os.File + var err error + if len(location) > 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err == nil { + readbg(f, s) + f.Close() + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// readbg reads and parses the XML specification +func readbg(r io.Reader, s *svg.SVG) { + var bg Bulletgraph + if err := xml.NewDecoder(r).Decode(&bg); err == nil { + drawbg(bg, s) + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// drawbg draws the bullet graph +func drawbg(bg Bulletgraph, canvas *svg.SVG) { + qmheight := barheight / 3 + + if bg.Left == 0 { + bg.Left = 250 + } + if bg.Right == 0 { + bg.Right = 50 + } + if bg.Top == 0 { + bg.Top = 50 + } + if len(title) > 0 { + bg.Title = title + } + + maxwidth := width - (bg.Left + bg.Right) + x := bg.Left + y := bg.Top + scalesep := 4 + tx := x - fontsize + + canvas.Title(bg.Title) + // for each bdata element... + for _, v := range bg.Bdata { + + // extract the data from the XML attributes + sc := strings.Split(v.Scale, ",") + qm := strings.Split(v.Qmeasure, ",") + + // you must have min,max,increment for the scale, at least one qualitative measure + if len(sc) != 3 || len(qm) < 1 { + continue + } + // get the qualitative measures + qmeasures := make([]float64, len(qm)) + for i, q := range qm { + qmeasures[i], _ = strconv.ParseFloat(q, 64) + } + scalemin, _ := strconv.ParseFloat(sc[0], 64) + scalemax, _ := strconv.ParseFloat(sc[1], 64) + scaleincr, _ := strconv.ParseFloat(sc[2], 64) + + // label the graph + canvas.Text(tx, y+(barheight/2), fmt.Sprintf("%s (%g)", v.Title, v.Measure), "text-anchor:end;font-weight:bold") + canvas.Text(tx, y+(barheight/2)+fontsize, v.Subtitle, "fill:darkgray;text-anchor:end;font-size:75%") + + // draw the scale + scfmt := "%g" + if fraction(scaleincr) > 0 { + scfmt = "%.1f" + } + canvas.Gstyle("text-anchor:middle;font-size:75%") + for sc := scalemin; sc <= scalemax; sc += scaleincr { + scx := vmap(sc, scalemin, scalemax, 0, float64(maxwidth)) + canvas.Text(x+int(scx), y+scalesep+barheight+fontsize/2, fmt.Sprintf(scfmt, sc)) + } + canvas.Gend() + + // draw the qualitative measures + canvas.Gstyle("fill-opacity:0.5;fill:" + barcolor) + canvas.Rect(x, y, maxwidth, barheight) + for _, q := range qmeasures { + qbarlength := vmap(q, scalemin, scalemax, 0, float64(maxwidth)) + canvas.Rect(x, y, int(qbarlength), barheight) + } + canvas.Gend() + + // draw the measure and the comparative measure + barlength := int(vmap(v.Measure, scalemin, scalemax, 0, float64(maxwidth))) + canvas.Rect(x, y+qmheight, barlength, qmheight, "fill:"+datacolor) + cmx := int(vmap(v.Cmeasure, scalemin, scalemax, 0, float64(maxwidth))) + if circlemark { + canvas.Circle(x+cmx, y+barheight/2, circleradius, "fill-opacity:0.3;fill:"+compcolor) + } else { + cbh := barheight / 4 + canvas.Line(x+cmx, y+cbh, x+cmx, y+barheight-cbh, "stroke-width:3;stroke:"+compcolor) + } + + y += barheight + gutter // adjust vertical position for the next iteration + } + // if requested, place the title below the last bar + if showtitle && len(bg.Title) > 0 { + y += fontsize * 2 + canvas.Text(bg.Left, y, bg.Title, "text-anchor:start;font-size:200%") + } + + if len(bg.Note) > 0 { + canvas.Gstyle("font-size:100%;text-anchor:start") + y += fontsize * 2 + leading := 3 + for _, note := range bg.Note { + canvas.Text(bg.Left, y, note.Text) + y += fontsize + leading + } + canvas.Gend() + } +} + +//vmap maps one interval to another +func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) +} + +// fraction returns the fractions portion of a floating point number +func fraction(n float64) float64 { + i := int(n) + return n - float64(i) +} + +// init sets up the command flags +func init() { + flag.StringVar(&bgcolor, "bg", "white", "background color") + flag.StringVar(&barcolor, "bc", "rgb(240,240,240)", "bar color") + flag.StringVar(&datacolor, "dc", "rgb(200,200,200)", "data color") + flag.StringVar(&compcolor, "cc", "rgb(127,0,0)", "comparative color") + flag.StringVar(&font, "font", "Calibri", "font") + flag.IntVar(&width, "w", 1024, "width") + flag.IntVar(&height, "h", 800, "height") + flag.IntVar(&barheight, "bh", 32, "bar height") + flag.IntVar(&circleradius, "cr", 8, "circle radius") + flag.IntVar(&gutter, "g", 36, "gutter") + flag.IntVar(&fontsize, "f", 18, "fontsize (px)") + flag.BoolVar(&circlemark, "circle", false, "circle mark") + flag.BoolVar(&showtitle, "showtitle", true, "show title") + flag.StringVar(&title, "t", "", "title") + flag.Parse() +} + +// for every input file (or stdin), draw a bullet graph +// as specified by command flags +func main() { + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:"+bgcolor) + canvas.Gstyle(fmt.Sprintf(gstyle, font, fontsize)) + if len(flag.Args()) == 0 { + dobg("", canvas) + } else { + for _, f := range flag.Args() { + dobg(f, canvas) + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/codepic/codepic.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/codepic/codepic.go new file mode 100644 index 0000000..79885ae --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/codepic/codepic.go @@ -0,0 +1,183 @@ +// codepic -- produce code+output sample suitable for slides +// +build !appengine + +package main + + +import ( + "bufio" + "encoding/xml" + "flag" + "fmt" + "io" + "os" + "strings" + + "github.com/ajstarks/svgo" +) + +var ( + canvas = svg.New(os.Stdout) + font string + codeframe, picframe, syntax bool + linespacing, fontsize, top, left, boxwidth, width, height int +) + +const ( + framestyle = "stroke:gray;stroke-dasharray:1,1;fill:none" + codefmt = "font-family:%s;font-size:%dpx" + labelfmt = "text-anchor:middle;" + codefmt + kwfmt = `%s` + commentfmt = `%s` + textfmt = "%s\n" + svgofmt = `font-weight="bold" fill="rgb(0,0,127)"` + gokwfmt = `font-style="italic" fill="rgb(127,0,0)"` +) + +// SVG is the incoming SVG file, capture everything into between and +// in the Doc string. This code will be translated to form the "picture" portion +type SVG struct { + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Doc string `xml:",innerxml"` +} + +var gokw = []string{ + "defer ", "go ", "range ", "chan ", " continue", "if ", "for ", "func ", + "uint8", "uint", "uint16", "uint32", "complex64", "complex128", " byte", "int8", "int16", "int32", + "int64", " int", "float64", "float32", " string", "import ", "const ", + "package ", "return", "var ", "type ", "switch ", "case ", "default:", +} + +var svgokw = []string{ + ".Start", ".Startview,", ".End", ".Script", ".Gstyle", ".Gtransform", ".Scale", ".Offcolor", + ".ScaleXY", ".SkewX", ".SkewY", ".SkewXY,", ".Rotate", ".TranslateRotate", ".RotateTranslate", ".Translate", + ".Group", ".Gid", ".Gend", ".ClipPath", ".ClipEnd", ".DefEnd", ".Def", ".Desc", ".Title", ".Linkf", + ".LinkEnd", ".Use", ".Mask", ".MaskEnd", ".Circle", ".Ellipse", ".Polygon", ".Rect", ".CenterRect", + ".Roundrect", ".Square", ".Path", ".Arc", ".Bezier", ".Qbez", ".Qbezier", ".Line", ".Polyline", ".Image", + ".Textpath", ".Textlines,", ".Text", ".RGBA", ".RGB", ".LinearGradient", ".RadialGradient", ".Grid", +} + +// codepic makes a code+picture SVG file, given a go source file +// and conventionally named output -- given .go, .svg +func codepic(filename string) { + var basename string + + bn := strings.Split(filename, ".") + if len(bn) > 0 { + basename = bn[0] + } else { + fmt.Fprintf(os.Stderr, "cannot get the basename for %s\n", filename) + return + } + canvas.Start(width, height) + canvas.Title(basename) + canvas.Rect(0, 0, width, height, "fill:white") + placepic(width/2, top, basename) + canvas.Gstyle(fmt.Sprintf(codefmt, font, fontsize)) + placecode(left+fontsize, top+fontsize*2, filename) + canvas.Gend() + canvas.End() +} + +// placecode places the code section on the left +func placecode(x, y int, filename string) { + var rerr error + var line string + var ic bool + f, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer f.Close() + in := bufio.NewReader(f) + + for xp := left + fontsize; rerr == nil; y += linespacing { + line, rerr = in.ReadString('\n') + if len(line) > 0 { + line, ic = svgtext(xp, y, line[0:len(line)-1]) + if !ic && syntax { + line = keyword(line, gokwfmt, gokw) + line = keyword(line, svgofmt, svgokw) + } + io.WriteString(canvas.Writer, line) + } + } + if codeframe { + canvas.Rect(top, left, left+boxwidth, y, framestyle) + } +} + +// keyword styles keywords in a line of code +func keyword(line string, style string, kw []string) string { + for _, k := range kw { + line = strings.Replace(line, k, fmt.Sprintf(kwfmt, style, k), 1) + } + return line +} + +// svgtext +func svgtext(x, y int, s string) (string, bool) { + var iscomment = false + s = strings.Replace(s, "&", "&", -1) + s = strings.Replace(s, "<", "<", -1) + s = strings.Replace(s, ">", ">", -1) + + if syntax { + i := strings.Index(s, "// ") + if i >= 0 { + iscomment = true + s = strings.Replace(s, s[i:], fmt.Sprintf(commentfmt, s[i:]), 1) + } + } + return fmt.Sprintf(textfmt, x, y, s), iscomment +} + +// placepic places the picture on the right +func placepic(x, y int, basename string) { + var s SVG + f, err := os.Open(basename + ".svg") + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer f.Close() + if err := xml.NewDecoder(f).Decode(&s); err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse (%v)\n", err) + return + } + canvas.Text(x, height-10, basename+".go", fmt.Sprintf(labelfmt, font, fontsize*2)) + canvas.Group(`clip-path="url(#pic)"`, fmt.Sprintf(`transform="translate(%d,%d)"`, x, y)) + canvas.ClipPath(`id="pic"`) + canvas.Rect(0, 0, s.Width, s.Height) + canvas.ClipEnd() + io.WriteString(canvas.Writer, s.Doc) + canvas.Gend() + if picframe { + canvas.Rect(x, y, s.Width, s.Height, framestyle) + } +} + +// init initializes flags +func init() { + flag.BoolVar(&codeframe, "codeframe", false, "frame the code") + flag.BoolVar(&picframe, "picframe", false, "frame the picture") + flag.BoolVar(&syntax, "syntax", false, "syntax coloring") + flag.IntVar(&width, "w", 1024, "width") + flag.IntVar(&height, "h", 768, "height") + flag.IntVar(&linespacing, "ls", 16, "linespacing") + flag.IntVar(&fontsize, "fs", 14, "fontsize") + flag.IntVar(&top, "top", 20, "top") + flag.IntVar(&left, "left", 20, "left") + flag.IntVar(&boxwidth, "boxwidth", 450, "boxwidth") + flag.StringVar(&font, "font", "Inconsolata", "font name") + flag.Parse() +} + +// for every file, make a code+pic SVG file +func main() { + for _, f := range flag.Args() { + codepic(f) + } +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/colortab.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/colortab.go new file mode 100644 index 0000000..5bfb6f0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/colortab.go @@ -0,0 +1,90 @@ +// colortab -- make a color/code placemat +// +build !appengine + +package main + + +import ( + "bufio" + "flag" + "fmt" + "os" + "strings" + + "github.com/ajstarks/svgo" +) + +func main() { + var ( + canvas = svg.New(os.Stdout) + filename = flag.String("f", "svgcolors.txt", "input file") + fontname = flag.String("font", "Calibri,sans-serif", "fontname") + outline = flag.Bool("o", false, "outline") + neg = flag.Bool("n", false, "negative") + showrgb = flag.Bool("rgb", false, "show RGB") + showcode = flag.Bool("showcode", true, "only show colors") + circsw = flag.Bool("circle", true, "circle swatch") + fontsize = flag.Int("fs", 12, "fontsize") + width = flag.Int("w", 1600, "width") + height = flag.Int("h", 900, "height") + rowsize = flag.Int("r", 32, "rowsize") + colw = flag.Int("c", 320, "column size") + swatch = flag.Int("s", 16, "swatch size") + gutter = flag.Int("g", 11, "gutter") + err error + colorfmt, tcolor, line string + ) + + flag.Parse() + f, oerr := os.Open(*filename) + if oerr != nil { + fmt.Fprintf(os.Stderr, "%v\n", oerr) + return + } + canvas.Start(*width, *height) + canvas.Title("SVG Color Table") + if *neg { + canvas.Rect(0, 0, *width, *height, "fill:black") + tcolor = "white" + } else { + canvas.Rect(0, 0, *width, *height, "fill:white") + tcolor = "black" + } + top := 32 + left := 32 + in := bufio.NewReader(f) + canvas.Gstyle(fmt.Sprintf("font-family:%s;font-size:%dpt;fill:%s", + *fontname, *fontsize, tcolor)) + for x, y, nr := left, top, 0; err == nil; nr++ { + line, err = in.ReadString('\n') + fields := strings.Split(strings.TrimSpace(line), "\t") + if nr%*rowsize == 0 && nr > 0 { + x += *colw + y = top + } + if len(fields) == 3 { + colorfmt = "fill:" + fields[1] + if *outline { + colorfmt = colorfmt + ";stroke-width:1;stroke:" + tcolor + } + if *circsw { + canvas.Circle(x, y, *swatch/2, colorfmt) + } else { + canvas.CenterRect(x, y, *swatch, *swatch, colorfmt) + } + canvas.Text(x+*swatch+*fontsize/2, y+(*swatch/4), fields[0], "stroke:none") + var label string + if *showcode { + if *showrgb { + label = fields[1] + } else { + label = fields[2] + } + canvas.Text(x+((*colw*4)/5), y+(*swatch/4), label, "text-anchor:end;fill:gray") + } + } + y += (*swatch + *gutter) + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/svgcolors.txt b/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/svgcolors.txt new file mode 100644 index 0000000..88ae603 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/colortab/svgcolors.txt @@ -0,0 +1,147 @@ +aliceblue #F0F8FF 240,248,255 +antiquewhite #FAEBD7 250,235,215 +aqua #00FFFF 0,255,25 +aquamarine #7FFFD4 127,255,21 +azure #F0FFFF 240,255,255 +beige #F5F5DC 245,245,220 +bisque #FFE4C4 255,228,196 +black #000000 0,0,0 +blanchedalmond #FFEBCD 255,235,205 +blue #0000FF 0,0,255 +blueviolet #8A2BE2 138,43,226 +brown #A52A2A 165,42,42 +burlywood #DEB887 222,184,135 +cadetblue #5F9EA0 95,158,160 +chartreuse #7FFF00 127,255,0 +chocolate #D2691E 210,105,30 +coral #FF7F50 255,127,80 +cornflowerblue #6495ED 100,149,237 +cornsilk #FFF8DC 255,248,220 +crimson #DC143C 220,20,60 +cyan #00FFFF 0,255,255 +darkblue #00008B 0,0,139 +darkcyan #008B8B 0,139,139 +darkgoldenrod #B8860B 184,134,11 +darkgray #A9A9A9 169,169,169 +darkgreen #006400 0,100,0 +darkgrey #A9A9A9 169,169,169 +darkkhaki #BDB76B 189,183,107 +darkmagenta #8B008B 139,0,139 +darkolivegreen #556B2F 85,107,47 +darkorange #FF8C00 255,140,0 +darkorchid #9932CC 153,50,204 +darkred #8B0000 139,0,0 +darksalmon #E9967A 233,150,122 +darkseagreen #8FBC8F 143,188,143 +darkslateblue #483D8B 72,61,139 +darkslategray #2F4F4F 47,79,79 +darkslategrey #2F4F4F 47,79,79 +darkturquoise #00CED1 0,206,209 +darkviolet #9400D3 148,0,211 +deeppink #FF1493 255,20,147 +deepskyblue #00BFFF 0,191,255 +dimgray #696969 105,105,105 +dimgrey #696969 105,105,105 +dodgerblue #1E90FF 30,144,255 +firebrick #B22222 178,34,34 +floralwhite #FFFAF0 255,250,240 +forestgreen #228B22 34,139,34 +fuchsia #FF00FF 255,0,255 +gainsboro #DCDCDC 220,220,220 +ghostwhite #F8F8FF 248,248,255 +gold #FFD700 255,215,0 +goldenrod #DAA520 218,165,32 +gray #808080 128,128,128 +green #008000 0,128,0 +greenyellow #ADFF2F 173,255,47 +grey #808080 128,128,128 +honeydew #F0FFF0 240,255,240 +hotpink #FF69B4 255,105,180 +indianred #CD5C5C 205,92,92 +indigo #4B0082 75,0,130 +ivory #FFFFF0 255,255,240 +khaki #F0E68C 240,230,140 +lavender #E6E6FA 230,230,250 +lavenderblush #FFF0F5 255,240,245 +lawngreen #7CFC00 124,252,0 +lemonchiffon #FFFACD 255,250,205 +lightblue #ADD8E6 173,216,230 +lightcoral #F08080 240,128,128 +lightcyan #E0FFFF 224,255,255 +lightgoldenrodyellow #FAFAD2 250,250,210 +lightgray #D3D3D3 211,211,211 +lightgreen #90EE90 144,238,144 +lightgrey #D3D3D3 211,211,211 +lightpink #FFB6C1 255,182,193 +lightsalmon #FFA07A 255,160,122 +lightseagreen #20B2AA 32,178,170 +lightskyblue #87CEFA 135,206,250 +lightslategray #778899 119,136,153 +lightslategrey #778899 119,136,153 +lightsteelblue #B0C4DE 176,196,222 +lightyellow #FFFFE0 255,255,224 +lime #00FF00 0,255,0 +limegreen #32CD32 50,205,50 +linen #FAF0E6 250,240,230 +magenta #FF00FF 255,0,255 +maroon #800000 128,0,0 +mediumaquamarine #66CDAA 102,205,170 +mediumblue #0000CD 0,0,205 +mediumorchid #BA55D3 186,85,211 +mediumpurple #9370DB 147,112,219 +mediumseagreen #3CB371 60,179,113 +mediumslateblue #7B68EE 123,104,238 +mediumspringgreen #00FA9A 0,250,154 +mediumturquoise #48D1CC 72,209,204 +mediumvioletred #C71585 199,21,133 +midnightblue #191970 25,25,112 +mintcream #F5FFFA 245,255,250 +mistyrose #FFE4E1 255,228,225 +moccasin #FFE4B5 255,228,181 +navajowhite #FFDEAD 255,222,173 +navy #000080 0,0,128 +oldlace #FDF5E6 253,245,230 +olive #808000 128,128,0 +olivedrab #6B8E23 107,142,35 +orange #FFA500 255,165,0 +orangered #FF4500 255,69,0 +orchid #DA70D6 218,112,214 +palegoldenrod #EEE8AA 238,232,170 +palegreen #98FB98 152,251,152 +paleturquoise #AFEEEE 175,238,238 +palevioletred #DB7093 219,112,147 +papayawhip #FFEFD5 255,239,213 +peachpuff #FFDAB9 255,218,185 +peru #CD853F 205,133,63 +pink #FFC0CB 255,192,203 +plum #DDA0DD 221,160,221 +powderblue #B0E0E6 176,224,230 +purple #800080 128,0,128 +red #FF0000 255,0,0 +rosybrown #BC8F8F 188,143,143 +royalblue #4169E1 65,105,225 +saddlebrown #8B4513 139,69,19 +salmon #FA8072 250,128,114 +sandybrown #F4A460 244,164,96 +seagreen #2E8B57 46,139,87 +seashell #FFF5EE 255,245,238 +sienna #A0522D 160,82,45 +silver #C0C0C0 192,192,192 +skyblue #87CEEB 135,206,235 +slateblue #6A5ACD 106,90,205 +slategray #708090 112,128,144 +slategrey #708090 112,128,144 +snow #FFFAFA 255,250,250 +springgreen #00FF7F 0,255,127 +steelblue #4682B4 70,130,180 +tan #D2B48C 210,180,140 +teal #008080 0,128,128 +thistle #D8BFD8 216,191,216 +tomato #FF6347 255,99,71 +turquoise #40E0D0 64,224,208 +violet #EE82EE 238,130,238 +wheat #F5DEB3 245,222,179 +white #FFFFFF 255,255,255 +whitesmoke #F5F5F5 245,245,245 +yellow #FFFF00 255,255,0 +yellowgreen #9ACD32 154,205,50 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/comps.xml b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/comps.xml new file mode 100644 index 0000000..178afc2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/comps.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/compx.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/compx.go new file mode 100644 index 0000000..c9c1891 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/compx.go @@ -0,0 +1,894 @@ +// compx: display components and connections on a grid, given a XML description +// +build !appengine + +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "github.com/ajstarks/svgo" + "io" + "math" + "os" + "strconv" + "strings" + "time" +) + +// Component XML structures +type Component struct { + Top int `xml:"top,attr"` + Left int `xml:"left,attr"` + Gutter int `xml:"gutter,attr"` + Gw int `xml:"gw,attr"` + Gh int `xml:"gh,attr"` + Gc string `xml:"gc,attr"` + Legend []legend `xml:"legend"` + Note []note `xml:"note"` + Group []group `xml:"group"` + Comp []comp `xml:"comp"` +} + +type group struct { + Brow int `xml:"brow,attr"` + Bcol int `xml:"bcol,attr"` + Erow int `xml:"erow,attr"` + Ecol int `xml:"ecol,attr"` + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Label string `xml:"label,attr"` + Color string `xml:"color,attr"` + Opacity float64 `xml:"opacity,attr"` +} + +type note struct { + Row int `xml:"row,attr"` + Col int `xml:"col,attr"` + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Size int `xml:"size,attr"` + Spacing int `xml:"spacing,attr"` + Align string `xml:"align,attr"` + Nitem []nitem `xml:"nitem"` +} + +type legend struct { + Title string `xml:"title,attr"` + Row int `xml:"row,attr"` + Col int `xml:"col,attr"` + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Size int `xml:"size,attr"` + Litem []litem `xml:"litem"` +} + +type comp struct { + Id string `xml:"id,attr"` + Col int `xml:"col,attr"` + Row int `xml:"row,attr"` + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Name string `xml:"name,attr"` + Os string `xml:"os,attr"` + Sw string `xml:"sw,attr"` + Color string `xml:"color,attr"` + Shape string `xml:"shape,attr"` + Image string `xml:"image,attr"` + Connect []Connect `xml:"connect"` +} + +type litem struct { + Color string `xml:"color,attr"` + Type string `xml:"type,attr"` + Label string `xml:",chardata"` +} + +type nitem struct { + Color string `xml:"color,attr"` + Align string `xml:"align,attr"` + Text string `xml:",chardata"` +} + +// Connect defines connections +type Connect struct { + Sloc string `xml:"sloc,attr"` + Dloc string `xml:"dloc,attr"` + Dest string `xml:"dest,attr"` + Mark string `xml:"mark,attr"` + Color string `xml:"color,attr"` + Dir string `xml:"dir,attr"` + Label string `xml:",chardata"` +} + +type gcomp struct { + x, y, w, h int +} + +var ( + width, height, fontscale int + linesize, labelfs, notchsize, groupmargin int + showtitle, showtimestamp, roundbox, arc, italiclabel bool + title, bgcolor, guide string + gridw = 0 + gridh = 0 + globalcolor = "black" + canvas = svg.New(os.Stdout) +) + +const ( + lcolor = "rgb(190,190,190)" + boxradius = 10 + lopacity = "1.0" + defcolor = "black" + linefmt = "stroke:%s;fill:none" + globalstyle = "font-family:Calibri,sans-serif;font-size:%dpx;fill:black;text-anchor:middle;stroke-linecap:round;stroke-width:%dpx;stroke-opacity:%s" + ltstyle = "text-anchor:%s;fill:black" + legendstyle = "text-anchor:start;fill:black;font-size:%dpx" + gridstyle = "fill:none; stroke:gray; stroke-opacity:0.3" + notefmt = "font-size:%dpx" + ntfmt = "text-anchor:%s;fill:%s" +) + +func background(fc string) { canvas.Rect(0, 0, width, height, "fill:"+fc) } + +// docomp does XML file processing +func docomp(location string) { + var f *os.File + var err error + if len(location) > 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + + if err == nil { + readcomp(f) + f.Close() + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// readcomp reads the XML into the component data structure +func readcomp(r io.Reader) { + var c Component + if err := xml.NewDecoder(r).Decode(&c); err == nil { + drawc(c) + } else { + fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err) + } +} + +// drawc interprets the compoment data structure, and displays it +func drawc(c Component) { + gridw = c.Gw + gridh = c.Gh + if len(c.Gc) == 0 { + globalcolor = c.Gc + } else { + globalcolor = c.Gc + } + + // Groups + for _, group := range c.Group { + dogroup(group, c.Top, c.Left, c.Gutter) + } + + // Legends + for _, leg := range c.Legend { + dolegend(leg, c.Top, c.Left, c.Gutter, labelfs, labelfs+4) + } + + // Notes + for _, note := range c.Note { + donote(note, c.Top, c.Left, c.Gutter) + } + + // Components + for _, x := range c.Comp { + for _, y := range x.Connect { + connect(gc(x, c.Top, c.Left, c.Gutter), y.Sloc, + lookup(y.Dest, c.Comp, c.Top, c.Left, c.Gutter), + y.Dloc, y.Label, y.Mark, y.Dir, y.Color) + } + display(x, c.Top, c.Left, c.Gutter) + } + + if len(guide) > 0 { + grid(c.Top, c.Left, c.Gutter) + } + if showtitle { + dotitle(c.Left, 30, title) + } + if showtimestamp { + timestamp(30) + } +} + +// lookup returns a graphic object given an id +func lookup(id string, c []comp, t, l, g int) gcomp { + var x gcomp + for _, v := range c { + if id == v.Id { + return gc(v, t, l, g) + } + } + return x +} + +// dotitle positions the title relative to the bottom of the drawing +func dotitle(left, offset int, t string) { + canvas.Text(left, height-offset, t, "font-size:200%;text-anchor:start") +} + +// timestamp draws a timestamp in the lower right of the drawing +func timestamp(offset int) { + t := time.Now() + canvas.Text(width-offset, height-offset, t.Format(time.ANSIC), "text-anchor:end") +} + +// grid displays a grid overlay, useful for determining optimal positioning +func grid(top, left, gutter int) { + gs := strings.SplitN(guide, `x`, 4) + if len(gs) != 4 { + return + } + w, _ := strconv.Atoi(gs[0]) + h, _ := strconv.Atoi(gs[1]) + nr, _ := strconv.Atoi(gs[2]) + nc, _ := strconv.Atoi(gs[3]) + canvas.Gstyle(gridstyle) + y := top + for r := 0; r < nr; r++ { + x := left + for c := 0; c < nc; c++ { + canvas.Rect(x, y, w, h) + canvas.Text(x+w/2, y+h/2, fmt.Sprintf("%d,%d", r, c), + "font-size:150%;fill:lightgray;stroke:none") + x += w + gutter + } + y += h + gutter + } + canvas.Gend() +} + +// dogroup displays a colored rectangular area +func dogroup(g group, top, left, gutter int) { + margin := groupmargin + bx := colx(g.Bcol, g.Width, gutter, left) + by := rowy(g.Brow, g.Height, gutter, top) + ex := colx(g.Ecol, g.Width, gutter, left) + ey := rowy(g.Erow, g.Height, gutter, top) + gw := (ex + g.Width) - bx + gh := (ey + g.Height) - by + var gop float64 + if g.Opacity <= 0 { + gop = 1.0 + } else { + gop = g.Opacity + } + canvas.Rect(bx-margin, by-margin, gw+margin*2, gh+margin*2, + fmt.Sprintf("fill-opacity:%.2f;fill:%s", gop, g.Color)) + if len(g.Label) > 0 { + canvas.Text(bx+gw/2, by+gh/2, g.Label, "fill:gray") + } +} + +// dolegend displays the legend +func dolegend(leg legend, top, left, gutter, fs, ls int) { + if leg.Size > 0 { + fs = leg.Size + ls = fs + 4 + } + fsh := fs / 2 + x := colx(leg.Col, leg.Width, gutter, left) + y := rowy(leg.Row, leg.Height, gutter, top) + + canvas.Gstyle(fmt.Sprintf(legendstyle, fs)) + for _, v := range leg.Litem { + if v.Type == "line" { + canvas.Rect(x, y+fs/4, fs, fs/4, "fill:"+v.Color) + } else { + canvas.Square(x, y, fs, "fill:"+v.Color) + } + canvas.Text(x+(fs*2), y+fsh, v.Label, "baseline-shift:-30%") + y += ls + } + canvas.Gend() +} + +// donote displays a note +func donote(n note, top, left, gutter int) { + var align, color string + + size := n.Size + ls := n.Spacing + x := colx(n.Col, n.Width, gutter, left) + y := rowy(n.Row, n.Height, gutter, top) + if n.Align == "middle" { + y += n.Height / 2 + } + if size <= 0 { + size = labelfs + } + if ls == 0 { + ls = size + 2 + } + xp := x + canvas.Gstyle(fmt.Sprintf(notefmt, size)) + for _, v := range n.Nitem { + switch v.Align { + case "left", "start", "begin": + align = "start" + xp = x + case "right", "end": + align = "end" + xp = x + n.Width + case "middle", "mid", "center": + align = "middle" + xp = x + (n.Width / 2) + default: + align = "start" + xp = x + } + if len(v.Color) == 0 { + color = "black" + } else { + color = v.Color + } + canvas.Text(xp, y, v.Text, fmt.Sprintf(ntfmt, align, color)) + y += ls + } + canvas.Gend() +} + +// gc computes the components coordinates +func gc(c comp, top, left, gutter int) gcomp { + var g gcomp + + // the object and grid dimensions equal, unless explicitly overridden + + if gridw > 0 && gridh > 0 { + g.x = colx(c.Col, gridw, gutter, left) + g.y = rowy(c.Row, gridh, gutter, top) + } else { + g.x = colx(c.Col, c.Width, gutter, left) + g.y = rowy(c.Row, c.Height, gutter, top) + } + g.w = c.Width + g.h = c.Height + return g +} + +// display a component in the context of the grid +func display(c comp, top, left, gutter int) { + g := gc(c, top, left, gutter) + component(g, c) +} + +// component positions and draws a components and its attributes +func component(g gcomp, c comp) { + x := g.x + y := g.y + w := g.w + h := g.h + fs := w / fontscale + fs2 := fs / 2 + w2 := w / 2 + h3 := h / 3 + + var boxcolor string + if len(c.Color) == 0 { + boxcolor = globalcolor + } else { + boxcolor = c.Color + } + rectstyle := fmt.Sprintf("stroke:%s;stroke-width:1;fill:%s", boxcolor, boxcolor) + + if len(c.Image) > 0 { + canvas.Image(x, y, w, h, c.Image) + if len(c.Name) > 0 { + canvas.Text(x+w2, y+h/3, c.Name, + fmt.Sprintf("font-size:%dpx;fill:%s;baseline-shift:50%%", fs, boxcolor)) + } + return + } + + if strings.HasPrefix(c.Shape, "#") { + uselibrary(x, y, w, h, c.Shape) + return + } + + switch c.Shape { + + case "mobile", "screen": + screen(x, y, w, h, 10, boxcolor, bgcolor) + canvas.Gstyle(fmt.Sprintf("font-size:%dpx", fs)) + canvas.Text(x+w/2, y+h/3, c.Name) + if len(c.Os) > 0 { + canvas.Text(x+w/2, y+h3+fs+2, c.Os, "font-size:60%") + } + if len(c.Sw) > 0 { + canvas.Text(x+w/2, y+h3+fs*2, c.Sw, "font-size:75%") + } + canvas.Gend() + + case "server": + server(x, y, w, h, 10, boxcolor, lcolor) + canvas.Text(x+w/2, y+h-10, c.Name, fmt.Sprintf("font-size:%dpx;fill:white", fs)) + + case "desktop": + l := h / 20 + desktop(x, y, w, h, l, boxcolor, bgcolor) + canvas.Gstyle(fmt.Sprintf("font-size:%dpx", fs)) + canvas.Text(x+w/2, y+h/3, c.Name) + if len(c.Os) > 0 { + canvas.Text(x+w/2, y+h3+fs+2, c.Os, "font-size:60%") + } + if len(c.Sw) > 0 { + canvas.Text(x+w/2, y+h3+fs*2, c.Sw, "font-size:75%") + } + canvas.Gend() + + case "message": + l := h / 20 + pmy := h / 8 + message(x, y, w, h, l, lcolor, boxcolor) + canvas.Gstyle(fmt.Sprintf("font-size:%dpx;fill:white", fs)) + if len(c.Os) > 0 { + canvas.Text(x+w2, y+pmy+fs, c.Os, "font-size:75%") + } + canvas.Text(x+w2, y+h-10, c.Name, fmt.Sprintf("font-size:%dpx;fill:%s", fs, bgcolor)) + canvas.Gend() + + case "cloud": + r := w / 3 + xc := (x + w/2) + r/4 + yc := y + h/2 + cloud(xc, yc, r, boxcolor) + canvas.Text(xc-(r/4), yc+r/2, c.Name, fmt.Sprintf("font-size:%dpx;fill:white", fs)) + + case "db": + cylinder(x, y+(h/4), w, h-(h/2), h/4, lcolor, boxcolor) + canvas.Text(x+w2, y+h3, c.Name, fmt.Sprintf("font-size:%dpx;fill:white", fs)) + if len(c.Sw) > 0 { + canvas.Text(x+w2, y+2*h3, c.Sw, "font-size:75%") + } + + case "folder": + l := w / 10 + folder(x, y, w, h, l, boxcolor, lcolor) + yp := y + h/2 + canvas.Gstyle(fmt.Sprintf("font-size:%dpx;fill:white", fs)) + canvas.Text(x+w/2, yp, c.Name) + if len(c.Os) > 0 { + canvas.Text(x+w/2, yp+fs+2, c.Os, "font-size:60%") + } + if len(c.Sw) > 0 { + canvas.Text(x+w/2, yp+fs*2, c.Sw, "font-size:75%") + } + canvas.Gend() + + case "face": + fr := (w / 4) - (fs / 2) // (h*3)/8 + face(x+w/2, y+h/2, fr, linesize, boxcolor, bgcolor) + canvas.Text(x+w/2, y+(h/2)+fr+15, c.Name, fmt.Sprintf("font-size:%dpx;fill:black", fs)) + + case "role": + role(x, y, w, h, boxcolor) + canvas.Text(x+w/2, y+h-5, c.Name, fmt.Sprintf("font-size:%dpx;fill:%s", fs, bgcolor)) + + case "eaec": + l := w / 10 + eaec(x, y, w, h, l, boxcolor, bgcolor) + canvas.Text(x+w/2, y+h-5, c.Name, fmt.Sprintf("font-size:%dpx;fill:%s", fs, bgcolor)) + + case "plain": + if roundbox { + canvas.Roundrect(x, y, w, h, fs2, fs2, rectstyle) + } else { + canvas.Rect(x, y, w, h, rectstyle) + } + canvas.Text(x+w2, y+(h/2), c.Name, fmt.Sprintf("font-size:%dpx;fill:white;baseline-shift:-25%%", fs)) + + default: + canvas.Rect(x, y, w, h3, rectstyle) + canvas.Rect(x, y+h3, w, h-h3, "fill:white;stroke-width:1;stroke:gray") + canvas.Gstyle(fmt.Sprintf("font-size:%dpx", fs)) + canvas.Text(x+w2, y+h3, c.Name, "fill:white;baseline-shift:50%") + canvas.Text(x+w2, y+h3+fs2, c.Os, "font-size:60%") + wordstack(x+w2, (y+h)-fs2, fs, strings.Split(c.Sw, `\n`), "fill-opacity:0.75;font-size:75%") + canvas.Gend() + } +} + +// uselibrary draws a previously defined object +func uselibrary(x, y, w, h int, name string) { + canvas.Use(x, y, name, fmt.Sprintf(`width="%d"`, w), fmt.Sprintf(`height="%d"`, h)) +} + +// Object functions +func cylinder(x, y, w, h, eh int, fill, tfill string) { + f := "fill:" + fill + tf := "fill:" + tfill + canvas.Rect(x, y, w, h, f) + canvas.Ellipse(x+w/2, y+h, w/2, eh, f) + canvas.Ellipse(x+w/2, y, w/2, eh, tf) +} + +// folder object +func folder(x, y, w, h, l int, bcolor, color string) { + nl := w / 10 + xl := x + nl + xw := x + w + yl := y + nl + yh := y + h + l2 := nl * 2 + l3 := nl * 3 + lh := nl / 2 + var ( + xn = []int{xl, xl + l2, xl + l3, xw, xw, xl} + yn = []int{y, y, y + lh, y + lh, yh, yh} + xf = []int{xw, xw - l, x, x + l} + yf = []int{yh, yl + lh, yl + lh, yh} + ) + canvas.Polygon(xn, yn, "fill:"+color) + canvas.Polygon(xf, yf, "fill:"+bcolor) +} + +// cloud object +func cloud(x, y, r int, style string) { + small := r / 2 + medium := (r * 6) / 10 + canvas.Gstyle("fill:" + style) + canvas.Circle(x, y, r) + canvas.Circle(x+r, y+small, small) + canvas.Circle(x-r-small, y+small, small) + canvas.Circle(x-r, y, medium) + canvas.Rect(x-r-small, y, r*2+small, r) + canvas.Gend() +} + +// message object +func message(x, y, w, h, l int, bcolor, scolor string) { + et := h / 3 + w2 := w / 2 + px := w / 8 + py := h / 8 + e1x := []int{x, x, x + w, x + w, x + w2, x} + e1y := []int{y + et, y + h, y + h, y + et, y + (et * 2), y + et} + e2x := []int{x, x + w2, x + w, x + w2, x} + e2y := []int{y + et, y, y + et, y + (et * 2), y + et} + + canvas.Polygon(e2x, e2y, "fill:"+bcolor) + canvas.Polygon(e1x, e1y, "fill:"+scolor) + canvas.Roundrect(x+px, y+py, w-(px*2), h-py, l, l, "fill:"+scolor) + canvas.Line(x, y+et, x+w2, y+(et*2), "stroke-width:1;stroke:"+bcolor) + canvas.Line(x+w, y+et, x+w2, y+(et*2), "stroke-width:1;stroke:"+bcolor) +} + +// eaec person object +func eaec(x, y, w, h, l int, scolor, bcolor string) { + wu := w / 8 + hu := h / 12 + wh := w / 2 + hh := h / 2 + sx := []int{x + wu*2, x + wu*6, x + wh} + sy := []int{y + hu*6, y + hu*6, y + hu*11} + tx := []int{x + wh, x + wh + wu, x + wh, x + wh - wu, x + wh} + ty := []int{y + hu*6, y + hu*7, y + hu*10, y + hu*7, y + hu*6} + + canvas.Ellipse(x+wh, y+hu*4, wu+wu/2, hu*2, "fill:"+bcolor) + canvas.Roundrect(x+wu, y+hh, w-wu*2, hu*6, l, l, "fill:"+bcolor) + canvas.Polygon(sx, sy, "fill:"+scolor) + canvas.Polygon(tx, ty, "fill:"+bcolor) +} + +// screen object +func screen(x, y, w, h, l int, bcolor, color string) { + canvas.Roundrect(x, y, w, h, l, l, "fill:"+bcolor) + canvas.Rect(x+l, y+l, w-(l*2), h-(l*2), "fill:"+color) +} + +// kb (keyboard) object +func kb(x, y, w, h, l int, color string) { + var xp = []int{x + l, x, x + w, x + w - l} + var yp = []int{y, y + h, y + h, y} + canvas.Polygon(xp, yp, "fill:"+color) +} + +// desktop object +func desktop(x, y, w, h, l int, bcolor, color string) { + screen(x, y, w, h-l*3, l, bcolor, color) + kb(x, y+h-(l*2), w+l, l*2, l*2, bcolor) +} + +// face object +func face(x, y, r, l int, color, fcolor string) { + fu := r / 10 // "face unit" + ep := 3 * fu + my := y + ep + canvas.Circle(x, y, r, fmt.Sprintf("fill:%s;stroke-width:%dpx;stroke:%s", fcolor, l, color)) + canvas.Circle(x+ep, y-ep, r/10, "fill:"+color) + canvas.Circle(x-ep, y-ep, r/10, "fill:"+color) + canvas.Qbez(x+ep, my, x, y+ep*2, x-ep, my, fmt.Sprintf("fill:%s;stroke-width:%dpx;stroke:%s", color, l, color)) +} + +// server object +func server(x, y, w, h, l int, bcolor, color string) { + var xp = []int{x + (l * 2), (x + w) - (l * 2), (x + w) - l, x + l} + var yp = []int{y, y, y + l, y + l} + canvas.Polygon(xp, yp, "fill:"+color) + canvas.Roundrect(x, y+l, w, h-l, 5, 5, "fill:"+bcolor) + canvas.Gstyle("stroke:" + color) + yl := y + (l / 2) + h/4 + spacing := w / 5 + for r := 0; r < 2; r++ { + xl := x + l + for c := 0; c < 2; c++ { + canvas.Line(xl, yl, xl+spacing, yl) + xl += spacing + 10 + } + yl += h / 4 + } + canvas.Gend() + canvas.Circle((x+w)-l, y+h/2, l/2, "fill:"+color) +} + +// role object +func role(x, y, w, h int, color string) { + hs := h / 20 + var xp = []int{x, x, x + w/3, x + w/2, x + (w / 2) + (w / 6), x + w, x + w} + var yp = []int{y + h, y + (16 * hs), y + (12 * hs), y + (14 * hs), y + (12 * hs), y + (16 * hs), y + h} + + // var xp = []int{x, x, x + w/2, x + w, x + w} + // var yp = []int{y + h, y + (h5 * 4), y + (h5 * 2), y + (h5 * 4), y + h} + canvas.Gstyle("fill:" + color) + canvas.Polygon(xp, yp) + canvas.Ellipse(x+w/2, (y + h/3), w/5, h/3) // "stroke:white;stroke-width:2") + canvas.Gend() +} + +// sloper computes the slope and r of a line +func sloper(x1, y1, x2, y2 int) (m, r float64) { + dy := float64(y1 - y2) + dx := float64(x1 - x2) + m = dy / dx + r = math.Atan2(dy, dx) * (180 / math.Pi) + return m, r +} + +// rowy computes the y position of a row +func rowy(n, h, g, t int) int { return t + (n * g) + (n * h) } + +// colx computes the x position of a column +func colx(n, w, g, l int) int { return l + (n * g) + (n * w) } + +// compass returns the coordinates of a compass point +func compass(g gcomp, point string) (cx, cy int, dir string) { + switch point { + case "nw": + cx, cy, dir = g.x, g.y, "r" + case "nnw": + cx, cy, dir = g.x+g.w/4, g.y, "d" + case "nne": + cx, cy, dir = (g.x+g.w)-(g.w/4), g.y, "d" + case "n": + cx, cy, dir = g.x+(g.w/2), g.y, "d" + case "ne": + cx, cy, dir = g.x+g.w, g.y, "l" + case "w": + cx, cy, dir = g.x, g.y+g.h/2, "r" + case "wnw": + cx, cy, dir = g.x, g.y+g.h/4, "r" + case "wsw": + cx, cy, dir = g.x, (g.y+g.h)-(g.h/4), "r" + case "ese": + cx, cy, dir = g.x+g.w, (g.y+g.h)-(g.h/4), "l" + case "ene": + cx, cy, dir = g.x+g.w, g.y+(g.h/4), "l" + case "c": + cx, cy, dir = g.x+(g.w/2), g.y+(g.h/2), "n" + case "e": + cx, cy, dir = g.x+g.w, g.y+(g.h/2), "l" + case "sw": + cx, cy, dir = g.x, g.y+g.h, "r" + case "ssw": + cx, cy, dir = g.x+(g.w/4), g.y+g.h, "u" + case "sse": + cx, cy, dir = (g.x+g.w)-(g.w/4), g.y+g.h, "u" + case "s": + cx, cy, dir = g.x+(g.w/2), g.y+g.h, "u" + case "se": + cx, cy, dir = g.x+g.w, g.y+g.h, "l" + } + return cx, cy, dir +} + +// connect two components +func connect(c1 gcomp, p1 string, c2 gcomp, p2 string, label string, mark string, dir string, color string) { + x1, y1, d1 := compass(c1, p1) + x2, y2, d2 := compass(c2, p2) + linelabel(x1, y1, x2, y2, label, mark, d1, d2, dir, color) +} + +// linestyle returns the style for lines +func linestyle(color string) string { + return fmt.Sprintf(linefmt, color) +} + +// linelabel determines the connection and arrow geometry +func linelabel(x1, y1, x2, y2 int, label string, mark string, d1 string, d2 string, dir string, color string) { + aw := linesize * 4 + ah := linesize * 3 + + if len(color) == 0 { + color = lcolor + } + switch mark { + case "b": + lx1, ly1 := arrow(x1, y1, aw, ah, d1, color) + lx2, ly2 := arrow(x2, y2, aw, ah, d2, color) + doline(lx1, ly1, lx2, ly2, linestyle(color), dir, label) + + case "s": + lx1, ly1 := arrow(x1, y1, aw, ah, d1, color) + doline(lx1, ly1, x2, y2, linestyle(color), dir, label) + + case "d": + lx2, ly2 := arrow(x2, y2, aw, ah, d2, color) + doline(x1, y1, lx2, ly2, linestyle(color), dir, label) + + default: + doline(x1, y1, x2, y2, linestyle(color), dir, label) + } +} + +// doline draws a line between to coordinates +func doline(x1, y1, x2, y2 int, style, direction, label string) { + var labelstyle string + var upflag bool + + if italiclabel { + labelstyle = "font-style:italic;" + } + + tadjust := 6 + mx := (x2 - x1) / 2 + my := (y2 - y1) / 2 + lx := x1 + mx + ly := y1 + my + m, _ := sloper(x1, y1, x2, y2) + hline := m == 0 + vline := m == math.Inf(-1) || m == math.Inf(1) + straight := hline || vline + + switch { + case m < 0: // upwards line + upflag = true + labelstyle += "text-anchor:end;" + lx -= tadjust + case hline: // horizontal line + labelstyle += "text-anchor:middle;baseline-shift:20%;" + ly -= tadjust + case m > 0: // downwards line + upflag = false + labelstyle += "text-anchor:start;" + lx += tadjust + } + if arc && !straight { + cx, cy := x1, y2 // initial control points + // fmt.Fprintf(os.Stderr, "%s slope = %.3f\n", label, m) + if upflag { + if direction == "ccw" { + cx, cy = x2, y1 + } else { + cx, cy = x1, y2 + } + } else { + if direction == "ccw" { + cx, cy = x1, y2 + } else { + cx, cy = x2, y1 + } + } + canvas.Qbez(x1, y1, cx, cy, x2, y2, style) + labelstyle += "text-anchor:middle" + canvas.Text(lx, ly, label, labelstyle) + } else { + canvas.Line(x1, y1, x2, y2, style) + canvas.Text(lx, ly, label, labelstyle) // midpoint + } +} + +// wordstack displays text in a left-justified stack +func wordstack(x, y, fs int, s []string, style ...string) { + ls := fs + 2 + for i := len(s); i > 0; i-- { + canvas.Text(x, y, s[i-1], style...) + y -= ls + } +} + +// arrow constructs line-ending arrows according to connecting points +func arrow(x, y, w, h int, dir string, color string) (xl, yl int) { + var xp = []int{x, x, x, x} + var yp = []int{y, y, y, y} + + n := notchsize + switch dir { + case "r": + xp[1] = x - w + yp[1] = y - h/2 + xp[2] = (x - w) + n + yp[2] = y + xp[3] = x - w + yp[3] = y + h/2 + xl, yl = xp[2], y + case "l": + xp[1] = x + w + yp[1] = y - h/2 + xp[2] = (x + w) - n + yp[2] = y + xp[3] = x + w + yp[3] = y + h/2 + xl, yl = xp[2], y + case "u": + xp[1] = x - w/2 + yp[1] = y + h + xp[2] = x + yp[2] = (y + h) - n + xp[3] = x + w/2 + yp[3] = y + h + xl, yl = x, yp[2] + case "d": + xp[1] = x - w/2 + yp[1] = y - h + xp[2] = x + yp[2] = (y - h) + n + xp[3] = x + w/2 + yp[3] = y - h + xl, yl = x, yp[2] + } + canvas.Polygon(xp, yp, "fill:"+color+";fill-opacity:"+lopacity) + return xl, yl +} + +// init processes command line arguments +func init() { + flag.IntVar(&width, "w", 1024, "width") + flag.IntVar(&height, "h", 768, "height") + flag.IntVar(&linesize, "l", 4, "line weight") + flag.IntVar(&labelfs, "lf", 16, "label font size (px)") + flag.IntVar(&fontscale, "f", 10, "font scaling factor") + flag.IntVar(¬chsize, "n", 0, "arrow notch size") + flag.IntVar(&groupmargin, "gm", 10, "group margin") + flag.StringVar(&bgcolor, "bg", "white", "background color") + flag.StringVar(&title, "t", "comp grid", "title") + flag.BoolVar(&italiclabel, "il", false, "italic labels") + flag.BoolVar(&showtitle, "showtitle", false, "Show the title") + flag.BoolVar(&showtimestamp, "time", false, "Show a timestamp") + flag.BoolVar(&roundbox, "roundbox", false, "make boxes round") + flag.BoolVar(&arc, "arc", false, "use arcs to connect") + flag.StringVar(&guide, "g", "", "grid guide: WxHxRxC") + flag.Parse() +} + +// for every file (or stdin) make a component diagram +func main() { + canvas.Start(width, height) + canvas.Title(title) + background(bgcolor) + canvas.Gstyle(fmt.Sprintf(globalstyle, labelfs, linesize, lopacity)) + + if len(flag.Args()) == 0 { + docomp("") + } else { + for _, f := range flag.Args() { + docomp(f) + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/testcomp.xml b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/testcomp.xml new file mode 100644 index 0000000..acbfb92 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/compx/testcomp.xml @@ -0,0 +1,69 @@ + + + + Component + Another Thing + + + Public + Confidential + Restricted + Highly Restricted + + + Note + This is the test pattern for compx + for best results, set the width to be + at least 1300 + + the management + + + Here is another Note + This one is bigger + + + a stupid note + you did not specify anything + + + + + + + + + + + + + + + [1] + [2] + [3] + North to East + + + [4] + [5] + [6] + [7] + [8] + West to North + + + [9] + [10] + [11] + South to West + + + [12] + [13] + [14] + [15] + [16] + East to South + + diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/doc.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/doc.go new file mode 100644 index 0000000..a6b22f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/doc.go @@ -0,0 +1,118 @@ +/* +Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (). +Output goes to the specified io.Writer. + +Supported SVG elements and functions + +Shapes, lines, text + + circle, ellipse, polygon, polyline, rect (including roundrects), line, text + +Paths + + general, arc, cubic and quadratic bezier paths, + +Image and Gradients + + image, linearGradient, radialGradient, + +Transforms + + translate, rotate, scale, skewX, skewY + +Filter Effects + + filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting, + feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight, + feSpecularLighting, feSpotLight,feTile, feTurbulence + + +Metadata elements + + desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, use + +Usage: (assuming GOPATH is set) + + go get github.com/ajstarks/svgo + go install github.com/ajstarks/svgo/... + + +You can use godoc to browse the documentation from the command line: + + $ godoc github.com/ajstarks/svgo + + +a minimal program, to generate SVG to standard output. + + package main + + import ( + "github.com/ajstarks/svgo" + "os" + ) + + func main() { + width := 500 + height := 500 + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Circle(width/2, height/2, 100) + canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white") + canvas.End() + } + +Drawing in a web server: (http://localhost:2003/circle) + + package main + + import ( + "log" + "github.com/ajstarks/svgo" + "net/http" + ) + + func main() { + http.Handle("/circle", http.HandlerFunc(circle)) + err := http.ListenAndServe(":2003", nil) + if err != nil { + log.Fatal("ListenAndServe:", err) + } + } + + func circle(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Circle(250, 250, 125, "fill:none;stroke:black") + s.End() + } + +Functions and types + +Many functions use x, y to specify an object's location, and w, h to specify the object's width and height. +Where applicable, a final optional argument specifies the style to be applied to the object. +The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a +series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: ) + +The Offcolor type: + + type Offcolor struct { + Offset uint8 + Color string + Opacity float + } + +is used to specify the offset, color, and opacity of stop colors in linear and radial gradients + +The Filterspec type: + + type Filterspec struct { + In string + In2 string + Result string + } + +is used to specify inputs and results for filter effects + +*/ +package svg diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/f50/f50.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/f50/f50.go new file mode 100644 index 0000000..0e14aa6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/f50/f50.go @@ -0,0 +1,121 @@ +// f50 -- given a search term, display 10x5 image grid, sorted by interestingness +// +build !appengine + +package main + + +import ( + "encoding/xml" + "fmt" + "net/http" + "net/url" + "os" + + "github.com/ajstarks/svgo" +) + +// FlickrResp defines the Flickr response +type FlickrResp struct { + Stat string `xml:"stat,attr"` + Photos Photos `xml:"photos"` +} + +// Photos defines a set of Flickr photos +type Photos struct { + Page string `xml:"page,attr"` + Pages string `xml:"pages,attr"` + Perpage string `xml:"perpage,attr"` + Total string `xml:"total,attr"` + Photo []Photo `xml:"photo"` +} + +// Photo defines a Flickr photo +type Photo struct { + Id string `xml:"id,attr"` + Owner string `xml:"owner,attr"` + Secret string `xml:"secret,attr"` + Server string `xml:"server,attr"` + Farm string `xml:"farm,attr"` + Title string `xml:"title,attr"` + Ispublic string `xml:"ispublic,attr"` + Isfriend string `xml:"isfriend,attr"` + IsFamily string `xml:"isfamily,attr"` +} + +var ( + width = 805 + height = 500 + canvas = svg.New(os.Stdout) +) + +const ( + apifmt = "https://api.flickr.com/services/rest/?method=%s&api_key=%s&%s=%s&per_page=50&sort=interestingness-desc" + urifmt = "http://farm%s.static.flickr.com/%s/%s.jpg" + apiKey = "YOURKEY" + textStyle = "font-family:Calibri,sans-serif; font-size:48px; fill:white; text-anchor:start" + imageWidth = 75 + imageHeight = 75 +) + +// FlickrAPI calls the API given a method with single name/value pair +func flickrAPI(method, name, value string) string { + return fmt.Sprintf(apifmt, method, apiKey, name, value) +} + +// makeURI converts the elements of a photo into a Flickr photo URI +func makeURI(p Photo, imsize string) string { + im := p.Id + "_" + p.Secret + + if len(imsize) > 0 { + im += "_" + imsize + } + return fmt.Sprintf(urifmt, p.Farm, p.Server, im) +} + +// imageGrid reads the response from Flickr, and creates a grid of images +func imageGrid(f FlickrResp, x, y, cols, gutter int, imgsize string) { + if f.Stat != "ok" { + fmt.Fprintf(os.Stderr, "Status: %v\n", f.Stat) + return + } + xpos := x + for i, p := range f.Photos.Photo { + if i%cols == 0 && i > 0 { + xpos = x + y += (imageHeight + gutter) + } + canvas.Link(makeURI(p, ""), p.Title) + canvas.Image(xpos, y, imageWidth, imageHeight, makeURI(p, "s")) + canvas.LinkEnd() + xpos += (imageWidth + gutter) + } +} + +// fs calls the Flickr API to perform a photo search +func fs(s string) { + var f FlickrResp + r, weberr := http.Get(flickrAPI("flickr.photos.search", "text", s)) + if weberr != nil { + fmt.Fprintf(os.Stderr, "%v\n", weberr) + return + } + defer r.Body.Close() + xmlerr := xml.NewDecoder(r.Body).Decode(&f) + if xmlerr != nil || r.StatusCode != http.StatusOK { + fmt.Fprintf(os.Stderr, "%v (status=%d)\n", xmlerr, r.StatusCode) + return + } + canvas.Title(s) + imageGrid(f, 5, 5, 10, 5, "s") + canvas.Text(20, height-40, s, textStyle) +} + +// for each search term on the commandline, create a photo grid +func main() { + for i := 1; i < len(os.Args); i++ { + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:black") + fs(url.QueryEscape(os.Args[i])) + canvas.End() + } +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/fe/fe.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/fe/fe.go new file mode 100644 index 0000000..1681192 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/fe/fe.go @@ -0,0 +1,53 @@ +// fe: SVG Filter Effect example from http://www.w3.org/TR/SVG/filters.html#AnExample +// +build !appengine + +package main + + +import ( + "github.com/ajstarks/svgo" + "os" +) + +func main() { + + canvas := svg.New(os.Stdout) + width := 410 + height := 120 + + canvas.Start(width, height) + canvas.Title(`SVGo Filter Example`) + canvas.Desc(`Combines multiple filter primitives to produce a 3D lighting effect`) + + gfs := svg.Filterspec{In: "SourceAlpha", Result: "blur"} + ofs := svg.Filterspec{In: "blur", Result: "offsetBlur"} + sfs := svg.Filterspec{In: "blur", Result: "specOut"} + cfs1 := svg.Filterspec{In: "specOut", In2: "SourceAlpha", Result: "specOut"} + cfs2 := svg.Filterspec{In: "SourceGraphic", In2: "specOut", Result: "litPaint"} + + // define the filters + canvas.Def() + canvas.Filter("myFilter") + canvas.FeGaussianBlur(gfs, 4, 4) + canvas.FeOffset(ofs, 4, 4) + canvas.FeSpecularLighting(sfs, 5, .75, 20, "#bbbbbb") + canvas.FePointLight(-5000, -10000, 20000) + canvas.FeSpecEnd() + canvas.FeComposite(cfs1, "in", 0, 0, 0, 0) + canvas.FeComposite(cfs2, "arithmetic", 0, 1, 1, 0) + canvas.FeMerge([]string{ofs.Result, cfs2.Result}) + canvas.Fend() + canvas.DefEnd() + + // specify the graphic + canvas.Gid("SVG") + canvas.Path("M50,90 C0,90 0,30 50,30 L150,30 C200,30 200,90 150,90 z", "fill:none;stroke:#D90000;stroke-width:10") + canvas.Path("M60,80 C30,80 30,40 60,40 L140,40 C170,40 170,80 140,80 z", "fill:#D90000") + canvas.Text(52, 76, "SVG", "fill:white;stroke:black;font-size:45;font-family:Verdana") + canvas.Gend() + + canvas.Rect(0, 0, width, height, "stroke:black;fill:white") + canvas.Use(0, 0, "#SVG") // plain graphic + canvas.Use(200, 0, "#SVG", `filter="url(#myFilter)"`) // filter applied + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/flower/flower.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/flower/flower.go new file mode 100644 index 0000000..63cbb75 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/flower/flower.go @@ -0,0 +1,80 @@ +// flower - draw random flowers, inspired by Evelyn Eastmond's DesignBlocks gererated "grain2" +// +build !appengine + +package main + + +import ( + "flag" + "fmt" + "math" + "math/rand" + "os" + "time" + + "github.com/ajstarks/svgo" +) + +var ( + canvas = svg.New(os.Stdout) + niter = flag.Int("n", 200, "number of iterations") + width = flag.Int("w", 500, "width") + height = flag.Int("h", 500, "height") + thickness = flag.Int("t", 10, "max petal thinkness") + np = flag.Int("p", 15, "max number of petals") + psize = flag.Int("s", 30, "max length of petals") + opacity = flag.Int("o", 50, "max opacity (10-100)") +) + +const flowerfmt = `stroke:rgb(%d,%d,%d); stroke-opacity:%.2f; stroke-width:%d` + +func radial(xp int, yp int, n int, l int, style ...string) { + var x, y, r, t, limit float64 + limit = 2.0 * math.Pi + r = float64(l) + canvas.Gstyle(style[0]) + for t = 0.0; t < limit; t += limit / float64(n) { + x = r * math.Cos(t) + y = r * math.Sin(t) + canvas.Line(xp, yp, xp+int(x), yp+int(y)) + } + canvas.Gend() +} + +func random(howsmall, howbig int) int { + if howsmall >= howbig { + return howsmall + } + return rand.Intn(howbig-howsmall) + howsmall +} + +func randrad(w int, h int, n int) { + var x, y, r, g, b, o, s, t, p int + for i := 0; i < n; i++ { + x = rand.Intn(w) + y = rand.Intn(h) + r = rand.Intn(255) + g = rand.Intn(255) + b = rand.Intn(255) + o = random(10, *opacity) + s = random(10, *psize) + t = random(2, *thickness) + p = random(10, *np) + radial(x, y, p, s, fmt.Sprintf(flowerfmt, r, g, b, float64(o)/100.0, t)) + } +} + +func background(v int) { canvas.Rect(0, 0, *width, *height, canvas.RGB(v, v, v)) } + +func init() { + flag.Parse() + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) +} + +func main() { + canvas.Start(*width, *height) + canvas.Title("Random Flowers") + background(255) + randrad(*width, *height, *niter) + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/fontcompare/fontcompare.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/fontcompare/fontcompare.go new file mode 100644 index 0000000..37049b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/fontcompare/fontcompare.go @@ -0,0 +1,53 @@ +// fontcompare: compare two fonts +// +build !appengine + +package main + + +import ( + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +var ( + canvas = svg.New(os.Stdout) + width = 1000 + height = 600 + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789(){}[],.:;-+*/\\&_^%$#@!~`'\"<>" + gstyle = "font-family:%s;font-size:%dpt;text-anchor:middle;fill:%s;fill-opacity:%.2f" +) + +func letters(top, left int, font, color string, opacity float32) { + rows := 7 + cols := 13 + glyph := 0 + fontsize := 32 + spacing := fontsize * 2 + x := left + y := top + canvas.Gstyle(fmt.Sprintf(gstyle, font, fontsize, color, opacity)) + for r := 0; r < rows; r++ { + for c := 0; c < cols; c++ { + canvas.Text(x, y, chars[glyph:glyph+1]) + glyph++ + x += spacing + } + x = left + y += spacing + } + canvas.Gend() +} + +func main() { + if len(os.Args) > 2 { + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:white") + canvas.Text(80, 540, os.Args[1], "font-size:14pt; fill:blue; font-family:"+os.Args[1]) + canvas.Text(80, 560, os.Args[2], "font-size:14pt; fill:red; font-family:"+os.Args[2]) + letters(100, 100, os.Args[1], "blue", 0.5) + letters(100, 100, os.Args[2], "red", 0.5) + canvas.End() + } +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/funnel/funnel.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/funnel/funnel.go new file mode 100644 index 0000000..2340055 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/funnel/funnel.go @@ -0,0 +1,30 @@ +// funnel draws a funnel-like shape +// +build !appengine + +package main + + +import ( + "os" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) +var width = 320 +var height = 480 + +func funnel(bg int, fg int, grid int, dim int) { + h := dim / 2 + canvas.Rect(0, 0, width, height, canvas.RGB(bg, bg, bg)) + for size := grid; size < width; size += grid { + canvas.Ellipse(h, size, size/2, size/2, canvas.RGBA(fg, fg, fg, 0.2)) + } +} + +func main() { + canvas.Start(width, height) + canvas.Title("Funnel") + funnel(0, 255, 25, width) + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/gophercolor128x128.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/gophercolor128x128.png new file mode 100644 index 0000000000000000000000000000000000000000..aa11cf2c65ca40829b6d2e9ff54a5a0945287f64 GIT binary patch literal 12425 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

5n^YlnFv2lK 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

5n^YlnFv2lK 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 1 { + image = os.Args[1] + } + canvas.Start(width, height) + canvas.Title("Image Fade") + opacity := 1.0 + for i := 0; i < width-128; i += 100 { + canvas.Image(i, 0, 128, 128, image, fmt.Sprintf("opacity:%.2f", opacity)) + opacity -= 0.10 + } + canvas.Grid(0, 0, width, height, 16, "stroke:gray; opacity:0.2") + + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/lewitt/lewitt.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/lewitt/lewitt.go new file mode 100644 index 0000000..b914f80 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/lewitt/lewitt.go @@ -0,0 +1,83 @@ +// lewitt: inspired by by Sol LeWitt's Wall Drawing 91: +// +build !appengine + +package main +// +// A six-inch (15 cm) grid covering the wall. +// Within each square, not straight lines from side to side, using +// red, yellow and blue pencils. Each square contains at least +// one line of each color. +// +// This version violates the original instructions in that straight lines +// as well as arcs are used + +import ( + "flag" + "fmt" + "math/rand" + "os" + "time" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +const tilestyle = `stroke-width:1; stroke:rgb(128,128,128); stroke-opacity:0.5; fill:white` +const penstyle = `stroke:rgb%s; fill:none; stroke-opacity:%.2f; stroke-width:%d` + +var width = 720 +var height = 720 + +var nlines = flag.Int("n", 20, "number of lines/square") +var nw = flag.Int("w", 3, "maximum pencil width") +var pencils = []string{"(250, 13, 44)", "(247, 212, 70)", "(52, 114, 245)"} + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + +func lewitt(x int, y int, gsize int, n int, w int) { + var x1, x2, y1, y2 int + var op float64 + canvas.Rect(x, y, gsize, gsize, tilestyle) + for i := 0; i < n; i++ { + choice := rand.Intn(len(pencils)) + op = float64(random(1, 10)) / 10.0 + x1 = random(x, x+gsize) + y1 = random(y, y+gsize) + x2 = random(x, x+gsize) + y2 = random(y, y+gsize) + if random(0, 100) > 50 { + canvas.Line(x1, y1, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w))) + } else { + canvas.Arc(x1, y1, gsize, gsize, 0, false, true, x2, y2, fmt.Sprintf(penstyle, pencils[choice], op, random(1, w))) + } + } +} + +func random(howsmall, howbig int) int { + if howsmall >= howbig { + return howsmall + } + return rand.Intn(howbig-howsmall) + howsmall +} + +func init() { + flag.Parse() + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) +} + +func main() { + + canvas.Start(width, height) + canvas.Title("Sol Lewitt's Wall Drawing 91") + background(255) + gsize := 120 + nc := width / gsize + nr := height / gsize + for cols := 0; cols < nc; cols++ { + for rows := 0; rows < nr; rows++ { + lewitt(cols*gsize, rows*gsize, gsize, *nlines, *nw) + } + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/ltr/ltr.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/ltr/ltr.go new file mode 100644 index 0000000..d58a9e1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/ltr/ltr.go @@ -0,0 +1,178 @@ +// ltr: Layer Tennis remixes + +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +var ( + canvas = svg.New(os.Stdout) + poster, opacity, row, col, offset bool + title string + width, height int +) + +const ( + stdwidth = 900 + stdheight = 280 + ni = 11 +) + +// imagefiles returns a list of files in the specifed directory +// or nil on error. Each file includes the prepended directory name +func imagefiles(directory string) []string { + f, ferr := os.Open(directory) + if ferr != nil { + return nil + } + defer f.Close() + files, derr := f.Readdir(-1) + if derr != nil || len(files) == 0 { + return nil + } + names := make([]string, len(files)) + for i, v := range files { + names[i] = directory + "/" + v.Name() + } + return names +} + +// ltposter creates poster style: a title, followed by a list +// of volleys +func ltposter(x, y, w, h int, f []string) { + canvas.Image(x, y, w*2, h*2, f[0]) // first file, assumed to be the banner + y = y + (h * 2) + for i := 1; i < len(f); i += 2 { + canvas.Image(x, y, w, h, f[i]) + canvas.Image(x+w, y, w, h, f[i+1]) + if i%2 == 1 { + y += h + } + } +} + +// ltcol creates a single column of volley images +func ltcol(x, y, w, h int, f []string) { + for i := 0; i < len(f); i++ { + canvas.Image(x, y, w, h, f[i]) + y += h + } +} + +// ltop creates a view with each volley stacked together with +// semi-transparent opacity +func ltop(x, y, w, h int, f []string) { + for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner + canvas.Image(x, y, w, h, f[i], "opacity:0.2") + } +} + +// ltrow creates a row-wise view of volley images. +func ltrow(x, y, w, h int, f []string) { + for i := 0; i < len(f); i++ { + canvas.Image(x, y, w, h, f[i]) + x += w + } +} + +// ltoffset creates a view where each volley is offset from its opposing volley. +func ltoffset(x, y, w, h int, f []string) { + for i := 1; i < len(f); i++ { // skip the first file, assumed to be the banner + + if i%2 == 0 { + x += w + } else { + x = 0 + } + canvas.Image(x, y, w, h, f[i]) + y += h + } +} + +// dotitle creates the title +func dotitle(s string) { + if len(title) > 0 { + canvas.Title(title) + } else { + canvas.Title(s) + } +} + +// init sets up the command line flags. +func init() { + flag.BoolVar(&poster, "poster", false, "poster style") + flag.BoolVar(&opacity, "opacity", false, "opacity style") + flag.BoolVar(&row, "row", false, "display is a single row") + flag.BoolVar(&col, "col", false, "display in a single column") + flag.BoolVar(&offset, "offset", false, "display in a row, even layers offset") + flag.IntVar(&width, "width", stdwidth, "image width") + flag.IntVar(&height, "height", stdheight, "image height") + flag.StringVar(&title, "title", "", "title") + flag.Parse() +} + +func main() { + x := 0 + y := 0 + nd := len(flag.Args()) + for i, dir := range flag.Args() { + filelist := imagefiles(dir) + if len(filelist) != ni || filelist == nil { + fmt.Fprintf(os.Stderr, "in the %s directory, need %d images, read %d\n", dir, ni, len(filelist)) + continue + } + switch { + + case opacity: + if i == 0 { + canvas.Start(width*nd, height*nd) + dotitle(dir) + } + ltop(x, y, width, height, filelist) + y += height + + case poster: + if i == 0 { + canvas.Start(width, ((height*(ni-1)/4)+height)*nd) + dotitle(dir) + } + ltposter(x, y, width/2, height/2, filelist) + y += (height * 3) + (height / 2) + + case col: + if i == 0 { + canvas.Start(width*nd, height*ni) + dotitle(dir) + } + ltcol(x, y, width, height, filelist) + x += width + + case row: + if i == 0 { + canvas.Start(width*ni, height*nd) + dotitle(dir) + } + ltrow(x, y, width, height, filelist) + y += height + + case offset: + n := ni - 1 + pw := width * 2 + ph := nd * (height * (n)) + if i == 0 { + canvas.Start(pw, ph) + canvas.Rect(0, 0, pw, ph, "fill:white") + dotitle(dir) + } + ltoffset(x, y, width, height, filelist) + y += n * height + + } + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/marker/marker.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/marker/marker.go new file mode 100644 index 0000000..c1d2904 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/marker/marker.go @@ -0,0 +1,40 @@ +// marker test +// +build !appengine + +package main + + +import ( + "github.com/ajstarks/svgo" + "os" +) + +func main() { + canvas := svg.New(os.Stdout) + canvas.Start(500, 500) + canvas.Title("Marker") + + canvas.Def() + canvas.Marker("dot", 5, 5, 8, 8) + canvas.Circle(5, 5, 3, "fill:black") + canvas.MarkerEnd() + + canvas.Marker("box", 5, 5, 8, 8) + canvas.CenterRect(5, 5, 6, 6, "fill:green") + canvas.MarkerEnd() + + canvas.Marker("arrow", 2, 6, 13, 13) + canvas.Path("M2,2 L2,11 L10,6 L2,2", "fill:blue") + canvas.MarkerEnd() + canvas.DefEnd() + + x := []int{100, 250, 100} + y := []int{100, 250, 400} + canvas.Polyline(x, y, + `fill="none"`, + `stroke="red"`, + `marker-start="url(#dot)"`, + `marker-mid="url(#arrow)"`, + `marker-end="url(#box)"`) + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/newsvg b/Godeps/_workspace/src/github.com/ajstarks/svgo/newsvg new file mode 100644 index 0000000..46c7099 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/newsvg @@ -0,0 +1,39 @@ +#!/bin/sh + +if test $# -lt 1 +then + echo "specify a file" + exit 2 +fi + +if test ! -f $1 +then +cat < $1 +package main + +import ( + "github.com/ajstarks/svgo" + "os" +) + +var ( + width = 500 + height = 500 + canvas = svg.New(os.Stdout) +) + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + + +func main() { + canvas.Start(width, height) + background(255) + + // your code here + + canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1") + canvas.End() +} +! +fi +$EDITOR $1 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/paths/paths.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/paths/paths.go new file mode 100644 index 0000000..5a9283a --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/paths/paths.go @@ -0,0 +1,31 @@ +// paths draws the W3C logo as a paths +// +build !appengine + +package main + +import ( + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +func w3c() { + w3path := `M36,5l12,41l12-41h33v4l-13,21c30,10,2,69-21,28l7-2c15,27,33,-22,3,-19v-4l12-20h-15l-17,59h-1l-13-42l-12,42h-1l-20-67h9l12,41l8-28l-4-13h9` + cpath := `M94,53c15,32,30,14,35,7l-1-7c-16,26-32,3-34,0M122,16c-10-21-34,0-21,30c-5-30 16,-38 23,-21l5-10l-2-9` + canvas.Path(w3path, "fill:#005A9C") + canvas.Path(cpath) +} + +func main() { + canvas.Startview(700, 200, 0, 0, 700, 200) + canvas.Title("Paths") + for i := 0; i < 5; i++ { + canvas.Gtransform(fmt.Sprintf("translate(%d,0)", i*130)) + w3c() + canvas.Gend() + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/pattern/pattern.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/pattern/pattern.go new file mode 100644 index 0000000..ac9111c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/pattern/pattern.go @@ -0,0 +1,33 @@ +// pattern: test the pattern function +package main + +import ( + "github.com/ajstarks/svgo" + "fmt" + "os" +) + +func main() { + canvas := svg.New(os.Stdout) + w, h := 500, 500 + pct := 5 + pw, ph := (w*pct)/100, (h*pct)/100 + canvas.Start(w, h) + + // define the pattern + canvas.Def() + canvas.Pattern("hatch", 0, 0, pw, ph, "user") + canvas.Gstyle("fill:none;stroke-width:1") + canvas.Path(fmt.Sprintf("M0,0 l%d,%d", pw, ph), "stroke:red") + canvas.Path(fmt.Sprintf("M%d,0 l-%d,%d", pw, pw, ph), "stroke:blue") + canvas.Gend() + canvas.PatternEnd() + canvas.DefEnd() + + // use the pattern + canvas.Gstyle("stroke:black; stroke-width:2") + canvas.Circle(w/2, h/2, h/8, "fill:url(#hatch)") + canvas.CenterRect((w*4)/5, h/2, h/4, h/4, "fill:url(#hatch)") + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/earth.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/earth.png new file mode 100644 index 0000000000000000000000000000000000000000..9e2bcac319853dd556565645e77f1f8990834ca6 GIT binary patch literal 97409 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 z zIeZ{UV8~Bg7LF6YL&$;3 z3?7;lLdZvn3=2-nOs}l0?H+JA+E_okSzDz#yZccftJ3-Me3dTXX-;ZPnFv6j2c{ z05=Viq3c4M6EWYohnH__XNQNYI}4n2`r5T(DuD`xYSpUcM~@x%^YKYdi6(od^o@Q! z3v9N4;=!^3`;Uv)LA0=qI2!7kfTDFVI&@+%7kDNhQ?>OCt5&XRZfW`Q%P&I%m9ep? zA~`X_p0JyE6laeErv8$ZnZ+Ir-yCb9AW>xIf?U3I z89~P%{%~hQeGfa8R-?h@2+ebI)6SePdH(qqe*4>(*+zEldJ2udPe1+S$dSV}H8*ze zejVFdzC3^FlKEZ6L0{hhf^tf2?18wnty}L*O;2Ny8x4ld8#kq=r(+fbyBHrI1sQ|* zibJihZ>B&?RMXVdP+eVvsj+N`n`zQdO-a?NqkVmdcIB+PjPy(rS*;e>#6gzAcd=2B zrFeN!3$*wLP#J-?VC5_LK#&<1?TVMkC07lObpV-lv*f?(ejMg zGkGC1==*wlXd=dh37BV*{r?kY5WxJsHv2bU_pKQ;qCxZ~00I!4L1rc7n3xdw+sNIY zJ$rh`_MH-cxrev7s&lfP;KjS| z*_xY|yKmpehYlWMJv2ABJod!HhYurTdkU-#6~D!c6Cj)X@sE3sojjG6mOgLZ{Ga^v z$%@Jw>Z>Xc<~26cz9Xf{IW6tIIme{Jo}K@R$fuLG;b;PLVrbdRaIhq zLRe_XAO849yeUS8)2*qg!>!Ir(YhoiCIkNg={FY0QGy2XO-@d^dhHtccW0*|IWYyU zDg42}zz~2!jXE0sBc*#bPd)+D#B38JUmw4&?HdWN35AyalkE3PM+t^7fntw zTs4Cm)5rBTc4Nx8F{JAV_bEZg$H!yVMOUw!IQb>nENnYnOpG!@cK$+%4i5EKRaG85bl~p0?jhwNXWDf8ihzJXs3!C0&!+)| z@jc}?%1N6RFI~22dxAEf7N z*RFke_AKXR8ZhC@m*wWJT#?b*(*5NZ#~lKaFh(UmD=UlCf;ok_>Wv#W1HccP%_XJ9 zglZ9y;UcjQPI}$CbwnCai0})%pugXw*BjV6@WC|EQ6<5Yu}*B4XYlvfuH8P$BB`ma zk^nj39qVXUJd zm?l(Qxz$w7XG4xzkNfTTXE2GsUMv5;W`zH8Cbx9$vpWNS`szQ(tb%W56803+^@#Xg z=!55TW|Iu};r=0nD3|0cLie2lmA<<>EW%~lN!;4X2m-N-*K|{R-H?@rIrTule$Yf? zuq#?6M#Dh3G7_Y7a>^+#JRmAOkhfIScR5^r2_+D8HV^f*b_}5Vf8xutdP9#|rK5wD za`A2J*4d|AkDWN-FAK3UufyRP6GzVvy{jf;ueq;p6xGd;U~hF4Wj04re5iTI*fnSq z`S_S^lsWq*p|-iY*VZ)CEEpCTOgfcEdYGOp*OvJ>6a1tSFh=_2VPJ^g3HC!pj6vVKcK$=`g4N-@}>fql}X5#9h|6!w;IX2qWrPpdzNJ94a zbZa7`JRyU7iy1Q_k%;I-9G|cS1_YBmkI_muG2YhPU^KMTNdr`mSz#T*znc%?_rHbM z&1Hvk*{xvEw=`Uy{P*wtPye0k#b0@z_uPEO-%o^G5Fgf1Tg$MahzJ$!-|=xtw4EYw zF*q=b0XqN6pVwqb%=N@yhM>({TtTi}q{>^hoSluC6%V%XsD1S79e&67(h zB%>*cdgl-^JE0!QSzB9cW>z|p?zQ4lf(;0n9-eMjuN3w4*wRvyaXl#YR8&;9v^3L) zLWCoe%E%hoWTRu^)6-((;}U}8@`Sh;-rL{TjYb}Jj8jJ^mt#Y)9bl0whU3IaKwqF> zG&OZr*VYkvLofnFgAj|GWJcrW&3A+=!)a3FQm8fR=B5@(Yw+~|i@^fPtEYZzCNNAtMcVCeEWa4VWY4aF`qMiOdU}|J1Hs__p-a3}%vpHo zumge)I!W;zVGtzOKl9EBUi^Rt-E8>m~KkvEdpWqa8hJt2>>YL@{xylcx_b zo(D=0X~T~Dcfc7((SodzG|oOYZ62}4#>L74y{QKP8`ak|MMWfrM}@+zBcH>XsbEnU zN6rH1kXAx?&oDMIXy#I0zI+K%%=YcuCdM6wmoH<=EQ3pziXdcyYI}K!65~UO9y!Pv z$5gl?bY7+o0jIi}%3g_09;%6Ubc~3jI@X~vVytA&hN$Mi?4V|)n657aBmP?u4# z=GLKsK8HiV=@>5|km|mnhrXo|-hA?}&FnvZ|294JUw_Si9EU_i4ap|t0WL%Zu9IW- zi05)|sya{)Rk{hgI3fdBp zC^SSK5~S$rF@3)OP-K*XekW9gq*6KDOO+}LuXE}0r2v_KYHA9;kdIoJy@*1dhu|KOtp_I`(2-U%EpXR$*56(<1_oGjau1&C& zX)q8rO<)tNWomHPQdLuP{d#dz3)_7^RCfeDyxm=uiXii_o%yqYfl{U+YBee#5vmM` zU+FtK$bN)!qO{~IO9QwhV@*xcv|!aZZhcx#>k?!c`11=N)r!Q~=uAY{7v zB=-g4Kw>-rq_U@*xzV_O=m~BB&wX7@ZLQ6{Jzat||J$s=n^yOq{M~;T;y3*T1H0v0 z<}_%I_?B;&>&d_Vg1`Qqegyo-lmq=F52UnMsStomiVMH{$WIE3E|7h(^KykpM$IiP z_2CiWB9Si*(GNfJ$a{O=ef^EyKm<%V4hdDbd%Dx+2?nH5X)Km8*krgXa;(#5zGR%j zh7I{;Wi@Pe>FF6QEyQ;I&4|9}yP~6G2rHQ$LPn1xVPI5qOB2VCy5csrUChw7?i$X4%j~73>-mhPtHfF5)~ZW z8`BuptX|U6(uubwNv8fnXfiO+M_|n`)~2RLVkf+p$uz(s;TM_AkD4zzfKC@q$pg4u zp$NyF51Yr@It)}VMPdnrIB?&H@Cb&NLYM2%H@E9n;Inq&Ju!E- zi<_p*U$J;jZI*&B@C{kjgwE`HynvU%mF+V2QLN!x`c1v?%n&1}tFP`gv?5hKHfEz$ zlLHpKKJhcoU=Bl^%dX~ney-1%)1c@2p#SUhU->4)DWMMH(onD$QCso=%Nw_i^chX& zM;>{iq`1=8Cm*_Ml=EU&U#~yox%ZHBvJZ^4o)yAkw%gU&)c)5#W zbg__>Fttck_?gq^FuV5l-ut$#t*mH<)2UKLl3>y60SNv5?>~%9gW#5zS83HcCKZK- zNS^y&&w<}D((lO8qd}p8n3_t(U|Ef0nw#P7jDVq!oxVwu$(9zrIsiVCCRjiJZIk&Sr<1&YB&C=HH` z2vHDhL|W>C1Eth`=4MhcwUu&;=8gMxz4 zCVt`h-$3wJDk4^_SS>X98oKejxPRzuS@Y5n$G}H&Qsf>snFtAW?sOj&H6^{LipvI+B0vv`!32}QBrc<$KP2O9i9-Ms)|g?&dv~vMF=(2 z*41p?x`q2eK18ZfR#j{@TLOHgrPoXIa+lLZj5q>lw8+Z`4laS+wX0Q-kL&7cxlh}- zZw-(Iojr4ovcjT83n|~>U#a(kINf>Y%1xU#U%7Ujb}7{783LqGh9a|9SJS~(0Fv6< ziy)^bw}DnG@_M#r?jPYJ_ZZv&J{?;9B>QwIsrzrP8Fdg65*{0yD3^yUn4dxBfPqSwsOLo-Jk{LX z#FbQpg=4|2Mh<`m8P+7$i9wT(V(9$NT?5c4D=WKc=y+&e%r)d%2pg2YKMod~#SdtRTp~!h^x09g0#ZhuJ}5{EXwyHyvTU>r!m$>HJUap7IcDHap?}8`XFdFF z`}xXW|Jt0s@~2z=@0NnurR3ZT+L=b0e5#Ai~=5I)&;yzI*9sEF{i zr1@7b*Nu;RghvJ1r-#E}+sl13G7>_9<^6pB8O#B1#P*6c@U{3a zIPNl;n4}#+T^A<@!*${NyVHy#b|14b&(574MxfwKF}y;kQ_?TPa%RCu2tpva!vPeA zVogw2=o>P7iNwmt;D)9SO7wIwLY4xr4VDKnyd^y?F+D>?*ovYT`CM9hIu$zr3P2t( zBI0AJSJ5#VCVY;K+Sx|PFTK3Pp2AqIpwB-)NMQ@XG?@M{5cys(3T%`L5y=_Z8{is6 zJKlx!=EJdh_6JmQ$YCJkgHl7fB2MEUd!47ZH_1APDqe|RFLy+IIQog{5gT$VD1w_k zy=IU<>L2WC?`Y~XbdpsHwkw#gaO(-ci2H?K@Q1hh_kSvf|Jm&3GOf9u7n+mj`r9p^ zpUZRK^2LASNnG{>L($#jzy8a5H(pZ3qkm5wo+2gE!Msi3^1O z&hSls;X+|*oG-uN9R+xClB98pd;rly+#zrXcwB31o5ehiTP0|Q_sknf=179^+yK2S zIkJeDm^coeezub*Pr@37h|CA_fDOli5#@6L@G)A1W(eVYF#uQtd?s(_I0+tDwfrwf z&!%+q4)VrPC^|&a4lao>8;P^RPxC;778)hB;i;){y`dfHy@3G`l?fMzAWiZ0$6PE? zK-I6m_`mSXoF4kW@LAvJEr3E~b?DZe`tpmEjMN=Fwx2qA4pp^aIjtcACmo#)iI|yA zAqaTl7zkClzGNP3(`>%*Ex{#-{}f8J2I&|Vd=F*?D#8`#HDJ#avk8KuwOUl+KwzOC z!2(1EMW>5ohzUM2IXM#Qa7js(SR#gD!iT^aVSmIB*&=&;`ztD{ykIZPF!0BtueXn3 za~#YpH7AHZXO4$Gch;H(G=Ojs^P+7`*kpMRR_Tliltb|9XyRBcAT7d}WOq+`AK1FU z3XtFj>Y|B@TC>$+;T+NZ?(WtPGHKJ;#z^n#m6VG(1?+Ea6IfS!g}!aB}V5k*oAz25HS+g9_xo=Bb?x> z&h9?~N15sjJ%7S0XP5L%o8}zJo@b_cE!tn;A;Q=wunXd`;gLaFU+MMl@9$;;UAJCO zh{@;<9P_R1VGg_frf}Y^Km2Ctd#=Czw?2<%IJg*)lZ&VG>9Z%fTR(dI(Xx^nh>;S+ zYDdQ_$}4JX>$x&Yr4j?h8xdfD!GU8S(sTFS_sn*Wn>#w&Qc@VoWW>7M-35yWIpT_6 zy>^wi1j=N=A#$9kGE@esnp7LWp`V$RQ-9FS;b`UZ0J<9qad4clpjg|4Y5?<4<`n%NyC(IT%VvzP@76Za^LY5Potn(9=lT~&URe9 z&^Qc}cn-sz%Mt&--k`}SKq!6vy_uUmH9mU%dWqRQ{`jMh>WxOMmZY028y+6PH3br) z3P29d9a1Q8-fryp&=2s?Bsz?SV0th22J5oftWYBad2nSIMLYPxWZ1Mw7?8B`^%J!2 z2n?JY2>S{jGC3*eQ!$}12!RAC{Zv#9xTDAf{XWv~|fAP2$tlp`)aIy*ZvH8sK4*ITU) z|Jl#}sk-{cnl%M3E*{t@A%|KGL3EK zc`-dJJt-+(ES44*7jvk9+^l@q`q1&Has%)a5)iv{8Nq^B_RvVEAQCHcn!-AqnZcUb ze=uADhc*eDA&!~nd6G-Y39_3p1&T9dNim;t*aBnM#Mz-j;joW%cDCX&CdOIGcnrG! z1=Yp>-x)M?B6ex^W}tX>LGHn%eWbLcu%*-Z*kcbfHUYP%R8mbqO-q%k z6(mB4RskA>MgjOn!thqMY<5+wi0~siI}A|*}dlL95*Wa2{BngWrqn{~8E(FvLX2u5PZ1R(@FKMduVP5&Lj(zgrp(vXF~1Mp{4F4D}*g&%(K$<-@m{e5uov0LP6X2|QKqN4B- z7%mr;a2Y?%wT_7u&<1*s$=R`Z(s`0@?#JX9Tq18~lM$E_kJ4J_?t*@g_mD6b)QNk{ zeWiXz+3e<>8!(x*6#?F{QtMsHb}XE)G)Rm@d8_)CIc%?@J1pHCzLm^OSUG*ECS8bUw!<47Ukf?Z*Ft^ zb$*tDbx|0kR#H!2d@oQCj3#$2MeTb`lq?TgscE?4UHpR`grVYmDcn%!BK>V_Y9fEdF%uCXgbrp*hF#ax z(%0YH)nn-D?nG`u1pe$UCeRz0umR;bhD(UF9`0Vd5_S%_bYF_NVIF#}gXFqMrGbM3qfRcqdq3D89-+BbT-Mam zwqjZK;Ls2>R5wpI+TyWV60;GDwV|y=6%_#zf;r;th(0j{O1=`YuwCL{@SX^XbK79m z;i$aG`euQCA?BGCj0aWY_7B?#yWK$6aDFD!AYK;C8(d=2A=C(=suEjAN5?X$wzJdF z*4FmF+q=`|I*aTM;JVtZ&9aRw+t^^U3=;+z!cs|q1gHTQ1CUjXdnV<8!64%WFMwBBvKDK*($#kJJKa~;_W0h36mSgo?UEn&?Wdo9 z`sx1n>C@+&KKz-Fv!sQD!E8+{y`8>1 z_mh;WHF`BgRRx8oeh=V-zJIy4bBjxOTn{3{9?7KLBzEHDY2RyxgJ1y*BgnuUTxJF4 z6may@Epi^S8Dh`&Joz$=TWe?nVFnf?po%{Dt2Br zGqXPN`GI|-!>66`*OXqAzG+pL(6p>2^RnP;YAGX`Z~pS1_{8{& zKc^|YbKr}^P7t((mdmy+ef^f)C>yCQs|%7{WL4kjX`O1a)1$zYIu3Ed)?w^Iu_eND zP4eD`dk?^HTpeB9RCe|u5EE><>U~r28*jXEad>3sF3z~NZQ0sSl!gu=e1tNNird=X z$J%q#M$W={Z{yDzSBZ^m4uN;fyiG8Xgn&-t2xH<_)HiF)5?)=jfE4rx!c<#Wxf47L z{+nzh%$D22PRJ@YQ8tt>B;TH+;gdgoX~YR6v;cO+D2ox`#O*MmP)HaC1;m+AM~Flz z*ewppVfwANemO93(0k|$AAInk#~=Qa&aNJ-S)OmRScrK{SfegZakw%!H~Gys$G$jp zU~K4Ym^im32bT5vvR3-q=xu7ETE)~&``!VXIvGUU+Y7VBiKeB}Z-2MP5jgDqw`|=( zeB1}o{ab(+pzqwVwWUz*>fLCKSPXp_Cwe_NMM??~*KOEzqKVXnIK0sO=}$eI2wqJ0 zf#}p^`ksh&2cMh?TbNe}z~ed;V%zQgs4PB`t&c9Wo+CcyITDkwX;b&6+d53&OpYi! zXwHBWZAFjS>scQnc*s98&2$)syvoo4EPt2*aQ9aQv{5AF3b^vPpw150QfK0GU=0z& z;?k&N)ni2#4sB(?MvI(1d)D~$v6yddM>wb`uagbQj)pJ*g2M?>hN1+kh-aUD_Vw3a z-?L}$Yp=b=-GXy7$VulNY~Y9u2VmmfT3dVx0p`ry3{jI+`{`3B2R{3Fa`Zy86-j5? z!d!?)>Vm7*jkV=Yy;G~0x@q4#Kw}RU7DCusB_6*p=JGg?5~}_Ee@ISF2-)Q5kzf6ycMdQ1;-@& zKZJ&K5p4m!0-+vdMg>^8E8qay1n&o9)d=Z8o-ERpSWykJ4s*_{CH`PNXo!S-jbJ56 zvOix)Cga-G)otzQT>{R;Xbk+*o<}fQ$#3KYsg_xiIv5EW7M*_Ol~>RLQXra3Mqn2& zkjDjSruJefUUVKKE_-p z1a@u>T`Z@-0Tz4X#wcK2-H8;RzFI0E-Umc~#4=5Wu5 z4Mvt$tfRYg%tlcMO}DjSWA!nJpxL|`A#@pxI8~$(w9q?~ESVQ>5;PS;wxTy4eBdE2 z${;`@j>1q;4)%6z>=GrRkgyA{cx$FQaZ;P7WPsm{9!y9Q&<3X=7!2`W3ZNg5B^(K* zB6tM7%YQL-@Blz+7)!i(Cb1{6GmU36PkRtQV<-|Gx*DiqN|4NjygK11=&{!Y_*6+5 zL6@V_uDOIHpDuoBV+`~0V#zQz7>cE7{2ME6ZDL_SXL{s_33A`yxieIib`Zu&q#Uwp z41TOr>)C9?+iHYc4$yj+{FvjzjN90O3__+t|SJwO&T zJ4~oqMB&I$%3$j=$VB83kKo%<@b)Ue00U{sEDD_M<{%dHN{MHqvLXoXR8hm249^reD6>_#sZd}VD9T-3xM6_ywKZ#~$!dn0nI+1cZ{-5>&uZeePq~$7 z4EUC;g%E(q9J4nAG0tn2FAkl2^NoMxsg#=)n(cFg16bA4Ti#MOK-YJ0SxXi=5P{LA5?cCD)ww4_+UiVYy5F6$=6kh({&bTu_n~}fCux$ zDMn~q4O)LD2LmL?Z)#~*@j!UScTo1=zpe1Fza+urZie1+fyG>`3fNH;gR^S!cAOL@ z-pEL-&7v0Pn%bDPkiBi=W-9&h(Mv)9F=NQDkx95c)x_YUxy%=psjDzEdugUPG`~1b zRYD$PGCOzX=x6))o;*HKUYuocwG`qaRrWPLLZ2k2sXm%xuF?C~{9W7Qe>f$2e=T9n zi^8?O;P>+9pTC#5nhNVI2S(ZFtYa9+?fdP2z4!iy&We2GffpU^y1sYarO8ucm(F%} zv5mr;UXZ>et2P*QD*%@B=12)pMHp-2G4pO?j2s&sOV43GpJ{$q6 zhUG||5+f76)P>|Ehj@R=lOA-r{J3)5 za`m`K${$y3OumIf$t*SMsC;$vI0=RUrWrlub*2>aLvRJHt++4Q1j7<-7D4!F2l)fy zVt!$1K6a&FnvfZT{pHbLn!4N3?geLe2Qa;s5x}zn?!n^VCyM5PMjUCvjHWrVX2&9z+=JG>pxgHbVol zO5A|L>ieL3WWqKCg(?oQ2@<;;UN+hxMq=U(!g2$AA%qo4vO@~)8TfU)3M5VdWd#02 ziA=+U&nR&~(|rYpIh#7K@YA@ODjV&wcrsrBJCBPiukQL5^$8>guB(*W;xBCCd`IFR z0-Y+OY_=JBBq%mRU%ItOxj!zl)}a5^Pmp*s#|nJCSU@h0)%2YYty+_ii6&sm~jjBnf6>jVZo zo(-+~lYDC9#$IF;>#B!BrjRCU;79<9qaP7M3oJNBWlhac0td_~Swh>rUFZq zL+^k1?wOMZ%eGE2;SW7yb7%-{xJq9Uv(8$=+H%(~`KARl&QWKOz}hs_(b39=PFE3@ zH7&Pt;$w@`^xUcUKls)D{U1O4z@Po-u1BY5FR&=0IONf1*IoDUP)|DC*1?s0_>Rx) zEevKzfhOx}tcAb81dUAMeRV2BB6JWGuqRfHY2C%h-R^{7loQC}A}-{9(FZx6(kkf* zzClXmQLH>U7)92M8Q;Muuizd@HE(!}ClukDOE6KWn>aTO28A{W~#i=Wks zc?(eHbfvkQ<6E}fF+6m>RN`rHc<-Kn`|RVrrQ)<%fi%wei?OkA*-8WL524~(LIcqM zOKVXR;t0?{jY|$jd;s0{aV*Z`Dd&57d51Comd>9&@y@ZTR9S zWY{c?TOJv^*wfV$qJxm@hF(M82f@}|OLhU8U_&|v1W=+vfz)O4kdjSJv=}+jC?Ue* zs3Vx>^d|ygAp#)P4#E$DmA{NsuYkgg?_9W|@=qBmFG@naRG+IZ0qF=ac;qB4K`T@F zF-I6+SP18rmY1A;5#J(b`nup){GsLSknl*}3(Z(BAA+v)c|A3C;iG-;?S22(lM^GM zd=2YvipG$m`y%<(SrAdx5Lm1wtTB7tqP@NDy;?W&+M>pt_=Z}c-V6Rf{(LK6^W&8^ zs#SiyG~J0RBgwioM7+&bfwo9!_$G=KCcE}W+7350J0>l>K`iWg_0?be)yuDTb#GuT zJv=hFeP{o}fBfLQZA444*f%i9&_DqB({03Qf^^dWj#+JKD?$%9-((x23XhEP;8@_m zh48>r@#2*rb3hK1L}=GnLKU^riBJM=!88YN;wmmBzss}vr)w@ph7^(4=n8AQc&c2W z2~UMYoxRQ3U49yUtSwMtkoBO79VLL z%PKwZ){a$RyfLbwiDDdyuRd5Te0uwV#;Sm>g)}?Gr$P z7v}iI;-s&Gt%xB3I$)PW#uGMy0jeRF;1q^$3IQYa3|OauB!Z~s^3%Bm!3p@O9N7R) zw!qL8%!!{05h&^Mvr%$$lp{#T@0wp^gJ1#|#MZ!;J~me#9~*HJXRFnNQDKqC%?<};V({tehM$A#$>}-cFoIZB+(AQsmSqym`ZOUa9 zc@;Pb@np@=0Cde}`M)RX@0t&r!=>{O*<4sRJyXI8clF*z?(w&O^LLvaxVdFB5ry3- zwt3ogjq^o@Gv>gH8xCikKnG}!UWgr2j51@#f7sN=mQv;yO<_nEnq*M(NLsK4ffRu@ zm=n)Iu`mb0366n&=}}1LiP>+*w0?P6nZL**5+U1FP^P$m5AxF>l0i=8Anwu`lg4B~ zNHK4pPSFk*`psZC7N;2RANk_og>$F82Pa}&G8ULAg?F3PhX$Zm-&Wr$XYT%Mi|Fm* z{(^Qc5tg)ysxE6b+_vS}XMg_O^Dp1A<9@sQ7D_Fph4R$wS0LN!n9hVwt0_zX&@|1@ z=EBY2fXGP(nO(3PVOJfD0jSB4!3}D$-b#Kqq-4Oks~kB+TqU~ZkRGf6SBhK8l-V_u z<|$O6oFmvF3=;m{P0W%Kf-KWFwZvw}*zrIh(L^lc*pySe@*Vi}Jr9~yl*z#WPC$MF zl%^PrYX}WMuc5KNP1bsV=5Y>wP$X(w&_a)KoHAQfaX?UtE%)m>yKjH_m0vvb%nOdS zSSvC+&>6Q9 zUGS4V&%(1nW3Ntu?h~gEA3t%-vF9TfDN2^wY0k?b#^Z^=75DBN@ zLL5BXL_%Rpl->sn4mKcZxqWIrpPkTb?-Sm%ZTrq2J^JY5_uv0B+p%`-yd!*l@Y%uB zE)!%BT%5{i_ADr?M#f)pI!-ivcH)qLcKotRaH=^L*6c6QT=wBuUZ3N3b>(5AO@p|oTB zJq(`E?j76j?CaaiddY?{fbMAD9Jmg)A?Pu2VGdzVl>+!?1Ov2CXyJ7{0!(A>si|qF z37AiQd5Djw;o6QfP#65#}566@M9P_9jD)ZH7Zy^!M#Kvx>1UaMW%WYxC22_hDG z`71L1#B&Q!zJ!>Iv;dP3y0?@o7FeChmdG{|DTqQ#I|W92yYr3k8@LWm=tNlCV>SY> zS925@%|PrBTN-J$B0x;3lk4EhbQ9r5$H^{m9HIG3fhAlew({SVJ#uZl0q9CQ-O7N@ zO;Blp#LEWX5ZotmM4(sP29e-J{am4mq=SH)ZCsVW+&VF}zu~V?(3{u|w7kQd513H(Muer9>@@35NTMf{cTPHW@Eqeu{DjISmU)Kej zfpH)(&TV5!7v@iRN=g=o&3TYg((;bR8eh@7zEA#mB%6Jr;9)gwUzep?FsP7M0bf9_ zJYRBVFd5%vF&}A%ly#15NuZ3^#rz;c>^$JhFW1AfIYa)L%gpgx`P$&2E4?54mshA- z0Bwct5;x}@fyKxZc*^L>E4h5vHOn#hvZ$uW7tC~=)rn4rI0_gNJe4OUdup-X$ zhz#V}p5l@rkF*#bc`jJtJ@Q*}fsr!}xa!Y%WBP_$jY2m7oq-YC|0=Po|7&S>8lMm= zxwICm$hL9OQX!9qB>p89*)^OSE-~G#kX3n-A`edtVTogjr=mnZBPDj>LH#QNhVLQ-Cmy56e}R2WO}d(2R#Ycfzva7sNoy0y*@4QKWf3mt&lA;yZ`_I07*qoM6N<$f-sX=xGw^L2goRjpdJ>R+o?t*Y zTRwg5!^@@RrTlw~VYam7-$R6l=jS1ae-~tZ>!SZIXzV-Qgv)olhW9IY6A3)*fPWKm z?>~MR^<2^#J)DMKdVkP-OOU;&q*n$#`{jeX=%LcNHIxM{8>n#i1^PqbGdTEG=zrY|Jh% zrSw&bJg&2gOY|?!rYC2|r^jbUCx>Sz=VuqE;@= z^!`!)+dJ5Ey||ohZmxs-#aRYN2E_8xim{2>(&+RYKVZK!9xbh`j%OFs^NUwC=)3>+ zdmYdq+u`i&ta}^21?m|zU3Yi3R#ukRC+jO~K(Y#W05_u<3z*{mTN$lL0m-%t6pvki z?iD?~I+=i9Y0}}^8v6X~OwRFWr8I6)Um6=E^t76!8$7P!W{K(L#aF-j)q~@s0Xo#1?B>3a>+-*bR&)V4Xn7ND5RB8a&O!K+uoWHMP9 zk5<_o0L8orTt^qP{5QRvN&%ViXuK>||B1(zr8-C83FyPMpt!U&ozc@BcWwPG1q5A} zP-Kjt?Qg}m);AyM|I1N7J)cdlU%!5KetvQc)W?Sh2Y2q?y?yIebaRsk?|3QnsC7Yx{r&yU@2M15x2n6im>LXtZnmYMAbj(i-@J9} z*6llcA-=Sx0}){Mcjy1Hgof#8da zi+Y;YA0NN)!VAwl^UT4~iBc<*<>T|&gNh}6w}%qzoO5}ld};YyedHvW~QKS$B;0zjufbfha>n3Y@SfuxkUYPp)jsuB>E*@ZRb8(NH1|SnW1necRrun|x1W9C z`ENe+oO(?O;C^<-NqAA`wwr*q8?4K0_p1Hk^#MH$f=Kl$Xa-N>t62HY-CpJGJIBTU zkD4vyO2g`}Ay9MrlJ{VKzqGP8VIuGB>};;Ba)s$^umh%s)BxQpAf}Q%?ewMDbchtJ z&>ZqZL@VREPyXuY<(0r1G+Z06uFXl)?6?g5)F|YkSm^VP$7eCu=RwRouB_TzTQ|FE zd*5nN-}a-fWodbQPHV4>kB?6n**qjiXYu5*TdaRi&k5rc$;w@~m0z53R#nub$O4YMwv7^39lAiF+XQHeHzOJy z#Aj+G8;jjF)W#v9IXwXP&&>AmDBy4QO#tYnm(Lfc1?4-Psm^Rxo3xIA|g=2%XTi%pX=2wcFPK!hgXiHnbF) zSBfFl>1}>!T~T{5W){>SxND3E3eeyyva@?*?|uS;Fg;mYoz9l5DL?g@&rr|j7pfbb zXLm6mUR_%|HM2FpB)MqGqMFx~eX9FJ-79d+4th$}=<1^LiP+b7^zdzjGYCEj0rQBmpcaJ2k<+ps^ZH zdK!Frme>+O}R z8{pzOlVJeXLUOz^;({|cEEr$)ACUKdwpq%??&lbu#M50HHSpW(U^VlccYKlJsWFEi ztH8MVE{nINz&hBSUT*E|@Y_89{PTbGFTZ^A_FdesuT2hYs;epk2(A$uG{q~r>3u-I zLL6`FjjkP!mo_)IHZ~xZa}A&<&vb)Z&N#{m+R{Pgkd?w*tu0q8PyW|!v~2EC{N9lrE`;`N!EViP&;ayJs3SwpA2Tt zqP|x_=6ygfB=apIq)nRNB15!Hv@ct5eT7X}TDJOVSmbrr0(UfIh1Q6bhvgcuimO6J zY=Di84e&!Jc~f!BXhBNH5%SEn(_I;<==4Am7ZhWuJClK?oWPXos|s`^3T~T7-OW~4 zvVqcN(d^8wf^1!?pVsYU>2h$-$sm8z7Y9voK_beE7`m=_=x8XWB}*4hgp@sImXHCy zmF<+QHMSr5^k+Wz$6xsqnbjU5sc30=vT|B3zX)X6L6NxsD3XgaGt7G^ylS|;S2%lB z*SZx70Rtc`*g+7a$4tZW$3af_Xal#QO!|#yPqQIX@N#9mHrm>#0J))T0G%qjM)5A< zHl^l(ZZS!8tW>~Q17f1?0GF|l&6S*>1Nk~y1OKMeRY{ZFW2}_-3>7+1d1rfn!f)@96Bw z@B7frn>T;=cYpWhoqHyb#eop~njC0*^=<8JgpiqTmd_}S@0?b25qfi zNR?X)KQgSb$V-ix#B=LFMWA$1NAqWB)KO5!O{ZQ4s6(LRi_7!r*~}5twG}7r#?E>+ z1Bz#G%$0hUaapmI3Ejc{orNu*C$8-}$BvaH4cJ$7|l*D-$DGEoGDE<1;H+KYZ$ z+hr>+F+-;3)6?l}Yxl-;FTA+5wL@Qg>B~?5(U-q;aC~ma7^^22S%s{uuJE3mPqTli z3r#Sojllw@Obhg>qLefQ3`LSuux@bIwfRidhDVv528jO}`>qPafR}^cBu}Co zt7xuBcF6PMW1cWnx#+LTa<^CtPO?%yr7^UNe1Xq?e@GQ8N8vP za!Pl5d+Ye{(7f^S@Bi4NH?AM--@BYmkI#(BoOWWK7*FA$93}?Kg7SMI@?O-?>6V^2 z=Ms{_VunR3A#RG;wG}R>8*5q8Nzv)_tI#G#`e(G1!|^&eeLNT>b*Lz1lRorCx(5X* zNcAJP9b$phveMHIt)W#!eX_%QXc-p@ruw)f28}-CPimkkEFue)sm>l#l`EHUP^9D+ zfjmK;&LE+lhkrpjHe?X*^u9Jts{`;;&{KKjT+pG^-F8abT{6VFo-J{PtZr-7l8$9cnPNbFJ# zXtie@cA0Y{IBaeWkxA5XtdSw}oPU`^pB67?i~PV;-i8MoLtS+<e#xbv zR>%Oj(ZzUqHh|Rw0E*|%s;}cUBubq@Lp_gMx($hBu2lCnIO=IGHWa#c=`0v=k-mzw z^A1Z80MPP#lsIZ?jYb^wy}U`Rz#r^yqhJ07fEY*dvXqJq@lv`nWVBS-PLxuC6+3Jg zb$Wx+z-T}hB6AtCk47inwT)Gh>>2xp|CgKRi4hGug+J z+js8Wc=U;r)9Igl^=n`J{Xd|o%T8H6K0g`U`$K)-`9Jfy`#aCct2xjC!SU+K#=7<2 z+VtWaQfap`J#6L%iDQ1>ywVrV7f+l%<9X6=bHk&|X4!(ON_Igw-CM^#2CyTU))H>g<^8+JmlZj32iak?7Yj za1u}eB~O{7YytnuFa<;tg}gH@ZB|{)0TL;6D6g@=m5x2F#vR&;qupZxVru}JB_bp5 zi>1BB*vfL=N=FHVdA`Xra!Zqn!Nk*+r5W8}IeL12>bQXam?oNfczCqCyYs|jk8Z53 z9o*kPIyo^n;51F}|AT;nRxkd&^5KJQ?^G-j|Bi=7897rD%@9+$6|;p%KZRfsUfZjI z8wmoFqF>8?5N&PU+qQZXM$2jlh}|xV>wXFPPhT%|DhSFGs1_ChYn8nchy?UmaOS8+ zdfm+7TFUi;N5Ovp?WxD{D?1iZ6U7*WYv`;^9M^Ob`J7p!an6XyV^2Cmuh6gg6ekHZ zhngR0RPac^7&qm~$w@gv=yev0WZsq3!AMX$YNy|+(1ySux$Zr}aF7ryw_Z@efsb#gXMMXT!@ zHg^ao&gqNuIm`{(jN9a>@J<7KQPI4@cl~eo8Sx1!j|!Z>tm=gkfo>Gm3(Jk(?s7Rr z8lw%=!OUo#Q`>Jc05tz8;`7X&G4VW87mv4r3eysO1_p|T6Xbk6j7Yr~Xp@41TZx*|_~^{8s5L>?&L**m zyY0}~RDnWM!~c zKKF+QGu~^5K-P~=&#vFN;cfJsME7k5WDFf+#K8~i>Q)1^+1AG?ggBRU0}A5!QKt3&bf{XjB&prItqil8(*%@?D1u6QqX#y6VCo?*(9R{{yEyV6eM;=8)WDsvhja!L={A#4CJ#FGG z2am3%$b(>K7Lgs$|yrM;S&&Pl%rw7y#rWjitqt(y3o8Bd2IpqUezIR=E3h^t3} z2z~?+h!l~i;G$mX9l9bL9vz>Y zZcZjg4u?$7ZQ~k9h$qVmxu!|)g`i(e$`o6}GzV}BxURjhTPtg$k8Q}) z-KGzsyDCWO5KnMf8akMgH%JiCVV+imY6SOyLl_FnKaSU;`_j44^tP+c!Mdo0N^lcMyrJfOyO+mp^1O;8)JoF>+W*9&lqiU20e}X(TbQcttg0hGV zeK-l(u2D))2yM4WY(T5pBQ%@6&ffz}x&Vk7D+Y{bzTwm+hvFui99GTB!$>+|e4Lz} z&SsO%9V6o({=}Bl^T~)v78~=>Cr&GMp7f6V9lGZoq5YPLk}oLNrpXFX z5-UJSOwVQCFN-bz!-Ke|^mMM((}P!H4p6;d0C*%U;xadebdf?HH6}WetPnp(hPXNv zQ9M47;fzEEm{P9Ytlr;o+c{l@+}2P8N*Ov&BTQUD@#drprbwGob8RXm)y|tEn}S_$ z#bcT`aktG$gj6;O84AjFjp4rro2$gm)}y6U7PW|UNP!A%Zdo|GixZiW^M}Oc1E1tMMEF~-W<6kRH*Z%j;g8DnGp^bJOnw~Fj9I*nl zwcyZ&WnjcUu0uW`sux{}QcivAn)?bQNOQYYHpT1n1fGZ^s*hzz{w(!}Jf0@8Bu-1= z!RZQl7Jx{-+_*(nDUZ5HlkGSPjZsg9>Dl0K7Vk$*P5f%`6v5E!m26eoN>nzKJ!-OP zOSQ3a>^n)gmci3Sqf+slAhq3eI??s2B6qvsXF)-Y9rtMBjTHe-VbRHuIvv1r;=?&U zJ4Km&t%8%&Q*3Q)Zr|S9J38^DgyqMdeBz({=0E+TuRKH0Hgk_0ST%>F&8??P%$dH@ z5$|01PHJc!qeBCudSgl_k)(m2eje0eI;D+}ww{CLq;z`yDcmS4z?<&xvC%HlWLBo+ zR~abWVb1kPiYF#gS5;%fF%kK7n;^q#4q*78Xdi^)=uQ6Stx=-X0xko!G7MyEV2LR1 z{E1tPiMyg8hzO@=u{D+L;vG@-g0e{49ul=~Z8dXJX9MvjD8l3bO2N~HKH|w=?!RtP zSJVynXi-eb!hUb!KhL0lCiy78|*t?mYudkVKMu!LYE#f)p*n`HzM?d<} z%jwxmFW%zj3z7O)7k9{{op>0MEB3Zux`MZz>;r335pBH3!2#QD59&(ZZ!dA8RQq2f=4OMLl;L- zup_;V;!$>_NjL8c#yac_ zG1MM}CL5qgBYlt+`Z;jv#gxzHcHZib&wpu!AFA`nFQjapnvgBE)VT;5`Y8&)AzhGD zVk%f#YLA!{83bh|x{A|_Dh16(l7bC_Di;_D=yY|Sc14CaoI%^(-d+=Inw{U(EpQ=@ zax-9Db)`+P>4%%bF0vg#wn@o=F}KE{tAJF`7Xuie<++q2rXm$WGUPDI`f zlLL6FuZw@3o;hr3d)pCEP|I?|KM^9!meMDEs2%lop8tYr066;vK#ddx7^ZMrzNzwPfzTA4Q57YlwR}{syYK! zDO9%&j-$f^;L!K22m^u&hLhroND)Lj-Ky8}fqa<0u9{=W9UR=pwLB`Ue?v><+*~)s zUwD_>2+<1lVg@NRn_!hnei4;QRBz{zBJ?dHT`J?e_~}7qM^gqVZo6!E9uXSg>4b_j zV;V}bGkZf(#4EjZ>8QdZk3F(FS$^i(7j^I1MR=%zH&PQK^{;t(`>*c`prg4S1e3Gg z*AoO6@l+5Y^oXk*8lB#DGc=G=0nj*;UuFdb*@TW~*sR!C?n52cUV|7BN1;op8YhB?;E^~^lydEqMdp6A zXqR@vWly8>;KBbQ9nb3n6_oNDxOHZ2z?9z&d%R;b|X#QQlalMN) z)KHLFmuZ4{_iMwYktbbF8IbDWDGJagGs;SNDiWBQgGXGyCyH?9xKTue%9p1FB7%Yd zv#1_loNaEbKXT)mboT4X@u3Ks@MO8D3fmwT^_BC`iw{4x7Wyb77#UEf^|=KjX=6R_ zzcgbpia6Y?k+Y$anX1#1fgMMKRqC`XJ0p_!G4`Mwm*ndp_1VeM&;9Jr z+`6;(2Ty-(b9Kc5z4g@%p6dwroeb}Wh9N z0x*t7R`&^=(0aYB3oDjIm&m4~_HT&Dt0FFA2@ESqdFWsO6Aui z;Qbh+RN)a)&Ad_~vWW;yTttTBqeJk8A=OD&)~yNGBZ*qw-1Zf}K0n|~p-c#k^G#GV zJ?fyW{8vkP+VfRYmO-Rgh>rKvRetHoS<-Tqr?ZJ;Lw--2!>&RtBF!)!o0M!KIPsvX zNWA(&q=}Gq4jGgdz$hic-XIhw_2>RJF$;~`PvuJb9O&pvLAHp@$5Ztg zf$}b!(aTa#%>{J?Q&a!blSLMVXw&XLs<&-roC}RwV?&TZVUHh*4`) zY^-mZKF-dkKl|5z?(?7foVxBG9ls0v-|q^bBf%xpt4{{f8r@aEVU`CWNGd7SYRo+) z?dw3LSJIcW_{#)>B4Ry2GzGf>!L7067XnZ6F3s&${+6^3YfN}*e?6T+w3$hRWB(L0 z^~cAZZW;`aswnV6s-cWNItmKW=yhYK-HO264Uj-6o_Z@Z1yLz5p%eyUHyf`S?;rDH zdI9%vDYFc6N)+T*U79)%VRB{V>?~hTg;yCo(X<|NiYPALPx0&#E8o~?YObET?fkA9 zWX^rqm!!TkIGgin)bq-_0sW_(Nx%b~JZpziknzP(WA;L;_LP`di_^C z07*naR9pgG&Xl=w89V|L_Ie?h$5EE;*Mwh9egb@7)wJne~iHl^!<>l-s^5y&_ z0Q@alvAwmW7S!aFsIF{BN3))Rn>*<^L;*j7vX~L1_w3qU!oc(D{Dv}9_4jzpm()LXntyHQBE~`)O@39@nFRM4k~NR6xO`~fd0Y#qnBR3 z_2}bI-akC}@BYF6V&37j>z$;uE4#ghG+X7WB|;==SBD3Xnx}m0zi*p{*4j6yi2&R3 ziw1=yeVzU;T?-L$ljV>GOCwm%lCFXn?c2zZ94azIpAJD{%@2;KeO*YP zrpm=JGyf*kyq%F#cNr&6_Cup_Y^bs}2cbkbu{s1*Bcw?4VCjfS1#{Fs-Jf1AYQcJo z!)+(J#V{hM2}(&>T|Iu{Tdut@A~g#*FNQ4w^wNSy_QQ2^9Z=HWnJ{Tw zbP&WUS&7uf>&SpSIX+=cZEbAs-Me#rXZK(G@gM!&-}`-l24rlQ860pbu1BUVl2^7u zw%bK8?av#bYRLPVKW`_XhY|eBIws~|L zq+6gi*GOCj0+e6l{T~WJrNNL6mTm(g0`^ZrCJB8X6x1|&?rHbmh4Tq#wx9FMK$%A) zZdyF_Bi8siqMJ=Ds*9mgYFigN5ZvQULsjaZT|kLONn6F)|(>D{x5H*7=ci1v!>k8365!6Z zwtT^OVg~s5$3JE)|MAzqncdFuvg4*vK@%o;rqgJIAq;>Hk$KWv|NC|Vx@%3d=RtgH zd#7-$4GG#D$O^sGtHcGM$)4}29%n(8J%j2w-BU#Z$`nl>5AXIBnnOim;U;zhLF!su z6h6$_Nfg)VScuNa5X&)0CeRbAgFfR}PHJKW5v9ex8JxG#Ojz z&9o~mmZy>2Dho>IQc)`UEYvav!*<&n3j+g99ERtBJSdllRx{y%P&9k+=~oF9~Q$LP6!#M=fX*(HFN{| zVKM!-0XlqCm~&QrmaVL~xAa~My5poAh3Z-una6VnXn3kfLtnR%QoIK&6FK`IILatq zU0fe%Ot+1}dS+1c9OqHCG8 zI@=7J-i#_kf$oK^oDYdwN*BiA9l56;v)w3;zfNp9KPpVLGUwI4_rA z#ck+k?Hiw0YOa@pjmaR9rmI?N6sNA5H}Dm%G7Vs(5_{3sPs1wbbet^=t58Tc=OhI{ z=e;r`jnX!bU1cQ+;Pzp)jm>q*8LO>sNIA7fsVj(S^QDQ!9}TQAVhI>E+-&qnz-ql* zn`O!B`YH?C?&&3bT-2uG~=!b=J zqy;(s=WV&dGdlBDWicm|p*5HX5E$LqqkN%q3YP_RAgj~NfjjI*4Kz*afES_jkZqt@ zmMvViE#3;rm4O1Q08>SyswE0t_1gO66c8Y5kW~H@X&;4Rt@Z#ni@8fLz^*cv z(PVNQ9v!Fb99sx#rK>R~!+i32(B#;2>GrVHL4Lx~xQ)#X%MFdSbF{wUmjljk-Mzbg z{gIFVzz=-k3tx!UH_I3^D(N017NG&XxP7R|+X-kDYf@KFf?6VAwS-dBur5uAIHFt= zO{j#Ddh|zsIHvNHF)e4XlM){JQ~#p!yGYPU1RdL=ZHThE^ zbml?4y>Ag|1u22o(h_sRn1tFi&?A5@1iI}xi~^P(%g2J4`EhzyLujSde8y5fQpzk2 z53QAh_S#Q$pQU2niqGmreYnMzYK%$Nv&9Jk5c-`vx9K{b`J1=z{n(HF*zwW9SHJp< zQhsfL^*&e6==Q&~aA;?u4-wvGKrd=0x&}1H#9J%3l=Xgo=y@nji!hzr*&7_DM>5~KtwuCPyIACbc$4M#pTx*7)5e9l3zP{Wm(G* zYI&h9$ObLdsJ)dpRm!HWf@1(O5GXt$8g6Y(%BHVX*WtCAL+dB+Yp$Sj4H64^%1;D@ zMN-e`(u~1DcAC#z4DN`edll-0iy$F6`OC{Qts>}$^bYtDWDw-!crm|=^wU&yet!F< zmnF*3@I&L9O#h5Z~O`G&ybW?r3l4)H=N)zfO?SW?We`sYK;I#*5$reqpG3 z$!v4~{yw*0wq|n<$8of+qhqRi>remmPk;84pM2r@Z@zr0$z>&c% zxyd#_^Y}PLs=fV=Bi=w;qMTU5EmA7&)=9)$$K`2mF+4l<8~3%LqfCu$dE}HwKm>&* zlK5!p@Hp;glUFF5obx4Xg5#{?iLsQk-JY7bToYljC{)4isqd=#{fk;)0CWiYxL)o2 zJ9Z+H51vl8Zr-|g?fN6zH?I8`zw%#tlk4y-ZY2XQR#K65(OETA7YXwRZ}#`?*U;Tn zjUI8oSd+^fc8jX!|ahuAM<3#U$cKvJB6{x%h+vZw1sh53`7Y;0u@w80^K4x4I8LR4x5pThmxA7 zjY}ou!2rfZxDiA6JI zLpfq9Lmzw(4*kAXosKdKN7@NW8Pe$VoGLSJQ_;%UN0%Ob?6Lj*d#0(s`qTgF|MRKO z(oT$NquY<<%0?y%HhU6jm8$KnUf#~rcDU*GxMHgtmQBEf;ZHKOK91*d2t$fH zaUn$X7wpW-eAk&U=w$RY*UCJavk)BvPjcSNi-L{d;<>e=^p-?G8S&&}LJ7$bjP)D( zbRB!WY+}S~C9lQ`{-=Df*S~GM@bUHe?rB zu025%JXH`IO37@i6XJsVIwUPa=u+XVd2aK|Xcm;@Pg$pjcx>uTGtCq_zdSsEjZtji zaDPAMQ2dm}?Ijy2R!vt*!*jta=+uUPB8r=!1S3%8%3v%N`PLNg$ns?4+{@PFevrqq?E%C#%h+PpN_SSgfn0$Cc`t1iak!e*oSlp!3afuD_LVK1| zQs!ZvC%*u4W`qK$95LT&ywWTtS{7%?z5akkHp0QOR#&B=iGv_4w_FrV*>ql)3ZP+_ z5{+x8RycW(5t2Z)>HGj7J*&Lgv6Sq^;61>+Y+3}$%F&2*8;DSh;u7JGMV@13qv>jX zyLQ*vGnls23i#RqR>Gm%Dx3s8d5P`h*ig;unWKmIsUn4wvk&%P@4 z=%bIy+2(toYig}IOe;E1gxT7}pXv0G>(_3+eD}tqkGcEGAAk9m{`SB5^jH4$xtI5> zmk0omQHzeMQAaK*{4n9|1T-cZvLcHBPK$U)TDT@`;_HCHu*+-{-v!FouU)s6JUThPI5|FNZS(W&?xJx0 z`VDU%HHY3IRd{E0XEM2N7aiA`EUKx{$zt{eBwAKQYRpz&ogBi6$c<~)JJRiSSnu>W zZ($+mS4KOL130kAY-b3^<7RaWoeDNtPXP`y2(GQr%O%*`{>MU$EqVXw4C1WIylH#7hE|6vX&plz0iDCk6znaTRrx6 z?fS;`-EC9kfB5hJd;j&{|L>jGvbuz2yl3g7TUQ->s@W2AZ#lF&ddu10IHT550OX~? z;(e<;LwT|Jm<+Rwn)d!_WDU$i%vkhEDV$!rKOD;?rymb0uY=BlM1V z1DZsVUb7&>)NOV~gi^g8NwI-Kz5<*e)WVX5ToC44lSlhpC8A;Qhh~8faz0-1)Ca!r_U+r$CMF2V7dqj|-eXLaRFvIH>-`_hx6>!z-5bd>V*T7p&hfOuaz)+D#suoc5#Z{!3 zlx*ld8-@(A$@=xjp7_syY$snx{66C8%(ifo$${z@Han?uyAv-Tp^Z}7&rC5`76BnoS z74b+g1YBK`60>&D=rJc+i&)#qfTJQq^XMHX9<2Kehk1plnvNa*jg7ADMwf15Dp)if z9o+Y+a75@fR>qA-)}MUhDL(3Z_xF7!E39iWhd@RJc==X-+_}C(ntE|-yK`;j#3{T| z4d_#zSf$rjSEkFCdp^tM69Kj~H@s-PdPD67883LF2S;l+cC_X2=)Q@6ZL&SGV6$73 zFRo8EwsPcyn(&D_P`fw{^c_fbxWW+f0~F7G^Lg`$6|JX-hqn>;tLb`)hY-!SSd3Dl zM#q%H^G>%Q0@L+VkkrHF`RwA*7>Jt4QF0NZ0t3d9pRax4g_jHJF`p|&3?`BNNw!WtIVhq+l%ZEf@ zNe@9?@MiLh3qq%PVl50!%w&0L9hSod(F)TG*v@A(YaS=HA*`$tm7I!8LCy#W9uf6+ zIG=;xUXP2d-s{XDDWTK=Dg{^GZdUQhh4r4|LTui0RI8U6*mxpbw+&yAhthn-e=Q%m z8q_}6sWA)Hyg(HcJ8EL3nZ_>d?ccZ8=V#z4IXxgrmmb)En_aGLZLe+RYjJwlA9w{F zqk?wS({ABR5l?|*XLlDARL_2}5Q8LU&C&kdTQ5AbI=Zkuv3uQ{)mx@_!1FUzSh{oj zWy6E-`|$qxox8Wrj*qzd&h{o5RF6IC(AAy2z2g~3UCVCd@e=dhpp6WcN4q=Mob$YY z_YQ$rFx=nIs&QvK2F&RRI++;t^6dQV=;+Wafb-m%4G!+`O3}*KcbL~}_E`|OX|T01 z$=bM71B{;7Qa?V1oAJu|V^4h_4BvS45!Uw37Q@50X`cJH|IROc_Vb^k9-y2217*tW z$^`h9;H;0{V!j92dNO`+9|KEI%yE~$OxkFdD40^i20^UfQpWY%rFCkh)`Nm+?3Cn+ zIgx}RF-e8CrjdBvir}LlT`8nMBBCT>$gvd`Y$nkY-?~+EnM0~bErwby;kMl+(fUT5 zR-}6Dn1C6#-pp{FNYqC)%edpi&LL2HyIk3BQIS6@3Ep2HVPaXv-?+?rKF#mBGCGVw z+F1?(D%On>v$bLBtrh3(H@CJuRpZX?H4O7PU`6`I)|TP`w{mvmY_jI$VEOt6(Hk;E zCFdHLZ>~FRw7-An{{1~eg?OxIHFI}|OL|-8^^MK7^)2~rf;cy-&6$j1Cgu2rX<@Rl zDO(QxuJX{(K3Sij(2}h4rm3BL3~5^2m^U?+hDc}RbgtuDv#CY7Jd~O~vC)c}W$FIG zp`Z550Vk3nUH`=Z4KW-EAtPVA2~? z`#(885K|=f|LVr+bm@d(vT~`N%XDU5CPLUOc<;{LzxvZZ{a1eEx4-a*e*#WejC`yM z%RgOWd`p*y26R)!OSe$Ng4}FW4#sHY{s6Bs4nUDCF2BEZO~!e6kZ18Ud1|M(Da`5k zo6Pxx8tnv$IptiMAa?HnzWhK$jfX{D!#F6UDjRtfKOvMb@ZUH6R7I=aM`BYXL^EX_ zn_c*fk0>xlK+A;;JB8Gh*}7jIsjc&Niq-xf;dpAD(*vc|$w6t;k5xroqX0my<6-z3op7suo z$LrgxTh~UbTY%^#KRb=SsphB@djPUX3vma+jHQg|wM{!B;gdRu0=hJMG~aE@oRe95 zTSTzFyvk7+(HH~8YAj188o9>EO7B?Z0d+cM_u8YITi1LPJ@a}0=(}q-1m~;(KSPa-5Q=kEPv;j-vUdR5d^Avw zW@;%fO4VS(&%e@Qq2v(^!NuCz?&>4g&ady9t=S2_W`?FMcJsZ~V}_T}al#MmGLcUY z@9*#L-5V_*ee`2LM8VU=GkRoY{X+eI)@pstISp0Uk-}_#s)(M9akaM2S%#&^8CWgw zVl}~Lz&gHM^HyVz?fjsS!%Wpu910XIy$b$XJK5M~HM?bd7`YAxX#chC9cbn?5{wKJ z64!OQZgjS>wf)lVJD>QWAO5Ak{Wt&j-~267r>D=(Pv`wvo%R+k-wvP`WT}LV>@u(- z`+{m;BaVasqhnXE$&=OHgOI{ie4#1xj1}=9J^6Kck}Kr4K#3n>P0;$D>-pr&ZRrsY zqYLw}O2Lx9oub#eEpC$^bg!5ST@j#bVC>^q81l;~I5DTo)9ab1Q__xrj>_k`KrCV@ z`$1MtwIW|?FR+e2V}w-M(6m}A7eqW2N-tW6D~h=p27Q%KmK+dznpWmZ%$LPgJkD<; z`inJ|C<}aczNYaz*RHQ^Zp9K!pqMXDveXWKY+kVPwa7`oGdQ#nZW7|8;}~6CzjR`% zq(R5mn}p#g!xFD-ynJ~va>HsfZxv!~=?gPjD8*|Lc-9s!9k}%gbgPBS(E+!{;mPsV z#)btcK!a@0^6ulE|I#=77 zs+T}v0`$p5ajVRHf>|>&1|ljmzkLj^rrzJ*J32f9UAs#!yzs*Q-o6nO|APG*)55xw zbb&0nTf;ezs$v;f6WFsqhbto&cNyh%Mk?cz;PM_!MtmVTdPTo8lOoNMXG~=a%53g( ziTLVdPIsicVLpbG@mb&9MRDAgAF4zn6t-9f)m z_IV)}5!-UEZdsg$Nqky1eK;OAH{*o!Q3RmKj)R?qOu}TPQ}rG`GsFR%wzoxha;ipp zL5;xCn|$!$){zb;N`XkdS_w=X1sjxZIYxXsAg_EFX4wHb+uwJVBgZ?Dw_&GMD5%d+ zlVSVX_4TbS@KvMf;_khBuo6H!c#$u?ujy0(g2(typ%&fDa>nY+N1?skU@)K$i85ja z@0!M^2b9@oSXxfgNJ_h=w%*TBAp7uOGV`jHQ&2oJs*KAvgBg+V67EpQG1}e zlRx>B|KvA*1Lkqn^GoCHtz5r7Ku2AXEIi}Qn2iUE`4u4KUn#jLUw2%gK}exF*b}r& zpw!LQg``gCMlJv?v-B>Kc$JOPpNAk1v39o=%HS4!%c8cPjE&2?Y|P0?Hksw4Wof3k zrSMs=2hwusJ!;Xl6*wHkx2!vLQndXr5}NgNG7FdUX`N?z8-mTxF&ez~$rhoT0c33* z{2idszf4W-=U)#};)IU*7*(f9BjAhNY&ozvHP?A2Yd6dQKY~CM?xKVJrt_*Z@)`va z#*%EtP~ho(#O09{WVQv-%3JbjJ}%7;rv7}#dc4f_ti3D`ir4X{2x1Ph$jYxL>Z(jC z#ELO$I`0Q@usQfj&=f2#EE+OFXA(I#B4LtyFSA!PEzrVzzD*M#GVP2R>rc}6Cpssi z2G*k$(%I?V5OV^g-b7}3UBCXwt=o4#^cVi(&0Dv>`n5lUX1ybXMy(^oNr3b!`E@Q2 zZ$lI3e8EyM(IG?JBy!!bX2Pw5HDHfw&{08d0HWxH<5k% zQH`##|FV5JJwARivLUiS!2Z6N0WJ~AZs;eb*90@&i1yc!H=Q& z$p^ZRTr#C)>@kpxLEBYvqk#;|CE_YWAa+nK7hpxz-2^+DDu|3Q3PE`xH*3la%2N3` z`C4Ig=|IJHq@Fj(A_pOsY=9VRK*l^xkiiTy@~dr0HquQ~nQ78$Gd#_zCXNysLe+U% z{fVlG@uNb|t!DUQyq#=f?nljuhNLY3SU{)0PF)|LX7iFGd@{-6yWs!;AOJ~3K~%nb z@Akj(i+}5%{?>1=8V2j@M-GP6G1q2D71}GMZ(ntNctDpek0N2kGd4#7&^7KNg9cn2 z!|8bUJnS;2g=wvk+IiDv4I^(6rQ}&W5;w$)d@h|X5?Ib}(XvOV+m#hmLTeN$dskU+ zDWwlc6qEr3%T&7be&ngpgG5U4CV_Y#WLVA2Iow{%%I7Ly^_iwQ!DfO2Dbo-JqNi5fhlcS=bQW!IX$G+CS)CH4)9asNwgw6&V=;Ou&8sQq#6~mTf@b{=ptB{D$-&_* zhf>&GzVi4}KmJ!f_r*W3dD9H2iG6DxT-2MoUlSe{(18ff8dYJXf{2t?g@TVx&N8Cw z)lQKTSK-*hYEP%M6s)Mw(Zg<8nvp_@Y)^+6JY ziu6cVlv4AMx@ty?h$}SD(v5%&2AYs<3S)ECZo6{GWFz}w9!&d%5V_ZDp(m$Ss zv@NKpu;78vt3B~jX^(Uqb!2f@E4h@8G*!^khSby>n>pzb zTmFjC49>f8a>aLBDVbP{{|p^Sk3vxq0HsO9YdMIe>=h(ubwRWZ_?V)Ks@~BSoY%@B zQ#>7qdQD(4AoClAV_)ygml#JGKHi#@h;FI12{2_kEcX%e9?d{ceW8&=G{T9?Tw5zBbl?Na_g_v-jFS(?a zl&zN7)W~4YSjYkllS+a6#`cyxbAhSI5TO^A{6dyg<`76)AR78hevH%SSFBkfPuDw+ z93cBYFUi6N%^R7KBWa9+Gq=%mvpSAa+X6oGFFnJ2Li=-ITVKYVNO!l&*gMRLxhR2* zvb^%HmNJtcD3t8s2gGUbr88ss6LX=5JzPrQPP()`Qonfbwxo(_W@L`cMHgh)F*{=s zVIQA*K--baMr8(66u|k(spaS9<<`NmFCXM{tT6q{zx>Nz{%`(13nW$7w2WPn*N{$m z?e(DnonEV;7qQ~1j*g^Xv!r=|T`6)UnYich9SrYyUNZ;gQ3mNC+I|B(#SkxDB0k*8 zQ-i^yMToH&v6+9_XSL=6!qjCBA;0J!C&$78NcilA|LfH~Gz${CEFnET54u1!27CcJ za80kKUn?YEx;;Rtrd8`aQjn{F$3T6KEy$uBJizLz2@@RB7U*#`1fl}VeoYsTmK~8c z2%?p)+0clctZeJNzN-j3VVet zD)aa>=a}hB`-Y~iD2B{kex-`XEg^7>Km%x@e3bAPfV!e;yZ3MJU{;%aVCAQq=)*K|F`V3eD)i zHjcxzaZg)S6~wDZVK_fVCXc0_Jm`0GjBr~Qtc%vIsX!lna+1cpBhsrWsq6^dcv7&hjK-^0s^mh#|12%Y8 z*zSb_WE9ni=M1$(j3(gH$K;;5-dOPnjShctgdxvDdK*FbW}3ZuFhj< zB|T5J`4@bh z!s_se#~%N=pZVlxzwkvrH;QXs!5&Fu`ua+G7)EK&F$C3CL3iH)W|`wcB%9V9y+h984YFh^#!it2&=jz7PUyV+uMUjEkm$K z1N3oJU0dQ^Bn8Sa$BmqBI=?@>apovo6az$pa;9d@=^pNiRG4<)YL=f1f~deU-vKnV z=r|iF+qDM@joxoWB+54_V?!8Tl`R8TL;7F##$ylU-w;B-pg&LW7ljV8g}W$7hvK3v zs~OSo#ayle;-!*(hbBifWVUnz#xyGQr)NfBHvgebH<1Ith}h2$!3xP%pB+nvmHaeE zzg}64r+ZCJ*3Fgz*x=2#E#<7NQe3PDr$MoUiE4Z)899pz&O{m4_68x|or-wahI%w%r%hZ#Tu1{0HY0qB&JbAd~l zD|Ct`05c+P$%~F~rF*Epbaeu-1qvA=4&87_uWxXv83)cl8@HiT!_SYdic7&{2DG;1 zsdH~@G;OV4VTt=VGuYDVvg7%{7uRjHGqJ|1!hziyjC4vI3ffd^I%hTl^e~nIYnD&b zYXC&qODFHRXo%h0&f&X#h3ZVKWtht!$904Eohm;ByOw0p$hFUq;9 z%oLEhFbn#+x62(RO>?F5${7;T=*L+9aShhLDie`rdT`CYCjr`bHEp@iCXGFqlgFO= ziy!{rhi~3|@x*8HR(-Sd)CnrPGpXzK!kYp5YMD8d(@C!kQUa!gz*>-!-cGldB8l;@ z4}+b`8M@uhNI1?TqR&iJiz?-<>}aMK)|s@I^AQ$gU9%U7iu{oS#ZmIEM>0^t5j9}h zooJ}0Vzp)o7EGi+qwxe}ATvf_e92h}NK7pS@uceSS?RQzKr;1L26=Q?Q@~LfK`}y1 z%XCx*U$st6DAj-pl##=clv-68*D}q#OdFb+Jlr@R>~qm{C)q`ST1ZT&RkuS2UkX=7 z-bts&Sup{6`4i614n`AmgMGWCNY4z#BtANklJ!?qZxjewXumncrnKEm*a7a+MDZ#v zo%uXSwle8W_H)gvtTn$f>R2phv;dh6tXzdJ3=Q z`2g^=0~2XYY4Joei~=y({GtX1e(@Lo*025AugTD%NzLzG{r4>Zx>!+8<3KBs3aac# zU+3u$0NokVlwew?BtQO0r$FhJh_xlVv6)YU6`OljDKG`68Ybb=#;d~#!CuTo3DFb8 zlFs!W^5zz@s9RYS%83my$(~X@Lj0+S6zNycEMa7m3glPK z3YBdrH5xtf#1lhrl@kqS+)_eO@%Yd|TG#%8(4F~CcKFGB6K$@dp8T`1Y*oE%K^8mJ z$F!<*^DViW;3Fl&hcQf(>1#SwTe8k9siMZA;Od=KvZYOdo@R4|#5S`h8!PK!15a>B z&YGUu6^mpDhKxLd9>bkKd5Jd?jUwa&cs)&2Wh9GB7YPp82ZD&-~2K{ObSst9DbVXO1Ah$J}LJhl zyuY$ybqH7Ur^Bm>)uLPqCrQ-%`6EKeS*T_9*UYrKP;7Km_YzIlLk`hiK51KrnX)Ww zGMTC}E0w=EdO9NmdCUMpx~6npVW<}Cnbu8w`3_xkn_LPh$rM}7kA@7q6^`jDx8yh^ z29g8w29Q^kYE6(6Olp)zaSpQ=Z{DoV28(#jdfG^b`^H78G6FR;GNE+<++mK~grzJn zfml$`Oi}`}vMt=05%6ilJzxI=4)Y3jy;U4g zFW=q!&_}=jBOm(Ev(G-;M2c9(%n;Hc55gM)TF;gA=UGcX=>y;yI)2gXg%9~6fVuLU z7n`vds;EZGP?l6?n6yX@1=-B?Is+)cA~t}veCAnJxH>3S6CnO|Cux|DGk?~=5b;Ul ztR4_gEg-_(1yZ~OwKAe zfN1jdAQ^c}l>azm0E@K-mdC6zzIFGGq^dOYfc$A~PBKhtW=ThQT6hkiqdw`*iyT)- zPSj*|A{T4AD%nH9JRMWTr?<8?(pm*w{nMJL%HSkAW01o9DRI3{s?l#hZ^jB$waW0*Fboxo0}g&dLcUo7v4rVOc= zz2a&hlFUIo+rGG`mwS|AEsMFcGp{U46g0Mtt6(B;^#d+?5fTSa`rSBWBCf>~XC|(B zD@RbAkJShwJh;F2H-6!-|N5{0dg-r}j0D1&P@|z@eR<1()~*gUBX@_DpoZj5nOBxR z(-?J}pE^$&)#eANV)^A$xf#IGA(3(V9$T5e0{yAip3^bZ7=@eDEJd!=^dTrO=QB236OWx^IgI6 zFf8XxDCts5@gZbf(PL^lIym%lKgS|qv#|A=axTeI!Y>>QolzFzwJP8z&g0sxy9vz_}M8E_RwCr|9Ul@#NU&J{U96WGs(J z2S*paqOi2|v5$V_sV5(Q;l-PJ$^f4ed!4^}_BEjg0KsJ_)J=VnG9KKVlZeY%zx{(y|4_fb5KD~;18A3=+T#ulDvguuN(d2*UT04ViO zB-6G@qJBm@b5ZuNOPc~;SVG&*wAsLEOA%9i~mzciCqi`G|t zM_$jz&>E(Z zonaXv2Pn?-_;a2w>?ghDW8h>!W*$pbU`9xcgJ2^womUM?`BVqTwyjqgiQ>qoGm1Sb z$O!VmCL1By)rvjWvWN#dgCD7Uda;&$<#CCg^aY<7Tv<^VsGbpAC{hYUIW?7K^hQ}{ znHYCoU6)r3$kbb_7M~$Lv5N|)z9y_DD}t!4m~gFBm@es`Xq_;!JhQVKPk`Un$;%n0 zjvxM^A1uT!Su#exuMGv#ZwcshOmYPoCGNg1NLBKKOQf&Gl`}ZZfJ=GCBqXS=56o9j%hI;^l4XvY zxSn%Nh6-0r{157mA8<8JE|Y()&h_i``pz|1($t0J_lgQt%8W<$>atM774MyC6@~0< z1+p5EMJ)cqfH0pB^<)eQ{mC}9rEkx~=7Eox(yuJ7^)^k7!T5<2XUS8lhNU>;CJ`WvPF+>s%h~wFy z@1rRIm!UL0I_=XR8)C-QqS@n$H^7I2`%(Q6sweg_)5tB zu-j%MGOCb%3JSBsr1NsbWs+@kU^R>-$NUw`D`H$0)@3gFgjYTfXE!fm3pccuNqZn( z>={jAbm@|z3^Veic9q!EGJl^Q!*}?sR-_>rkUBIj#@vv%-lfstB$`nF6=>iCnd)=A z7*P?AV{%Rt>2yj@wg}EOb1nl(J5bZyDEr%zXl4*Pm4qxf2dWmNs)2sr>&>f+$A=D) z{=`rG`2YPopH>NJypfuIV-1aq;<}NAeL<(D7q5g)mmso8USuP&eiY1IU=*G~-oMk+M+J&%S=eMzj7vCksek_#9eyPLWuu%OQsf zv1rM9g^DEzy)pH>d58!IG9vRm7l*vUOJ;_e18vwP$TRmk7CTwV)>2c)m2kq7kRH=3 zu_dN#*;?47?)V%u+XouWxo3=KFCrG9(wRPwk&&!8)$CU4k_zYgF`8IgKbFj>0tp8|#7pc=y`)$W}UhN0RhK&4-yy^{9TxnMA}& zwIOEk5Q+&7qg#rdIfXjRFb#yQk3S_jkkctgu z4vG4es+?r&Gd!1k~3bR0Gjuo(U zs)&ZnDL?0hZLH+mihk*ktB|;T(N^t>JD}^E;ZgE>vr>Yt>MFO;s9W^3ZYfTAGsrv{ z3S>BnA%w+(MeJi5wugt+9b0A9)6y5H1!{Ua`3-Y%C{PeGK#5>W^@}HD&l1hxW0r*` zcSNOR!^Pux>*Y6TWwlxibSXJQVV)A7v~lIffwB=+{-LTR1`Vpw0{L5^*h9T02*hLc zO?|0`G-FO1C0X+3ECCz2K|f;Xll4B2LQs~1r+%O8^mKB>ur{k)_>?lv z6}J>Uvy{wfLR?+?!XXYjL=(i=$(+5O9mF^;^G_cJ3^8D`j|M+v(U{P9^A5w-FK*?a zX@NV>Bc+tnk;rC7BBiJ`DY0D~D(dMTl)|ZIjpA>V8*ABNkSIOWZ7_7#&)~`ws{YZ zPP}Gp02uM5;K%E`!xu1ANJSf7kjqAxZDduB6Kc*R>C*-B%R}<%e(KS3JpXfB6U6AK zrjD8!EJl}E7_7*woPr73E*cofB8_zF)G;$cmI*Q-0ltS&BP4XKlb<|YtKIGSad1r&K1&P4Z}77`oZE#s3;n4_!#E{tZTlV zoaVDdRZm_p68y#B6!U)uXC#m=HU!P-`PoT^QCKHK6AgSM)P$MMmiP+N6Ly1aPUx%b zb7lb!j?@}oW_vf2rS?(%;DQfonzlI0EY&y{PLJ=W<7a-)we3xkdg=DdEQ3fc(t3r5E$e$B}b%wPJX;_-_U`RoySBEu{;5xYik^p-jJW~& zpeJ|2n`vl+>&kFRe4bK&Gm>g$tRz@@#y2$fL`hGqd5F==hT{C)T33;Cp3yRHDn`2PgnT$XyO}kFN_6BIBP0+h zO>9X)gV(ZwH*u6Bm|B@*hc!J?MuErj)NcaeAki{9psEL35Sg~O@UM13$D_zdym}|u zj`LDum=roadL1;gBZ%dApR(L7otohr#hiTd8z=w(AOJ~3K~#a9h)l8Jef`1aP21KX zE=Rlw^oO}IOP|xDQ!}Gk_sPefP!=ZH>WpIR1nm`76y$ia^X(_A?ezSqmnHQ@})6tkM+Z4=jc2tf1HdyG~1Ck zb;0#o!$AXy)`*|uO(LY}Kvg zCmJ*beVJWZ1hQpZu4v43c6>2fyWCpc+CTKo*8Vl8yu?rRCN>bfbLTF~R1jG?8E3y{ zfB)d<^c#;pc6j5)Bac1u$mFq=!=nQ~hjV@sUp@SU(UMKz#>laXo2(q(y2TYc8DGi0wr89l2{KDG1sQyFXh|HL}Pmp_Pf5#xj$us~Ful3#78l4S-S>N@}yI z4khNDOM{$Rt0h6ce&+XA;N6z8f&v?lr4Ols;((40GeV^;HxD_F4CcILEgVw0?M6HsO>UPO zbRG_0l*%_QD2Ob&Vkw*5DJJo|FW-_+hx;N7PX)bjSo)p=Bx@i(i-R?%us^ zEcq?KXPi^U*{k0Tl{Q%eq>YM>r> z$Ls>2({c$Vv%0B`@_uTLj)`p+6NAMUuaT!Jb{)Dh2pc+Gia`F>;I8DKfu?UNvuIYO z`of18GrB^gKnVJJ(;UGeORv)tnXd8KYt^*VZRzY_B}zI1Xc@$$RxnnNJ`F{K&&ye| z^)Z!=N`%$rrF04HZPL#w#tr&T>x9+PM|45UG@b z7ALDFnh?pVZIxKZLbblfSX>nPEozu57-(RtM^up~=opefija|Pnu-lNUagl&JEQFh z8-xabbk%8oO-y$g@maikVV8b&b(DwFI`)gi=NG)n+kO~_VY0q%;BQ~M7AJ{ACe|X^ z5jOWuukCCloH5J(C5^=}8QF2S2P{ja@U(^zvZuP^Dh{#;~evqaPj} z-eua~zIAW^E{jNidX(1sm)SU0MZ{>`;Qt|zNWdh>RCtI*S+IziXSniOxajmOw)&8k z??o9cejCO3KlM`Lp&wrM9$w()+q)FAS3Z2Q(KjG1tjmv4-%byvGA8ONvnFCI*g=mP zJptB^yA^`ALU7V8D`V2csH{F85Mb05zydsNG$R#QOaCjIp%iN)J(&g>W}GY7%r7cI zb*f2URtjv_ccEw;y>oDQ^m||UA~Y2J!s-CnAo*Kq=tA9tyo9bm;We=b4??FH43o*w zx_KzBx8^!K%7u1_J0u}FDe+PRuAmpyk>16Bz>g?t=25*mF2Bd5k@4W2r6oruk8Fsm zz20_|GxhB3-J!+(N%jFw^p|w^Qwh$bm!iky*n41 zJ1FPdNY}6L!Z)oZC##j+%5%NEwtoL$Kb~nlk&tcb_H~`??Q5j^(w1oO3;-!KpZ}&y zrz^Y}9!DEyJo_w*VR>!GCt~o;a$s9OGLcH4MyCW zMoGQ!H(jA~Iv4ad$nVjUCcm`-7K$7~rO!h2qVtr%)(eW9&HmS5!sXm#j z1**Z~qDFPfX2Gb88ht9(*;vyhr=A7+CqD5BEJq-V&_kzgzZIZmSn%Ct-M%W)x30LU zP?346`~^`JMo*WcrzvEF8)L0~^`!gO1b`U)U#U7mj6p&hrDMo!-Exo_kb~r$V$PD= zX38s4wZCm;mnt~nnFkP`Ce&vf^g1mK`)lKPBnrpHbn5srq$LXFySQppdWIeE5q$zEXzMO}911YK0L41YP<9Yi#p=ocC|^GD4t-LcMArRN#{qr?5rNLP*r0*s(ka?2 zNXG!?YiOl>lM?y=Si7@d-?r?q>u2}vX>L=MlC6qEY()_eBoGpazlaB<2%dRBL`rx7 z6kZUDk|+`mphS7GLXdC-A&mhU2ipY4B$&8TvR!rC)9ikqvp1jbn7@7MR@FV%bxWCd z-*wh+t=Wt@`Z;E^JeNV`D|zmrjIOGXUxxiRHuB{6uZPo4vsZaOS4EvEo@`rQNTP!I znY(a>Li&GoZQJPO)yrqcN0-lE`Uruqi;JGJU;*N~>DcC7 zv_-iEYKgXCQ5otI>1TS02Hde!PSj*s6Ny3r6HGPJJ>PrO99zt#F^>Y1>8#c+@|hX2 zF}!m;>%s-IIWb^vyCx*;I) zUtXMj^asE1yQv&pT2Z7NrV7Tk#tHjGeueccGWu@N#iixgoLqp}1FVYz(mW`J1=!R% z#&ziR(UH$zg~I?cIj`|O8{|3^oz2ERoLtwN%T{gbF7{|GvWd*z+1b!(5Q}Q+@OJ7- zk*W|#Y7w0w819kD=-JQ3(|jEFIw_O{4IUB$Ppl_5G#U6I0E%H$v-p(PRhrf3}bBnjrCcH4EO&@6k+I7*UA%$oBcG zSH4CIr2L@AK5(zEYFQ~0t&)|B;t`dj!;Z>M+{dO5 zX2q(ZaS;!8;DT1xUpboBoP-@^ku-{f@vb`5ydJvokl6e7aRtXSBv@I=R8k@#c}`c! zj5`$vXezQOdHP$-)25!{&9Zd#p4uZGHpM?64ScEVxxUsU6{2pQMZ-4vSzwRr2%FQUS42|NykS(ri!NRb%7t1c-t(d;r^wVRG8syKY+hBaT z#hA32hYICg52vd+CNC*SXd3{V0kMapDS@ztOXB!ASx)XNEiK|NSDui zRCh!uPQ+ofBDB~R9O5BjNOyXSR_f0IY=e5M0!8zvBD3%^kI@7NDELAaL1q(7{Q<$U zKkSS{s5`t!N-PE91zw$<+q11~v*d_v0QBZ={|!zx?PEP^AgTZA;?lQV)qj3=cz*Ws z2n+A*V+l+_DlX5@0;tFXJiSeXdnu~X3EXVm1yodqiECl^XksJ%?qv38bzou?3Dg3s}uiWYg4QUMwvwfXgr+m*+!6gaTCT zXxPXUG*Si9K+y>W9~Gg~h&SUACH7O_3Vo$NYJQ`ZTAxIXp^Z|{o;~~4haWsUJ$VuT zy}G@4Y0?CskZxwONCyk+OU44Qd0AGtXG7Fho%eT_RM)E)+vfttj><@vLyr274vrHE z8avN&O_bySK!^`+pR+Uh{tfeTh7V4QY1~}DdVPCwW}|ky9Q({>4$OtIt?$FD8Kt9y zay0Y9@rV@y1#fZ+vFi}Yn&&^v#EE`%xxM8rF8x7XL3Fwj3 zVP6kbAh?7`#Ro7wUX~JCo0olUGVC-aUag>JgOAu7wgy}cfx+L5Fs#W}B%9zl9;@5F zMhUZJ)=Jw1OQ6v6@gXT}?`eD`V(OZx;A*rMn=;2~*Ug82^3nH6zi+tvV#BEr^8V8^ zOW`kl$R_tu{1+STHo_}DTD|HsO(_>#t>)TF>GVL}q@q^;^KK5!t6gqq|ktcwxJh{ROdZQkBV#gMEaecYFzB!Q% zSv1!38g1EaA0&_ehSl_m+V*Q42sDDT#$xIji_tQc=x*I%BngA7tOgNo##yzHPdT$fLGj_=R8i+kgA-MD#DDuvzHf9cdU-)f)}?_GV=dkW#-QX5{Q9X2RTn zodm+X0MBR45yO?GoRm(b;^t)CJ?+{%IVX7{y}h>}LAAPLq|_iz_EsG<)F9!5&06uM zfzHm>;ocE?ps2Un(Wd%Sq3#ozT2#5~>V?d_*^9Fq->iJ`=_kj>Lei%P^xGC^QAoQ} z$pk-4Ss7m$pdka%8|wo3)rves&x9tEE^vpU*RRg7n~nlp_V|xU$Myb)*G9Kiu^wiw ztgpK0Xbym3Ajt7QCS8rBDsS#DG<@DU6`9RE6 z_#>BhGxP%lZwoA#)al7$h4nNdmE6sCJQ2_`1&AXIePRQY-~B}maU(O9!&_^LW9G)y$`h95sZ!ng5GXQ}StIO^7HB)y0Nv2chQ;nZE!BFnM0 z_N;-^W`jmOK0bK@72*_-)#vSf_foWR=h2gWOSJN1oQojNs5?3(|4p+tA-36|VrQJq z63zp;s!%<5A<2%1u8l7IC<4^5jJ7u@XUU8NF0XIzp1;_;v=p(!H#jgZ*s3vD&T%XL zNNBU(Tg4wbd^ja34q^~qt>o2MS?{9CP#=c7vwihub3#@52Of{Xh7kN2Ih6Ic;U-`; z`PCS;Hrha()~zZ_3t0fT4r%j0%PHnK>$k>%25LNGiPHM+N2Mlh7;f(0$BP_muSnSCe~$lOF~sTLp0q1XUVzRu6j$~R6n zr0I#FV5A5Gt7(@ zUbeYmEDKK!OXJHVyh8%=^~nnbp~C8H$ftjspht`m%SHnEqbdd95ek!G?91G6>aKli zx8ky-vDf>Y@;z)iO|~bUBl~ie)UhC^TnRB!9u_r$@-Ijc6q`-9(>J72Vr-4>uH{&od z)J9Jf#=yG&_!!YaFI3^neuX%=6j#@Uv_+C<6?Pw4ixT9|dG~0i@C2U6{pDKHkg60D z*5?*KE;Xcivf{sqeCg&zfZ3*cuysTrec#eVeIFzi>iYaSx!QTp{({5(qsP84|Kxgm z>*(?OzU^AHK@9l$a{+U$n0z^Y_U!2W_da;?xU+6Wd!N6w&y0ZHUY_xeWVXnJ<)Pp) zeIdDn{c&km49SX@{lgQ7x-<$k(?g$+hL3$G{197H zDi{tj@tYKeDKDLen+CEmvpQQAFhL2c#r0ywD#X4;eLU$+xkrx-yH!BU=vtN9K!=Q` z5Zc7yoAsTw(BdlLA}a{aea++O$i{y~5Ep+L>U?0k5VTu7_Jhq9pa8}!XA8!kEY+jw z(k6SV%I`dcI+r(`;d=l*md5IusY?JXt!p@;M`P#*hr1^xmh!;IcXtv*-B=N8;Lq~6 zRl^j~W!s?n0Ws<{DJEB3Z_=T44jLOBP-7}jN*>t+%7NpH-QKS8M!_sReSxQWCgZ)P z&dJos202Y9rDAlju_ihp<+TX{8*(`(nIuw7_Hw0tA0(sHjExrYMprGmIw|!OJndy`;QNwd~m31 zcdrjV`xsG$0}qRWJcZ*&FVC{?AP><6BGJZe{DCRWFt)^{z2>gcU_UJBpcoI;uTkeS zgy!+z;6YL+DTM_^>OKUFI#NH7icSQ?6i z9C+IbNNomng1EWLq~?9&^%$KS3IDZu0%e zeT05Uy-HsvuPNcYVeR0`3{g<0nf*!#AWG1KPo+=L?OLbxz%DgD21?A626#Bpizz>N z;V_4B&!g9j%BQf$`IPBr_^LV(G|=>7JcR}!20b!JsFF;D=q!x&7-2up6Et97?Y7|3 z(+Ut-+QwLprLY$ZAM=3lbegPjp3d!yx1 zooHrd|KRbP!<|0ueqiC=iI2=WAj_nn^-^|XTbR*IEh#T{nK|@zA}jtCyk}2hvalKh zLr$%(*6SY=D%1x8=CWS20n=;=Zs3=bUc&Y4$$JM*A~l3Xs@7lSu1=o~85?0LUIW~) z5pP)bM52k^qXjPlZWVz-^Fdi`W_mC@6ijSN{FgM##P+v3LRU?^d0=p>jteYZ-kpYDygaKiiq zIz&ZMTsI0kURgDWw`pH~Fc|~DXTFBLn}z$3oI4%A)kP@6{_>4Cf~6(^3Ry9CL!5bD zi`|r;wUZWRU8^C!FCm-P6oBmR0Z>|cH->BYh<)XR1*oGJ+=HMlU>8UXja$~D$~+a$ z4hllp#sZ=KJS&Xin5tUBHLmwonAi;t2_}F*(u!kBocj?cna$oBI^_ZJa5ggC5idB= zDk>kwH1+fPsVF*u;ch{Ycf-vSz8Jm0wwv5pNa0>7xjZHnp&={0ICgo6%Vh#zz(-5g| z&?2SOjWuWCz$#^Uz)d%?8lgrnyk%$wRi)Gjc=#h)_0p8SA4aUUiW)i}s%dr@$htVK zGl;kP{7e?2X*J_QGp!0Oivc#xMPdJZMUpS*C+oT&)t-tN#;xeeuxk9`6&HBxNd}kW zkzzsWR5UU*OQMc$Ts(u>cwJ|su0PC}3J?1FWze8}LrnF?`AZAgn1}&;sA@z8tpCk# zXO-gSfud_STRwAb@j&_2i49SUYpIuO1Xd9>v;n1lNkSnsp8RQKf>A30HsK0@fLK$8 zH97b?CIt2aoVW{s!2?!v5W;b8I=)fH6=Oes_ITU3*if4Zm0 zLzPIDIT4;sCrBB=+UF;ZVSyA&FVuW`bXpv6t$^e8^jYlJ&Lf#{&ju2$@;#6C8S?|t z>NuLCYbBFz01N7m)R02Oe4!xJr69rv&PJ!!k;&Z2byXjIv6yA&dbju@56*hlVqnskPHC919-5~$!VW2 zq<1p3GGEB{gIn;t{Q(oyzxp@K8pmz0=0;B96>AP(Vo+5=)bbwyhO$QZ2F8wwM)ob#Hc0PHb?hFj;Bw8HN8?94jri_bs5{QR@q z^Yag$J`n-1?H$#zNDP7;IUa_F{^P4OtxVtQyAf*Ya5O6hvciQ;kh6&P>ud=70m*?O zxtSqdu&Fs^BB0JKYUvUQ)6M<8Lw&=HycqTCVc2=gL6zDtMoLU^eL&;1FZ#_B;rV8g zvIZVUD>Et1i7%>n%g2KwtmLzyv>xDQTjH8cT2OXX>YWVjbq#+Z0W?^L1qTvX+hp(@ z`4-uJq(nR2@o`zP3H&B&pvtFn1LvXMUurvv$QP71L2cZH3hCyGRQkSSY2rWAs3h6q zr#rDNI4|^;f>CWmBr2+b$buqaHsP>bL6t2#O(A8?3i4ApXdnJF8psRaYG5iT;P@6H zt6)8CYK}w*Eh!$4Q!U;N_X=(al_zma=mx3<6lyK}pdh4M_`GZ3hbh#Xi@sI1@9j;9H_e_J zV+bg{esq0xeJ(@XPw#AM{)?_J@C$qy-K2wNB zrC8NbWrCA+2WN+iq08d8WLP(U!a?CGGgq~Z_UeawthoGURh7P&M}zHtpj$Ev7y}Qc z(1?WN->n>wo%LME$5Q%>kp%|y8Dpx=|0>*Ya}8D#Ekb}tKz%e8V`(E3XyG~}D_fDR zdjYYSHA!0!$p`&&%R3MpGS1jAe+sN%M365W30CD)C>H%p7B26D3$MtqN3j&)<&hek z)29_N(CntCt^%jZWm$s^F-A$FKac0gqOUt5az=3v}k6{gJ8 zrLnwO!MU@SpMCc7vrla=z;l{C5(+SJj=U-~Pa)XKd(k5(Z!wTPm}Vq+QDitbjh#1u zNKjNa9-t;nP3Ep`*=ys|m*gI8&1pEInz%+W0%(DLH1|I`SWndo+vkQ5_?kZ8br24p z6rr}S&O3@Ol*VI_SNH1!&r<(6j?Sp`@(qY9Uu~K0}Y$v_jul^lOS(`B_xy z8}}Jg>@g;Uwyc3D_K@DGq2*h7+$^bDKjAhck40Myx1N*8VT)mR{R6Q14$!5OhgE2W z#9ZmvMQCR3@S>>_DlKu^l8pcWAOJ~3K~ya}J+1RQoy-XRUDNOb!?3eERZvHx1!HDT z$)NusSgi<{FVC&O4E+gm0A@g$zgCRDnIg85f#wFoSb(%QWLU_-UZ7mb=67i+waGmcIOkpl=+Tg~+#7m0ljFx(m6O6?Gtf!^%_ zu*k^S*=t8@QfhU_oW2R277bV2sR%nZhGEdVkN^31Y&dYXq@}IxD#Hp;`1;1oXmN0A z6vA4?W+3W7K;R4PXKU06E|5e4M9~ozZLp)kG=&3NDpc4_DoJ|UqQkF7hlXLdvN$&L`wS|=)Qbs)Ac=4z@XtfYG9n=s8oLlT2S7XR{25Nj` zrIAzc@KZuF_TYr`@n=Tq0edx3QUa3+|MsI6We8W>4$GlLv9xkFg)LTt7QO^-DYJ~$ z&A&dk_Pxp9vsd&4|HPV>!!&Dys_B$Ub(0m>cMK9$HVs7uozfb=1+eC6n+F+U$+scX z;7|>f?V|@UbT3AOm=W6%mvTbX^@SY^0(3>_4)^yQ|4%N!iG7XfX9lp4DDNe`DWPCzMR6{i_r#zx1 zcuJDTxvwo6E8!V$3SRx-xTjpHwqh?8w=EI)%96WeZ!N_05h>fdLTtTD@%a^Vg*Q!8 zkQtmQSSGSsmw+ZBYlX2;Yi}Du-22*9PA&yv35Xqzw>EQJ-#oE}jNnY*ugqDvvKZs_ zGpWtjh?@uVm`8mYT8(-!N`&&2wP&v0LQ%z)<)th!&rqm3hYq?8pDX#Apj}$n?I#Rd zy=7m!^1z^~4VV386__YYQdJ(@j#tVW0jl*aslAwQ6b@}rGhhu)ulQz78Cp?g8tO)v z{6JuDbW}8l@#)-9zJi+s{@l(#G;B9bcX=o{ZPxT+<>AvvmYfV@Ku_7H$bkBTlHGv_ zBCBy|{BXqs^9anS7>CAoarUIRmu6*9V5oOcBwNf7qB*qAq@}m<{dgc9h{+G!$&~ra zlMkc?a|_-A5)LFVhIJLjhOY78lB=?#q&8A(nL|fJpjzZlNPwm^K&fV|I$LfnHo3}G zsZgL7bLsHALL5QfqX7endQXzbNoq)};gQq)$3cpNh-=KB9q+_1T;X`FZAX#dWoM<$ zJZLLhCbXAmr~e3aj11tkaMf#BB|!GSAHxrE)UY!#i*Uoi6hcmhTtg+yS1eDraccRQ z2kEl?DM!28wqYhz>U-^+Tv_N;!0_w%N-g2X#xaM>{cYO16Q5)Bj>18m#n7-H>-c3* zY04xuZ(_a=QncFueW8O(nc6l)vOj@|p{@V|Si;_Zj~H(QH<;h4p*6BnSb0cwchLbr zkoCZD-pjnng%)8*L*tF;5rw84JjY)dat=gVjdTqDWY6UuMsG+xL?6+eJn7=3dmI4@ zHnv!liQ%NaMJyhY;giO@_Fj!RAN#O;4H<=|i3PT#VrfbYojx0nK&OTW>RcVsJG2JJ zn^sc!G}&DOkTR=~7Db+`4C zW{-&BZ%{1+IeFIEyB@|YjkdXQQb&MT`*v+^Xxn(h3Zm@78=uVTeM_YT>^e(+Ty%(s z0$iwxMi_d~XoAr5%z#y^(NuE8I=#0vi6U0q<1iYV|{%rUZzW~JsIZMSROLg zdR3K^4gay#^_jSh7iMJU$9%A2Y;cN}dtFb6fEXFxu&OKONH+9Z`(h$^rcWA*H<&`7 zaRm^34hBM@Js@J185+@z!=Q+w%K8OwAREG$X9x$hq#5DniHnoTV^PoA>!)Fyj}YA3 zLv9z3N*InAu)4KKij`n%8GiRgMrlJdFx7J zVIEDV?*~jQabxJ9eq&;zgRmh*Bb#=!ag{2Onf?jC???${5UmT45{ygqayNZ3GUyH%Zo#{-=M1%oGeu6?4ruMi#s8;W36-#_B ztn~H1(P3lAS|Xw`3=kF2@&56yi8;=ZQ9NnJ02c45P2nv85z-msBEN~wR;-|@})K02TLH{MQK>QAZ~Q*^4-2uG~oMWpJ9zroJ{*KiEqS(}PGV-GB+ zY)6~oLBmkyfgB3nHMd$T)d_8hJ8EPF#t}q;T3`}=l7bv;C=M3QZ`r;Enz&1O6i5KW zNI?6y|5Z?MP9CxZf6b2V?3xd&4RuU5zK>Q`x5n7iE$N*Mt!gP;S8TQ2lf*Eg4lg(o zVX2_WwXWU9UGY%tB6;DjvZ{%5ya2D?$drJG736B58P|GHM)>gp#RGfva)Gln7IsRl z_PP#FT1BczQUjNco3-ASZM@#38*|23MBPJ`M3hy<+t8u#oL5tZW`f`i*mX2^_8S|` zIb@#*2N?u>Noe(Te#1#{B+@L80c97Zrk< zYHGVH!`hi;)Bsoz-3kVQlUOdDgdflzh=ICs81ZVd2;wk|cQQxlX;l}oTRhPD7&=K% zq9dHL^KGNTxOF^%^=C>;{pcLvXlcL{YLpzgm3zSy@A^@D1{h=N8jxWVkb!3Xzr-ji zK)-@PfZ+mQX#hA{EP8s}CeY~zb*TbXZpK>`A~U%?8ud$I-%UA)#@Ho7%{uOsQEM;) zOfuI7`ebot^aj%?)Qrw%qc|9fK3#IjPK;+3qqmZsEi%9Xcx;h{pl;Ejzp!fN z^3qx=(?$2!Z!Y?7{nmTm_?Zh6hLf)7?dicVUIc!phF%5Yr!OQw;XY}F+psabt+yG1 zxV5|~j{fbJH=%8#3)XmhDOXQ1!FuaH&ea`sYEX((TSi0{HF9Ig zo_!t;?VWE?a>$c>;Nzr*OSew7!xQ6V3+5&kas!v!+9wD3xYu9|jdf(Y5}f`o>Cw>6 zVMfLVq;yy_B^6So)KGbn+4yFlCbU-DUC=Iu8hP@Rh%ST1iL4IQ)-jwnRn(*(q8ZkF zhN|GO7IcQ3%v4L#)t)fIiwqdYnAo>hjt=Sh=g&VgBY$wCj1|8uKN9@wv0~PJY^d}Jk7DLFRd&`kkOWslQ?t@jY z1JyMt3;sc=3g;m{Go3NrN@7>ETV^>o8T=S*R9lkjn1Mcg&j{AK^&L9g1~Dt;8Z@h- zAbV~@LN3{m*;0d{&Zuf07y2N7hAf(@KLmzj`7I{w1kIg2hxdr|*oF4q;h_b6z5cTF zPKK7b_8)lY?frT(KhdmTh|2TGW7J8^+Pjz%pom9B>dLTO=u;-oyMUsHu$#sor5=|k zgZxWQ7tbMxjzU}+JxWEWY`P6$+@WYzs(6$P6;;_z)o~>wvvSca-JQS?EKK}AA!clYF^ z&1U>&Mbw7T)r1*PERKz9sGImjJw4uU6$k4!?guOZDLbdES{z(sN8okQ=zGUWnWt3z zOVhgo>5G(-PTK;S*TPotQ~r+zR- zDm9kPabY%7Nv)zCV%129UC`HRkC%&{u&~J$ObaCF)x7?Thm)BU!0=j*LTW6~doua` zTvew*C#{Bh9%WWaQtD`_YYI!T>YqgBQrn3!)9E%<$sq~SmQUzs-bMHsnT&;jg>#HR z)Ut{v`pMWpTc0yjb4@WLd}>m?15*%%u-vF1a$c6c4lW^J`R4!$44}4zs~L!%OzbzPLVf+SV1q>1Hj~=a#?& z0yR~%9$D7xSkgBpZET|1CFS^4((T1y>?!soHs=>tP669Fd_2s(^PTT}=K=MD|IMXm z?`G)9q;~(V>$>rvHW5s}Xn~Zmt#yVkS)4|Q1`PPhmuvD_z3d~=m2oa+sV+4(spiy@ zu|vAE>JTh0SxR*Cc!;1)^FzpogsPp~v4?t#@Dm)bG_J~SFgjSE7DMIUjCSuy5YAZC z{WKbJ$xkV5=Y2F!9MhB~n#{w@{3K8MnFP}&J(52*i(1_?(>fA!=z0SHwSl~zG8p{s za+)=p!s>}v2;n3T-=>&bh;G_iZ|N#G)*#8Fud#3at+3z0nb4#LKomGb^DJU+Dboa? zM9yp1c>?Zg^Hb%NG^1VEKP(D_f+lw11)ER%O)${lx}r#-CV7nC*I1zH zbWX20owCJ_UI}o>JX#n6TA!5}&8EuI0A15cB5}hL1It*Bq(G2Y?7g4F%kOvs-QqBL zx(@Mx)WIvrTZ=Ud*^!A44Q|}2^!Q8DP0guCi?KtD0G1F904hX1I6#IUU`K~wFVrAFmp|J ztOx#~5JS|kcM~C-1;>eupKyV)W`?GUP&V8gL(ezQTilrr3r}fQbXaOAsUFl!{aPD^ z4>T*W46#T$DG~mTaJf8mSSPCiCA;RT?+tUe-#ESd_UzI+e?E-q)R(V*3YTMWQ6ENv z=T~?c4FCA0BK6W^dfulxbP`z+t1qmpgj&Gn>2vm7fg9~W} zPcTZW#1IAggC>1Z1p>i1zeeCvj3n7nGrQKnC$ctT8AYJ(hG=nBBl8AaVHSw>5)~C? z0?6TLz+w^ijuEgLVMPxT#Ka*ySTs{7vd%0Ip>pjlmxBbZw4j>&^+~C^{t$BVTn5K< z48z1F{hr(|F-C>biGIlg7j*-M=4IZDJeHSDL0h|kRYBMF7cX@Z!qBCvrf6AK0?ugN z09#MAqS3C?M@51;%bE7^5P|Lj$Kk-0vOufZzZ7&%Uz&{_*QqX=t^u zxPVSm4XTx2_|=~0e!hL~X4Gu4n`9KB{bPYZ8#Gdp0jqc8-2oPQIF=Gtn4UCXfN}xKR9Qi@y**2x zSx+j|^l=c=4{#XbTZ*Z#aPCHZB+C4Ne+iwO8tc3WUSQ&S=lJ=t9$IdTD4Lo^n1NoH z0SV#-d7gt>cRN}xI~$YrKwBX%p@UM--EW?65F1<=M_ukVDej^ZAeQvc&Jk}dqG&00 z>tnp`W{w8!xnSUw^gM{5U))>=vH>t;RqDu%8f54qR<>fu9GKlCq%VJ72?4lsgS z{n059(wadbkE9?wZk>)E+SX>SH*G4;p#VgMDbdWBKud%oqf?M`o>1D@P81kpBQOUUJ ze6f<2Ef*4J0E6H6Km7ENG_5Y$-KdQ#$45fH@@#hltt!IzhX=>!zOrz1YIA{&x0vVm z|M~CXk!9y7%^xv4e>g+yr_Q}indP`P=KJBap^)s{A!T3(DKJ2qmL<>a4`h}p#!Cd# zRj=&#xQK_2OnMjoACleH9hR9jA>)1OhoHqP-e?j4MRwI{lj zOF%z$bZ(!*MTpV87%h1V6GKe z=sdI>UsUU@D)9oS&N_1>+Vz$FU3ceKhKash@LRw2TajiVQd^~jcBMarp=);-+I4Qe zl$EP0tWA5Lq-1nqA$z*Rz^p|U)8_DPsy+olb{gDZQAyG3d+0M+d9@%q6?Qy~4>fsq!4YBqw3a&lc^Of2%dv$kX@|p5D=n+Rfp}o&XM$p zn+9@5QOls!Q-{+r7GiF}sdN&KWDCC-K+S1uwe!@j#$?XM#w0eCkd>t1V3xJb?W~<% z=7Un&JDu`xj_~68=3A$yzxkWL>8Qb^j2zb2uy(Hj)qQwJse)|VPTB> zN1uH50;6=m#sg|q(-0xOa_9Tr(_DZi$T4f2G(#U4h@lrbyu#3u8>n`jCYq&lFUh%X zTotJjAEeuRgYz7lMn~J7X^jH`ZqWei1r9SZ%|-gDPUW&mc%nJ-nWxY@3+i!9vjiLQ zOEgrjOEENp<^6-j|6GDcPYzE^B09OCFG1coZhcOLIoNXw@Z={}jTt1>j$pRfVY61VA!Ll z_z2R2EL%WE(xcuQDmj&xE~1wB?p#S_d~w(|LMexEAsMil zxf|Q75({eAx=n)v|LHw@Mo-TjDDd>@Gj0wzkn>De&*OmMiEf``mX7qd3|&C;^i`)3nB931H5RF(2Vk%9}d7t$`S&k z4OI;guwnY9vA~R=2q4#%2#gx?p6}GhgU9v6q`6Yc+onXBl1~xC-oj4`E5gGeiVsUu z5be#zdf4gW(RB*W4kZ#*2(h;)V108an55k=5HToq)tebD_bMq{te3FF&W&`iHofme zw|Sxxiumg03Rg?$S>3asD&73RzFBwh60W&o1OQIb@UX?ZE``<7xQ+F( zMd5@gvE?vWWgFaw`ha)MzB{3;oB%LpMQF^Bk5ikur8Ugv$&4&Wt;#w-+?*sRODCz0 zQt>sL*L{|cYI7i!uk3IaaGSDeRfPg2&sEaU>Vs$NRn1ejSJ!}7v$r)((^~&^v-9MA z?bDk*kHs5PRu5=d1UbR5tiz(cmBBY+bl*i(Xt=8MVP`9lGze!XLL93Jer zXkF#-ResYim>)zy7>uO|GDpDY$NR71=?BwYHKMA)RxZgjTC2d$xgi9*JC4fLs3v$$ zuvDi+HgM~nu@m)+m75huW=tFx`-~G!02rPIUKvVbbo*6&lHGgO;T!KC|C4|EJ9Sbl zqralo)3a)5FGXTR@EwoVp03ZNKL_t*8FoGon&dQ)!b8m2__i(B5WfGwjBZ5>YN3H8KLPVA@ ztiWF_l^hTP7>AM_@C_8Fd<2N<@7>+;@u{UXD)jjM_dfjm#dBjpv(`Eiyo?WV@(JC8 zj=iIXluD}O=SO4sAc_QKr%(=$JA>!Z_3NFZZ`qv$DtY(Y6@TtLP{ytz}Uv*;#e8L;{ zp(}3KNK7R8-QWE_Jd#ZHI?xt`6qXZ!oM!2*I^wL95V7IQ?w((rc`nsAUJ%3Xp9#n4 zA>IOHEt2uU@8VsoBLOKHNDn4aDskgF6>M16>U=qg`RXiay#)bNBlGH}tLL@jjZAIT*I*7^A=z}U+zs==WxGYXU$w>a%+*KrzhHeBdafOgCD z6aJ2m^0s42mL#vwIx%?bW29ggA#V3|Y@GO2qyfei?9lm`4xZYJ4&F9I-yBUXHZ~h zTva_I0P)>a;@S>%UsM^DsaD-K2$nuQi?LKSsRn30eEIarDMahBYt1mh43ePMC~4TY z^Zxp*VO}p;-fa`8d=jkOvVrpNK31pN^dwsHkh6d~6?Btx z)M`RUZ4#8adPVXfjfbwdq?9`4$2l09wg%6FL~U-~`pwE3#F3o^tX%@nh*HVrbPZ!Ym@KN@A%&F;K!=yjzl4 z@5(tbU9O7!eo9^e1#JZTonltrp*lq&6mcw>$Wa8mJMrjaJQe#uIy`; zn9(~iz--JN0Dz3rhGloV_xo``x@*(BQ5<(~d||e)6fcFB0F?AvD{{K=>TOb6A7p7P z%}sE)*UtK6sy%Z+;%jNtf%x15i~^rvQ&xq`Uax9BAx*Y6T{w&8#hF)1)sbQM@W0d0 zS^XH)5X}@3{rc+6&Jkv((G>3@sp8>wsThhA2L5t7XR*W1?(xa7ZeF}RL$aY{G6TrBz8tNtJIPu@rc@1I zhwX)g7gldv-hAt4e)fO-JAb!jB9C@CaLR7d4|DZ98G0c_8B~Np+_T`oxWs7PJEzl# zyWs_11kJ2=Mc*{Y(oaQ&I9NQR+4TQFSGYC>6c_WV{BJxf=};*p%>h9e0}C1mcj7pn zt~zCQW!%|%<3Y5-h(^X}c-fm~1fi3Xe){w=-f_dxy>IxDB7_X|Q?pVWiVhT{>#ib!*V{s12RYoj5JF*KK!PNWit3LJpZGF}q&0X0|7u#N z@aT+Lj}-Tu9tBC2U}Ou3DYzz(Ex*}6+1v5qB5X$+3WOK#=&a}Tm@shi36lBz58+oQ zX7aH!Co}s9Qc)(KhM&Z1Q8`{aK0G0aOC+KW0o^}y)!R`=?YDqj{rT26KQt`^THoYq z9etaiq(kgT(>ntUkAedt_76|#B&>XsQ@+QyGu)*5kc(?!$e&o}xQ`;(KhOl#&emuq zf11cq{^>aQ@FjK=Ntn3PGHGLGOgSPK2$PYlj6_Hj$^K(Pg7y&uI_wh%yJqE_nB7(u`)sF`)*ebbLra-(DME_jg+Sr{@y9>%Oa`jeO;avNiiR1}@Y@G8TH&^+qJ(KY|6zJ$|Uo z{2TgGW^rBdXz`%P=>QjMO%uA^3EmwG-gqd6_GQ*|fOKS`+uNo4M)3btod=V5J4W=h7;eDEfbrF33is zD~-L}E2ukE*xC6@5A_9qP(Bb@n8iqq*v}_JF+Cj&ubz9TYs%_oyP`5O$!7#@%_DB! z{^;XR`Bd@O?(X9!wo&1=;D7P?3%Ky%crUQ8zERj#-`kRXe6qt(P&P8a0cd@w%Ey>_ z=JAPvMS&*|PPjd|R+K}F7}S+);0-gKMw%Hr2Jy}WP!XeIu?A2rEPnLl*?a%+*MBqq z)AN%48XG+6hiHF&!CE>xg?jzLPxQ7eW;x-tw$y>Jg z!I6HlwUkzIm_-j?%W=^w%y)9yxAbC>@vl*0R;yENH;5%6OzVfX3Os8f&uWB92Gm8L z55M^hqY);X7PPv>O3=oEExuVF7RfK?Q-bKzPe1?UqmO*i45Wn!oeQv3$QUPN^K&s) zE==Q?oQ=kfv^CQ!<}Exd^wMzhc0bl#!XF+ymqOBm>Q%quk#2a`08r)VDgg=bGlnZB zurlCR#dY(&Ny*`)P_yECMSFZ{ha;PM(l(abpX0GFFRWB6zOH5vtYrmeizvUqN|1^p zsY;b$)mi1o$6CxC@`=QQy941lqVf98#YOI)B08nW>a|i3%mO~a_8H*39WnBY>#I+n zzwox7naDjp!|#9Z4^;d3vG303jIBVmw*qjy8$LZWaoR~<{s#7FH00Q2OI$^Tw}18j z_=n=791`wg-hN6tqW?&Gr-p_ssWPKhVOdF@tcSdPR0Yb|(o`2vcuw-dCK2p(crlF@ z-EBER5(yXf)dw9_c;D!?w3jbISmB*ogm>KB*V{XWqdAa;Wu!xYi1WLCgnIqP5hAnK zOsdS1%rR+arL>6b;ApMP%wv|EZVY&BgMol>g)DaA=?J4Tj+F2r??!~R`S4s@j0rXb zR;{1NL*Cph0Up)Xz`o~m9L#v{+BKn*7;9t|oJL13g|2sf8^{UeeQ9C%$zk_Rpm+kG zRX*$pF%YtP?j0WBTIiaCbZPxYZk77vra->?-S4Wi z`Ra%G$pTLfRp<-U4FFm1yBDwAhnB6a^Gl)|U&@b4pwxauGTP7Q7Ost(z5TUc`!$oN z&z?U1^s~?TcbFPY6f9*{K{F5ZDqWS2c{=k{zT-x#cI|ib;$)XcX>D}q!9`kg0(T)$ir$w_t zh?9n*dkfq)4q2_Y7{d%)O|4!Vry2w%*z&B7ctw&F^i!HsemAJ`DpC!DlF9NkNuTap zi4~rIf97Yu{kz}!?jL;g3A*5Bk@J}19qFAK zdXs-THjpLaQ&Hv<^I|vb%r0tL2C3ex za?SA0=?UOP2jOI)P|CPb9UtgX=PxH$2Dm;(MH*S0^cloYFu)K=Y^*1eN+8!2TP;$t z8slj*`Nd8tfilg?)Ob`Nred{4_ay3fx5wAXxRQ|_S247@Ywm#n zdIclIBjJ6kKE0jxgt0?R=4IWb{z+!fUf&v-)ba0v{4b{L;;(k_t}dV@RpEpYfLZi@3wJ^eHcI0 zGWLSIrVK3>bJol4ZMBhy8sO9C2&MYfH_8|9DO;WgUjjer7q2@4E3NLUz&5pL)XE!tb$&t@<%e^MiA0BMaM<^0cA;4$)$ljTkuXsfX9QH`}|%2RI&U(F0woJcG>7 zqb+Hd?Ki$mWhmo=YewKAT)VeE+SJ!aoWRlt?kOeylERs6SZ;x+W$1563&IMHp+PeQ zVq00BcH{#Usiw1d!Y5i(v6=c>7(r|{Drkm-SJ&Jt^7cv0l#QR+)9~f(aLecn^<$

p`Gq&@FUmv-3O;iORc9qG%j4f}y6*Y{y{J^-AD{2RSs*8<5k4nArRm zia=7FK4eZSeaT7WBP5H%P=r2B38^Z^2-8TPN2PmEo%g^%JT!vY_Jq7=+B_hb=_m=>&r`(C|FM?c@@~hnxSNj_aHgk;zc+# z%rk@7q*7LzpfqVRtiv5GrX*bB%HmvHIFlUmvR{*j^I(PFm3z-~k5W-FKa~|}Sx79f^}+k!_y_;>eM}F4@@!I$^e5pyc}u9M++G+#R*z}l&@7p^+BfzlG^3bifa#b93I?F zqdgrAhQw(>WL{?IB-I4NJpfTdTXt%+6d+J|9#pbfVl$EHLP8ctNVnLSq5xuR@fv^n z4DrFu^R~aGa2$dY3*U7$a$@CaJFln*n4$$E|WfS5X*|rWt*2 z`c;94WKb16LqgZ;BNng6A~m*-gKK?e$lc>w_fkzy!Vuv87XyoAOE(XKm=hoKFxc7N)5o^-jFeVKq_1qZey7SejG^!J z+4sNy{onYF-%uZf^jOB>1*7AB+``{_@#^6Cz=B4S1{Wf0##3DQ%d?kTJRpA-3S*E={nq}~-rY%` zjpx+Yn_L>FsX-g;0YEn&5ZIpIdMz*#Ra#`@8_hY53qVurb_e7{F9d{DPM>Sv zUX?WS&=E+`l(MQTC5;DvUEUVD0;GaWh=?5sga7vR_VJUbTd8&)rS`clsa^!G6~)s$ zAb_~-{one3e!EFiQ@n>qwiO9B&fb>Q(I37d%Z%iH`02whjn1trz60yrkgfas$H%Ai z0c`M-G%Qe%dX9S`BkoF52B)5Hw|3Lp=I-@4+r_f!vgM4>JN;K@+Or1nvuH(MDvu9v zGk&#+==$n{aW=7Maf2mavmnX1p0$lLpuaIOw60-mb=4ZQE$35B90b(nT;N7e)x*%{ zx$n2m!wRxuUFc*F70+8yjfdYNN;+R%P=Em0ZW)Llo=AnXmD=*$&X*8+zxP>c;>q`? z`obLG7E07RM31}2gy}A{@OngP&wZ1R`$H?DC3mJr9+I*k?w72*9ix=Zig&KOFYt`5cHhn2>=BPk7 z1jch#iJOh&0|W+FtDCqKL+`#g6Edcl=6`wH$Jii9*s(%-ArXGJH4gH0Zx$9<{_6`o?=@qBtwh9>aEmNrt z`kAnmFt>!e5u^E*&_m}j@xo%LzHC#faJXe7)7WOCYktsjk~FrUH~wG#4-$>}Zmp7p ziAWA)l}Pm(RB7UtPwZlyjx3IEK{Npd1t9G{~4wBQ}lS0MJkd<)fJ>I<(vQ?R?pM~aLEl| zXjhKOdMLlj=$|GcO@$1lyqdQcijV>Qo*}+oR1Xv>?!#_+IRTQifFzGnFV5R-j%zgp zr^*5VGQvz9^I!fC;FTq8ug)(Fz{xZRAYITHeeb)!_j~{MZ~tS0t3u2c0K>fQ-F!(B zclna1U-<}F6b8|^s<{yI*8S1m31DXxM?T`Dh9LPP z4E-Y^PqgVN;G1;Ku(9%WvvGE2NUrdL7XDQ>0$a8b14SwVrRGfa%_wFY|rJ(v2~No#sAERtKgrX!lNHskDjc1T_E85BxH8*Pt9tMe)RmcWkJ%wGb4 zY5_1s>melIN;HUDf-B&4EKOvNj4PIDXqkx55sqFtJTMo z##7p`GL%)I%gRvrt(ujdNJ4>kS-6G&^;w~ z?fckA#deVgO`zEtX8Db-ik37Or-3#Y_x$3_r>t{y>&S@TV(8sjVN**IkRM#wB6~Cv z(1)A7ToInfC#TxtKj~xk_F$if45VjI-sfAMpI^STOplM;;>?}X$1~sF_mMddJ619` z#XaYU3B-cLt&YVbVfuOL~Ukw1}Zq=%uzMJX*Nga~VAJ%5yh1mo< z0&SBm_xE-ZF(I6xpuMzjGB{z;*dRy>b*B+LZ{f^LB)P(YCrcstsmT7}QBEf@ZIeRE zBJev|GoN1TVu73+R`l6>@BQ*G|FV~B(p0CX(7q1O_4*Il& zy%$3RG-TWE{a`6{hFH2zc)*E1>pX|#*@y;%gH@U1+46x1xRt8yAjUX@*WlBALi{yH zq$5!tG;^nib9P65N+1y+d4f%ylV4sc0V?qR%wcq71c5MJE+>atdUEiDJXETRx$q-R z_xGnq7V9{he67A1OSjlMt=y=%wrZ{5i=w1K4brkD8WQ=LnV=-s@i7?=Ykz0SD5X%SqXoFlq@A-wQ1uBPS+Qt?00@>+2;t2kkF4L_ zS<-pi#*t*(G7=oKvgxus0rRjqS3${$NJ8C(L-*zc<$Ut&sks9mGsH5)(CY4>j5V*<9T;ielN8wl&y&iyHa{ zT9v|z?%{2`>Q&J6L8}drAO8wKV9UY=96NY1JEL~YU1jWoi4c-NyYoXvkuOg~o=&t; zL2D-#iZNXp5wvI=pJjp<%oqqIFLA+5Iv{J6IYb!&nsc4m;X!3n= z1ov`fVMUSRlzPt)4rMtd4$!U{DDK~ceX8>9kMW0sgjSI>%63{5wIA8w*VU9l`f11s zQ=V)Qzs8xd1${w~tnkp<%CENC83}5HUchT^L1`Lflo6?zqCF5nlm({cSyVS&)f1;U1pmj|MV@Fn*3ofTC| z!Idw$wPgzsh(#?R-lqv;%|@EWFfK$(Lhkr!j)r%#^#ga7qc{@~+J zd0MnqW26TN)$`TqYcsU!P*gvC_xb78Qy{Io7Ur1Q{n)c)2)qx+awQFkoSS_@p{Aafp{6 zz=I}h0s=XNCUugyP}w4Pb{BYvq$3i?P7KwWxXL2fUdI?ne(Gi|`EP6M)ys1|VD6F4 z^O~P@dSa@)q<+rQVqB`WT^kwb#K2oMVBCU+cQ<RV+{ddWTQ_#twnw3%yzSR0k3q02$XloxfqsTP? z03ZNKL_t*Fd+!5v;5@A;QZE{VZu6V{oQ0#la&n5H^_t?B4LU^i5mIKr+hcB?{g7@J zKAh_=Z{=tm9Ldn0jkhR-6tmiTR^10j}ggimA*0muCoJW##C^?Mp!{H!Fg73 z9;8YIrd{Wlj|NN+3;`{iLZ<*x2A~z(@IgN2bd(l9a{mBfnALGSQdP901xuy_EZcz- z&M`;C>!xrGJ}hK&cIY@8I%o}Xil`a7o{lVXsL24r9 zO%~iJ&UJwL@>n;rpt>nRneFIKT2%-j4_S%6KzuhFybp68RH-UzxCWOeM(xgc!}ENB9=2E9 z&=9tNvFU~I>KYj1x&p!Wm|GhuWqj}}v zy!!Sv(odtIRgbOVqjB?5%U+>ojZ4}hSGN;a1U5jM_^X5POWxEmqgf}ZZj)MTJ2?QG z&HdJn`dO60p6bGrdzrG~2mO8i@{Id|#`Kh$J3hsMp0Aw_YRZJp^$DWGVYNtzHRkx& zq9yDv9RP{=319NR3BpmcGA*(Z?dJ=gK7I23_BRVCn&y~QJ{DFPiAiS2=^1Q%=+ z3@|5$E5?bS54GFs@x?N*zc_51<8?^5x(ea2c!s?2X=An>D7Knwqd8dMTf~JEeXTXM zZ9n?*3V=ED!9HEw7=wm|2O8Rak~(CH$KkoLl@_pcxDt9Pe=yTaZkaA_k=cGOc2*YU z>8TjncnRxo?|lE0Pyg7@|MCCxH-7zBf9=a{TNUUIB6~Xg#ylYQ7{cC~_j zwE%4vl4ky3GJl08L|qI)3z@nQ^@7B0`Yv78x=F2^*q?Hk@`X*?k!J}(=Oda)w!Va8 z!=u+a?=B&80AGH|-f@FJZX6%+3J7XThHKNPB~JiS3dQ)1oSYwAKcjLCALYi03O{~J z7;X*awzr?X_kqmF&KxVG4}>#~p2~w}sgm10_ga)+S>_b3JlB0EhNwRk5ZezQg9Stqw72aj z82~`vFwS;Vibay&aA@9LcLIq>Ec+dfodap7403+>;Wz)xpZ#-W9#Iv| z6Ih+8LqAdadJMg)EG&epOftWDbnZ;Pt(~XuJxgYXFhJLt?rAbnCj%c8a!^ZS-_YG) zZ~RLrzzd$zsGJPIa==hI_Fz*#f>ZID-0%} z$)H0n=NE-?V~fYxpq2Ywd{>*-tjP=r|b_5?6b&+Us_*@K^rLf+Q z-|v(3f|&){$7O=dNwW-BkvEWGG2UE=TfuI^v+e2xw76`kmgz zzPr1}`zLyVFHiS6cn+Q9AAr>`k=a?dI8nhQ-Kz+F|LE1r=azt2X&UPvG-%Ov8o~Io zJn64A9vMyu^76RL;vGzx-T5^YZ#-&e&Adx6;u{u#EBJ9dxNPibk30KXb0b*h)WgS2|k2{P`_k4Q{CfhL!=+ zANoxmF)8`ot5XZrJgRwBURBi1ffgHya}JbIH^gFVOedzb^ZLk0jXSXxQ9vvil8>8_ z+ffizwb-v=c&O*(jUh*Ue4ta<)x+onBn=cXb8b+x5}-^uk1xn9kS|qP@V+BB%->PM zn$_ekd@%hD_y9QClw_10=qAAI1I*Qt>SnkEZ7d znH2!MpP+KHVY}hT)A!yhfBHO~BS&nm#=Q2DhY9y=smSc1ZtlhskfB8~4 zY(&b#HTt18t>_-m%cVPB(=CV$unXfw_>c1RDz9E1`E z;5s6ih#TI-SYdeCZFu3mAu~_+=*VP}mG|OeZ_=kRv}N3)RmUbA%pMtQ!4rCz1%YBh zf`AG7n>&jN@>M6h<-fdS!$Mq)xsFdy=rcxSPLzr+{!D|>XEZlLHmBm507YAi9qwUS zW<9|{>hq^FKzjB0XNs{9c>}bZQli)mX9UWzF2@tMAvqr`_IqK5k1Nk`9G{BRYYX+Y zYh8EF$H3tU{OE@KhtHm2c{~>^Mpe4K=3hFjfB6qelQApYod9}rZPhE~2EBCKYo{`Z zO4x(<=;Yb6J;qAkNhzZSeqMW5CnupY6-#`GAdQ%5Ns{NRa@}Npz^U{61$vx2-pPXt z+cda?x!NyxH_MWpWnye{aenp^BskgElxz-l0O>1$&@=W#@7daIex4!*v=4@aIpX zp?|n~v`h@oUcGSMnFte#Lw8Kv@Xk2t^`04V2ZO}z=+5=@Huqqu1gLf6N!BzHHmdN4 zt6^Cs4uO#@W=h5#epc)feJx+@<>QXiRXYC0kSQ#Tv9pWQ7Mv<2QfgLr8OML92(2}i zwAS&?O!v%V=l7U_Z*3QjKID>*ZssF+LeWsl`bf6a*buWi;}R?2y8(%HWVVM`^gaqL zuscHtNH}}WTba20^m*rz74&+_hq1Q6Si?5 z&Xx3$hijZ;%ma7@bt>>m<+a;_7HYP%2a$3d56iY&G))uf1?1pnmC(>;a& zx8}2<2+AN1K}Gz^_)U-rp)z+=b>}OY zO1*Au#E4ESvnGjP%=DPHdNZyI?U>~gh;{Z5jWMU61LuaSn_XEtVowkSsVoH>bUaK~nNht1} zXJWSpH>djG6&^Y_$X6e3YhEpI0wk>5+xth(#+64msf?r3$2Qdb>p%Y||HJ?2KR!G6 zDWLESzeFJDaC85Dz4TKu^p7@zSInxIX1ll)tI~NmMRz!IPE}_1hZ8ldx1Pl!y-wnX z5_kJ>1`+d<$0ZL8N>;nG9R;NWNBzm&=rf_ zA6d`lvuJdff}$B#u`(YWF>Tn6ycV!kWJH0>*SK5F#+B?xqJsRfy@#BKra!hD2KeQP z#^+jJ-rjpMyba8T8G1l#F&>qtBJ&GKHBM3HH6M-#LS@sRg&%L5mpv3bHjPJc&NeGL z)R_p){t1ji4`%6eUfM$dCpJ?#4M3NTVwKf2|AiWd2QY9xa5H?m-8H$(kAHo|VbMO* zZ-lN0`@-urHWBtq=ign!SmGH^m%;8cwT7RY)JBRznR_B0;KF1MR6*2BHTnm#w;q_k-Lk|wE zS9*2-3Lee!>8CYR7`mGsL5p~lmc-@A+W*a*r8PsqaB%+d{ms#Si-Fd|oNhG_)2N3+ zgefCKz8Z5HDxCrUf! zfwRUaUOd~~wmACcd5%Oc|zJ4U&jl0g2A!-0KSB_v;0`_J}DFv zH$vSJTHp0yi268n>ouGd3B?|{Ow0((U}{h1ZgnMF~Ehg8AsXyKByCV;OskXo2)td!CGzZ#FqG#r2&_( z4D-0nM63PA#5snZ)2uB=(4s~Vs9?XSh>4n6?U-7lu;)PT(a`sfc?Ocz2EYHkk3RVD zo1cFE{4f6HzZ9Z&oTK@Jz%$rrlb?`@znJY`7(=g0$-B8jih)4tB!#!Z)R>;Za2Y+M zPIQ7v(wPEhnelXn;;7|5!I<~}UXeU3epHxwmt9A4L{|xB6gY;m$Y}reL?QgM8jRNw zEe32~UsOv4P1-frJ?Oaf|74>vgp3OPzU zVw8!86!+-E%(+uEok0bIkAe7I)Ok`Ry4HM2kYf$OxXe^m77`@=#Xv+~v|a)?tMtfH zA;W(nX^6MB>y1Zi1V(?uMOxJuM)i}_<15*a3F9#w9vMdW#3*G*kO5?esBk(Q1)OFR zd^R_?S&)a8n8aIj@0IBiquXox8_bA{UHQ)tn|#_upS)|LkV7i%QjxOSjmg^XBf1UKT89J%|vcwS> zc$zzeh?CP(Lnc@n-O~yoEQ>rttS)e;nzSfPqq6@}q-h#hkWiY9#h{QCFD@6j?WeRz zK`~VzEFYW(b5ckQJA;UV_|V~lct|)rj6MZI%MHiH03`eYj74BbU$c^a;`x-Mo#%Mk z{{C^A!|p8UrbucgbZp{MNbr5Pjcn?unx*8 z7xn-ic8j4GRCMq(hRKJ!XKK%c5WLCID=jSTaCi6Y=~GCXf#B%f@xEj;woKSK4{90d zPO9k!L8s*lSj*9hA`4BMvCsAKr5Ms;J#zygwWcu~PH9-bYXyiMyLm|l_ZsKqs8uK< zkRZ(hUinQET82?5j~6sfF%<=B9sA_d=O<6!|NZZO^soM@f9=2fYk&RMfAb%!#__vj zy8G@Z5!yH!Ghori*GfN)R{yaEme)9&%aO&%V`oR4Tyz;jGqunM%0XiKm8~Vl2#b1H zrr#w~4&ww5g)}i(-)yfyInx@*CtSye#?lal_BiI$jt&l^@=d4rg7Q>+&4a90s`3_o z{{QywE!OYsx(m2{?BjF4_#9tm5>L{ZTu3SeDH4y7-~oX^1&NoYD&hgDs#@`egaj=T zRNNk*0SbZ^RXkS>m5>m$Bn|DfAc8{FCYhOBoJ>4PJiZ>E`#C=L*nGZg{r`XeXB=?y$?QDvN=&nkmf^{uG5??}5N&wex~-i;d)Up35+*%Gl?rEK zeS7EZ*{4vepZFVp3%9a&iyTIMLK>PTido~8T(WSUq(7*8%M|)eGDaRkbGg^fxF`C`sB}HSOQxy}Md6C}G}nafzPW7#x^i)Prqtu0 zOc!%&|Ln?=VQNHfZ`}Z_9A47|E#<@4_z*hz2xQ2f zm1dvL)O61CwgF57R)bB@ZIQra{4+g8RM?tF>XPNDB*d?z>o{hO5M_d$?YlRB?0wJv z#lQ5Ib)!AMv$NBEP{9bz+AT9`zVa=*!vu5+H|?YY!aP|(1VP!3kKTr*7HC?tH%C)i ze}oB0ljULXX2LpZpL58M{!7|1l7@#!*A_9Ww}BI)u2XyTE4&~Vi1RNl&Zjz=w$d;% zi@@TpL}CjX*C_#7?Gxx8m%%K}I*hUUh|FJf>o z$v&x`j#xtFyU7Li_ji(Nni@Tdq^Y3x<}N0{2;AS<23{7qpyhjL>!#7TQbr=4X0 z(A(Or-U~}hFTC*5{NgO4KEJRO>z###E9=+(+@Jpo|MZ{#Guoh#|2wWhs?(}e@i5#W z0~&y8Zn09ng$6Qy+FxVt%ACRnwZAR_?tujq7=_(HrA zpI<`ABy|x9Vw8+GFstfwVhyZLErFzfsFp@#VP}RW-^yyY7?11~c0_(u_gM+JS|=7* z2)sF3iQyd)@>u6Jh}Alk)F8Avq!FQ#VIvfe=O#I@2x~jAd`ge}DoFm&AzFQ+{S&D( zM|Y|M0>itsZpaz6WE9Mz%wimCs-qJ#E+(?{oSb&dgF>|i=cCoRh-DhbB*UdyYH};~ zni4@tS)iuTg8#^uw6F&F+SkrM{oZF@eCeys`fviZHobJ^>W}@IKl@*Q`?r4U)4ziZ zVd=uN>H4BmDmm^ttl=~s{^tH*<%f3ns^i;6xur?iY0S;SCa-WuIi=Msm-qK;{zu#f z(RD=OEfiZ{cW4u2ZKM$yxu=g%TzJ;H*p~d$ghfh@He;SJP=uz4(6Zt(Q)+S-k!ldBhA`OdlbJ);-a*4GK? z+VE0q4F!8A3 zIB3rkxzK+K=a&QpL!lBMoxkeFjl5ieZtKbXG7%{7wIJUF51OFseo>AE3w8C5q z>(IFd4Y>-TiiA4S22)hcRO3UeeHCclAlnj0WU55qLwLdwA$JjM(U9#j!=R>tV75Lb z)-${&O%8^E5p=X}I0v9eeg+Z-ELc_}nobLlt+R^Zw*$sJ;X_kW=(_QXZ^6=Ix)P%D zmMET~>A2^xVJ8eIV+RKusc=-$mTP!}X-~qMY$4ut~HPT^DC{ zb+uvC!$93+h#hgvr**9y6$6eo;-Xq^=*!~Od~y4hvnmWiod{S3J7-BuMqU#!tWHy4 z%?#v|bQQTZkiAg&@yMN>Pwe!d6s6sozUF)eu%$^7$b}y$ii_9xX-PuwK%*T7w>~)g z{D@DEI+clluBHfE00Ys$(e~k3Njp7*&k4GrkP-cFpknXA{77;HB6K+&?8qr(_YFW&nPG)}==;_f3 z#+IG7y1p=Fddrsb5MZnheqDWt5k-(qNNh<1J)0Ladl+n4`VfEx()J@q001BWNkl$ufjrzGhk)3GHYb#>oN+#@b)csS4T{|;MFMvRBlWSfR! zXPZ@oRp;!ZsqOM% zfq9H-%>s5z`UrELkBgPxiP?q4lcyJ!R$MBYUA!lJ#0e z@YR)5b5pbEi7CgYg`R6o?dZM;lBKNw*T|-~clQ-fT`o_hm~bIFzahq$e0mV{Os1&hBLJ>ER1rd z=AaI#VlK?i-Ay^_thD1tCU2A7s5}@zZRnwaM(Fd&CM?^Ew3z|KCPe|YuC1JT$3Alx5ex3 z_SXLH-tiOmd>lxbjSGN2fn>70pGh{EF=CQ0I~6cx7zSz~5-D9uiMZ0z@Xp@uO~b;T z$lx%uQS|ov`&-4%MWrfSpkbU{le6=;?~sZ_1ue2Kl}R?3?Cptz*ODqdl4CtwF}1UG zuuh$A=Ik%4mNE)*aEiegUWt(2Ou~AoEbru_wZf}R9KUqs^32RU3Ud0)Q$O-2Gc%IN{NcHW4(P6WnleZ|Vm8$-fCAI~s@s}h6L;gL ze_?j$LN8-r5D;f71y)>0vv+x#VFOcd6Co~7rKD?X`Qm4=8Ls^<`)r&JKzoY=kWVKh zV`@+E1V@roX2pUBQj+TrAV5NWg=&ir^y16J*KKTXVHvks-yc7=yJ_#@jxr2RiPc|1 zWY;2*YK~CR@Ir)@z@XH3XZe`gW|d6&;vPKs_LY8uZE(?EHc*5~!6xWw7U6P?m5`|w~ zUN-hJ&e3N367mVz(Zi|*ZzbcLeQFa{T%>@->3(KeyZ8*w`d&e(F8v zp8ddcKmOPLx^K5W^)yJO;d!X;Z36W8H|S|-a1*;$hLT1Gd~14rz#1szDDsT}8)#s~ zQ-6Eb!Z_W9hM`6J%2GTCE?fe&u1aSjD!VztMv_eEvB<+>4_S7aJfOBmJF`00Ps>ru!pAqjn$bFOZh`t!;VDk>h{cYWWkxXHD3_>H1ISJdT;EMSvm z2F_q&(Yb>1UAU1oVywYfRWytuhcXR}V=0Oy04m&n47mV2q8|)smIu=gQGfb9=gyou zO)zQ>xwdxw@~f9_ZLEjZ-+ImAX-BqqY9#u;8hFYycvx!`a`NllJ!qN1s(U9{yv{ZHJ%n_sdcLS)gb^v{@6k0}RcC zWSYtY&~nZ%<-jQ5tA$!5Acf#Sx^x**B~lF=(G(t$Rap)JG(y*bQA!W2f5P)rm&6iT zQnZQ@f}DZM&r~DBBd0}5%g@Qy+s?Vc?UkCVn;9THw;P5q2~kcstltLOC^#fjS=H!F zbUBcOy#SD))Y5ZcdM_at8BMMBQf@K(;3cYBDFHDiht4^`FE=Y)`N+;RESx)sKx1C0ExBuhAKmG`A7vBI^d z!K^`)h&lFk?xXjmkt!O!;Q)`)QPb>&<6`bCv}P6+HkXi7l7H*COP8;p`p>-o*`N8@ zpCeP!ngsNk2!5BAm$4#BQ3os7Q5QW7&lmz8M)nWRQ=4iR4UIM|FE2NF(3-2OIl4+K zi$Iqf%3Y$mGET}(=#qHl2`)|1HJXqF))T5FH!XMJ$*Sl&5A@NRb^g^a=F`|!OXnvh z*%30L@rzR#B5TFK2(6jdD}eA66U9;=PeZ~4-IUcBbi$Z<7Ig>u*~r&(oFV9%weil= zvop|cA%P9W%uwc_!^I1X_eJ(ZL6x#BKvkUlN6xGy!p&y^AwteUHDZN_(npkrvSxfC z0)~)*13l&n$ZHUfMro^^yB)CX?fR?b?q~eSat0y>sW!djQlHy1Gc=t?Eh3Q-xW9b) z>cr&Cb}jYJoPAoVfBnn<_Uq?gAp^`I9n}E<-5zxw2Grk&^9cL+NSwy%cGPpIg*q6% zs35H}=oGi0Wi$Dr!v*3%O84YXIZX|#09OAr=5Q&OkjMu-yKhDjv@GIMxmKTLk>iW)x3W5R@SDt2xhk4W^jz_(qIS;{TB{7RhA zO@E3!1ayPZfQnW0%1t1EgmsCIt+a>9EwckcrHlmS1RG^V_vS_ScX${Z1=8^VXQD?c zN!z@~minFPqso*ZGe>cs0?|&Bf?{K^HP`>m)q&!UK%;_p_9q_4c!LoyJ=k*C)IN7{eGBAMT>fI@%(aO$y$rs4wUR zW_eZv<;Wr1?!oga60(e)jz4zSvcXs#w&foocl5y%r zi@-L3e?6M3$Er8@9-ez-fDYKo^|ytki)F;Y(o%MgAlrTxf-)a_3&d&a;I#njPw-1k z;}fa}g4uLB=asZ_hRir^k41qeiy{6&SYn3wHs;fFi1%RqByC>f^^3pkpQ)<6%7q^o zKo(lU0!aWCDj9uy7^RE~JX46SL3?l$OyN*%vM`7T2TT)g>qbImsYZfof~g%j3b;t+v2kZ@~nZ*F1f!gntI!(aR-i;HuZ zW=(>RBBv{wlLmX6-6I3E?;Jp*kf+Nb*rEj6p*6IN{tiS!WCNUV0|S2uCqJk6MFZ>+ z0Sz6kw4xM2O(DqwiZh{_16UR7+8CrK_vAv<%?2&A1dq;w1AUfkP51?qP^SJ@jJi_Q zP2@_Edhnxd{-Lvgm^#bf+KUaU9auYi$%YADq-m0w$M5d$?XGWZSS{syQA(ys+0UN$ z@>d?_-Q0C!b3?O)ov=PL?YwrEwQ+$k%TqEtsmv*=YCA(z9(8$mJLx4b$0$g>PNB z@c!pM@Z}f3`b+=j-%X#GBr=rF0CWciOh=#oHe)Ib92{IrCx`HgJQoPG1@x91r_AG(j zzFpGgwoCm)yb$eLj$6)oH}Hyu@TA${#Ifh7^>x!2UMNk+G-3wvm}XaT?Ek=tHl3``a!<`BNYJ*suP_ zU;EAf^50IKUL_q@ZfG;OYg`7IVaGc}@`;nMb#QBQ!5%+2yepK-VO>d`Yd{8gT52gt0^;%!P|Z(%7l zbOajh1n7QcafogwN&K)VQS*w%&jWU)Bfe>1sG167!RTNnE*T$lk(mPJVA#P9V?s}^ z<8(ffN(isW`TWd+FXm(}nrW>|=TRbI5e`I5Ko_DAKLcQ7JQO;@Ja74-6U(Fq;>cWv z0=XRXRh5ZpDNmb*+ER3UlDx(9I~Om0_`@GIC_nkhPqKL4&e5lFx-8m$Ur_`|Ru%kq zKOQllyVu7*-popar5TV4mr`mbRn+>q8M|hGN!mefIM2xJqKMHe98NLzsb&A9tHeyN z6ldHKuoMl;K_>XrFuH@;09Pq(@+mk3PLBn1vuH}>N;B|vqrd0nu!nxVUf(mx=1wibcX%Klp|3y zP`g}l8oLS-0@L0?tw1$lmn>#i(p%Dxl4 ziS^Ab0KujE+MsSPS?Y;@1Bbv=yE?YW=tU^*@v9sbTMmTSZQW_1J z0RmIRKA{1?pk@5f!Nx!ql%;GFPyY$@T!OcqGf;oH8n0a%n*=b%--s!@7tsf4{5DAWtb>ZmM3DmmEE2c-@c{C=*|n z8IOrlokq~$GXTA<@GCjIdVcwjzVyZ4_>E8P-EzZX9lnt@ z>|^8}J3zmoZ4hQ=K+q7|gQ2;ppZYu9bNkl9w1d|BF8g%2&I>jR#DQ0l>+az6{TQGH zCPI?r*+7r>n?EALIDL%m;OU&zO$b0Mg@Z`^gI8VWBs6gk`?r5~61YJ=mDq)Pc7_=Z zVP2xU;mxmtT^HRUqa0G9$SJc*YbSEY7e{1)s@IhXnz=i#v3t(&q^CrHp}6R17t}H9 z8Rd(Y9t%@c0d@vQ8g2H#+ynHLYu6pJxV&=e*FW`}|L5~xIJvZhpc(<< z)ZVgydDNA-zkEH=M4Dk297B&daNJY87UweKpEz!*6Ny$_9madlq$ zv^9Q!3wNzr9Gt!WnoZW(ZY};~*v`xV5|;UU^u3D4Om`guXu|-*TGZX;S@~HMJ)YcE zmyQ0NgMgueV7Z2-5`CkXvdKaqYu|8BBh*UH`6<4Z;jb(+ zGl=h>&#;*yymA;_f;tT%a#D5aJXI^hFa~`_7VU}o(6kT<-QBy_E?>6!;JFWe;QTjV z`B%UE@9l4~)6AjH@>pJ6S=(GsUXPP|>?pJ@segggkldufu(d?6*pHrzz2%|Dj>T6j zIzlxICn1x13(EZzSa=c;7vznB%m$+ZQ&Cn2#E2hE4swV{07<0T9$Jrd$N4Fh$|Y*y z(jZCh6;1@*foLQriJz+B?RZFXD1vIomk9k+1elgY;3;T!L6v=Xq`PJ8<7nHyVMzxVt9>;Hb? z%VsXex`|2(9;1FnH_qL2D(2(l9y>r|3)*SqZf8HMkMh(#ObFWcLoX7f+6OC1+1dd{ zTo{XjAb;6=4EW>f?@<00nE2UFKf%+uzp@F)WQ2FDJX5X8c#w)HO9XE4yZO-Ke{+{c zG29JA(DV~hLB|t%q>@`>F$=*VweI-d3h71=(k`W!vufm5s9&Bkk*zp*=&q+g5rWe~ zYJ=5~L)mluf!~~?nbTSo(*4z|n! z=fQ%n5Ma_3f7JIuhR7w^kbnEVIf9b`q1|C;0^3;a1MW8qUSm4WPj)R{CzCgd$dENao=duPI zPVpl^Lq@=nTNqy>kLz+(1KNZ(V*Ko3(-5o#cZlU$RHBkyRhfHb$;BhE9X}@=7gXiP zpUj7Jw|VQ1CLb=1aMT?`nf6Ccc+uk53~y;U(JFf4YXk@0B%#q57gZ&{5aJ7w0a#=@ z!k#+l2`L_D78bwy_4Cg^|NQ#qo~W7Nj?w>7(?q`m?ir+!dC%@_Z}sSq;bY<+J3wpo zZt?1XRImF|XZIZ14W~0;8UWsV?rHtinCMM&5q_b;#dQ>*QSp$v^tSG{b)qyWFxo|+ zuF7mbGCx@2HTV=}+kV+yjH=OqmRC?Rlsfw<2T*5SQBvzc|EA1x5Yry1lCXx5kD^QT zQ|umNJYsSHS`ouhaKckVgU|A@0Nn+M)9@8X4xI4LP;pT)VKRWeA}JORo$N|>NnI%z zniG&kho z8Xqo(9ZvCvgYW=LgrSl({*LS~dh6Vb5@rkUFYE{nI58EiIDsmFGdbAF8FPyA^hz{n z7J;IazhinOD}HdaIb}p;NBQ%}3Q?DqzjG5GUqlWW@kMD#*kg#0;+#kp#rOmbIrDsT z-NNGHE3bU}3t#-wAAaQVidD;H89hk+is%mYK`{V>Wf=n?*X>?rhGZ3|?&1a(I- z)~8lh@zxsNJ`PZhtxYzPWY05*2cO7XOnl39GCNAb`*N_9^-lY<6_pg$vdQu4Id-k9 z6dV-tD*o#L?NyRu9^lo@T_l8LiT2Ik8vA1xqT%I%!XDULxlZ+n&0VPx7DaND z9y{v^tQme;wAmX{wz24bgE|tZPR8#|zDB_!t6u z)L3H;_(1QyW=F*+mT}cf^R^#PJ7r>VkwkStZ%2oGE4sJYxoJlaVhU{90y*)oINL zF@=XcCPnTsMF_KR_9gNa7KTYRN^02rmbYe!N{5vWp)_Yy6_lDWDxjaWzAzvXr8+l5 zw+TP%3*HUlZ0+xdycFN*)h%<9&3m3mzE#ZG7>^ z9nhKzC<8lsuKt+ig;{FFGpA3(F*S!*bdAuAV4G(dQ-n@2!2#$TZ)FM3_9u)7bRcGd z2&xANU=2s zZ9S-G1`yKJdYp0(*&q4Ejf!sOL`XQ3p)AE)8@+b6qokalUzE9x;dI)$fO=-;w?F-v z7r%Z%Ax?*Kz)}eFG50>cIh3o^!J~)ac*vvoHwGSkJ;rJhton*;7Szy+slYDx^@!8dfP7 zDJjOnDH`&&oceSe&xW#KfdJi@rl0E&(+KtZ^q?uCq7(T?)x@qwOP zIOpQR6`@?VPtG5sSiO4f+T`@i=~HK%c5wci-}>C={-4nq<2#c@@fhGvCYY^LK=A2%h)X@DWtv^+molK-(}nLNjfmE^L$B&c$&*&K5d#a>f0&OxRKA9K={K zD{@)%w)n38*$`Ks1|oo!CDZ`WgTSh8Gae1x=ES}A#@QAclB(a>I zVHXmjPf49j*$zoO_n!BP=>`{8g>-i|_F;@1dxyHgC?2Xus6s&Qq7Yn1JckiUR@eK70I*#1^;Iq#n zSDaq#X_`4D(|?+2gq2E_#`BhtSIFGlLI4)D_+MRA&6k?uYeRrK7T-MLp2OrAXpXIcXY#h8ti2Y8fo`L` zVeKXC3h~o$Z9OGMjQQM4=fC#NmoHpf+fiQZtcZY82dhjg!z>&spR_p+1pZA*dEIUd zt@i^ZwSO1T!=^gqznWX)_w&uO76(lFfE$uN$SwrmCBwq8pxn?f!Zd!y-NnXbhmr<c;}QJ6)6aE!F0_Sd_JALf^5guai33uapA&+Z=8Sm`i;#2 zXIi+zeCw49Yil&pacD4W z=Jr1!&nFJhqkatx$`x92M?5<|8b=~ONCs|_0ean)#rZN%B?}3VF#tK+)5e0i`G#pT z27{*z3qL&_k3W%pV`vo86r3zPOiZyd=yCg7%CYC*Q0f&_-!Q7XTphf{ki&@tZs8{3 zW({Zf(TO-$Zfj@v{Q2{a>#}>6D&04Z_=YD7&Nb{uUHk-j8qJ`L^fkAQ(J=e-qk81l z$hH;a65}VQXXKmQRRMH6xzw9 z*=8$F=rG*}idsPONe&g%s z4KL>Pja5%eJ(l)B?0A>lw=zbPGhy_2z4y8AaNiRL=JEdh z|C8{1;s6bKh1${TKd=~y@N~JK9U>6!BguL_&_P!w_H+p6&)TtEUKni_0{nWhj;pBBVxjgB#xm)pF3Vk?e zdN-HHqy^rk(BG#czPBvn`y_fxuipY?E!9cUpf>WX-}e_bUgrlyxj)f6B;;KR{pKC{ k7SQ(Q8NK`V?P-Dk4>HQR_s)t>MF0Q*07*qoM6N<$f;bVtU;qFB literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mars.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mars.png new file mode 100644 index 0000000000000000000000000000000000000000..d01ea08872ca7e9cb1decaac34cc7a91cdef76bc GIT binary patch literal 66839 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 bvIqVjxCv1IX;u{a00000NkvXXu0mjf6p+Kh literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mercury.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/mercury.png new file mode 100644 index 0000000000000000000000000000000000000000..ccbd0fe1d13beb6a782052dcd0e93044d0377d9b GIT binary patch literal 254995 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 z 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 )<)-Xc9o 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 H-1Ty@Lq(^pU_rbJ?vyv*YJ+RH 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 UK3PRPga7~l07*qoM6N<$f{zLad;kCd literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/neptune.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/neptune.png new file mode 100644 index 0000000000000000000000000000000000000000..bd3cec8d9041bca14df9dab0e8ddfee4ce8dfae4 GIT binary patch literal 60626 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 T_=B?#00000NkvXXu0mjfkQKdy literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/planets.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/planets.go new file mode 100644 index 0000000..5393d1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/planets.go @@ -0,0 +1,122 @@ +// planets: an exploration of scale +// +build !appengine + +package main + +import ( + "flag" + "image/png" + "os" + + "github.com/ajstarks/svgo" +) + +var ssDist = []float64{ + 0.00, // Sun + 0.34, // Mercury + 0.72, // Venus + 1.00, // Earth + 1.54, // Mars + 5.02, // Jupiter + 9.46, // Saturn + 20.11, // Uranus + 30.08} // Netpune + +var ssRad = []float64{ // Miles + 423200.0, // Sun + 1516.0, // Mercury + 3760.0, // Venus + 3957.0, // Earth + 2104.0, // Mars + 42980.0, // Jupiter + 35610.0, // Saturn + 15700.0, // Uranus + 15260.0} // Neptune + +var ssColor = []string{ // R, G, B + // Eyeballed from image + "F7730C", // Sun + "FAF8F2", // Mercury + "FFFFF2", // Venus + "0B5CE3", // Earth + "F0C61D", // Mars + "FDC791", // Jupiter + "E0C422", // Saturn + "DCF1F5", // Uranus + "39B6F7"} // Neptune + +var ssImages = []string{ + "sun.png", + "mercury.png", + "venus.png", + "earth.png", + "mars.png", + "jupiter.png", + "saturn.png", + "uranus.png", + "neptune.png"} + +var showdisk = flag.Bool("d", false, "show disk") +var canvas = svg.New(os.Stdout) + +func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) +} + +func main() { + + width := 1200 + height := 200 + + flag.Parse() + canvas.Start(width, height) + canvas.Title("Planets") + canvas.Rect(0, 0, width, height, "fill:black") + nobj := len(ssDist) + y := height / 2 + margin := 100 + minsize := 7.0 + labeloc := height / 4 + + var x, r, imScale, maxh float64 + var px, po int + + if *showdisk { + maxh = float64(height) / minsize + } else { + maxh = float64(height) / 4.0 + } + for i := 1; i < nobj; i++ { + x = vmap(ssDist[i], ssDist[1], ssDist[nobj-1], float64(margin), float64(width-margin)) + r = (vmap(ssRad[i], ssRad[1], ssRad[nobj-1], minsize, maxh)) / 2 + px = int(x) + if *showdisk { + po = 0 + canvas.Circle(px, y, int(r), "fill:#"+ssColor[i]) + } else { // show images + f, err := os.Open(ssImages[i]) + if err != nil { + println("bad image file:", ssImages[i]) + continue + } + defer f.Close() + p, perr := png.DecodeConfig(f) + if perr != nil { + println("bad decode:", ssImages[i]) + continue + } + imScale = r / float64(p.Width) + hs := float64(p.Height) * imScale + dy := y - (int(hs) / 2) // center the image + po = int(r) / 2 + canvas.Image(px, dy, int(r), int(hs), ssImages[i]) + } + if ssDist[i] == 1.0 { // earth + canvas.Line(px+po, y-po, px+po, y-labeloc, + "stroke-width:1px;stroke:white") + canvas.Text(px+po, y-labeloc-10, "You are here", + "fill:white; font-size:14px; font-family:Calibri; text-anchor:middle") + } + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/saturn.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/saturn.png new file mode 100644 index 0000000000000000000000000000000000000000..4281478e8579dd71122db275d3bcab153ea3c189 GIT binary patch literal 110922 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

AzX)J#F}=4hzh5_q9KGL%S2FoaCW)I0SfO$Ms(> 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%0DR_D;}ap|h#Z)Xg(|3MRA?S)eKDC~rdL7FUyMWn z4)@WmoQR?d7_!Kfdg02YW8MfiiuyW*(55MtS;XNFIM)}G((w`>ui$CglNWjkAq>bq zO)gqU>MVo_gi}VI4wJk(pCuO4j} zg-rnjNyiC}1hcaWE#W-#9}(0S0~5XLQKZxBGQ5fg!vR$C-6^7-e=Wq&J0Eobgc77@Iam8cRq+ z$a8cbTC`|DBUS;Uf`5w)TH0b|a%Gpb_c+m&E)MxHJYLfx-RtF07ci&@&jj81+&nt8 zx8KPVPIEI1(<+5zJn;EjgH1J{zG*9F%n3*&8TyX=2kzQxB9?exGpp#0HiYH8@~7Fh z{Qhch-LcQ(Gb+|I_G4b?Jvd+_5*;nxnCp#>99sbZV{I<;Up2iO9{ze42Vj1z&yE>U zL>|95_wyY_9JA5w6Y2})L((=RU;dETo#j|I110BBM(^dS9(|HE*47s2_!sAZ8XT_ z49qaGWmkN2DZeu8nM3ymvbYFs^0O}DmMHlpx~7vLnTiuYh05Y{&KDQ&=VE-_S18)f z^TMJBOq>lH6`aV@#5?BrUi{$w7Oq~@iJ{^{uyIHHs0$SV#v!g>J1$&|_dw23cojD2 zfpd%;(h?0OGZ}Z`w>#9%M)2xq;&4rcauoOxqF9c;fP~+6nZxa*!A4Af@ws;7T+<

4AU-H6+H*%@# zOH$)gLaw2X;h3WnLr5fd8JysH0YCy0+^Q((I@FFa_(g)labj)($=&gWF9FD@E;x{* zc9=CX{g6a6y>-XgD5Mi!V9x4JfkZO~YY!XU#6VgZQI#D^4e`vnoQ^cPB*n4eSLSBK zr%59v3laoQhui};I;S6#n<;jxp5dsD6c8#?6vl|JFR-Y*bM9TyN<2J1DXv z5!9i=Hq!D^W(j7D+5i9`07*naR4tO-;=(A{IU?&s6}2YdMAL3-Zt{T#5AV3=!glKi zhgUJU^>F_v42FScFK68V35xLG%^wD?_3MwkBX`VqK_F|>?i1bl!IWK= zFE_JG@z*5k(R|bekF6$Q|aMLz1Nd#kp9nfoOrptj3ubZ z(*IcH5BIbz|Lr~kMhwFO2o<Bf%`eua zRuZ~j{yndEezwD=sc|QF@ww(KcBLTxLB*WFT-$9z!XSrz2`HU{F=iM!M1n?t9EbsO zyNj#D-Fy(zj4jNZo&PZ>D$K7JAC04QfeDOlV*BnUYgC*Ks$RzQ3yF5t`p*BC#maZh zi{a_z0WtuRKyAOe*Yk1zIW!hNa`=jweY%@oXK%x?FJ9Nh1c85e>6@=byA>=~kgO=X zAdpWZ!iTvTnKxj=_fJ$f4OaCfKF?F97KJ9s3myhIoz;c*%w7q&j7Cc^P#QtJ4_UEr zIqxGgO}N_W$0=hNum!NI8p%pczGy)eD&n)rB7T2&ZrIJAn5^?wX&oeNQLlN~Zu0BR z6#!tMTW7IE&nioZj0+y~1Yr)c03#**N;+TDtA(dT+Qgr7+cq$n}|s*(@N@1~lX z^f$Wkz~*9V58Kqz*u)r?U|t49h1DR`k-gv#lii7>9FUU{pU!GY0QQ|u90M3nGZyj| zr{>|h&+hzz8e@?f5i{Ioq%i{_PR`WT`yimz7gu4%T3L4M(-?vXlH1q98&szJvP$~) zE>aPyGPO`3Bf8`(rVxV4@AH?lzise-$Q#W1`smpa{~CSz>5aECLRbV)uaxD=K}N*J z?;BF8Wk++j3&KQjx2Q+GRNOLj<>TB1bnQ-70GCe}0Aze~^siyVeZA3o_GYx$&}Uel zLM`V0=1B50PyhAK`epg&I&0r$15^2HeK-Jy*QA%9>4(AoVJ`#xl`ar&6)*N4>KJgN z%Mc^P1E0yOZ*VN*L-CAhtnZ8;$6KCJ?q<$AE^wRSdJB@iolww`q;u(+7@1lA>C*B;!gk4_Dw^$lNksuk-I}JM2ogv{t zMd3rNLPNUD`M6iYpnHW{#!@`2J5G>z&+}U^r?15tJg=B~NYHNPx&OE>oYIz;Iv4=h z*t<@zy_1`x@E~U7@mud7U$}gH)DKJ&ZBA+Y<3O?u(po&D6sNpD`5I$5l;j1TGI6Mv0)sz%QnENtKI(qgyWU0WrP-(N2bju1Wt~B*QFSqJ$0hxZMvFQc zps{F)Xp(G=f)gW=IS`)&wOlopUQz$NeuIk|L~9klB{Zb?Cb))NkOVFGBG6&qMm z^o>4y761+*!3iX&OkU}p0_j142Xg7#&<;{N@s&u|DGMYp`*kYJ^yVeK@DCo0U->#V zZ4RXY;sfL1$myb^LkJKI*HGvUW2&mt=|?caqJ2f46{WRQmqi=H74L zIsVJOwwWhB{m}9CkB-09E5I_jiMzt2Rs)8CmpT$W-4+>J`>zY?-W_C>VC49F4*&FV z{CvOsF2w%L<9PWvUhIxP>_{3Sj0Zp2HHdc@S(30Lj-Rko=2e&KBJjva^*K{rH-OrH z^`?hMV{Gc?)^F3kyA-++On54zdVVi`#8jUAbPGq*gby`H_cP4mfjwg5?gOfDBl`x5 zb_#_dBT9Gu^kVX5hmF%O-T($q+ zCuc9vpe4hAU%U1IIrPPg$MqX|?)O5!YHJ1o1G*1vPrsf`9BzSU6Dwyq1TWm3ltwHO z%jY&Y-IS-yN$Iw)4%NuvIGK)As;_+HEKTBBTF3tan>YelyS&3qWNjm8unFm5ncEApY z`7Bw`XIG_|RM8!hc|I_W4z(`|@P_ko3zBojg5CF^G?30>V;-IWF~D?w%}-1+@W98^ zDQA2F)T@jStMWsOkOQYeyf?d6REpTtKH#r63mSe z$^;o6x*FiIluTq`h!RJ|*|Z=}4Nb&;nFD6+e(BR$7481m#V=Ygvx4?auIuw-9`NQC z8s}h4ypB|{0Za?Fr5zg#QGSPWFY=Vfai~{3xo;*nkYsWx^E5n>fLvjt+uu80yc~to zyibB#xk>`k?c?VI!*AX5>Vg5;H!FSTdI){E&k(tl&UMhfp8d0KPz=i449Y~ZKaVg~ z{${u0?Z%sGf2r31pnu^w{*fW!qrAHRi^uVcy+f4p^!lme_@!>q|7df#Ed6A^1^;iH zy7PLC1}7cQ{n@_X?aPvQ*AY^@)2=@lEB@8?XXW!9c~m3;3V&D%yK`Ldz=a8sx7oN# z#&Ygh>gTYZ`|?3Tc4Zq%e6W`*XF_^s6`o32G@C;jWEINq^>GtG!JHc>diQiNxL>Vi z(8bJ}jdi3}hGYz5fHFU0W*llnG*mE3Ji>Hl6bEveLM3l9b_sIVk9h-TQW{-XU(NJi z`gp@79e3b9cHEFOYYZQ)l!DpG+}y{2IBISI_E8sW!(K_o9ssT&)w;C+pZ*Yv_nX;u zhQ$@gf+cF<;wF9kNu$XTVXs}wYoyNiU4sO{5}u(Alil0Ziv}E0l=f?{HCr`1a7Uy! zXW#S$bxIS`&;CfhbYa92^NR-<@C=n-V*n2Eqk68~)<#qWEs--OQEx9)LgkvR0-L%M z_=!!MJAh@A&?6U}FC&eAkh4A#7()?G(rq_PM^yZ2;VOm`ic*P0C*OcUfm4MZBxOd# zq;#f2Kj8|7Zasm~s4t=#Ni34QTQx6Xp3v7z-l zh6zO?6o)O0qRH9nFe*$SvXsm>`$ROH;1PvcJ%61uMKc;Zk~sy&De@wu(2Rn42ubG@ z<;Un1+$r!gBBC*~&bgaDm8#5QU;krto= z%(bQpK8z(8063*MC4{-U3MSz3iw6*s2u>I@g2w~8QB?;X?B%e2!h!8lg!#S^R=eCS zruZA2d9BC^C!(TBtUcUiT+ntP!LalN1Abr=)DXH;ktsv6gFR(t^YFgowU6@IuLnOo zu6>xU>kITlSKislC5{?VjW1ZqLBv1{}LG&vmlTMADRy`tSOANb#Y3 zrRf?r`uII+JdVHCSGqY1q4aK0{+qP-CwsZzxBIF&qsQ}w{%Vt*H39V}L#o@1R^x_5TdFN0Bsc9(@6QZe_UHU8%mi$95f)s;P{A72|=`fL@9AUToyAiyVzo6+eJbcV5nT$rPOt@CGBf_WAcn zI~;q!80Lx>{ohEh4}~;Z=7!b7qmN`|=W>m3^|c2Vfj#bkoaBW!49h{pdD}N8FI12| z(@PbY#P;h~kI(h~92&6dltIEC-AZbnJnX9nF<||KD?S7vLI6C+uRSy6=3+{N6GF0x zTa%YX4u&ILcW-&yZ6C$$t%~6JuJLSXG4bFMkWW_ijok2T9ObgnSm)Z#Dm08$KEwo5 zyS``vQ;BEyzFWvK8J=r#tkQ7eupPAv0{{x;Z1%SEuIWJj0CIwAxZ3BnqgmNGN~ zR**oK?XZCd$Pp6H`#2hXY6P=*7J+QUJ(NJ6uJHl`Lkn=Cf*CNB10E0RASYwS&gOI! z*#Z)W6h#n7sAVmajtDBOfI2V*66`IBQ$X4~P9)nlwSk9CJC9Y#IB^=8oXV851H)h< zb$teDb;!eiJ#bd@tC2VgjiD8-?mAGP=%!486TO-4Y&WSJVzFt{eRwzxoM$anJBooX zITa*KY$1VP(1BEkmWrGraum{zw1EwUO0fh6!#o)$G(Qo*Ob>oz^u8K|q|Qz*m_*Q& zgA;gAVRQigz~h-;>CX3PawZthfTg+qUyF`Y~)+3fSGni7Nzd`SWM{h!OzP((UgbPv3uBa43H^ zgUjrm26+6_<3IZJ`03;LkB{RI+2ZeoRV#qM*DW2PDdW5BD%ncTEc44sJc=F4VtT*Uf7!1Dt- zVig04Xv97BF(GAn)en1{@yTUo)+wDk@Uv|fsN^bZS7!(*u(2pCbAnhGLoMO33(vKH z)KOg};UN+5hkCyXTY9{}3qa&thUdBzf&%e<%j|}MogUoKd2hbz?yBD2c86O9LgT#O z&-F$|uAt5dgxvXyO?=jv-X!I1g2wA^P_BXvtIi7-6Dq=Wi6(+H2_~p|<0HqK9(a$; z?$9sqH&3SoS)LULjvR$6z-TF}kft6L2_4LJGmXyKnM0zFLw7o$596}P$K{IQ~}1$KM^;EZG#iu z=$24A!i*5)3hEl=0{+8r@H=#h3o($h73ph6nKlgTAskP6UftS3>4aPVrs^W0V7-m4u7H%*x5% z@?fd&ru3Z?ruMg*VKDOCW5-Q5j~>U*cN^vxo55{|Dsl{fOL9&#txL1EjsJ^o=wEKG z1|YJpx66gXGrM&ES9`&LPyI@V1TAvfm%CZ?n=JTK9TxsZuQqHL_?@l>z*zyvRG^W( zXmZ3bmLBfkmp%lfblC|q1zmXTc*W~kj17-Pzx5H zfTlJSXIj4l<~?cbS)6?5>lv{Tb?1+}^%b(->R|_@z(xi7tC`k~M z(Y!zdawS)T2Ni$PqaKvFyV!Vg1q*q7POWW;d_hj?H|=FV`^h1WbsUCIE=VPK;N z3>fN!jWRwN$Y1JB!7+4of=?akHhBk@j)Dl0hz@y(Uj;>h4Wafdk6dvv?sguU(V{t2 zv@80;NeXOZgEBE3Z|tN;MP}3p3`O$BMSItZmYBP1GL3gJM5Ttz4hA?}7nq@ny&4oj zUZQYr{D1+gnH(5F`#?jPJ2NXKj8Mj^?%-v6q4Meoxki3SQbPsOR1_ZitX8m@$dskg zowI>S$Ig=ktVWQiYlrQuavHW{rt%DoM-4x_&pMFP9xAb5`N1u0mUr+ZUw71`SbxFO zL;+QF(#tJp%d)x(Y`WtuI1BHPX9iU-bghd+;y?myBCGRK+f!8GEC%S)&c6K_MPph{ z&vr1P7E5zylbZU(49qBIAoE4Jw|iq)pKPFZE(bqXNjmCFY7vSEog&*#fROu92!5`< z9*XCMo7WyUt@AaR_VaH3aYt!4d0c;$e(ea|?4McxL-*z73nuGM$w*f`@YL)tQCsqv zmtX7!;m5jd^OyR$;7~b^FZQ7>dF&@TDu@8U3Y-=J836SE>E7J=g%~Jfh!F`-Mi||6 z0P1mMe)7nD$8WuVeECw6xJ@B5KMzB3-0yvGyl^plVevdO>ps38W!3ewsb?4=VQ>95 zLQ+k#K;xFDo|(MU7G_Is>h>jOK+>I9j{A03gdSOfu0b+600!o%VBle@z@J74hrpbp zdPUzFFdfsSIpVIHO8s{KQIQA9b@lCRt36^%QYNg@W$1!M|@A zjk?P(@^5`Nb)4XHw3b^@#E?dZ!YY%rZ~(9+K-dOE`6;(csyXnf>ds$PfY5oqc9kgezhk{ zc0WpNkPCa-wE&LV8K~Kr(F5jAXZP*(Wn0upd;Lhf=R_2{F`}^z!lOMK!}VE`2e%}UENvK&je{68LMDuOw8*W5jr^GMD`c16g!{P zK2-cO9qT+H(cuRP?5%MCdilFM^M#xUItG#syLO>)_+=b;G~&dsx?Zpmihgi1v`IY$ z1Fywj9H|2vk@*kiqn557Na~Lz;^!ukaw1nh`8LM14D*x&o0ja`oH*nX(Z&Mgbg3Dz zLO`sduHX9CN4yUn+h7*phoyOwf+fuf2Lawso1R-j-AQlc7fNEcz z<9HlN3+Es6>2JJ@LBF*~{JGpO1OLB$Ip|;Rb%W0v1MWLs z_vtP-1^U?UdxzcdR|TY1ysOymfwko5+|cXZ?3m}9@7R4eYNFO8pyp+BI@;%{rf+OU z5j3J~7Ehb(C!^6_;P(ZtzSN$s;c+&uU)MBSREZP!9p7i(NpYRHF_nxdc`=E)+yT-b zFdilz{4zPT3mb>#(Jy^HRLp9TzV`0%q^B0RI9Fg5k!)ms=6LmX210+Ayz#;n|ND-2 ztY8|G_qblvtKvy*-Kv&uj9Usnvv^G67JjF_NRGwnIWCG@TvHS_1Qi&{AuRpiQw5j{ z41RTiQ{n)lBvefL9?a&3M<{&uKug@6)E}g~Q=5Y$O?*W4<+X-k27-L5T1Xp`fvNC(LWP0m;)EZy^dp^cVnDn3CP-!cQ+pzzPfKB5v}0-90^kxj z(F;*}V@JTEj)An}A>?|~C;YJ*%nf+lDv%EbPNNR(5riQeQbh`$n;Bi4hmcEv!5&ol z;vZVSl;aQs9LytmQ={-sW>uiVUNqCOKPkA` zAH{6obbt(X5!F1Uf`6)@8R-dEoEiUki1M4?Nui`~@6K|lPGrdSA}@*>F}#5X#i*!L zJfO@&YZVr4q7q)uTKQ7!8JB3kcgz#(8IEVY+*bx2^-3kD*L z6BY0pquyOM(9y*9^Z-1xkoZL+cpAL;)E#`d>dmt7r?K-Yb=bI;Q~EjtOnf23G?Y(2 zn^&>mIM)|^@=3!}26t#ltsQ{CM9AM9VCn+wuHwwSs@-VeF|PyI@;1;@+BaoGUy3w<8!nGOJs zCFFnJ4n}>b|2^a0M;SCOFh6H%JR9WhXHjWUgw)Tx*+#kLy)GS&q*0bm_V+2^C(kru zalim_J%?r~r+u7YEqz8G8)8NNh%*(}GC+u~P%PEY} zlH8q0Zq~yjo&}7v(t+#@m>|IDZFnp)$eq4Tmn210GO}E-QUN9oA)z8p2?ptDW=exx z+66{q&{|R~_)$?L?#Yrao%ba>bbY`m8poarCbS@d8IlqK26eqeonA98oH*1gvDdKA zR#HQk;S|gemiS=&sU&DS0}aB?ZGjp2bf5N&pxTk3Pts{L;nIb7=?=L@ykP)iDjX6# zfJiL@o03dTfH4`nC>lY|!C>HH)A_s+z+Kycl)SWKI}BIA5rv!zupPf`GNmXcP^ay+ zLmTaN!vsI+-jOdYbr1L<*9$hB>kFF}VvJdw;;6>9|n*j5uXYrYh+VM zv~PuX#-3Z(dTAFkFtCLRK1G4Q!LSX!|i=HW#4=C_x zC%ayf5}m*kLpQmbvyMI8IV$QHR&4RBJH@rQD`1Ct!9iCHe3Jh|Wu}j@g_}meJW(;B zG@tVbk+(Z|u#~kWafsitqV2?-!gybzrknNO?7jHX{bL;fXikWjtQ#R@b};`<2Y^3v z9AE4Xl3zv?xWCpbz>lA93`OEAg%nuIf6X!eo#Xg@MfYkmzCKSD1wJlf#Vzoq@ut^U z_K7eqik{VkU$bME1dL|)ifukxs?^HT@&&8AhW_SDXH!>=EFm`HXZ?thRK*D>MW&-c z?Fq{cysQGoQh;sfkuWMO3MVjUwXl=FIh?}<7%OH(IZcc7(!%Fva&>4mGUt*kx?*YH z`SNhAuXkPb-9dpWp?5Krp-qD8V2esT*>2dKUf=e12MNm>r8jT9CuKZ(Tzfl@2lEwz z;LX$n+r;Ejq!Sg|<9Urh?N}0iIHU)4XC30o@TtXA6cX56!gWUhx$H>tBsc}e!p~Vc zx%O(*F5~O&79#-8?vK)FUVTIfPJvVxB^tlGTRnu>6Z2Trh4Pn(ElR?F?^MB<#^lY$ zuwKCUw{`_fz%UFyayW^+n?H86Bj)4<%nATdH$l@~1>IPu(`1lh2`b&mg-;A4r&oZx zc}Q?z%LUJ@_IfM~{&aB`=W`OM{9%5aW7WLOsku`eukQjICzv-HIR!aRXir^7 z@ZvoF(`6_roQ-;$L-CMGJ=5KMPo#1rY;JV z(Gd(J)HNkw4-6YcUK=vaq*`D@9cJy3u}dqJ@y1WUOlqgV_=S_tD-&~}s*c;BQt6To z6?XKRPY&Re7;%>DqmIPLNi|6nTMBU3rgF&vEoqH_#Dp;3RS|UO2D~l|bS#WVbta)N z_7qf5oI^S!i&Ywt!&oP*x&siZkRz=fZzQ#1!0o$U4bXJT7p~ZGl5(w_st4_i@LY!h z`5IGF&5YpCpWJQk4@O^`LLBgk4ictUr-fbV#MZFxFxnAuO;i)Eb6#m=e__<#D z-4Oa%hBg2IAOJ~3K~#bqfuKG2^L+_~o-cCQZZ-Y&M~w_cr}QGh(VaTYQhf~5oFbv< z0*^AU<)HdP=y1RD0NNL&%$X756h+hxMkAK!UAsC~AyHTzx!AG}su2=f=Wo8OA?;VM z98dNQ{@(9KN3==aytdP5WZBzGBNsf%=sui#lZzkWyPZ%w=c2Iy^H4Yuuq2)7MN;V! z!%MsP(SjkaMtm*;rww9}i2F`y;zC+*GdTA(%TE#psEc8wzUCY>$kG^231# z2hf+I;EZo~kkb-i0_$3B=&n!;DV&O*7ZOY~!*404$SFk5Ri_-M@C+Mw@=K+`ME`4=&MHUQT*psK>hjO|I zn^A;SuBr5sN-(n#H-N`rX8wl|c`@*fYq{j>8@iXCK0bUeckDb(s5^P(r~B+5w|4wN z08ISFoUuWkFwd6j)vXUMdxx=SLK&z-2oJFW;75PC0RMfj^lEfgfND37N8;IH1Nn!G z5H_cb0atn_C`F$8T4(x(J*cmDp+Rt?%8zstinp5xllPZxR$3=^Y@rv60}*%g`^OXa z<;9<_U_{HEt5TeO9%jf;x!no(qJ+?kp>{v9gnAc6VQQNfr- z7MyX(0w??mLK=C(z!;i!AlDAbMq-SqLxMM))3z4ov**AAr%8fGw5ny9NK1I==735_ zETQ{7C0|IOLXO?Rki{VqDW`0!0${#PoXFde0EfEI5O~IU$m!li73xGzucm&~^#x1` zWQjx_L7m06KaH@(E=`@Yf-bb=tcqVH-qLZ@uX49hU&g|!7JVTPo=)Lm<_!L5)>)iz z0}>5>cDE9WGgR$hu%T}l?q&-@>SU3}BXwPuq#Lt|fP?~Ag&wp(rKk^yE^unp#aX6Q z!EJQg3%1h_A4{-wLqd0gkU}6pLYbzdxB@wHkn2952g8Dbi+*$=0&aVM7jYI7iy-5F`Ob)2(Z{b&!sqx5D`F_@|y3Zc_E(U*C%P{HJ*=>j`VSPV?nn zdHAX0xXLl2=G-78=04-Ofv6#y(tOI_i>B0f@(uRqkK<>K8qWISqkEFR zZxU+YZKk6a%90>V31iOQneNs@uFOv2O}MfOkFm6rU%YtI|KFY@*WKwySX|Z9)T=q|f zr2+d?eAEnrx<5XiwuqrCm_L)Rp4kYJm!Tz^d51rKfO$Uj*wf7^9oz7uMP`JP-(V?J zI_orA=uSn5L5qRxARE9q{k<6*oYlhDtsfIw-EYxC6}j1_Q*}zFVJ-{5>sM&OPjw{a z+*^v}8hf{=!Q8wO>Zm9qN>afC@tFc z@~2AxdgY%I6#Bs!LKH{}fAH`0ZYpixoB31GC8NW}M=m>gnh zSzsLrP9Zo!T00&9=*UB;?k;D&EuO3;xGu?-BK{5JdOtL|?UG09RB z1UwA>FiaLaI2+e8AM9PZ05B7McAe)J@YDr^@I+Kg2HMUTW<)Kp>4m5=PB4st0Mw1S z(7FI-N(Hx6dZy&ly{C}q#RQOm)C)y*70DL{2&X7uq`)lrWf_DqjD6)>kEGG^e9`}KJpXVDsaY@s6#nPWQHtY4Qgz)1kx zSu)3p-&Q|#jv|S?_Qw|=-xO#c=hqgCSdm4&@_{UpyG@%{+Q=m(WMjv#bPCRMwFpW{ zLQb{?#OrVLkXc?gF>1@DE&~3RF7S4C|HUSHbyU99ogZ$+5tQ#7$G=}B|De6j{RjQ_ zuT+jR1pz6Zha;?hwc~-Fc=8^!^L;@bTm}iuAy3kGx&n}oXq-GeEGfR*a}+K6_+=g$ z(`3z60otK%$`T|AzwYQsF3e2((PL-#0^8g+Gf+vbGrL%{UP9@N1dkR4{#CdL29(W5 zIJ85y=LZ~!m!$NT>L|lkx~?6seJfcKV@s^48@j`A7kZCB8HyhF^(`|cbZ!1T8Yv7; zx_Iw}y)2W3Ctt`wrA0fsk3SX8$!^`_&m7;cLp};2Zs8|5kx}G@y3hcjFbn`cy3=jq zstFp5KhKfnRJR=ySNCR*XsCculvCLotrWD!7Znjgu~81CIqr*2-2f_kl8CN6)~G`5 zFyKVTElt$9FB_3s_vJ54(htmlkzPE&2|&>j@8)gI6blB1US7inC1fz`V}MIEvnEJg zNHjJep^6}@`5**FWBSsM?syvvMQNncgs43P;nJI1;2FYk1yy?0vf2eFJTk>N9~IP* zz%9|FOB!ZSJZlFGA@!<5!pSGvBn7F=MleuOnB0ZUE^hFK7I|kKz%!pN>04*hOQFDF z6DqgV+IK-0tNP{j^ht@iJ<$h zb{Qd!f5jaQF6o`7|Ic^zaP2t$Ztwm-_iy)YbN{&S0R@dw1YYU7L-sH<83Uee#&=e6 zyOLr=@+n~Sebn&re(sVY7$770ZYJsM$wR-%Xm&r`jM!9?!0?t=Z$oQzQ9YlaHA#n} zo${vwSdlE8QsPym>kt*aVmYOgjzj_*cXF))z>ZD8I$hGjFwkyPu-HsA*t5QwyZ%Gh z=w~g~Sr5{ZwF$dP0gLi5hm!kJybXFAJML@3nCm<)`b+x$w96M05cNsunmw2n?23YSsrqcT;DzAO+3v6M4#|>weFBD56 zBrf%jOOhiXsaDg?<#wNF`yTQ%fyz zkM93Cr4Gy=(p-jbNGsY`94c7CtrTE9R;T17h|a{(LXo7->hE{jKn@Q{XI)B?F|2MY zpzoK~1s;AyCo28`qp~j0E}>_*ZAwCpk|hfr{bhTq+CX%Kpdui!>4BL>6#i+0Ak4vI zA=eAAzeu2ky`(;t z5xObc?@W)-1{h(bf`2|HI-JEec)!Dj)=-f&e%MtJfB|!IsE9ln2?d4)HIit`feKwn z%+Qg80ecDu0Ha;@*DGL67kJPeDh#O4Sipo*l=DNM%B3*YYj{%@Iu`vg35cLhKt)&T}V_ldG? zc7vON?f6OnkkA}}Z*-qW7YWSbz_$F4_C-Fw)w5qH1`qJw@^sE|NaIa=slO4G9Dk&} zau<-{;%AQI|LE5L*B&^2qu(eXDL#w=NV@0UjqRCIe42eIBY~%!_Td+P122`)1@*~* za<}W5a*#%D7j?sYEwg6D8@D-@wCFc3(rIio-lxp9z-B8$cgAA?F-Ey}J(xG}A&w%6 zASY`CgOH$x_H{V73$cc}zD%NE#vc*#cf>+(ycF}J4v#bevwI(r<1~B@7(OwkZWvH^ zO2)*ws6$j1o5}CG0&I{NiaujPA%yIZsPr}xqOEeDMPfIR;XxM$-7%>nU9E)_R!DTK z3w^ruB(2+P!eo5foewVOvUz=uDj+e$0(N%60FoZyfy6*{^R0-9g!YX+WYMy4N}}v` zGbH#V7k8&*FyW-T&-P59GQfaDNO6`uDYJ%17bDecbEa7Oa%ApB_m+0oA|RYx+>g5BLVSD^8|^1Zb8sHI3XY8ScMaS zI;j;5M-NpLNlCZ73^Os~5*<5j4uHokztWc<4ui@d_X`7IoSL%;Y*DU-UdzCn=*qOqo0RLBC1-Ki83eQ}6Ye?(hBg$DFW)KtCEkN1uD z-er9wmmusoq~oZAgcE-1#Tz7dH8aZs)0bw5qmU5^N&_5OdQY=PFw;L6vSZI5>9EPDWLVs zPk0VUI4Ki6+pRHq340w;qR*xj$|1gvgvE9=zsGCnf)u?djco#A*iGc_61?t7pl;;@ zkx^Z9U3N9YzSa!v75|WHlwD1eWKFsE?{|OypY`q_ybGXPP42Qehc#0-4A8ef2(`T3 zJ3GzkP1#>5s{i>;E+>5JI4*ZK&y_d=RGo?CB|_)^d^da$`;k6c_SVO_7yz|F1`q;9 zjL+5iu;_8H>%MfOT~d==yt>jFXV2aBMg=oLn-WyR*i1`7@)WC>L309%qgE=TWgg@9 zn;B1wirnNkxz&hT^HYga3_~@G9Nn2g-LE)jKZc+4xicpuD58sx7`WRhD#^l7_)}VO zd$`RX#y}KKdqGTq9Cb^(Fg^%*B z@Z7^#RSt4!Mbc#{Y|ci;dg_+L@M|F&S4LFb+}{`gv45Ds()`%)AO^7E5D7_BFYe0( zG$~ic7JZD)-jsHpiY46QUo5Ra>lApb`gP@l{%KD~kQigHxOnesC7@Gypl4w)OF4{; zz2t*i(&>m3=HmnKh-&P`#Jxfpd;=W)21^Ij6Lc9X#{tNW%_8;xzq)P^0cr7(Jh9pb>*5cyO!Zc%XR| zQiQi|I4oOXLkrP@yz>~k1%Pd&{i!Ps!2@te$vfpRGtKf0e<(Jqs*hnh0tvHT`RkYp z4{R)gDABO30!%NWag4hHO4IA*mzX*_mB6f_9qOx>vq+z%>64PfKwkq+Mf9K}wpZ{I z{h#jR0w8-rGK55s9Hd2sVjIxy7!*yV6&mpyk?Tw(F@X;#XaLDm!DNOd) zMy__YK$kb-JpR&o{k#!fz)JkB`+M{ZOqck4H-RZOYOL?+OB~p8SVcMHKdE!(>eeZZ z)D8(gRfr}e-M;bd*N=x?)qCi8%QHat9`|?iRQPg+ogIw4H3>L(pTE%(P+elU)KQOC+^c($eqR_oQCcr;( z3eCjgg@FMux@h@TfFY;o&Q%oc1knfyEv>s=Do)`_ebax~l;G@^rizXtimj<}Wm>gz z8Wk`S4NcCDV9-^ka9Sm4*yvCPQe|A7@!X4EU=n47Eg|$!hfvErZB_^$ovdibIc)T% zA~#_V2C<_K%x^~A?RN?B?aOyaj=T*z%OEiLp|^w*az)OybjJXkAynmrlcJqCNQ2R3 zkxN);HEIC>r@nYTo7pszXi)%+qC;llIS?bET^SogLO(-J4+)JXmc2m|Egj(jFb#ll z2!5}oI&Kdp4)6!j&Ug!#_u7e?o?2RK?0zx{pmSX6Oy^-_- zZ)rSt+;mfrWX-8;)ZCN9&r+8utYmGI;N#=rUR#wf<5oU2b{ubYV}}?2zTT@Nm}GNX zKsTE|WM&*m;&uUE>vBQTezR)}kW`2ejXl}K{&JTQQo2U)I!fAKXu|(Vz<;JqFLwIx zLEk4z`vqfwxNV0kR(7r6$+uAQ)tB#v8(Ywi^kpohCAmSubIcdq67$G$-RRd(y&INo zT*#+J1e=$bLPj3^M*uh z12{k(SnEubdQY#*$GXy)m-bvumNYV5ee<}lBL_|tg5b~~q=HF=_)t8iF{TAr*zQmV zo494IB_NQBJs%9jCz_+NVdVibJT317VpSd_^>^kjq43Li*mj)zRzgr{MZg9;mTp{$ z4)x=#^7zZI=jzJ}4s&gwIdKg0RC9j}3oREdXxQNs&mLD^PVRQAH71jEdRZQ;*jBI| zRy(k>!l8nVea%mqU~&m;_#wyhV%yLR1th zDo|w}-3>H6vs(K0{rE^w4$hN3J09b0g2SmJR8Fa&?hkvs5!=kCju~LOIHeJljXH4J z7v)!BzI$1d6zBfTfdHtm9flQ1B7Vng^PVw)^D&QG&Xy&`u1IyUjsZCLl}7;T8`vPp zlBimhyYPljBIrvB6w%7nvfgYe^O)|il)Y|5;S7~pfoM`nTxe2tw{7zF|*4SlDlMUULk|~lLh6H>oI9I ze`I_cHh=Lr{?u_;84S-;4;+6NlkNB%n>M|Fqxb;;L;7!Yd2#L!mE?`y9t68{k^o1t zxcF9w7yWHidFjmqg5T>VI+`Ki)=w`&aC=kc&M5Wu25Q{v*FQME;z8%$CNFW&QJ#(N z?hOF?I$OC@dfkGS#Yv*Kcipift-+Z}%|;_mu{{{(k?$O;_*L#E;$T}^)LEvWV~2$C zr=@?~#dqhSV!!i`2S488VO@S=j9W7{oVYRwt>A=Da6uJ+fO#ror_y~7ze>`5&#J`x zHx8j|$c{smD<<{E#P03z`1o#D-18@HB-z=7Q6NW=nAS$P!jDTMQ_-V+t1ag7kfio- zegWUSk*AS`oJ9aU#1Ugo;n`qJZ=@isVHH{c;mSiw-?*)|dK)H}lqqLmHSh zfm8A08fY3IICAERxP|jfGb(g1b+#qO>fSeCJVOfuZdXKd^wi-Qm30{aNK}L?h81-c zBnSmh(oOfBEfewJ?06HU$zNQwwv9i_@$-1^e3LA_?-l@^$c&0Ea3VJh z(9(=DoBelELSQi;kN#6#ecJS(g zI@avSI-_JP>`u$vfC;t0MqRp)4s}*=mdG$*W81A*#SH4i2cvzMUG!o{6441h#~aVg zn1Nef*BOJe6BZRl4-f97bj_eoSEwTmz(p_IN*BAXfCqMA0{lRSGzMrjPlKD-HPLU~ z09C@gcSE5I?L@$m=mX5D46BSz$#V$`fe_j$3dQf4rTGTr=Z^RGp(OA7>N8xjk@vXW zPghbu#ol{-)BLYl!M4iVA01D1d3SFCpnR#bcqv}2U|crB5+uLdq;D7ymzSDX{^)UB z>mtBV5w>k&H~RyYb)^2MnuHum?}F*s?g;%`qTm<00zm9vYQn@Bqz57E#~P52&oJP z#!JL@-bZ)r9WUv^ld&nCVHQQ$V0camOd~LMvO$qJG_xS0g38pER5UL0g1Y&!t_IXq{3$$)D~q?ghXEF=7?4I6kkp3_8Z<)K za^n!47mzekH}Ng=l1N8NLsS?blpNGe z$Kp^gjJbepw!(d#1w-UpR#%s~)q{Gm%_~=ZZnHbuL2$;{H zO9TwWK;3xNo$A;$)VqpWPQe&*1I7S&&ZbM!Q1AGXp8O#SYz)940}>DetiVy7q6hfH zcE{t0*<1VdjdQXsY<0Q#WM{810GkxqTy9>AakpvHns@N^2SJ0)XAI}$6L8ggdb5{Q3xc$N(*j50#nq9 zCW(1eD6~3?69fagb4Y5@QFl(I#SnP)wF+*1Z#L#hVBu5e#J-IBg!v`WK6hL-3VwQg z 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 Eb4XIl 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%0DJ#6N} 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'~~4 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

  • 3vqFy#=T(G%+6&VF?Z_S4OR}>clMx*5Q;_ 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%0Afwz}73Jn78y&JByUYq2|Tu(`2v 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 literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/venus.png b/Godeps/_workspace/src/github.com/ajstarks/svgo/planets/venus.png new file mode 100644 index 0000000000000000000000000000000000000000..c17ea73ed3f7f6ab752e37505cd4a93118bc2615 GIT binary patch literal 37098 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≈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 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err == nil { + readpmap(f) + f.Close() + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +func readpmap(r io.Reader) { + var pm Pmap + if err := xml.NewDecoder(r).Decode(&pm); err == nil { + drawpmap(pm) + } else { + fmt.Fprintf(os.Stderr, "Unable to parse pmap (%v)\n", err) + } +} + +func drawpmap(m Pmap) { + fs := fontsize + if m.Left > 0 { + leftmargin = m.Left + } + if m.Top > 0 { + topmargin = m.Top + } else { + topmargin = fs * fontscale + } + x := leftmargin + y := topmargin + if len(m.Title) > 0 { + title = m.Title + } + canvas.Title(title) + if showtitle { + dotitle(title) + } + for _, p := range m.Pdata { + pmap(x, y, fs, p) + y += fs*fontscale + (gutter + fs*2) + } +} + +func pmap(x, y, fs int, m Pdata) { + var tfill, vfmt, oc string + var up bool + h := fs * fontscale + fw := fs * 80 + slen := fs + (fs / 2) + up = false + + sum := 0.0 + for _, v := range m.Item { + sum += v.Value + } + + if len(olcolor) > 0 { + oc = olcolor + } else { + oc = bgcolor + } + loffset := (fs * fontscale) + fs + gline := fmt.Sprintf(linefmt, "gray") + wline := fmt.Sprintf(linefmt, oc) + if len(m.Legend) > 0 && showlegend { + if showtotal { + canvas.Text(x, y-fs, fmt.Sprintf("%s (total: "+floatfmt(sum)+")", m.Legend, sum), legendstyle) + } else { + canvas.Text(x, y-fs, m.Legend, legendstyle) + } + } + for i, p := range m.Item { + k := p.Name + v := p.Value + if v == 0.0 { + continue + } + pct := v / sum + pw := int(pct * float64(fw)) + xw := x + (pw / 2) + yh := y + (h / 2) + if pct >= .4 { + tfill = "fill:white" + } else { + tfill = "fill:black" + } + if round > 0 { + canvas.Roundrect(x, y, pw, h, round, round, canvas.RGBA(pred, pgreen, pblue, pct)) + } else { + canvas.Rect(x, y, pw, h, canvas.RGBA(pred, pgreen, pblue, pct)) + } + + dy := yh + fs + (fs / 2) + if pct <= ofpct || len(k) > oflen { // overflow label + if up { + dy -= loffset + yh -= loffset + canvas.Line(xw, y, xw, dy+(fs/2), gline) + } else { + dy += loffset + yh += loffset + canvas.Line(xw, y+h, xw, dy-(fs*3), gline) + } + if alternate { + up = !up + slen = fs * 2 + } else { + slen = fs * 3 + } + if stagger { + loffset += slen + } + tfill = "fill:black" + } + canvas.Text(xw, yh, k, tfill) + dpfmt := tfill + ";font-size:75%" + vfmt = floatfmt(v) + switch { + case showpercent && !showdata: + canvas.Text(xw, dy, fmt.Sprintf("%.1f%%", pct*100), dpfmt) + case showpercent && showdata: + canvas.Text(xw, dy, fmt.Sprintf(vfmt+", %.1f%%", v, pct*100), dpfmt) + case showdata && !showpercent: + canvas.Text(xw, dy, fmt.Sprintf(vfmt, v), dpfmt) + } + x += pw + if i < len(m.Item) { + canvas.Line(x, y, x, y+h, wline) + } + } +} + +func floatfmt(v float64) string { + var vfmt = "%.1f" + if v-float64(int(v)) == 0.0 { + vfmt = "%.0f" + } + return vfmt +} + +func dotitle(s string) { + offset := 40 + canvas.Text(leftmargin, height-offset, s, "text-anchor:start;font-size:250%") +} + +func init() { + flag.IntVar(&width, "w", 1024, "width") + flag.IntVar(&height, "h", 768, "height") + flag.IntVar(&fontsize, "f", 12, "font size (pt)") + flag.IntVar(&fontscale, "s", 5, "font scaling factor") + flag.IntVar(&round, "r", 0, "rounded corner size") + flag.IntVar(&gutter, "g", 100, "gutter") + flag.IntVar(&oflen, "ol", 20, "overflow length") + flag.StringVar(&bgcolor, "bg", "white", "background color") + flag.StringVar(&olcolor, "oc", "", "outline color") + flag.StringVar(&colorspec, "c", "0,0,0", "color (r,g,b)") + flag.StringVar(&title, "t", "Proportions", "title") + flag.BoolVar(&showpercent, "p", false, "show percentage") + flag.BoolVar(&showdata, "d", false, "show data") + flag.BoolVar(&alternate, "a", false, "alternate overflow labels") + flag.BoolVar(&stagger, "stagger", false, "stagger labels") + flag.BoolVar(&showlegend, "showlegend", true, "show the legend") + flag.BoolVar(&showtitle, "showtitle", false, "show the title") + flag.BoolVar(&showtotal, "showtotal", false, "show totals in the legend") + flag.Float64Var(&ofpct, "op", 0.05, "overflow percentage") + flag.Parse() + fmt.Sscanf(colorspec, "%d,%d,%d", &pred, &pgreen, &pblue) +} + +func main() { + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:"+bgcolor) + canvas.Gstyle(fmt.Sprintf(globalfmt, fontsize)) + + if len(flag.Args()) == 0 { + dopmap("") + } else { + for _, f := range flag.Args() { + dopmap(f) + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/randcomp/randcomp.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/randcomp/randcomp.go new file mode 100644 index 0000000..696f67a --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/randcomp/randcomp.go @@ -0,0 +1,59 @@ +// randcomp visualizes random number generators +// +build !appengine + +package main + +import ( + "fmt" + "math/rand" + "os" + "strconv" + "time" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +func main() { + width := 512 + height := 256 + var n = 256 + var rx, ry int + + if len(os.Args) > 1 { + n, _ = strconv.Atoi(os.Args[1]) + } + + f, _ := os.Open("/dev/urandom") + x := make([]byte, n) + y := make([]byte, n) + f.Read(x) + f.Read(y) + f.Close() + + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) + canvas.Start(600, 400) + canvas.Title("Random Integer Comparison") + canvas.Desc("Comparison of Random integers: the random device & the Go rand package") + canvas.Rect(0, 0, width/2, height, "fill:white; stroke:gray") + canvas.Rect(width/2, 0, width/2, height, "fill:white; stroke:gray") + + canvas.Desc("Left: Go rand package (red), Right: /dev/urandom") + canvas.Gstyle("stroke:none; fill-opacity:0.5") + for i := 0; i < n; i++ { + rx = rand.Intn(255) + ry = rand.Intn(255) + canvas.Circle(rx, ry, 5, canvas.RGB(127, 0, 0)) + canvas.Circle(int(x[i])+255, int(y[i]), 5, "fill:black") + } + canvas.Gend() + + canvas.Desc("Legends") + canvas.Gstyle("text-anchor:middle; font-size:18; font-family:Calibri") + canvas.Text(128, 280, "Go rand package", "") + canvas.Text(384, 280, "/dev/urandom") + canvas.Text(256, 280, fmt.Sprintf("n=%d", n), "font-size:12") + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/richter/richter.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/richter/richter.go new file mode 100644 index 0000000..a99bdee --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/richter/richter.go @@ -0,0 +1,35 @@ +// richter -- inspired by Gerhard Richter's 256 colors, 1974 +// +build !appengine + +package main + +import ( + "math/rand" + "os" + "time" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +var width = 700 +var height = 400 + +func main() { + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) + canvas.Start(width, height) + canvas.Title("Richter") + canvas.Rect(0, 0, width, height, "fill:white") + rw := 32 + rh := 18 + margin := 5 + for i, x := 0, 20; i < 16; i++ { + x += (rw + margin) + for j, y := 0, 20; j < 16; j++ { + canvas.Rect(x, y, rw, rh, canvas.RGB(rand.Intn(255), rand.Intn(255), rand.Intn(255))) + y += (rh + margin) + } + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/rl/rl.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/rl/rl.go new file mode 100644 index 0000000..a233d4c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/rl/rl.go @@ -0,0 +1,34 @@ +// rl - draw random lines +// +build !appengine + +package main + +import ( + "fmt" + "math/rand" + "os" + "time" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +func main() { + width := 200 + height := 200 + canvas.Start(width, height) + canvas.Title("Random Lines") + canvas.Rect(0, 0, width, height, "fill:black") + rand.Seed(int64(time.Now().Nanosecond()) % 1e9) + canvas.Gstyle("stroke-width:10") + r := 0 + for i := 0; i < width; i++ { + r = rand.Intn(255) + canvas.Line(i, 0, rand.Intn(width), height, fmt.Sprintf("stroke:rgb(%d,%d,%d); opacity:0.39", r, r, r)) + } + canvas.Gend() + + canvas.Text(width/2, height/2, "Random Lines", "fill:white; font-size:20; font-family:Calibri; text-anchor:middle") + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/rpd.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/rpd.go new file mode 100644 index 0000000..397c418 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/rpd.go @@ -0,0 +1,80 @@ +package main + +// Imports +import ( + "encoding/xml" + "flag" + "fmt" + "github.com/ajstarks/svgo" + "io" + "os" +) + +// +// This is small +// This is medium +// This is large +// + +type Thing struct { + Top int `xml:"top,attr"` + Left int `xml:"left,attr"` + Sep int `xml:"sep,attr"` + Item []item `xml:"item"` +} + +type item struct { + Width int `xml:"width,attr"` + Height int `xml:"height,attr"` + Name string `xml:"name,attr"` + Color string `xml:"color,attr"` + Text string `xml:",chardata"` +} + +var ( + width = flag.Int("w", 1024, "width") + height = flag.Int("h", 768, "height") + canvas = svg.New(os.Stdout) +) + +// Open the file +func dothing(location string) { + f, err := os.Open(location) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer f.Close() + readthing(f) +} + +// Read the file, loading the defined structure +func readthing(r io.Reader) { + var t Thing + if err := xml.NewDecoder(r).Decode(&t); err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse components (%v)\n", err) + return + } + drawthing(t) +} + +// use the items of "thing" to make the picture +func drawthing(t Thing) { + x := t.Left + y := t.Top + for _, v := range t.Item { + style := fmt.Sprintf("font-size:%dpx;fill:%s", v.Width/2, v.Color) + canvas.Circle(x, y, v.Height/4, "fill:"+v.Color) + canvas.Text(x+t.Sep, y, v.Name+":"+v.Text+"/"+v.Color, style) + y += v.Height + } +} + +func main() { + flag.Parse() + for _, f := range flag.Args() { + canvas.Start(*width, *height) + dothing(f) + canvas.End() + } +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/thing.xml b/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/thing.xml new file mode 100644 index 0000000..fbd9d0c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/rpd/thing.xml @@ -0,0 +1,5 @@ + + This is small + This is medium + This is large + diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/rr/rr.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/rr/rr.go new file mode 100644 index 0000000..0f93c83 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/rr/rr.go @@ -0,0 +1,208 @@ +// radar roadmap (via Ernst and Young) +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "github.com/ajstarks/svgo" + "io" + "math" + "os" + "strings" +) + +var ( + width, height, iscale, fontsize, margin int + bgcolor, itemcolor, title string + opacity float64 + showtitle bool + gstyle = "font-family:Calibri;font-size:%dpx;text-anchor:middle" + canvas = svg.New(os.Stdout) +) + +// Roadmap XML structure: +// a roadmap consists of sections, which contain items, which indicate maturity and impact +type Roadmap struct { + Title string `xml:"title,attr"` + Duration int `xml:"duration,attr"` + Unit string `xml:"unit,attr"` + Section []section `xml:"section"` +} + +type section struct { + Name string `xml:"name,attr"` + Spacing float64 `xml:"spacing,attr"` + Item []item `xml:"item"` +} + +type item struct { + Impact int `xml:"impact,attr"` + Effort int `xml:"effort,attr"` + Begin string `xml:"begin,attr"` + Age float64 `xml:"age,attr"` + Name string `xml:",chardata"` + Desc desc `xml:"desc"` +} + +type desc struct { + Description string `xml:",chardata"` +} + +// dorr does file i/o +func dorr(location string) { + var f *os.File + var err error + if len(location) > 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err == nil { + readrr(f) + f.Close() + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// readrr reads and parses the XML specification +func readrr(r io.Reader) { + var rm Roadmap + if err := xml.NewDecoder(r).Decode(&rm); err == nil { + drawrr(rm) + } else { + fmt.Fprintf(os.Stderr, "%v\n", err) + } +} + +// drawrr draws the roadmap +func drawrr(rm Roadmap) { + + if len(rm.Title) > 0 { + title = rm.Title + } + canvas.Title(title) + canvas.Gstyle(fmt.Sprintf(gstyle, fontsize)) + canvas.Rect(0, 0, width, height, "fill:"+bgcolor) + duration := rm.Duration + if duration <= 0 { + duration = 3 + } + ns := len(rm.Section) + cx := (width / 2) + cy := (height / 2) + r := ((width - margin) / duration) / 2 + sr := r + midsize := width / 100 + + // for each unit of time, draw cencentric circles + for i := 0; i < duration; i++ { + canvas.Circle(cx, cy, sr, "fill:none;stroke:lightgray;stroke-dasharray:7,7") + canvas.Text(cx, (cy - sr), fmt.Sprintf("%s %d", rm.Unit, i+1), "font-size:150%;fill:gray") + sr += r + } + // for each section, define its boundaries and draw its label + angle := 360.0 / float64(ns) + a := angle + a2 := a / 2 + for _, s := range rm.Section { + drawseclines(cx, cy, float64(r*duration), a, a2, s.Name) + spacing := s.Spacing + if spacing == 0 { + spacing = (angle / float64(len(s.Item))) - 1 + } + iangle := a + spacing + // for each item in the section, place the marker and label + for _, i := range s.Item { + itemx, itemy := dpolar(cx, cy, i.Age*float64(r), iangle) + drawitem(itemx, itemy, i.Impact*iscale, i.Effort, i.Name) + iangle += spacing + } + a += angle + } + + canvas.Circle(cx, cy, midsize, "fill:red") + canvas.Text(cx, cy, "READY", "baseline-shift:-25%") + if showtitle { + dotitle(title) + } + canvas.Gend() +} + +// radians converts degrees to radians +func radians(d float64) float64 { + return d * (math.Pi / 180.0) +} + +// dotitle places the title text +func dotitle(s string) { + canvas.Text(width/2, height-10, s, "font-size:200%;text-anchor:middle") +} + +// dpolar returns the cartesion coordinates given the center, size, and angle (in degrees) +func dpolar(cx, cy int, r, d float64) (int, int) { + x := r * math.Cos(radians(d)) + y := r * math.Sin(radians(d)) + return cx + int(x), cy + int(y) +} + +// drawseclines defines and labels the sections +func drawseclines(cx, cy int, size, a, h float64, s string) { + fs := fontsize + (fontsize / 2) + ix, iy := dpolar(cx, cy, size, a) + ix2, iy2 := dpolar(cx, cy, size+50, a+h) + canvas.Line(cx, cy, ix, iy, "stroke:lightgray") + textlines(ix2, iy2, fs, fs+2, "middle", "black", strings.Split(s, "\\n")) +} + +// drawitem draws a roadmap item +func drawitem(x, y, isize, ieffort int, s string) { + var op float64 + if ieffort > 0 { + op = opacity * (float64(ieffort) / 10.0) + } else { + op = opacity + } + style := fmt.Sprintf("fill:%s;fill-opacity:%.2f;stroke:white", itemcolor, op) + canvas.Circle(x, y, isize/2, style) + textlines(x-(isize/2)-2, y, fontsize, fontsize+2, "end", "black", strings.Split(s, "\\n")) +} + +// textlines displays text at a specified size, leading, fill, and alignment +func textlines(x, y, fs, leading int, align, fill string, s []string) { + canvas.Gstyle(fmt.Sprintf("font-size:%dpx;text-anchor:%s;fill:%s", fs, align, fill)) + for _, v := range s { + canvas.Text(x, y, v) + y += leading + } + canvas.Gend() +} + +// init sets up the command flags +func init() { + flag.StringVar(&bgcolor, "bg", "white", "background color") + flag.StringVar(&itemcolor, "ic", "rgb(131,206,226)", "item color") + flag.IntVar(&width, "w", 800, "width") + flag.IntVar(&height, "h", 800, "height") + flag.IntVar(&fontsize, "f", 12, "fontsize (px)") + flag.IntVar(&iscale, "s", int(float64(width)*.009), "impact scale") + flag.IntVar(&margin, "m", 150, "outside margin") + flag.BoolVar(&showtitle, "showtitle", false, "show title") + flag.StringVar(&title, "t", "Roadmap", "title") + flag.Float64Var(&opacity, "o", 1.0, "opacity") + flag.Parse() +} + +// for every input file (or stdin), draw a roadmap as specified by command flags +func main() { + canvas.Start(width, height) + if len(flag.Args()) == 0 { + dorr("") + } else { + for _, f := range flag.Args() { + dorr(f) + } + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/skewabc/skewabc.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/skewabc/skewabc.go new file mode 100644 index 0000000..b2769b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/skewabc/skewabc.go @@ -0,0 +1,37 @@ +// skewabc - exercise the skew functions +// +build !appengine + +package main + +import ( + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +var ( + g = svg.New(os.Stdout) + width = 500 + height = 500 +) + +func sky(x, y, w, h int, a float64, s string) { + g.Gstyle(fmt.Sprintf("font-family:sans-serif;font-size:%dpx;text-anchor:middle", w/2)) + g.SkewY(a) + g.Rect(x, y, w, h, `fill:black; fill-opacity:0.3`) + g.Text(x+w/2, y+h/2, s, `fill:white;baseline-shift:-33%`) + g.Gend() + g.Gend() +} + +func main() { + g.Start(width, height) + g.Title("Skew") + g.Rect(0, 0, width, height, "fill:white") + g.Grid(0, 0, width, height, 50, "stroke:lightblue") + sky(100, 100, 100, 100, 30, "A") + sky(200, 332, 100, 100, -30, "B") + sky(300, -15, 100, 100, 30, "C") + g.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/stockproduct/stockproduct.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/stockproduct/stockproduct.go new file mode 100644 index 0000000..a59a15e --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/stockproduct/stockproduct.go @@ -0,0 +1,179 @@ +// stockproduct draws a bar chart comparing stock price to products +// +build !appengine + +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "os" + + "github.com/ajstarks/svgo" +) + +// Parameters defines options +type Parameters struct { + showline, showimage, showproduct, showprice, showdate, showgrid bool + x, y, w, h, width, height, spacing, fontsize, dot int + minvalue, maxvalue, ginterval, opacity, rotatetext float64 + barcolor string +} + +// +// +// +// +// +// +// +// + +// StockProduct is the top-level drawing +type StockProduct struct { + Title string `xml:"title,attr"` + Sdata []Sdata `xml:"sdata"` +} + +// Sdata defines stock data +type Sdata struct { + Price float64 `xml:"price,attr"` + Date string `xml:"date,attr"` + Product string `xml:"product,attr"` + Image string `xml:"image,attr"` +} + +// vmap maps ranges +func vmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) + +} + +// barchart draws a chart from data read at location, on a SVG canvas +// if the location is the empty string, read from standard input. +// Data items are scaled according to the width, with parameters controlling the visibility +// of lines, products, images, and dates +func (p *Parameters) barchart(location string, canvas *svg.SVG) { + var ( + f *os.File + err error + sp StockProduct + ) + if len(location) > 0 { + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + defer f.Close() + if err := xml.NewDecoder(f).Decode(&sp); err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + + bottom := p.y + p.h + interval := p.w / (len(sp.Sdata) - 1) + bw := interval - p.spacing + offset := 120 + halfoffset := offset / 2 + + if bw < 2 { + bw = 2 + } + canvas.Text(p.x, p.y-halfoffset, sp.Title, "font-size:400%") + if p.showgrid { + canvas.Gstyle("stroke:lightgray;stroke-width:1px") + gx := p.x - (bw / 2) + for i := p.maxvalue; i >= p.minvalue; i -= p.ginterval { + yp := int(vmap(i, p.minvalue, p.maxvalue, float64(p.y), float64(bottom))) + by := p.y + (bottom - yp) + canvas.Line(gx, by, p.x+p.w+(bw/2), by) + canvas.Text(gx-halfoffset, by, fmt.Sprintf("%.0f", i), "fill:black;stroke:none") + } + canvas.Gend() + } + canvas.Gstyle(fmt.Sprintf("stroke-opacity:%.2f;stroke:%s;stroke-width:%d;text-anchor:middle", p.opacity, p.barcolor, bw)) + for _, d := range sp.Sdata { + yp := int(vmap(d.Price, p.minvalue, p.maxvalue, float64(p.y), float64(bottom))) + by := p.y + (bottom - yp) + if p.showline { + canvas.Line(p.x, bottom, p.x, by) + } + if p.dot > 0 { + canvas.Circle(p.x, by, p.dot, fmt.Sprintf("stroke:none;fill-opacity:%.2f;fill:%s", p.opacity, p.barcolor)) + } + if p.showimage { + if len(d.Image) > 0 { + canvas.Image(p.x-bw/2, by-offset-2, bw, offset, d.Image) + } + } + canvas.Gstyle("stroke:none;fill:black") + if p.showproduct { + if p.rotatetext != 0 { + canvas.TranslateRotate(p.x, bottom+40, p.rotatetext) + canvas.Text(0, 0, d.Product) + canvas.Gend() + } else { + canvas.Text(p.x, bottom+40, d.Product) + } + } + if p.showprice { + canvas.Text(p.x, by, fmt.Sprintf("%.2f", d.Price), "font-weight:bold") + } + if p.showdate { + canvas.Text(p.x, bottom+20, d.Date) + } + canvas.Gend() + p.x += interval + } + canvas.Gend() +} + +var param Parameters + +// set parameters according to command flags +func init() { + flag.BoolVar(¶m.showline, "line", true, "show lines") + flag.BoolVar(¶m.showimage, "image", true, "show images") + flag.BoolVar(¶m.showproduct, "product", true, "show products") + flag.BoolVar(¶m.showprice, "price", true, "show prices") + flag.BoolVar(¶m.showdate, "date", true, "show dates") + flag.BoolVar(¶m.showgrid, "grid", true, "show grid") + flag.IntVar(¶m.width, "w", 1600, "overall width") + flag.IntVar(¶m.height, "h", 900, "overall height") + flag.IntVar(¶m.x, "left", 150, "left") + flag.IntVar(¶m.y, "top", 120, "top") + flag.IntVar(¶m.w, "gw", 1400, "graph width") + flag.IntVar(¶m.h, "gh", 700, "graph height") + flag.IntVar(¶m.dot, "dot", 0, "dotsize") + flag.IntVar(¶m.fontsize, "fs", 14, "font size (px)") + flag.IntVar(¶m.spacing, "spacing", 15, "bar spacing") + flag.Float64Var(¶m.maxvalue, "max", 400, "max value") + flag.Float64Var(¶m.minvalue, "min", 0, "max value") + flag.Float64Var(¶m.ginterval, "ginterval", 50, "max value") + flag.Float64Var(¶m.opacity, "opacity", 0.5, "bar opacity") + flag.Float64Var(¶m.rotatetext, "rt", 0, "rotate text") + flag.StringVar(¶m.barcolor, "color", "lightgray", "bar color") + flag.Parse() +} + +func main() { + width := 1600 + height := 900 + canvas := svg.New(os.Stdout) + canvas.Start(param.width, param.height) + canvas.Rect(0, 0, width, height, canvas.RGB(255, 255, 255)) + canvas.Gstyle(fmt.Sprintf("font-family:Calibri;font-size:%dpx", param.fontsize)) + if len(flag.Args()) == 0 { + param.barchart("", canvas) + } else { + for _, f := range flag.Args() { + param.barchart(f, canvas) + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svg.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svg.go new file mode 100644 index 0000000..c0b84f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svg.go @@ -0,0 +1,979 @@ +// Package svg provides an API for generating Scalable Vector Graphics (SVG) +package svg + +// package main +// +// import ( +// "github.com/ajstarks/svgo" +// "os" +// ) +// +// var ( +// width = 500 +// height = 500 +// canvas = svg.New(os.Stdout) +// ) +// +// func main() { +// canvas.Start(width, height) +// canvas.Circle(width/2, height/2, 100) +// canvas.Text(width/2, height/2, "Hello, SVG", +// "text-anchor:middle;font-size:30px;fill:white") +// canvas.End() +// } +// + +import ( + "fmt" + "io" + + "encoding/xml" + "strings" +) + +// SVG defines the location of the generated SVG +type SVG struct { + Writer io.Writer +} + +// Offcolor defines the offset and color for gradients +type Offcolor struct { + Offset uint8 + Color string + Opacity float64 +} + +// Filterspec defines the specification of SVG filters +type Filterspec struct { + In, In2, Result string +} + +const ( + svgtop = ` + +` + vbfmt = `viewBox="%d %d %d %d"` + + emptyclose = "/>\n" +) + +// New is the SVG constructor, specifying the io.Writer where the generated SVG is written. +func New(w io.Writer) *SVG { return &SVG{w} } + +func (svg *SVG) print(a ...interface{}) (n int, errno error) { + return fmt.Fprint(svg.Writer, a...) +} + +func (svg *SVG) println(a ...interface{}) (n int, error error) { + return fmt.Fprintln(svg.Writer, a...) +} + +func (svg *SVG) printf(format string, a ...interface{}) (n int, errno error) { + return fmt.Fprintf(svg.Writer, format, a...) +} + +func (svg *SVG) genattr(ns []string) { + for _, v := range ns { + svg.printf("\n %s", v) + } + svg.println(svgns) +} + +// Structure, Metadata, Scripting, Transformation, and Links + +// Start begins the SVG document with the width w and height h. +// Other attributes may be optionally added, for example viewbox or additional namespaces +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#SVGElement +func (svg *SVG) Start(w int, h int, ns ...string) { + svg.printf(svginitfmt, svgtop, w, "", h, "") + svg.genattr(ns) +} + +// Startunit begins the SVG document, with width and height in the specified units +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startunit(w int, h int, unit string, ns ...string) { + svg.printf(svginitfmt, svgtop, w, unit, h, unit) + svg.genattr(ns) +} + +// Startpercent begins the SVG document, with width and height as percentages +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startpercent(w int, h int, ns ...string) { + svg.printf(svginitfmt, svgtop, w, "%", h, "%") + svg.genattr(ns) +} + +// Startview begins the SVG document, with the specified width, height, and viewbox +// Other attributes may be optionally added, for example viewbox or additional namespaces +func (svg *SVG) Startview(w, h, minx, miny, vw, vh int) { + svg.Start(w, h, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) +} + +func (svg *SVG) StartviewUnit(w, h int, unit string, minx, miny, vw, vh int) { + svg.Startunit(w, h, unit, fmt.Sprintf(vbfmt, minx, miny, vw, vh)) +} + + +// Startraw begins the SVG document, passing arbitrary attributes +func (svg *SVG) Startraw(ns ...string) { + svg.printf(svgtop) + svg.genattr(ns) +} + +// End the SVG document +func (svg *SVG) End() { svg.println("") } + +// Script defines a script with a specified type, (for example "application/javascript"). +// if the first variadic argument is a link, use only the link reference. +// Otherwise, treat those arguments as the text of the script (marked up as CDATA). +// if no data is specified, just close the script element +func (svg *SVG) Script(scriptype string, data ...string) { + svg.printf(`\n") + + default: + svg.println(`/>`) + } +} + +// Gstyle begins a group, with the specified style. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#GElement +func (svg *SVG) Gstyle(s string) { svg.println(group("style", s)) } + +// Gtransform begins a group, with the specified transform +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Gtransform(s string) { svg.println(group("transform", s)) } + +// Translate begins coordinate translation, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Translate(x, y int) { svg.Gtransform(translate(x, y)) } + +// Scale scales the coordinate system by n, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Scale(n float64) { svg.Gtransform(scale(n)) } + +// ScaleXY scales the coordinate system by dx and dy, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) ScaleXY(dx, dy float64) { svg.Gtransform(scaleXY(dx, dy)) } + +// SkewX skews the x coordinate system by angle a, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewX(a float64) { svg.Gtransform(skewX(a)) } + +// SkewY skews the y coordinate system by angle a, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewY(a float64) { svg.Gtransform(skewY(a)) } + +// SkewXY skews x and y coordinates by ax, ay respectively, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) SkewXY(ax, ay float64) { svg.Gtransform(skewX(ax) + " " + skewY(ay)) } + +// Rotate rotates the coordinate system by r degrees, end with Gend() +// Standard Reference: http://www.w3.org/TR/SVG11/coords.html#TransformAttribute +func (svg *SVG) Rotate(r float64) { svg.Gtransform(rotate(r)) } + +// TranslateRotate translates the coordinate system to (x,y), then rotates to r degrees, end with Gend() +func (svg *SVG) TranslateRotate(x, y int, r float64) { + svg.Gtransform(translate(x, y) + " " + rotate(r)) +} + +// RotateTranslate rotates the coordinate system r degrees, then translates to (x,y), end with Gend() +func (svg *SVG) RotateTranslate(x, y int, r float64) { + svg.Gtransform(rotate(r) + " " + translate(x, y)) +} + +// Group begins a group with arbitrary attributes +func (svg *SVG) Group(s ...string) { svg.printf("`)) } + +// Gid begins a group, with the specified id +func (svg *SVG) Gid(s string) { + svg.print(``) +} + +// Gend ends a group (must be paired with Gsttyle, Gtransform, Gid). +func (svg *SVG) Gend() { svg.println(``) } + +// ClipPath defines a clip path +func (svg *SVG) ClipPath(s ...string) { svg.printf(``)) } + +// ClipEnd ends a ClipPath +func (svg *SVG) ClipEnd() { + svg.println(``) +} + +// Def begins a defintion block. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DefsElement +func (svg *SVG) Def() { svg.println(``) } + +// DefEnd ends a defintion block. +func (svg *SVG) DefEnd() { svg.println(``) } + +// Marker defines a marker +// Standard reference: http://www.w3.org/TR/SVG11/painting.html#MarkerElement +func (svg *SVG) Marker(id string, x, y, width, height int, s ...string) { + svg.printf(`\n")) +} + +// MarkEnd ends a marker +func (svg *SVG) MarkerEnd() { svg.println(``) } + +// Pattern defines a pattern with the specified dimensions. +// The putype can be either "user" or "obj", which sets the patternUnits +// attribute to be either userSpaceOnUse or objectBoundingBox +// Standard reference: http://www.w3.org/TR/SVG11/pservers.html#Patterns +func (svg *SVG) Pattern(id string, x, y, width, height int, putype string, s ...string) { + puattr := "userSpaceOnUse" + if putype != "user" { + puattr = "objectBoundingBox" + } + svg.printf(`\n")) +} + +// PatternEnd ends a marker +func (svg *SVG) PatternEnd() { svg.println(``) } + +// Desc specified the text of the description tag. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#DescElement +func (svg *SVG) Desc(s string) { svg.tt("desc", s) } + +// Title specified the text of the title tag. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#TitleElement +func (svg *SVG) Title(s string) { svg.tt("title", s) } + +// Link begins a link named "name", with the specified title. +// Standard Reference: http://www.w3.org/TR/SVG11/linking.html#Links +func (svg *SVG) Link(href string, title string) { + svg.printf("") +} + +// LinkEnd ends a link. +func (svg *SVG) LinkEnd() { svg.println(``) } + +// Use places the object referenced at link at the location x, y, with optional style. +// Standard Reference: http://www.w3.org/TR/SVG11/struct.html#UseElement +func (svg *SVG) Use(x int, y int, link string, s ...string) { + svg.printf(``)) +} + +// MaskEnd ends a Mask. +func (svg *SVG) MaskEnd() { svg.println(``) } + +// Shapes + +// Circle centered at x,y, with radius r, with optional style. +// Standard Reference: http://www.w3.org/TR/SVG11/shapes.html#CircleElement +func (svg *SVG) Circle(x int, y int, r int, s ...string) { + svg.printf(`")) + xml.Escape(svg.Writer, []byte(t)) + svg.println(``) +} + +// Textpath places text optionally styled text along a previously defined path +// Standard Reference: http://www.w3.org/TR/SVG11/text.html#TextPathElement +func (svg *SVG) Textpath(t string, pathid string, s ...string) { + svg.printf("", endstyle(s, ">"), pathid) + xml.Escape(svg.Writer, []byte(t)) + svg.println(``) +} + +// Textlines places a series of lines of text starting at x,y, at the specified size, fill, and alignment. +// Each line is spaced according to the spacing argument +func (svg *SVG) Textlines(x, y int, s []string, size, spacing int, fill, align string) { + svg.Gstyle(fmt.Sprintf("font-size:%dpx;fill:%s;text-anchor:%s", size, fill, align)) + for _, t := range s { + svg.Text(x, y, t) + y += spacing + } + svg.Gend() +} + +// Colors + +// RGB specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple. +// Standard reference: http://www.w3.org/TR/css3-color/ +func (svg *SVG) RGB(r int, g int, b int) string { + return fmt.Sprintf(`fill:rgb(%d,%d,%d)`, r, g, b) +} + +// RGBA specifies a fill color in terms of a (r)ed, (g)reen, (b)lue triple and opacity. +func (svg *SVG) RGBA(r int, g int, b int, a float64) string { + return fmt.Sprintf(`fill-opacity:%.2f; %s`, a, svg.RGB(r, g, b)) +} + +// Gradients + +// LinearGradient constructs a linear color gradient identified by id, +// along the vector defined by (x1,y1), and (x2,y2). +// The stop color sequence defined in sc. Coordinates are expressed as percentages. +func (svg *SVG) LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor) { + svg.printf("\n", + id, pct(x1), pct(y1), pct(x2), pct(y2)) + svg.stopcolor(sc) + svg.println("") +} + +// RadialGradient constructs a radial color gradient identified by id, +// centered at (cx,cy), with a radius of r. +// (fx, fy) define the location of the focal point of the light source. +// The stop color sequence defined in sc. +// Coordinates are expressed as percentages. +func (svg *SVG) RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor) { + svg.printf("\n", + id, pct(cx), pct(cy), pct(r), pct(fx), pct(fy)) + svg.stopcolor(sc) + svg.println("") +} + +// stopcolor is a utility function used by the gradient functions +// to define a sequence of offsets (expressed as percentages) and colors +func (svg *SVG) stopcolor(oc []Offcolor) { + for _, v := range oc { + svg.printf("\n", + pct(v.Offset), v.Color, v.Opacity) + } +} + +// Filter Effects: +// Most functions have common attributes (in, in2, result) defined in type Filterspec +// used as a common first argument. + +// Filter begins a filter set +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement +func (svg *SVG) Filter(id string, s ...string) { + svg.printf(`\n")) +} + +// Fend ends a filter set +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#FilterElement +func (svg *SVG) Fend() { + svg.println(``) +} + +// FeBlend specifies a Blend filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feBlendElement +func (svg *SVG) FeBlend(fs Filterspec, mode string, s ...string) { + switch mode { + case "normal", "multiply", "screen", "darken", "lighten": + break + default: + mode = "normal" + } + svg.printf(` 360 { + value = 0 + } + svg.printf(` 1 { + value = 1 + } + svg.printf(``) +} + +// FeCompEnd ends a feComponent filter element +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement +func (svg *SVG) FeCompEnd() { + svg.println(``) +} + +// FeComposite specifies a feComposite filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feCompositeElement +func (svg *SVG) FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string) { + switch operator { + case "over", "in", "out", "atop", "xor", "arithmetic": + break + default: + operator = "over" + } + svg.printf(``)) +} + +// FeDiffEnd ends a diffuse lighting filter primitive container +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement +func (svg *SVG) FeDiffEnd() { + svg.println(``) +} + +// FeDisplacementMap specifies a feDisplacementMap filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement +func (svg *SVG) FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string) { + svg.printf(``) + for _, n := range nodes { + svg.printf("\n", n) + } + svg.println(``) +} + +// FeMorphology specifies a feMorphologyLight filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feMorphologyElement +func (svg *SVG) FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string) { + switch operator { + case "erode", "dilate": + break + default: + operator = "erode" + } + svg.printf(`\n")) +} + +// FeSpecEnd ends a specular lighting filter primitive container +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpecularLightingElement +func (svg *SVG) FeSpecEnd() { + svg.println(``) +} + +// FeSpotLight specifies a feSpotLight filter primitive +// Standard reference: http://www.w3.org/TR/SVG11/filters.html#feSpotLightElement +func (svg *SVG) FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string) { + svg.printf(` 1 { + bfx = 0 + } + if bfy < 0 || bfy > 1 { + bfy = 0 + } + switch ftype[0:1] { + case "f", "F": + ftype = "fractalNoise" + case "t", "T": + ftype = "turbulence" + default: + ftype = "turbulence" + } + + var ss string + if stitch { + ss = "stitch" + } else { + ss = "noStitch" + } + svg.printf(` 0 { + svg.Gstyle(s[0]) + } + for ix := x; ix <= x+w; ix += n { + svg.Line(ix, y, ix, y+h) + } + + for iy := y; iy <= y+h; iy += n { + svg.Line(x, iy, x+w, iy) + } + if len(s) > 0 { + svg.Gend() + } + +} + +// Support functions + +// style returns a style name,attribute string +func style(s string) string { + if len(s) > 0 { + return fmt.Sprintf(`style="%s"`, s) + } + return s +} + +// pp returns a series of polygon points +func (svg *SVG) pp(x []int, y []int, tag string) { + svg.print(tag) + if len(x) != len(y) { + svg.print(" ") + return + } + lx := len(x) - 1 + for i := 0; i < lx; i++ { + svg.print(coord(x[i], y[i]) + " ") + } + svg.print(coord(x[lx], y[lx])) +} + +// endstyle modifies an SVG object, with either a series of name="value" pairs, +// or a single string containing a style +func endstyle(s []string, endtag string) string { + if len(s) > 0 { + nv := "" + for i := 0; i < len(s); i++ { + if strings.Index(s[i], "=") > 0 { + nv += (s[i]) + " " + } else { + nv += style(s[i]) + } + } + return nv + endtag + } + return endtag + +} + +// tt creates a xml element, tag containing s +func (svg *SVG) tt(tag string, s string) { + svg.print("<" + tag + ">") + xml.Escape(svg.Writer, []byte(s)) + svg.println("") +} + +// poly compiles the polygon element +func (svg *SVG) poly(x []int, y []int, tag string, s ...string) { + svg.pp(x, y, "<"+tag+" points=\"") + svg.print(`" ` + endstyle(s, "/>\n")) +} + +// onezero returns "0" or "1" +func onezero(flag bool) string { + if flag { + return "1" + } + return "0" +} + +// pct returns a percetage, capped at 100 +func pct(n uint8) uint8 { + if n > 100 { + return 100 + } + return n +} + +// islink determines if a string is a script reference +func islink(link string) bool { + return strings.HasPrefix(link, "http://") || strings.HasPrefix(link, "#") || + strings.HasPrefix(link, "../") || strings.HasPrefix(link, "./") +} + +// group returns a group element +func group(tag string, value string) string { return fmt.Sprintf(``, tag, value) } + +// scale return the scale string for the transform +func scale(n float64) string { return fmt.Sprintf(`scale(%g)`, n) } + +// scaleXY return the scale string for the transform +func scaleXY(dx, dy float64) string { return fmt.Sprintf(`scale(%g,%g)`, dx, dy) } + +// skewx returns the skewX string for the transform +func skewX(angle float64) string { return fmt.Sprintf(`skewX(%g)`, angle) } + +// skewx returns the skewX string for the transform +func skewY(angle float64) string { return fmt.Sprintf(`skewY(%g)`, angle) } + +// rotate returns the rotate string for the transform +func rotate(r float64) string { return fmt.Sprintf(`rotate(%g)`, r) } + +// translate returns the translate string for the transform +func translate(x, y int) string { return fmt.Sprintf(`translate(%d,%d)`, x, y) } + +// coord returns a coordinate string +func coord(x int, y int) string { return fmt.Sprintf(`%d,%d`, x, y) } + +// ptag returns the beginning of the path element +func ptag(x int, y int) string { return fmt.Sprintf(` 0 { + attrs += fmt.Sprintf(`in="%s" `, s.In) + } + if len(s.In2) > 0 { + attrs += fmt.Sprintf(`in2="%s" `, s.In2) + } + if len(s.Result) > 0 { + attrs += fmt.Sprintf(`result="%s" `, s.Result) + } + return attrs +} + +// tablevalues outputs a series of values as a XML attribute +func (svg *SVG) tablevalues(s string, t []float64) { + svg.printf(` %s="`, s) + for i := 0; i < len(t)-1; i++ { + svg.printf("%g ", t[i]) + } + svg.printf(`%g"%s`, t[len(t)-1], emptyclose) +} + +// imgchannel validates the image channel indicator +func imgchannel(c string) string { + switch c { + case "R", "G", "B", "A": + return c + case "r", "g", "b", "a": + return strings.ToUpper(c) + case "red", "green", "blue", "alpha": + return strings.ToUpper(c[0:1]) + case "Red", "Green", "Blue", "Alpha": + return c[0:1] + } + return "R" +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.pdf b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cc8ff60c8f6b16fe190b14fafbdbbca2e5870720 GIT binary patch literal 234105 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�kpEQBB|qrs0!aVSA=V&rTlbgYubC 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

    v&`Q4+4X0dI)ZWQ&bj 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

  • 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

    U-5HO$n7R9al1uA>I+HThSqdU1utYwCu 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μ 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

      by}jOR_iaY`cc>LIJIGnMQA`hdS{M4| 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

      xz+%KBd}=7s^24GQC#otwQoebz_@ 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

      Hf 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 z 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 Ak^lez literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.svg b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.svg new file mode 100644 index 0000000..46c80c2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef.svg @@ -0,0 +1,395 @@ + + + +Object Definitions + + + + + + + + + + + + + + + + +x, y + +w +Square(x, y, w int, style ...string) + + + +x, y + +h +w +Rect(x, y, w, h int, style ...string) + + + +x, y + +h +w +CenterRect(x, y, w, h int, style ...string) + + + +x, y + +h +w + + +ry +rx +Roundrect(x, y, w, h, rx, ry int, style ...string) + + + +x, y + +x, y + +x, y + +x, y + +x, y + +x, y + +Polygon(x, y []int, style ...string) + + + + +x, y + + +r +Circle(x, y, r int, style ...string) + + + + + +x, y + + + +rx +ry +Ellipse(x, y, rx, ry int, style ...string) + + + + +x1, y1 + +x2, y2 + +Line(x1, y1, x2, y2 int, style ...string) + + + +x, y + +x, y + +x, y + +x, y + +Polyline(x, y []int, style ...string) + + + +sx, sy + +ex, ey + +Arc(sx, sy, ax, ay, r int, lflag, sflag bool, ex, ey int, style ...string) + + + + + +x, y +Path(s string, style ...string) + + + +sx, sy + +cx, cy + +ex, ey + +Qbez(sx, sy, cx, cy, ex, ey int, style ...string) + + + +sx, sy + +cx, cy + +px, py + +ex, ey + +Bezier(sx, sy, cx, cy, px, py, ex, ey int, style ...string) + + + +x, y + +h +w + +Image(x, y, w, h, int path string, style ...string) + + + + +x1%, y1% + +x2%, y2% +LinearGradient(s string, x1, y1, x2, y2 uint8, oc []Offcolor) + + + + +cx%, cy% + +fx%, fy% +RadialGradient(s string, cx, cy, r, fx, fy uint8, oc []Offcolor) + + + +0, 0 + +x, y +Translate(x, y int) + + + + + + + +x, y +h +w +n + + + + + + + + + + + + + + + + +Grid(x, y, w, h, n int, style ...string) + + + +x, y +hello, this is SVG +Text(x, y int, s string, style ...string) + + + +0, 0 + + + + +Scale(n float64) + + + +0, 0 + + + + +ScaleXY(x, y float64) + + + +0, 0 + + + + +SkewX(a float64) + + + +0, 0 + + + + +SkewY(a float64) + + + +0, 0 + + + + +SkewXY(x, y float64) + + + +0, 0 +Rotate(r float64) + + +r + + + + + +It's "fine" & "dandy" to draw text along a path +Textpath(s, pathid string, style ...string) + + + +New(w io Writer) +Start(w, h int, options ...string)/End() +Startview(w, h, minx, miny, vw, vh int) +Group(s ...string)/Gend() +Gstyle(s string)/Gend() +Gtransform(s string)/Gend() +Gid(id string)/Gend() +ClipPath(s ...string)/ClipEnd() +Def()/DefEnd() +Marker()/MarkerEnd() +Pattern()/PatternEnd() +Desc(s string) +Title(s string) +Script(type, data ...string) +Mask(id string, x,y,w,h int, style ...string)/MaskEnd() +Link(href string, title string)/LinkEnd() +Use(x int, y int, link string, style ...string) + + +specify destination +begin/end the document +begin/end the document with viewport +begin/end group with attributes +begin/end group style +begin/end group transform +begin/end group id +begin/end clip path +begin/end a defintion block +begin/end markers +begin/end pattern +set the description element +set the title element +define a script +begin/end mask element +begin/end link to href, with a title +use defined objects + +Textlines(x, y int, s []string, size, spacing int, fill, align string) + + + + + + + +r +g +b +-> + +RGB(r, g, b int) + + + + + + + + +alpha +r +g +b +-> + +RGBA(r, g, b int, opacity float64) + + +SVG Go Library Description + + + +SVG Go Library +github.com/ajstarks/svgo + +Object Usage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef/svgdef.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef/svgdef.go new file mode 100644 index 0000000..c5e776b --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgdef/svgdef.go @@ -0,0 +1,536 @@ +// svgdef - SVG Object Definition and Use +// +build !appengine + +package main + +import ( + "math" + "os" + + "github.com/ajstarks/svgo" +) + +const ( + textsize = 24 + coordsize = 4 + objcolor = "rgb(0,0,127)" + objstyle = "fill:none; stroke-width:2;stroke:" + objcolor + fobjstyle = "fill-opacity:0.25;fill:" + objcolor + legendstyle = "fill:gray; text-anchor:middle" + titlestyle = "fill:black; text-anchor:middle;font-size:24px" + linestyle = "stroke:black; stroke-width:1" + gtextstyle = "font-family:Calibri,sans; text-anchor:middle; font-size:24px" + coordstring = "x, y" + tpathstring = `It's "fine" & "dandy" to draw text along a path` +) + +var ( + canvas = svg.New(os.Stdout) + grayfill = canvas.RGB(220, 220, 220) + oc1 = svg.Offcolor{Offset:0, Color:"white", Opacity:1.0} + oc2 = svg.Offcolor{Offset:25, Color:"lightblue", Opacity:1.0} + oc3 = svg.Offcolor{Offset:75, Color:"blue", Opacity:1.0} + oc4 = svg.Offcolor{Offset:100, Color:objcolor, Opacity:1.0} + ga = []svg.Offcolor{oc1, oc2, oc3, oc4} +) + +// defcoodstr defines coordinate strings: (x,y) +func defcoordstr(x int, y int, s string) { + canvas.Circle(x, y, coordsize, grayfill) + canvas.Text(x, y-textsize, s, legendstyle) +} + +// defcoord defines a coordinate +func defcoord(x, y, n int) { + canvas.Circle(x, y, coordsize, grayfill) + canvas.Text(x, y+n, coordstring, legendstyle) +} + +// deflegend makes object legends +func deflegend(x int, y int, size int, legend string) { + canvas.Text(x, y+size+textsize, legend, titlestyle) +} + +// defcircle defines the circle object for arbitrary placement and size +func defcircle(id string, w, h int, legend string) { + canvas.Gid(id) + canvas.Translate(w, h) + defcoord(0, 0, -textsize) + canvas.Circle(0, 0, h, objstyle) + canvas.Line(0, 0, h, 0, linestyle) + canvas.Text(h/2, textsize, "r", legendstyle) + deflegend(0, 0, h, legend) + canvas.Gend() + canvas.Gend() +} + +// defellipse defines the ellipse object for arbitrary placement and size +func defellipse(id string, w int, h int, legend string) { + canvas.Gid(id) + canvas.Translate(w, h) + defcoord(0, 0, -textsize) + canvas.Ellipse(0, 0, w, h, objstyle) + canvas.Line(0, 0, w, 0, linestyle) + canvas.Line(0, 0, 0, h, linestyle) + canvas.Text(w/2, textsize, "rx", legendstyle) + canvas.Text(-textsize, (h / 2), "ry", legendstyle) + deflegend(0, 0, h, legend) + canvas.Gend() + canvas.Gend() +} + +// defrect defines the rectangle object for arbitrary placement and size +func defrect(id string, w int, h int, legend string) { + canvas.Gid(id) + defcoord(0, 0, -textsize) + canvas.Rect(0, 0, w, h, objstyle) + canvas.Text(-textsize, (h / 2), "h", legendstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// defcrect defines the centered rectangle object for arbitrary placement and size +func defcrect(id string, w int, h int, legend string) { + canvas.Gid(id) + defcoord(w/2, h/2, -textsize) + canvas.Rect(0, 0, w, h, objstyle) + canvas.Text(-textsize, (h / 2), "h", legendstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// defsquare defines the square object for arbitrary placement and size +func defsquare(id string, w int, legend string) { + canvas.Gid(id) + defcoord(0, 0, -textsize) + canvas.Square(0, 0, w, objstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + deflegend((w / 2), 0, w, legend) + canvas.Gend() +} + +// defimage defines the image object for arbitrary placement and size +func defimage(id string, w int, h int, s string, legend string) { + canvas.Gid(id) + defcoord(0, 0, -textsize) + canvas.Rect(0, 0, w, h, objstyle) + canvas.Text(-textsize, (h / 2), "h", legendstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + canvas.Image(0, 0, w, h, s) + deflegend(w/2, h, 0, legend) + canvas.Gend() +} + +// defline defines the line object for arbitrary placement and size +func defline(id string, w int, h int, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "x1, y1") + defcoordstr(w, 0, "x2, y2") + canvas.Line(0, 0, w, 0, objstyle) + deflegend(w/2, h, 0, legend) + canvas.Gend() +} + +// defarc defines the arc object for arbitrary placement and size +func defarc(id string, w int, h int, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "sx, sy") + defcoordstr(w*2, 0, "ex, ey") + canvas.Arc(0, 0, h, h, 0, false, true, w*2, 0, objstyle) + deflegend(w, h, h, legend) + canvas.Gend() +} + +// defbez defines the cublic bezier object for arbitrary placement and size +func defbez(id string, x int, y int, h int, legend string) { + sx, sy := 0, 0 + cx, cy := x, -y + px, py := x, y + ex, ey := x*2, 0 + canvas.Gid(id) + defcoordstr(sx, sy, "sx, sy") + defcoordstr(cx, cy, "cx, cy") + defcoordstr(px, py, "px, py") + defcoordstr(ex, ey, "ex, ey") + canvas.Bezier(sx, sy, cx, cy, px, py, ex, ey, objstyle) + deflegend(px, h, 0, legend) + canvas.Gend() +} + +// defqbez defines the quadratic bezier object for arbitrary placement and size +func defqbez(id string, px int, py int, h int, legend string) { + sx, sy := 0, 0 + ex, ey := px*2, 0 + cx, cy := (ex-px)/3, -py-(py/2) + canvas.Gid(id) + defcoordstr(sx, sy, "sx, sy") + defcoordstr(cx, cy, "cx, cy") + defcoordstr(ex, ey, "ex, ey") + canvas.Qbez(sx, sy, cx, cy, ex, ey, objstyle) + deflegend(px, h, 0, legend) + canvas.Gend() +} + +// defroundrect defines the roundrect object for arbitrary placement and size +func defroundrect(id string, w int, h int, rx int, ry int, legend string) { + canvas.Gid(id) + defcoord(0, 0, -textsize) + canvas.Roundrect(0, 0, w, h, rx, ry, objstyle) + canvas.Text(-textsize, (h / 2), "h", legendstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + canvas.Line(rx, 0, rx, ry, linestyle) + canvas.Line(0, ry, rx, ry, linestyle) + canvas.Text(rx+textsize, ry-(ry/2), "ry", legendstyle) + canvas.Text((rx / 2), ry+textsize, "rx", legendstyle) + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// defpolygon defines the polygon object for arbitrary placement and size +func defpolygon(id string, w int, h int, legend string) { + var x = []int{0, w / 2, w, w, w / 2, 0} + var y = []int{0, -h / 4, 0, (h * 3) / 4, h / 2, (h * 3) / 4} + canvas.Gid(id) + for i := 0; i < len(x); i++ { + defcoord(x[i], y[i], -textsize) + } + canvas.Polygon(x, y, objstyle) + deflegend(w/2, h, 0, legend) + canvas.Gend() +} + +// defpolyline defines the polyline object for arbitrary placement and size +func defpolyline(id string, w int, h int, legend string) { + var x = []int{0, w / 3, (w * 3) / 4, w} + var y = []int{0, -(h / 2), -(h / 3), -h} + canvas.Gid(id) + for i := 0; i < len(x); i++ { + defcoord(x[i], y[i], -textsize) + } + canvas.Polyline(x, y, objstyle) + deflegend(w/2, h, 0, legend) + canvas.Gend() +} + +// defpath defines the path object for arbitrary placement and size +func defpath(id string, x, y int, legend string) { + var w3path = `M36,5l12,41l12-41h33v4l-13,21c30,10,2,69-21,28l7-2c15,27,33,-22,3,-19v-4l12-20h-15l-17,59h-1l-13-42l-12,42h-1l-20-67h9l12,41l8-28l-4-13h9` + var cpath = `M94,53c15,32,30,14,35,7l-1-7c-16,26-32,3-34,0M122,16c-10-21-34,0-21,30c-5-30 16,-38 23,-21l5-10l-2-9` + canvas.Gid(id) + canvas.Path(w3path, `fill="`+objcolor+`"`) + canvas.Path(cpath, canvas.RGBA(0, 0, 0, 0.5)) + defcoord(0, 0, -textsize) + deflegend(x/2, y+50, textsize, legend) + canvas.Gend() +} + +// deflg defines the linear gradient object for arbitrary placement and size +func deflg(id string, w int, h int, legend string) { + canvas.Gid(id) + canvas.Rect(0, 0, w, h, "fill:url(#linear)") + defcoordstr(0, 0, "x1%, y1%") + defcoordstr(w, 0, "x2%, y2%") + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// defrg defines the radial gradient object for arbitrary placement and size +func defrg(id string, w int, h int, legend string) { + canvas.Gid(id) + canvas.Rect(0, 0, w, h, "fill:url(#radial)") + defcoordstr(0, 0, "cx%, cy%") + defcoordstr(w/2, h/2, "fx%, fy%") + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// deftrans defines the trans object for arbitrary placement and size +func deftrans(id string, w, h int, legend string) { + tx := w / 3 + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + defcoordstr(w-tx, 0, "x, y") + deflegend(w/2, 0, h, legend) + canvas.Rect(0, 0, tx, h, objstyle) + canvas.Translate(w-tx, 0) + canvas.Rect(0, 0, tx, h, fobjstyle) + canvas.Gend() + canvas.Gend() +} + +// defgrid defines the grid object for arbitrary placement and size +func defgrid(id string, w, h int, legend string) { + n := h / 4 + canvas.Gid(id) + defcoord(0, 0, -textsize) + canvas.Text(-textsize, (h / 2), "h", legendstyle) + canvas.Text((w / 2), -textsize, "w", legendstyle) + canvas.Text(n+textsize, n/2, "n", legendstyle) + canvas.Grid(0, 0, w, h, n, "stroke:"+objcolor) + deflegend((w / 2), 0, h, legend) + canvas.Gend() +} + +// deftext defines the text object for arbitrary placement and size +func deftext(id string, w, h int, text string, legend string) { + canvas.Gid(id) + defcoord(0, h/2, textsize) + canvas.Text(0, h/2, text, "text-anchor:start;font-size:32pt") + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// deftextpath defines the textpath object for arbitrary placement and size +func deftextpath(id string, pathid string, s string, w, h int, legend string) { + canvas.Gid(id) + canvas.Textpath(s, pathid, `fill="`+objcolor+`"`, `text-anchor="start"`, `font-size="16pt"`) + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defscale defines the scale object for arbitrary placement and size +func defscale(id string, w, h int, n float64, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + canvas.Rect(0, 0, w, h, objstyle) + canvas.Scale(n) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defscaleXY defines the scaleXY object for arbitrary placement and size +func defscaleXY(id string, w, h int, dx, dy float64, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + canvas.Rect(0, 0, w, h, objstyle) + canvas.ScaleXY(dx, dy) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defskewX defines the skewX object for arbitrary placement and size +func defskewX(id string, w, h int, angle float64, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + canvas.Rect(0, 0, w, h, objstyle) + canvas.SkewX(angle) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defskewY defines the skewY object for arbitrary placement and size +func defskewY(id string, w, h int, angle float64, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + canvas.Rect(0, 0, w, h, objstyle) + canvas.SkewY(angle) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defskewXY defines the skewXY object for arbitrary placement and size +func defskewXY(id string, w, h int, ax, ay float64, legend string) { + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + canvas.Rect(0, 0, w, h, objstyle) + canvas.SkewXY(ax, ay) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defrotate defines the rotate object for arbitrary placement and size +func defrotate(id string, w, h int, deg float64, legend string) { + t := deg * (math.Pi / 180.0) + r := float64(w / 2) + rx := r * math.Cos(t) + ry := r * math.Sin(t) + canvas.Gid(id) + defcoordstr(0, 0, "0, 0") + deflegend(w/2, 0, h, legend) + canvas.Rect(0, 0, w, h, objstyle) + canvas.Qbez(w/2, 0, (w/2)+10, int(ry)/2, int(rx), int(ry), "fill:none;stroke:gray") + canvas.Text(w/4, textsize, "r", legendstyle) + canvas.Rotate(deg) + canvas.Rect(0, 0, w, h, fobjstyle) + canvas.Gend() + canvas.Gend() +} + +// defmeta defines the metadata objects +func defmeta(id string, w int, name, desc []string, legend string) { + canvas.Gid(id) + canvas.Textlines(0, textsize, name, 24, 28, "black", "start") + canvas.Textlines(w+150, textsize, desc, 24, 28, "rgb(127,127,127)", "start") + deflegend(w, 0, 30*len(name), legend) + canvas.Gend() +} + +// defrgb defines the rgb object for arbitrary placement and size +func defrgb(id string, w, h, r, g, b int, opacity float64, legend string) { + size := h / 8 + canvas.Gid(id) + canvas.Gstyle(legendstyle) + colordot(w/4, 0, size, r, 0, 0, 1.0) + colordot(w/2, 0, size, 0, g, 0, 1.0) + colordot(w*3/4, 0, size, 0, 0, b, 1.0) + colordot(w, 0, size, r, g, b, opacity) + if opacity < 1.0 { + colordot(w+10, 0, size, r, g, b, opacity) + canvas.Text(w, h/2, "alpha") + } + canvas.Text(w/4, h/2, "r") + canvas.Text(w/2, h/2, "g") + canvas.Text(w*3/4, h/2, "b") + canvas.Text(w-(w/8), size-size/2, "->") + canvas.Gend() + deflegend(w/2, 0, h, legend) + canvas.Gend() +} + +// defobjects defines a set of objects with the specified dimensions, +// once defined, the objects are referenced for placement +func defobjects(w, h int) { + var ( + metatext = []string{ + "New(w io Writer)", + "Start(w, h int, options ...string)/End()", + "Startview(w, h, minx, miny, vw, vh int)", + "Group(s ...string)/Gend()", + "Gstyle(s string)/Gend()", + "Gtransform(s string)/Gend()", + "Gid(id string)/Gend()", + "ClipPath(s ...string)/ClipEnd()", + "Def()/DefEnd()", + "Marker()/MarkerEnd()", + "Pattern()/PatternEnd()", + "Desc(s string)", + "Title(s string)", + "Script(type, data ...string)", + "Mask(id string, x,y,w,h int, style ...string)/MaskEnd()", + "Link(href string, title string)/LinkEnd()", + "Use(x int, y int, link string, style ...string)", + } + metadesc = []string{ + "specify destination", + "begin/end the document", + "begin/end the document with viewport", + "begin/end group with attributes", + "begin/end group style", + "begin/end group transform", + "begin/end group id", + "begin/end clip path", + "begin/end a defintion block", + "begin/end markers", + "begin/end pattern", + "set the description element", + "set the title element", + "define a script", + "begin/end mask element", + "begin/end link to href, with a title", + "use defined objects", + } + ) + h2 := h / 2 + canvas.Desc("Object Definitions") + canvas.Def() + canvas.LinearGradient("linear", 0, 0, 100, 0, ga) + canvas.RadialGradient("radial", 0, 0, 100, 50, 50, ga) + canvas.Path("M 0,0 A62,62 0 0 1 250,0", `id="tpath"`) + defsquare("square", h, "Square(x, y, w int, style ...string)") + defrect("rect", w, h, "Rect(x, y, w, h int, style ...string)") + defcrect("crect", w, h, "CenterRect(x, y, w, h int, style ...string)") + defroundrect("roundrect", w, h, 25, 25, "Roundrect(x, y, w, h, rx, ry int, style ...string)") + defpolygon("polygon", w, h, "Polygon(x, y []int, style ...string)") + defcircle("circle", h, h2, "Circle(x, y, r int, style ...string)") + defellipse("ellipse", h, h2, "Ellipse(x, y, rx, ry int, style ...string)") + defline("line", w, h, "Line(x1, y1, x2, y2 int, style ...string)") + defpolyline("polyline", w, h, "Polyline(x, y []int, style ...string)") + defarc("arc", h, h2, "Arc(sx, sy, ax, ay, r int, lflag, sflag bool, ex, ey int, style ...string)") + defpath("path", h, h2, "Path(s string, style ...string)") + defqbez("qbez", h, h2, h, "Qbez(sx, sy, cx, cy, ex, ey int, style ...string)") + defbez("bezier", h, h2, h, "Bezier(sx, sy, cx, cy, px, py, ex, ey int, style ...string)") + defimage("image", 128, 128, "gophercolor128x128.png", "Image(x, y, w, h, int path string, style ...string)") + deflg("lgrad", w, h, "LinearGradient(s string, x1, y1, x2, y2 uint8, oc []Offcolor)") + defrg("rgrad", w, h, "RadialGradient(s string, cx, cy, r, fx, fy uint8, oc []Offcolor)") + deftrans("trans", w, h, "Translate(x, y int)") + defgrid("grid", w, h, "Grid(x, y, w, h, n int, style ...string)") + deftext("text", w, h, "hello, this is SVG", "Text(x, y int, s string, style ...string)") + defscale("scale", w, h, 0.5, "Scale(n float64)") + defscaleXY("scalexy", w, h, 0.5, 0.75, "ScaleXY(x, y float64)") + defskewX("skewx", w, h, 30, "SkewX(a float64)") + defskewY("skewy", w, h, 10, "SkewY(a float64)") + defskewXY("skewxy", w, h, 10, 10, "SkewXY(x, y float64)") + defrotate("rotate", w, h, 30, "Rotate(r float64)") + deftextpath("textpath", "#tpath", tpathstring, w, h, "Textpath(s, pathid string, style ...string)") + defmeta("meta", w*2, metatext, metadesc, "Textlines(x, y int, s []string, size, spacing int, fill, align string)") + defrgb("rgb", w, h, 44, 77, 232, 1.0, "RGB(r, g, b int)") + defrgb("rgba", w, h, 44, 77, 232, 0.33, "RGBA(r, g, b int, opacity float64)") + canvas.DefEnd() +} + +// colordot makes a colored dot, with opacity +func colordot(x, y, r, red, green, blue int, a float64) { + // canvas.Circle(x,y,r+textsize/6,"fill:none;stroke:"+objcolor) + if a == 1.0 { + canvas.Circle(x, y, r, canvas.RGB(red, green, blue)) + } else { + canvas.Circle(x, y, r, canvas.RGBA(red, green, blue, a)) + } +} + +// placerow is a helper for placeobjects, placing to previously +// defined objects row-wise +func placerow(w int, s []string) { + for x, name := range s { + canvas.Use(x*w, 0, "#"+name) + } +} + +// placeobjects places a grid of objects on the canvas as specified +// by a string array. +func placeobjects(x, y, w, h int, data [][]string) { + canvas.Desc("Object Usage") + for _, object := range data { + canvas.Translate(x, y) + placerow(w, object) + canvas.Gend() + y += h + } +} + +var roworder = [][]string{ + {"rect", "crect", "roundrect", "square", "line", "polyline"}, + {"polygon", "circle", "ellipse", "arc", "qbez", "bezier"}, + {"trans", "scale", "scalexy", "skewx", "skewy", "skewxy"}, + {"rotate", "text", "textpath", "path", "image", "grid"}, + {"lgrad", "rgrad", "rgb", "rgba", "meta"}, +} + +func main() { + width := 4500 + height := (width * 3) / 4 + canvas.Start(width, height) + defobjects(250, 125) + canvas.Title("SVG Go Library Description") + canvas.Rect(0, 0, width, height, "fill:white;stroke:black;stroke-width:2") + canvas.Gstyle(gtextstyle) + canvas.Link("http://github.com/ajstarks/svgo", "SVGo Library") + canvas.Text(width/2, 150, "SVG Go Library", "font-size:125px") + canvas.Text(width/2, 200, "github.com/ajstarks/svgo", "font-size:50px;fill:gray") + canvas.LinkEnd() + placeobjects(400, 400, 700, 600, roworder) + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgopher/svgopher.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgopher/svgopher.go new file mode 100644 index 0000000..084781c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgopher/svgopher.go @@ -0,0 +1,76 @@ +// svgopher - Go mascot remix +// +build !appengine + +package main + +import ( + "os" + + "github.com/ajstarks/svgo" +) + +var ( + width = 500 + height = 300 + canvas = svg.New(os.Stdout) +) + +func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) } + +func gordon(x, y, w, h int) { + + w10 := w / 10 + w12 := w / 12 + w2 := w / 2 + w3 := w / 3 + w8 := w / 8 + w6 := w / 6 + xw := x + w + h23 := (h * 2) / 3 + + blf := "fill:black" + wf := "fill:white" + nf := "fill:brown" + brf := "fill:brown; fill-opacity:0.2" + brb := "fill:rgb(210,161,161)" + + canvas.Gstyle("fill:none; stroke:none") + canvas.Bezier(x, y+h, x, y+h, x+w2, y-h, x+w, y+h, brb) + canvas.Roundrect(x, y+(h-1), w, h, 10, 10, brb) + canvas.Circle(x, y+h, w12, brf) // left ear + canvas.Circle(x, y+h, w12-10, nf) + + canvas.Circle(x+w, y+h, w12, brf) // right ear + canvas.Circle(x+w, y+h, w12-10, nf) + + canvas.Circle(x+w3, y+h23, w/9, wf) // left eye + canvas.Circle(x+w3+10, y+h23, w10-10, blf) + canvas.Circle(x+w3+15, y+h23, 5, wf) + + canvas.Circle(xw-w3, y+h23, w/9, wf) // right eye + canvas.Circle(xw-w3+10, y+h23, w10-10, blf) + canvas.Circle(xw-(w3)+15, y+h23, 5, wf) + + canvas.Roundrect(x+w2-w8, y+h+30, w8, w6, 5, 5, wf) // left tooth + canvas.Roundrect(x+w2, y+h+30, w8, w6, 5, 5, wf) // right tooth + + canvas.Ellipse(x+(w2), y+h+30, w6, w12, nf) // snout + canvas.Ellipse(x+(w2), y+h+10, w10, w12, blf) // nose + + canvas.Gend() +} + +func main() { + canvas.Start(width, height) + canvas.Title("SVG Gopher") + background(255) + canvas.Gtransform("translate(100, 100)") + canvas.Gtransform("rotate(-30)") + gordon(48, 48, 240, 72) + canvas.Gend() + canvas.Gend() + canvas.Link("svgdef.svg", "SVG Spec & Usage") + canvas.Text(90, 145, "SVG", "font-family:Calibri,sans-serif;font-size:80;fill:brown") + canvas.LinkEnd() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplay/svgplay.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplay/svgplay.go new file mode 100644 index 0000000..b37e176 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplay/svgplay.go @@ -0,0 +1,320 @@ +// svgplay: sketch with SVGo, (derived from the old misc/goplay), except: +// (1) only listen on localhost, (default port 1999) +// (2) always render html, +// (3) SVGo default code, +// (4) invoke the compiler and linker directly +package main + +import ( + "bytes" + "flag" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "text/template" +) + +var ( + httpListen = flag.String("port", "1999", "port to listen on") +) + +var ( + // a source of numbers, for naming temporary files + uniq = make(chan int) +) + +func main() { + flag.Parse() + + // source of unique numbers + go func() { + for i := 0; ; i++ { + uniq <- i + } + }() + + http.HandleFunc("/", FrontPage) + http.HandleFunc("/compile", Compile) + log.Printf("☠ ☠ ☠ Warning: this server allows a client connecting to 127.0.0.1:%s to execute code on this computer ☠ ☠ ☠", *httpListen) + log.Fatal(http.ListenAndServe("127.0.0.1:"+*httpListen, nil)) +} + +// FrontPage is an HTTP handler that renders the svgoplay interface. +// If a filename is supplied in the path component of the URI, +// its contents will be put in the interface's text area. +// Otherwise, the default "hello, world" program is displayed. +func FrontPage(w http.ResponseWriter, req *http.Request) { + data, err := ioutil.ReadFile(req.URL.Path[1:]) + if err != nil { + data = helloWorld + } + frontPage.Execute(w, data) +} + +// Compile is an HTTP handler that reads Go source code from the request, +// runs the program (returning any errors), +// and sends the program's output as the HTTP response. +func Compile(w http.ResponseWriter, req *http.Request) { + out, err := compile(req) + if err != nil { + compileError(w, out, err) + return + } + + // write the output of target as the http response + w.Write(out) +} + +var ( + compiler, linker, tmpdir, pkgdir, toolchar, buildpid string +) + +func init() { + // find real temporary directory (for rewriting filename in output) + var err error + tmpdir, err = filepath.EvalSymlinks(os.TempDir()) + if err != nil { + log.Fatal(err) + } + + // build the command paths for the compiler and linker, assumes that GOPATH is set + var toolmap = map[string]string{"arm": "5", "amd64": "6", "386": "8"} + toolchar = toolmap[runtime.GOARCH] + ga := runtime.GOOS + "_" + runtime.GOARCH + tool := runtime.GOROOT() + "/pkg/tool/" + ga + "/" + toolchar + pkgdir = os.Getenv("GOPATH") + "/pkg/" + ga + compiler = tool + "g" + linker = tool + "l" + buildpid = strconv.Itoa(os.Getpid()) +} + +func compile(req *http.Request) (out []byte, err error) { + // target is the base name for .go, object, executable files + target := filepath.Join(tmpdir, "svgplay"+buildpid+strconv.Itoa(<-uniq)) + src := target + ".go" + obj := target + "." + toolchar + bin := target + if runtime.GOOS == "windows" { + bin += ".exe" + } + + // write body to target.go + body := new(bytes.Buffer) + if _, err = body.ReadFrom(req.Body); err != nil { + return + } + defer os.Remove(src) + if err = ioutil.WriteFile(src, body.Bytes(), 0666); err != nil { + return + } + + // build and link target.go, creating target + out, err = run("", compiler, "-I", pkgdir, "-o", obj, src) + defer os.Remove(obj) + if err != nil { + return + } + + out, err = run("", linker, "-L", pkgdir, "-o", bin, obj) + defer os.Remove(bin) + if err != nil { + return + } + // run target + return run("", bin) +} + +// error writes compile, link, or runtime errors to the HTTP connection. +// The JavaScript interface uses the 404 status code to identify the error. +func compileError(w http.ResponseWriter, out []byte, err error) { + w.WriteHeader(404) + if out != nil { + elines := bytes.Split(out, []byte{'\n'}) + for _, l := range elines { + i := bytes.Index(l, []byte{':'} ) + output.Execute(w, l[i+1:]) + } + } else { + output.Execute(w, err.Error()) + } +} + +// run executes the specified command and returns its output and an error. +func run(dir string, args ...string) ([]byte, error) { + var buf bytes.Buffer + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = dir + cmd.Stdout = &buf + cmd.Stderr = cmd.Stdout + err := cmd.Run() + return buf.Bytes(), err +} + +var frontPage = template.Must(template.New("frontPage").Parse(frontPageText)) // HTML template +var output = template.Must(template.New("output").Parse(outputText)) // HTML template + +var outputText = `
      {{printf "%s" . |html}}
      ` + +var frontPageText = ` + + +svgplay: SVGo sketching + + + + +
      + +
      +(Shift-Enter to compile and run.)     + Compile and run after each keystroke +
      +
      + +
      +
      +
      + + +` + +var helloWorld = []byte(`package main + +import ( + "math/rand" + "os" + "time" + "github.com/ajstarks/svgo" +) + +func ri(n int) int { return rand.Intn(n) } + +func main() { + canvas := svg.New(os.Stdout) + width := 500 + height := 500 + nstars := 250 + style := "font-size:48pt;fill:white;text-anchor:middle" + + rand.Seed(time.Now().Unix()) + canvas.Start(width, height) + canvas.Rect(0,0,width,height) + for i := 0; i < nstars; i++ { + canvas.Circle(ri(width), ri(height), ri(3), "fill:white") + } + canvas.Circle(width/2, height, width/2, "fill:rgb(77, 117, 232)") + canvas.Text(width/2, height*4/5, "hello, world", style) + canvas.End() +}`) diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/mksvgplotdef b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/mksvgplotdef new file mode 100644 index 0000000..e1dbc94 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/mksvgplotdef @@ -0,0 +1,24 @@ +#!/bin/sh +rm f??*.svg +gopts="-bx=50 -by=20 -width=460 -height=130 -pw=400 -ph=90 -barsize=3 -fontsize=11" +data="test.d" +# opt +svgplot $gopts -showbg -connect=f -label=showbg $data > f00.svg +svgplot $gopts -connect -label=connect $data > f01.svg +svgplot $gopts -showbar -connect=f -label=showbar $data > f02.svg +svgplot $gopts -area -connect=f -label=area $data > f03.svg +svgplot $gopts -showdot -connect=f -label=showdot $data > f04.svg +svgplot $gopts -showx -label=showx $data > f05.svg +svgplot $gopts -showy -label=showy $data > f06.svg +svgplot $gopts -showfile -label=showfile $data > f07.svg +# attr +svgplot $gopts -bgcolor=lightsteelblue -label=bgcolor $data > f08.svg +svgplot $gopts -linecolor=red -label=linecolor $data > f09.svg +svgplot $gopts -barcolor=red -showbar -connect=f -label=barcolor $data > f10.svg +svgplot $gopts -areacolor=red -area -connect=f -label=hcolor $data > f11.svg +svgplot $gopts -showdot -dotcolor=red -connect=f -label=dotcolor $data > f12.svg +svgplot $gopts -label=labelcolor -labelcolor=red $data > f13.svg +svgplot $gopts -label=fontsize $data > f14.svg +svgplot $gopts -font=Courier -label="font" $data > f15.svg +svgrid -h 1200 -w 1024 -r=f -x=50 -y=50 -g 10 -c 8 f??.svg > svgplotdef.svg + diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/svgplot.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/svgplot.go new file mode 100644 index 0000000..481b0ae --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/svgplot.go @@ -0,0 +1,301 @@ +//svgplot -- plot data (a stream of x,y coordinates) +// +build !appengine + +package main + +import ( + "flag" + "fmt" + "io" + "math" + "os" + + "github.com/ajstarks/svgo" +) + +// rawdata defines data as float64 x,y coordinates +type rawdata struct { + x float64 + y float64 +} + +type options map[string]bool +type attributes map[string]string +type measures map[string]int + +// plotset defines plot metadata +type plotset struct { + opt options + attr attributes + size measures +} + +var ( + canvas = svg.New(os.Stdout) + plotopt = options{} + plotattr = attributes{} + plotnum = measures{} + ps = plotset{plotopt, plotattr, plotnum} + plotw, ploth, plotc, gwidth, gheight, gutter, beginx, beginy int +) + +const ( + globalfmt = "font-family:%s;font-size:%dpt;stroke-width:%dpx" + linestyle = "fill:none;stroke:" + linefmt = "fill:none;stroke:%s" + barfmt = linefmt + ";stroke-width:%dpx" + ticfmt = "stroke:rgb(200,200,200);stroke-width:1px" + labelfmt = ticfmt + ";text-anchor:end;fill:black" + textfmt = "stroke:none;baseline-shift:-33.3%" +) + +// init initializes command flags and sets default options +func init() { + + // boolean options + showx := flag.Bool("showx", false, "show the xaxis") + showy := flag.Bool("showy", false, "show the yaxis") + showbar := flag.Bool("showbar", false, "show data bars") + area := flag.Bool("area", false, "area chart") + connect := flag.Bool("connect", true, "connect data points") + showdot := flag.Bool("showdot", false, "show dots") + showbg := flag.Bool("showbg", true, "show the background color") + showfile := flag.Bool("showfile", false, "show the filename") + sameplot := flag.Bool("sameplot", false, "plot on the same frame") + + // attributes + bgcolor := flag.String("bgcolor", "rgb(240,240,240)", "plot background color") + barcolor := flag.String("barcolor", "gray", "bar color") + dotcolor := flag.String("dotcolor", "black", "dot color") + linecolor := flag.String("linecolor", "gray", "line color") + areacolor := flag.String("areacolor", "gray", "area color") + font := flag.String("font", "Calibri,sans", "font") + labelcolor := flag.String("labelcolor", "black", "label color") + plotlabel := flag.String("label", "", "plot label") + + // sizes + dotsize := flag.Int("dotsize", 2, "dot size") + linesize := flag.Int("linesize", 2, "line size") + barsize := flag.Int("barsize", 2, "bar size") + fontsize := flag.Int("fontsize", 11, "font size") + xinterval := flag.Int("xint", 10, "x axis interval") + yinterval := flag.Int("yint", 4, "y axis interval") + + // meta options + flag.IntVar(&beginx, "bx", 100, "initial x") + flag.IntVar(&beginy, "by", 50, "initial y") + flag.IntVar(&plotw, "pw", 500, "plot width") + flag.IntVar(&ploth, "ph", 500, "plot height") + flag.IntVar(&plotc, "pc", 2, "plot columns") + flag.IntVar(&gutter, "gutter", ploth/10, "gutter") + flag.IntVar(&gwidth, "width", 1024, "canvas width") + flag.IntVar(&gheight, "height", 768, "canvas height") + + flag.Parse() + + // fill in the plotset -- all options, attributes, and sizes + plotopt["showx"] = *showx + plotopt["showy"] = *showy + plotopt["showbar"] = *showbar + plotopt["area"] = *area + plotopt["connect"] = *connect + plotopt["showdot"] = *showdot + plotopt["showbg"] = *showbg + plotopt["showfile"] = *showfile + plotopt["sameplot"] = *sameplot + + plotattr["bgcolor"] = *bgcolor + plotattr["barcolor"] = *barcolor + plotattr["linecolor"] = *linecolor + plotattr["dotcolor"] = *dotcolor + plotattr["areacolor"] = *areacolor + plotattr["font"] = *font + plotattr["label"] = *plotlabel + plotattr["labelcolor"] = *labelcolor + + plotnum["dotsize"] = *dotsize + plotnum["linesize"] = *linesize + plotnum["fontsize"] = *fontsize + plotnum["xinterval"] = *xinterval + plotnum["yinterval"] = *yinterval + plotnum["barsize"] = *barsize +} + +// fmap maps world data to document coordinates +func fmap(value float64, low1 float64, high1 float64, low2 float64, high2 float64) float64 { + return low2 + (high2-low2)*(value-low1)/(high1-low1) +} + +// doplot opens a file and makes a plot +func doplot(x, y int, location string) { + var f *os.File + var err error + if len(location) > 0 { + if plotopt["showfile"] { + plotattr["label"] = location + } + f, err = os.Open(location) + } else { + f = os.Stdin + } + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + nd, data := readxy(f) + f.Close() + + if nd >= 2 { + plot(x, y, plotw, ploth, ps, data) + } +} + +// plot places a plot at the specified location with the specified dimemsions +// usinng the specified settings, using the specified data +func plot(x, y, w, h int, settings plotset, d []rawdata) { + nd := len(d) + if nd < 2 { + fmt.Fprintf(os.Stderr, "%d is not enough points to plot\n", len(d)) + return + } + // Compute the minima and maxima + maxx, minx := d[0].x, d[0].x + maxy, miny := d[0].y, d[0].y + for _, v := range d { + + if v.x > maxx { + maxx = v.x + } + if v.y > maxy { + maxy = v.y + } + if v.x < minx { + minx = v.x + } + if v.y < miny { + miny = v.y + } + } + // Prepare for a area or line chart by allocating + // polygon coordinates; for the hrizon plot, you need two extra coordinates + // for the extrema. + needpoly := settings.opt["area"] || settings.opt["connect"] + var xpoly, ypoly []int + if needpoly { + xpoly = make([]int, nd+2) + ypoly = make([]int, nd+2) + // preload the extrema of the polygon, + // the bottom left and bottom right of the plot's rectangle + xpoly[0] = x + ypoly[0] = y + h + xpoly[nd+1] = x + w + ypoly[nd+1] = y + h + } + // Draw the plot's bounding rectangle + if settings.opt["showbg"] && !settings.opt["sameplot"] { + canvas.Rect(x, y, w, h, "fill:"+settings.attr["bgcolor"]) + } + // Loop through the data, drawing items as specified + spacer := 10 + canvas.Gstyle(fmt.Sprintf(globalfmt, + settings.attr["font"], settings.size["fontsize"], settings.size["linesize"])) + for i, v := range d { + xp := int(fmap(v.x, minx, maxx, float64(x), float64(x+w))) + yp := int(fmap(v.y, miny, maxy, float64(y), float64(y-h))) + + if needpoly { + xpoly[i+1] = xp + ypoly[i+1] = yp + h + } + if settings.opt["showbar"] { + canvas.Line(xp, yp+h, xp, y+h, + fmt.Sprintf(barfmt, settings.attr["barcolor"], settings.size["barsize"])) + } + if settings.opt["showdot"] { + canvas.Circle(xp, yp+h, settings.size["dotsize"], "fill:"+settings.attr["dotcolor"]) + } + if settings.opt["showx"] { + if i%settings.size["xinterval"] == 0 { + canvas.Text(xp, (y+h)+(spacer*2), fmt.Sprintf("%d", int(v.x)), "text-anchor:middle") + canvas.Line(xp, (y + h), xp, (y+h)+spacer, ticfmt) + } + } + } + // Done constructing the points for the area or line plots, display them in one shot + if settings.opt["area"] { + canvas.Polygon(xpoly, ypoly, "fill:"+settings.attr["areacolor"]) + } + + if settings.opt["connect"] { + canvas.Polyline(xpoly[1:nd+1], ypoly[1:nd+1], linestyle+settings.attr["linecolor"]) + } + // Put on the y axis labels, if specified + if settings.opt["showy"] { + bot := math.Floor(miny) + top := math.Ceil(maxy) + yrange := top - bot + interval := yrange / float64(settings.size["yinterval"]) + canvas.Gstyle(labelfmt) + for yax := bot; yax <= top; yax += interval { + yaxp := fmap(yax, bot, top, float64(y), float64(y-h)) + canvas.Text(x-spacer, int(yaxp)+h, fmt.Sprintf("%.1f", yax), textfmt) + canvas.Line(x-spacer, int(yaxp)+h, x, int(yaxp)+h) + } + canvas.Gend() + } + // Finally, tack on the label, if specified + if len(settings.attr["label"]) > 0 { + canvas.Text(x, y+spacer, settings.attr["label"], "font-size:120%;fill:"+settings.attr["labelcolor"]) + } + + canvas.Gend() +} + +// readxy reads coordinates (x,y float64 values) from a io.Reader +func readxy(f io.Reader) (int, []rawdata) { + var ( + r rawdata + err error + n, nf int + ) + data := make([]rawdata, 1) + for ; err == nil; n++ { + if n > 0 { + data = append(data, r) + } + nf, err = fmt.Fscan(f, &data[n].x, &data[n].y) + if nf != 2 { + continue + } + } + return n - 1, data[0 : n-1] +} + +// plotgrid places plots on a grid, governed by a number of columns. +func plotgrid(x, y int, files []string) { + px := x + for i, f := range files { + if i > 0 && i%plotc == 0 && !plotopt["sameplot"] { + px = x + y += (ploth + gutter) + } + doplot(px, y, f) + if !plotopt["sameplot"] { + px += (plotw + gutter) + } + } +} + +// main plots data from specified files or standard input in a +// grid where plotc specifies the number of columns. +func main() { + canvas.Start(gwidth, gheight) + canvas.Rect(0, 0, gwidth, gheight, "fill:white") + filenames := flag.Args() + if len(filenames) == 0 { + doplot(beginx, beginy, "") + } else { + plotgrid(beginx, beginy, filenames) + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/test.d b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/test.d new file mode 100644 index 0000000..e79c144 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgplot/test.d @@ -0,0 +1,51 @@ +0 493.65 +1 497.57 +2 506.38 +3 521.03 +4 532.44 +5 535.36 +6 546.60 +7 531.99 +8 527.28 +9 534.01 +10 538.26 +11 528.94 +12 597.62 +13 594.94 +14 602.55 +15 595.35 +16 606.99 +17 618.23 +18 618.98 +19 622.52 +20 607.22 +21 610.94 +22 603.69 +23 606.77 +24 592.40 +25 601.17 +26 577.52 +27 579.04 +28 546.02 +29 573.41 +30 549.01 +31 562.13 +32 563.77 +33 557.23 +34 539.00 +35 533.15 +36 504.88 +37 490.92 +38 498.17 +39 518.82 +40 523.29 +41 520.04 +42 526.86 +43 539.08 +44 540.70 +45 540.96 +46 532.50 +47 524.84 +48 522.18 +49 534.03 +50 534.96 diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/svgrid/svgrid.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgrid/svgrid.go new file mode 100644 index 0000000..6bb662c --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/svgrid/svgrid.go @@ -0,0 +1,107 @@ +// svgrid -- composite SVG files in a grid +// +build !appengine + +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "io" + "os" + + "github.com/ajstarks/svgo" +) + +// SVG is a SVG document +type SVG struct { + Width string `xml:"width,attr"` + Height string `xml:"height,attr"` + Doc string `xml:",innerxml"` +} + +var ( + byrow bool + startx, starty, count, gutter, gwidth, gheight int + canvas = svg.New(os.Stdout) +) + +// init sets up command line options +func init() { + flag.BoolVar(&byrow, "r", true, "order row wise") + flag.IntVar(&startx, "x", 0, "begin x") + flag.IntVar(&starty, "y", 0, "begin y") + flag.IntVar(&count, "c", 3, "columns or rows") + flag.IntVar(&gutter, "g", 100, "gutter") + flag.IntVar(&gwidth, "w", 1024, "width") + flag.IntVar(&gheight, "h", 768, "height") + flag.Parse() +} + +// placepic puts a SVG file at a location +func placepic(x, y int, filename string) (int, int) { + var ( + s SVG + width, height int + wunit, hunit string + ) + f, err := os.Open(filename) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return 0, 0 + } + defer f.Close() + if err := xml.NewDecoder(f).Decode(&s); err != nil { + fmt.Fprintf(os.Stderr, "Unable to parse (%v)\n", err) + return 0, 0 + } + + // read the width and height, including any units + // if there are errors use 10 as a default + nw, _ := fmt.Sscanf(s.Width, "%d%s", &width, &wunit) + if nw < 1 { + width = 10 + } + nh, _ := fmt.Sscanf(s.Height, "%d%s", &height, &hunit) + if nh < 1 { + height = 10 + } + canvas.Group(`clip-path="url(#pic)"`, fmt.Sprintf(`transform="translate(%d,%d)"`, x, y)) + canvas.ClipPath(`id="pic"`) + canvas.Rect(0, 0, width, height) + canvas.ClipEnd() + io.WriteString(canvas.Writer, s.Doc) + canvas.Gend() + return width, height +} + +// compose places files row or column-wise +func compose(x, y, n int, rflag bool, files []string) { + px := x + py := y + var pw, ph int + for i, f := range files { + if i > 0 && i%n == 0 { + if rflag { + px = x + py += gutter + ph + } else { + px += gutter + pw + py = y + } + } + pw, ph = placepic(px, py, f) + if rflag { + px += gutter + pw + } else { + py += gutter + ph + } + } +} + +// main lays out files as specified on the command line +func main() { + canvas.Start(gwidth, gheight) + compose(startx, starty, count, byrow, flag.Args()) + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/tsg/tsg.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/tsg/tsg.go new file mode 100644 index 0000000..8605392 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/tsg/tsg.go @@ -0,0 +1,126 @@ +// tsg -- twitter search grid +// +build !appengine + +package main + +import ( + "encoding/xml" + "flag" + "fmt" + "io" + "net/http" + "net/url" + "os" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +// Feed is the Atom feed structure +type Feed struct { + XMLName xml.Name `xml:"http://www.w3.org/2005/Atom feed"` + Entry []Entry `xml:"entry"` +} + +// Entry defines an entry within an Aton feed +type Entry struct { + Link []Link `xml:"link"` + Title string `xml:"title"` + Author Person `xml:"author"` +} + +// Link defines a link within an Atom feed +type Link struct { + Rel string `xml:"rel,attr"` + Href string `xml:"href,attr"` +} + +// Person defines a person responsible for the tweet +type Person struct { + Name string `xml:"name"` +} + +// Text defines the text of the tweet +type Text struct { + Type string `xml:",attr"` + Body string `xml:",chardata"` +} + +var ( + nresults = flag.Int("n", 100, "Maximum results (up to 100)") + since = flag.String("d", "", "Search since this date (YYYY-MM-DD)") +) + +const ( + queryURI = "http://search.twitter.com/search.atom?q=%s&since=%s&rpp=%d" + textfmt = "font-family:Calibri,Lucida,sans;fill:gray;text-anchor:middle;font-size:48px" + imw = 48 + imh = 48 +) + +// ts dereferences the twitter search URL and reads the XML (Atom) response +func ts(s string, date string, n int) { + + r, err := http.Get(fmt.Sprintf(queryURI, url.QueryEscape(s), date, n)) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + if r.StatusCode == http.StatusOK { + readatom(r.Body) + } else { + fmt.Fprintf(os.Stderr, + "Twitter is unable to search for %s (%s)\n", s, r.Status) + } + r.Body.Close() +} + +// readatom unmarshals the twitter search response and formats the results into a grid +func readatom(r io.Reader) { + var twitter Feed + err := xml.NewDecoder(r).Decode(&twitter) + if err == nil { + tgrid(twitter, 25, 25, 50, 50, 10) + } else { + fmt.Fprintf(os.Stderr, "Unable to parse the Atom feed (%v)\n", err) + } +} + +// tgrid makes a clickable grid of tweets from the Atom feed +func tgrid(t Feed, x, y, w, h, nc int) { + var slink, imlink string + xp := x + for i, entry := range t.Entry { + for _, link := range entry.Link { + switch link.Rel { + case "alternate": + slink = link.Href + case "image": + imlink = link.Href + } + } + if i%nc == 0 && i > 0 { + xp = x + y += h + } + canvas.Link(slink, slink) + canvas.Image(xp, y, imw, imh, imlink) + canvas.LinkEnd() + xp += w + } +} + +// for every non-flag argument, make a twitter search grid +func main() { + flag.Parse() + width := 550 + height := 700 + canvas.Start(width, height) + for _, s := range flag.Args() { + canvas.Title("Twitter search for " + s) + ts(s, *since, *nresults) + canvas.Text(width/2, height-50, s, textfmt) + } + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tlist b/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tlist new file mode 100644 index 0000000..c709df1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tlist @@ -0,0 +1,4 @@ +f0o0od.tumblr.com +ign0ranceisbliiiss.tumblr.com +agilaagira.tumblr.com +geek-art.tumblr.com diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tumblrgrid.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tumblrgrid.go new file mode 100644 index 0000000..82c20e8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/tumblrgrid/tumblrgrid.go @@ -0,0 +1,218 @@ +// tumblrgrid: display a flexible grid of pictures from tumblr, filtered by tags +// +build !appengine + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + + "github.com/ajstarks/svgo" +) + +var ( + localfile, postlink bool + ncols, gutter, thumbwidth, piclimit int + filtertag string +) + +const ( + apiKey = "APIKEY" + apifmt = "http://api.tumblr.com/v2/blog/%s/posts?api_key=%s&type=photo&limit=%d" +) + +// Tumblr is the JSON data descriptions +type Tumblr struct { + Meta meta + Response response +} + +type meta struct { + Msg string + Status int +} + +type response struct { + Blog blog + Posts []posts + Total_posts int +} + +type blog struct { + Name string + Posts int + Title string + Updated int + Description string + Url string +} + +type posts struct { + Photos []photos + Tags []string + Type string + Link_url string + Post_url string +} + +type photos struct { + Alt_sizes []altsizes +} + +type altsizes struct { + Height int + Url string + Width int +} + +// resource gives a ReadCloser given a local file or URL +func resource(name string) (io.ReadCloser, error) { + if len(name) == 0 { + return os.Stdin, nil + } + if localfile { + return os.Open(name) + } + h, err := http.Get(fmt.Sprintf(apifmt, name, apiKey, piclimit)) + return h.Body, err +} + +// grid displays tumblr photos in a flexible grid +func grid(canvas *svg.SVG, location string, x, y, nc, gutter int) { + var ( + t Tumblr + r io.ReadCloser + err error + b []byte + ) + + //get data from the resource, put it into the data structure + if r, err = resource(location); err != nil { + fmt.Fprintln(os.Stderr, err) + return + } + + defer r.Close() + if b, err = ioutil.ReadAll(r); err != nil { + fmt.Fprintf(os.Stderr, "%s %v\n", location, err) + return + } + + if err = json.Unmarshal(b, &t); err != nil { + fmt.Fprintf(os.Stderr, "%s, %v\n", location, err) + return + } + + // create the linked blog title + fontsize := thumbwidth / 3 + title := t.Response.Blog.Title + + if len(title) == 0 { + title = t.Response.Blog.Name + } + canvas.Link(t.Response.Blog.Url, t.Response.Blog.Name) + if nc < 4 { // if the columns are too narrow, rotate the title text 90 degrees + canvas.TranslateRotate(x, y/2, 90) + canvas.Text(fontsize, fontsize, title, "fill:lightgray") + canvas.Gend() + } else { + canvas.Text(x, y/2, title, "fill:lightgray") + } + canvas.LinkEnd() + + // walk through the posts, displaying thumbnails, filtered by tags + np := 0 + xp := x + + for _, posts := range t.Response.Posts { + if np >= piclimit { + break + } + if !intag(filtertag, posts.Tags) { + continue + } + for _, photos := range posts.Photos { + for i, p := range photos.Alt_sizes { + if i == 0 { // link to the first image in the list + if postlink { + canvas.Link(posts.Post_url, "Photo") + } else { + canvas.Link(p.Url, "Photo") + } + } + if p.Width == thumbwidth { + np++ + canvas.Image(xp, y, p.Width, p.Width, p.Url) + xp += p.Width + gutter + if np%nc == 0 { + xp = x + y += p.Width + gutter + } + } + } + canvas.LinkEnd() + } + } +} + +// intag searches for tags in list +func intag(tag string, list []string) bool { + if len(tag) == 0 { + return true + } + for _, s := range list { + if s == tag { + return true + } + } + return false +} + +// init sets up command flags +func init() { + flag.BoolVar(&localfile, "f", false, "read from local files") + flag.BoolVar(&postlink, "p", false, "link to original post") + flag.IntVar(&ncols, "nc", 5, "number of columns") + flag.IntVar(&gutter, "g", 5, "gutter (pixels)") + flag.IntVar(&thumbwidth, "tw", 75, "thumbnail width") + flag.IntVar(&piclimit, "n", 30, "picture limit") + flag.StringVar(&filtertag, "tag", "", "filter tag") + flag.Parse() +} + +func main() { + + np := len(flag.Args()) + if np == 0 { + np = 1 + } + + thalf := thumbwidth / 2 + x := thalf + y := 50 + nrows := piclimit / ncols + colincr := (ncols * thumbwidth) + (ncols * gutter) + thalf + width := (colincr * np) + thalf + height := (thumbwidth * nrows) + (nrows * gutter) + y + thalf + gstyle := "font-family:Calibri,sans-serif;font-size:18px" + + canvas := svg.New(os.Stdout) + canvas.Start(width, height) + canvas.Rect(0, 0, width, height, "fill:rgb(43,62,87)") // tumblr blue + canvas.Gstyle(gstyle) + if len(flag.Args()) == 0 { + grid(canvas, "", x, y, ncols, gutter) + } else { + for _, f := range flag.Args() { + grid(canvas, f, x, y, ncols, gutter) + x += colincr + } + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/turbulence/turbulence.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/turbulence/turbulence.go new file mode 100644 index 0000000..e247d4d --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/turbulence/turbulence.go @@ -0,0 +1,73 @@ +// turbulence example from http://www.w3.org/TR/2003/REC-SVG11-20030114/filters.html#feTurbulence +// +build !appengine + +package main + +import ( + "fmt" + "github.com/ajstarks/svgo" + "os" +) + +var ( + canvas = svg.New(os.Stdout) + width = 500 + height = 500 +) + +type perlin struct { + id string + ftype string + basefreqx float64 + basefreqy float64 + octave int + seed int64 + tile bool +} + +func (p perlin) defturbulence() { + x := svg.Filterspec{} + canvas.Filter(p.id) + canvas.FeTurbulence(x, p.ftype, p.basefreqx, p.basefreqy, p.octave, p.seed, p.tile) + canvas.Fend() +} + +func (p perlin) frect(x, y, w, h int) { + bot := y + h + canvas.Rect(x, y, w, h, fmt.Sprintf(`filter="url(#%s)"`, p.id)) + canvas.Text(x+w/2, bot+25, fmt.Sprintf("type=%s", p.ftype)) + canvas.Text(x+w/2, bot+40, fmt.Sprintf("baseFrequency=%.2f", p.basefreqx)) + canvas.Text(x+w/2, bot+55, fmt.Sprintf("numOctaves=%d", p.octave)) +} + +func main() { + var t1, t2, t3, t4, t5, t6 perlin + + t1 = perlin{"Turb1", "t", 0.05, 0.05, 2, 0, false} + t2 = perlin{"Turb2", "t", 0.10, 0.10, 2, 0, false} + t3 = perlin{"Turb3", "t", 0.05, 0.05, 8, 0, false} + t4 = perlin{"Turb4", "f", 0.10, 0.10, 4, 0, false} + t5 = perlin{"Turb5", "f", 0.40, 0.40, 4, 0, false} + t6 = perlin{"Turb6", "f", 0.10, 0.10, 1, 0, false} + + canvas.Start(width, height) + canvas.Title("Example of feTurbulence") + canvas.Def() + t1.defturbulence() + t2.defturbulence() + t3.defturbulence() + t4.defturbulence() + t5.defturbulence() + t6.defturbulence() + canvas.DefEnd() + + canvas.Gstyle("font-size:10;font-family:Verdana;text-anchor:middle") + t1.frect(25, 25, 100, 75) + t2.frect(175, 25, 100, 75) + t3.frect(325, 25, 100, 75) + t4.frect(25, 180, 100, 75) + t5.frect(175, 180, 100, 75) + t6.frect(325, 180, 100, 75) + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/vismem/vismem.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/vismem/vismem.go new file mode 100644 index 0000000..f0d1b54 --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/vismem/vismem.go @@ -0,0 +1,51 @@ +// vismem visualizes memory locations +// +build !appengine + +package main + +import ( + "os" + + "github.com/ajstarks/svgo" +) + +var canvas = svg.New(os.Stdout) + +func main() { + width := 512 + height := 512 + n := 1024 + rowsize := 32 + diameter := 16 + var value int + var source string + + if len(os.Args) > 1 { + source = os.Args[1] + } else { + source = "/dev/urandom" + } + + f, _ := os.Open(source) + mem := make([]byte, n) + f.Read(mem) + f.Close() + + canvas.Start(width, height) + canvas.Title("Visualize Files") + canvas.Rect(0, 0, width, height, "fill:white") + dx := diameter / 2 + dy := diameter / 2 + canvas.Gstyle("fill-opacity:1.0") + for i := 0; i < n; i++ { + value = int(mem[i]) + if i%rowsize == 0 && i != 0 { + dx = diameter / 2 + dy += diameter + } + canvas.Circle(dx, dy, diameter/2, canvas.RGB(value, value, value)) + dx += diameter + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/webfonts/webfonts.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/webfonts/webfonts.go new file mode 100644 index 0000000..6a6593e --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/webfonts/webfonts.go @@ -0,0 +1,62 @@ +// webfonts demo +// +build !appengine + +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "strings" + + "github.com/ajstarks/svgo" +) + +var ( + canvas = svg.New(os.Stdout) + width = 500 + height = 1100 + fontlist = "Sue Ellen Francisco|Over the Rainbow|Pacifico|Inconsolata|Miltonian|Megrim|Monofett|Permanent Marker|Homemade Apple|Ultra" +) + +const ( + gwfURI = "http://fonts.googleapis.com/css?family=" + fontfmt = "\n" + gfmt = "fill:white;font-size:36pt;text-anchor:middle" +) + +func googlefont(f string) []string { + empty := []string{} + r, err := http.Get(gwfURI + url.QueryEscape(f)) + if err != nil { + return empty + } + defer r.Body.Close() + b, rerr := ioutil.ReadAll(r.Body) + if rerr != nil || r.StatusCode != http.StatusOK { + return empty + } + canvas.Def() + fmt.Fprintf(canvas.Writer, fontfmt, b) + canvas.DefEnd() + return strings.Split(fontlist, "|") +} + +func main() { + canvas.Start(width, height) + canvas.Title("Webfonts") + if len(os.Args) > 1 { + fontlist = os.Args[1] + } + fl := googlefont(fontlist) + canvas.Rect(0, 0, width, height) + canvas.Ellipse(width/2, height+50, width/2, height/5, "fill:rgb(44,77,232)") + canvas.Gstyle(gfmt) + for i, f := range fl { + canvas.Text(width/2, (i+1)*100, "Hello, World", "font-family:"+f) + } + canvas.Gend() + canvas.End() +} diff --git a/Godeps/_workspace/src/github.com/ajstarks/svgo/websvg/websvg.go b/Godeps/_workspace/src/github.com/ajstarks/svgo/websvg/websvg.go new file mode 100644 index 0000000..ad1ad6f --- /dev/null +++ b/Godeps/_workspace/src/github.com/ajstarks/svgo/websvg/websvg.go @@ -0,0 +1,74 @@ +// websvg draws SVG in a web server +// +build !appengine + +package main + + +import ( + "flag" + "log" + "net/http" + "strings" + + "github.com/ajstarks/svgo" +) + +const defaultstyle = "fill:rgb(127,0,0)" + +var port = flag.String("port", ":2003", "http service address") + +func main() { + flag.Parse() + http.Handle("/circle/", http.HandlerFunc(circle)) + http.Handle("/rect/", http.HandlerFunc(rect)) + http.Handle("/arc/", http.HandlerFunc(arc)) + http.Handle("/text/", http.HandlerFunc(text)) + err := http.ListenAndServe(*port, nil) + if err != nil { + log.Println("ListenAndServe:", err) + } +} + +func shapestyle(path string) string { + i := strings.LastIndex(path, "/") + 1 + if i > 0 && len(path[i:]) > 0 { + return "fill:" + path[i:] + } + return defaultstyle +} + +func circle(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Title("Circle") + s.Circle(250, 250, 125, shapestyle(req.URL.Path)) + s.End() +} + +func rect(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Title("Rectangle") + s.Rect(250, 250, 100, 200, shapestyle(req.URL.Path)) + s.End() +} + +func arc(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Title("Arc") + s.Arc(250, 250, 100, 100, 0, false, false, 100, 125, shapestyle(req.URL.Path)) + s.End() +} + +func text(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "image/svg+xml") + s := svg.New(w) + s.Start(500, 500) + s.Title("Text") + s.Text(250, 250, "Hello, world", "text-anchor:middle;font-size:32px;"+shapestyle(req.URL.Path)) + s.End() +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml b/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml new file mode 100644 index 0000000..7ecdd6d --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml @@ -0,0 +1,27 @@ +language: go +go: + - 1.2.2 + - 1.3.1 +env: + - NSQ_DOWNLOAD=nsq-0.2.24.linux-amd64.go1.2 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.2.24.linux-amd64.go1.2 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.2.27.linux-amd64.go1.2 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.2.27.linux-amd64.go1.2 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.2.28.linux-amd64.go1.2.1 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.2.28.linux-amd64.go1.2.1 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.2.30.linux-amd64.go1.3 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.2.30.linux-amd64.go1.3 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.2.31.linux-amd64.go1.3.1 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.2.31.linux-amd64.go1.3.1 GOARCH=386 +install: + - go get github.com/bitly/go-simplejson + - go get github.com/mreiferson/go-snappystream +script: + - wget http://bitly-downloads.s3.amazonaws.com/nsq/$NSQ_DOWNLOAD.tar.gz + - tar zxvf $NSQ_DOWNLOAD.tar.gz + - sudo cp $NSQ_DOWNLOAD/bin/nsqd $NSQ_DOWNLOAD/bin/nsqlookupd /usr/local/bin + - pushd $TRAVIS_BUILD_DIR + - ./test.sh + - popd +notifications: + email: false diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md b/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md new file mode 100644 index 0000000..b48f791 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md @@ -0,0 +1,191 @@ +## go-nsq Change Log + +### 1.0.1 - 2014-11-09 + +**Upgrading from 1.0.0**: There are no backward incompatible changes functionally, however this +release no longer compiles with Go `1.0.x`. + + * #89 - don't spam connection teardown cleanup messages + * #91 - add consumer `DisconnectFrom*` + * #87 - allow `heartbeat_interval` and `output_buffer_timeout` to be disabled + * #86 - pluggable `nsqlookupd` behaviors + * #83 - send `RDY` before `FIN`/`REQ` (forwards compatibility with bitly/nsq#404) + * #82 - fix panic when conn isn't assigned + * #75/#76 - minor config related bug fixes + * #75/#77/#78 - add `tls-cert` and `tls-key` config options + +### 1.0.0 - 2014-08-11 + +**Upgrading from 0.3.7**: The public API was significantly refactored and is not backwards +compatible, please read [UPGRADING](UPGRADING.md). + + * #58 - support `IDENTIFY` `msg_timeout` + * #54 - per-connection TLS config and set `ServerName` + * #49 - add common connect helpers + * #43/#63 - more flexible `nsqlookupd` URL specification + * #35 - `AUTH` support + * #41/#62 - use package private RNG + * #36 - support 64 character topic/channel names + * #30/#38/#39/#42/#45/#46/#48/#51/#52/#65/#70 - refactor public API (see [UPGRADING](UPGRADING.md)) + +### 0.3.7 - 2014-05-25 + +**Upgrading from 0.3.6**: There are no backward incompatible changes. **THIS IS THE LAST STABLE +RELEASE PROVIDING THIS API**. Future releases will be based on the api in #30 and **will not be +backwards compatible!** + +This is a bug fix release relating to the refactoring done in `0.3.6`. + + * #32 - fix potential panic for race condition when # conns == 0 + * #33/#34 - more granular connection locking + +### 0.3.6 - 2014-04-29 + +**Upgrading from 0.3.5**: There are no backward incompatible changes. + +This release includes a significant internal refactoring, designed +to better encapsulate responsibility, see #19. + +Specifically: + + * make `Conn` public + * move transport responsibilities into `Conn` from `Reader`/`Writer` + * supply callbacks for hooking into `Conn` events + +As part of the refactoring, a few additional clean exit related +issues were resolved: + + * wait group now includes all exit related goroutines + * ensure that readLoop exits before exiting cleanup + * always check messagesInFlight at readLoop exit + * close underlying connection last + +### 0.3.5 - 2014-04-05 + +**Upgrading from 0.3.4**: There are no backward incompatible changes. + +This release includes a few new features such as support for channel +sampling and sending along a user agent string (which is now displayed +in `nsqadmin`). + +Also, a critical bug fix for potential deadlocks (thanks @kjk +for reporting and help testing). + +New Features/Improvements: + + * #27 - reader logs disambiguate topic/channel + * #22 - channel sampling + * #23 - user agent + +Bug Fixes: + + * #24 - fix racey reader IDENTIFY buffering + * #29 - fix recursive RLock deadlocks + +### 0.3.4 - 2013-11-19 + +**Upgrading from 0.3.3**: There are no backward incompatible changes. + +This is a bug fix release, notably potential deadlocks in `Message.Requeue()` and `Message.Touch()` +as well as a potential busy loop cleaning up closed connections with in-flight messages. + +New Features/Improvements: + + * #14 - add `Reader.Configure()` + * #18 - return an exported error when an `nsqlookupd` address is already configured + +Bug Fixes: + + * #15 - dont let `handleError()` loop if already connected + * #17 - resolve potential deadlocks on `Message` responders + * #16 - eliminate busy loop when draining `finishedMessages` + +### 0.3.3 - 2013-10-21 + +**Upgrading from 0.3.2**: This release requires NSQ binary version `0.2.23+` for compression +support. + +This release contains significant `Reader` refactoring of the RDY handling code paths. The +motivation is documented in #1 however the commits in #8 identify individual changes. Additionally, +we eliminated deadlocks during connection cleanup in `Writer`. + +As a result, both user-facing APIs should now be considerably more robust and stable. Additionally, +`Reader` should behave better when backing off. + +New Features/Improvements: + + * #9 - ability to ignore publish responses in `Writer` + * #12 - `Requeue()` method on `Message` + * #6 - `Touch()` method on `Message` + * #4 - snappy/deflate feature negotiation + +Bug Fixes: + + * #8 - `Reader` RDY handling refactoring (race conditions, deadlocks, consolidation) + * #13 - fix `Writer` deadlocks + * #10 - stop accessing simplejson internals + * #5 - fix `max-in-flight` race condition + +### 0.3.2 - 2013-08-26 + +**Upgrading from 0.3.1**: This release requires NSQ binary version `0.2.22+` for TLS support. + +New Features/Improvements: + + * #227 - TLS feature negotiation + * #164/#202/#255 - add `Writer` + * #186 - `MaxBackoffDuration` of `0` disables backoff + * #175 - support for `nsqd` config option `--max-rdy-count` + * #169 - auto-reconnect to hard-coded `nsqd` + +Bug Fixes: + + * #254/#256/#257 - new connection RDY starvation + * #250 - `nsqlookupd` polling improvements + * #243 - limit `IsStarved()` to connections w/ in-flight messages + * #169 - use last RDY count for `IsStarved()`; redistribute RDY state + * #204 - fix early termination blocking + * #177 - support `broadcast_address` + * #161 - connection pool goroutine safety + +### 0.3.1 - 2013-02-07 + +**Upgrading from 0.3.0**: This release requires NSQ binary version `0.2.17+` for `TOUCH` support. + + * #119 - add TOUCH command + * #133 - improved handling of errors/magic + * #127 - send IDENTIFY (missed in #90) + * #16 - add backoff to Reader + +### 0.3.0 - 2013-01-07 + +**Upgrading from 0.2.4**: There are no backward incompatible changes to applications +written against the public `nsq.Reader` API. + +However, there *are* a few backward incompatible changes to the API for applications that +directly use other public methods, or properties of a few NSQ data types: + +`nsq.Message` IDs are now a type `nsq.MessageID` (a `[16]byte` array). The signatures of +`nsq.Finish()` and `nsq.Requeue()` reflect this change. + +`nsq.SendCommand()` and `nsq.Frame()` were removed in favor of `nsq.SendFramedResponse()`. + +`nsq.Subscribe()` no longer accepts `shortId` and `longId`. If upgrading your consumers +before upgrading your `nsqd` binaries to `0.2.16-rc.1` they will not be able to send the +optional custom identifiers. + + * #90 performance optimizations + * #81 reader performance improvements / MPUB support + +### 0.2.4 - 2012-10-15 + + * #69 added IsStarved() to reader API + +### 0.2.3 - 2012-10-11 + + * #64 timeouts on reader queries to lookupd + * #54 fix crash issue with reader cleaning up from unexpectedly closed nsqd connections + +### 0.2.2 - 2012-10-09 + + * Initial public release diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/LICENSE b/Godeps/_workspace/src/github.com/bitly/go-nsq/LICENSE new file mode 100644 index 0000000..89de354 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md b/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md new file mode 100644 index 0000000..a107b11 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md @@ -0,0 +1,28 @@ +## go-nsq + +The official Go package for [NSQ][nsq]. + +[![Build Status](https://secure.travis-ci.org/bitly/go-nsq.png?branch=master)][travis] [![GoDoc](https://godoc.org/github.com/bitly/go-nsq?status.svg)](https://godoc.org/github.com/bitly/go-nsq) + +The latest stable release is **[1.0.1][latest_tag]**. + +NOTE: The public API has been refactored as of `v1.0.0` and is not backwards compatible with +previous releases. **[0.3.7][legacy]** is the last stable release compatible with the legacy API. +Please read the [UPGRADING](UPGRADING.md) guide. + +### Docs + +See [godoc][nsq_gopkgdoc]. + +See the [main repo apps][apps] directory for examples of clients built using this package. + +[nsq]: https://github.com/bitly/nsq +[nsq_gopkgdoc]: http://godoc.org/github.com/bitly/go-nsq +[protocol]: http://bitly.github.io/nsq/clients/tcp_protocol_spec.html +[apps]: https://github.com/bitly/nsq/tree/master/apps +[consumer]: http://godoc.org/github.com/bitly/go-nsq#Consumer +[producer]: http://godoc.org/github.com/bitly/go-nsq#Producer +[pr30]: https://github.com/bitly/go-nsq/pull/30 +[legacy]: https://github.com/bitly/go-nsq/releases/tag/v0.3.7 +[travis]: http://travis-ci.org/bitly/go-nsq +[latest_tag]: https://github.com/bitly/go-nsq/releases/tag/v1.0.1 diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/UPGRADING.md b/Godeps/_workspace/src/github.com/bitly/go-nsq/UPGRADING.md new file mode 100644 index 0000000..d229aaa --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/UPGRADING.md @@ -0,0 +1,180 @@ +This outlines the backwards incompatible changes that were made to the public API after the +`v0.3.7` stable release, and and how to migrate existing legacy codebases. + +#### Background + +The original `go-nsq` codebase is some of our earliest Go code, and one of our first attempts at a +public Go library. + +We've learned a lot over the last 2 years and we wanted `go-nsq` to reflect the experiences we've +had working with the library as well as the general Go conventions and best practices we picked up +along the way. + +The diff can be seen via: https://github.com/bitly/go-nsq/compare/v0.3.7...HEAD + +The bulk of the refactoring came via: https://github.com/bitly/go-nsq/pull/30 + +#### Naming + +Previously, the high-level types we exposed were named `nsq.Reader` and `nsq.Writer`. These +reflected internal naming conventions we had used at bitly for some time but conflated semantics +with what a typical Go developer would expect (they obviously did not implement `io.Reader` and +`io.Writer`). + +We renamed these types to `nsq.Consumer` and `nsq.Producer`, which more effectively communicate +their purpose and is consistent with the NSQ documentation. + +#### Configuration + +In the previous API there were inconsistent and confusing ways to configure your clients. + +Now, configuration is performed *before* creating an `nsq.Consumer` or `nsq.Producer` by creating +an `nsq.Config` struct. The only valid way to do this is via `nsq.NewConfig` (i.e. using a struct +literal will panic due to invalid internal state). + +The `nsq.Config` struct has exported variables that can be set directly in a type-safe manner. You +can also call `cfg.Validate()` to check that the values are correct and within range. + +`nsq.Config` also exposes a convenient helper method `Set(k string, v interface{})` that can set +options by *coercing* the supplied `interface{}` value. + +This is incredibly convenient if you're reading options from a config file or in a serialized +format that does not exactly match the native types. + +It is both flexible and forgiving. + +#### Improving the nsq.Handler interface + +`go-nsq` attempts to make writing the common use case consumer incredibly easy. + +You specify a type that implements the `nsq.Handler` interface, the interface method is called per +message, and the return value of said method indicates to the library what the response to `nsqd` +should be (`FIN` or `REQ`), all the while managing flow control and backoff. + +However, more advanced use cases require the ability to respond to a message *later* +("asynchronously", if you will). Our original API provided a *second* message handler interface +called `nsq.AsyncHandler`. + +Unfortunately, it was never obvious from the name alone (or even the documentation) how to properly +use this form. The API was needlessly complex, involving the garbage creation of wrapping structs +to track state and respond to messages. + +We originally had the same problem in `pynsq`, our Python client library, and we were able to +resolve the tension and expose an API that was robust and supported all use cases. + +The new `go-nsq` message handler interface exposes only `nsq.Handler`, and its `HandleMessage` +method remains identical (specifically, `nsq.AsyncHandler` has been removed). + +Additionally, the API to configure handlers has been improved to provide better first-class support +for common operations. We've added `AddConcurrentHandlers` (for quickly spawning multiple handler +goroutines). + +For the most common use case, where you want `go-nsq` to respond to messages on your behalf, there +are no changes required! In fact, we've made it even easier to implement the `nsq.Handler` +interface for simple functions by providing the `nsq.HandlerFunc` type (in the spirit of the Go +standard library's `http.HandlerFunc`): + +```go +r, err := nsq.NewConsumer("test_topic", "test_channel", nsq.NewConfig()) +if err != nil { + log.Fatalf(err.Error()) +} + +r.AddHandler(nsq.HandlerFunc(func(m *nsq.Message) error { + return doSomeWork(m) +}) + +err := r.ConnectToNSQD(nsqdAddr) +if err != nil { + log.Fatalf(err.Error()) +} + +<-r.StopChan +``` + +In the new API, we've made the `nsq.Message` struct more robust, giving it the ability to proxy +responses. If you want to usurp control of the message from `go-nsq`, you simply call +`msg.DisableAutoResponse()`. + +This is effectively the same as if you had used `nsq.AsyncHandler`, only you don't need to manage +`nsq.FinishedMessage` structs or implement a separate interface. Instead you just keep/pass +references to the `nsq.Message` itself, and when you're ready to respond you call `msg.Finish()`, +`msg.Requeue()` or `msg.Touch()`. Additionally, this means you can make this +decision on a *per-message* basis rather than for the lifetime of the handler. + +Here is an example: + +```go +type myHandler struct {} + +func (h *myHandler) HandleMessage(m *nsq.Message) error { + m.DisableAutoResponse() + workerChan <- m + return nil +} + +go func() { + for m := range workerChan { + err := doSomeWork(m) + if err != nil { + m.Requeue(-1) + continue + } + m.Finish() + } +}() + +cfg := nsq.NewConfig() +cfg.MaxInFlight = 1000 +r, err := nsq.NewConsumer("test_topic", "test_channel", cfg) +if err != nil { + log.Fatalf(err.Error()) +} +r.AddConcurrentHandlers(&myHandler{}, 20) + +err := r.ConnectToNSQD(nsqdAddr) +if err != nil { + log.Fatalf(err.Error()) +} + +<-r.StopChan +``` + +#### Requeue without backoff + +As a side effect of the message handler restructuring above, it is now trivial to respond to a +message without triggering a backoff state in `nsq.Consumer` (which was not possible in the +previous API). + +The `nsq.Message` type now has a `msg.RequeueWithoutBackoff()` method for this purpose. + +#### Producer Error Handling + +Previously, `Writer` (now `Producer`) returned a triplicate of `frameType`, `responseBody`, and +`error` from calls to `*Publish`. + +This required the caller to check both `error` and `frameType` to confirm success. `Producer` +publish methods now return only `error`. + +#### Logging + +One of the challenges library implementors face is how to provide feedback via logging, while +exposing an interface that follows the standard library and still provides a means to control and +configure the output. + +In the new API, we've provided a method on `Consumer` and `Producer` called `SetLogger` that takes +an interface compatible with the Go standard library `log.Logger` (which can be instantiated via +`log.NewLogger`) and a traditional log level integer `nsq.LogLevel{Debug,Info,Warning,Error}`: + + Output(maxdepth int, s string) error + +This gives the user the flexibility to control the format, destination, and verbosity while still +conforming to standard library logging conventions. + +#### Misc. + +Un-exported `NewDeadlineTransport` and `ApiRequest`, which never should have been exported in the +first place. + +`nsq.Message` serialization switched away from `binary.{Read,Write}` for performance and +`nsq.Message` now implements the `io.WriterTo` interface. diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/api_request.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/api_request.go new file mode 100644 index 0000000..271be2c --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/api_request.go @@ -0,0 +1,78 @@ +package nsq + +import ( + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "time" + + "github.com/bitly/go-simplejson" +) + +type deadlinedConn struct { + Timeout time.Duration + net.Conn +} + +func (c *deadlinedConn) Read(b []byte) (n int, err error) { + c.Conn.SetReadDeadline(time.Now().Add(c.Timeout)) + return c.Conn.Read(b) +} + +func (c *deadlinedConn) Write(b []byte) (n int, err error) { + c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout)) + return c.Conn.Write(b) +} + +func newDeadlineTransport(timeout time.Duration) *http.Transport { + transport := &http.Transport{ + Dial: func(netw, addr string) (net.Conn, error) { + c, err := net.DialTimeout(netw, addr, timeout) + if err != nil { + return nil, err + } + return &deadlinedConn{timeout, c}, nil + }, + } + return transport +} + +func apiRequestNegotiateV1(method string, endpoint string, body io.Reader) (*simplejson.Json, error) { + httpclient := &http.Client{Transport: newDeadlineTransport(2 * time.Second)} + req, err := http.NewRequest(method, endpoint, body) + if err != nil { + return nil, err + } + + req.Header.Add("Accept", "application/vnd.nsq; version=1.0") + + resp, err := httpclient.Do(req) + if err != nil { + return nil, err + } + + respBody, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("got response %s %q", resp.Status, respBody) + } + + if len(respBody) == 0 { + respBody = []byte("{}") + } + + data, err := simplejson.NewJson(respBody) + if err != nil { + return nil, err + } + + if resp.Header.Get("X-NSQ-Content-Type") == "nsq; version=1.0" { + return data, nil + } + return data.Get("data"), nil +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/command.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/command.go new file mode 100644 index 0000000..71b1542 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/command.go @@ -0,0 +1,214 @@ +package nsq + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "io" + "strconv" + "time" +) + +var byteSpace = []byte(" ") +var byteNewLine = []byte("\n") + +// Command represents a command from a client to an NSQ daemon +type Command struct { + Name []byte + Params [][]byte + Body []byte +} + +// String returns the name and parameters of the Command +func (c *Command) String() string { + if len(c.Params) > 0 { + return fmt.Sprintf("%s %s", c.Name, string(bytes.Join(c.Params, byteSpace))) + } + return string(c.Name) +} + +// WriteTo implements the WriterTo interface and +// serializes the Command to the supplied Writer. +// +// It is suggested that the target Writer is buffered +// to avoid performing many system calls. +func (c *Command) WriteTo(w io.Writer) (int64, error) { + var total int64 + var buf [4]byte + + n, err := w.Write(c.Name) + total += int64(n) + if err != nil { + return total, err + } + + for _, param := range c.Params { + n, err := w.Write(byteSpace) + total += int64(n) + if err != nil { + return total, err + } + n, err = w.Write(param) + total += int64(n) + if err != nil { + return total, err + } + } + + n, err = w.Write(byteNewLine) + total += int64(n) + if err != nil { + return total, err + } + + if c.Body != nil { + bufs := buf[:] + binary.BigEndian.PutUint32(bufs, uint32(len(c.Body))) + n, err := w.Write(bufs) + total += int64(n) + if err != nil { + return total, err + } + n, err = w.Write(c.Body) + total += int64(n) + if err != nil { + return total, err + } + } + + return total, nil +} + +// Identify creates a new Command to provide information about the client. After connecting, +// it is generally the first message sent. +// +// The supplied map is marshaled into JSON to provide some flexibility +// for this command to evolve over time. +// +// See http://bitly.github.io/nsq/clients/tcp_protocol_spec.html#identify for information +// on the supported options +func Identify(js map[string]interface{}) (*Command, error) { + body, err := json.Marshal(js) + if err != nil { + return nil, err + } + return &Command{[]byte("IDENTIFY"), nil, body}, nil +} + +// Auth sends credentials for authentication +// +// After `Identify`, this is usually the first message sent, if auth is used. +func Auth(secret string) (*Command, error) { + return &Command{[]byte("AUTH"), nil, []byte(secret)}, nil +} + +// Register creates a new Command to add a topic/channel for the connected nsqd +func Register(topic string, channel string) *Command { + params := [][]byte{[]byte(topic)} + if len(channel) > 0 { + params = append(params, []byte(channel)) + } + return &Command{[]byte("REGISTER"), params, nil} +} + +// UnRegister creates a new Command to remove a topic/channel for the connected nsqd +func UnRegister(topic string, channel string) *Command { + params := [][]byte{[]byte(topic)} + if len(channel) > 0 { + params = append(params, []byte(channel)) + } + return &Command{[]byte("UNREGISTER"), params, nil} +} + +// Ping creates a new Command to keep-alive the state of all the +// announced topic/channels for a given client +func Ping() *Command { + return &Command{[]byte("PING"), nil, nil} +} + +// Publish creates a new Command to write a message to a given topic +func Publish(topic string, body []byte) *Command { + var params = [][]byte{[]byte(topic)} + return &Command{[]byte("PUB"), params, body} +} + +// MultiPublish creates a new Command to write more than one message to a given topic. +// This is useful for high-throughput situations to avoid roundtrips and saturate the pipe. +func MultiPublish(topic string, bodies [][]byte) (*Command, error) { + var params = [][]byte{[]byte(topic)} + + num := uint32(len(bodies)) + bodySize := 4 + for _, b := range bodies { + bodySize += len(b) + 4 + } + body := make([]byte, 0, bodySize) + buf := bytes.NewBuffer(body) + + err := binary.Write(buf, binary.BigEndian, &num) + if err != nil { + return nil, err + } + for _, b := range bodies { + err = binary.Write(buf, binary.BigEndian, int32(len(b))) + if err != nil { + return nil, err + } + _, err = buf.Write(b) + if err != nil { + return nil, err + } + } + + return &Command{[]byte("MPUB"), params, buf.Bytes()}, nil +} + +// Subscribe creates a new Command to subscribe to the given topic/channel +func Subscribe(topic string, channel string) *Command { + var params = [][]byte{[]byte(topic), []byte(channel)} + return &Command{[]byte("SUB"), params, nil} +} + +// Ready creates a new Command to specify +// the number of messages a client is willing to receive +func Ready(count int) *Command { + var params = [][]byte{[]byte(strconv.Itoa(count))} + return &Command{[]byte("RDY"), params, nil} +} + +// Finish creates a new Command to indiciate that +// a given message (by id) has been processed successfully +func Finish(id MessageID) *Command { + var params = [][]byte{id[:]} + return &Command{[]byte("FIN"), params, nil} +} + +// Requeue creates a new Command to indicate that +// a given message (by id) should be requeued after the given delay +// NOTE: a delay of 0 indicates immediate requeue +func Requeue(id MessageID, delay time.Duration) *Command { + var params = [][]byte{id[:], []byte(strconv.Itoa(int(delay / time.Millisecond)))} + return &Command{[]byte("REQ"), params, nil} +} + +// Touch creates a new Command to reset the timeout for +// a given message (by id) +func Touch(id MessageID) *Command { + var params = [][]byte{id[:]} + return &Command{[]byte("TOUCH"), params, nil} +} + +// StartClose creates a new Command to indicate that the +// client would like to start a close cycle. nsqd will no longer +// send messages to a client in this state and the client is expected +// finish pending messages and close the connection +func StartClose() *Command { + return &Command{[]byte("CLS"), nil, nil} +} + +// Nop creates a new Command that has no effect server side. +// Commonly used to respond to heartbeats +func Nop() *Command { + return &Command{[]byte("NOP"), nil, nil} +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go new file mode 100644 index 0000000..0abb264 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go @@ -0,0 +1,23 @@ +package nsq + +import ( + "bytes" + "io/ioutil" + "log" + "os" + "testing" +) + +func BenchmarkCommand(b *testing.B) { + b.StopTimer() + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + data := make([]byte, 2048) + cmd := Publish("test", data) + var buf bytes.Buffer + b.StartTimer() + + for i := 0; i < b.N; i++ { + cmd.WriteTo(&buf) + } +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go new file mode 100644 index 0000000..9a5563c --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go @@ -0,0 +1,562 @@ +package nsq + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "reflect" + "strconv" + "strings" + "time" + "unsafe" +) + +// Define handlers for setting config defaults, and setting config values from command line arguments or config files +type configHandler interface { + HandlesOption(c *Config, option string) bool + Set(c *Config, option string, value interface{}) error + Validate(c *Config) error +} + +type defaultsHandler interface { + SetDefaults(c *Config) error +} + +// Config is a struct of NSQ options +// +// The only valid way to create a Config is via NewConfig, using a struct literal will panic. +// After Config is passed into a high-level type (like Consumer, Producer, etc.) the values are no +// longer mutable (they are copied). +// +// Use Set(key string, value interface{}) as an alternate way to set parameters +type Config struct { + initialized bool + + // used to Initialize, Validate + configHandlers []configHandler + + // Deadlines for network reads and writes + ReadTimeout time.Duration `opt:"read_timeout" min:"100ms" max:"5m" default:"60s"` + WriteTimeout time.Duration `opt:"write_timeout" min:"100ms" max:"5m" default:"1s"` + + // Duration between polling lookupd for new producers, and fractional jitter to add to + // the lookupd pool loop. this helps evenly distribute requests even if multiple consumers + // restart at the same time + LookupdPollInterval time.Duration `opt:"lookupd_poll_interval" min:"5s" max:"5m" default:"60s"` + LookupdPollJitter float64 `opt:"lookupd_poll_jitter" min:"0" max:"1" default:"0.3"` + + // Maximum duration when REQueueing (for doubling of deferred requeue) + MaxRequeueDelay time.Duration `opt:"max_requeue_delay" min:"0" max:"60m" default:"15m"` + DefaultRequeueDelay time.Duration `opt:"default_requeue_delay" min:"0" max:"60m" default:"90s"` + // Unit of time for calculating consumer backoff + BackoffMultiplier time.Duration `opt:"backoff_multiplier" min:"0" max:"60m" default:"1s"` + + // Maximum number of times this consumer will attempt to process a message before giving up + MaxAttempts uint16 `opt:"max_attempts" min:"0" max:"65535" default:"5"` + // Amount of time in seconds to wait for a message from a producer when in a state where RDY + // counts are re-distributed (ie. max_in_flight < num_producers) + LowRdyIdleTimeout time.Duration `opt:"low_rdy_idle_timeout" min:"1s" max:"5m" default:"10s"` + + // Identifiers sent to nsqd representing this client + // UserAgent is in the spirit of HTTP (default: "/") + ClientID string `opt:"client_id"` // (defaults: short hostname) + Hostname string `opt:"hostname"` + UserAgent string `opt:"user_agent"` + + // Duration of time between heartbeats. This must be less than ReadTimeout + HeartbeatInterval time.Duration `opt:"heartbeat_interval" default:"30s"` + // Integer percentage to sample the channel (requires nsqd 0.2.25+) + SampleRate int32 `opt:"sample_rate" min:"0" max:"99"` + + // To set TLS config, use the following options: + // + // tls_v1 - Bool enable TLS negotiation + // tls_root_ca_file - String path to file containing root CA + // tls_insecure_skip_verify - Bool indicates whether this client should verify server certificates + // tls_cert - String path to file containing public key for certificate + // tls_key - String path to file containing private key for certificate + // tls_min_version - String indicating the minimum version of tls acceptable ('ssl30', 'tls10', 'tls11', 'tls12') + // + TlsV1 bool `opt:"tls_v1"` + TlsConfig *tls.Config `opt:"tls_config"` + + // Compression Settings + Deflate bool `opt:"deflate"` + DeflateLevel int `opt:"deflate_level" min:"1" max:"9" default:"6"` + Snappy bool `opt:"snappy"` + + // Size of the buffer (in bytes) used by nsqd for buffering writes to this connection + OutputBufferSize int64 `opt:"output_buffer_size" default:"16384"` + // Timeout used by nsqd before flushing buffered writes (set to 0 to disable). + // + // WARNING: configuring clients with an extremely low + // (< 25ms) output_buffer_timeout has a significant effect + // on nsqd CPU usage (particularly with > 50 clients connected). + OutputBufferTimeout time.Duration `opt:"output_buffer_timeout" default:"250ms"` + + // Maximum number of messages to allow in flight (concurrency knob) + MaxInFlight int `opt:"max_in_flight" min:"0" default:"1"` + + // Maximum amount of time to backoff when processing fails 0 == no backoff + MaxBackoffDuration time.Duration `opt:"max_backoff_duration" min:"0" max:"60m" default:"2m"` + + // The server-side message timeout for messages delivered to this client + MsgTimeout time.Duration `opt:"msg_timeout" min:"0"` + + // secret for nsqd authentication (requires nsqd 0.2.29+) + AuthSecret string `opt:"auth_secret"` +} + +// NewConfig returns a new default nsq configuration. +// +// This must be used to initialize Config structs. Values can be set directly, or through Config.Set() +func NewConfig() *Config { + c := &Config{} + c.configHandlers = append(c.configHandlers, &structTagsConfig{}, &tlsConfig{}) + c.initialized = true + if err := c.setDefaults(); err != nil { + panic(err.Error()) + } + return c +} + +// Set takes an option as a string and a value as an interface and +// attempts to set the appropriate configuration option. +// +// It attempts to coerce the value into the right format depending on the named +// option and the underlying type of the value passed in. +// +// Calls to Set() that take a time.Duration as an argument can be input as: +// +// "1000ms" (a string parsed by time.ParseDuration()) +// 1000 (an integer interpreted as milliseconds) +// 1000*time.Millisecond (a literal time.Duration value) +// +// Calls to Set() that take bool can be input as: +// +// "true" (a string parsed by strconv.ParseBool()) +// true (a boolean) +// 1 (an int where 1 == true and 0 == false) +// +// It returns an error for an invalid option or value. +func (c *Config) Set(option string, value interface{}) error { + c.assertInitialized() + option = strings.Replace(option, "-", "_", -1) + for _, h := range c.configHandlers { + if h.HandlesOption(c, option) { + return h.Set(c, option, value) + } + } + return fmt.Errorf("invalid option %s", option) +} + +func (c *Config) assertInitialized() { + if !c.initialized { + panic("Config{} must be created with NewConfig()") + } +} + +// Validate checks that all values are within specified min/max ranges +func (c *Config) Validate() error { + c.assertInitialized() + + for _, h := range c.configHandlers { + if err := h.Validate(c); err != nil { + return err + } + } + return nil +} + +func (c *Config) setDefaults() error { + for _, h := range c.configHandlers { + hh, ok := h.(defaultsHandler) + if ok { + if err := hh.SetDefaults(c); err != nil { + return err + } + } + } + + return nil +} + +type structTagsConfig struct{} + +// Handle options that are listed in StructTags +func (h *structTagsConfig) HandlesOption(c *Config, option string) bool { + val := reflect.ValueOf(c).Elem() + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + opt := field.Tag.Get("opt") + if opt == option { + return true + } + } + return false +} + +// Set values based on parameters in StructTags +func (h *structTagsConfig) Set(c *Config, option string, value interface{}) error { + val := reflect.ValueOf(c).Elem() + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + opt := field.Tag.Get("opt") + + if option != opt { + continue + } + + min := field.Tag.Get("min") + max := field.Tag.Get("max") + + fieldVal := val.FieldByName(field.Name) + dest := unsafeValueOf(fieldVal) + coercedVal, err := coerce(value, field.Type) + if err != nil { + return fmt.Errorf("failed to coerce option %s (%v) - %s", + option, value, err) + } + if min != "" { + coercedMinVal, _ := coerce(min, field.Type) + if valueCompare(coercedVal, coercedMinVal) == -1 { + return fmt.Errorf("invalid %s ! %v < %v", + option, coercedVal.Interface(), coercedMinVal.Interface()) + } + } + if max != "" { + coercedMaxVal, _ := coerce(max, field.Type) + if valueCompare(coercedVal, coercedMaxVal) == 1 { + return fmt.Errorf("invalid %s ! %v > %v", + option, coercedVal.Interface(), coercedMaxVal.Interface()) + } + } + dest.Set(coercedVal) + return nil + } + return fmt.Errorf("unknown option %s", option) +} + +func (h *structTagsConfig) SetDefaults(c *Config) error { + val := reflect.ValueOf(c).Elem() + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + opt := field.Tag.Get("opt") + defaultVal := field.Tag.Get("default") + if defaultVal == "" || opt == "" { + continue + } + + if err := c.Set(opt, defaultVal); err != nil { + return err + } + } + + hostname, err := os.Hostname() + if err != nil { + log.Fatalf("ERROR: unable to get hostname %s", err.Error()) + } + + c.ClientID = strings.Split(hostname, ".")[0] + c.Hostname = hostname + c.UserAgent = fmt.Sprintf("go-nsq/%s", VERSION) + return nil +} + +func (h *structTagsConfig) Validate(c *Config) error { + val := reflect.ValueOf(c).Elem() + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + + min := field.Tag.Get("min") + max := field.Tag.Get("max") + + if min == "" && max == "" { + continue + } + + value := val.FieldByName(field.Name) + + if min != "" { + coercedMinVal, _ := coerce(min, field.Type) + if valueCompare(value, coercedMinVal) == -1 { + return fmt.Errorf("invalid %s ! %v < %v", + field.Name, value.Interface(), coercedMinVal.Interface()) + } + } + if max != "" { + coercedMaxVal, _ := coerce(max, field.Type) + if valueCompare(value, coercedMaxVal) == 1 { + return fmt.Errorf("invalid %s ! %v > %v", + field.Name, value.Interface(), coercedMaxVal.Interface()) + } + } + } + + if c.HeartbeatInterval > c.ReadTimeout { + return fmt.Errorf("HeartbeatInterval %v must be less than ReadTimeout %v", c.HeartbeatInterval, c.ReadTimeout) + } + return nil +} + +// Parsing for higher order TLS settings +type tlsConfig struct { + certFile string + keyFile string +} + +func (t *tlsConfig) HandlesOption(c *Config, option string) bool { + switch option { + case "tls_root_ca_file", "tls_insecure_skip_verify", "tls_cert", "tls_key", "tls_min_version": + return true + } + return false +} + +func (t *tlsConfig) Set(c *Config, option string, value interface{}) error { + if c.TlsConfig == nil { + c.TlsConfig = &tls.Config{} + } + val := reflect.ValueOf(c.TlsConfig).Elem() + + switch option { + case "tls_cert", "tls_key": + if option == "tls_cert" { + t.certFile = value.(string) + } else { + t.keyFile = value.(string) + } + if t.certFile != "" && t.keyFile != "" && len(c.TlsConfig.Certificates) == 0 { + cert, err := tls.LoadX509KeyPair(t.certFile, t.keyFile) + if err != nil { + return err + } + c.TlsConfig.Certificates = []tls.Certificate{cert} + } + return nil + case "tls_root_ca_file": + filename, ok := value.(string) + if !ok { + return fmt.Errorf("ERROR: %v is not a string", value) + } + tlsCertPool := x509.NewCertPool() + caCertFile, err := ioutil.ReadFile(filename) + if err != nil { + return fmt.Errorf("ERROR: failed to read custom Certificate Authority file %s", err) + } + if !tlsCertPool.AppendCertsFromPEM(caCertFile) { + return fmt.Errorf("ERROR: failed to append certificates from Certificate Authority file") + } + c.TlsConfig.RootCAs = tlsCertPool + return nil + case "tls_insecure_skip_verify": + fieldVal := val.FieldByName("InsecureSkipVerify") + dest := unsafeValueOf(fieldVal) + coercedVal, err := coerce(value, fieldVal.Type()) + if err != nil { + return fmt.Errorf("failed to coerce option %s (%v) - %s", + option, value, err) + } + dest.Set(coercedVal) + return nil + case "tls_min_version": + version, ok := value.(string) + if !ok { + return fmt.Errorf("ERROR: %v is not a string", value) + } + switch version { + case "ssl30": + c.TlsConfig.MinVersion = tls.VersionSSL30 + case "tls10": + c.TlsConfig.MinVersion = tls.VersionTLS10 + case "tls11": + c.TlsConfig.MinVersion = tls.VersionTLS11 + case "tls12": + c.TlsConfig.MinVersion = tls.VersionTLS12 + default: + return fmt.Errorf("ERROR: %v is not a tls version", value) + } + return nil + } + + return fmt.Errorf("unknown option %s", option) +} + +func (t *tlsConfig) Validate(c *Config) error { + return nil +} + +// because Config contains private structs we can't use reflect.Value +// directly, instead we need to "unsafely" address the variable +func unsafeValueOf(val reflect.Value) reflect.Value { + uptr := unsafe.Pointer(val.UnsafeAddr()) + return reflect.NewAt(val.Type(), uptr).Elem() +} + +func valueCompare(v1 reflect.Value, v2 reflect.Value) int { + switch v1.Type().String() { + case "int", "int16", "int32", "int64": + if v1.Int() > v2.Int() { + return 1 + } else if v1.Int() < v2.Int() { + return -1 + } + return 0 + case "uint", "uint16", "uint32", "uint64": + if v1.Uint() > v2.Uint() { + return 1 + } else if v1.Uint() < v2.Uint() { + return -1 + } + return 0 + case "float32", "float64": + if v1.Float() > v2.Float() { + return 1 + } else if v1.Float() < v2.Float() { + return -1 + } + return 0 + case "time.Duration": + if v1.Interface().(time.Duration) > v2.Interface().(time.Duration) { + return 1 + } else if v1.Interface().(time.Duration) < v2.Interface().(time.Duration) { + return -1 + } + return 0 + } + panic("impossible") +} + +func coerce(v interface{}, typ reflect.Type) (reflect.Value, error) { + var err error + if typ.Kind() == reflect.Ptr { + return reflect.ValueOf(v), nil + } + switch typ.String() { + case "string": + v, err = coerceString(v) + case "int", "int16", "int32", "int64": + v, err = coerceInt64(v) + case "uint", "uint16", "uint32", "uint64": + v, err = coerceUint64(v) + case "float32", "float64": + v, err = coerceFloat64(v) + case "bool": + v, err = coerceBool(v) + case "time.Duration": + v, err = coerceDuration(v) + default: + v = nil + err = errors.New(fmt.Sprintf("invalid type %s", typ.String())) + } + return valueTypeCoerce(v, typ), err +} + +func valueTypeCoerce(v interface{}, typ reflect.Type) reflect.Value { + val := reflect.ValueOf(v) + if reflect.TypeOf(v) == typ { + return val + } + tval := reflect.New(typ).Elem() + switch typ.String() { + case "int", "int16", "int32", "int64": + tval.SetInt(val.Int()) + case "uint", "uint16", "uint32", "uint64": + tval.SetUint(val.Uint()) + case "float32", "float64": + tval.SetFloat(val.Float()) + } + return tval +} + +func coerceString(v interface{}) (string, error) { + switch v.(type) { + case string: + return v.(string), nil + case int, int16, int32, int64, uint, uint16, uint32, uint64: + return fmt.Sprintf("%d", v), nil + case float32, float64: + return fmt.Sprintf("%f", v), nil + default: + return fmt.Sprintf("%s", v), nil + } + return "", errors.New("invalid value type") +} + +func coerceDuration(v interface{}) (time.Duration, error) { + switch v.(type) { + case string: + return time.ParseDuration(v.(string)) + case int, int16, int32, int64: + // treat like ms + return time.Duration(reflect.ValueOf(v).Int()) * time.Millisecond, nil + case uint, uint16, uint32, uint64: + // treat like ms + return time.Duration(reflect.ValueOf(v).Uint()) * time.Millisecond, nil + case time.Duration: + return v.(time.Duration), nil + } + return 0, errors.New("invalid value type") +} + +func coerceBool(v interface{}) (bool, error) { + switch v.(type) { + case bool: + return v.(bool), nil + case string: + return strconv.ParseBool(v.(string)) + case int, int16, int32, int64: + return reflect.ValueOf(v).Int() != 0, nil + case uint, uint16, uint32, uint64: + return reflect.ValueOf(v).Uint() != 0, nil + } + return false, errors.New("invalid value type") +} + +func coerceFloat64(v interface{}) (float64, error) { + switch v.(type) { + case string: + return strconv.ParseFloat(v.(string), 64) + case int, int16, int32, int64: + return float64(reflect.ValueOf(v).Int()), nil + case uint, uint16, uint32, uint64: + return float64(reflect.ValueOf(v).Uint()), nil + case float32: + return float64(v.(float32)), nil + case float64: + return v.(float64), nil + } + return 0, errors.New("invalid value type") +} + +func coerceInt64(v interface{}) (int64, error) { + switch v.(type) { + case string: + return strconv.ParseInt(v.(string), 10, 64) + case int, int16, int32, int64: + return reflect.ValueOf(v).Int(), nil + case uint, uint16, uint32, uint64: + return int64(reflect.ValueOf(v).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +func coerceUint64(v interface{}) (uint64, error) { + switch v.(type) { + case string: + return strconv.ParseUint(v.(string), 10, 64) + case int, int16, int32, int64: + return uint64(reflect.ValueOf(v).Int()), nil + case uint, uint16, uint32, uint64: + return reflect.ValueOf(v).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go new file mode 100644 index 0000000..f06ea26 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go @@ -0,0 +1,41 @@ +package nsq + +import "testing" + +func TestConfigSet(t *testing.T) { + c := NewConfig() + if err := c.Set("not a real config value", struct{}{}); err == nil { + t.Error("No error when setting an invalid value") + } + if err := c.Set("tls_v1", "lol"); err == nil { + t.Error("No error when setting `tls_v1` to an invalid value") + } + if err := c.Set("tls_v1", true); err != nil { + t.Errorf("Error setting `tls_v1` config. %v", err) + } + + if err := c.Set("tls-insecure-skip-verify", true); err != nil { + t.Errorf("Error setting `tls-insecure-skip-verify` config. %v", err) + } + if c.TlsConfig.InsecureSkipVerify != true { + t.Errorf("Error setting `tls-insecure-skip-verify` config: %v", c.TlsConfig) + } + if err := c.Set("tls-min-version", "tls12"); err != nil { + t.Errorf("Error setting `tls-min-version` config: %v", err) + } + if err := c.Set("tls-min-version", "tls13"); err == nil { + t.Error("No error when setting `tls-min-version` to an invalid value") + } +} + +func TestConfigValidate(t *testing.T) { + c := NewConfig() + if err := c.Validate(); err != nil { + t.Error("initialized config is invalid") + } + c.DeflateLevel = 100 + if err := c.Validate(); err == nil { + t.Error("no error set for invalid value") + } + +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go new file mode 100644 index 0000000..ad31ef0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go @@ -0,0 +1,706 @@ +package nsq + +import ( + "bufio" + "bytes" + "compress/flate" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/mreiferson/go-snappystream" +) + +// IdentifyResponse represents the metadata +// returned from an IDENTIFY command to nsqd +type IdentifyResponse struct { + MaxRdyCount int64 `json:"max_rdy_count"` + TLSv1 bool `json:"tls_v1"` + Deflate bool `json:"deflate"` + Snappy bool `json:"snappy"` + AuthRequired bool `json:"auth_required"` +} + +// AuthResponse represents the metadata +// returned from an AUTH command to nsqd +type AuthResponse struct { + Identity string `json:"identity"` + IdentityUrl string `json:"identity_url"` + PermissionCount int64 `json:"permission_count"` +} + +type msgResponse struct { + msg *Message + cmd *Command + success bool + backoff bool +} + +// Conn represents a connection to nsqd +// +// Conn exposes a set of callbacks for the +// various events that occur on a connection +type Conn struct { + // 64bit atomic vars need to be first for proper alignment on 32bit platforms + messagesInFlight int64 + maxRdyCount int64 + rdyCount int64 + lastRdyCount int64 + lastMsgTimestamp int64 + + mtx sync.Mutex + + config *Config + + conn *net.TCPConn + tlsConn *tls.Conn + addr string + + delegate ConnDelegate + + logger logger + logLvl LogLevel + logFmt string + + r io.Reader + w io.Writer + + backoffCounter int32 + rdyRetryTimer *time.Timer + + cmdChan chan *Command + msgResponseChan chan *msgResponse + exitChan chan int + drainReady chan int + + closeFlag int32 + stopper sync.Once + wg sync.WaitGroup + + readLoopRunning int32 +} + +// NewConn returns a new Conn instance +func NewConn(addr string, config *Config, delegate ConnDelegate) *Conn { + if !config.initialized { + panic("Config must be created with NewConfig()") + } + return &Conn{ + addr: addr, + + config: config, + delegate: delegate, + + maxRdyCount: 2500, + lastMsgTimestamp: time.Now().UnixNano(), + + cmdChan: make(chan *Command), + msgResponseChan: make(chan *msgResponse), + exitChan: make(chan int), + drainReady: make(chan int), + } +} + +// SetLogger assigns the logger to use as well as a level. +// +// The format parameter is expected to be a printf compatible string with +// a single %s argument. This is useful if you want to provide additional +// context to the log messages that the connection will print, the default +// is '(%s)'. +// +// The logger parameter is an interface that requires the following +// method to be implemented (such as the the stdlib log.Logger): +// +// Output(calldepth int, s string) +// +func (c *Conn) SetLogger(l logger, lvl LogLevel, format string) { + c.logger = l + c.logLvl = lvl + c.logFmt = format + if c.logFmt == "" { + c.logFmt = "(%s)" + } +} + +// Connect dials and bootstraps the nsqd connection +// (including IDENTIFY) and returns the IdentifyResponse +func (c *Conn) Connect() (*IdentifyResponse, error) { + conn, err := net.DialTimeout("tcp", c.addr, time.Second) + if err != nil { + return nil, err + } + c.conn = conn.(*net.TCPConn) + c.r = conn + c.w = conn + + _, err = c.Write(MagicV2) + if err != nil { + c.Close() + return nil, fmt.Errorf("[%s] failed to write magic - %s", c.addr, err) + } + + resp, err := c.identify() + if err != nil { + return nil, err + } + + if resp != nil && resp.AuthRequired { + if c.config.AuthSecret == "" { + c.log(LogLevelError, "Auth Required") + return nil, errors.New("Auth Required") + } + err := c.auth(c.config.AuthSecret) + if err != nil { + c.log(LogLevelError, "Auth Failed %s", err) + return nil, err + } + } + + c.wg.Add(2) + atomic.StoreInt32(&c.readLoopRunning, 1) + go c.readLoop() + go c.writeLoop() + return resp, nil +} + +// Close idempotently initiates connection close +func (c *Conn) Close() error { + atomic.StoreInt32(&c.closeFlag, 1) + if c.conn != nil && atomic.LoadInt64(&c.messagesInFlight) == 0 { + return c.conn.CloseRead() + } + return nil +} + +// IsClosing indicates whether or not the +// connection is currently in the processing of +// gracefully closing +func (c *Conn) IsClosing() bool { + return atomic.LoadInt32(&c.closeFlag) == 1 +} + +// RDY returns the current RDY count +func (c *Conn) RDY() int64 { + return atomic.LoadInt64(&c.rdyCount) +} + +// LastRDY returns the previously set RDY count +func (c *Conn) LastRDY() int64 { + return atomic.LoadInt64(&c.lastRdyCount) +} + +// SetRDY stores the specified RDY count +func (c *Conn) SetRDY(rdy int64) { + atomic.StoreInt64(&c.rdyCount, rdy) + atomic.StoreInt64(&c.lastRdyCount, rdy) +} + +// MaxRDY returns the nsqd negotiated maximum +// RDY count that it will accept for this connection +func (c *Conn) MaxRDY() int64 { + return c.maxRdyCount +} + +// LastMessageTime returns a time.Time representing +// the time at which the last message was received +func (c *Conn) LastMessageTime() time.Time { + return time.Unix(0, atomic.LoadInt64(&c.lastMsgTimestamp)) +} + +// RemoteAddr returns the configured destination nsqd address +func (c *Conn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +// String returns the fully-qualified address +func (c *Conn) String() string { + return c.addr +} + +// Read performs a deadlined read on the underlying TCP connection +func (c *Conn) Read(p []byte) (int, error) { + c.conn.SetReadDeadline(time.Now().Add(c.config.ReadTimeout)) + return c.r.Read(p) +} + +// Write performs a deadlined write on the underlying TCP connection +func (c *Conn) Write(p []byte) (int, error) { + c.conn.SetWriteDeadline(time.Now().Add(c.config.WriteTimeout)) + return c.w.Write(p) +} + +// WriteCommand is a goroutine safe method to write a Command +// to this connection, and flush. +func (c *Conn) WriteCommand(cmd *Command) error { + c.mtx.Lock() + + _, err := cmd.WriteTo(c) + if err != nil { + goto exit + } + err = c.Flush() + +exit: + c.mtx.Unlock() + if err != nil { + c.log(LogLevelError, "IO error - %s", err) + c.delegate.OnIOError(c, err) + } + return err +} + +type flusher interface { + Flush() error +} + +// Flush writes all buffered data to the underlying TCP connection +func (c *Conn) Flush() error { + if f, ok := c.w.(flusher); ok { + return f.Flush() + } + return nil +} + +func (c *Conn) identify() (*IdentifyResponse, error) { + ci := make(map[string]interface{}) + ci["client_id"] = c.config.ClientID + ci["hostname"] = c.config.Hostname + ci["user_agent"] = c.config.UserAgent + ci["short_id"] = c.config.ClientID // deprecated + ci["long_id"] = c.config.Hostname // deprecated + ci["tls_v1"] = c.config.TlsV1 + ci["deflate"] = c.config.Deflate + ci["deflate_level"] = c.config.DeflateLevel + ci["snappy"] = c.config.Snappy + ci["feature_negotiation"] = true + if c.config.HeartbeatInterval == -1 { + ci["heartbeat_interval"] = -1 + } else { + ci["heartbeat_interval"] = int64(c.config.HeartbeatInterval / time.Millisecond) + } + ci["sample_rate"] = c.config.SampleRate + ci["output_buffer_size"] = c.config.OutputBufferSize + if c.config.OutputBufferTimeout == -1 { + ci["output_buffer_timeout"] = -1 + } else { + ci["output_buffer_timeout"] = int64(c.config.OutputBufferTimeout / time.Millisecond) + } + ci["msg_timeout"] = int64(c.config.MsgTimeout / time.Millisecond) + cmd, err := Identify(ci) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + + err = c.WriteCommand(cmd) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + + if frameType == FrameTypeError { + return nil, ErrIdentify{string(data)} + } + + // check to see if the server was able to respond w/ capabilities + // i.e. it was a JSON response + if data[0] != '{' { + return nil, nil + } + + resp := &IdentifyResponse{} + err = json.Unmarshal(data, resp) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + + c.log(LogLevelDebug, "IDENTIFY response: %+v", resp) + + c.maxRdyCount = resp.MaxRdyCount + + if resp.TLSv1 { + c.log(LogLevelInfo, "upgrading to TLS") + err := c.upgradeTLS(c.config.TlsConfig) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + } + + if resp.Deflate { + c.log(LogLevelInfo, "upgrading to Deflate") + err := c.upgradeDeflate(c.config.DeflateLevel) + if err != nil { + return nil, ErrIdentify{err.Error()} + } + } + + if resp.Snappy { + c.log(LogLevelInfo, "upgrading to Snappy") + err := c.upgradeSnappy() + if err != nil { + return nil, ErrIdentify{err.Error()} + } + } + + // now that connection is bootstrapped, enable read buffering + // (and write buffering if it's not already capable of Flush()) + c.r = bufio.NewReader(c.r) + if _, ok := c.w.(flusher); !ok { + c.w = bufio.NewWriter(c.w) + } + + return resp, nil +} + +func (c *Conn) upgradeTLS(tlsConf *tls.Config) error { + // create a local copy of the config to set ServerName for this connection + var conf tls.Config + if tlsConf != nil { + conf = *tlsConf + } + host, _, err := net.SplitHostPort(c.addr) + if err != nil { + return err + } + conf.ServerName = host + + c.tlsConn = tls.Client(c.conn, &conf) + err = c.tlsConn.Handshake() + if err != nil { + return err + } + c.r = c.tlsConn + c.w = c.tlsConn + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + return err + } + if frameType != FrameTypeResponse || !bytes.Equal(data, []byte("OK")) { + return errors.New("invalid response from TLS upgrade") + } + return nil +} + +func (c *Conn) upgradeDeflate(level int) error { + conn := net.Conn(c.conn) + if c.tlsConn != nil { + conn = c.tlsConn + } + fw, _ := flate.NewWriter(conn, level) + c.r = flate.NewReader(conn) + c.w = fw + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + return err + } + if frameType != FrameTypeResponse || !bytes.Equal(data, []byte("OK")) { + return errors.New("invalid response from Deflate upgrade") + } + return nil +} + +func (c *Conn) upgradeSnappy() error { + conn := net.Conn(c.conn) + if c.tlsConn != nil { + conn = c.tlsConn + } + c.r = snappystream.NewReader(conn, snappystream.SkipVerifyChecksum) + c.w = snappystream.NewWriter(conn) + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + return err + } + if frameType != FrameTypeResponse || !bytes.Equal(data, []byte("OK")) { + return errors.New("invalid response from Snappy upgrade") + } + return nil +} + +func (c *Conn) auth(secret string) error { + cmd, err := Auth(secret) + if err != nil { + return err + } + + err = c.WriteCommand(cmd) + if err != nil { + return err + } + + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + return err + } + + if frameType == FrameTypeError { + return errors.New("Error authenticating " + string(data)) + } + + resp := &AuthResponse{} + err = json.Unmarshal(data, resp) + if err != nil { + return err + } + + c.log(LogLevelInfo, "Auth accepted. Identity: %q %s Permissions: %d", resp.Identity, resp.IdentityUrl, resp.PermissionCount) + + return nil +} + +func (c *Conn) readLoop() { + delegate := &connMessageDelegate{c} + for { + if atomic.LoadInt32(&c.closeFlag) == 1 { + goto exit + } + + frameType, data, err := ReadUnpackedResponse(c) + if err != nil { + if !strings.Contains(err.Error(), "use of closed network connection") { + c.log(LogLevelError, "IO error - %s", err) + c.delegate.OnIOError(c, err) + } + goto exit + } + + if frameType == FrameTypeResponse && bytes.Equal(data, []byte("_heartbeat_")) { + c.log(LogLevelDebug, "heartbeat received") + c.delegate.OnHeartbeat(c) + err := c.WriteCommand(Nop()) + if err != nil { + c.log(LogLevelError, "IO error - %s", err) + c.delegate.OnIOError(c, err) + goto exit + } + continue + } + + switch frameType { + case FrameTypeResponse: + c.delegate.OnResponse(c, data) + case FrameTypeMessage: + msg, err := DecodeMessage(data) + if err != nil { + c.log(LogLevelError, "IO error - %s", err) + c.delegate.OnIOError(c, err) + goto exit + } + msg.Delegate = delegate + + atomic.AddInt64(&c.rdyCount, -1) + atomic.AddInt64(&c.messagesInFlight, 1) + atomic.StoreInt64(&c.lastMsgTimestamp, time.Now().UnixNano()) + + c.delegate.OnMessage(c, msg) + case FrameTypeError: + c.log(LogLevelError, "protocol error - %s", data) + c.delegate.OnError(c, data) + default: + c.log(LogLevelError, "IO error - %s", err) + c.delegate.OnIOError(c, fmt.Errorf("unknown frame type %d", frameType)) + } + } + +exit: + atomic.StoreInt32(&c.readLoopRunning, 0) + // start the connection close + messagesInFlight := atomic.LoadInt64(&c.messagesInFlight) + if messagesInFlight == 0 { + // if we exited readLoop with no messages in flight + // we need to explicitly trigger the close because + // writeLoop won't + c.close() + } else { + c.log(LogLevelWarning, "delaying close, %d outstanding messages", messagesInFlight) + } + c.wg.Done() + c.log(LogLevelInfo, "readLoop exiting") +} + +func (c *Conn) writeLoop() { + for { + select { + case <-c.exitChan: + c.log(LogLevelInfo, "breaking out of writeLoop") + // Indicate drainReady because we will not pull any more off msgResponseChan + close(c.drainReady) + goto exit + case cmd := <-c.cmdChan: + err := c.WriteCommand(cmd) + if err != nil { + c.log(LogLevelError, "error sending command %s - %s", cmd, err) + c.close() + continue + } + case resp := <-c.msgResponseChan: + // Decrement this here so it is correct even if we can't respond to nsqd + msgsInFlight := atomic.AddInt64(&c.messagesInFlight, -1) + + if resp.success { + c.log(LogLevelDebug, "FIN %s", resp.msg.ID) + c.delegate.OnMessageFinished(c, resp.msg) + if resp.backoff { + c.delegate.OnResume(c) + } + } else { + c.log(LogLevelDebug, "REQ %s", resp.msg.ID) + c.delegate.OnMessageRequeued(c, resp.msg) + if resp.backoff { + c.delegate.OnBackoff(c) + } + } + + err := c.WriteCommand(resp.cmd) + if err != nil { + c.log(LogLevelError, "error sending command %s - %s", resp.cmd, err) + c.close() + continue + } + + if msgsInFlight == 0 && + atomic.LoadInt32(&c.closeFlag) == 1 { + c.close() + continue + } + } + } + +exit: + c.wg.Done() + c.log(LogLevelInfo, "writeLoop exiting") +} + +func (c *Conn) close() { + // a "clean" connection close is orchestrated as follows: + // + // 1. CLOSE cmd sent to nsqd + // 2. CLOSE_WAIT response received from nsqd + // 3. set c.closeFlag + // 4. readLoop() exits + // a. if messages-in-flight > 0 delay close() + // i. writeLoop() continues receiving on c.msgResponseChan chan + // x. when messages-in-flight == 0 call close() + // b. else call close() immediately + // 5. c.exitChan close + // a. writeLoop() exits + // i. c.drainReady close + // 6a. launch cleanup() goroutine (we're racing with intraprocess + // routed messages, see comments below) + // a. wait on c.drainReady + // b. loop and receive on c.msgResponseChan chan + // until messages-in-flight == 0 + // i. ensure that readLoop has exited + // 6b. launch waitForCleanup() goroutine + // b. wait on waitgroup (covers readLoop() and writeLoop() + // and cleanup goroutine) + // c. underlying TCP connection close + // d. trigger Delegate OnClose() + // + c.stopper.Do(func() { + c.log(LogLevelInfo, "beginning close") + close(c.exitChan) + c.conn.CloseRead() + + c.wg.Add(1) + go c.cleanup() + + go c.waitForCleanup() + }) +} + +func (c *Conn) cleanup() { + <-c.drainReady + ticker := time.NewTicker(100 * time.Millisecond) + lastWarning := time.Now() + // writeLoop has exited, drain any remaining in flight messages + for { + // we're racing with readLoop which potentially has a message + // for handling so infinitely loop until messagesInFlight == 0 + // and readLoop has exited + var msgsInFlight int64 + select { + case <-c.msgResponseChan: + msgsInFlight = atomic.AddInt64(&c.messagesInFlight, -1) + case <-ticker.C: + msgsInFlight = atomic.LoadInt64(&c.messagesInFlight) + } + if msgsInFlight > 0 { + if time.Now().Sub(lastWarning) > time.Second { + c.log(LogLevelWarning, "draining... waiting for %d messages in flight", msgsInFlight) + lastWarning = time.Now() + } + continue + } + // until the readLoop has exited we cannot be sure that there + // still won't be a race + if atomic.LoadInt32(&c.readLoopRunning) == 1 { + if time.Now().Sub(lastWarning) > time.Second { + c.log(LogLevelWarning, "draining... readLoop still running") + lastWarning = time.Now() + } + continue + } + goto exit + } + +exit: + ticker.Stop() + c.wg.Done() + c.log(LogLevelInfo, "finished draining, cleanup exiting") +} + +func (c *Conn) waitForCleanup() { + // this blocks until readLoop and writeLoop + // (and cleanup goroutine above) have exited + c.wg.Wait() + c.conn.CloseWrite() + c.log(LogLevelInfo, "clean close complete") + c.delegate.OnClose(c) +} + +func (c *Conn) onMessageFinish(m *Message) { + c.msgResponseChan <- &msgResponse{m, Finish(m.ID), true, true} +} + +func (c *Conn) onMessageRequeue(m *Message, delay time.Duration, backoff bool) { + if delay == -1 { + // linear delay + delay = c.config.DefaultRequeueDelay * time.Duration(m.Attempts) + // bound the requeueDelay to configured max + if delay > c.config.MaxRequeueDelay { + delay = c.config.MaxRequeueDelay + } + } + c.msgResponseChan <- &msgResponse{m, Requeue(m.ID, delay), false, backoff} +} + +func (c *Conn) onMessageTouch(m *Message) { + select { + case c.cmdChan <- Touch(m.ID): + case <-c.exitChan: + } +} + +func (c *Conn) log(lvl LogLevel, line string, args ...interface{}) { + if c.logger == nil { + return + } + + if c.logLvl > lvl { + return + } + + c.logger.Output(2, fmt.Sprintf("%-4s %s %s", logPrefix(lvl), + fmt.Sprintf(c.logFmt, c.String()), + fmt.Sprintf(line, args...))) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go new file mode 100644 index 0000000..b77b6a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go @@ -0,0 +1,1094 @@ +package nsq + +import ( + "bytes" + "errors" + "fmt" + "log" + "math" + "math/rand" + "net" + "net/url" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +// Handler is the message processing interface for Consumer +// +// Implement this interface for handlers that return whether or not message +// processing completed successfully. +// +// When the return value is nil Consumer will automatically handle FINishing. +// +// When the returned value is non-nil Consumer will automatically handle REQueing. +type Handler interface { + HandleMessage(message *Message) error +} + +// HandlerFunc is a convenience type to avoid having to declare a struct +// to implement the Handler interface, it can be used like this: +// +// consumer.AddHandler(nsq.HandlerFunc(func(m *Message) error { +// // handle the message +// })) +type HandlerFunc func(message *Message) error + +// HandleMessage implements the Handler interface +func (h HandlerFunc) HandleMessage(m *Message) error { + return h(m) +} + +// DiscoveryFilter is an interface accepted by `SetBehaviorDelegate()` +// for filtering the nsqds returned from discovery via nsqlookupd +type DiscoveryFilter interface { + Filter([]string) []string +} + +// FailedMessageLogger is an interface that can be implemented by handlers that wish +// to receive a callback when a message is deemed "failed" (i.e. the number of attempts +// exceeded the Consumer specified MaxAttemptCount) +type FailedMessageLogger interface { + LogFailedMessage(message *Message) +} + +// ConsumerStats represents a snapshot of the state of a Consumer's connections and the messages +// it has seen +type ConsumerStats struct { + MessagesReceived uint64 + MessagesFinished uint64 + MessagesRequeued uint64 + Connections int +} + +var instCount int64 + +// Consumer is a high-level type to consume from NSQ. +// +// A Consumer instance is supplied a Handler that will be executed +// concurrently via goroutines to handle processing the stream of messages +// consumed from the specified topic/channel. See: Handler/HandlerFunc +// for details on implementing the interface to create handlers. +// +// If configured, it will poll nsqlookupd instances and handle connection (and +// reconnection) to any discovered nsqds. +type Consumer struct { + // 64bit atomic vars need to be first for proper alignment on 32bit platforms + messagesReceived uint64 + messagesFinished uint64 + messagesRequeued uint64 + totalRdyCount int64 + backoffDuration int64 + maxInFlight int32 + + mtx sync.RWMutex + + logger logger + logLvl LogLevel + + behaviorDelegate interface{} + + id int64 + topic string + channel string + config Config + + rng *rand.Rand + + needRDYRedistributed int32 + + backoffMtx sync.RWMutex + backoffCounter int32 + + incomingMessages chan *Message + + rdyRetryMtx sync.RWMutex + rdyRetryTimers map[string]*time.Timer + + pendingConnections map[string]*Conn + connections map[string]*Conn + + nsqdTCPAddrs []string + + // used at connection close to force a possible reconnect + lookupdRecheckChan chan int + lookupdHTTPAddrs []string + lookupdQueryIndex int + + wg sync.WaitGroup + runningHandlers int32 + stopFlag int32 + connectedFlag int32 + stopHandler sync.Once + + // read from this channel to block until consumer is cleanly stopped + StopChan chan int + exitChan chan int +} + +// NewConsumer creates a new instance of Consumer for the specified topic/channel +// +// The only valid way to create a Config is via NewConfig, using a struct literal will panic. +// After Config is passed into NewConsumer the values are no longer mutable (they are copied). +func NewConsumer(topic string, channel string, config *Config) (*Consumer, error) { + config.assertInitialized() + + if err := config.Validate(); err != nil { + return nil, err + } + + if !IsValidTopicName(topic) { + return nil, errors.New("invalid topic name") + } + + if !IsValidChannelName(channel) { + return nil, errors.New("invalid channel name") + } + + r := &Consumer{ + id: atomic.AddInt64(&instCount, 1), + + topic: topic, + channel: channel, + config: *config, + + logger: log.New(os.Stderr, "", log.Flags()), + logLvl: LogLevelInfo, + maxInFlight: int32(config.MaxInFlight), + + incomingMessages: make(chan *Message), + + rdyRetryTimers: make(map[string]*time.Timer), + pendingConnections: make(map[string]*Conn), + connections: make(map[string]*Conn), + + lookupdRecheckChan: make(chan int, 1), + + rng: rand.New(rand.NewSource(time.Now().UnixNano())), + + StopChan: make(chan int), + exitChan: make(chan int), + } + r.wg.Add(1) + go r.rdyLoop() + return r, nil +} + +// Stats retrieves the current connection and message statistics for a Consumer +func (r *Consumer) Stats() *ConsumerStats { + return &ConsumerStats{ + MessagesReceived: atomic.LoadUint64(&r.messagesReceived), + MessagesFinished: atomic.LoadUint64(&r.messagesFinished), + MessagesRequeued: atomic.LoadUint64(&r.messagesRequeued), + Connections: len(r.conns()), + } +} + +func (r *Consumer) conns() []*Conn { + r.mtx.RLock() + conns := make([]*Conn, 0, len(r.connections)) + for _, c := range r.connections { + conns = append(conns, c) + } + r.mtx.RUnlock() + return conns +} + +// SetLogger assigns the logger to use as well as a level +// +// The logger parameter is an interface that requires the following +// method to be implemented (such as the the stdlib log.Logger): +// +// Output(calldepth int, s string) +// +func (r *Consumer) SetLogger(l logger, lvl LogLevel) { + r.logger = l + r.logLvl = lvl +} + +// SetBehaviorDelegate takes a type implementing one or more +// of the following interfaces that modify the behavior +// of the `Consumer`: +// +// DiscoveryFilter +// +func (r *Consumer) SetBehaviorDelegate(cb interface{}) { + matched := false + + if _, ok := cb.(DiscoveryFilter); ok { + matched = true + } + + if !matched { + panic("behavior delegate does not have any recognized methods") + } + + r.behaviorDelegate = cb +} + +// perConnMaxInFlight calculates the per-connection max-in-flight count. +// +// This may change dynamically based on the number of connections to nsqd the Consumer +// is responsible for. +func (r *Consumer) perConnMaxInFlight() int64 { + b := float64(r.getMaxInFlight()) + s := b / float64(len(r.conns())) + return int64(math.Min(math.Max(1, s), b)) +} + +// IsStarved indicates whether any connections for this consumer are blocked on processing +// before being able to receive more messages (ie. RDY count of 0 and not exiting) +func (r *Consumer) IsStarved() bool { + for _, conn := range r.conns() { + threshold := int64(float64(atomic.LoadInt64(&conn.lastRdyCount)) * 0.85) + inFlight := atomic.LoadInt64(&conn.messagesInFlight) + if inFlight >= threshold && inFlight > 0 && !conn.IsClosing() { + return true + } + } + return false +} + +func (r *Consumer) getMaxInFlight() int32 { + return atomic.LoadInt32(&r.maxInFlight) +} + +// ChangeMaxInFlight sets a new maximum number of messages this comsumer instance +// will allow in-flight, and updates all existing connections as appropriate. +// +// For example, ChangeMaxInFlight(0) would pause message flow +// +// If already connected, it updates the reader RDY state for each connection. +func (r *Consumer) ChangeMaxInFlight(maxInFlight int) { + if r.getMaxInFlight() == int32(maxInFlight) { + return + } + + atomic.StoreInt32(&r.maxInFlight, int32(maxInFlight)) + + for _, c := range r.conns() { + r.maybeUpdateRDY(c) + } +} + +// ConnectToNSQLookupd adds an nsqlookupd address to the list for this Consumer instance. +// +// If it is the first to be added, it initiates an HTTP request to discover nsqd +// producers for the configured topic. +// +// A goroutine is spawned to handle continual polling. +func (r *Consumer) ConnectToNSQLookupd(addr string) error { + if atomic.LoadInt32(&r.stopFlag) == 1 { + return errors.New("consumer stopped") + } + if atomic.LoadInt32(&r.runningHandlers) == 0 { + return errors.New("no handlers") + } + + if err := validatedLookupAddr(addr); err != nil { + return err + } + + atomic.StoreInt32(&r.connectedFlag, 1) + + r.mtx.Lock() + for _, x := range r.lookupdHTTPAddrs { + if x == addr { + r.mtx.Unlock() + return nil + } + } + r.lookupdHTTPAddrs = append(r.lookupdHTTPAddrs, addr) + numLookupd := len(r.lookupdHTTPAddrs) + r.mtx.Unlock() + + // if this is the first one, kick off the go loop + if numLookupd == 1 { + r.queryLookupd() + r.wg.Add(1) + go r.lookupdLoop() + } + + return nil +} + +// ConnectToNSQLookupd adds multiple nsqlookupd address to the list for this Consumer instance. +// +// If adding the first address it initiates an HTTP request to discover nsqd +// producers for the configured topic. +// +// A goroutine is spawned to handle continual polling. +func (r *Consumer) ConnectToNSQLookupds(addresses []string) error { + for _, addr := range addresses { + err := r.ConnectToNSQLookupd(addr) + if err != nil { + return err + } + } + return nil +} + +func validatedLookupAddr(addr string) error { + if strings.Contains(addr, "/") { + _, err := url.Parse(addr) + if err != nil { + return err + } + return nil + } + if !strings.Contains(addr, ":") { + return errors.New("missing port") + } + return nil +} + +// poll all known lookup servers every LookupdPollInterval +func (r *Consumer) lookupdLoop() { + // add some jitter so that multiple consumers discovering the same topic, + // when restarted at the same time, dont all connect at once. + jitter := time.Duration(int64(r.rng.Float64() * + r.config.LookupdPollJitter * float64(r.config.LookupdPollInterval))) + ticker := time.NewTicker(r.config.LookupdPollInterval) + + select { + case <-time.After(jitter): + case <-r.exitChan: + goto exit + } + + for { + select { + case <-ticker.C: + r.queryLookupd() + case <-r.lookupdRecheckChan: + r.queryLookupd() + case <-r.exitChan: + goto exit + } + } + +exit: + ticker.Stop() + r.log(LogLevelInfo, "exiting lookupdLoop") + r.wg.Done() +} + +// return the next lookupd endpoint to query +// keeping track of which one was last used +func (r *Consumer) nextLookupdEndpoint() string { + r.mtx.RLock() + addr := r.lookupdHTTPAddrs[r.lookupdQueryIndex] + num := len(r.lookupdHTTPAddrs) + r.mtx.RUnlock() + r.lookupdQueryIndex = (r.lookupdQueryIndex + 1) % num + + urlString := addr + if !strings.Contains(urlString, "://") { + urlString = "http://" + addr + } + + u, err := url.Parse(urlString) + if err != nil { + panic(err) + } + if u.Path == "/" || u.Path == "" { + u.Path = "/lookup" + } + + v, err := url.ParseQuery(u.RawQuery) + v.Add("topic", r.topic) + u.RawQuery = v.Encode() + return u.String() +} + +// make an HTTP req to one of the configured nsqlookupd instances to discover +// which nsqd's provide the topic we are consuming. +// +// initiate a connection to any new producers that are identified. +func (r *Consumer) queryLookupd() { + endpoint := r.nextLookupdEndpoint() + + r.log(LogLevelInfo, "querying nsqlookupd %s", endpoint) + + data, err := apiRequestNegotiateV1("GET", endpoint, nil) + if err != nil { + r.log(LogLevelError, "error querying nsqlookupd (%s) - %s", endpoint, err) + return + } + + // { + // "channels": [], + // "producers": [ + // { + // "broadcast_address": "jehiah-air.local", + // "http_port": 4151, + // "tcp_port": 4150 + // } + // ], + // "timestamp": 1340152173 + // } + nsqdAddrs := make([]string, 0) + for i := range data.Get("producers").MustArray() { + producer := data.Get("producers").GetIndex(i) + broadcastAddress := producer.Get("broadcast_address").MustString() + port := producer.Get("tcp_port").MustInt() + joined := net.JoinHostPort(broadcastAddress, strconv.Itoa(port)) + nsqdAddrs = append(nsqdAddrs, joined) + } + // apply filter + if discoveryFilter, ok := r.behaviorDelegate.(DiscoveryFilter); ok { + nsqdAddrs = discoveryFilter.Filter(nsqdAddrs) + } + for _, addr := range nsqdAddrs { + err = r.ConnectToNSQD(addr) + if err != nil && err != ErrAlreadyConnected { + r.log(LogLevelError, "(%s) error connecting to nsqd - %s", addr, err) + continue + } + } +} + +// ConnectToNSQD takes multiple nsqd addresses to connect directly to. +// +// It is recommended to use ConnectToNSQLookupd so that topics are discovered +// automatically. This method is useful when you want to connect to local instance. +func (r *Consumer) ConnectToNSQDs(addresses []string) error { + for _, addr := range addresses { + err := r.ConnectToNSQD(addr) + if err != nil { + return err + } + } + return nil +} + +// ConnectToNSQD takes a nsqd address to connect directly to. +// +// It is recommended to use ConnectToNSQLookupd so that topics are discovered +// automatically. This method is useful when you want to connect to a single, local, +// instance. +func (r *Consumer) ConnectToNSQD(addr string) error { + if atomic.LoadInt32(&r.stopFlag) == 1 { + return errors.New("consumer stopped") + } + + if atomic.LoadInt32(&r.runningHandlers) == 0 { + return errors.New("no handlers") + } + + atomic.StoreInt32(&r.connectedFlag, 1) + + conn := NewConn(addr, &r.config, &consumerConnDelegate{r}) + conn.SetLogger(r.logger, r.logLvl, + fmt.Sprintf("%3d [%s/%s] (%%s)", r.id, r.topic, r.channel)) + + r.mtx.Lock() + _, pendingOk := r.pendingConnections[addr] + _, ok := r.connections[addr] + if ok || pendingOk { + r.mtx.Unlock() + return ErrAlreadyConnected + } + if !pendingOk { + r.pendingConnections[addr] = conn + } + r.nsqdTCPAddrs = append(r.nsqdTCPAddrs, addr) + r.mtx.Unlock() + + r.log(LogLevelInfo, "(%s) connecting to nsqd", addr) + + cleanupConnection := func() { + r.mtx.Lock() + delete(r.pendingConnections, addr) + r.mtx.Unlock() + conn.Close() + } + + resp, err := conn.Connect() + if err != nil { + cleanupConnection() + return err + } + + if resp != nil { + if resp.MaxRdyCount < int64(r.getMaxInFlight()) { + r.log(LogLevelWarning, + "(%s) max RDY count %d < consumer max in flight %d, truncation possible", + conn.String(), resp.MaxRdyCount, r.getMaxInFlight()) + } + } + + cmd := Subscribe(r.topic, r.channel) + err = conn.WriteCommand(cmd) + if err != nil { + cleanupConnection() + return fmt.Errorf("[%s] failed to subscribe to %s:%s - %s", + conn, r.topic, r.channel, err.Error()) + } + + r.mtx.Lock() + delete(r.pendingConnections, addr) + r.connections[addr] = conn + r.mtx.Unlock() + + // pre-emptive signal to existing connections to lower their RDY count + for _, c := range r.conns() { + r.maybeUpdateRDY(c) + } + + return nil +} + +func indexOf(n string, h []string) int { + for i, a := range h { + if n == a { + return i + } + } + return -1 +} + +// DisconnectFromNSQD closes the connection to and removes the specified +// `nsqd` address from the list +func (r *Consumer) DisconnectFromNSQD(addr string) error { + r.mtx.Lock() + defer r.mtx.Unlock() + + idx := indexOf(addr, r.nsqdTCPAddrs) + if idx == -1 { + return ErrNotConnected + } + + // slice delete + r.nsqdTCPAddrs = append(r.nsqdTCPAddrs[:idx], r.nsqdTCPAddrs[idx+1:]...) + + pendingConn, pendingOk := r.pendingConnections[addr] + conn, ok := r.connections[addr] + + if ok { + conn.Close() + } else if pendingOk { + pendingConn.Close() + } + + return nil +} + +// DisconnectFromNSQLookupd removes the specified `nsqlookupd` address +// from the list used for periodic discovery. +func (r *Consumer) DisconnectFromNSQLookupd(addr string) error { + r.mtx.Lock() + defer r.mtx.Unlock() + + idx := indexOf(addr, r.lookupdHTTPAddrs) + if idx == -1 { + return ErrNotConnected + } + + if len(r.lookupdHTTPAddrs) == 1 { + return errors.New(fmt.Sprintf("cannot disconnect from only remaining nsqlookupd HTTP address %s", addr)) + } + + r.lookupdHTTPAddrs = append(r.lookupdHTTPAddrs[:idx], r.lookupdHTTPAddrs[idx+1:]...) + + return nil +} + +func (r *Consumer) onConnMessage(c *Conn, msg *Message) { + atomic.AddInt64(&r.totalRdyCount, -1) + atomic.AddUint64(&r.messagesReceived, 1) + r.incomingMessages <- msg + r.maybeUpdateRDY(c) +} + +func (r *Consumer) onConnMessageFinished(c *Conn, msg *Message) { + atomic.AddUint64(&r.messagesFinished, 1) +} + +func (r *Consumer) onConnMessageRequeued(c *Conn, msg *Message) { + atomic.AddUint64(&r.messagesRequeued, 1) +} + +func (r *Consumer) onConnBackoff(c *Conn) { + r.startStopContinueBackoff(c, false) +} + +func (r *Consumer) onConnResume(c *Conn) { + r.startStopContinueBackoff(c, true) +} + +func (r *Consumer) startStopContinueBackoff(conn *Conn, success bool) { + // prevent many async failures/successes from immediately resulting in + // max backoff/normal rate (by ensuring that we dont continually incr/decr + // the counter during a backoff period) + if r.inBackoffBlock() { + return + } + + // update backoff state + r.backoffMtx.Lock() + backoffUpdated := false + if success { + if r.backoffCounter > 0 { + r.backoffCounter-- + backoffUpdated = true + } + } else { + maxBackoffCount := int32(math.Max(1, math.Ceil( + math.Log2(r.config.MaxBackoffDuration.Seconds())))) + if r.backoffCounter < maxBackoffCount { + r.backoffCounter++ + backoffUpdated = true + } + } + r.backoffMtx.Unlock() + + if r.backoffCounter == 0 && backoffUpdated { + // exit backoff + count := r.perConnMaxInFlight() + r.log(LogLevelWarning, "exiting backoff, returning all to RDY %d", count) + for _, c := range r.conns() { + r.updateRDY(c, count) + } + } else if r.backoffCounter > 0 { + // start or continue backoff + backoffDuration := r.backoffDurationForCount(r.backoffCounter) + atomic.StoreInt64(&r.backoffDuration, backoffDuration.Nanoseconds()) + time.AfterFunc(backoffDuration, func() { + var choice *Conn + + atomic.StoreInt64(&r.backoffDuration, 0) + + // pick a random connection to test the waters + var i int + conns := r.conns() + if len(conns) == 0 { + return + } + idx := r.rng.Intn(len(conns)) + for _, c := range conns { + if i == idx { + choice = c + break + } + i++ + } + + r.log(LogLevelWarning, + "(%s) backoff timeout expired, sending RDY 1", + choice.String()) + // while in backoff only ever let 1 message at a time through + r.updateRDY(choice, 1) + }) + + r.log(LogLevelWarning, "backing off for %.04f seconds (backoff level %d), setting all to RDY 0", + backoffDuration.Seconds(), r.backoffCounter) + + // send RDY 0 immediately (to *all* connections) + for _, c := range r.conns() { + r.updateRDY(c, 0) + } + } +} + +func (r *Consumer) onConnResponse(c *Conn, data []byte) { + switch { + case bytes.Equal(data, []byte("CLOSE_WAIT")): + // server is ready for us to close (it ack'd our StartClose) + // we can assume we will not receive any more messages over this channel + // (but we can still write back responses) + r.log(LogLevelInfo, "(%s) received CLOSE_WAIT from nsqd", c.String()) + c.Close() + } +} + +func (r *Consumer) onConnError(c *Conn, data []byte) {} + +func (r *Consumer) onConnHeartbeat(c *Conn) {} + +func (r *Consumer) onConnIOError(c *Conn, err error) { + c.Close() +} + +func (r *Consumer) onConnClose(c *Conn) { + var hasRDYRetryTimer bool + + // remove this connections RDY count from the consumer's total + rdyCount := c.RDY() + atomic.AddInt64(&r.totalRdyCount, -rdyCount) + + r.rdyRetryMtx.Lock() + if timer, ok := r.rdyRetryTimers[c.String()]; ok { + // stop any pending retry of an old RDY update + timer.Stop() + delete(r.rdyRetryTimers, c.String()) + hasRDYRetryTimer = true + } + r.rdyRetryMtx.Unlock() + + r.mtx.Lock() + delete(r.connections, c.String()) + left := len(r.connections) + r.mtx.Unlock() + + r.log(LogLevelWarning, "there are %d connections left alive", left) + + if (hasRDYRetryTimer || rdyCount > 0) && + (int32(left) == r.getMaxInFlight() || r.inBackoff()) { + // we're toggling out of (normal) redistribution cases and this conn + // had a RDY count... + // + // trigger RDY redistribution to make sure this RDY is moved + // to a new connection + atomic.StoreInt32(&r.needRDYRedistributed, 1) + } + + // we were the last one (and stopping) + if atomic.LoadInt32(&r.stopFlag) == 1 { + if left == 0 { + r.stopHandlers() + } + return + } + + r.mtx.RLock() + numLookupd := len(r.lookupdHTTPAddrs) + reconnect := indexOf(c.String(), r.nsqdTCPAddrs) >= 0 + r.mtx.RUnlock() + if numLookupd > 0 { + // trigger a poll of the lookupd + select { + case r.lookupdRecheckChan <- 1: + default: + } + } else if reconnect { + // there are no lookupd and we still have this nsqd TCP address in our list... + // try to reconnect after a bit + go func(addr string) { + for { + r.log(LogLevelInfo, "(%s) re-connecting in 15 seconds...", addr) + time.Sleep(15 * time.Second) + if atomic.LoadInt32(&r.stopFlag) == 1 { + break + } + r.mtx.RLock() + reconnect := indexOf(addr, r.nsqdTCPAddrs) >= 0 + r.mtx.RUnlock() + if !reconnect { + r.log(LogLevelWarning, "(%s) skipped reconnect after removal...", addr) + return + } + err := r.ConnectToNSQD(addr) + if err != nil && err != ErrAlreadyConnected { + r.log(LogLevelError, "(%s) error connecting to nsqd - %s", addr, err) + continue + } + break + } + }(c.RemoteAddr().String()) + } +} + +func (r *Consumer) backoffDurationForCount(count int32) time.Duration { + backoffDuration := r.config.BackoffMultiplier * + time.Duration(math.Pow(2, float64(count))) + if backoffDuration > r.config.MaxBackoffDuration { + backoffDuration = r.config.MaxBackoffDuration + } + return backoffDuration +} + +func (r *Consumer) inBackoff() bool { + r.backoffMtx.RLock() + backoffCounter := r.backoffCounter + r.backoffMtx.RUnlock() + return backoffCounter > 0 +} + +func (r *Consumer) inBackoffBlock() bool { + return atomic.LoadInt64(&r.backoffDuration) > 0 +} + +func (r *Consumer) maybeUpdateRDY(conn *Conn) { + if r.inBackoff() || r.inBackoffBlock() { + return + } + + remain := conn.RDY() + lastRdyCount := conn.LastRDY() + count := r.perConnMaxInFlight() + + // refill when at 1, or at 25%, or if connections have changed and we're imbalanced + if remain <= 1 || remain < (lastRdyCount/4) || (count > 0 && count < remain) { + r.log(LogLevelDebug, "(%s) sending RDY %d (%d remain from last RDY %d)", + conn, count, remain, lastRdyCount) + r.updateRDY(conn, count) + } else { + r.log(LogLevelDebug, "(%s) skip sending RDY %d (%d remain out of last RDY %d)", + conn, count, remain, lastRdyCount) + } +} + +func (r *Consumer) rdyLoop() { + redistributeTicker := time.NewTicker(5 * time.Second) + + for { + select { + case <-redistributeTicker.C: + r.redistributeRDY() + case <-r.exitChan: + goto exit + } + } + +exit: + redistributeTicker.Stop() + r.log(LogLevelInfo, "rdyLoop exiting") + r.wg.Done() +} + +func (r *Consumer) updateRDY(c *Conn, count int64) error { + if c.IsClosing() { + return nil + } + + // never exceed the nsqd's configured max RDY count + if count > c.MaxRDY() { + count = c.MaxRDY() + } + + // stop any pending retry of an old RDY update + r.rdyRetryMtx.Lock() + if timer, ok := r.rdyRetryTimers[c.String()]; ok { + timer.Stop() + delete(r.rdyRetryTimers, c.String()) + } + r.rdyRetryMtx.Unlock() + + // never exceed our global max in flight. truncate if possible. + // this could help a new connection get partial max-in-flight + rdyCount := c.RDY() + maxPossibleRdy := int64(r.getMaxInFlight()) - atomic.LoadInt64(&r.totalRdyCount) + rdyCount + if maxPossibleRdy > 0 && maxPossibleRdy < count { + count = maxPossibleRdy + } + if maxPossibleRdy <= 0 && count > 0 { + if rdyCount == 0 { + // we wanted to exit a zero RDY count but we couldn't send it... + // in order to prevent eternal starvation we reschedule this attempt + // (if any other RDY update succeeds this timer will be stopped) + r.rdyRetryMtx.Lock() + r.rdyRetryTimers[c.String()] = time.AfterFunc(5*time.Second, + func() { + r.updateRDY(c, count) + }) + r.rdyRetryMtx.Unlock() + } + return ErrOverMaxInFlight + } + + return r.sendRDY(c, count) +} + +func (r *Consumer) sendRDY(c *Conn, count int64) error { + if count == 0 && c.LastRDY() == 0 { + // no need to send. It's already that RDY count + return nil + } + + atomic.AddInt64(&r.totalRdyCount, -c.RDY()+count) + c.SetRDY(count) + err := c.WriteCommand(Ready(int(count))) + if err != nil { + r.log(LogLevelError, "(%s) error sending RDY %d - %s", c.String(), count, err) + return err + } + return nil +} + +func (r *Consumer) redistributeRDY() { + if r.inBackoffBlock() { + return + } + + numConns := int32(len(r.conns())) + maxInFlight := r.getMaxInFlight() + if numConns > maxInFlight { + r.log(LogLevelDebug, "redistributing RDY state (%d conns > %d max_in_flight)", + numConns, maxInFlight) + atomic.StoreInt32(&r.needRDYRedistributed, 1) + } + + if r.inBackoff() && numConns > 1 { + r.log(LogLevelDebug, "redistributing RDY state (in backoff and %d conns > 1)", numConns) + atomic.StoreInt32(&r.needRDYRedistributed, 1) + } + + if !atomic.CompareAndSwapInt32(&r.needRDYRedistributed, 1, 0) { + return + } + + conns := r.conns() + possibleConns := make([]*Conn, 0, len(conns)) + for _, c := range conns { + lastMsgDuration := time.Now().Sub(c.LastMessageTime()) + rdyCount := c.RDY() + r.log(LogLevelDebug, "(%s) rdy: %d (last message received %s)", + c.String(), rdyCount, lastMsgDuration) + if rdyCount > 0 && lastMsgDuration > r.config.LowRdyIdleTimeout { + r.log(LogLevelDebug, "(%s) idle connection, giving up RDY", c.String()) + r.updateRDY(c, 0) + } + possibleConns = append(possibleConns, c) + } + + availableMaxInFlight := int64(maxInFlight) - atomic.LoadInt64(&r.totalRdyCount) + if r.inBackoff() { + availableMaxInFlight = 1 - atomic.LoadInt64(&r.totalRdyCount) + } + + for len(possibleConns) > 0 && availableMaxInFlight > 0 { + availableMaxInFlight-- + i := r.rng.Int() % len(possibleConns) + c := possibleConns[i] + // delete + possibleConns = append(possibleConns[:i], possibleConns[i+1:]...) + r.log(LogLevelDebug, "(%s) redistributing RDY", c.String()) + r.updateRDY(c, 1) + } +} + +// Stop will initiate a graceful stop of the Consumer (permanent) +// +// NOTE: receive on StopChan to block until this process completes +func (r *Consumer) Stop() { + if !atomic.CompareAndSwapInt32(&r.stopFlag, 0, 1) { + return + } + + r.log(LogLevelInfo, "stopping...") + + if len(r.conns()) == 0 { + r.stopHandlers() + } else { + for _, c := range r.conns() { + err := c.WriteCommand(StartClose()) + if err != nil { + r.log(LogLevelError, "(%s) error sending CLS - %s", c.String(), err) + } + } + + time.AfterFunc(time.Second*30, func() { + r.stopHandlers() + }) + } +} + +func (r *Consumer) stopHandlers() { + r.stopHandler.Do(func() { + r.log(LogLevelInfo, "stopping handlers") + close(r.incomingMessages) + }) +} + +// AddHandler sets the Handler for messages received by this Consumer. This can be called +// multiple times to add additional handlers. Handler will have a 1:1 ratio to message handling goroutines. +// +// This panics if called after connecting to NSQD or NSQ Lookupd +// +// (see Handler or HandlerFunc for details on implementing this interface) +func (r *Consumer) AddHandler(handler Handler) { + r.AddConcurrentHandlers(handler, 1) +} + +// AddConcurrentHandlers sets the Handler for messages received by this Consumer. It +// takes a second argument which indicates the number of goroutines to spawn for +// message handling. +// +// This panics if called after connecting to NSQD or NSQ Lookupd +// +// (see Handler or HandlerFunc for details on implementing this interface) +func (r *Consumer) AddConcurrentHandlers(handler Handler, concurrency int) { + if atomic.LoadInt32(&r.connectedFlag) == 1 { + panic("already connected") + } + + atomic.AddInt32(&r.runningHandlers, int32(concurrency)) + for i := 0; i < concurrency; i++ { + go r.handlerLoop(handler) + } +} + +func (r *Consumer) handlerLoop(handler Handler) { + r.log(LogLevelDebug, "starting Handler") + + for { + message, ok := <-r.incomingMessages + if !ok { + goto exit + } + + if r.shouldFailMessage(message, handler) { + message.Finish() + continue + } + + err := handler.HandleMessage(message) + if err != nil { + r.log(LogLevelError, "Handler returned error (%s) for msg %s", err, message.ID) + if !message.IsAutoResponseDisabled() { + message.Requeue(-1) + } + continue + } + + if !message.IsAutoResponseDisabled() { + message.Finish() + } + } + +exit: + r.log(LogLevelDebug, "stopping Handler") + if atomic.AddInt32(&r.runningHandlers, -1) == 0 { + r.exit() + } +} + +func (r *Consumer) shouldFailMessage(message *Message, handler interface{}) bool { + // message passed the max number of attempts + if r.config.MaxAttempts > 0 && message.Attempts > r.config.MaxAttempts { + r.log(LogLevelWarning, "msg %s attempted %d times, giving up", + message.ID, message.Attempts) + + logger, ok := handler.(FailedMessageLogger) + if ok { + logger.LogFailedMessage(message) + } + + return true + } + return false +} + +func (r *Consumer) exit() { + close(r.exitChan) + r.wg.Wait() + close(r.StopChan) +} + +func (r *Consumer) log(lvl LogLevel, line string, args ...interface{}) { + if r.logger == nil { + return + } + + if r.logLvl > lvl { + return + } + + r.logger.Output(2, fmt.Sprintf("%-4s %3d [%s/%s] %s", + logPrefix(lvl), r.id, r.topic, r.channel, + fmt.Sprintf(line, args...))) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go new file mode 100644 index 0000000..3febc12 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go @@ -0,0 +1,225 @@ +package nsq + +import ( + "bytes" + "crypto/tls" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "strings" + "testing" + "time" + + "github.com/bitly/go-simplejson" +) + +type MyTestHandler struct { + t *testing.T + q *Consumer + messagesSent int + messagesReceived int + messagesFailed int +} + +var nullLogger = log.New(ioutil.Discard, "", log.LstdFlags) + +func (h *MyTestHandler) LogFailedMessage(message *Message) { + h.messagesFailed++ + h.q.Stop() +} + +func (h *MyTestHandler) HandleMessage(message *Message) error { + if string(message.Body) == "TOBEFAILED" { + h.messagesReceived++ + return errors.New("fail this message") + } + + data, err := simplejson.NewJson(message.Body) + if err != nil { + return err + } + + msg, _ := data.Get("msg").String() + if msg != "single" && msg != "double" { + h.t.Error("message 'action' was not correct: ", msg, data) + } + h.messagesReceived++ + return nil +} + +func SendMessage(t *testing.T, port int, topic string, method string, body []byte) { + httpclient := &http.Client{} + endpoint := fmt.Sprintf("http://127.0.0.1:%d/%s?topic=%s", port, method, topic) + req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body)) + resp, err := httpclient.Do(req) + if err != nil { + t.Fatalf(err.Error()) + return + } + resp.Body.Close() +} + +func TestConsumer(t *testing.T) { + consumerTest(t, nil) +} + +func TestConsumerTLS(t *testing.T) { + consumerTest(t, func(c *Config) { + c.TlsV1 = true + c.TlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + }) +} + +func TestConsumerDeflate(t *testing.T) { + consumerTest(t, func(c *Config) { + c.Deflate = true + }) +} + +func TestConsumerSnappy(t *testing.T) { + consumerTest(t, func(c *Config) { + c.Snappy = true + }) +} + +func TestConsumerTLSDeflate(t *testing.T) { + consumerTest(t, func(c *Config) { + c.TlsV1 = true + c.TlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + c.Deflate = true + }) +} + +func TestConsumerTLSSnappy(t *testing.T) { + consumerTest(t, func(c *Config) { + c.TlsV1 = true + c.TlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + c.Snappy = true + }) +} + +func TestConsumerTLSClientCert(t *testing.T) { + envDl := os.Getenv("NSQ_DOWNLOAD") + if strings.HasPrefix(envDl, "nsq-0.2.24") || strings.HasPrefix(envDl, "nsq-0.2.27") { + t.Log("skipping due to older nsqd") + return + } + cert, _ := tls.LoadX509KeyPair("./test/client.pem", "./test/client.key") + consumerTest(t, func(c *Config) { + c.TlsV1 = true + c.TlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + InsecureSkipVerify: true, + } + }) +} + +func TestConsumerTLSClientCertViaSet(t *testing.T) { + envDl := os.Getenv("NSQ_DOWNLOAD") + if strings.HasPrefix(envDl, "nsq-0.2.24") || strings.HasPrefix(envDl, "nsq-0.2.27") { + t.Log("skipping due to older nsqd") + return + } + consumerTest(t, func(c *Config) { + c.Set("tls_v1", true) + c.Set("tls_cert", "./test/client.pem") + c.Set("tls_key", "./test/client.key") + c.Set("tls_insecure_skip_verify", true) + }) +} + +func consumerTest(t *testing.T, cb func(c *Config)) { + config := NewConfig() + // so that the test can simulate reaching max requeues and a call to LogFailedMessage + config.DefaultRequeueDelay = 0 + // so that the test wont timeout from backing off + config.MaxBackoffDuration = time.Millisecond * 50 + if cb != nil { + cb(config) + } + topicName := "rdr_test" + if config.Deflate { + topicName = topicName + "_deflate" + } else if config.Snappy { + topicName = topicName + "_snappy" + } + if config.TlsV1 { + topicName = topicName + "_tls" + } + topicName = topicName + strconv.Itoa(int(time.Now().Unix())) + q, _ := NewConsumer(topicName, "ch", config) + // q.SetLogger(nullLogger, LogLevelInfo) + + h := &MyTestHandler{ + t: t, + q: q, + } + q.AddHandler(h) + + SendMessage(t, 4151, topicName, "put", []byte(`{"msg":"single"}`)) + SendMessage(t, 4151, topicName, "mput", []byte("{\"msg\":\"double\"}\n{\"msg\":\"double\"}")) + SendMessage(t, 4151, topicName, "put", []byte("TOBEFAILED")) + h.messagesSent = 4 + + addr := "127.0.0.1:4150" + err := q.ConnectToNSQD(addr) + if err != nil { + t.Fatal(err) + } + + stats := q.Stats() + if stats.Connections == 0 { + t.Fatal("stats report 0 connections (should be > 0)") + } + + err = q.ConnectToNSQD(addr) + if err == nil { + t.Fatal("should not be able to connect to the same NSQ twice") + } + + err = q.DisconnectFromNSQD("1.2.3.4:4150") + if err == nil { + t.Fatal("should not be able to disconnect from an unknown nsqd") + } + + err = q.ConnectToNSQD("1.2.3.4:4150") + if err == nil { + t.Fatal("should not be able to connect to non-existent nsqd") + } + + err = q.DisconnectFromNSQD("1.2.3.4:4150") + if err != nil { + t.Fatal("should be able to disconnect from an nsqd - " + err.Error()) + } + + <-q.StopChan + + stats = q.Stats() + if stats.Connections != 0 { + t.Fatalf("stats report %d active connections (should be 0)", stats.Connections) + } + + stats = q.Stats() + if stats.MessagesReceived != uint64(h.messagesReceived+h.messagesFailed) { + t.Fatalf("stats report %d messages received (should be %d)", + stats.MessagesReceived, + h.messagesReceived+h.messagesFailed) + } + + if h.messagesReceived != 8 || h.messagesSent != 4 { + t.Fatalf("end of test. should have handled a diff number of messages (got %d, sent %d)", h.messagesReceived, h.messagesSent) + } + if h.messagesFailed != 1 { + t.Fatal("failed message not done") + } +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go new file mode 100644 index 0000000..4bef7aa --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go @@ -0,0 +1,143 @@ +package nsq + +import "time" + +// LogLevel specifies the severity of a given log message +type LogLevel int + +type logger interface { + Output(calldepth int, s string) error +} + +// logging constants +const ( + LogLevelDebug LogLevel = iota + LogLevelInfo + LogLevelWarning + LogLevelError + + LogLevelDebugPrefix = "DBG" + LogLevelInfoPrefix = "INF" + LogLevelWarningPrefix = "WRN" + LogLevelErrorPrefix = "ERR" +) + +// LogPrefix Resolution +func logPrefix(lvl LogLevel) string { + var prefix string + + switch lvl { + case LogLevelDebug: + prefix = LogLevelDebugPrefix + case LogLevelInfo: + prefix = LogLevelInfoPrefix + case LogLevelWarning: + prefix = LogLevelWarningPrefix + case LogLevelError: + prefix = LogLevelErrorPrefix + } + + return prefix +} + +// MessageDelegate is an interface of methods that are used as +// callbacks in Message +type MessageDelegate interface { + // OnFinish is called when the Finish() method + // is triggered on the Message + OnFinish(*Message) + + // OnRequeue is called when the Requeue() method + // is triggered on the Message + OnRequeue(m *Message, delay time.Duration, backoff bool) + + // OnTouch is called when the Touch() method + // is triggered on the Message + OnTouch(*Message) +} + +type connMessageDelegate struct { + c *Conn +} + +func (d *connMessageDelegate) OnFinish(m *Message) { d.c.onMessageFinish(m) } +func (d *connMessageDelegate) OnRequeue(m *Message, t time.Duration, b bool) { + d.c.onMessageRequeue(m, t, b) +} +func (d *connMessageDelegate) OnTouch(m *Message) { d.c.onMessageTouch(m) } + +// ConnDelegate is an interface of methods that are used as +// callbacks in Conn +type ConnDelegate interface { + // OnResponse is called when the connection + // receives a FrameTypeResponse from nsqd + OnResponse(*Conn, []byte) + + // OnError is called when the connection + // receives a FrameTypeError from nsqd + OnError(*Conn, []byte) + + // OnMessage is called when the connection + // receives a FrameTypeMessage from nsqd + OnMessage(*Conn, *Message) + + // OnMessageFinished is called when the connection + // handles a FIN command from a message handler + OnMessageFinished(*Conn, *Message) + + // OnMessageRequeued is called when the connection + // handles a REQ command from a message handler + OnMessageRequeued(*Conn, *Message) + + // OnBackoff is called when the connection triggers a backoff state + OnBackoff(*Conn) + + // OnResume is called when the connection triggers a resume state + OnResume(*Conn) + + // OnIOError is called when the connection experiences + // a low-level TCP transport error + OnIOError(*Conn, error) + + // OnHeartbeat is called when the connection + // receives a heartbeat from nsqd + OnHeartbeat(*Conn) + + // OnClose is called when the connection + // closes, after all cleanup + OnClose(*Conn) +} + +// keeps the exported Consumer struct clean of the exported methods +// required to implement the ConnDelegate interface +type consumerConnDelegate struct { + r *Consumer +} + +func (d *consumerConnDelegate) OnResponse(c *Conn, data []byte) { d.r.onConnResponse(c, data) } +func (d *consumerConnDelegate) OnError(c *Conn, data []byte) { d.r.onConnError(c, data) } +func (d *consumerConnDelegate) OnMessage(c *Conn, m *Message) { d.r.onConnMessage(c, m) } +func (d *consumerConnDelegate) OnMessageFinished(c *Conn, m *Message) { d.r.onConnMessageFinished(c, m) } +func (d *consumerConnDelegate) OnMessageRequeued(c *Conn, m *Message) { d.r.onConnMessageRequeued(c, m) } +func (d *consumerConnDelegate) OnBackoff(c *Conn) { d.r.onConnBackoff(c) } +func (d *consumerConnDelegate) OnResume(c *Conn) { d.r.onConnResume(c) } +func (d *consumerConnDelegate) OnIOError(c *Conn, err error) { d.r.onConnIOError(c, err) } +func (d *consumerConnDelegate) OnHeartbeat(c *Conn) { d.r.onConnHeartbeat(c) } +func (d *consumerConnDelegate) OnClose(c *Conn) { d.r.onConnClose(c) } + +// keeps the exported Producer struct clean of the exported methods +// required to implement the ConnDelegate interface +type producerConnDelegate struct { + w *Producer +} + +func (d *producerConnDelegate) OnResponse(c *Conn, data []byte) { d.w.onConnResponse(c, data) } +func (d *producerConnDelegate) OnError(c *Conn, data []byte) { d.w.onConnError(c, data) } +func (d *producerConnDelegate) OnMessage(c *Conn, m *Message) {} +func (d *producerConnDelegate) OnMessageFinished(c *Conn, m *Message) {} +func (d *producerConnDelegate) OnMessageRequeued(c *Conn, m *Message) {} +func (d *producerConnDelegate) OnBackoff(c *Conn) {} +func (d *producerConnDelegate) OnResume(c *Conn) {} +func (d *producerConnDelegate) OnIOError(c *Conn, err error) { d.w.onConnIOError(c, err) } +func (d *producerConnDelegate) OnHeartbeat(c *Conn) { d.w.onConnHeartbeat(c) } +func (d *producerConnDelegate) OnClose(c *Conn) { d.w.onConnClose(c) } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go new file mode 100644 index 0000000..7e1cd63 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go @@ -0,0 +1,41 @@ +package nsq + +import ( + "errors" + "fmt" +) + +// ErrNotConnected is returned when a publish command is made +// against a Producer that is not connected +var ErrNotConnected = errors.New("not connected") + +// ErrStopped is returned when a publish command is +// made against a Producer that has been stopped +var ErrStopped = errors.New("stopped") + +// ErrAlreadyConnected is returned from ConnectToNSQD when already connected +var ErrAlreadyConnected = errors.New("already connected") + +// ErrOverMaxInFlight is returned from Consumer if over max-in-flight +var ErrOverMaxInFlight = errors.New("over configure max-inflight") + +// ErrIdentify is returned from Conn as part of the IDENTIFY handshake +type ErrIdentify struct { + Reason string +} + +// Error returns a stringified error +func (e ErrIdentify) Error() string { + return fmt.Sprintf("failed to IDENTIFY - %s", e.Reason) +} + +// ErrProtocol is returned from Producer when encountering +// an NSQ protocol level error +type ErrProtocol struct { + Reason string +} + +// Error returns a stringified error +func (e ErrProtocol) Error() string { + return e.Reason +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go new file mode 100644 index 0000000..f77191a --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go @@ -0,0 +1,162 @@ +package nsq + +import ( + "bytes" + "encoding/binary" + "io" + "io/ioutil" + "sync/atomic" + "time" +) + +// The number of bytes for a Message.ID +const MsgIDLength = 16 + +// MessageID is the ASCII encoded hexadecimal message ID +type MessageID [MsgIDLength]byte + +// Message is the fundamental data type containing +// the id, body, and metadata +type Message struct { + ID MessageID + Body []byte + Timestamp int64 + Attempts uint16 + + Delegate MessageDelegate + + autoResponseDisabled int32 + responded int32 +} + +// NewMessage creates a Message, initializes some metadata, +// and returns a pointer +func NewMessage(id MessageID, body []byte) *Message { + return &Message{ + ID: id, + Body: body, + Timestamp: time.Now().UnixNano(), + } +} + +// DisableAutoResponse disables the automatic response that +// would normally be sent when a handler.HandleMessage +// returns (FIN/REQ based on the error value returned). +// +// This is useful if you want to batch, buffer, or asynchronously +// respond to messages. +func (m *Message) DisableAutoResponse() { + atomic.StoreInt32(&m.autoResponseDisabled, 1) +} + +// IsAutoResponseDisabled indicates whether or not this message +// will be responded to automatically +func (m *Message) IsAutoResponseDisabled() bool { + return atomic.LoadInt32(&m.autoResponseDisabled) == 1 +} + +// HasResponded indicates whether or not this message has been responded to +func (m *Message) HasResponded() bool { + return atomic.LoadInt32(&m.responded) == 1 +} + +// Finish sends a FIN command to the nsqd which +// sent this message +func (m *Message) Finish() { + if m.HasResponded() { + return + } + m.Delegate.OnFinish(m) + atomic.StoreInt32(&m.responded, 1) +} + +// Touch sends a TOUCH command to the nsqd which +// sent this message +func (m *Message) Touch() { + if m.HasResponded() { + return + } + m.Delegate.OnTouch(m) +} + +// Requeue sends a REQ command to the nsqd which +// sent this message, using the supplied delay. +// +// A delay of -1 will automatically calculate +// based on the number of attempts and the +// configured default_requeue_delay +func (m *Message) Requeue(delay time.Duration) { + m.doRequeue(delay, true) +} + +// RequeueWithoutBackoff sends a REQ command to the nsqd which +// sent this message, using the supplied delay. +// +// Notably, using this method to respond does not trigger a backoff +// event on the configured Delegate. +func (m *Message) RequeueWithoutBackoff(delay time.Duration) { + m.doRequeue(delay, false) +} + +func (m *Message) doRequeue(delay time.Duration, backoff bool) { + if m.HasResponded() { + return + } + m.Delegate.OnRequeue(m, delay, backoff) + atomic.StoreInt32(&m.responded, 1) +} + +// WriteTo implements the WriterTo interface and serializes +// the message into the supplied producer. +// +// It is suggested that the target Writer is buffered to +// avoid performing many system calls. +func (m *Message) WriteTo(w io.Writer) (int64, error) { + var buf [10]byte + var total int64 + + binary.BigEndian.PutUint64(buf[:8], uint64(m.Timestamp)) + binary.BigEndian.PutUint16(buf[8:10], uint16(m.Attempts)) + + n, err := w.Write(buf[:]) + total += int64(n) + if err != nil { + return total, err + } + + n, err = w.Write(m.ID[:]) + total += int64(n) + if err != nil { + return total, err + } + + n, err = w.Write(m.Body) + total += int64(n) + if err != nil { + return total, err + } + + return total, nil +} + +// DecodeMessage deseralizes data (as []byte) and creates a new Message +func DecodeMessage(b []byte) (*Message, error) { + var msg Message + + msg.Timestamp = int64(binary.BigEndian.Uint64(b[:8])) + msg.Attempts = binary.BigEndian.Uint16(b[8:10]) + + buf := bytes.NewBuffer(b[10:]) + + _, err := io.ReadFull(buf, msg.ID[:]) + if err != nil { + return nil, err + } + + msg.Body, err = ioutil.ReadAll(buf) + if err != nil { + return nil, err + } + + return &msg, nil +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go new file mode 100644 index 0000000..9f62a00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go @@ -0,0 +1,265 @@ +package nsq + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "net" + "os" + "strconv" + "testing" + "time" +) + +type instruction struct { + delay time.Duration + frameType int32 + body []byte +} + +type mockNSQD struct { + script []instruction + got [][]byte + tcpAddr *net.TCPAddr + tcpListener net.Listener + exitChan chan int +} + +func newMockNSQD(script []instruction) *mockNSQD { + n := &mockNSQD{ + script: script, + exitChan: make(chan int), + } + + addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + tcpListener, err := net.Listen("tcp", addr.String()) + if err != nil { + log.Fatalf("FATAL: listen (%s) failed - %s", n.tcpAddr.String(), err) + } + n.tcpListener = tcpListener + n.tcpAddr = tcpListener.Addr().(*net.TCPAddr) + + go n.listen() + + return n +} + +func (n *mockNSQD) listen() { + log.Printf("TCP: listening on %s", n.tcpListener.Addr().String()) + + for { + conn, err := n.tcpListener.Accept() + if err != nil { + break + } + go n.handle(conn) + } + + log.Printf("TCP: closing %s", n.tcpListener.Addr().String()) + close(n.exitChan) +} + +func (n *mockNSQD) handle(conn net.Conn) { + var idx int + + log.Printf("TCP: new client(%s)", conn.RemoteAddr()) + + buf := make([]byte, 4) + _, err := io.ReadFull(conn, buf) + if err != nil { + log.Fatalf("ERROR: failed to read protocol version - %s", err) + } + + readChan := make(chan []byte) + readDoneChan := make(chan int) + scriptTime := time.After(n.script[0].delay) + rdr := bufio.NewReader(conn) + + go func() { + for { + line, err := rdr.ReadBytes('\n') + if err != nil { + return + } + // trim the '\n' + line = line[:len(line)-1] + readChan <- line + <-readDoneChan + } + }() + + var rdyCount int + for idx < len(n.script) { + select { + case line := <-readChan: + log.Printf("mock: %s", line) + n.got = append(n.got, line) + params := bytes.Split(line, []byte(" ")) + switch { + case bytes.Equal(params[0], []byte("IDENTIFY")): + l := make([]byte, 4) + _, err := io.ReadFull(rdr, l) + if err != nil { + log.Printf(err.Error()) + goto exit + } + size := int32(binary.BigEndian.Uint32(l)) + b := make([]byte, size) + _, err = io.ReadFull(rdr, b) + if err != nil { + log.Printf(err.Error()) + goto exit + } + log.Printf("%s", b) + case bytes.Equal(params[0], []byte("RDY")): + rdy, _ := strconv.Atoi(string(params[1])) + rdyCount = rdy + case bytes.Equal(params[0], []byte("FIN")): + case bytes.Equal(params[0], []byte("REQ")): + } + readDoneChan <- 1 + case <-scriptTime: + inst := n.script[idx] + if bytes.Equal(inst.body, []byte("exit")) { + goto exit + } + if inst.frameType == FrameTypeMessage { + if rdyCount == 0 { + log.Printf("!!! RDY == 0") + scriptTime = time.After(n.script[idx+1].delay) + continue + } + rdyCount-- + } + _, err := conn.Write(framedResponse(inst.frameType, inst.body)) + if err != nil { + log.Printf(err.Error()) + goto exit + } + scriptTime = time.After(n.script[idx+1].delay) + idx++ + } + } + +exit: + n.tcpListener.Close() +} + +func framedResponse(frameType int32, data []byte) []byte { + var w bytes.Buffer + + beBuf := make([]byte, 4) + size := uint32(len(data)) + 4 + + binary.BigEndian.PutUint32(beBuf, size) + _, err := w.Write(beBuf) + if err != nil { + return nil + } + + binary.BigEndian.PutUint32(beBuf, uint32(frameType)) + _, err = w.Write(beBuf) + if err != nil { + return nil + } + + _, err = w.Write(data) + return w.Bytes() +} + +type testHandler struct{} + +func (h *testHandler) HandleMessage(message *Message) error { + if bytes.Equal(message.Body, []byte("bad")) { + return errors.New("bad") + } + return nil +} + +func TestConsumerBackoff(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + logger := log.New(ioutil.Discard, "", log.LstdFlags) + + var mgood bytes.Buffer + msgIDGood := MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + msgGood := NewMessage(msgIDGood, []byte("good")) + msgGood.WriteTo(&mgood) + msgBytesGood := mgood.Bytes() + + var mbad bytes.Buffer + msgIDBad := MessageID{'z', 'x', 'c', 'v', 'b', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + msgBad := NewMessage(msgIDBad, []byte("bad")) + msgBad.WriteTo(&mbad) + msgBytesBad := mbad.Bytes() + + script := []instruction{ + // SUB + instruction{0, FrameTypeResponse, []byte("OK")}, + // IDENTIFY + instruction{0, FrameTypeResponse, []byte("OK")}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesBad}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesBad}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + // needed to exit test + instruction{200 * time.Millisecond, -1, []byte("exit")}, + } + n := newMockNSQD(script) + + topicName := "test_consumer_commands" + strconv.Itoa(int(time.Now().Unix())) + config := NewConfig() + config.MaxInFlight = 5 + config.BackoffMultiplier = 10 * time.Millisecond + q, _ := NewConsumer(topicName, "ch", config) + q.SetLogger(logger, LogLevelDebug) + q.AddHandler(&testHandler{}) + err := q.ConnectToNSQD(n.tcpAddr.String()) + if err != nil { + t.Fatalf(err.Error()) + } + + <-n.exitChan + + for i, r := range n.got { + log.Printf("%d: %s", i, r) + } + + expected := []string{ + "IDENTIFY", + "SUB " + topicName + " ch", + "RDY 5", + fmt.Sprintf("FIN %s", msgIDGood), + fmt.Sprintf("FIN %s", msgIDGood), + fmt.Sprintf("FIN %s", msgIDGood), + "RDY 5", + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDBad), + "RDY 1", + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDBad), + "RDY 1", + "RDY 0", + fmt.Sprintf("FIN %s", msgIDGood), + "RDY 1", + "RDY 5", + fmt.Sprintf("FIN %s", msgIDGood), + } + if len(n.got) != len(expected) { + t.Fatalf("we got %d commands != %d expected", len(n.got), len(expected)) + } + for i, r := range n.got { + if string(r) != expected[i] { + t.Fatalf("cmd %d bad %s != %s", i, r, expected[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go new file mode 100644 index 0000000..be0755d --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go @@ -0,0 +1,326 @@ +package nsq + +import ( + "fmt" + "log" + "os" + "sync" + "sync/atomic" + "time" +) + +// Producer is a high-level type to publish to NSQ. +// +// A Producer instance is 1:1 with a destination `nsqd` +// and will lazily connect to that instance (and re-connect) +// when Publish commands are executed. +type Producer struct { + id int64 + addr string + conn *Conn + config Config + + logger logger + logLvl LogLevel + + responseChan chan []byte + errorChan chan []byte + closeChan chan int + + transactionChan chan *ProducerTransaction + transactions []*ProducerTransaction + state int32 + + concurrentProducers int32 + stopFlag int32 + exitChan chan int + wg sync.WaitGroup + guard sync.Mutex +} + +// ProducerTransaction is returned by the async publish methods +// to retrieve metadata about the command after the +// response is received. +type ProducerTransaction struct { + cmd *Command + doneChan chan *ProducerTransaction + Error error // the error (or nil) of the publish command + Args []interface{} // the slice of variadic arguments passed to PublishAsync or MultiPublishAsync +} + +func (t *ProducerTransaction) finish() { + if t.doneChan != nil { + t.doneChan <- t + } +} + +// NewProducer returns an instance of Producer for the specified address +// +// The only valid way to create a Config is via NewConfig, using a struct literal will panic. +// After Config is passed into NewProducer the values are no longer mutable (they are copied). +func NewProducer(addr string, config *Config) (*Producer, error) { + config.assertInitialized() + err := config.Validate() + if err != nil { + return nil, err + } + + p := &Producer{ + id: atomic.AddInt64(&instCount, 1), + + addr: addr, + config: *config, + + logger: log.New(os.Stderr, "", log.Flags()), + logLvl: LogLevelInfo, + + transactionChan: make(chan *ProducerTransaction), + exitChan: make(chan int), + responseChan: make(chan []byte), + errorChan: make(chan []byte), + closeChan: make(chan int), + } + return p, nil +} + +// SetLogger assigns the logger to use as well as a level +// +// The logger parameter is an interface that requires the following +// method to be implemented (such as the the stdlib log.Logger): +// +// Output(calldepth int, s string) +// +func (w *Producer) SetLogger(l logger, lvl LogLevel) { + w.logger = l + w.logLvl = lvl +} + +// String returns the address of the Producer +func (w *Producer) String() string { + return w.addr +} + +// Stop initiates a graceful stop of the Producer (permanent) +// +// NOTE: this blocks until completion +func (w *Producer) Stop() { + w.guard.Lock() + if !atomic.CompareAndSwapInt32(&w.stopFlag, 0, 1) { + w.guard.Unlock() + return + } + w.log(LogLevelInfo, "stopping") + close(w.exitChan) + w.close() + w.guard.Unlock() + w.wg.Wait() +} + +// PublishAsync publishes a message body to the specified topic +// but does not wait for the response from `nsqd`. +// +// When the Producer eventually receives the response from `nsqd`, +// the supplied `doneChan` (if specified) +// will receive a `ProducerTransaction` instance with the supplied variadic arguments +// and the response error if present +func (w *Producer) PublishAsync(topic string, body []byte, doneChan chan *ProducerTransaction, + args ...interface{}) error { + return w.sendCommandAsync(Publish(topic, body), doneChan, args) +} + +// MultiPublishAsync publishes a slice of message bodies to the specified topic +// but does not wait for the response from `nsqd`. +// +// When the Producer eventually receives the response from `nsqd`, +// the supplied `doneChan` (if specified) +// will receive a `ProducerTransaction` instance with the supplied variadic arguments +// and the response error if present +func (w *Producer) MultiPublishAsync(topic string, body [][]byte, doneChan chan *ProducerTransaction, + args ...interface{}) error { + cmd, err := MultiPublish(topic, body) + if err != nil { + return err + } + return w.sendCommandAsync(cmd, doneChan, args) +} + +// Publish synchronously publishes a message body to the specified topic, returning +// the an error if publish failed +func (w *Producer) Publish(topic string, body []byte) error { + return w.sendCommand(Publish(topic, body)) +} + +// MultiPublish synchronously publishes a slice of message bodies to the specified topic, returning +// the an error if publish failed +func (w *Producer) MultiPublish(topic string, body [][]byte) error { + cmd, err := MultiPublish(topic, body) + if err != nil { + return err + } + return w.sendCommand(cmd) +} + +func (w *Producer) sendCommand(cmd *Command) error { + doneChan := make(chan *ProducerTransaction) + err := w.sendCommandAsync(cmd, doneChan, nil) + if err != nil { + close(doneChan) + return err + } + t := <-doneChan + return t.Error +} + +func (w *Producer) sendCommandAsync(cmd *Command, doneChan chan *ProducerTransaction, + args []interface{}) error { + // keep track of how many outstanding producers we're dealing with + // in order to later ensure that we clean them all up... + atomic.AddInt32(&w.concurrentProducers, 1) + defer atomic.AddInt32(&w.concurrentProducers, -1) + + if atomic.LoadInt32(&w.state) != StateConnected { + err := w.connect() + if err != nil { + return err + } + } + + t := &ProducerTransaction{ + cmd: cmd, + doneChan: doneChan, + Args: args, + } + + select { + case w.transactionChan <- t: + case <-w.exitChan: + return ErrStopped + } + + return nil +} + +func (w *Producer) connect() error { + w.guard.Lock() + defer w.guard.Unlock() + + if atomic.LoadInt32(&w.stopFlag) == 1 { + return ErrStopped + } + + if !atomic.CompareAndSwapInt32(&w.state, StateInit, StateConnected) { + return ErrNotConnected + } + + w.log(LogLevelInfo, "(%s) connecting to nsqd", w.addr) + + w.conn = NewConn(w.addr, &w.config, &producerConnDelegate{w}) + w.conn.SetLogger(w.logger, w.logLvl, fmt.Sprintf("%3d (%%s)", w.id)) + _, err := w.conn.Connect() + if err != nil { + w.conn.Close() + w.log(LogLevelError, "(%s) error connecting to nsqd - %s", w.addr, err) + atomic.StoreInt32(&w.state, StateInit) + return err + } + + w.wg.Add(1) + go w.router() + + return nil +} + +func (w *Producer) close() { + if !atomic.CompareAndSwapInt32(&w.state, StateConnected, StateDisconnected) { + return + } + w.conn.Close() + go func() { + // we need to handle this in a goroutine so we don't + // block the caller from making progress + w.wg.Wait() + atomic.StoreInt32(&w.state, StateInit) + }() +} + +func (w *Producer) router() { + for { + select { + case t := <-w.transactionChan: + w.transactions = append(w.transactions, t) + err := w.conn.WriteCommand(t.cmd) + if err != nil { + w.log(LogLevelError, "(%s) sending command - %s", w.conn.String(), err) + w.close() + } + case data := <-w.responseChan: + w.popTransaction(FrameTypeResponse, data) + case data := <-w.errorChan: + w.popTransaction(FrameTypeError, data) + case <-w.closeChan: + goto exit + case <-w.exitChan: + goto exit + } + } + +exit: + w.transactionCleanup() + w.wg.Done() + w.log(LogLevelInfo, "exiting router") +} + +func (w *Producer) popTransaction(frameType int32, data []byte) { + t := w.transactions[0] + w.transactions = w.transactions[1:] + if frameType == FrameTypeError { + t.Error = ErrProtocol{string(data)} + } + t.finish() +} + +func (w *Producer) transactionCleanup() { + // clean up transactions we can easily account for + for _, t := range w.transactions { + t.Error = ErrNotConnected + t.finish() + } + w.transactions = w.transactions[:0] + + // spin and free up any writes that might have raced + // with the cleanup process (blocked on writing + // to transactionChan) + for { + select { + case t := <-w.transactionChan: + t.Error = ErrNotConnected + t.finish() + default: + // keep spinning until there are 0 concurrent producers + if atomic.LoadInt32(&w.concurrentProducers) == 0 { + return + } + // give the runtime a chance to schedule other racing goroutines + time.Sleep(5 * time.Millisecond) + continue + } + } +} + +func (w *Producer) log(lvl LogLevel, line string, args ...interface{}) { + if w.logger == nil { + return + } + + if w.logLvl > lvl { + return + } + + w.logger.Output(2, fmt.Sprintf("%-4s %3d %s", logPrefix(lvl), w.id, fmt.Sprintf(line, args...))) +} + +func (w *Producer) onConnResponse(c *Conn, data []byte) { w.responseChan <- data } +func (w *Producer) onConnError(c *Conn, data []byte) { w.errorChan <- data } +func (w *Producer) onConnHeartbeat(c *Conn) {} +func (w *Producer) onConnIOError(c *Conn, err error) { w.close() } +func (w *Producer) onConnClose(c *Conn) { w.closeChan <- 1 } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go new file mode 100644 index 0000000..c4733ac --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go @@ -0,0 +1,271 @@ +package nsq + +import ( + "errors" + "io/ioutil" + "log" + "os" + "strconv" + "testing" + "time" +) + +type ConsumerHandler struct { + t *testing.T + q *Consumer + messagesGood int + messagesFailed int +} + +func (h *ConsumerHandler) LogFailedMessage(message *Message) { + h.messagesFailed++ + h.q.Stop() +} + +func (h *ConsumerHandler) HandleMessage(message *Message) error { + msg := string(message.Body) + if msg == "bad_test_case" { + return errors.New("fail this message") + } + if msg != "multipublish_test_case" && msg != "publish_test_case" { + h.t.Error("message 'action' was not correct:", msg) + } + h.messagesGood++ + return nil +} + +func TestProducerConnection(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + + err := w.Publish("write_test", []byte("test")) + if err != nil { + t.Fatalf("should lazily connect") + } + + w.Stop() + + err = w.Publish("write_test", []byte("fail test")) + if err != ErrStopped { + t.Fatalf("should not be able to write after Stop()") + } +} + +func TestProducerPublish(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + topicName := "publish" + strconv.Itoa(int(time.Now().Unix())) + msgCount := 10 + + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + for i := 0; i < msgCount; i++ { + err := w.Publish(topicName, []byte("publish_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + } + + err := w.Publish(topicName, []byte("bad_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + + readMessages(topicName, t, msgCount) +} + +func TestProducerMultiPublish(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + topicName := "multi_publish" + strconv.Itoa(int(time.Now().Unix())) + msgCount := 10 + + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + var testData [][]byte + for i := 0; i < msgCount; i++ { + testData = append(testData, []byte("multipublish_test_case")) + } + + err := w.MultiPublish(topicName, testData) + if err != nil { + t.Fatalf("error %s", err) + } + + err = w.Publish(topicName, []byte("bad_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + + readMessages(topicName, t, msgCount) +} + +func TestProducerPublishAsync(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + topicName := "async_publish" + strconv.Itoa(int(time.Now().Unix())) + msgCount := 10 + + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + responseChan := make(chan *ProducerTransaction, msgCount) + for i := 0; i < msgCount; i++ { + err := w.PublishAsync(topicName, []byte("publish_test_case"), responseChan, "test") + if err != nil { + t.Fatalf(err.Error()) + } + } + + for i := 0; i < msgCount; i++ { + trans := <-responseChan + if trans.Error != nil { + t.Fatalf(trans.Error.Error()) + } + if trans.Args[0].(string) != "test" { + t.Fatalf(`proxied arg "%s" != "test"`, trans.Args[0].(string)) + } + } + + err := w.Publish(topicName, []byte("bad_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + + readMessages(topicName, t, msgCount) +} + +func TestProducerMultiPublishAsync(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + topicName := "multi_publish" + strconv.Itoa(int(time.Now().Unix())) + msgCount := 10 + + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + var testData [][]byte + for i := 0; i < msgCount; i++ { + testData = append(testData, []byte("multipublish_test_case")) + } + + responseChan := make(chan *ProducerTransaction) + err := w.MultiPublishAsync(topicName, testData, responseChan, "test0", 1) + if err != nil { + t.Fatalf(err.Error()) + } + + trans := <-responseChan + if trans.Error != nil { + t.Fatalf(trans.Error.Error()) + } + if trans.Args[0].(string) != "test0" { + t.Fatalf(`proxied arg "%s" != "test0"`, trans.Args[0].(string)) + } + if trans.Args[1].(int) != 1 { + t.Fatalf(`proxied arg %d != 1`, trans.Args[1].(int)) + } + + err = w.Publish(topicName, []byte("bad_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + + readMessages(topicName, t, msgCount) +} + +func TestProducerHeartbeat(t *testing.T) { + log.SetOutput(ioutil.Discard) + defer log.SetOutput(os.Stdout) + + topicName := "heartbeat" + strconv.Itoa(int(time.Now().Unix())) + + config := NewConfig() + config.HeartbeatInterval = 100 * time.Millisecond + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + err := w.Publish(topicName, []byte("publish_test_case")) + if err == nil { + t.Fatalf("error should not be nil") + } + if identifyError, ok := err.(ErrIdentify); !ok || + identifyError.Reason != "E_BAD_BODY IDENTIFY heartbeat interval (100) is invalid" { + t.Fatalf("wrong error - %s", err) + } + + config = NewConfig() + config.HeartbeatInterval = 1000 * time.Millisecond + w, _ = NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + defer w.Stop() + + err = w.Publish(topicName, []byte("publish_test_case")) + if err != nil { + t.Fatalf(err.Error()) + } + + time.Sleep(1100 * time.Millisecond) + + msgCount := 10 + for i := 0; i < msgCount; i++ { + err := w.Publish(topicName, []byte("publish_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + } + + err = w.Publish(topicName, []byte("bad_test_case")) + if err != nil { + t.Fatalf("error %s", err) + } + + readMessages(topicName, t, msgCount+1) +} + +func readMessages(topicName string, t *testing.T, msgCount int) { + config := NewConfig() + config.DefaultRequeueDelay = 0 + config.MaxBackoffDuration = 50 * time.Millisecond + q, _ := NewConsumer(topicName, "ch", config) + q.SetLogger(nullLogger, LogLevelInfo) + + h := &ConsumerHandler{ + t: t, + q: q, + } + q.AddHandler(h) + + err := q.ConnectToNSQD("127.0.0.1:4150") + if err != nil { + t.Fatalf(err.Error()) + } + <-q.StopChan + + if h.messagesGood != msgCount { + t.Fatalf("end of test. should have handled a diff number of messages %d != %d", h.messagesGood, msgCount) + } + + if h.messagesFailed != 1 { + t.Fatal("failed message not done") + } +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/protocol.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/protocol.go new file mode 100644 index 0000000..69b23ab --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/protocol.go @@ -0,0 +1,96 @@ +package nsq + +import ( + "encoding/binary" + "errors" + "io" + "regexp" +) + +// MagicV1 is the initial identifier sent when connecting for V1 clients +var MagicV1 = []byte(" V1") + +// MagicV2 is the initial identifier sent when connecting for V2 clients +var MagicV2 = []byte(" V2") + +// frame types +const ( + FrameTypeResponse int32 = 0 + FrameTypeError int32 = 1 + FrameTypeMessage int32 = 2 +) + +var validTopicChannelNameRegex = regexp.MustCompile(`^[\.a-zA-Z0-9_-]+(#ephemeral)?$`) + +// IsValidTopicName checks a topic name for correctness +func IsValidTopicName(name string) bool { + return isValidName(name) +} + +// IsValidChannelName checks a channel name for correctness +func IsValidChannelName(name string) bool { + return isValidName(name) +} + +func isValidName(name string) bool { + if len(name) > 64 || len(name) < 1 { + return false + } + return validTopicChannelNameRegex.MatchString(name) +} + +// ReadResponse is a client-side utility function to read from the supplied Reader +// according to the NSQ protocol spec: +// +// [x][x][x][x][x][x][x][x]... +// | (int32) || (binary) +// | 4-byte || N-byte +// ------------------------... +// size data +func ReadResponse(r io.Reader) ([]byte, error) { + var msgSize int32 + + // message size + err := binary.Read(r, binary.BigEndian, &msgSize) + if err != nil { + return nil, err + } + + // message binary data + buf := make([]byte, msgSize) + _, err = io.ReadFull(r, buf) + if err != nil { + return nil, err + } + + return buf, nil +} + +// UnpackResponse is a client-side utility function that unpacks serialized data +// according to NSQ protocol spec: +// +// [x][x][x][x][x][x][x][x]... +// | (int32) || (binary) +// | 4-byte || N-byte +// ------------------------... +// frame ID data +// +// Returns a triplicate of: frame type, data ([]byte), error +func UnpackResponse(response []byte) (int32, []byte, error) { + if len(response) < 4 { + return -1, nil, errors.New("length of response is too small") + } + + return int32(binary.BigEndian.Uint32(response)), response[4:], nil +} + +// ReadUnpackedResponse reads and parses data from the underlying +// TCP connection according to the NSQ TCP protocol spec and +// returns the frameType, data or error +func ReadUnpackedResponse(r io.Reader) (int32, []byte, error) { + resp, err := ReadResponse(r) + if err != nil { + return -1, nil, err + } + return UnpackResponse(resp) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/states.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/states.go new file mode 100644 index 0000000..76e8e7b --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/states.go @@ -0,0 +1,12 @@ +package nsq + +// states +const ( + StateInit = iota + StateDisconnected + StateConnected + StateSubscribed + // StateClosing means CLOSE has started... + // (responses are ok, but no new messages will be sent) + StateClosing +) diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh b/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh new file mode 100644 index 0000000..da16138 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +# a helper script to run tests + +if ! which nsqd >/dev/null; then + echo "missing nsqd binary" && exit 1 +fi + +if ! which nsqlookupd >/dev/null; then + echo "missing nsqlookupd binary" && exit 1 +fi + +# run nsqlookupd +LOOKUP_LOGFILE=$(mktemp -t nsqlookupd.XXXXXXX) +echo "starting nsqlookupd" +echo " logging to $LOOKUP_LOGFILE" +nsqlookupd >$LOOKUP_LOGFILE 2>&1 & +LOOKUPD_PID=$! + +# run nsqd configured to use our lookupd above +rm -f *.dat +NSQD_LOGFILE=$(mktemp -t nsqlookupd.XXXXXXX) +EXTRA_ARGS="--tls-root-ca-file=./test/ca.pem" +if [[ $NSQ_DOWNLOAD == nsq-0.2.24* ]] || [[ $NSQ_DOWNLOAD == nsq-0.2.27* ]]; then + EXTRA_ARGS="" +fi +echo "starting nsqd --data-path=/tmp --lookupd-tcp-address=127.0.0.1:4160 --tls-cert=./test/server.pem --tls-key=./test/server.key $EXTRA_ARGS" +echo " logging to $NSQD_LOGFILE" +nsqd --data-path=/tmp --lookupd-tcp-address=127.0.0.1:4160 --tls-cert=./test/server.pem --tls-key=./test/server.key $EXTRA_ARGS >$NSQD_LOGFILE 2>&1 & +NSQD_PID=$! + +sleep 0.3 + +cleanup() { + echo "killing nsqd PID $NSQD_PID" + kill -s TERM $NSQD_PID || cat $NSQD_LOGFILE + echo "killing nsqlookupd PID $LOOKUPD_PID" + kill -s TERM $LOOKUPD_PID || cat $LOOKUP_LOGFILE +} +trap cleanup INT TERM EXIT + +go test -v -timeout 15s diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/test/ca.pem b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/ca.pem new file mode 100644 index 0000000..f5c4723 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID9zCCAt+gAwIBAgIJAPYpAVNDj2lgMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD +VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO +UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t +LmNvbTElMCMGCSqGSIb3DQEJARYWS3J5cHRvS2luZ3NAcmFuZG9tLmNvbTAeFw0x +NDA0MDIyMTE0NTJaFw0xNTA0MDIyMTE0NTJaMIGRMQswCQYDVQQGEwJERTEMMAoG +A1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBh +bnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTElMCMGCSqG +SIb3DQEJARYWS3J5cHRvS2luZ3NAcmFuZG9tLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAL/sJU6ODQCsdWAmq3Qyp6vCqVFkSIHwR3oH8vPuwwob +IOrx/pXz2LIRekQ4egT8LCH3QDxhEvFhDNXYM4h/mkQ+GpgzynoIqYrw+yF93pik +T9Tpel2IuntThlZrO/4APRbVw4Ihf3zp47AY71J+8usJjmfWsId4dhqa1lTYecXK +Zwxii8RTH/7LsuwIDOW1QJLGGKNzvVYA42Gh8Cw3uHlmqZ2tA/sp5qg1Z3QU5g7y +EzzRybotHaRb5XMUWHAlGbIl/TW4KlFqFZ0kCXJXL1uO3uq2nIS3bG7ryjbobRVn +dZ6sV34eenIeZWu6zlDxQP/EqxAezz5Ndyt9uYWb/JECAwEAAaNQME4wHQYDVR0O +BBYEFI9l/QHE30clqx+1oCR6IhUYEdqLMB8GA1UdIwQYMBaAFI9l/QHE30clqx+1 +oCR6IhUYEdqLMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAES6GKxL +oeCLZa83TjJXLagcc9mmdQZgfF3/o61+ye7D9BLqBwN5lx4+kIE1LAUI/Id0mCdW +9uXmPhpCJ926krahNc4Ol+wQTmZ3j7Mn3DCkFufjr64cGPU/UzH4yjMg9wEf84qz +5oH+dBifwJM8yoRCxbnMqGBu3xY8WCjPlw8E8lizXFk8wUbLZ/EC5Rjm+KmdT5ud +KTEgM+K6RMNo9vLn5ZasrYyhVcHdEKIzo6qLm1ZVIgpi/1WX0m8hACMfEcqee6ot +76LEyM3kwfqRkWGZWHEF9D4emp3quU+0AmjM57LHrYjidpDJkVTUHDoMBFHl9Uiq +0O9+azN48F/bVgU= +-----END CERTIFICATE----- diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.key b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.key new file mode 100644 index 0000000..0e3b4e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDFHWaPfRA5nU/F +E8AVoFj2TAgMRISLduWlbAgDnMtFLSGVwgjxU13Txsv0LgwJgo4A5xpd2WNV0dIQ +brerxvPVJruKO8KxKFS2U58BCFIG0xGrlQSg5wDGyqxEQY80XlrBtxs81v79GYHy +fBhizg7onlmbNZzxPy7idU0a7TpgzakeDrfJHQ7rI3llvR0U0TdOLno82CtPvosY +6TYZAIFYgH05yN7DWKuDUI8Fa2RFVkbHPUlJVKROw/0n1yWy7XcwTmQQyaodFYgg +KMCdyR0ElPxLv8dKYFjLvef2DTmuYwbalt5hiQqOpY1wm616Xf4ywz2uEU+ooLW4 +/Q6DcRUBAgMBAAECggEBAKDUgVc4YuGvkmOp3sOGhq/Hj5V63m7wvyV/BPb1mwFB +drK7lBJbxCXEgaslWxrzVCw2ZFQIyL1AKtbPj7tv5ImZRdHfOtbTonL1vbnY8Ryy +YSuPtiwW75JD0dULbO38wq5mWaoFfh5DDr0sNbWAjbeNZG14jCpnNDkAHr6Gq2hJ +VzwEwy+W7LXn8s8lYJHi3MsxCJqAFN1u2FOkjBtrcVW06VgV87IX59SOFns80x4E +Nn0ZKH7RU2DuJ7Fm4HtaNH+yaDYxUeo2A/2/UoavyYYCgC5gThsNjlp9/R4gtm0h +VO+8cN5a3s7zL+aITIusY7H/rwWc6XpRmxQn+jwqF8ECgYEA5PpAz4fhXA9abFZx +0XqCgcwTFY5gTX/JDd1qv0b/PueAR7dY5d37bRbTXpzrHiNFVrq3x432V3+KY0b5 +55PEB1YxwBUga5DvTSa5fLfUibvLpdZjganzdTOsG53wMvNwUT8iUzUQDLkyRfIi +mV0r4Sa34RrBZdWJ2Aou9by2SlkCgYEA3GCHTP7nAcuHXRTsXH3eK/HsfwxdwjhA +G5SG7L7KSoMpzCbe90DuYEr6J/O1nnP0QiSQ2uEeTOARzMfio4E16exWlDDtvPBQ +HqSuQKg4M7fMTN1tj95xmk1yGZMyPxgEfCScBeCbYQzOyZ0j93iFjqMnb2mlriq5 +MoSPat3BeukCgYEAjSGaFNABnUZxS1k0qhLCodHw6VZqERp0B7Gze9X8uP7jWFCv +4G6j66cn/KbnXBoNQNmxMLRVY7TezTYQDiZLquH7pBLheqtIc9ssdKyxuXsgmES9 +7EueHV0N9a+xPxZA4jLxqyuHivATBn2pybFdvFaq+3oMPgISBjCwpRH9oXECgYAN ++n16j8ydW4iZieM4Nq+p/+1tXZ5w3FqMpU4tpCh2s30qOuj3rAGyz+6wLBBAHcDH +lUQu7gqa+7eFUsR4dJCz5s7TFYtu6ZtbZjy7UzBFb4og8gaqEoUIMZNkNecBA4f9 +S+EtqkKQ1Fwlg7ctUlK+anDs6zmcI4+dubTTJX/JSQKBgQCsu/gCgoOi2GFgebIh +URvEMrhaiHxcw5u30nMNjWUGpDQK3lVTK51+7wj4xmVfiomvUW6M/HaR2+5xF1U1 +QV08cKeWCGfGUFetTxjdhsVhMIk84ygF2l9K6jiHqvtd5rIoQ9Lf8XXbYaQVicRg +qmB2iOzmbQQM/GOSofAeUfE7/A== +-----END PRIVATE KEY----- diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.pem b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.pem new file mode 100644 index 0000000..6e88f03 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/test/server.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID7zCCAtegAwIBAgIJAMsErP97ZQmgMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD +VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO +UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t +LmNvbTEhMB8GCSqGSIb3DQEJARYSZm9vYmFyQGV4YW1wbGUuY29tMB4XDTE0MDQw +MjIxMTQ1MloXDTI0MDMzMDIxMTQ1MlowgY0xCzAJBgNVBAYTAkRFMQwwCgYDVQQI +DANOUlcxDjAMBgNVBAcMBUVhcnRoMRcwFQYDVQQKDA5SYW5kb20gQ29tcGFueTEL +MAkGA1UECwwCSVQxFzAVBgNVBAMMDnd3dy5yYW5kb20uY29tMSEwHwYJKoZIhvcN +AQkBFhJmb29iYXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQDFHWaPfRA5nU/FE8AVoFj2TAgMRISLduWlbAgDnMtFLSGVwgjxU13T +xsv0LgwJgo4A5xpd2WNV0dIQbrerxvPVJruKO8KxKFS2U58BCFIG0xGrlQSg5wDG +yqxEQY80XlrBtxs81v79GYHyfBhizg7onlmbNZzxPy7idU0a7TpgzakeDrfJHQ7r +I3llvR0U0TdOLno82CtPvosY6TYZAIFYgH05yN7DWKuDUI8Fa2RFVkbHPUlJVKRO +w/0n1yWy7XcwTmQQyaodFYggKMCdyR0ElPxLv8dKYFjLvef2DTmuYwbalt5hiQqO +pY1wm616Xf4ywz2uEU+ooLW4/Q6DcRUBAgMBAAGjUDBOMB0GA1UdDgQWBBTxyT32 +Exu5TuortZY8zkVotLDNDTAfBgNVHSMEGDAWgBTxyT32Exu5TuortZY8zkVotLDN +DTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAu+0B+caaV4HzIHyfX +Zc6BUPcRoTEZIWX/7JLeeOVUztOjl9ExjYTzjo0QEt+PVcOzfQL/hxE2SPG6fRF7 +YRZU1h9t5Ti9rTg9myAbGGMo6MdWZULFcxIWjxhv6qnFPk/fF47PvGwjygFNnzv8 +FYmrAI99kK0CYolvXZ5ue250dpE/TCIAyk09a3WeBbHU/hMR/mBUNsitphelDbNK +oohrY9D7QR5Mf/NZgx3a0eDH6zoMYDRPARY3M02EuHHiRKmlyfnPv4ns4/0wCarj +pKpds+G80+k2fyiMgQ5bPTw8sfNgq1z0IvIuWB36XSNenTgnnjArbWii+x95jjNw +XcQg +-----END CERTIFICATE----- diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go new file mode 100644 index 0000000..b744228 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go @@ -0,0 +1,8 @@ +// nsq is the official Go package for NSQ (http://nsq.io/) +// +// It provides high-level Consumer and Producer types as well as low-level +// functions to communicate over the NSQ protocol +package nsq + +// VERSION +const VERSION = "1.0.1" diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/.travis.yml b/Godeps/_workspace/src/github.com/bitly/go-simplejson/.travis.yml new file mode 100644 index 0000000..55accb9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.0.3 + - 1.1.2 + - 1.2 + - tip +install: + - go get github.com/bmizerany/assert +notifications: + email: false diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/LICENSE b/Godeps/_workspace/src/github.com/bitly/go-simplejson/LICENSE new file mode 100644 index 0000000..89de354 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/README.md b/Godeps/_workspace/src/github.com/bitly/go-simplejson/README.md new file mode 100644 index 0000000..8c6c101 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/README.md @@ -0,0 +1,13 @@ +### go-simplejson + +a Go package to interact with arbitrary JSON + +[![Build Status](https://secure.travis-ci.org/bitly/go-simplejson.png)](http://travis-ci.org/bitly/go-simplejson) + +### Importing + + import github.com/bitly/go-simplejson + +### Documentation + +Visit the docs on [gopkgdoc](http://godoc.org/github.com/bitly/go-simplejson) diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go new file mode 100644 index 0000000..516fe76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go @@ -0,0 +1,421 @@ +package simplejson + +import ( + "encoding/json" + "errors" + "log" +) + +// returns the current implementation version +func Version() string { + return "0.5.0-alpha" +} + +type Json struct { + data interface{} +} + +// NewJson returns a pointer to a new `Json` object +// after unmarshaling `body` bytes +func NewJson(body []byte) (*Json, error) { + j := new(Json) + err := j.UnmarshalJSON(body) + if err != nil { + return nil, err + } + return j, nil +} + +// New returns a pointer to a new, empty `Json` object +func New() *Json { + return &Json{ + data: make(map[string]interface{}), + } +} + +// Interface returns the underlying data +func (j *Json) Interface() interface{} { + return j.data +} + +// Encode returns its marshaled data as `[]byte` +func (j *Json) Encode() ([]byte, error) { + return j.MarshalJSON() +} + +// EncodePretty returns its marshaled data as `[]byte` with indentation +func (j *Json) EncodePretty() ([]byte, error) { + return json.MarshalIndent(&j.data, "", " ") +} + +// Implements the json.Marshaler interface. +func (j *Json) MarshalJSON() ([]byte, error) { + return json.Marshal(&j.data) +} + +// Set modifies `Json` map by `key` and `value` +// Useful for changing single key/value in a `Json` object easily. +func (j *Json) Set(key string, val interface{}) { + m, err := j.Map() + if err != nil { + return + } + m[key] = val +} + +// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path, +// and then finally writing in the value +func (j *Json) SetPath(branch []string, val interface{}) { + if len(branch) == 0 { + j.data = val + return + } + + // in order to insert our branch, we need map[string]interface{} + if _, ok := (j.data).(map[string]interface{}); !ok { + // have to replace with something suitable + j.data = make(map[string]interface{}) + } + curr := j.data.(map[string]interface{}) + + for i := 0; i < len(branch)-1; i++ { + b := branch[i] + // key exists? + if _, ok := curr[b]; !ok { + n := make(map[string]interface{}) + curr[b] = n + curr = n + continue + } + + // make sure the value is the right sort of thing + if _, ok := curr[b].(map[string]interface{}); !ok { + // have to replace with something suitable + n := make(map[string]interface{}) + curr[b] = n + } + + curr = curr[b].(map[string]interface{}) + } + + // add remaining k/v + curr[branch[len(branch)-1]] = val +} + +// Del modifies `Json` map by deleting `key` if it is present. +func (j *Json) Del(key string) { + m, err := j.Map() + if err != nil { + return + } + delete(m, key) +} + +// Get returns a pointer to a new `Json` object +// for `key` in its `map` representation +// +// useful for chaining operations (to traverse a nested JSON): +// js.Get("top_level").Get("dict").Get("value").Int() +func (j *Json) Get(key string) *Json { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val} + } + } + return &Json{nil} +} + +// GetPath searches for the item as specified by the branch +// without the need to deep dive using Get()'s. +// +// js.GetPath("top_level", "dict") +func (j *Json) GetPath(branch ...string) *Json { + jin := j + for _, p := range branch { + jin = jin.Get(p) + } + return jin +} + +// GetIndex returns a pointer to a new `Json` object +// for `index` in its `array` representation +// +// this is the analog to Get when accessing elements of +// a json array instead of a json object: +// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() +func (j *Json) GetIndex(index int) *Json { + a, err := j.Array() + if err == nil { + if len(a) > index { + return &Json{a[index]} + } + } + return &Json{nil} +} + +// CheckGet returns a pointer to a new `Json` object and +// a `bool` identifying success or failure +// +// useful for chained operations when success is important: +// if data, ok := js.Get("top_level").CheckGet("inner"); ok { +// log.Println(data) +// } +func (j *Json) CheckGet(key string) (*Json, bool) { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val}, true + } + } + return nil, false +} + +// Map type asserts to `map` +func (j *Json) Map() (map[string]interface{}, error) { + if m, ok := (j.data).(map[string]interface{}); ok { + return m, nil + } + return nil, errors.New("type assertion to map[string]interface{} failed") +} + +// Array type asserts to an `array` +func (j *Json) Array() ([]interface{}, error) { + if a, ok := (j.data).([]interface{}); ok { + return a, nil + } + return nil, errors.New("type assertion to []interface{} failed") +} + +// Bool type asserts to `bool` +func (j *Json) Bool() (bool, error) { + if s, ok := (j.data).(bool); ok { + return s, nil + } + return false, errors.New("type assertion to bool failed") +} + +// String type asserts to `string` +func (j *Json) String() (string, error) { + if s, ok := (j.data).(string); ok { + return s, nil + } + return "", errors.New("type assertion to string failed") +} + +// Bytes type asserts to `[]byte` +func (j *Json) Bytes() ([]byte, error) { + if s, ok := (j.data).(string); ok { + return []byte(s), nil + } + return nil, errors.New("type assertion to []byte failed") +} + +// StringArray type asserts to an `array` of `string` +func (j *Json) StringArray() ([]string, error) { + arr, err := j.Array() + if err != nil { + return nil, err + } + retArr := make([]string, 0, len(arr)) + for _, a := range arr { + if a == nil { + retArr = append(retArr, "") + continue + } + s, ok := a.(string) + if !ok { + return nil, err + } + retArr = append(retArr, s) + } + return retArr, nil +} + +// MustArray guarantees the return of a `[]interface{}` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, v := range js.Get("results").MustArray() { +// fmt.Println(i, v) +// } +func (j *Json) MustArray(args ...[]interface{}) []interface{} { + var def []interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustArray() received too many arguments %d", len(args)) + } + + a, err := j.Array() + if err == nil { + return a + } + + return def +} + +// MustMap guarantees the return of a `map[string]interface{}` (with optional default) +// +// useful when you want to interate over map values in a succinct manner: +// for k, v := range js.Get("dictionary").MustMap() { +// fmt.Println(k, v) +// } +func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} { + var def map[string]interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustMap() received too many arguments %d", len(args)) + } + + a, err := j.Map() + if err == nil { + return a + } + + return def +} + +// MustString guarantees the return of a `string` (with optional default) +// +// useful when you explicitly want a `string` in a single value return context: +// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default")) +func (j *Json) MustString(args ...string) string { + var def string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustString() received too many arguments %d", len(args)) + } + + s, err := j.String() + if err == nil { + return s + } + + return def +} + +// MustInt guarantees the return of an `int` (with optional default) +// +// useful when you explicitly want an `int` in a single value return context: +// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150)) +func (j *Json) MustInt(args ...int) int { + var def int + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt() received too many arguments %d", len(args)) + } + + i, err := j.Int() + if err == nil { + return i + } + + return def +} + +// MustFloat64 guarantees the return of a `float64` (with optional default) +// +// useful when you explicitly want a `float64` in a single value return context: +// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150)) +func (j *Json) MustFloat64(args ...float64) float64 { + var def float64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustFloat64() received too many arguments %d", len(args)) + } + + f, err := j.Float64() + if err == nil { + return f + } + + return def +} + +// MustBool guarantees the return of a `bool` (with optional default) +// +// useful when you explicitly want a `bool` in a single value return context: +// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true)) +func (j *Json) MustBool(args ...bool) bool { + var def bool + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustBool() received too many arguments %d", len(args)) + } + + b, err := j.Bool() + if err == nil { + return b + } + + return def +} + +// MustInt64 guarantees the return of an `int64` (with optional default) +// +// useful when you explicitly want an `int64` in a single value return context: +// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150)) +func (j *Json) MustInt64(args ...int64) int64 { + var def int64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt64() received too many arguments %d", len(args)) + } + + i, err := j.Int64() + if err == nil { + return i + } + + return def +} + +// MustUInt64 guarantees the return of an `uint64` (with optional default) +// +// useful when you explicitly want an `uint64` in a single value return context: +// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150)) +func (j *Json) MustUint64(args ...uint64) uint64 { + var def uint64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustUint64() received too many arguments %d", len(args)) + } + + i, err := j.Uint64() + if err == nil { + return i + } + + return def +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10.go new file mode 100644 index 0000000..c9151e9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10.go @@ -0,0 +1,75 @@ +// +build !go1.1 + +package simplejson + +import ( + "encoding/json" + "errors" + "io" + "reflect" +) + +// NewFromReader returns a *Json by decoding from an io.Reader +func NewFromReader(r io.Reader) (*Json, error) { + j := new(Json) + dec := json.NewDecoder(r) + err := dec.Decode(&j.data) + return j, err +} + +// Implements the json.Unmarshaler interface. +func (j *Json) UnmarshalJSON(p []byte) error { + return json.Unmarshal(p, &j.data) +} + +// Float64 coerces into a float64 +func (j *Json) Float64() (float64, error) { + switch j.data.(type) { + case float32, float64: + return reflect.ValueOf(j.data).Float(), nil + case int, int8, int16, int32, int64: + return float64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int coerces into an int +func (j *Json) Int() (int, error) { + switch j.data.(type) { + case float32, float64: + return int(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return int(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int64 coerces into an int64 +func (j *Json) Int64() (int64, error) { + switch j.data.(type) { + case float32, float64: + return int64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return reflect.ValueOf(j.data).Int(), nil + case uint, uint8, uint16, uint32, uint64: + return int64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Uint64 coerces into an uint64 +func (j *Json) Uint64() (uint64, error) { + switch j.data.(type) { + case float32, float64: + return uint64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return uint64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(j.data).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10_test.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10_test.go new file mode 100644 index 0000000..4aa6a51 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go10_test.go @@ -0,0 +1,86 @@ +// +build !go1.1 + +package simplejson + +import ( + "bytes" + "github.com/bmizerany/assert" + "strconv" + "testing" +) + +func TestNewFromReader(t *testing.T) { + buf := bytes.NewBuffer([]byte(`{ + "test": { + "array": [1, "2", 3], + "arraywithsubs": [ + {"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3} + ], + "bignum": 8000000000 + } + }`)) + js, err := NewFromReader(buf) + + //Standard Test Case + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + arr, _ := js.Get("test").Get("array").Array() + assert.NotEqual(t, nil, arr) + for i, v := range arr { + var iv int + switch v.(type) { + case float64: + iv = int(v.(float64)) + case string: + iv, _ = strconv.Atoi(v.(string)) + } + assert.Equal(t, i+1, iv) + } + + ma := js.Get("test").Get("array").MustArray() + assert.Equal(t, ma, []interface{}{float64(1), "2", float64(3)}) + + mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap() + assert.Equal(t, mm, map[string]interface{}{"subkeyone": float64(1)}) + + assert.Equal(t, js.Get("test").Get("bignum").MustInt64(), int64(8000000000)) +} + +func TestSimplejsonGo10(t *testing.T) { + js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "arraywithsubs": [ + {"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3} + ], + "bignum": 8000000000 + } + }`)) + + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + arr, _ := js.Get("test").Get("array").Array() + assert.NotEqual(t, nil, arr) + for i, v := range arr { + var iv int + switch v.(type) { + case float64: + iv = int(v.(float64)) + case string: + iv, _ = strconv.Atoi(v.(string)) + } + assert.Equal(t, i+1, iv) + } + + ma := js.Get("test").Get("array").MustArray() + assert.Equal(t, ma, []interface{}{float64(1), "2", float64(3)}) + + mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap() + assert.Equal(t, mm, map[string]interface{}{"subkeyone": float64(1)}) + + assert.Equal(t, js.Get("test").Get("bignum").MustInt64(), int64(8000000000)) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11.go new file mode 100644 index 0000000..1c47953 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11.go @@ -0,0 +1,89 @@ +// +build go1.1 + +package simplejson + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "reflect" + "strconv" +) + +// Implements the json.Unmarshaler interface. +func (j *Json) UnmarshalJSON(p []byte) error { + dec := json.NewDecoder(bytes.NewBuffer(p)) + dec.UseNumber() + return dec.Decode(&j.data) +} + +// NewFromReader returns a *Json by decoding from an io.Reader +func NewFromReader(r io.Reader) (*Json, error) { + j := new(Json) + dec := json.NewDecoder(r) + dec.UseNumber() + err := dec.Decode(&j.data) + return j, err +} + +// Float64 coerces into a float64 +func (j *Json) Float64() (float64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Float64() + case float32, float64: + return reflect.ValueOf(j.data).Float(), nil + case int, int8, int16, int32, int64: + return float64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int coerces into an int +func (j *Json) Int() (int, error) { + switch j.data.(type) { + case json.Number: + i, err := j.data.(json.Number).Int64() + return int(i), err + case float32, float64: + return int(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return int(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int64 coerces into an int64 +func (j *Json) Int64() (int64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Int64() + case float32, float64: + return int64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return reflect.ValueOf(j.data).Int(), nil + case uint, uint8, uint16, uint32, uint64: + return int64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Uint64 coerces into an uint64 +func (j *Json) Uint64() (uint64, error) { + switch j.data.(type) { + case json.Number: + return strconv.ParseUint(j.data.(json.Number).String(), 10, 64) + case float32, float64: + return uint64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return uint64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(j.data).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11_test.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11_test.go new file mode 100644 index 0000000..16362ad --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_go11_test.go @@ -0,0 +1,96 @@ +// +build go1.1 + +package simplejson + +import ( + "bytes" + "encoding/json" + "github.com/bmizerany/assert" + "strconv" + "testing" +) + +func TestNewFromReader(t *testing.T) { + //Use New Constructor + buf := bytes.NewBuffer([]byte(`{ + "test": { + "array": [1, "2", 3], + "arraywithsubs": [ + {"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3} + ], + "bignum": 9223372036854775807, + "uint64": 18446744073709551615 + } + }`)) + js, err := NewFromReader(buf) + + //Standard Test Case + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + arr, _ := js.Get("test").Get("array").Array() + assert.NotEqual(t, nil, arr) + for i, v := range arr { + var iv int + switch v.(type) { + case json.Number: + i64, err := v.(json.Number).Int64() + assert.Equal(t, nil, err) + iv = int(i64) + case string: + iv, _ = strconv.Atoi(v.(string)) + } + assert.Equal(t, i+1, iv) + } + + ma := js.Get("test").Get("array").MustArray() + assert.Equal(t, ma, []interface{}{json.Number("1"), "2", json.Number("3")}) + + mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap() + assert.Equal(t, mm, map[string]interface{}{"subkeyone": json.Number("1")}) + + assert.Equal(t, js.Get("test").Get("bignum").MustInt64(), int64(9223372036854775807)) + assert.Equal(t, js.Get("test").Get("uint64").MustUint64(), uint64(18446744073709551615)) +} + +func TestSimplejsonGo11(t *testing.T) { + js, err := NewJson([]byte(`{ + "test": { + "array": [1, "2", 3], + "arraywithsubs": [ + {"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3} + ], + "bignum": 9223372036854775807, + "uint64": 18446744073709551615 + } + }`)) + + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + arr, _ := js.Get("test").Get("array").Array() + assert.NotEqual(t, nil, arr) + for i, v := range arr { + var iv int + switch v.(type) { + case json.Number: + i64, err := v.(json.Number).Int64() + assert.Equal(t, nil, err) + iv = int(i64) + case string: + iv, _ = strconv.Atoi(v.(string)) + } + assert.Equal(t, i+1, iv) + } + + ma := js.Get("test").Get("array").MustArray() + assert.Equal(t, ma, []interface{}{json.Number("1"), "2", json.Number("3")}) + + mm := js.Get("test").Get("arraywithsubs").GetIndex(0).MustMap() + assert.Equal(t, mm, map[string]interface{}{"subkeyone": json.Number("1")}) + + assert.Equal(t, js.Get("test").Get("bignum").MustInt64(), int64(9223372036854775807)) + assert.Equal(t, js.Get("test").Get("uint64").MustUint64(), uint64(18446744073709551615)) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go new file mode 100644 index 0000000..5a11156 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go @@ -0,0 +1,235 @@ +package simplejson + +import ( + "encoding/json" + "testing" + + "github.com/bmizerany/assert" +) + +func TestSimplejson(t *testing.T) { + var ok bool + var err error + + js, err := NewJson([]byte(`{ + "test": { + "string_array": ["asdf", "ghjk", "zxcv"], + "string_array_null": ["abc", null, "efg"], + "array": [1, "2", 3], + "arraywithsubs": [{"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3}], + "int": 10, + "float": 5.150, + "string": "simplejson", + "bool": true, + "sub_obj": {"a": 1} + } + }`)) + + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + _, ok = js.CheckGet("test") + assert.Equal(t, true, ok) + + _, ok = js.CheckGet("missing_key") + assert.Equal(t, false, ok) + + aws := js.Get("test").Get("arraywithsubs") + assert.NotEqual(t, nil, aws) + var awsval int + awsval, _ = aws.GetIndex(0).Get("subkeyone").Int() + assert.Equal(t, 1, awsval) + awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int() + assert.Equal(t, 2, awsval) + awsval, _ = aws.GetIndex(1).Get("subkeythree").Int() + assert.Equal(t, 3, awsval) + + i, _ := js.Get("test").Get("int").Int() + assert.Equal(t, 10, i) + + f, _ := js.Get("test").Get("float").Float64() + assert.Equal(t, 5.150, f) + + s, _ := js.Get("test").Get("string").String() + assert.Equal(t, "simplejson", s) + + b, _ := js.Get("test").Get("bool").Bool() + assert.Equal(t, true, b) + + mi := js.Get("test").Get("int").MustInt() + assert.Equal(t, 10, mi) + + mi2 := js.Get("test").Get("missing_int").MustInt(5150) + assert.Equal(t, 5150, mi2) + + ms := js.Get("test").Get("string").MustString() + assert.Equal(t, "simplejson", ms) + + ms2 := js.Get("test").Get("missing_string").MustString("fyea") + assert.Equal(t, "fyea", ms2) + + ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"}) + assert.Equal(t, ma2, []interface{}{"1", 2, "3"}) + + mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false}) + assert.Equal(t, mm2, map[string]interface{}{"found": false}) + + strs, err := js.Get("test").Get("string_array").StringArray() + assert.Equal(t, err, nil) + assert.Equal(t, strs[0], "asdf") + assert.Equal(t, strs[1], "ghjk") + assert.Equal(t, strs[2], "zxcv") + + strs2, err := js.Get("test").Get("string_array_null").StringArray() + assert.Equal(t, err, nil) + assert.Equal(t, strs2[0], "abc") + assert.Equal(t, strs2[1], "") + assert.Equal(t, strs2[2], "efg") + + gp, _ := js.GetPath("test", "string").String() + assert.Equal(t, "simplejson", gp) + + gp2, _ := js.GetPath("test", "int").Int() + assert.Equal(t, 10, gp2) + + assert.Equal(t, js.Get("test").Get("bool").MustBool(), true) + + js.Set("float2", 300.0) + assert.Equal(t, js.Get("float2").MustFloat64(), 300.0) + + js.Set("test2", "setTest") + assert.Equal(t, "setTest", js.Get("test2").MustString()) + + js.Del("test2") + assert.NotEqual(t, "setTest", js.Get("test2").MustString()) + + js.Get("test").Get("sub_obj").Set("a", 2) + assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt()) + + js.GetPath("test", "sub_obj").Set("a", 3) + assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt()) +} + +func TestStdlibInterfaces(t *testing.T) { + val := new(struct { + Name string `json:"name"` + Params *Json `json:"params"` + }) + val2 := new(struct { + Name string `json:"name"` + Params *Json `json:"params"` + }) + + raw := `{"name":"myobject","params":{"string":"simplejson"}}` + + assert.Equal(t, nil, json.Unmarshal([]byte(raw), val)) + + assert.Equal(t, "myobject", val.Name) + assert.NotEqual(t, nil, val.Params.data) + s, _ := val.Params.Get("string").String() + assert.Equal(t, "simplejson", s) + + p, err := json.Marshal(val) + assert.Equal(t, nil, err) + assert.Equal(t, nil, json.Unmarshal(p, val2)) + assert.Equal(t, val, val2) // stable +} + +func TestSet(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + js.Set("baz", "bing") + + s, err := js.GetPath("baz").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bing", s) +} + +func TestReplace(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`)) + assert.Equal(t, nil, err) + + s, err := js.GetPath("baz").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bing", s) +} + +func TestSetPath(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"foo", "bar"}, "baz") + + s, err := js.GetPath("foo", "bar").String() + assert.Equal(t, nil, err) + assert.Equal(t, "baz", s) +} + +func TestSetPathNoPath(t *testing.T) { + js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`)) + assert.Equal(t, nil, err) + + f := js.GetPath("some_number").MustFloat64(99.0) + assert.Equal(t, f, 1.0) + + js.SetPath([]string{}, map[string]interface{}{"foo": "bar"}) + + s, err := js.GetPath("foo").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bar", s) + + f = js.GetPath("some_number").MustFloat64(99.0) + assert.Equal(t, f, 99.0) +} + +func TestPathWillAugmentExisting(t *testing.T) { + js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"this", "d"}, "dd") + + cases := []struct { + path []string + outcome string + }{ + { + path: []string{"this", "a"}, + outcome: "aa", + }, + { + path: []string{"this", "b"}, + outcome: "bb", + }, + { + path: []string{"this", "c"}, + outcome: "cc", + }, + { + path: []string{"this", "d"}, + outcome: "dd", + }, + } + + for _, tc := range cases { + s, err := js.GetPath(tc.path...).String() + assert.Equal(t, nil, err) + assert.Equal(t, tc.outcome, s) + } +} + +func TestPathWillOverwriteExisting(t *testing.T) { + // notice how "a" is 0.1 - but then we'll try to set at path a, foo + js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"this", "a", "foo"}, "bar") + + s, err := js.GetPath("this", "a", "foo").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bar", s) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go new file mode 100644 index 0000000..7122be0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child.go @@ -0,0 +1,23 @@ +package etcd + +// Add a new directory with a random etcd-generated key under the given path. +func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) { + raw, err := c.post(key, "", ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// Add a new file with a random etcd-generated key under the given path. +func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) { + raw, err := c.post(key, value, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go new file mode 100644 index 0000000..26223ff --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/add_child_test.go @@ -0,0 +1,73 @@ +package etcd + +import "testing" + +func TestAddChild(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("fooDir", true) + c.Delete("nonexistentDir", true) + }() + + c.CreateDir("fooDir", 5) + + _, err := c.AddChild("fooDir", "v0", 5) + if err != nil { + t.Fatal(err) + } + + _, err = c.AddChild("fooDir", "v1", 5) + if err != nil { + t.Fatal(err) + } + + resp, err := c.Get("fooDir", true, false) + // The child with v0 should proceed the child with v1 because it's added + // earlier, so it should have a lower key. + if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) { + t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ + " The response was: %#v", resp) + } + + // Creating a child under a nonexistent directory should succeed. + // The directory should be created. + resp, err = c.AddChild("nonexistentDir", "foo", 5) + if err != nil { + t.Fatal(err) + } +} + +func TestAddChildDir(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("fooDir", true) + c.Delete("nonexistentDir", true) + }() + + c.CreateDir("fooDir", 5) + + _, err := c.AddChildDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + + _, err = c.AddChildDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + + resp, err := c.Get("fooDir", true, false) + // The child with v0 should proceed the child with v1 because it's added + // earlier, so it should have a lower key. + if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) { + t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+ + " The response was: %#v", resp) + } + + // Creating a child under a nonexistent directory should succeed. + // The directory should be created. + resp, err = c.AddChildDir("nonexistentDir", 5) + if err != nil { + t.Fatal(err) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go new file mode 100644 index 0000000..f6ae548 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go @@ -0,0 +1,435 @@ +package etcd + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "path" + "time" +) + +// See SetConsistency for how to use these constants. +const ( + // Using strings rather than iota because the consistency level + // could be persisted to disk, so it'd be better to use + // human-readable values. + STRONG_CONSISTENCY = "STRONG" + WEAK_CONSISTENCY = "WEAK" +) + +const ( + defaultBufferSize = 10 +) + +type Config struct { + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + CaCertFile []string `json:"caCertFiles"` + DialTimeout time.Duration `json:"timeout"` + Consistency string `json:"consistency"` +} + +type Client struct { + config Config `json:"config"` + cluster *Cluster `json:"cluster"` + httpClient *http.Client + persistence io.Writer + cURLch chan string + // CheckRetry can be used to control the policy for failed requests + // and modify the cluster if needed. + // The client calls it before sending requests again, and + // stops retrying if CheckRetry returns some error. The cases that + // this function needs to handle include no response and unexpected + // http status code of response. + // If CheckRetry is nil, client will call the default one + // `DefaultCheckRetry`. + // Argument cluster is the etcd.Cluster object that these requests have been made on. + // Argument numReqs is the number of http.Requests that have been made so far. + // Argument lastResp is the http.Responses from the last request. + // Argument err is the reason of the failure. + CheckRetry func(cluster *Cluster, numReqs int, + lastResp http.Response, err error) error +} + +// NewClient create a basic client that is configured to be used +// with the given machine list. +func NewClient(machines []string) *Client { + config := Config{ + // default timeout is one second + DialTimeout: time.Second, + // default consistency level is STRONG + Consistency: STRONG_CONSISTENCY, + } + + client := &Client{ + cluster: NewCluster(machines), + config: config, + } + + client.initHTTPClient() + client.saveConfig() + + return client +} + +// NewTLSClient create a basic client with TLS configuration +func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) { + // overwrite the default machine to use https + if len(machines) == 0 { + machines = []string{"https://127.0.0.1:4001"} + } + + config := Config{ + // default timeout is one second + DialTimeout: time.Second, + // default consistency level is STRONG + Consistency: STRONG_CONSISTENCY, + CertFile: cert, + KeyFile: key, + CaCertFile: make([]string, 0), + } + + client := &Client{ + cluster: NewCluster(machines), + config: config, + } + + err := client.initHTTPSClient(cert, key) + if err != nil { + return nil, err + } + + err = client.AddRootCA(caCert) + + client.saveConfig() + + return client, nil +} + +// NewClientFromFile creates a client from a given file path. +// The given file is expected to use the JSON format. +func NewClientFromFile(fpath string) (*Client, error) { + fi, err := os.Open(fpath) + if err != nil { + return nil, err + } + + defer func() { + if err := fi.Close(); err != nil { + panic(err) + } + }() + + return NewClientFromReader(fi) +} + +// NewClientFromReader creates a Client configured from a given reader. +// The configuration is expected to use the JSON format. +func NewClientFromReader(reader io.Reader) (*Client, error) { + c := new(Client) + + b, err := ioutil.ReadAll(reader) + if err != nil { + return nil, err + } + + err = json.Unmarshal(b, c) + if err != nil { + return nil, err + } + if c.config.CertFile == "" { + c.initHTTPClient() + } else { + err = c.initHTTPSClient(c.config.CertFile, c.config.KeyFile) + } + + if err != nil { + return nil, err + } + + for _, caCert := range c.config.CaCertFile { + if err := c.AddRootCA(caCert); err != nil { + return nil, err + } + } + + return c, nil +} + +// Override the Client's HTTP Transport object +func (c *Client) SetTransport(tr *http.Transport) { + c.httpClient.Transport = tr +} + +// initHTTPClient initializes a HTTP client for etcd client +func (c *Client) initHTTPClient() { + tr := &http.Transport{ + Dial: c.dial, + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + c.httpClient = &http.Client{Transport: tr} +} + +// initHTTPClient initializes a HTTPS client for etcd client +func (c *Client) initHTTPSClient(cert, key string) error { + if cert == "" || key == "" { + return errors.New("Require both cert and key path") + } + + tlsCert, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + return err + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + InsecureSkipVerify: true, + } + + tr := &http.Transport{ + TLSClientConfig: tlsConfig, + Dial: c.dial, + } + + c.httpClient = &http.Client{Transport: tr} + return nil +} + +// SetPersistence sets a writer to which the config will be +// written every time it's changed. +func (c *Client) SetPersistence(writer io.Writer) { + c.persistence = writer +} + +// SetConsistency changes the consistency level of the client. +// +// When consistency is set to STRONG_CONSISTENCY, all requests, +// including GET, are sent to the leader. This means that, assuming +// the absence of leader failures, GET requests are guaranteed to see +// the changes made by previous requests. +// +// When consistency is set to WEAK_CONSISTENCY, other requests +// are still sent to the leader, but GET requests are sent to a +// random server from the server pool. This reduces the read +// load on the leader, but it's not guaranteed that the GET requests +// will see changes made by previous requests (they might have not +// yet been committed on non-leader servers). +func (c *Client) SetConsistency(consistency string) error { + if !(consistency == STRONG_CONSISTENCY || consistency == WEAK_CONSISTENCY) { + return errors.New("The argument must be either STRONG_CONSISTENCY or WEAK_CONSISTENCY.") + } + c.config.Consistency = consistency + return nil +} + +// Sets the DialTimeout value +func (c *Client) SetDialTimeout(d time.Duration) { + c.config.DialTimeout = d +} + +// AddRootCA adds a root CA cert for the etcd client +func (c *Client) AddRootCA(caCert string) error { + if c.httpClient == nil { + return errors.New("Client has not been initialized yet!") + } + + certBytes, err := ioutil.ReadFile(caCert) + if err != nil { + return err + } + + tr, ok := c.httpClient.Transport.(*http.Transport) + + if !ok { + panic("AddRootCA(): Transport type assert should not fail") + } + + if tr.TLSClientConfig.RootCAs == nil { + caCertPool := x509.NewCertPool() + ok = caCertPool.AppendCertsFromPEM(certBytes) + if ok { + tr.TLSClientConfig.RootCAs = caCertPool + } + tr.TLSClientConfig.InsecureSkipVerify = false + } else { + ok = tr.TLSClientConfig.RootCAs.AppendCertsFromPEM(certBytes) + } + + if !ok { + err = errors.New("Unable to load caCert") + } + + c.config.CaCertFile = append(c.config.CaCertFile, caCert) + c.saveConfig() + + return err +} + +// SetCluster updates cluster information using the given machine list. +func (c *Client) SetCluster(machines []string) bool { + success := c.internalSyncCluster(machines) + return success +} + +func (c *Client) GetCluster() []string { + return c.cluster.Machines +} + +// SyncCluster updates the cluster information using the internal machine list. +func (c *Client) SyncCluster() bool { + return c.internalSyncCluster(c.cluster.Machines) +} + +// internalSyncCluster syncs cluster information using the given machine list. +func (c *Client) internalSyncCluster(machines []string) bool { + for _, machine := range machines { + httpPath := c.createHttpPath(machine, path.Join(version, "machines")) + resp, err := c.httpClient.Get(httpPath) + if err != nil { + // try another machine in the cluster + continue + } else { + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + // try another machine in the cluster + continue + } + + // update Machines List + c.cluster.updateFromStr(string(b)) + + // update leader + // the first one in the machine list is the leader + c.cluster.switchLeader(0) + + logger.Debug("sync.machines ", c.cluster.Machines) + c.saveConfig() + return true + } + } + return false +} + +// createHttpPath creates a complete HTTP URL. +// serverName should contain both the host name and a port number, if any. +func (c *Client) createHttpPath(serverName string, _path string) string { + u, err := url.Parse(serverName) + if err != nil { + panic(err) + } + + u.Path = path.Join(u.Path, _path) + + if u.Scheme == "" { + u.Scheme = "http" + } + return u.String() +} + +// dial attempts to open a TCP connection to the provided address, explicitly +// enabling keep-alives with a one-second interval. +func (c *Client) dial(network, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(network, addr, c.config.DialTimeout) + if err != nil { + return nil, err + } + + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return nil, errors.New("Failed type-assertion of net.Conn as *net.TCPConn") + } + + // Keep TCP alive to check whether or not the remote machine is down + if err = tcpConn.SetKeepAlive(true); err != nil { + return nil, err + } + + if err = tcpConn.SetKeepAlivePeriod(time.Second); err != nil { + return nil, err + } + + return tcpConn, nil +} + +func (c *Client) OpenCURL() { + c.cURLch = make(chan string, defaultBufferSize) +} + +func (c *Client) CloseCURL() { + c.cURLch = nil +} + +func (c *Client) sendCURL(command string) { + go func() { + select { + case c.cURLch <- command: + default: + } + }() +} + +func (c *Client) RecvCURL() string { + return <-c.cURLch +} + +// saveConfig saves the current config using c.persistence. +func (c *Client) saveConfig() error { + if c.persistence != nil { + b, err := json.Marshal(c) + if err != nil { + return err + } + + _, err = c.persistence.Write(b) + if err != nil { + return err + } + } + + return nil +} + +// MarshalJSON implements the Marshaller interface +// as defined by the standard JSON package. +func (c *Client) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(struct { + Config Config `json:"config"` + Cluster *Cluster `json:"cluster"` + }{ + Config: c.config, + Cluster: c.cluster, + }) + + if err != nil { + return nil, err + } + + return b, nil +} + +// UnmarshalJSON implements the Unmarshaller interface +// as defined by the standard JSON package. +func (c *Client) UnmarshalJSON(b []byte) error { + temp := struct { + Config Config `json:"config"` + Cluster *Cluster `json:"cluster"` + }{} + err := json.Unmarshal(b, &temp) + if err != nil { + return err + } + + c.cluster = temp.Cluster + c.config = temp.Config + return nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go new file mode 100644 index 0000000..c245e47 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go @@ -0,0 +1,96 @@ +package etcd + +import ( + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "testing" +) + +// To pass this test, we need to create a cluster of 3 machines +// The server should be listening on 127.0.0.1:4001, 4002, 4003 +func TestSync(t *testing.T) { + fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003") + + // Explicit trailing slash to ensure this doesn't reproduce: + // https://github.com/coreos/go-etcd/issues/82 + c := NewClient([]string{"http://127.0.0.1:4001/"}) + + success := c.SyncCluster() + if !success { + t.Fatal("cannot sync machines") + } + + for _, m := range c.GetCluster() { + u, err := url.Parse(m) + if err != nil { + t.Fatal(err) + } + if u.Scheme != "http" { + t.Fatal("scheme must be http") + } + + host, _, err := net.SplitHostPort(u.Host) + if err != nil { + t.Fatal(err) + } + if host != "127.0.0.1" { + t.Fatal("Host must be 127.0.0.1") + } + } + + badMachines := []string{"abc", "edef"} + + success = c.SetCluster(badMachines) + + if success { + t.Fatal("should not sync on bad machines") + } + + goodMachines := []string{"127.0.0.1:4002"} + + success = c.SetCluster(goodMachines) + + if !success { + t.Fatal("cannot sync machines") + } else { + fmt.Println(c.cluster.Machines) + } + +} + +func TestPersistence(t *testing.T) { + c := NewClient(nil) + c.SyncCluster() + + fo, err := os.Create("config.json") + if err != nil { + t.Fatal(err) + } + defer func() { + if err := fo.Close(); err != nil { + panic(err) + } + }() + + c.SetPersistence(fo) + err = c.saveConfig() + if err != nil { + t.Fatal(err) + } + + c2, err := NewClientFromFile("config.json") + if err != nil { + t.Fatal(err) + } + + // Verify that the two clients have the same config + b1, _ := json.Marshal(c) + b2, _ := json.Marshal(c2) + + if string(b1) != string(b2) { + t.Fatalf("The two configs should be equal!") + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go new file mode 100644 index 0000000..aaa2054 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go @@ -0,0 +1,51 @@ +package etcd + +import ( + "net/url" + "strings" +) + +type Cluster struct { + Leader string `json:"leader"` + Machines []string `json:"machines"` +} + +func NewCluster(machines []string) *Cluster { + // if an empty slice was sent in then just assume HTTP 4001 on localhost + if len(machines) == 0 { + machines = []string{"http://127.0.0.1:4001"} + } + + // default leader and machines + return &Cluster{ + Leader: machines[0], + Machines: machines, + } +} + +// switchLeader switch the current leader to machines[num] +func (cl *Cluster) switchLeader(num int) { + logger.Debugf("switch.leader[from %v to %v]", + cl.Leader, cl.Machines[num]) + + cl.Leader = cl.Machines[num] +} + +func (cl *Cluster) updateFromStr(machines string) { + cl.Machines = strings.Split(machines, ", ") +} + +func (cl *Cluster) updateLeader(leader string) { + logger.Debugf("update.leader[%s,%s]", cl.Leader, leader) + cl.Leader = leader +} + +func (cl *Cluster) updateLeaderFromURL(u *url.URL) { + var leader string + if u.Scheme == "" { + leader = "http://" + u.Host + } else { + leader = u.Scheme + "://" + u.Host + } + cl.updateLeader(leader) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go new file mode 100644 index 0000000..11131bb --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete.go @@ -0,0 +1,34 @@ +package etcd + +import "fmt" + +func (c *Client) CompareAndDelete(key string, prevValue string, prevIndex uint64) (*Response, error) { + raw, err := c.RawCompareAndDelete(key, prevValue, prevIndex) + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +func (c *Client) RawCompareAndDelete(key string, prevValue string, prevIndex uint64) (*RawResponse, error) { + if prevValue == "" && prevIndex == 0 { + return nil, fmt.Errorf("You must give either prevValue or prevIndex.") + } + + options := Options{} + if prevValue != "" { + options["prevValue"] = prevValue + } + if prevIndex != 0 { + options["prevIndex"] = prevIndex + } + + raw, err := c.delete(key, options) + + if err != nil { + return nil, err + } + + return raw, err +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go new file mode 100644 index 0000000..223e50f --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go @@ -0,0 +1,46 @@ +package etcd + +import ( + "testing" +) + +func TestCompareAndDelete(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + }() + + c.Set("foo", "bar", 5) + + // This should succeed an correct prevValue + resp, err := c.CompareAndDelete("foo", "bar", 0) + if err != nil { + t.Fatal(err) + } + if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { + t.Fatalf("CompareAndDelete 1 prevNode failed: %#v", resp) + } + + resp, _ = c.Set("foo", "bar", 5) + // This should fail because it gives an incorrect prevValue + _, err = c.CompareAndDelete("foo", "xxx", 0) + if err == nil { + t.Fatalf("CompareAndDelete 2 should have failed. The response is: %#v", resp) + } + + // This should succeed because it gives an correct prevIndex + resp, err = c.CompareAndDelete("foo", "", resp.Node.ModifiedIndex) + if err != nil { + t.Fatal(err) + } + if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { + t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) + } + + c.Set("foo", "bar", 5) + // This should fail because it gives an incorrect prevIndex + resp, err = c.CompareAndDelete("foo", "", 29817514) + if err == nil { + t.Fatalf("CompareAndDelete 4 should have failed. The response is: %#v", resp) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go new file mode 100644 index 0000000..bb4f906 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap.go @@ -0,0 +1,36 @@ +package etcd + +import "fmt" + +func (c *Client) CompareAndSwap(key string, value string, ttl uint64, + prevValue string, prevIndex uint64) (*Response, error) { + raw, err := c.RawCompareAndSwap(key, value, ttl, prevValue, prevIndex) + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +func (c *Client) RawCompareAndSwap(key string, value string, ttl uint64, + prevValue string, prevIndex uint64) (*RawResponse, error) { + if prevValue == "" && prevIndex == 0 { + return nil, fmt.Errorf("You must give either prevValue or prevIndex.") + } + + options := Options{} + if prevValue != "" { + options["prevValue"] = prevValue + } + if prevIndex != 0 { + options["prevIndex"] = prevIndex + } + + raw, err := c.put(key, value, ttl, options) + + if err != nil { + return nil, err + } + + return raw, err +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go new file mode 100644 index 0000000..14a1b00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go @@ -0,0 +1,57 @@ +package etcd + +import ( + "testing" +) + +func TestCompareAndSwap(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + }() + + c.Set("foo", "bar", 5) + + // This should succeed + resp, err := c.CompareAndSwap("foo", "bar2", 5, "bar", 0) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { + t.Fatalf("CompareAndSwap 1 failed: %#v", resp) + } + + if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { + t.Fatalf("CompareAndSwap 1 prevNode failed: %#v", resp) + } + + // This should fail because it gives an incorrect prevValue + resp, err = c.CompareAndSwap("foo", "bar3", 5, "xxx", 0) + if err == nil { + t.Fatalf("CompareAndSwap 2 should have failed. The response is: %#v", resp) + } + + resp, err = c.Set("foo", "bar", 5) + if err != nil { + t.Fatal(err) + } + + // This should succeed + resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { + t.Fatalf("CompareAndSwap 3 failed: %#v", resp) + } + + if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) { + t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp) + } + + // This should fail because it gives an incorrect prevIndex + resp, err = c.CompareAndSwap("foo", "bar3", 5, "", 29817514) + if err == nil { + t.Fatalf("CompareAndSwap 4 should have failed. The response is: %#v", resp) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go new file mode 100644 index 0000000..0f77788 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug.go @@ -0,0 +1,55 @@ +package etcd + +import ( + "fmt" + "io/ioutil" + "log" + "strings" +) + +var logger *etcdLogger + +func SetLogger(l *log.Logger) { + logger = &etcdLogger{l} +} + +func GetLogger() *log.Logger { + return logger.log +} + +type etcdLogger struct { + log *log.Logger +} + +func (p *etcdLogger) Debug(args ...interface{}) { + msg := "DEBUG: " + fmt.Sprint(args...) + p.log.Println(msg) +} + +func (p *etcdLogger) Debugf(f string, args ...interface{}) { + msg := "DEBUG: " + fmt.Sprintf(f, args...) + // Append newline if necessary + if !strings.HasSuffix(msg, "\n") { + msg = msg + "\n" + } + p.log.Print(msg) +} + +func (p *etcdLogger) Warning(args ...interface{}) { + msg := "WARNING: " + fmt.Sprint(args...) + p.log.Println(msg) +} + +func (p *etcdLogger) Warningf(f string, args ...interface{}) { + msg := "WARNING: " + fmt.Sprintf(f, args...) + // Append newline if necessary + if !strings.HasSuffix(msg, "\n") { + msg = msg + "\n" + } + p.log.Print(msg) +} + +func init() { + // Default logger uses the go default log. + SetLogger(log.New(ioutil.Discard, "go-etcd", log.LstdFlags)) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go new file mode 100644 index 0000000..97f6d11 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/debug_test.go @@ -0,0 +1,28 @@ +package etcd + +import ( + "testing" +) + +type Foo struct{} +type Bar struct { + one string + two int +} + +// Tests that logs don't panic with arbitrary interfaces +func TestDebug(t *testing.T) { + f := &Foo{} + b := &Bar{"asfd", 3} + for _, test := range []interface{}{ + 1234, + "asdf", + f, + b, + } { + logger.Debug(test) + logger.Debugf("something, %s", test) + logger.Warning(test) + logger.Warningf("something, %s", test) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go new file mode 100644 index 0000000..b37accd --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete.go @@ -0,0 +1,40 @@ +package etcd + +// Delete deletes the given key. +// +// When recursive set to false, if the key points to a +// directory the method will fail. +// +// When recursive set to true, if the key points to a file, +// the file will be deleted; if the key points to a directory, +// then everything under the directory (including all child directories) +// will be deleted. +func (c *Client) Delete(key string, recursive bool) (*Response, error) { + raw, err := c.RawDelete(key, recursive, false) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// DeleteDir deletes an empty directory or a key value pair +func (c *Client) DeleteDir(key string) (*Response, error) { + raw, err := c.RawDelete(key, false, true) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) { + ops := Options{ + "recursive": recursive, + "dir": dir, + } + + return c.delete(key, ops) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go new file mode 100644 index 0000000..5904971 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/delete_test.go @@ -0,0 +1,81 @@ +package etcd + +import ( + "testing" +) + +func TestDelete(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + }() + + c.Set("foo", "bar", 5) + resp, err := c.Delete("foo", false) + if err != nil { + t.Fatal(err) + } + + if !(resp.Node.Value == "") { + t.Fatalf("Delete failed with %s", resp.Node.Value) + } + + if !(resp.PrevNode.Value == "bar") { + t.Fatalf("Delete PrevNode failed with %s", resp.Node.Value) + } + + resp, err = c.Delete("foo", false) + if err == nil { + t.Fatalf("Delete should have failed because the key foo did not exist. "+ + "The response was: %v", resp) + } +} + +func TestDeleteAll(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + c.Delete("fooDir", true) + }() + + c.SetDir("foo", 5) + // test delete an empty dir + resp, err := c.DeleteDir("foo") + if err != nil { + t.Fatal(err) + } + + if !(resp.Node.Value == "") { + t.Fatalf("DeleteAll 1 failed: %#v", resp) + } + + if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { + t.Fatalf("DeleteAll 1 PrevNode failed: %#v", resp) + } + + c.CreateDir("fooDir", 5) + c.Set("fooDir/foo", "bar", 5) + _, err = c.DeleteDir("fooDir") + if err == nil { + t.Fatal("should not able to delete a non-empty dir with deletedir") + } + + resp, err = c.Delete("fooDir", true) + if err != nil { + t.Fatal(err) + } + + if !(resp.Node.Value == "") { + t.Fatalf("DeleteAll 2 failed: %#v", resp) + } + + if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") { + t.Fatalf("DeleteAll 2 PrevNode failed: %#v", resp) + } + + resp, err = c.Delete("foo", true) + if err == nil { + t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+ + "The response was: %v", resp) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go new file mode 100644 index 0000000..7e69287 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go @@ -0,0 +1,48 @@ +package etcd + +import ( + "encoding/json" + "fmt" +) + +const ( + ErrCodeEtcdNotReachable = 501 +) + +var ( + errorMap = map[int]string{ + ErrCodeEtcdNotReachable: "All the given peers are not reachable", + } +) + +type EtcdError struct { + ErrorCode int `json:"errorCode"` + Message string `json:"message"` + Cause string `json:"cause,omitempty"` + Index uint64 `json:"index"` +} + +func (e EtcdError) Error() string { + return fmt.Sprintf("%v: %v (%v) [%v]", e.ErrorCode, e.Message, e.Cause, e.Index) +} + +func newError(errorCode int, cause string, index uint64) *EtcdError { + return &EtcdError{ + ErrorCode: errorCode, + Message: errorMap[errorCode], + Cause: cause, + Index: index, + } +} + +func handleError(b []byte) error { + etcdErr := new(EtcdError) + + err := json.Unmarshal(b, etcdErr) + if err != nil { + logger.Warningf("cannot unmarshal etcd error: %v", err) + return err + } + + return etcdErr +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go new file mode 100644 index 0000000..976bf07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go @@ -0,0 +1,27 @@ +package etcd + +// Get gets the file or directory associated with the given key. +// If the key points to a directory, files and directories under +// it will be returned in sorted or unsorted order, depending on +// the sort flag. +// If recursive is set to false, contents under child directories +// will not be returned. +// If recursive is set to true, all the contents will be returned. +func (c *Client) Get(key string, sort, recursive bool) (*Response, error) { + raw, err := c.RawGet(key, sort, recursive) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) { + ops := Options{ + "recursive": recursive, + "sorted": sort, + } + + return c.get(key, ops) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go new file mode 100644 index 0000000..279c4e2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get_test.go @@ -0,0 +1,131 @@ +package etcd + +import ( + "reflect" + "testing" +) + +// cleanNode scrubs Expiration, ModifiedIndex and CreatedIndex of a node. +func cleanNode(n *Node) { + n.Expiration = nil + n.ModifiedIndex = 0 + n.CreatedIndex = 0 +} + +// cleanResult scrubs a result object two levels deep of Expiration, +// ModifiedIndex and CreatedIndex. +func cleanResult(result *Response) { + // TODO(philips): make this recursive. + cleanNode(result.Node) + for i, _ := range result.Node.Nodes { + cleanNode(result.Node.Nodes[i]) + for j, _ := range result.Node.Nodes[i].Nodes { + cleanNode(result.Node.Nodes[i].Nodes[j]) + } + } +} + +func TestGet(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + }() + + c.Set("foo", "bar", 5) + + result, err := c.Get("foo", false, false) + + if err != nil { + t.Fatal(err) + } + + if result.Node.Key != "/foo" || result.Node.Value != "bar" { + t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL) + } + + result, err = c.Get("goo", false, false) + if err == nil { + t.Fatalf("should not be able to get non-exist key") + } +} + +func TestGetAll(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("fooDir", true) + }() + + c.CreateDir("fooDir", 5) + c.Set("fooDir/k0", "v0", 5) + c.Set("fooDir/k1", "v1", 5) + + // Return kv-pairs in sorted order + result, err := c.Get("fooDir", true, false) + + if err != nil { + t.Fatal(err) + } + + expected := Nodes{ + &Node{ + Key: "/fooDir/k0", + Value: "v0", + TTL: 5, + }, + &Node{ + Key: "/fooDir/k1", + Value: "v1", + TTL: 5, + }, + } + + cleanResult(result) + + if !reflect.DeepEqual(result.Node.Nodes, expected) { + t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) + } + + // Test the `recursive` option + c.CreateDir("fooDir/childDir", 5) + c.Set("fooDir/childDir/k2", "v2", 5) + + // Return kv-pairs in sorted order + result, err = c.Get("fooDir", true, true) + + cleanResult(result) + + if err != nil { + t.Fatal(err) + } + + expected = Nodes{ + &Node{ + Key: "/fooDir/childDir", + Dir: true, + Nodes: Nodes{ + &Node{ + Key: "/fooDir/childDir/k2", + Value: "v2", + TTL: 5, + }, + }, + TTL: 5, + }, + &Node{ + Key: "/fooDir/k0", + Value: "v0", + TTL: 5, + }, + &Node{ + Key: "/fooDir/k1", + Value: "v1", + TTL: 5, + }, + } + + cleanResult(result) + + if !reflect.DeepEqual(result.Node.Nodes, expected) { + t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go new file mode 100644 index 0000000..701c9b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go @@ -0,0 +1,72 @@ +package etcd + +import ( + "fmt" + "net/url" + "reflect" +) + +type Options map[string]interface{} + +// An internally-used data structure that represents a mapping +// between valid options and their kinds +type validOptions map[string]reflect.Kind + +// Valid options for GET, PUT, POST, DELETE +// Using CAPITALIZED_UNDERSCORE to emphasize that these +// values are meant to be used as constants. +var ( + VALID_GET_OPTIONS = validOptions{ + "recursive": reflect.Bool, + "consistent": reflect.Bool, + "sorted": reflect.Bool, + "wait": reflect.Bool, + "waitIndex": reflect.Uint64, + } + + VALID_PUT_OPTIONS = validOptions{ + "prevValue": reflect.String, + "prevIndex": reflect.Uint64, + "prevExist": reflect.Bool, + "dir": reflect.Bool, + } + + VALID_POST_OPTIONS = validOptions{} + + VALID_DELETE_OPTIONS = validOptions{ + "recursive": reflect.Bool, + "dir": reflect.Bool, + "prevValue": reflect.String, + "prevIndex": reflect.Uint64, + } +) + +// Convert options to a string of HTML parameters +func (ops Options) toParameters(validOps validOptions) (string, error) { + p := "?" + values := url.Values{} + + if ops == nil { + return "", nil + } + + for k, v := range ops { + // Check if the given option is valid (that it exists) + kind := validOps[k] + if kind == reflect.Invalid { + return "", fmt.Errorf("Invalid option: %v", k) + } + + // Check if the given option is of the valid type + t := reflect.TypeOf(v) + if kind != t.Kind() { + return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.", + k, kind, t.Kind()) + } + + values.Set(k, fmt.Sprintf("%v", v)) + } + + p += values.Encode() + return p, nil +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go new file mode 100644 index 0000000..2741cd0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go @@ -0,0 +1,398 @@ +package etcd + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "net/url" + "path" + "strings" + "sync" + "time" +) + +// Errors introduced by handling requests +var ( + ErrRequestCancelled = errors.New("sending request is cancelled") +) + +type RawRequest struct { + Method string + RelativePath string + Values url.Values + Cancel <-chan bool +} + +// NewRawRequest returns a new RawRequest +func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan bool) *RawRequest { + return &RawRequest{ + Method: method, + RelativePath: relativePath, + Values: values, + Cancel: cancel, + } +} + +// getCancelable issues a cancelable GET request +func (c *Client) getCancelable(key string, options Options, + cancel <-chan bool) (*RawResponse, error) { + logger.Debugf("get %s [%s]", key, c.cluster.Leader) + p := keyToPath(key) + + // If consistency level is set to STRONG, append + // the `consistent` query string. + if c.config.Consistency == STRONG_CONSISTENCY { + options["consistent"] = true + } + + str, err := options.toParameters(VALID_GET_OPTIONS) + if err != nil { + return nil, err + } + p += str + + req := NewRawRequest("GET", p, nil, cancel) + resp, err := c.SendRequest(req) + + if err != nil { + return nil, err + } + + return resp, nil +} + +// get issues a GET request +func (c *Client) get(key string, options Options) (*RawResponse, error) { + return c.getCancelable(key, options, nil) +} + +// put issues a PUT request +func (c *Client) put(key string, value string, ttl uint64, + options Options) (*RawResponse, error) { + + logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + p := keyToPath(key) + + str, err := options.toParameters(VALID_PUT_OPTIONS) + if err != nil { + return nil, err + } + p += str + + req := NewRawRequest("PUT", p, buildValues(value, ttl), nil) + resp, err := c.SendRequest(req) + + if err != nil { + return nil, err + } + + return resp, nil +} + +// post issues a POST request +func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) { + logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + p := keyToPath(key) + + req := NewRawRequest("POST", p, buildValues(value, ttl), nil) + resp, err := c.SendRequest(req) + + if err != nil { + return nil, err + } + + return resp, nil +} + +// delete issues a DELETE request +func (c *Client) delete(key string, options Options) (*RawResponse, error) { + logger.Debugf("delete %s [%s]", key, c.cluster.Leader) + p := keyToPath(key) + + str, err := options.toParameters(VALID_DELETE_OPTIONS) + if err != nil { + return nil, err + } + p += str + + req := NewRawRequest("DELETE", p, nil, nil) + resp, err := c.SendRequest(req) + + if err != nil { + return nil, err + } + + return resp, nil +} + +// SendRequest sends a HTTP request and returns a Response as defined by etcd +func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { + + var req *http.Request + var resp *http.Response + var httpPath string + var err error + var respBody []byte + + var numReqs = 1 + + checkRetry := c.CheckRetry + if checkRetry == nil { + checkRetry = DefaultCheckRetry + } + + cancelled := make(chan bool, 1) + reqLock := new(sync.Mutex) + + if rr.Cancel != nil { + cancelRoutine := make(chan bool) + defer close(cancelRoutine) + + go func() { + select { + case <-rr.Cancel: + cancelled <- true + logger.Debug("send.request is cancelled") + case <-cancelRoutine: + return + } + + // Repeat canceling request until this thread is stopped + // because we have no idea about whether it succeeds. + for { + reqLock.Lock() + c.httpClient.Transport.(*http.Transport).CancelRequest(req) + reqLock.Unlock() + + select { + case <-time.After(100 * time.Millisecond): + case <-cancelRoutine: + return + } + } + }() + } + + // If we connect to a follower and consistency is required, retry until + // we connect to a leader + sleep := 25 * time.Millisecond + maxSleep := time.Second + + for attempt := 0; ; attempt++ { + if attempt > 0 { + select { + case <-cancelled: + return nil, ErrRequestCancelled + case <-time.After(sleep): + sleep = sleep * 2 + if sleep > maxSleep { + sleep = maxSleep + } + } + } + + logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath) + + if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY { + // If it's a GET and consistency level is set to WEAK, + // then use a random machine. + httpPath = c.getHttpPath(true, rr.RelativePath) + } else { + // Else use the leader. + httpPath = c.getHttpPath(false, rr.RelativePath) + } + + // Return a cURL command if curlChan is set + if c.cURLch != nil { + command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath) + for key, value := range rr.Values { + command += fmt.Sprintf(" -d %s=%s", key, value[0]) + } + c.sendCURL(command) + } + + logger.Debug("send.request.to ", httpPath, " | method ", rr.Method) + + req, err := func() (*http.Request, error) { + reqLock.Lock() + defer reqLock.Unlock() + + if rr.Values == nil { + if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil { + return nil, err + } + } else { + body := strings.NewReader(rr.Values.Encode()) + if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil { + return nil, err + } + + req.Header.Set("Content-Type", + "application/x-www-form-urlencoded; param=value") + } + return req, nil + }() + + if err != nil { + return nil, err + } + + resp, err = c.httpClient.Do(req) + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + + // If the request was cancelled, return ErrRequestCancelled directly + select { + case <-cancelled: + return nil, ErrRequestCancelled + default: + } + + numReqs++ + + // network error, change a machine! + if err != nil { + logger.Debug("network error: ", err.Error()) + lastResp := http.Response{} + if checkErr := checkRetry(c.cluster, numReqs, lastResp, err); checkErr != nil { + return nil, checkErr + } + + c.cluster.switchLeader(attempt % len(c.cluster.Machines)) + continue + } + + // if there is no error, it should receive response + logger.Debug("recv.response.from ", httpPath) + + if validHttpStatusCode[resp.StatusCode] { + // try to read byte code and break the loop + respBody, err = ioutil.ReadAll(resp.Body) + if err == nil { + logger.Debug("recv.success ", httpPath) + break + } + // ReadAll error may be caused due to cancel request + select { + case <-cancelled: + return nil, ErrRequestCancelled + default: + } + + if err == io.ErrUnexpectedEOF { + // underlying connection was closed prematurely, probably by timeout + // TODO: empty body or unexpectedEOF can cause http.Transport to get hosed; + // this allows the client to detect that and take evasive action. Need + // to revisit once code.google.com/p/go/issues/detail?id=8648 gets fixed. + respBody = []byte{} + break + } + } + + // if resp is TemporaryRedirect, set the new leader and retry + if resp.StatusCode == http.StatusTemporaryRedirect { + u, err := resp.Location() + + if err != nil { + logger.Warning(err) + } else { + // Update cluster leader based on redirect location + // because it should point to the leader address + c.cluster.updateLeaderFromURL(u) + logger.Debug("recv.response.relocate ", u.String()) + } + resp.Body.Close() + continue + } + + if checkErr := checkRetry(c.cluster, numReqs, *resp, + errors.New("Unexpected HTTP status code")); checkErr != nil { + return nil, checkErr + } + resp.Body.Close() + } + + r := &RawResponse{ + StatusCode: resp.StatusCode, + Body: respBody, + Header: resp.Header, + } + + return r, nil +} + +// DefaultCheckRetry defines the retrying behaviour for bad HTTP requests +// If we have retried 2 * machine number, stop retrying. +// If status code is InternalServerError, sleep for 200ms. +func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response, + err error) error { + + if numReqs >= 2*len(cluster.Machines) { + return newError(ErrCodeEtcdNotReachable, + "Tried to connect to each peer twice and failed", 0) + } + + code := lastResp.StatusCode + if code == http.StatusInternalServerError { + time.Sleep(time.Millisecond * 200) + + } + + logger.Warning("bad response status code", code) + return nil +} + +func (c *Client) getHttpPath(random bool, s ...string) string { + var machine string + if random { + machine = c.cluster.Machines[rand.Intn(len(c.cluster.Machines))] + } else { + machine = c.cluster.Leader + } + + fullPath := machine + "/" + version + for _, seg := range s { + fullPath = fullPath + "/" + seg + } + + return fullPath +} + +// buildValues builds a url.Values map according to the given value and ttl +func buildValues(value string, ttl uint64) url.Values { + v := url.Values{} + + if value != "" { + v.Set("value", value) + } + + if ttl > 0 { + v.Set("ttl", fmt.Sprintf("%v", ttl)) + } + + return v +} + +// convert key string to http path exclude version, including URL escaping +// for example: key[foo] -> path[keys/foo] +// key[/%z] -> path[keys/%25z] +// key[/] -> path[keys/] +func keyToPath(key string) string { + // URL-escape our key, except for slashes + p := strings.Replace(url.QueryEscape(path.Join("keys", key)), "%2F", "/", -1) + + // corner case: if key is "/" or "//" ect + // path join will clear the tailing "/" + // we need to add it back + if p == "keys" { + p = "keys/" + } + + return p +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests_test.go new file mode 100644 index 0000000..7a2bd19 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests_test.go @@ -0,0 +1,22 @@ +package etcd + +import "testing" + +func TestKeyToPath(t *testing.T) { + tests := []struct { + key string + wpath string + }{ + {"", "keys/"}, + {"foo", "keys/foo"}, + {"foo/bar", "keys/foo/bar"}, + {"%z", "keys/%25z"}, + {"/", "keys/"}, + } + for i, tt := range tests { + path := keyToPath(tt.key) + if path != tt.wpath { + t.Errorf("#%d: path = %s, want %s", i, path, tt.wpath) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go new file mode 100644 index 0000000..1fe9b4e --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/response.go @@ -0,0 +1,89 @@ +package etcd + +import ( + "encoding/json" + "net/http" + "strconv" + "time" +) + +const ( + rawResponse = iota + normalResponse +) + +type responseType int + +type RawResponse struct { + StatusCode int + Body []byte + Header http.Header +} + +var ( + validHttpStatusCode = map[int]bool{ + http.StatusCreated: true, + http.StatusOK: true, + http.StatusBadRequest: true, + http.StatusNotFound: true, + http.StatusPreconditionFailed: true, + http.StatusForbidden: true, + } +) + +// Unmarshal parses RawResponse and stores the result in Response +func (rr *RawResponse) Unmarshal() (*Response, error) { + if rr.StatusCode != http.StatusOK && rr.StatusCode != http.StatusCreated { + return nil, handleError(rr.Body) + } + + resp := new(Response) + + err := json.Unmarshal(rr.Body, resp) + + if err != nil { + return nil, err + } + + // attach index and term to response + resp.EtcdIndex, _ = strconv.ParseUint(rr.Header.Get("X-Etcd-Index"), 10, 64) + resp.RaftIndex, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Index"), 10, 64) + resp.RaftTerm, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Term"), 10, 64) + + return resp, nil +} + +type Response struct { + Action string `json:"action"` + Node *Node `json:"node"` + PrevNode *Node `json:"prevNode,omitempty"` + EtcdIndex uint64 `json:"etcdIndex"` + RaftIndex uint64 `json:"raftIndex"` + RaftTerm uint64 `json:"raftTerm"` +} + +type Node struct { + Key string `json:"key, omitempty"` + Value string `json:"value,omitempty"` + Dir bool `json:"dir,omitempty"` + Expiration *time.Time `json:"expiration,omitempty"` + TTL int64 `json:"ttl,omitempty"` + Nodes Nodes `json:"nodes,omitempty"` + ModifiedIndex uint64 `json:"modifiedIndex,omitempty"` + CreatedIndex uint64 `json:"createdIndex,omitempty"` +} + +type Nodes []*Node + +// interfaces for sorting +func (ns Nodes) Len() int { + return len(ns) +} + +func (ns Nodes) Less(i, j int) bool { + return ns[i].Key < ns[j].Key +} + +func (ns Nodes) Swap(i, j int) { + ns[i], ns[j] = ns[j], ns[i] +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go new file mode 100644 index 0000000..756e317 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go @@ -0,0 +1,42 @@ +package etcd + +import ( + "fmt" + "testing" +) + +func TestSetCurlChan(t *testing.T) { + c := NewClient(nil) + c.OpenCURL() + + defer func() { + c.Delete("foo", true) + }() + + _, err := c.Set("foo", "bar", 5) + if err != nil { + t.Fatal(err) + } + + expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5", + c.cluster.Leader) + actual := c.RecvCURL() + if expected != actual { + t.Fatalf(`Command "%s" is not equal to expected value "%s"`, + actual, expected) + } + + c.SetConsistency(STRONG_CONSISTENCY) + _, err = c.Get("foo", false, false) + if err != nil { + t.Fatal(err) + } + + expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false", + c.cluster.Leader) + actual = c.RecvCURL() + if expected != actual { + t.Fatalf(`Command "%s" is not equal to expected value "%s"`, + actual, expected) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go new file mode 100644 index 0000000..e2840cf --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create.go @@ -0,0 +1,137 @@ +package etcd + +// Set sets the given key to the given value. +// It will create a new key value pair or replace the old one. +// It will not replace a existing directory. +func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) { + raw, err := c.RawSet(key, value, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// SetDir sets the given key to a directory. +// It will create a new directory or replace the old key value pair by a directory. +// It will not replace a existing directory. +func (c *Client) SetDir(key string, ttl uint64) (*Response, error) { + raw, err := c.RawSetDir(key, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// CreateDir creates a directory. It succeeds only if +// the given key does not yet exist. +func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) { + raw, err := c.RawCreateDir(key, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// UpdateDir updates the given directory. It succeeds only if the +// given key already exists. +func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) { + raw, err := c.RawUpdateDir(key, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// Create creates a file with the given value under the given key. It succeeds +// only if the given key does not yet exist. +func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) { + raw, err := c.RawCreate(key, value, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// CreateInOrder creates a file with a key that's guaranteed to be higher than other +// keys in the given directory. It is useful for creating queues. +func (c *Client) CreateInOrder(dir string, value string, ttl uint64) (*Response, error) { + raw, err := c.RawCreateInOrder(dir, value, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +// Update updates the given key to the given value. It succeeds only if the +// given key already exists. +func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) { + raw, err := c.RawUpdate(key, value, ttl) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() +} + +func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) { + ops := Options{ + "prevExist": true, + "dir": true, + } + + return c.put(key, "", ttl, ops) +} + +func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) { + ops := Options{ + "prevExist": false, + "dir": true, + } + + return c.put(key, "", ttl, ops) +} + +func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) { + return c.put(key, value, ttl, nil) +} + +func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) { + ops := Options{ + "dir": true, + } + + return c.put(key, "", ttl, ops) +} + +func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) { + ops := Options{ + "prevExist": true, + } + + return c.put(key, value, ttl, ops) +} + +func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) { + ops := Options{ + "prevExist": false, + } + + return c.put(key, value, ttl, ops) +} + +func (c *Client) RawCreateInOrder(dir string, value string, ttl uint64) (*RawResponse, error) { + return c.post(dir, value, ttl) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go new file mode 100644 index 0000000..ced0f06 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_update_create_test.go @@ -0,0 +1,241 @@ +package etcd + +import ( + "testing" +) + +func TestSet(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + }() + + resp, err := c.Set("foo", "bar", 5) + if err != nil { + t.Fatal(err) + } + if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 { + t.Fatalf("Set 1 failed: %#v", resp) + } + if resp.PrevNode != nil { + t.Fatalf("Set 1 PrevNode failed: %#v", resp) + } + + resp, err = c.Set("foo", "bar2", 5) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" && resp.Node.TTL == 5) { + t.Fatalf("Set 2 failed: %#v", resp) + } + if resp.PrevNode.Key != "/foo" || resp.PrevNode.Value != "bar" || resp.Node.TTL != 5 { + t.Fatalf("Set 2 PrevNode failed: %#v", resp) + } +} + +func TestUpdate(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + c.Delete("nonexistent", true) + }() + + resp, err := c.Set("foo", "bar", 5) + + if err != nil { + t.Fatal(err) + } + + // This should succeed. + resp, err = c.Update("foo", "wakawaka", 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "update" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) { + t.Fatalf("Update 1 failed: %#v", resp) + } + if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.Node.TTL == 5) { + t.Fatalf("Update 1 prevValue failed: %#v", resp) + } + + // This should fail because the key does not exist. + resp, err = c.Update("nonexistent", "whatever", 5) + if err == nil { + t.Fatalf("The key %v did not exist, so the update should have failed."+ + "The response was: %#v", resp.Node.Key, resp) + } +} + +func TestCreate(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("newKey", true) + }() + + newKey := "/newKey" + newValue := "/newValue" + + // This should succeed + resp, err := c.Create(newKey, newValue, 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "create" && resp.Node.Key == newKey && + resp.Node.Value == newValue && resp.Node.TTL == 5) { + t.Fatalf("Create 1 failed: %#v", resp) + } + if resp.PrevNode != nil { + t.Fatalf("Create 1 PrevNode failed: %#v", resp) + } + + // This should fail, because the key is already there + resp, err = c.Create(newKey, newValue, 5) + if err == nil { + t.Fatalf("The key %v did exist, so the creation should have failed."+ + "The response was: %#v", resp.Node.Key, resp) + } +} + +func TestCreateInOrder(t *testing.T) { + c := NewClient(nil) + dir := "/queue" + defer func() { + c.DeleteDir(dir) + }() + + var firstKey, secondKey string + + resp, err := c.CreateInOrder(dir, "1", 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "create" && resp.Node.Value == "1" && resp.Node.TTL == 5) { + t.Fatalf("Create 1 failed: %#v", resp) + } + + firstKey = resp.Node.Key + + resp, err = c.CreateInOrder(dir, "2", 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "create" && resp.Node.Value == "2" && resp.Node.TTL == 5) { + t.Fatalf("Create 2 failed: %#v", resp) + } + + secondKey = resp.Node.Key + + if firstKey >= secondKey { + t.Fatalf("Expected first key to be greater than second key, but %s is not greater than %s", + firstKey, secondKey) + } +} + +func TestSetDir(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("foo", true) + c.Delete("fooDir", true) + }() + + resp, err := c.CreateDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) { + t.Fatalf("SetDir 1 failed: %#v", resp) + } + if resp.PrevNode != nil { + t.Fatalf("SetDir 1 PrevNode failed: %#v", resp) + } + + // This should fail because /fooDir already points to a directory + resp, err = c.CreateDir("/fooDir", 5) + if err == nil { + t.Fatalf("fooDir already points to a directory, so SetDir should have failed."+ + "The response was: %#v", resp) + } + + _, err = c.Set("foo", "bar", 5) + if err != nil { + t.Fatal(err) + } + + // This should succeed + // It should replace the key + resp, err = c.SetDir("foo", 5) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/foo" && resp.Node.Value == "" && resp.Node.TTL == 5) { + t.Fatalf("SetDir 2 failed: %#v", resp) + } + if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.PrevNode.TTL == 5) { + t.Fatalf("SetDir 2 failed: %#v", resp) + } +} + +func TestUpdateDir(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("fooDir", true) + }() + + resp, err := c.CreateDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + + // This should succeed. + resp, err = c.UpdateDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "update" && resp.Node.Key == "/fooDir" && + resp.Node.Value == "" && resp.Node.TTL == 5) { + t.Fatalf("UpdateDir 1 failed: %#v", resp) + } + if !(resp.PrevNode.Key == "/fooDir" && resp.PrevNode.Dir == true && resp.PrevNode.TTL == 5) { + t.Fatalf("UpdateDir 1 PrevNode failed: %#v", resp) + } + + // This should fail because the key does not exist. + resp, err = c.UpdateDir("nonexistentDir", 5) + if err == nil { + t.Fatalf("The key %v did not exist, so the update should have failed."+ + "The response was: %#v", resp.Node.Key, resp) + } +} + +func TestCreateDir(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("fooDir", true) + }() + + // This should succeed + resp, err := c.CreateDir("fooDir", 5) + if err != nil { + t.Fatal(err) + } + + if !(resp.Action == "create" && resp.Node.Key == "/fooDir" && + resp.Node.Value == "" && resp.Node.TTL == 5) { + t.Fatalf("CreateDir 1 failed: %#v", resp) + } + if resp.PrevNode != nil { + t.Fatalf("CreateDir 1 PrevNode failed: %#v", resp) + } + + // This should fail, because the key is already there + resp, err = c.CreateDir("fooDir", 5) + if err == nil { + t.Fatalf("The key %v did exist, so the creation should have failed."+ + "The response was: %#v", resp.Node.Key, resp) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go new file mode 100644 index 0000000..b3d05df --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/version.go @@ -0,0 +1,3 @@ +package etcd + +const version = "v2" diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go new file mode 100644 index 0000000..aa8d3df --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch.go @@ -0,0 +1,103 @@ +package etcd + +import ( + "errors" +) + +// Errors introduced by the Watch command. +var ( + ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel") +) + +// If recursive is set to true the watch returns the first change under the given +// prefix since the given index. +// +// If recursive is set to false the watch returns the first change to the given key +// since the given index. +// +// To watch for the latest change, set waitIndex = 0. +// +// If a receiver channel is given, it will be a long-term watch. Watch will block at the +//channel. After someone receives the channel, it will go on to watch that +// prefix. If a stop channel is given, the client can close long-term watch using +// the stop channel. +func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool, + receiver chan *Response, stop chan bool) (*Response, error) { + logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader) + if receiver == nil { + raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) + + if err != nil { + return nil, err + } + + return raw.Unmarshal() + } + defer close(receiver) + + for { + raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) + + if err != nil { + return nil, err + } + + resp, err := raw.Unmarshal() + + if err != nil { + return nil, err + } + + waitIndex = resp.Node.ModifiedIndex + 1 + receiver <- resp + } +} + +func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool, + receiver chan *RawResponse, stop chan bool) (*RawResponse, error) { + + logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader) + if receiver == nil { + return c.watchOnce(prefix, waitIndex, recursive, stop) + } + + for { + raw, err := c.watchOnce(prefix, waitIndex, recursive, stop) + + if err != nil { + return nil, err + } + + resp, err := raw.Unmarshal() + + if err != nil { + return nil, err + } + + waitIndex = resp.Node.ModifiedIndex + 1 + receiver <- raw + } +} + +// helper func +// return when there is change under the given prefix +func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) { + + options := Options{ + "wait": true, + } + if waitIndex > 0 { + options["waitIndex"] = waitIndex + } + if recursive { + options["recursive"] = true + } + + resp, err := c.getCancelable(key, options, stop) + + if err == ErrRequestCancelled { + return nil, ErrWatchStoppedByUser + } + + return resp, err +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go new file mode 100644 index 0000000..43e1dfe --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/watch_test.go @@ -0,0 +1,119 @@ +package etcd + +import ( + "fmt" + "runtime" + "testing" + "time" +) + +func TestWatch(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("watch_foo", true) + }() + + go setHelper("watch_foo", "bar", c) + + resp, err := c.Watch("watch_foo", 0, false, nil, nil) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { + t.Fatalf("Watch 1 failed: %#v", resp) + } + + go setHelper("watch_foo", "bar", c) + + resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") { + t.Fatalf("Watch 2 failed: %#v", resp) + } + + routineNum := runtime.NumGoroutine() + + ch := make(chan *Response, 10) + stop := make(chan bool, 1) + + go setLoop("watch_foo", "bar", c) + + go receiver(ch, stop) + + _, err = c.Watch("watch_foo", 0, false, ch, stop) + if err != ErrWatchStoppedByUser { + t.Fatalf("Watch returned a non-user stop error") + } + + if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { + t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) + } +} + +func TestWatchAll(t *testing.T) { + c := NewClient(nil) + defer func() { + c.Delete("watch_foo", true) + }() + + go setHelper("watch_foo/foo", "bar", c) + + resp, err := c.Watch("watch_foo", 0, true, nil, nil) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { + t.Fatalf("WatchAll 1 failed: %#v", resp) + } + + go setHelper("watch_foo/foo", "bar", c) + + resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil) + if err != nil { + t.Fatal(err) + } + if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") { + t.Fatalf("WatchAll 2 failed: %#v", resp) + } + + ch := make(chan *Response, 10) + stop := make(chan bool, 1) + + routineNum := runtime.NumGoroutine() + + go setLoop("watch_foo/foo", "bar", c) + + go receiver(ch, stop) + + _, err = c.Watch("watch_foo", 0, true, ch, stop) + if err != ErrWatchStoppedByUser { + t.Fatalf("Watch returned a non-user stop error") + } + + if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum { + t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum) + } +} + +func setHelper(key, value string, c *Client) { + time.Sleep(time.Second) + c.Set(key, value, 100) +} + +func setLoop(key, value string, c *Client) { + time.Sleep(time.Second) + for i := 0; i < 10; i++ { + newValue := fmt.Sprintf("%s_%v", value, i) + c.Set(key, newValue, 100) + time.Sleep(time.Second / 10) + } +} + +func receiver(c chan *Response, stop chan bool) { + for i := 0; i < 10; i++ { + <-c + } + stop <- true +} diff --git a/Godeps/_workspace/src/github.com/cupcake/sigil/gen/bench_test.go b/Godeps/_workspace/src/github.com/cupcake/sigil/gen/bench_test.go new file mode 100644 index 0000000..7f37fc5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cupcake/sigil/gen/bench_test.go @@ -0,0 +1,46 @@ +package gen + +import ( + "encoding/hex" + "image/color" + "image/png" + "io/ioutil" + "testing" +) + +var data, _ = hex.DecodeString("0447550ed53b522827e17b7d7976dd3f") +var config = Sigil{ + Rows: 5, + Foreground: []color.NRGBA{ + rgb(45, 79, 255), + rgb(44, 172, 0), + rgb(254, 180, 44), + rgb(226, 121, 234), + rgb(30, 179, 253), + rgb(232, 77, 65), + rgb(49, 203, 115), + rgb(141, 69, 170), + rgb(252, 125, 31), + }, + Background: rgb(224, 224, 224), +} + +func rgb(r, g, b uint8) color.NRGBA { return color.NRGBA{r, g, b, 255} } + +func BenchmarkMake(b *testing.B) { + for i := 0; i < b.N; i++ { + config.Make(420, false, data) + } +} + +func BenchmarkMakeEncode(b *testing.B) { + for i := 0; i < b.N; i++ { + png.Encode(ioutil.Discard, config.Make(420, false, data)) + } +} + +func BenchmarkMakeSVG(b *testing.B) { + for i := 0; i < b.N; i++ { + config.MakeSVG(ioutil.Discard, 420, false, data) + } +} diff --git a/Godeps/_workspace/src/github.com/cupcake/sigil/gen/sigil.go b/Godeps/_workspace/src/github.com/cupcake/sigil/gen/sigil.go new file mode 100644 index 0000000..05e0d12 --- /dev/null +++ b/Godeps/_workspace/src/github.com/cupcake/sigil/gen/sigil.go @@ -0,0 +1,94 @@ +package gen + +import ( + "fmt" + "image" + "image/color" + "io" + + "github.com/ajstarks/svgo" +) + +type Sigil struct { + Rows int + Foreground []color.NRGBA + Background color.NRGBA +} + +func (s *Sigil) Make(width int, inverted bool, data []byte) image.Image { + fg, bg := s.colors(data[0], inverted) + palette := color.Palette{bg, fg} + img := image.NewPaletted(image.Rect(0, 0, width, width), palette) + for _, rect := range s.cells(width, data[1:]) { + for x := rect.Min.X; x < rect.Max.X; x++ { + for y := rect.Min.Y; y < rect.Max.Y; y++ { + img.Pix[y*img.Stride+x] = 1 + } + } + } + return img +} + +func (s *Sigil) MakeSVG(w io.Writer, width int, inverted bool, data []byte) { + canvas := svg.New(w) + fg, bg := s.colors(data[0], inverted) + fgFill, bgFill := svgFill(fg), svgFill(bg) + + canvas.Start(width, width) + canvas.Rect(0, 0, width, width, bgFill) + for _, rect := range s.cells(width, data[1:]) { + canvas.Rect(rect.Min.X, rect.Min.Y, rect.Dx(), rect.Dy(), fgFill) + } + canvas.End() +} + +func svgFill(c color.NRGBA) string { + return fmt.Sprintf("fill:rgba(%d,%d,%d,%.2g);", c.R, c.G, c.B, float64(c.A)*1/255) +} + +func (s *Sigil) fill(cell int, data []byte) bool { + if data[cell/8]>>uint(8-((cell%8)+1))&1 == 0 { + return false + } + return true +} + +func (s *Sigil) cells(width int, data []byte) []image.Rectangle { + width = width / (s.Rows + 1) + cols := s.Rows/2 + s.Rows%2 + cells := cols * s.Rows + res := make([]image.Rectangle, 0, s.Rows*s.Rows) + padding := width / 2 + for i := 0; i < cells; i++ { + if !s.fill(i, data) { + continue + } + + column := i / s.Rows + row := i % s.Rows + + pt := image.Pt(padding+(column*width), padding+(row*width)) + rect := image.Rectangle{pt, image.Pt(pt.X+width, pt.Y+width)} + if s.Rows%2 == 0 && column == cols-1 { + // last/middle column, double width + rect.Max.X += width + } + res = append(res, rect) + + if column < cols-1 { + // add mirrored column + rect.Min.X = padding + ((s.Rows - column - 1) * width) + rect.Max.X = rect.Min.X + width + res = append(res, rect) + } + } + return res +} + +func (s *Sigil) colors(b byte, inverted bool) (color.NRGBA, color.NRGBA) { + fg := s.Foreground[int(b)%len(s.Foreground)] + if inverted { + return s.Background, fg + } + return fg, s.Background +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore b/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore new file mode 100644 index 0000000..385ee47 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore @@ -0,0 +1,23 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +.wercker \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md new file mode 100644 index 0000000..67d5b76 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md @@ -0,0 +1,119 @@ +# Changelog + +## v0.6.1 - 13 Feb 2015 + +- Reduce GC by using buffers when reading and writing +- Fixed encoding `time.Time` ignoring millseconds +- Fixed pointers in structs that implement the `Marshaler`/`Unmarshaler` interfaces being ignored + +## v0.6.0 - 1 Feb 2015 + +There are some major changes to the driver with this release that are not related to the RethinkDB v1.16 release. Please have a read through them: +- Improvements to result decoding by caching reflection calls. +- Finished implementing the `Marshaler`/`Unmarshaler` interfaces +- Connection pool overhauled. There were a couple of issues with connections in the previous releases so this release replaces the `fatih/pool` package with a connection pool based on the `database/sql` connection pool. +- Another change is the removal of the prefetching mechanism as the connection+cursor logic was becoming quite complex and causing bugs, hopefully this will be added back in the near future but for now I am focusing my efforts on ensuring the driver is as stable as possible #130 #137 +- Due to the above change the API for connecting has changed slightly (The API is now closer to the `database/sql` API. `ConnectOpts` changes: + - `MaxActive` renamed to `MaxOpen` + - `IdleTimeout` renamed to `Timeout` +- `Cursor`s are now only closed automatically when calling either `All` or `One` +- `Exec` now takes `ExecOpts` instead of `RunOpts`. The only difference is that `Exec` has the `NoReply` field + +With that out the way here are the v1.16 changes: + +- Added `Range` which generates all numbers from a given range +- Added an optional squash argument to the changes command, which lets the server combine multiple changes to the same document (defaults to true) +- Added new admin functions (`Config`, `Rebalance`, `Reconfigure`, `Status`, `Wait`) +- Added support for `SUCCESS_ATOM_FEED` +- Added `MinIndex` + `MaxInde`x functions +- Added `ToJSON` function +- Updated `WriteResponse` type + +Since this release has a lot of changes and although I have tested these changes sometimes things fall through the gaps. If you discover any bugs please let me know and I will try to fix them as soon as possible. + +## Hotfix - 14 Dec 2014 + +- Fixed empty slices being returned as `[]T(nil)` not `[]T{}` #138 + +## v0.5.0 - 6 Oct 2014 + +- Added geospatial terms (`Circle`, `Distance`, `Fill`, `Geojson`, `ToGeojson`, `GetIntersecting`, `GetNearest`, `Includes`, `Intersects`, `Line`, `Point`, `Polygon`, `PolygonSub`) +- Added `UUID` term for generating unique IDs +- Added `AtIndex` term, combines `Nth` and `GetField` +- Added the `Geometry` type, see the types package +- Updated the `BatchConf` field in `RunOpts`, now uses the `BatchOpts` type +- Removed support for the `FieldMapper` interface + +### Internal Changes +- Fixed encoding performance issues, greatly improves writes/second +- Updated `Next` to zero the destination value every time it is called. + +## v0.4.2 - 6 Sept 2014 + +- Fixed issue causing `Close` to start an infinite loop +- Tidied up connection closing logic + +## v0.4.1 - 5 Sept 2014 + +- Fixed bug causing Pseudotypes to not be decoded properly (#117) +- Updated github.com/fatih/pool to v2 (#118) + +## v0.4.0 - 13 Aug 2014 + +- Updated the driver to support RethinkDB v1.14 (#116) +- Added the Binary data type +- Added the Binary command which takes a `[]byte`, `io.Reader` or `bytes.Buffer{}` as an argument. +- Added the `BinaryFormat` optional argument to `RunOpts` +- Added the `GroupFormat` optional argument to `RunOpts` +- Added the `ArrayLimit` optional argument to `RunOpts` +- Renamed the `ReturnVals` optional argument to `ReturnChanges` +- Renamed the `Upsert` optional argument to `Conflict` +- Added the `IndexRename` command +- Updated `Distinct` to now take the `Index` optional argument (using `DistinctOpts`) + +### Internal Changes + +- Updated to use the new JSON protocol +- Switched the connection pool code to use github.com/fatih/pool +- Added some benchmarks + +## v0.3.2 - 17 Aug 2014 + +- Fixed issue causing connections not to be closed correctly (#109) +- Fixed issue causing terms in optional arguments to be encoded incorrectly (#114) + +## v0.3.1 - 14 June 2014 + +- Fixed "Token ## not in stream cache" error (#103) +- Changed Exec to no longer use NoReply. It now waits for the server to respond. + +## v0.3 (RethinkDB v1.13) - 26 June 2014 + +- Replaced `ResultRows`/`ResultRow` with `Cursor`, `Cursor` has the `Next`, `All` and `One` methods which stores the relevant value in the value pointed at by result. For more information check the examples. +- Changed the time constants (Days and Months) to package globals instead of functions +- Added the `Args` term and changed the arguments for many terms to `args ...interface{}` to allow argument splicing +- Added the `Changes` term and support for the feed response type +- Added the `Random` term +- Added the `Http` term +- The second argument for `Slice` is now optional +- `EqJoin` now accepts a function as its first argument +- `Nth` now returns a selection + +## v0.2 (RethinkDB v1.12) - 13 April 2014 + +* Changed `Connect` to use `ConnectOpts` instead of `map[string]interface{}` +* Migrated to new `Group`/`Ungroup` functions, these replace `GroupedMapReduce` and `GroupBy` +* Added new aggregators +* Removed base parameter for `Reduce` +* Added `Object` function +* Added `Upcase`, `Downcase` and `Split` string functions +* Added `GROUPED_DATA` pseudotype +* Fixed query printing + +## v0.1 (RethinkDB v1.11) - 27 November 2013 + +* Added noreply writes +* Added the new terms `index_status`, `index_wait` and `sync` +* Added the profile flag to the run functions +* Optional arguments are now structs instead of key, pair strings. Almost all of the struct fields are of type interface{} as they can have terms inside them. For example: `r.TableCreateOpts{ PrimaryKey: r.Expr("index") }` +* Returned arrays are now properly loaded into ResultRows. In the past when running `r.Expr([]interface{}{1,2,3})` would require you to use `RunRow` followed by `Scan`. You can now use `Run` followed by `ScanAll` diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/LICENSE b/Godeps/_workspace/src/github.com/dancannon/gorethink/LICENSE new file mode 100644 index 0000000..dd64ae2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "{}" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md new file mode 100644 index 0000000..0ccf115 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md @@ -0,0 +1,228 @@ +# GoRethink - RethinkDB Driver for Go + +[![GitHub tag](https://img.shields.io/github/tag/dancannon/gorethink.svg?style=flat)]() +[![GoDoc](https://godoc.org/github.com/dancannon/gorethink?status.png)](https://godoc.org/github.com/dancannon/gorethink) +[![wercker status](https://app.wercker.com/status/e315e764041af8e80f0c68280d4b4de2/s/master "wercker status")](https://app.wercker.com/project/bykey/e315e764041af8e80f0c68280d4b4de2) + +[Go](http://golang.org/) driver for [RethinkDB](http://www.rethinkdb.com/) + + +Current version: v0.6.1 (RethinkDB v1.16) + +**Version 0.6 introduced some small API changes and some significant internal changes, for more information check the [change log](CHANGELOG.md) and please be aware the driver is not yet stable** + +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dancannon/gorethink?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + +## Installation + +```sh +go get -u github.com/dancannon/gorethink +``` + +## Connection + +### Basic Connection + +Setting up a basic connection with RethinkDB is simple: + +```go +import ( + r "github.com/dancannon/gorethink" +) + +var session *r.Session + +session, err := r.Connect(r.ConnectOpts{ + Address: "localhost:28015", + Database: "test", +}) + +if err != nil { + log.Fatalln(err.Error()) +} + +``` +See the [documentation](http://godoc.org/github.com/dancannon/gorethink#Connect) for a list of supported arguments to Connect(). + +### Connection Pool + +The driver uses a connection pool at all times, by default it creates and frees connections automatically. It's safe for concurrent use by multiple goroutines. + +To configure the connection pool `MaxIdle`, `MaxOpen` and `IdleTimeout` can be specified during connection. If you wish to change the value of `MaxIdle` or `MaxOpen` during runtime then the functions `SetMaxIdleConns` and `SetMaxOpenConns` can be used. + +```go +import ( + r "github.com/dancannon/gorethink" +) + +var session *r.Session + +session, err := r.Connect(r.ConnectOpts{ + Address: "localhost:28015", + Database: "test", + MaxIdle: 10, + MaxOpen: 10, +}) +if err != nil { + log.Fatalln(err.Error()) +} + +session.SetMaxOpenConns(5) +``` + +A pre-configured [Pool](http://godoc.org/github.com/dancannon/gorethink#Pool) instance can also be passed to Connect(). + + +## Query Functions + +This library is based on the official drivers so the code on the [API](http://www.rethinkdb.com/api/) page should require very few changes to work. + +To view full documentation for the query functions check the [GoDoc](http://godoc.org/github.com/dancannon/gorethink#Term) + +Slice Expr Example +```go +r.Expr([]interface{}{1, 2, 3, 4, 5}).Run(session) +``` +Map Expr Example +```go +r.Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Run(session) +``` +Get Example +```go +r.Db("database").Table("table").Get("GUID").Run(session) +``` +Map Example (Func) +```go +r.Expr([]interface{}{1, 2, 3, 4, 5}).Map(func (row Term) interface{} { + return row.Add(1) +}).Run(session) +``` +Map Example (Implicit) +```go +r.Expr([]interface{}{1, 2, 3, 4, 5}).Map(r.Row.Add(1)).Run(session) +``` +Between (Optional Args) Example +```go +r.Db("database").Table("table").Between(1, 10, r.BetweenOpts{ + Index: "num", + RightBound: "closed", +}).Run(session) +``` + + +### Optional Arguments + +As shown above in the Between example optional arguments are passed to the function as a struct. Each function that has optional arguments as a related struct. This structs are named in the format FunctionNameOpts, for example BetweenOpts is the related struct for Between. + +## Results + +Different result types are returned depending on what function is used to execute the query. + +- `Run` returns a cursor which can be used to view all rows returned. +- `RunWrite` returns a WriteResponse and should be used for queries such as Insert, Update, etc... +- `Exec` sends a query to the server and closes the connection immediately after reading the response from the database. If you do not wish to wait for the response then you can set the `NoReply` flag. + +Example: + +```go +res, err := r.Db("database").Table("tablename").Get(key).Run(session) +if err != nil { + // error +} +``` + +Cursors have a number of methods available for accessing the query results + +- `Next` retrieves the next document from the result set, blocking if necessary. +- `All` retrieves all documents from the result set into the provided slice. +- `One` retrieves the first document from the result se. + +Examples: + +```go +var row interface{} +for res.Next(&row) { + // Do something with row +} +if res.Err() != nil { + // error +} +``` + +```go +var rows []interface{} +err := res.All(&rows) +if err != nil { + // error +} +``` + +```go +var row interface{} +err := res.One(&row) +if err == r.ErrEmptyResult { + // row not found +} +if err != nil { + // error +} +``` + +## Encoding/Decoding Structs +When passing structs to Expr(And functions that use Expr such as Insert, Update) the structs are encoded into a map before being sent to the server. Each exported field is added to the map unless + + - the field's tag is "-", or + - the field is empty and its tag specifies the "omitempty" option. + +Each fields default name in the map is the field name but can be specified in the struct field's tag value. The "gorethink" key in +the struct field's tag value is the key name, followed by an optional comma +and options. Examples: + +```go +// Field is ignored by this package. +Field int `gorethink:"-"` +// Field appears as key "myName". +Field int `gorethink:"myName"` +// Field appears as key "myName" and +// the field is omitted from the object if its value is empty, +// as defined above. +Field int `gorethink:"myName,omitempty"` +// Field appears as key "Field" (the default), but +// the field is skipped if empty. +// Note the leading comma. +Field int `gorethink:",omitempty"` +``` + +Alternatively you can implement the FieldMapper interface by providing the FieldMap function which returns a map of strings in the form of `"FieldName": "NewName"`. For example: + +```go +type A struct { + Field int +} + +func (a A) FieldMap() map[string]string { + return map[string]string{ + "Field": "myName", + } +} +``` + +## Examples + +View other examples on the [wiki](https://github.com/dancannon/gorethink/wiki/Examples). + +## License + +Copyright 2013 Daniel Cannon + +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. diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/buffer.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/buffer.go new file mode 100644 index 0000000..4884583 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/buffer.go @@ -0,0 +1,143 @@ +package gorethink + +import "io" + +const defaultBufSize = 4096 + +// A buffer which is used for both reading and writing. +// This is possible since communication on each connection is synchronous. +// In other words, we can't write and read simultaneously on the same connection. +// The buffer is similar to bufio.Reader / Writer but zero-copy-ish +// Also highly optimized for this particular use case. +type buffer struct { + buf []byte + rd io.Reader + idx int + length int +} + +func newBuffer(rd io.Reader) buffer { + var b [defaultBufSize]byte + return buffer{ + buf: b[:], + rd: rd, + } +} + +// fill reads into the buffer until at least _need_ bytes are in it +func (b *buffer) fill(need int) error { + n := b.length + + // move existing data to the beginning + if n > 0 && b.idx > 0 { + copy(b.buf[0:n], b.buf[b.idx:]) + } + + // grow buffer if necessary + // TODO: let the buffer shrink again at some point + // Maybe keep the org buf slice and swap back? + if need > len(b.buf) { + // Round up to the next multiple of the default size + newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) + copy(newBuf, b.buf) + b.buf = newBuf + } + + b.idx = 0 + + for { + nn, err := b.rd.Read(b.buf[n:]) + n += nn + + switch err { + case nil: + if n < need { + continue + } + b.length = n + return nil + + case io.EOF: + if n >= need { + b.length = n + return nil + } + return io.ErrUnexpectedEOF + + default: + return err + } + } +} + +// returns next N bytes from buffer. +// The returned slice is only guaranteed to be valid until the next read +func (b *buffer) readNext(need int) ([]byte, error) { + if b.length < need { + // refill + if err := b.fill(need); err != nil { + return nil, err + } + } + + offset := b.idx + b.idx += need + b.length -= need + return b.buf[offset:b.idx], nil +} + +// returns a buffer with the requested size. +// If possible, a slice from the existing buffer is returned. +// Otherwise a bigger buffer is made. +// Only one buffer (total) can be used at a time. +func (b *buffer) takeBuffer(length int) []byte { + if b.length > 0 { + return nil + } + + // test (cheap) general case first + if length <= defaultBufSize || length <= cap(b.buf) { + return b.buf[:length] + } + + return make([]byte, length) +} + +// shortcut which can be used if the requested buffer is guaranteed to be +// smaller than defaultBufSize +// Only one buffer (total) can be used at a time. +func (b *buffer) takeSmallBuffer(length int) []byte { + if b.length == 0 { + return b.buf[:length] + } + return nil +} + +// takeCompleteBuffer returns the complete existing buffer. +// This can be used if the necessary buffer size is unknown. +// Only one buffer (total) can be used at a time. +func (b *buffer) takeCompleteBuffer() []byte { + if b.length == 0 { + return b.buf + } + return nil +} + +var responseCache = make(chan *Response, 16) + +func newCachedResponse() *Response { + select { + case r := <-responseCache: + return r + default: + return new(Response) + } +} + +func putResponse(r *Response) { + *r = Response{} // zero it + select { + case responseCache <- r: + default: + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go new file mode 100644 index 0000000..076abd3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go @@ -0,0 +1,290 @@ +package gorethink + +import ( + "encoding/binary" + "encoding/json" + "io" + "net" + "sync/atomic" + "time" + + p "github.com/dancannon/gorethink/ql2" +) + +const ( + respHeaderLen = 12 +) + +type Response struct { + Token int64 + Type p.Response_ResponseType `json:"t"` + Responses []json.RawMessage `json:"r"` + Backtrace []interface{} `json:"b"` + Profile interface{} `json:"p"` +} + +// Connection is a connection to a rethinkdb database. Connection is not thread +// safe and should only be accessed be a single goroutine +type Connection struct { + conn net.Conn + opts *ConnectOpts + token int64 + cursors map[int64]*Cursor + bad bool + + headerBuf [respHeaderLen]byte + buf buffer +} + +// Dial closes the previous connection and attempts to connect again. +func NewConnection(opts *ConnectOpts) (*Connection, error) { + var err error + + // New mysqlConn + c := &Connection{ + opts: opts, + cursors: make(map[int64]*Cursor), + } + + // Connect to Server + nd := net.Dialer{Timeout: c.opts.Timeout} + c.conn, err = nd.Dial("tcp", c.opts.Address) + if err != nil { + return nil, err + } + + // Enable TCP Keepalives on TCP connections + if tc, ok := c.conn.(*net.TCPConn); ok { + if err := tc.SetKeepAlive(true); err != nil { + // Don't send COM_QUIT before handshake. + c.conn.Close() + c.conn = nil + return nil, err + } + } + + c.buf = newBuffer(c.conn) + + // Send handshake request + if err = c.writeHandshakeReq(); err != nil { + c.Close() + return nil, err + } + + // Read handshake response + err = c.readHandshakeSuccess() + if err != nil { + c.Close() + return nil, err + } + + return c, nil +} + +// Close closes the underlying net.Conn +func (c *Connection) Close() error { + if c.conn != nil { + c.conn.Close() + c.conn = nil + } + + c.cursors = nil + c.opts = nil + + return nil +} + +func (c *Connection) Query(q Query) (*Response, *Cursor, error) { + if c == nil { + return nil, nil, nil + } + if c.conn == nil { + c.bad = true + return nil, nil, nil + } + + // Add token if query is a START/NOREPLY_WAIT + if q.Type == p.Query_START || q.Type == p.Query_NOREPLY_WAIT { + q.Token = c.nextToken() + if c.opts.Database != "" { + q.Opts["db"] = Db(c.opts.Database).build() + } + } + + err := c.sendQuery(q) + if err != nil { + return nil, nil, err + } + + if noreply, ok := q.Opts["noreply"]; ok && noreply.(bool) { + return nil, nil, nil + } + + for { + response, err := c.readResponse() + if err != nil { + return nil, nil, err + } + + if response.Token == q.Token { + // If this was the requested response process and return + return c.processResponse(q, response) + } else if _, ok := c.cursors[response.Token]; ok { + // If the token is in the cursor cache then process the response + c.processResponse(q, response) + } else { + putResponse(response) + } + } +} + +func (c *Connection) sendQuery(q Query) error { + // Build query + b, err := json.Marshal(q.build()) + if err != nil { + return RqlDriverError{"Error building query"} + } + + // Set timeout + if c.opts.Timeout == 0 { + c.conn.SetDeadline(time.Time{}) + } else { + c.conn.SetDeadline(time.Now().Add(c.opts.Timeout)) + } + + // Send the JSON encoding of the query itself. + if err = c.writeQuery(q.Token, b); err != nil { + c.bad = true + return RqlConnectionError{err.Error()} + } + + return nil +} + +// getToken generates the next query token, used to number requests and match +// responses with requests. +func (c *Connection) nextToken() int64 { + return atomic.AddInt64(&c.token, 1) +} + +func (c *Connection) readResponse() (*Response, error) { + // Read response header (token+length) + _, err := io.ReadFull(c.conn, c.headerBuf[:respHeaderLen]) + if err != nil { + return nil, err + } + + responseToken := int64(binary.LittleEndian.Uint64(c.headerBuf[:8])) + messageLength := binary.LittleEndian.Uint32(c.headerBuf[8:]) + + // Read the JSON encoding of the Response itself. + b := c.buf.takeBuffer(int(messageLength)) + if _, err := io.ReadFull(c.conn, b[:]); err != nil { + c.bad = true + return nil, RqlConnectionError{err.Error()} + } + + // Decode the response + var response = newCachedResponse() + if err := json.Unmarshal(b, response); err != nil { + c.bad = true + return nil, RqlDriverError{err.Error()} + } + response.Token = responseToken + + return response, nil +} + +func (c *Connection) processResponse(q Query, response *Response) (*Response, *Cursor, error) { + switch response.Type { + case p.Response_CLIENT_ERROR: + return c.processErrorResponse(q, response, RqlClientError{rqlResponseError{response, q.Term}}) + case p.Response_COMPILE_ERROR: + return c.processErrorResponse(q, response, RqlCompileError{rqlResponseError{response, q.Term}}) + case p.Response_RUNTIME_ERROR: + return c.processErrorResponse(q, response, RqlRuntimeError{rqlResponseError{response, q.Term}}) + case p.Response_SUCCESS_ATOM: + return c.processAtomResponse(q, response) + case p.Response_SUCCESS_FEED, p.Response_SUCCESS_ATOM_FEED: + return c.processFeedResponse(q, response) + case p.Response_SUCCESS_PARTIAL: + return c.processPartialResponse(q, response) + case p.Response_SUCCESS_SEQUENCE: + return c.processSequenceResponse(q, response) + case p.Response_WAIT_COMPLETE: + return c.processWaitResponse(q, response) + default: + putResponse(response) + return nil, nil, RqlDriverError{"Unexpected response type"} + } +} + +func (c *Connection) processErrorResponse(q Query, response *Response, err error) (*Response, *Cursor, error) { + cursor := c.cursors[response.Token] + + delete(c.cursors, response.Token) + + return response, cursor, err +} + +func (c *Connection) processAtomResponse(q Query, response *Response) (*Response, *Cursor, error) { + // Create cursor + cursor := newCursor(c, response.Token, q.Term, q.Opts) + cursor.profile = response.Profile + + cursor.extend(response) + + return response, cursor, nil +} + +func (c *Connection) processFeedResponse(q Query, response *Response) (*Response, *Cursor, error) { + var cursor *Cursor + if _, ok := c.cursors[response.Token]; !ok { + // Create a new cursor if needed + cursor = newCursor(c, response.Token, q.Term, q.Opts) + cursor.profile = response.Profile + c.cursors[response.Token] = cursor + } else { + cursor = c.cursors[response.Token] + } + + cursor.extend(response) + + return response, cursor, nil +} + +func (c *Connection) processPartialResponse(q Query, response *Response) (*Response, *Cursor, error) { + cursor, ok := c.cursors[response.Token] + if !ok { + // Create a new cursor if needed + cursor = newCursor(c, response.Token, q.Term, q.Opts) + cursor.profile = response.Profile + + c.cursors[response.Token] = cursor + } + + cursor.extend(response) + + return response, cursor, nil +} + +func (c *Connection) processSequenceResponse(q Query, response *Response) (*Response, *Cursor, error) { + cursor, ok := c.cursors[response.Token] + if !ok { + // Create a new cursor if needed + cursor = newCursor(c, response.Token, q.Term, q.Opts) + cursor.profile = response.Profile + } + + delete(c.cursors, response.Token) + + cursor.extend(response) + + return response, cursor, nil +} + +func (c *Connection) processWaitResponse(q Query, response *Response) (*Response, *Cursor, error) { + delete(c.cursors, response.Token) + + return response, nil, nil +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go new file mode 100644 index 0000000..ec59c38 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go @@ -0,0 +1,91 @@ +package gorethink + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + + p "github.com/dancannon/gorethink/ql2" +) + +// Write 'data' to conn +func (c *Connection) writeData(data []byte) error { + _, err := c.conn.Write(data[:]) + if err != nil { + return RqlConnectionError{err.Error()} + } + + return nil +} + +func (c *Connection) writeHandshakeReq() error { + pos := 0 + dataLen := 4 + 4 + len(c.opts.AuthKey) + 4 + + data := c.buf.takeSmallBuffer(dataLen) + if data == nil { + return RqlDriverError{ErrBusyBuffer.Error()} + } + + // Send the protocol version to the server as a 4-byte little-endian-encoded integer + binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_V0_3)) + pos += 4 + + // Send the length of the auth key to the server as a 4-byte little-endian-encoded integer + binary.LittleEndian.PutUint32(data[pos:], uint32(len(c.opts.AuthKey))) + pos += 4 + + // Send the auth key as an ASCII string + if len(c.opts.AuthKey) > 0 { + pos += copy(data[pos:], c.opts.AuthKey) + } + + // Send the protocol type as a 4-byte little-endian-encoded integer + binary.LittleEndian.PutUint32(data[pos:], uint32(p.VersionDummy_JSON)) + pos += 4 + + return c.writeData(data) +} + +func (c *Connection) readHandshakeSuccess() error { + reader := bufio.NewReader(c.conn) + line, err := reader.ReadBytes('\x00') + if err != nil { + if err == io.EOF { + return fmt.Errorf("Unexpected EOF: %s", string(line)) + } + return RqlConnectionError{err.Error()} + } + // convert to string and remove trailing NUL byte + response := string(line[:len(line)-1]) + if response != "SUCCESS" { + // we failed authorization or something else terrible happened + return RqlDriverError{fmt.Sprintf("Server dropped connection with message: \"%s\"", response)} + } + + return nil +} + +func (c *Connection) writeQuery(token int64, q []byte) error { + pos := 0 + dataLen := 8 + 4 + len(q) + + data := c.buf.takeSmallBuffer(dataLen) + if data == nil { + return RqlDriverError{ErrBusyBuffer.Error()} + } + + // Send the protocol version to the server as a 4-byte little-endian-encoded integer + binary.LittleEndian.PutUint64(data[pos:], uint64(token)) + pos += 8 + + // Send the length of the auth key to the server as a 4-byte little-endian-encoded integer + binary.LittleEndian.PutUint32(data[pos:], uint32(len(q))) + pos += 4 + + // Send the auth key as an ASCII string + pos += copy(data[pos:], q) + + return c.writeData(data) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go new file mode 100644 index 0000000..f71018a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go @@ -0,0 +1,419 @@ +package gorethink + +import ( + "encoding/json" + "errors" + "reflect" + + "github.com/dancannon/gorethink/encoding" + p "github.com/dancannon/gorethink/ql2" +) + +var ( + errCursorClosed = errors.New("connection closed, cannot read cursor") +) + +func newCursor(conn *Connection, token int64, term *Term, opts map[string]interface{}) *Cursor { + cursor := &Cursor{ + conn: conn, + token: token, + term: term, + opts: opts, + } + + return cursor +} + +// Cursor is the result of a query. Its cursor starts before the first row +// of the result set. A Cursor is not thread safe and should only be accessed +// by a single goroutine at any given time. Use Next to advance through the +// rows: +// +// cursor, err := query.Run(session) +// ... +// defer cursor.Close() +// +// var response interface{} +// for cursor.Next(&response) { +// ... +// } +// err = cursor.Err() // get any error encountered during iteration +// ... +type Cursor struct { + pc *poolConn + releaseConn func(error) + + conn *Connection + token int64 + query Query + term *Term + opts map[string]interface{} + + lastErr error + fetching bool + closed bool + finished bool + isAtom bool + buffer queue + responses queue + profile interface{} +} + +// Profile returns the information returned from the query profiler. +func (c *Cursor) Profile() interface{} { + return c.profile +} + +// Err returns nil if no errors happened during iteration, or the actual +// error otherwise. +func (c *Cursor) Err() error { + return c.lastErr +} + +// Close closes the cursor, preventing further enumeration. If the end is +// encountered, the cursor is closed automatically. Close is idempotent. +func (c *Cursor) Close() error { + var err error + + if c.closed { + return nil + } + + conn := c.conn + if conn == nil { + return nil + } + if conn.conn == nil { + return nil + } + + // Stop any unfinished queries + if !c.closed && !c.finished { + q := Query{ + Type: p.Query_STOP, + Token: c.token, + } + + _, _, err = conn.Query(q) + } + + c.releaseConn(err) + + c.closed = true + c.conn = nil + c.buffer.elems = nil + c.responses.elems = nil + + return err +} + +// Next retrieves the next document from the result set, blocking if necessary. +// This method will also automatically retrieve another batch of documents from +// the server when the current one is exhausted, or before that in background +// if possible. +// +// Next returns true if a document was successfully unmarshalled onto result, +// and false at the end of the result set or if an error happened. +// When Next returns false, the Err method should be called to verify if +// there was an error during iteration. +// +// Also note that you are able to reuse the same variable multiple times as +// `Next` zeroes the value before scanning in the result. +func (c *Cursor) Next(dest interface{}) bool { + if c.closed { + return false + } + + hasMore, err := c.loadNext(dest) + if c.handleError(err) != nil { + c.Close() + return false + } + + return hasMore +} + +func (c *Cursor) loadNext(dest interface{}) (bool, error) { + for c.lastErr == nil { + // Check if response is closed/finished + if c.buffer.Len() == 0 && c.responses.Len() == 0 && c.closed { + + return false, errCursorClosed + } + + if c.buffer.Len() == 0 && c.responses.Len() == 0 && !c.finished { + + err := c.fetchMore() + if err != nil { + return false, err + } + } + + if c.buffer.Len() == 0 && c.responses.Len() == 0 && c.finished { + + return false, nil + } + + if c.buffer.Len() == 0 && c.responses.Len() > 0 { + if response, ok := c.responses.Pop().(json.RawMessage); ok { + var value interface{} + err := json.Unmarshal(response, &value) + if err != nil { + return false, err + } + + value, err = recursivelyConvertPseudotype(value, c.opts) + if err != nil { + return false, err + } + + // If response is an ATOM then try and convert to an array + if data, ok := value.([]interface{}); ok && c.isAtom { + for _, v := range data { + c.buffer.Push(v) + } + } else if value == nil { + c.buffer.Push(nil) + } else { + c.buffer.Push(value) + } + } + } + + if c.buffer.Len() > 0 { + data := c.buffer.Pop() + + err := encoding.Decode(dest, data) + if err != nil { + return false, err + } + + return true, nil + } + } + + return false, c.lastErr +} + +// All retrieves all documents from the result set into the provided slice +// and closes the cursor. +// +// The result argument must necessarily be the address for a slice. The slice +// may be nil or previously allocated. +// +// Also note that you are able to reuse the same variable multiple times as +// `All` zeroes the value before scanning in the result. It also attempts +// to reuse the existing slice without allocating any more space by either +// resizing or returning a selection of the slice if necessary. +func (c *Cursor) All(result interface{}) error { + resultv := reflect.ValueOf(result) + if resultv.Kind() != reflect.Ptr || resultv.Elem().Kind() != reflect.Slice { + panic("result argument must be a slice address") + } + slicev := resultv.Elem() + slicev = slicev.Slice(0, slicev.Cap()) + elemt := slicev.Type().Elem() + i := 0 + for { + if slicev.Len() == i { + elemp := reflect.New(elemt) + if !c.Next(elemp.Interface()) { + break + } + slicev = reflect.Append(slicev, elemp.Elem()) + slicev = slicev.Slice(0, slicev.Cap()) + } else { + if !c.Next(slicev.Index(i).Addr().Interface()) { + break + } + } + i++ + } + resultv.Elem().Set(slicev.Slice(0, i)) + + if err := c.Err(); err != nil { + c.Close() + return err + } + + if err := c.Close(); err != nil { + return err + } + + return nil +} + +// One retrieves a single document from the result set into the provided +// slice and closes the cursor. +// +// Also note that you are able to reuse the same variable multiple times as +// `One` zeroes the value before scanning in the result. +func (c *Cursor) One(result interface{}) error { + if c.IsNil() { + return ErrEmptyResult + } + + hasResult := c.Next(result) + + if err := c.Err(); err != nil { + c.Close() + return err + } + + if err := c.Close(); err != nil { + return err + } + + if !hasResult { + return ErrEmptyResult + } + + return nil +} + +// IsNil tests if the current row is nil. +func (c *Cursor) IsNil() bool { + if c.buffer.Len() > 0 { + bufferedItem := c.buffer.Peek() + if bufferedItem == nil { + return true + } + + if bufferedItem == nil { + return true + } + + return false + } + + if c.responses.Len() > 0 { + response := c.responses.Peek() + if response == nil { + return true + } + + if response, ok := response.(json.RawMessage); ok { + if string(response) == "null" { + return true + } + } + + return false + } + + return true +} + +// fetchMore fetches more rows from the database. +// +// If wait is true then it will wait for the database to reply otherwise it +// will return after sending the continue query. +func (c *Cursor) fetchMore() error { + var err error + if !c.fetching { + c.fetching = true + + if c.closed { + return errCursorClosed + } + + q := Query{ + Type: p.Query_CONTINUE, + Token: c.token, + } + + _, _, err = c.conn.Query(q) + c.handleError(err) + } + + return err +} + +// handleError sets the value of lastErr to err if lastErr is not yet set. +func (c *Cursor) handleError(err error) error { + return c.handleErrorLocked(err) +} + +// handleError sets the value of lastErr to err if lastErr is not yet set. +func (c *Cursor) handleErrorLocked(err error) error { + if c.lastErr == nil { + c.lastErr = err + } + + return c.lastErr +} + +// extend adds the result of a continue query to the cursor. +func (c *Cursor) extend(response *Response) { + for _, response := range response.Responses { + c.responses.Push(response) + } + + c.finished = response.Type != p.Response_SUCCESS_PARTIAL && + response.Type != p.Response_SUCCESS_FEED && + response.Type != p.Response_SUCCESS_ATOM_FEED + c.fetching = false + c.isAtom = response.Type == p.Response_SUCCESS_ATOM + + putResponse(response) +} + +// Queue structure used for storing responses + +type queue struct { + elems []interface{} + nelems, popi, pushi int +} + +func (q *queue) Len() int { + return q.nelems +} +func (q *queue) Push(elem interface{}) { + if q.nelems == len(q.elems) { + q.expand() + } + q.elems[q.pushi] = elem + q.nelems++ + q.pushi = (q.pushi + 1) % len(q.elems) +} +func (q *queue) Pop() (elem interface{}) { + if q.nelems == 0 { + return nil + } + elem = q.elems[q.popi] + q.elems[q.popi] = nil // Help GC. + q.nelems-- + q.popi = (q.popi + 1) % len(q.elems) + return elem +} +func (q *queue) Peek() (elem interface{}) { + if q.nelems == 0 { + return nil + } + return q.elems[q.popi] +} +func (q *queue) expand() { + curcap := len(q.elems) + var newcap int + if curcap == 0 { + newcap = 8 + } else if curcap < 1024 { + newcap = curcap * 2 + } else { + newcap = curcap + (curcap / 4) + } + elems := make([]interface{}, newcap) + if q.popi == 0 { + copy(elems, q.elems) + q.pushi = curcap + } else { + newpopi := newcap - (curcap - q.popi) + copy(elems, q.elems[:q.popi]) + copy(elems[newpopi:], q.elems[q.popi:]) + q.popi = newpopi + } + for i := range q.elems { + q.elems[i] = nil // Help GC. + } + q.elems = elems +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go new file mode 100644 index 0000000..42e4dc8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor_test.go @@ -0,0 +1,325 @@ +package gorethink + +import ( + "time" + + test "gopkg.in/check.v1" +) + +type object struct { + Id int64 `gorethink:"id,omitempty"` + Name string `gorethink:"name"` + Attrs []attr +} + +type attr struct { + Name string + Value interface{} +} + +func (s *RethinkSuite) TestCursorLiteral(c *test.C) { + res, err := Expr(5).Run(sess) + c.Assert(err, test.IsNil) + + var response interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, 5) +} + +func (s *RethinkSuite) TestCursorSlice(c *test.C) { + res, err := Expr([]interface{}{1, 2, 3, 4, 5}).Run(sess) + c.Assert(err, test.IsNil) + + var response []interface{} + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4, 5}) +} + +func (s *RethinkSuite) TestCursorPartiallyNilSlice(c *test.C) { + res, err := Expr(map[string]interface{}{ + "item": []interface{}{ + map[string]interface{}{"num": 1}, + nil, + }, + }).Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "item": []interface{}{ + map[string]interface{}{"num": 1}, + nil, + }, + }) +} + +func (s *RethinkSuite) TestCursorMap(c *test.C) { + res, err := Expr(map[string]interface{}{ + "id": 2, + "name": "Object 1", + }).Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "id": 2, + "name": "Object 1", + }) +} + +func (s *RethinkSuite) TestCursorMapIntoInterface(c *test.C) { + res, err := Expr(map[string]interface{}{ + "id": 2, + "name": "Object 1", + }).Run(sess) + c.Assert(err, test.IsNil) + + var response interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "id": 2, + "name": "Object 1", + }) +} + +func (s *RethinkSuite) TestCursorMapNested(c *test.C) { + res, err := Expr(map[string]interface{}{ + "id": 2, + "name": "Object 1", + "attr": []interface{}{map[string]interface{}{ + "name": "attr 1", + "value": "value 1", + }}, + }).Run(sess) + c.Assert(err, test.IsNil) + + var response interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "id": 2, + "name": "Object 1", + "attr": []interface{}{map[string]interface{}{ + "name": "attr 1", + "value": "value 1", + }}, + }) +} + +func (s *RethinkSuite) TestCursorStruct(c *test.C) { + res, err := Expr(map[string]interface{}{ + "id": 2, + "name": "Object 1", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }).Run(sess) + c.Assert(err, test.IsNil) + + var response object + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, object{ + Id: 2, + Name: "Object 1", + Attrs: []attr{attr{ + Name: "attr 1", + Value: "value 1", + }}, + }) +} + +func (s *RethinkSuite) TestCursorStructPseudoTypes(c *test.C) { + t := time.Now() + + res, err := Expr(map[string]interface{}{ + "T": time.Unix(t.Unix(), 0).In(time.UTC), + "B": []byte("hello"), + }).Run(sess) + c.Assert(err, test.IsNil) + + var response PseudoTypes + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response.T.Equal(time.Unix(t.Unix(), 0)), test.Equals, true) + c.Assert(response.B, jsonEquals, []byte("hello")) +} + +func (s *RethinkSuite) TestCursorAtomString(c *test.C) { + res, err := Expr("a").Run(sess) + c.Assert(err, test.IsNil) + + var response string + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "a") +} + +func (s *RethinkSuite) TestCursorAtomArray(c *test.C) { + res, err := Expr([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}).Run(sess) + c.Assert(err, test.IsNil) + + var response []int + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}) +} + +func (s *RethinkSuite) TestEmptyResults(c *test.C) { + DbCreate("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + res, err := Db("test").Table("test").Get("missing value").Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.IsNil(), test.Equals, true) + + res, err = Db("test").Table("test").Get("missing value").Run(sess) + c.Assert(err, test.IsNil) + var response interface{} + err = res.One(&response) + c.Assert(err, test.Equals, ErrEmptyResult) + c.Assert(res.IsNil(), test.Equals, true) + + res, err = Expr(nil).Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.IsNil(), test.Equals, true) + + res, err = Db("test").Table("test").Get("missing value").Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.IsNil(), test.Equals, true) + + res, err = Db("test").Table("test").GetAll("missing value", "another missing value").Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.Next(&response), test.Equals, false) + + var obj object + obj.Name = "missing value" + res, err = Db("test").Table("test").Filter(obj).Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.IsNil(), test.Equals, true) +} + +func (s *RethinkSuite) TestCursorAll(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableDrop("Table3").Exec(sess) + Db("test").TableCreate("Table3").Exec(sess) + Db("test").Table("Table3").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table3").Insert([]interface{}{ + map[string]interface{}{ + "id": 2, + "name": "Object 1", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }, + map[string]interface{}{ + "id": 3, + "name": "Object 2", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }, + }).Exec(sess) + + // Test query + query := Db("test").Table("Table3").OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response []object + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 2) + c.Assert(response, test.DeepEquals, []object{ + object{ + Id: 2, + Name: "Object 1", + Attrs: []attr{attr{ + Name: "attr 1", + Value: "value 1", + }}, + }, + object{ + Id: 3, + Name: "Object 2", + Attrs: []attr{attr{ + Name: "attr 1", + Value: "value 1", + }}, + }, + }) +} + +func (s *RethinkSuite) TestCursorReuseResult(c *test.C) { + // Test query + query := Expr([]interface{}{ + map[string]interface{}{ + "A": "a", + }, + map[string]interface{}{ + "B": 1, + }, + map[string]interface{}{ + "A": "a", + }, + map[string]interface{}{ + "B": 1, + }, + map[string]interface{}{ + "A": "a", + "B": 1, + }, + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var i int + var result SimpleT + for res.Next(&result) { + switch i { + case 0: + c.Assert(result, test.DeepEquals, SimpleT{ + A: "a", + B: 0, + }) + case 1: + c.Assert(result, test.DeepEquals, SimpleT{ + A: "", + B: 1, + }) + case 2: + c.Assert(result, test.DeepEquals, SimpleT{ + A: "a", + B: 0, + }) + case 3: + c.Assert(result, test.DeepEquals, SimpleT{ + A: "", + B: 1, + }) + case 4: + c.Assert(result, test.DeepEquals, SimpleT{ + A: "a", + B: 1, + }) + default: + c.Fatalf("Unexpected number of results") + } + + i++ + } + c.Assert(res.Err(), test.IsNil) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go new file mode 100644 index 0000000..c32c42a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go @@ -0,0 +1,6 @@ +// Go driver for RethinkDB +// +// Current version: v0.6.1 (RethinkDB v1.16) +// For more in depth information on how to use RethinkDB check out the API docs +// at http://rethinkdb.com/api +package gorethink diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/cache.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/cache.go new file mode 100644 index 0000000..feb28f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/cache.go @@ -0,0 +1,258 @@ +// This code is based on encoding/json and gorilla/schema + +package encoding + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string + nameBytes []byte // []byte(name) + equalFold func(s, t []byte) bool + + tag bool + index []int + typ reflect.Type + omitEmpty bool + quoted bool +} + +func fillField(f field) field { + f.nameBytes = []byte(f.name) + f.equalFold = foldFunc(f.nameBytes) + + return f +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that should be recognized for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + tag := getTag(sf) + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, fillField(field{ + name: name, + tag: tagged, + index: index, + typ: ft, + omitEmpty: opts.Contains("omitempty"), + })) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, fillField(field{name: ft.Name(), index: index, typ: ft})) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with valid tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// valid tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go new file mode 100644 index 0000000..76edc63 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go @@ -0,0 +1,136 @@ +package encoding + +import ( + "errors" + "reflect" + "runtime" + "sync" +) + +var byteSliceType = reflect.TypeOf([]byte(nil)) + +type decoderFunc func(dv reflect.Value, sv reflect.Value) + +// Decode decodes map[string]interface{} into a struct. The first parameter +// must be a pointer. +func Decode(dst interface{}, src interface{}) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + if v, ok := r.(string); ok { + err = errors.New(v) + } else { + err = r.(error) + } + } + }() + + dv := reflect.ValueOf(dst) + sv := reflect.ValueOf(src) + if dv.Kind() != reflect.Ptr { + return &DecodeTypeError{ + DestType: dv.Type(), + SrcType: sv.Type(), + Reason: "must be a pointer", + } + } + + dv = dv.Elem() + if !dv.CanAddr() { + return &DecodeTypeError{ + DestType: dv.Type(), + SrcType: sv.Type(), + Reason: "must be addressable", + } + } + + decode(dv, sv) + return nil +} + +// decode decodes the source value into the destination value +func decode(dv, sv reflect.Value) { + valueDecoder(dv, sv)(dv, sv) +} + +type decoderCacheKey struct { + dt, st reflect.Type +} + +var decoderCache struct { + sync.RWMutex + m map[decoderCacheKey]decoderFunc +} + +func valueDecoder(dv, sv reflect.Value) decoderFunc { + if !sv.IsValid() { + return invalidValueDecoder + } + + return typeDecoder(dv.Type(), sv.Type()) +} + +func typeDecoder(dt, st reflect.Type) decoderFunc { + decoderCache.RLock() + f := decoderCache.m[decoderCacheKey{dt, st}] + decoderCache.RUnlock() + if f != nil { + return f + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to be ready and then calls it. This indirect + // func is only used for recursive types. + decoderCache.Lock() + var wg sync.WaitGroup + wg.Add(1) + decoderCache.m[decoderCacheKey{dt, st}] = func(dv, sv reflect.Value) { + wg.Wait() + f(dv, sv) + } + decoderCache.Unlock() + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = newTypeDecoder(dt, st) + wg.Done() + decoderCache.Lock() + decoderCache.m[decoderCacheKey{dt, st}] = f + decoderCache.Unlock() + return f +} + +// indirect walks down v allocating pointers as needed, +// until it gets to a non-pointer. +func indirect(v reflect.Value, decodeNull bool) reflect.Value { + // If v is a named type and is addressable, + // start with its address, so that if the type has pointer methods, + // we find them. + if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { + v = v.Addr() + } + for { + // Load value from interface, but only if the result will be + // usefully addressable. + if v.Kind() == reflect.Interface && !v.IsNil() { + e := v.Elem() + if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodeNull || e.Elem().Kind() == reflect.Ptr) { + v = e + continue + } + } + + if v.Kind() != reflect.Ptr { + break + } + + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + return v +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go new file mode 100644 index 0000000..90f065f --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go @@ -0,0 +1,361 @@ +package encoding + +import ( + "bytes" + "encoding/json" + "fmt" + "image" + "reflect" + "testing" +) + +type T struct { + X string + Y int + Z int `gorethink:"-"` +} + +type U struct { + Alphabet string `gorethink:"alpha"` +} + +type V struct { + F1 interface{} + F2 int32 + F3 string +} + +type tx struct { + x int +} + +var txType = reflect.TypeOf((*tx)(nil)).Elem() + +// Test data structures for anonymous fields. + +type Point struct { + Z int +} + +type Top struct { + Level0 int + Embed0 + *Embed0a + *Embed0b `gorethink:"e,omitempty"` // treated as named + Embed0c `gorethink:"-"` // ignored + Loop + Embed0p // has Point with X, Y, used + Embed0q // has Point with Z, used +} + +type Embed0 struct { + Level1a int // overridden by Embed0a's Level1a with tag + Level1b int // used because Embed0a's Level1b is renamed + Level1c int // used because Embed0a's Level1c is ignored + Level1d int // annihilated by Embed0a's Level1d + Level1e int `gorethink:"x"` // annihilated by Embed0a.Level1e +} + +type Embed0a struct { + Level1a int `gorethink:"Level1a,omitempty"` + Level1b int `gorethink:"LEVEL1B,omitempty"` + Level1c int `gorethink:"-"` + Level1d int // annihilated by Embed0's Level1d + Level1f int `gorethink:"x"` // annihilated by Embed0's Level1e +} + +type Embed0b Embed0 + +type Embed0c Embed0 + +type Embed0p struct { + image.Point +} + +type Embed0q struct { + Point +} + +type Loop struct { + Loop1 int `gorethink:",omitempty"` + Loop2 int `gorethink:",omitempty"` + *Loop +} + +// From reflect test: +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// From reflect test: +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +type decodeTest struct { + in interface{} + ptr interface{} + out interface{} + err error +} + +type Ambig struct { + // Given "hello", the first match should win. + First int `gorethink:"HELLO"` + Second int `gorethink:"Hello"` +} + +var decodeTests = []decodeTest{ + // basic types + {in: true, ptr: new(bool), out: true}, + {in: 1, ptr: new(int), out: 1}, + {in: 1.2, ptr: new(float64), out: 1.2}, + {in: -5, ptr: new(int16), out: int16(-5)}, + {in: 2, ptr: new(string), out: string("2")}, + {in: float64(2.0), ptr: new(interface{}), out: float64(2.0)}, + {in: string("2"), ptr: new(interface{}), out: string("2")}, + {in: "a\u1234", ptr: new(string), out: "a\u1234"}, + {in: []interface{}{}, ptr: new([]string), out: []string{}}, + {in: map[string]interface{}{"X": []interface{}{1, 2, 3}, "Y": 4}, ptr: new(T), out: T{}, err: &DecodeTypeError{reflect.TypeOf([0]interface{}{}), reflect.TypeOf(""), ""}}, + {in: map[string]interface{}{"x": 1}, ptr: new(tx), out: tx{}}, + {in: map[string]interface{}{"F1": float64(1), "F2": 2, "F3": 3}, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: string("3")}}, + {in: map[string]interface{}{"F1": string("1"), "F2": 2, "F3": 3}, ptr: new(V), out: V{F1: string("1"), F2: int32(2), F3: string("3")}}, + { + in: map[string]interface{}{"k1": int64(1), "k2": "s", "k3": []interface{}{int64(1), 2.0, 3e-3}, "k4": map[string]interface{}{"kk1": "s", "kk2": int64(2)}}, + out: map[string]interface{}{"k1": int64(1), "k2": "s", "k3": []interface{}{int64(1), 2.0, 3e-3}, "k4": map[string]interface{}{"kk1": "s", "kk2": int64(2)}}, + ptr: new(interface{}), + }, + + // Z has a "-" tag. + {in: map[string]interface{}{"Y": 1, "Z": 2}, ptr: new(T), out: T{Y: 1}}, + + {in: map[string]interface{}{"alpha": "abc", "alphabet": "xyz"}, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: map[string]interface{}{"alpha": "abc"}, ptr: new(U), out: U{Alphabet: "abc"}}, + {in: map[string]interface{}{"alphabet": "xyz"}, ptr: new(U), out: U{}}, + + // array tests + {in: []interface{}{1, 2, 3}, ptr: new([3]int), out: [3]int{1, 2, 3}}, + {in: []interface{}{1, 2, 3}, ptr: new([1]int), out: [1]int{1}}, + {in: []interface{}{1, 2, 3}, ptr: new([5]int), out: [5]int{1, 2, 3, 0, 0}}, + + // empty array to interface test + {in: map[string]interface{}{"T": []interface{}{}}, ptr: new(map[string]interface{}), out: map[string]interface{}{"T": []interface{}{}}}, + + { + in: map[string]interface{}{ + "Level0": 1, + "Level1b": 2, + "Level1c": 3, + "level1d": 4, + "Level1a": 5, + "LEVEL1B": 6, + "e": map[string]interface{}{ + "Level1a": 8, + "Level1b": 9, + "Level1c": 10, + "Level1d": 11, + "x": 12, + }, + "Loop1": 13, + "Loop2": 14, + "X": 15, + "Y": 16, + "Z": 17, + }, + ptr: new(Top), + out: Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + }, + }, + { + in: map[string]interface{}{"hello": 1}, + ptr: new(Ambig), + out: Ambig{First: 1}, + }, + { + in: map[string]interface{}{"X": 1, "Y": 2}, + ptr: new(S5), + out: S5{S8: S8{S9: S9{Y: 2}}}, + }, + { + in: map[string]interface{}{"X": 1, "Y": 2}, + ptr: new(S10), + out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, + }, +} + +func TestDecode(t *testing.T) { + for i, tt := range decodeTests { + if tt.ptr == nil { + continue + } + + // v = new(right-type) + v := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + + err := Decode(v.Interface(), tt.in) + if !jsonEqual(err, tt.err) { + t.Errorf("#%d: got error %v want %v", i, err, tt.err) + continue + } + + if !jsonEqual(v.Elem().Interface(), tt.out) { + fmt.Printf("%#v\n", v.Elem().Interface()) + fmt.Printf("%#v\n", tt.out) + t.Errorf("#%d: mismatch\nhave: %+v\nwant: %+v", i, v.Elem().Interface(), tt.out) + continue + } + + // Check round trip. + if tt.err == nil { + enc, err := Encode(v.Interface()) + if err != nil { + t.Errorf("#%d: error re-marshaling: %v", i, err) + continue + } + vv := reflect.New(reflect.TypeOf(tt.ptr).Elem()) + + if err := Decode(vv.Interface(), enc); err != nil { + t.Errorf("#%d: error re-decodeing: %v", i, err) + continue + } + if !jsonEqual(v.Elem().Interface(), vv.Elem().Interface()) { + t.Errorf("#%d: mismatch\nhave: %#+v\nwant: %#+v", i, v.Elem().Interface(), vv.Elem().Interface()) + continue + } + } + } +} + +func TestStringKind(t *testing.T) { + type aMap map[string]int + + var m1, m2 map[string]int + m1 = map[string]int{ + "foo": 42, + } + + data, err := Encode(m1) + if err != nil { + t.Errorf("Unexpected error encoding: %v", err) + } + + err = Decode(&m2, data) + if err != nil { + t.Errorf("Unexpected error decoding: %v", err) + } + + if !jsonEqual(m1, m2) { + t.Error("Items should be equal after encoding and then decoding") + } + +} + +// Test handling of unexported fields that should be ignored. +type unexportedFields struct { + Name string + m map[string]interface{} `gorethink:"-"` + m2 map[string]interface{} `gorethink:"abcd"` +} + +func TestDecodeUnexported(t *testing.T) { + input := map[string]interface{}{ + "Name": "Bob", + "m": map[string]interface{}{ + "x": 123, + }, + "m2": map[string]interface{}{ + "y": 123, + }, + "abcd": map[string]interface{}{ + "z": 789, + }, + } + want := &unexportedFields{Name: "Bob"} + + out := &unexportedFields{} + err := Decode(out, input) + if err != nil { + t.Errorf("got error %v, expected nil", err) + } + if !jsonEqual(out, want) { + t.Errorf("got %q, want %q", out, want) + } +} + +type Foo struct { + FooBar interface{} `gorethink:"foobar"` +} +type Bar struct { + Baz int `gorethink:"baz"` +} + +func jsonEqual(a, b interface{}) bool { + ba, err := json.Marshal(a) + if err != nil { + panic(err) + } + bb, err := json.Marshal(b) + if err != nil { + panic(err) + } + + return bytes.Compare(ba, bb) == 0 +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go new file mode 100644 index 0000000..d49939e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go @@ -0,0 +1,520 @@ +package encoding + +import ( + "bytes" + "fmt" + "reflect" + "strconv" +) + +// newTypeDecoder constructs an decoderFunc for a type. +func newTypeDecoder(dt, st reflect.Type) decoderFunc { + if reflect.PtrTo(dt).Implements(unmarshalerType) || + dt.Implements(unmarshalerType) { + return unmarshalerDecoder + } + + if st.Kind() == reflect.Interface { + return interfaceAsTypeDecoder + } + + switch dt.Kind() { + case reflect.Bool: + switch st.Kind() { + case reflect.Bool: + return boolAsBoolDecoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intAsBoolDecoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintAsBoolDecoder + case reflect.Float32, reflect.Float64: + return floatAsBoolDecoder + case reflect.String: + return stringAsBoolDecoder + default: + return decodeTypeError + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + switch st.Kind() { + case reflect.Bool: + return boolAsIntDecoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intAsIntDecoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintAsIntDecoder + case reflect.Float32, reflect.Float64: + return floatAsIntDecoder + case reflect.String: + return stringAsIntDecoder + default: + return decodeTypeError + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + switch st.Kind() { + case reflect.Bool: + return boolAsUintDecoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intAsUintDecoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintAsUintDecoder + case reflect.Float32, reflect.Float64: + return floatAsUintDecoder + case reflect.String: + return stringAsUintDecoder + default: + return decodeTypeError + } + case reflect.Float32, reflect.Float64: + switch st.Kind() { + case reflect.Bool: + return boolAsFloatDecoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intAsFloatDecoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintAsFloatDecoder + case reflect.Float32, reflect.Float64: + return floatAsFloatDecoder + case reflect.String: + return stringAsFloatDecoder + default: + return decodeTypeError + } + case reflect.String: + switch st.Kind() { + case reflect.Bool: + return boolAsStringDecoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intAsStringDecoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintAsStringDecoder + case reflect.Float32, reflect.Float64: + return floatAsStringDecoder + case reflect.String: + return stringAsStringDecoder + default: + return decodeTypeError + } + case reflect.Interface: + if !st.AssignableTo(dt) { + return decodeTypeError + } + + return interfaceDecoder + case reflect.Ptr: + return newPtrDecoder(dt, st) + case reflect.Map: + if st.AssignableTo(dt) { + return interfaceDecoder + } + + switch st.Kind() { + case reflect.Map: + return newMapAsMapDecoder(dt, st) + default: + return decodeTypeError + } + case reflect.Struct: + if st.AssignableTo(dt) { + return interfaceDecoder + } + + switch st.Kind() { + case reflect.Map: + if kind := st.Key().Kind(); kind != reflect.String && kind != reflect.Interface { + return newDecodeTypeError(fmt.Errorf("map needs string keys")) + } + + return newMapAsStructDecoder(dt, st) + default: + return decodeTypeError + } + case reflect.Slice: + if st.AssignableTo(dt) { + return interfaceDecoder + } + + switch st.Kind() { + case reflect.Array, reflect.Slice: + return newSliceDecoder(dt, st) + default: + return decodeTypeError + } + case reflect.Array: + if st.AssignableTo(dt) { + return interfaceDecoder + } + + switch st.Kind() { + case reflect.Array, reflect.Slice: + return newArrayDecoder(dt, st) + default: + return decodeTypeError + } + default: + return unsupportedTypeDecoder + } +} + +func invalidValueDecoder(dv, sv reflect.Value) { + dv.Set(reflect.Zero(dv.Type())) +} + +func unsupportedTypeDecoder(dv, sv reflect.Value) { + panic(&UnsupportedTypeError{dv.Type()}) +} + +func decodeTypeError(dv, sv reflect.Value) { + panic(&DecodeTypeError{ + DestType: dv.Type(), + SrcType: sv.Type(), + }) +} + +func newDecodeTypeError(err error) decoderFunc { + return func(dv, sv reflect.Value) { + panic(&DecodeTypeError{ + DestType: dv.Type(), + SrcType: sv.Type(), + Reason: err.Error(), + }) + } +} + +func interfaceDecoder(dv, sv reflect.Value) { + dv.Set(sv) +} + +func interfaceAsTypeDecoder(dv, sv reflect.Value) { + decode(dv, sv.Elem()) +} + +type ptrDecoder struct { + elemDec decoderFunc +} + +func (d *ptrDecoder) decode(dv, sv reflect.Value) { + v := reflect.New(dv.Type().Elem()) + d.elemDec(v, sv) + dv.Set(v) +} + +func newPtrDecoder(dt, st reflect.Type) decoderFunc { + dec := &ptrDecoder{typeDecoder(dt.Elem(), st)} + + return dec.decode +} + +func unmarshalerDecoder(dv, sv reflect.Value) { + // modeled off of https://golang.org/src/encoding/json/decode.go?#L325 + if dv.Kind() != reflect.Ptr && dv.Type().Name() != "" && dv.CanAddr() { + dv = dv.Addr() + } + + u := dv.Interface().(Unmarshaler) + err := u.UnmarshalRQL(sv.Interface()) + if err != nil { + panic(&DecodeTypeError{dv.Type(), sv.Type(), err.Error()}) + } +} + +// Boolean decoders + +func boolAsBoolDecoder(dv, sv reflect.Value) { + dv.SetBool(sv.Bool()) +} +func boolAsIntDecoder(dv, sv reflect.Value) { + if sv.Bool() { + dv.SetInt(1) + } else { + dv.SetInt(0) + } +} +func boolAsUintDecoder(dv, sv reflect.Value) { + if sv.Bool() { + dv.SetUint(1) + } else { + dv.SetUint(0) + } +} +func boolAsFloatDecoder(dv, sv reflect.Value) { + if sv.Bool() { + dv.SetFloat(1) + } else { + dv.SetFloat(0) + } +} +func boolAsStringDecoder(dv, sv reflect.Value) { + if sv.Bool() { + dv.SetString("1") + } else { + dv.SetString("0") + } +} + +// Int decoders + +func intAsBoolDecoder(dv, sv reflect.Value) { + dv.SetBool(sv.Int() != 0) +} +func intAsIntDecoder(dv, sv reflect.Value) { + dv.SetInt(sv.Int()) +} +func intAsUintDecoder(dv, sv reflect.Value) { + dv.SetUint(uint64(sv.Int())) +} +func intAsFloatDecoder(dv, sv reflect.Value) { + dv.SetFloat(float64(sv.Int())) +} +func intAsStringDecoder(dv, sv reflect.Value) { + dv.SetString(strconv.FormatInt(sv.Int(), 10)) +} + +// Uint decoders + +func uintAsBoolDecoder(dv, sv reflect.Value) { + dv.SetBool(sv.Uint() != 0) +} +func uintAsIntDecoder(dv, sv reflect.Value) { + dv.SetInt(int64(sv.Uint())) +} +func uintAsUintDecoder(dv, sv reflect.Value) { + dv.SetUint(sv.Uint()) +} +func uintAsFloatDecoder(dv, sv reflect.Value) { + dv.SetFloat(float64(sv.Uint())) +} +func uintAsStringDecoder(dv, sv reflect.Value) { + dv.SetString(strconv.FormatUint(sv.Uint(), 10)) +} + +// Float decoders + +func floatAsBoolDecoder(dv, sv reflect.Value) { + dv.SetBool(sv.Float() != 0) +} +func floatAsIntDecoder(dv, sv reflect.Value) { + dv.SetInt(int64(sv.Float())) +} +func floatAsUintDecoder(dv, sv reflect.Value) { + dv.SetUint(uint64(sv.Float())) +} +func floatAsFloatDecoder(dv, sv reflect.Value) { + dv.SetFloat(float64(sv.Float())) +} +func floatAsStringDecoder(dv, sv reflect.Value) { + dv.SetString(strconv.FormatFloat(sv.Float(), 'f', -1, 64)) +} + +// String decoders + +func stringAsBoolDecoder(dv, sv reflect.Value) { + b, err := strconv.ParseBool(sv.String()) + if err == nil { + dv.SetBool(b) + } else if sv.String() == "" { + dv.SetBool(false) + } else { + panic(&DecodeTypeError{dv.Type(), sv.Type(), err.Error()}) + } +} +func stringAsIntDecoder(dv, sv reflect.Value) { + i, err := strconv.ParseInt(sv.String(), 0, dv.Type().Bits()) + if err == nil { + dv.SetInt(i) + } else { + panic(&DecodeTypeError{dv.Type(), sv.Type(), err.Error()}) + } +} +func stringAsUintDecoder(dv, sv reflect.Value) { + i, err := strconv.ParseUint(sv.String(), 0, dv.Type().Bits()) + if err == nil { + dv.SetUint(i) + } else { + panic(&DecodeTypeError{dv.Type(), sv.Type(), err.Error()}) + } +} +func stringAsFloatDecoder(dv, sv reflect.Value) { + f, err := strconv.ParseFloat(sv.String(), dv.Type().Bits()) + if err == nil { + dv.SetFloat(f) + } else { + panic(&DecodeTypeError{dv.Type(), sv.Type(), err.Error()}) + } +} +func stringAsStringDecoder(dv, sv reflect.Value) { + dv.SetString(sv.String()) +} + +// Slice/Array decoder + +type sliceDecoder struct { + arrayDec decoderFunc +} + +func (d *sliceDecoder) decode(dv, sv reflect.Value) { + if dv.Kind() == reflect.Slice { + dv.Set(reflect.MakeSlice(dv.Type(), dv.Len(), dv.Cap())) + } + + if !sv.IsNil() { + d.arrayDec(dv, sv) + } +} + +func newSliceDecoder(dt, st reflect.Type) decoderFunc { + // Byte slices get special treatment; arrays don't. + // if t.Elem().Kind() == reflect.Uint8 { + // return decodeByteSlice + // } + dec := &sliceDecoder{newArrayDecoder(dt, st)} + return dec.decode +} + +type arrayDecoder struct { + elemDec decoderFunc +} + +func (d *arrayDecoder) decode(dv, sv reflect.Value) { + // Iterate through the slice/array and decode each element before adding it + // to the dest slice/array + i := 0 + for i < sv.Len() { + if dv.Kind() == reflect.Slice { + // Get element of array, growing if necessary. + if i >= dv.Cap() { + newcap := dv.Cap() + dv.Cap()/2 + if newcap < 4 { + newcap = 4 + } + newdv := reflect.MakeSlice(dv.Type(), dv.Len(), newcap) + reflect.Copy(newdv, dv) + dv.Set(newdv) + } + if i >= dv.Len() { + dv.SetLen(i + 1) + } + } + + if i < dv.Len() { + // Decode into element. + d.elemDec(dv.Index(i), sv.Index(i)) + } + + i++ + } + + // Ensure that the destination is the correct size + if i < dv.Len() { + if dv.Kind() == reflect.Array { + // Array. Zero the rest. + z := reflect.Zero(dv.Type().Elem()) + for ; i < dv.Len(); i++ { + dv.Index(i).Set(z) + } + } else { + dv.SetLen(i) + } + } +} + +func newArrayDecoder(dt, st reflect.Type) decoderFunc { + dec := &arrayDecoder{typeDecoder(dt.Elem(), st.Elem())} + return dec.decode +} + +// Map decoder + +type mapAsMapDecoder struct { + keyDec, elemDec decoderFunc +} + +func (d *mapAsMapDecoder) decode(dv, sv reflect.Value) { + dt := dv.Type() + dv.Set(reflect.MakeMap(reflect.MapOf(dt.Key(), dt.Elem()))) + + var mapKey reflect.Value + var mapElem reflect.Value + + keyType := dv.Type().Key() + elemType := dv.Type().Elem() + + for _, sElemKey := range sv.MapKeys() { + var dElemKey reflect.Value + var dElemVal reflect.Value + + if !mapKey.IsValid() { + mapKey = reflect.New(keyType).Elem() + } else { + mapKey.Set(reflect.Zero(keyType)) + } + dElemKey = mapKey + + if !mapElem.IsValid() { + mapElem = reflect.New(elemType).Elem() + } else { + mapElem.Set(reflect.Zero(elemType)) + } + dElemVal = mapElem + + d.keyDec(dElemKey, sElemKey) + d.elemDec(dElemVal, sv.MapIndex(sElemKey)) + + dv.SetMapIndex(dElemKey, dElemVal) + } +} + +func newMapAsMapDecoder(dt, st reflect.Type) decoderFunc { + d := &mapAsMapDecoder{typeDecoder(dt.Key(), st.Key()), typeDecoder(dt.Elem(), st.Elem())} + return d.decode +} + +type mapAsStructDecoder struct { + fields []field + fieldDecs []decoderFunc +} + +func (d *mapAsStructDecoder) decode(dv, sv reflect.Value) { + for _, kv := range sv.MapKeys() { + var f *field + var fieldDec decoderFunc + key := []byte(kv.String()) + for i := range d.fields { + ff := &d.fields[i] + ffd := d.fieldDecs[i] + if bytes.Equal(ff.nameBytes, key) { + f = ff + fieldDec = ffd + break + } + if f == nil && ff.equalFold(ff.nameBytes, key) { + f = ff + fieldDec = ffd + } + } + + if f == nil { + continue + } + + dElemVal := fieldByIndex(dv, f.index) + sElemVal := sv.MapIndex(kv) + + if !sElemVal.IsValid() || !dElemVal.CanSet() { + continue + } + + fieldDec(dElemVal, sElemVal) + } +} + +func newMapAsStructDecoder(dt, st reflect.Type) decoderFunc { + fields := cachedTypeFields(dt) + se := &mapAsStructDecoder{ + fields: fields, + fieldDecs: make([]decoderFunc, len(fields)), + } + for i, f := range fields { + se.fieldDecs[i] = typeDecoder(typeByIndex(dt, f.index), st.Elem()) + } + return se.decode +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder.go new file mode 100644 index 0000000..3b0d350 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder.go @@ -0,0 +1,89 @@ +// This code is based on encoding/json and gorilla/schema + +package encoding + +import ( + "errors" + "reflect" + "runtime" + "sync" +) + +type encoderFunc func(v reflect.Value) interface{} + +// Encode returns the encoded value of v. +// +// Encode traverses the value v recursively and looks for structs. If a struct +// is found then it is checked for tagged fields and convert to +// map[string]interface{} +func Encode(v interface{}) (ev interface{}, err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + if v, ok := r.(string); ok { + err = errors.New(v) + } else { + err = r.(error) + } + } + }() + + return encode(reflect.ValueOf(v)), nil +} + +func encode(v reflect.Value) interface{} { + return valueEncoder(v)(v) +} + +var encoderCache struct { + sync.RWMutex + m map[reflect.Type]encoderFunc +} + +func valueEncoder(v reflect.Value) encoderFunc { + if !v.IsValid() { + return invalidValueEncoder + } + return typeEncoder(v.Type()) +} + +func typeEncoder(t reflect.Type) encoderFunc { + encoderCache.RLock() + f := encoderCache.m[t] + encoderCache.RUnlock() + if f != nil { + return f + } + + // To deal with recursive types, populate the map with an + // indirect func before we build it. This type waits on the + // real func (f) to + // be ready and then calls it. This indirect + // func is only used for recursive types. + encoderCache.Lock() + var wg sync.WaitGroup + wg.Add(1) + encoderCache.m[t] = func(v reflect.Value) interface{} { + wg.Wait() + return f(v) + } + encoderCache.Unlock() + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = newTypeEncoder(t, true) + wg.Done() + encoderCache.Lock() + encoderCache.m[t] = f + encoderCache.Unlock() + return f +} + +// IgnoreType causes the encoder to ignore a type when encoding +func IgnoreType(t reflect.Type) { + encoderCache.Lock() + encoderCache.m[t] = doNothingEncoder + encoderCache.Unlock() +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go new file mode 100644 index 0000000..dd1a626 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go @@ -0,0 +1,224 @@ +package encoding + +import ( + "image" + "reflect" + "testing" +) + +var encodeExpected = map[string]interface{}{ + "Level0": int64(1), + "Level1b": int64(2), + "Level1c": int64(3), + "Level1a": int64(5), + "LEVEL1B": int64(6), + "e": map[string]interface{}{ + "Level1a": int64(8), + "Level1b": int64(9), + "Level1c": int64(10), + "Level1d": int64(11), + "x": int64(12), + }, + "Loop1": int64(13), + "Loop2": int64(14), + "X": int64(15), + "Y": int64(16), + "Z": int64(17), +} + +func TestEncode(t *testing.T) { + // Top is defined in decoder_test.go + var in = Top{ + Level0: 1, + Embed0: Embed0{ + Level1b: 2, + Level1c: 3, + }, + Embed0a: &Embed0a{ + Level1a: 5, + Level1b: 6, + }, + Embed0b: &Embed0b{ + Level1a: 8, + Level1b: 9, + Level1c: 10, + Level1d: 11, + Level1e: 12, + }, + Loop: Loop{ + Loop1: 13, + Loop2: 14, + }, + Embed0p: Embed0p{ + Point: image.Point{X: 15, Y: 16}, + }, + Embed0q: Embed0q{ + Point: Point{Z: 17}, + }, + } + + got, err := Encode(&in) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, encodeExpected) { + t.Errorf(" got: %v\nwant: %v\n", got, encodeExpected) + } +} + +type Optionals struct { + Sr string `gorethink:"sr"` + So string `gorethink:"so,omitempty"` + Sw string `gorethink:"-"` + + Ir int `gorethink:"omitempty"` // actually named omitempty, not an option + Io int `gorethink:"io,omitempty"` + + Slr []string `gorethink:"slr"` + Slo []string `gorethink:"slo,omitempty"` + + Mr map[string]interface{} `gorethink:"mr"` + Mo map[string]interface{} `gorethink:",omitempty"` +} + +var optionalsExpected = map[string]interface{}{ + "sr": "", + "omitempty": int64(0), + "slr": []interface{}{}, + "mr": map[string]interface{}{}, +} + +func TestOmitEmpty(t *testing.T) { + var o Optionals + o.Sw = "something" + o.Mr = map[string]interface{}{} + o.Mo = map[string]interface{}{} + + got, err := Encode(&o) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, optionalsExpected) { + t.Errorf(" got: %v\nwant: %v\n", got, optionalsExpected) + } +} + +type IntType int + +type MyStruct struct { + IntType +} + +func TestAnonymousNonstruct(t *testing.T) { + var i IntType = 11 + a := MyStruct{i} + var want = map[string]interface{}{"IntType": int64(11)} + + got, err := Encode(a) + if err != nil { + t.Fatalf("Encode: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } +} + +type BugA struct { + S string +} + +type BugB struct { + BugA + S string +} + +type BugC struct { + S string +} + +// Legal Go: We never use the repeated embedded field (S). +type BugX struct { + A int + BugA + BugB +} + +// Issue 5245. +func TestEmbeddedBug(t *testing.T) { + v := BugB{ + BugA{"A"}, + "B", + } + got, err := Encode(v) + if err != nil { + t.Fatal("Encode:", err) + } + want := map[string]interface{}{"S": "B"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("Encode: got %v want %v", got, want) + } + // Now check that the duplicate field, S, does not appear. + x := BugX{ + A: 23, + } + got, err = Encode(x) + if err != nil { + t.Fatal("Encode:", err) + } + want = map[string]interface{}{"A": int64(23)} + if !reflect.DeepEqual(got, want) { + t.Fatalf("Encode: got %v want %v", got, want) + } +} + +type BugD struct { // Same as BugA after tagging. + XXX string `gorethink:"S"` +} + +// BugD's tagged S field should dominate BugA's. +type BugY struct { + BugA + BugD +} + +// Test that a field with a tag dominates untagged fields. +func TestTaggedFieldDominates(t *testing.T) { + v := BugY{ + BugA{"BugA"}, + BugD{"BugD"}, + } + got, err := Encode(v) + if err != nil { + t.Fatal("Encode:", err) + } + want := map[string]interface{}{"S": "BugD"} + if !reflect.DeepEqual(got, want) { + t.Fatalf("Encode: got %v want %v", got, want) + } +} + +// There are no tags here, so S should not appear. +type BugZ struct { + BugA + BugC + BugY // Contains a tagged S field through BugD; should not dominate. +} + +func TestDuplicatedFieldDisappears(t *testing.T) { + v := BugZ{ + BugA{"BugA"}, + BugC{"BugC"}, + BugY{ + BugA{"nested BugA"}, + BugD{"nested BugD"}, + }, + } + got, err := Encode(v) + if err != nil { + t.Fatal("Encode:", err) + } + want := map[string]interface{}{} + if !reflect.DeepEqual(got, want) { + t.Fatalf("Encode: got %v want %v", got, want) + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go new file mode 100644 index 0000000..f32930b --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_types.go @@ -0,0 +1,286 @@ +package encoding + +import ( + "encoding/base64" + "reflect" + "time" +) + +// newTypeEncoder constructs an encoderFunc for a type. +// The returned encoder only checks CanAddr when allowAddr is true. +func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc { + if t.Implements(marshalerType) { + return marshalerEncoder + } + if t.Kind() != reflect.Ptr && allowAddr { + if reflect.PtrTo(t).Implements(marshalerType) { + return newCondAddrEncoder(addrMarshalerEncoder, newTypeEncoder(t, false)) + } + } + + // Check for psuedo-types first + switch t { + case timeType: + return timePseudoTypeEncoder + } + + switch t.Kind() { + case reflect.Bool: + return boolEncoder + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intEncoder + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintEncoder + case reflect.Float32, reflect.Float64: + return floatEncoder + case reflect.String: + return stringEncoder + case reflect.Interface: + return interfaceEncoder + case reflect.Struct: + return newStructEncoder(t) + case reflect.Map: + return newMapEncoder(t) + case reflect.Slice: + return newSliceEncoder(t) + case reflect.Array: + return newArrayEncoder(t) + case reflect.Ptr: + return newPtrEncoder(t) + default: + return unsupportedTypeEncoder + } +} + +func invalidValueEncoder(v reflect.Value) interface{} { + return nil +} + +func doNothingEncoder(v reflect.Value) interface{} { + return v.Interface() +} + +func marshalerEncoder(v reflect.Value) interface{} { + if v.Kind() == reflect.Ptr && v.IsNil() { + return nil + } + m := v.Interface().(Marshaler) + ev, err := m.MarshalRQL() + if err != nil { + panic(&MarshalerError{v.Type(), err}) + } + + return ev +} + +func addrMarshalerEncoder(v reflect.Value) interface{} { + va := v.Addr() + if va.IsNil() { + return nil + } + m := va.Interface().(Marshaler) + ev, err := m.MarshalRQL() + if err != nil { + panic(&MarshalerError{v.Type(), err}) + } + + return ev +} + +func boolEncoder(v reflect.Value) interface{} { + if v.Bool() { + return true + } else { + return false + } +} + +func intEncoder(v reflect.Value) interface{} { + return v.Int() +} + +func uintEncoder(v reflect.Value) interface{} { + return v.Uint() +} + +func floatEncoder(v reflect.Value) interface{} { + return v.Float() +} + +func stringEncoder(v reflect.Value) interface{} { + return v.String() +} + +func interfaceEncoder(v reflect.Value) interface{} { + if v.IsNil() { + return nil + } + return encode(v.Elem()) +} + +func unsupportedTypeEncoder(v reflect.Value) interface{} { + panic(&UnsupportedTypeError{v.Type()}) +} + +type structEncoder struct { + fields []field + fieldEncs []encoderFunc +} + +func (se *structEncoder) encode(v reflect.Value) interface{} { + m := make(map[string]interface{}) + + for i, f := range se.fields { + fv := fieldByIndex(v, f.index) + if !fv.IsValid() || f.omitEmpty && isEmptyValue(fv) { + continue + } + + m[f.name] = se.fieldEncs[i](fv) + } + + return m +} + +func newStructEncoder(t reflect.Type) encoderFunc { + fields := cachedTypeFields(t) + se := &structEncoder{ + fields: fields, + fieldEncs: make([]encoderFunc, len(fields)), + } + for i, f := range fields { + se.fieldEncs[i] = typeEncoder(typeByIndex(t, f.index)) + } + return se.encode +} + +type mapEncoder struct { + elemEnc encoderFunc +} + +func (me *mapEncoder) encode(v reflect.Value) interface{} { + if v.IsNil() { + return nil + } + + m := make(map[string]interface{}) + + for _, k := range v.MapKeys() { + m[k.String()] = me.elemEnc(v.MapIndex(k)) + } + + return m +} + +func newMapEncoder(t reflect.Type) encoderFunc { + if t.Key().Kind() != reflect.String { + return unsupportedTypeEncoder + } + me := &mapEncoder{typeEncoder(t.Elem())} + return me.encode +} + +// sliceEncoder just wraps an arrayEncoder, checking to make sure the value isn't nil. +type sliceEncoder struct { + arrayEnc encoderFunc +} + +func (se *sliceEncoder) encode(v reflect.Value) interface{} { + if v.IsNil() { + return []interface{}{} + } + return se.arrayEnc(v) +} + +func newSliceEncoder(t reflect.Type) encoderFunc { + // Byte slices get special treatment; arrays don't. + if t.Elem().Kind() == reflect.Uint8 { + return encodeByteSlice + } + enc := &sliceEncoder{newArrayEncoder(t)} + return enc.encode +} + +type arrayEncoder struct { + elemEnc encoderFunc +} + +func (ae *arrayEncoder) encode(v reflect.Value) interface{} { + n := v.Len() + + a := make([]interface{}, n) + for i := 0; i < n; i++ { + a[i] = ae.elemEnc(v.Index(i)) + } + + return a +} + +func newArrayEncoder(t reflect.Type) encoderFunc { + enc := &arrayEncoder{typeEncoder(t.Elem())} + return enc.encode +} + +type ptrEncoder struct { + elemEnc encoderFunc +} + +func (pe *ptrEncoder) encode(v reflect.Value) interface{} { + if v.IsNil() { + return nil + } + return pe.elemEnc(v.Elem()) +} + +func newPtrEncoder(t reflect.Type) encoderFunc { + enc := &ptrEncoder{typeEncoder(t.Elem())} + return enc.encode +} + +type condAddrEncoder struct { + canAddrEnc, elseEnc encoderFunc +} + +func (ce *condAddrEncoder) encode(v reflect.Value) interface{} { + if v.CanAddr() { + return ce.canAddrEnc(v) + } else { + return ce.elseEnc(v) + } +} + +// newCondAddrEncoder returns an encoder that checks whether its value +// CanAddr and delegates to canAddrEnc if so, else to elseEnc. +func newCondAddrEncoder(canAddrEnc, elseEnc encoderFunc) encoderFunc { + enc := &condAddrEncoder{canAddrEnc: canAddrEnc, elseEnc: elseEnc} + return enc.encode +} + +// Pseudo-type encoders + +// Encode a time.Time value to the TIME RQL type +func timePseudoTypeEncoder(v reflect.Value) interface{} { + t := v.Interface().(time.Time) + + return map[string]interface{}{ + "$reql_type$": "TIME", + "epoch_time": float64(t.UnixNano())/1000/1000/1000, //milliseconds + "timezone": "+00:00", + } +} + +// Encode a byte slice to the BINARY RQL type +func encodeByteSlice(v reflect.Value) interface{} { + var b []byte + if !v.IsNil() { + b = v.Bytes() + } + + dst := make([]byte, base64.StdEncoding.EncodedLen(len(b))) + base64.StdEncoding.Encode(dst, b) + + return map[string]interface{}{ + "$reql_type$": "BINARY", + "data": string(dst), + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go new file mode 100644 index 0000000..caa8fde --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoding.go @@ -0,0 +1,32 @@ +package encoding + +import ( + "reflect" + "time" +) + +var ( + // type constants + stringType = reflect.TypeOf("") + timeType = reflect.TypeOf(new(time.Time)).Elem() + + marshalerType = reflect.TypeOf(new(Marshaler)).Elem() + unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem() +) + +// Marshaler is the interface implemented by objects that +// can marshal themselves into a valid RQL psuedo-type. +type Marshaler interface { + MarshalRQL() (interface{}, error) +} + +// Unmarshaler is the interface implemented by objects +// that can unmarshal a psuedo-type object of themselves. +type Unmarshaler interface { + UnmarshalRQL(interface{}) error +} + +func init() { + encoderCache.m = make(map[reflect.Type]encoderFunc) + decoderCache.m = make(map[decoderCacheKey]decoderFunc) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/errors.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/errors.go new file mode 100644 index 0000000..8b9ac2c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/errors.go @@ -0,0 +1,102 @@ +package encoding + +import ( + "fmt" + "reflect" + "strings" +) + +type MarshalerError struct { + Type reflect.Type + Err error +} + +func (e *MarshalerError) Error() string { + return "gorethink: error calling MarshalRQL for type " + e.Type.String() + ": " + e.Err.Error() +} + +type InvalidUnmarshalError struct { + Type reflect.Type +} + +func (e *InvalidUnmarshalError) Error() string { + if e.Type == nil { + return "gorethink: UnmarshalRQL(nil)" + } + + if e.Type.Kind() != reflect.Ptr { + return "gorethink: UnmarshalRQL(non-pointer " + e.Type.String() + ")" + } + return "gorethink: UnmarshalRQL(nil " + e.Type.String() + ")" +} + +// An InvalidTypeError describes a value that was +// not appropriate for a value of a specific Go type. +type DecodeTypeError struct { + DestType, SrcType reflect.Type + Reason string +} + +func (e *DecodeTypeError) Error() string { + if e.Reason != "" { + return "gorethink: could not decode type " + e.SrcType.String() + " into Go value of type " + e.DestType.String() + ": " + e.Reason + } else { + return "gorethink: could not decode type " + e.SrcType.String() + " into Go value of type " + e.DestType.String() + + } +} + +// An UnsupportedTypeError is returned by Marshal when attempting +// to encode an unsupported value type. +type UnsupportedTypeError struct { + Type reflect.Type +} + +func (e *UnsupportedTypeError) Error() string { + return "gorethink: unsupported type: " + e.Type.String() +} + +// An UnsupportedTypeError is returned by Marshal when attempting +// to encode an unexpected value type. +type UnexpectedTypeError struct { + DestType, SrcType reflect.Type +} + +func (e *UnexpectedTypeError) Error() string { + return "gorethink: expected type: " + e.DestType.String() + ", got " + e.SrcType.String() +} + +type UnsupportedValueError struct { + Value reflect.Value + Str string +} + +func (e *UnsupportedValueError) Error() string { + return "gorethink: unsupported value: " + e.Str +} + +// Error implements the error interface and can represents multiple +// errors that occur in the course of a single decode. +type Error struct { + Errors []string +} + +func (e *Error) Error() string { + points := make([]string, len(e.Errors)) + for i, err := range e.Errors { + points[i] = fmt.Sprintf("* %s", err) + } + + return fmt.Sprintf( + "%d error(s) decoding:\n\n%s", + len(e.Errors), strings.Join(points, "\n")) +} + +func appendErrors(errors []string, err error) []string { + switch e := err.(type) { + case *Error: + return append(errors, e.Errors...) + default: + return append(errors, e.Error()) + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/fold.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/fold.go new file mode 100644 index 0000000..21c9e68 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/fold.go @@ -0,0 +1,139 @@ +package encoding + +import ( + "bytes" + "unicode/utf8" +) + +const ( + caseMask = ^byte(0x20) // Mask to ignore case in ASCII. + kelvin = '\u212a' + smallLongEss = '\u017f' +) + +// foldFunc returns one of four different case folding equivalence +// functions, from most general (and slow) to fastest: +// +// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8 +// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S') +// 3) asciiEqualFold, no special, but includes non-letters (including _) +// 4) simpleLetterEqualFold, no specials, no non-letters. +// +// The letters S and K are special because they map to 3 runes, not just 2: +// * S maps to s and to U+017F 'ſ' Latin small letter long s +// * k maps to K and to U+212A 'K' Kelvin sign +// See http://play.golang.org/p/tTxjOc0OGo +// +// The returned function is specialized for matching against s and +// should only be given s. It's not curried for performance reasons. +func foldFunc(s []byte) func(s, t []byte) bool { + nonLetter := false + special := false // special letter + for _, b := range s { + if b >= utf8.RuneSelf { + return bytes.EqualFold + } + upper := b & caseMask + if upper < 'A' || upper > 'Z' { + nonLetter = true + } else if upper == 'K' || upper == 'S' { + // See above for why these letters are special. + special = true + } + } + if special { + return equalFoldRight + } + if nonLetter { + return asciiEqualFold + } + return simpleLetterEqualFold +} + +// equalFoldRight is a specialization of bytes.EqualFold when s is +// known to be all ASCII (including punctuation), but contains an 's', +// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t. +// See comments on foldFunc. +func equalFoldRight(s, t []byte) bool { + for _, sb := range s { + if len(t) == 0 { + return false + } + tb := t[0] + if tb < utf8.RuneSelf { + if sb != tb { + sbUpper := sb & caseMask + if 'A' <= sbUpper && sbUpper <= 'Z' { + if sbUpper != tb&caseMask { + return false + } + } else { + return false + } + } + t = t[1:] + continue + } + // sb is ASCII and t is not. t must be either kelvin + // sign or long s; sb must be s, S, k, or K. + tr, size := utf8.DecodeRune(t) + switch sb { + case 's', 'S': + if tr != smallLongEss { + return false + } + case 'k', 'K': + if tr != kelvin { + return false + } + default: + return false + } + t = t[size:] + + } + if len(t) > 0 { + return false + } + return true +} + +// asciiEqualFold is a specialization of bytes.EqualFold for use when +// s is all ASCII (but may contain non-letters) and contains no +// special-folding letters. +// See comments on foldFunc. +func asciiEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, sb := range s { + tb := t[i] + if sb == tb { + continue + } + if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') { + if sb&caseMask != tb&caseMask { + return false + } + } else { + return false + } + } + return true +} + +// simpleLetterEqualFold is a specialization of bytes.EqualFold for +// use when s is all ASCII letters (no underscores, etc) and also +// doesn't contain 'k', 'K', 's', or 'S'. +// See comments on foldFunc. +func simpleLetterEqualFold(s, t []byte) bool { + if len(s) != len(t) { + return false + } + for i, b := range s { + if b&caseMask != t[i]&caseMask { + return false + } + } + return true +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/tags.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/tags.go new file mode 100644 index 0000000..cea3eda --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/tags.go @@ -0,0 +1,69 @@ +// This code is based on encoding/json and gorilla/schema + +package encoding + +import ( + "reflect" + "strings" + "unicode" +) + +const TagName = "gorethink" + +// tagOptions is the string following a comma in a struct field's +// tag, or the empty string. It does not include the leading comma. +type tagOptions string + +func getTag(sf reflect.StructField) string { + return sf.Tag.Get(TagName) +} + +// parseTag splits a struct field's tag into its name and +// comma-separated options. +func parseTag(tag string) (string, tagOptions) { + if idx := strings.Index(tag, ","); idx != -1 { + return tag[:idx], tagOptions(tag[idx+1:]) + } + return tag, tagOptions("") +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +// Contains returns whether checks that a comma-separated list of options +// contains a particular substr flag. substr must be surrounded by a +// string boundary or commas. +func (o tagOptions) Contains(optionName string) bool { + if len(o) == 0 { + return false + } + s := string(o) + for s != "" { + var next string + i := strings.Index(s, ",") + if i >= 0 { + s, next = s[:i], s[i+1:] + } + if s == optionName { + return true + } + s = next + } + return false +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go new file mode 100644 index 0000000..efaaedc --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/utils.go @@ -0,0 +1,60 @@ +package encoding + +import "reflect" + +func getTypeKind(t reflect.Type) reflect.Kind { + kind := t.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func fieldByIndex(v reflect.Value, index []int) reflect.Value { + for _, i := range index { + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + v = v.Field(i) + } + + return v +} + +func typeByIndex(t reflect.Type, index []int) reflect.Type { + for _, i := range index { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + t = t.Field(i).Type + } + return t +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go new file mode 100644 index 0000000..ee8906c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/errors.go @@ -0,0 +1,119 @@ +package gorethink + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + p "github.com/dancannon/gorethink/ql2" +) + +var ( + ErrNoConnections = errors.New("gorethink: no connections were made when creating the session") + ErrConnectionClosed = errors.New("gorethink: the connection is closed") + + ErrBusyBuffer = errors.New("Busy buffer") +) + +func printCarrots(t Term, frames []*p.Frame) string { + var frame *p.Frame + if len(frames) > 1 { + frame, frames = frames[0], frames[1:] + } else if len(frames) == 1 { + frame, frames = frames[0], []*p.Frame{} + } + + for i, arg := range t.args { + if frame.GetPos() == int64(i) { + t.args[i] = Term{ + termType: p.Term_DATUM, + data: printCarrots(arg, frames), + } + } + } + + for k, arg := range t.optArgs { + if frame.GetOpt() == k { + t.optArgs[k] = Term{ + termType: p.Term_DATUM, + data: printCarrots(arg, frames), + } + } + } + + b := &bytes.Buffer{} + for _, c := range t.String() { + if c != '^' { + b.WriteString(" ") + } else { + b.WriteString("^") + } + } + + return b.String() +} + +// Error constants +var ErrEmptyResult = errors.New("The result does not contain any more rows") + +// Connection/Response errors + +type rqlResponseError struct { + response *Response + term *Term +} + +func (e rqlResponseError) Error() string { + var err = "An error occurred" + if e.response != nil { + json.Unmarshal(e.response.Responses[0], &err) + } + + if e.term == nil { + return fmt.Sprintf("gorethink: %s", err) + } + + return fmt.Sprintf("gorethink: %s in: \n%s", err, e.term.String()) + +} + +func (e rqlResponseError) String() string { + return e.Error() +} + +type RqlCompileError struct { + rqlResponseError +} + +type RqlRuntimeError struct { + rqlResponseError +} + +type RqlClientError struct { + rqlResponseError +} + +type RqlDriverError struct { + message string +} + +func (e RqlDriverError) Error() string { + return fmt.Sprintf("gorethink: %s", e.message) +} + +func (e RqlDriverError) String() string { + return e.Error() +} + +type RqlConnectionError struct { + message string +} + +func (e RqlConnectionError) Error() string { + return fmt.Sprintf("gorethink: %s", e.message) +} + +func (e RqlConnectionError) String() string { + return e.Error() +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go new file mode 100644 index 0000000..5d179dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_select_test.go @@ -0,0 +1,101 @@ +package gorethink_test + +import ( + "fmt" + "log" + + r "github.com/dancannon/gorethink" +) + +func Example_Get() { + type Person struct { + Id string `gorethink:"id, omitempty"` + FirstName string `gorethink:"first_name"` + LastName string `gorethink:"last_name"` + Gender string `gorethink:"gender"` + } + + sess, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + // Setup table + r.Db("test").TableDrop("table").Run(sess) + r.Db("test").TableCreate("table").Run(sess) + r.Db("test").Table("table").Insert(Person{"1", "John", "Smith", "M"}).Run(sess) + + // Fetch the row from the database + res, err := r.Db("test").Table("table").Get("1").Run(sess) + if err != nil { + log.Fatalf("Error finding person: %s", err) + } + + if res.IsNil() { + log.Fatalf("Person not found") + } + + // Scan query result into the person variable + var person Person + err = res.One(&person) + if err != nil { + log.Fatalf("Error scanning database result: %s", err) + } + fmt.Printf("%s %s (%s)", person.FirstName, person.LastName, person.Gender) + + // Output: + // John Smith (M) +} + +func Example_GetAll_Compound() { + type Person struct { + Id string `gorethink:"id, omitempty"` + FirstName string `gorethink:"first_name"` + LastName string `gorethink:"last_name"` + Gender string `gorethink:"gender"` + } + + sess, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + // Setup table + r.Db("test").TableDrop("table").Run(sess) + r.Db("test").TableCreate("table").Run(sess) + r.Db("test").Table("table").Insert(Person{"1", "John", "Smith", "M"}).Run(sess) + r.Db("test").Table("table").IndexCreateFunc("full_name", func(row r.Term) interface{} { + return []interface{}{row.Field("first_name"), row.Field("last_name")} + }).Run(sess) + r.Db("test").Table("table").IndexWait().Run(sess) + + // Fetch the row from the database + res, err := r.Db("test").Table("table").GetAllByIndex("full_name", []interface{}{"John", "Smith"}).Run(sess) + if err != nil { + log.Fatalf("Error finding person: %s", err) + } + + if res.IsNil() { + log.Fatalf("Person not found") + } + + // Scan query result into the person variable + var person Person + err = res.One(&person) + if err == r.ErrEmptyResult { + log.Fatalf("Person not found") + } else if err != nil { + log.Fatalf("Error scanning database result: %s", err) + } + + fmt.Printf("%s %s (%s)", person.FirstName, person.LastName, person.Gender) + + // Output: + // John Smith (M) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go new file mode 100644 index 0000000..e130471 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_query_table_test.go @@ -0,0 +1,81 @@ +package gorethink_test + +import ( + "fmt" + "log" + + r "github.com/dancannon/gorethink" +) + +func Example_TableCreate() { + sess, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + // Setup database + r.Db("test").TableDrop("table").Run(sess) + + response, err := r.Db("test").TableCreate("table").RunWrite(sess) + if err != nil { + log.Fatalf("Error creating table: %s", err) + } + + fmt.Printf("%d table created", response.TablesCreated) + + // Output: + // 1 table created +} + +func Example_IndexCreate() { + sess, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + // Setup database + r.Db("test").TableDrop("table").Run(sess) + r.Db("test").TableCreate("table").Run(sess) + + response, err := r.Db("test").Table("table").IndexCreate("name").RunWrite(sess) + if err != nil { + log.Fatalf("Error creating index: %s", err) + } + + fmt.Printf("%d index created", response.Created) + + // Output: + // 1 index created +} + +func Example_IndexCreate_compound() { + sess, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + // Setup database + r.Db("test").TableDrop("table").Run(sess) + r.Db("test").TableCreate("table").Run(sess) + + response, err := r.Db("test").Table("table").IndexCreateFunc("full_name", func(row r.Term) interface{} { + return []interface{}{row.Field("first_name"), row.Field("last_name")} + }).RunWrite(sess) + if err != nil { + log.Fatalf("Error creating index: %s", err) + } + + fmt.Printf("%d index created", response.Created) + + // Output: + // 1 index created +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go new file mode 100644 index 0000000..bbdda7f --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/example_test.go @@ -0,0 +1,46 @@ +package gorethink_test + +import ( + "fmt" + "log" + "os" + + r "github.com/dancannon/gorethink" +) + +var session *r.Session +var url, authKey string + +func init() { + // Needed for wercker. By default url is "localhost:28015" + url = os.Getenv("RETHINKDB_URL") + if url == "" { + url = "localhost:28015" + } + + // Needed for running tests for RethinkDB with a non-empty authkey + authKey = os.Getenv("RETHINKDB_AUTHKEY") +} + +func Example() { + session, err := r.Connect(r.ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + if err != nil { + log.Fatalf("Error connecting to DB: %s", err) + } + + res, err := r.Expr("Hello World").Run(session) + if err != nil { + log.Fatalln(err.Error()) + } + + var response string + err = res.One(&response) + if err != nil { + log.Fatalln(err.Error()) + } + + fmt.Println(response) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go new file mode 100644 index 0000000..c8e66ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink.go @@ -0,0 +1,12 @@ +package gorethink + +import ( + "reflect" + + "github.com/dancannon/gorethink/encoding" +) + +func init() { + // Set encoding package + encoding.IgnoreType(reflect.TypeOf(Term{})) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go new file mode 100644 index 0000000..2afac67 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go @@ -0,0 +1,394 @@ +package gorethink + +import ( + "encoding/json" + "flag" + "math/rand" + "os" + "runtime" + "sync" + "testing" + "time" + + test "gopkg.in/check.v1" +) + +var sess *Session +var debug = flag.Bool("gorethink.debug", false, "print query trees") +var url, db, authKey string + +func init() { + flag.Parse() + + // If the test is being run by wercker look for the rethink url + url = os.Getenv("RETHINKDB_URL") + if url == "" { + url = "localhost:28015" + } + + db = os.Getenv("RETHINKDB_DB") + if db == "" { + db = "test" + } + + // Needed for running tests for RethinkDB with a non-empty authkey + authKey = os.Getenv("RETHINKDB_AUTHKEY") +} + +// Hook up gocheck into the gotest runner. +func Test(t *testing.T) { test.TestingT(t) } + +type RethinkSuite struct{} + +var _ = test.Suite(&RethinkSuite{}) + +func (s *RethinkSuite) SetUpSuite(c *test.C) { + var err error + sess, err = Connect(ConnectOpts{ + Address: url, + AuthKey: authKey, + }) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TearDownSuite(c *test.C) { + sess.Close() +} + +type jsonChecker struct { + info *test.CheckerInfo +} + +func (j jsonChecker) Info() *test.CheckerInfo { + return j.info +} + +func (j jsonChecker) Check(params []interface{}, names []string) (result bool, error string) { + var jsonParams []interface{} + for _, param := range params { + jsonParam, err := json.Marshal(param) + if err != nil { + return false, err.Error() + } + jsonParams = append(jsonParams, jsonParam) + } + return test.DeepEquals.Check(jsonParams, names) +} + +// jsonEquals compares two interface{} objects by converting them to JSON and +// seeing if the strings match +var jsonEquals = &jsonChecker{ + &test.CheckerInfo{Name: "jsonEquals", Params: []string{"obtained", "expected"}}, +} + +// Expressions used in tests +var now = time.Now() +var arr = []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9} +var darr = []interface{}{1, 1, 2, 2, 3, 3, 5, 5, 6} +var narr = []interface{}{ + 1, 2, 3, 4, 5, 6, []interface{}{ + 7.1, 7.2, 7.3, + }, +} +var obj = map[string]interface{}{"a": 1, "b": 2, "c": 3} +var nobj = map[string]interface{}{ + "A": 1, + "B": 2, + "C": map[string]interface{}{ + "1": 3, + "2": 4, + }, +} +var noDupNumObjList = []interface{}{ + map[string]interface{}{"id": 1, "g1": 1, "g2": 1, "num": 0}, + map[string]interface{}{"id": 2, "g1": 2, "g2": 2, "num": 5}, + map[string]interface{}{"id": 3, "g1": 3, "g2": 2, "num": 10}, + map[string]interface{}{"id": 5, "g1": 2, "g2": 3, "num": 100}, + map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}, + map[string]interface{}{"id": 8, "g1": 4, "g2": 2, "num": 50}, + map[string]interface{}{"id": 9, "g1": 2, "g2": 3, "num": 25}, +} +var objList = []interface{}{ + map[string]interface{}{"id": 1, "g1": 1, "g2": 1, "num": 0}, + map[string]interface{}{"id": 2, "g1": 2, "g2": 2, "num": 5}, + map[string]interface{}{"id": 3, "g1": 3, "g2": 2, "num": 10}, + map[string]interface{}{"id": 4, "g1": 2, "g2": 3, "num": 0}, + map[string]interface{}{"id": 5, "g1": 2, "g2": 3, "num": 100}, + map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}, + map[string]interface{}{"id": 7, "g1": 1, "g2": 2, "num": 0}, + map[string]interface{}{"id": 8, "g1": 4, "g2": 2, "num": 50}, + map[string]interface{}{"id": 9, "g1": 2, "g2": 3, "num": 25}, +} +var nameList = []interface{}{ + map[string]interface{}{"id": 1, "first_name": "John", "last_name": "Smith", "gender": "M"}, + map[string]interface{}{"id": 2, "first_name": "Jane", "last_name": "Smith", "gender": "F"}, +} +var defaultObjList = []interface{}{ + map[string]interface{}{"a": 1}, + map[string]interface{}{}, +} +var joinTable1 = []interface{}{ + map[string]interface{}{"id": 0, "name": "bob"}, + map[string]interface{}{"id": 1, "name": "tom"}, + map[string]interface{}{"id": 2, "name": "joe"}, +} +var joinTable2 = []interface{}{ + map[string]interface{}{"id": 0, "title": "goof"}, + map[string]interface{}{"id": 2, "title": "lmoe"}, +} +var joinTable3 = []interface{}{ + map[string]interface{}{"it": 0, "title": "goof"}, + map[string]interface{}{"it": 2, "title": "lmoe"}, +} + +type TStr string +type TMap map[string]interface{} + +type T struct { + A string `gorethink:"id, omitempty"` + B int + C int `gorethink:"-"` + D map[string]interface{} + E []interface{} + F X +} + +type SimpleT struct { + A string + B int +} + +type X struct { + XA int + XB string + XC []string + XD Y + XE TStr + XF []TStr +} + +type Y struct { + YA int + YB map[string]interface{} + YC map[string]string + YD TMap +} + +type PseudoTypes struct { + T time.Time + B []byte +} + +var str = T{ + A: "A", + B: 1, + C: 1, + D: map[string]interface{}{ + "D1": 1, + "D2": "2", + }, + E: []interface{}{ + "E1", "E2", "E3", 4, + }, + F: X{ + XA: 2, + XB: "B", + XC: []string{"XC1", "XC2"}, + XD: Y{ + YA: 3, + YB: map[string]interface{}{ + "1": "1", + "2": "2", + "3": 3, + }, + YC: map[string]string{ + "YC1": "YC1", + }, + YD: TMap{ + "YD1": "YD1", + }, + }, + XE: "XE", + XF: []TStr{ + "XE1", "XE2", + }, + }, +} + +func (s *RethinkSuite) BenchmarkExpr(c *test.C) { + for i := 0; i < c.N; i++ { + // Test query + query := Expr(true) + err := query.Exec(sess) + c.Assert(err, test.IsNil) + } +} + +func (s *RethinkSuite) BenchmarkNoReplyExpr(c *test.C) { + for i := 0; i < c.N; i++ { + // Test query + query := Expr(true) + err := query.Exec(sess, ExecOpts{NoReply: true}) + c.Assert(err, test.IsNil) + } +} + +func (s *RethinkSuite) BenchmarkGet(c *test.C) { + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + data := []interface{}{} + for i := 0; i < 100; i++ { + data = append(data, map[string]interface{}{ + "id": i, + }) + } + Db("test").Table("TestMany").Insert(data).Run(sess) + + for i := 0; i < c.N; i++ { + n := rand.Intn(100) + + // Test query + var response interface{} + query := Db("test").Table("TestMany").Get(n) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": n}) + } +} + +func (s *RethinkSuite) BenchmarkGetStruct(c *test.C) { + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + data := []interface{}{} + for i := 0; i < 100; i++ { + data = append(data, map[string]interface{}{ + "id": i, + "name": "Object 1", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }) + } + Db("test").Table("TestMany").Insert(data).Run(sess) + + for i := 0; i < c.N; i++ { + n := rand.Intn(100) + + // Test query + var resObj object + query := Db("test").Table("TestMany").Get(n) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&resObj) + + c.Assert(err, test.IsNil) + } +} + +func (s *RethinkSuite) BenchmarkSelectMany(c *test.C) { + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + data := []interface{}{} + for i := 0; i < 100; i++ { + data = append(data, map[string]interface{}{ + "id": i, + }) + } + Db("test").Table("TestMany").Insert(data).Run(sess) + + for i := 0; i < c.N; i++ { + // Test query + res, err := Db("test").Table("TestMany").Run(sess) + c.Assert(err, test.IsNil) + + var response []map[string]interface{} + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 100) + } +} + +func (s *RethinkSuite) BenchmarkSelectManyStruct(c *test.C) { + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + data := []interface{}{} + for i := 0; i < 100; i++ { + data = append(data, map[string]interface{}{ + "id": i, + "name": "Object 1", + "Attrs": []interface{}{map[string]interface{}{ + "Name": "attr 1", + "Value": "value 1", + }}, + }) + } + Db("test").Table("TestMany").Insert(data).Run(sess) + + for i := 0; i < c.N; i++ { + // Test query + res, err := Db("test").Table("TestMany").Run(sess) + c.Assert(err, test.IsNil) + + var response []object + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 100) + } +} + +func doConcurrentTest(c *test.C, ct func()) { + maxProcs, numReqs := 1, 150 + if testing.Short() { + maxProcs, numReqs = 4, 50 + } + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) + + var wg sync.WaitGroup + wg.Add(numReqs) + + reqs := make(chan bool) + defer close(reqs) + + for i := 0; i < maxProcs*2; i++ { + go func() { + for _ = range reqs { + ct() + if c.Failed() { + wg.Done() + continue + } + wg.Done() + } + }() + } + + for i := 0; i < numReqs; i++ { + reqs <- true + } + + wg.Wait() +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go new file mode 100644 index 0000000..15bb116 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool.go @@ -0,0 +1,532 @@ +package gorethink + +import ( + "errors" + "fmt" + "runtime" + "sync" +) + +const defaultMaxIdleConns = 1 + +// maxBadConnRetries is the number of maximum retries if the driver returns +// driver.ErrBadConn to signal a broken connection. +const maxBadConnRetries = 10 + +var ( + connectionRequestQueueSize = 1000000 + + errPoolClosed = errors.New("gorethink: pool is closed") + errConnClosed = errors.New("gorethink: conn is closed") + errConnBusy = errors.New("gorethink: conn is busy") + errConnInactive = errors.New("gorethink: conn was never active") +) + +// depSet is a finalCloser's outstanding dependencies +type depSet map[interface{}]bool // set of true bools +// The finalCloser interface is used by (*Pool).addDep and related +// dependency reference counting. +type finalCloser interface { + // finalClose is called when the reference count of an object + // goes to zero. (*Pool).mu is not held while calling it. + finalClose() error +} + +type Pool struct { + opts *ConnectOpts + + mu sync.Mutex // protects following fields + err error // the last error that occurred + freeConn []*poolConn + connRequests []chan connRequest + numOpen int + pendingOpens int + // Used to signal the need for new connections + // a goroutine running connectionOpener() reads on this chan and + // maybeOpenNewConnections sends on the chan (one send per needed connection) + // It is closed during p.Close(). The close tells the connectionOpener + // goroutine to exit. + openerCh chan struct{} + closed bool + dep map[finalCloser]depSet + lastPut map[*poolConn]string // stacktrace of last conn's put; debug only + maxIdle int // zero means defaultMaxIdleConns; negative means 0 + maxOpen int // <= 0 means unlimited +} + +func NewPool(opts *ConnectOpts) (*Pool, error) { + p := &Pool{ + opts: opts, + + openerCh: make(chan struct{}, connectionRequestQueueSize), + lastPut: make(map[*poolConn]string), + maxIdle: opts.MaxIdle, + } + go p.connectionOpener() + return p, nil +} + +// Ping verifies a connection to the database is still alive, +// establishing a connection if necessary. +func (p *Pool) Ping() error { + pc, err := p.conn() + if err != nil { + return err + } + p.putConn(pc, nil) + return nil +} + +// Close closes the database, releasing any open resources. +// +// It is rare to Close a Pool, as the Pool handle is meant to be +// long-lived and shared between many goroutines. +func (p *Pool) Close() error { + p.mu.Lock() + if p.closed { // Make Pool.Close idempotent + p.mu.Unlock() + return nil + } + close(p.openerCh) + var err error + fns := make([]func() error, 0, len(p.freeConn)) + for _, pc := range p.freeConn { + fns = append(fns, pc.closePoolLocked()) + } + p.freeConn = nil + p.closed = true + for _, req := range p.connRequests { + close(req) + } + p.mu.Unlock() + for _, fn := range fns { + err1 := fn() + if err1 != nil { + err = err1 + } + } + return err +} + +func (p *Pool) maxIdleConnsLocked() int { + n := p.maxIdle + switch { + case n == 0: + return defaultMaxIdleConns + case n < 0: + return 0 + default: + return n + } +} + +// SetMaxIdleConns sets the maximum number of connections in the idle +// connection pool. +// +// If MaxOpenConns is greater than 0 but less than the new MaxIdleConns +// then the new MaxIdleConns will be reduced to match the MaxOpenConns limit +// +// If n <= 0, no idle connections are retained. +func (p *Pool) SetMaxIdleConns(n int) { + p.mu.Lock() + if n > 0 { + p.maxIdle = n + } else { + // No idle connections. + p.maxIdle = -1 + } + // Make sure maxIdle doesn't exceed maxOpen + if p.maxOpen > 0 && p.maxIdleConnsLocked() > p.maxOpen { + p.maxIdle = p.maxOpen + } + var closing []*poolConn + idleCount := len(p.freeConn) + maxIdle := p.maxIdleConnsLocked() + if idleCount > maxIdle { + closing = p.freeConn[maxIdle:] + p.freeConn = p.freeConn[:maxIdle] + } + p.mu.Unlock() + for _, c := range closing { + c.Close() + } +} + +// SetMaxOpenConns sets the maximum number of open connections to the database. +// +// If MaxIdleConns is greater than 0 and the new MaxOpenConns is less than +// MaxIdleConns, then MaxIdleConns will be reduced to match the new +// MaxOpenConns limit +// +// If n <= 0, then there is no limit on the number of open connections. +// The default is 0 (unlimited). +func (p *Pool) SetMaxOpenConns(n int) { + p.mu.Lock() + p.maxOpen = n + if n < 0 { + p.maxOpen = 0 + } + syncMaxIdle := p.maxOpen > 0 && p.maxIdleConnsLocked() > p.maxOpen + p.mu.Unlock() + if syncMaxIdle { + p.SetMaxIdleConns(n) + } +} + +// Assumes p.mu is locked. +// If there are connRequests and the connection limit hasn't been reached, +// then tell the connectionOpener to open new connections. +func (p *Pool) maybeOpenNewConnections() { + numRequests := len(p.connRequests) - p.pendingOpens + if p.maxOpen > 0 { + numCanOpen := p.maxOpen - (p.numOpen + p.pendingOpens) + if numRequests > numCanOpen { + numRequests = numCanOpen + } + } + for numRequests > 0 { + p.pendingOpens++ + numRequests-- + p.openerCh <- struct{}{} + } +} + +// Runs in a separate goroutine, opens new connections when requested. +func (p *Pool) connectionOpener() { + for _ = range p.openerCh { + p.openNewConnection() + } +} + +// Open one new connection +func (p *Pool) openNewConnection() { + ci, err := NewConnection(p.opts) + p.mu.Lock() + defer p.mu.Unlock() + if p.closed { + if err == nil { + ci.Close() + } + return + } + p.pendingOpens-- + if err != nil { + p.putConnPoolLocked(nil, err) + return + } + pc := &poolConn{ + p: p, + ci: ci, + } + if p.putConnPoolLocked(pc, err) { + p.addDepLocked(pc, pc) + p.numOpen++ + } else { + ci.Close() + } +} + +// connRequest represents one request for a new connection +// When there are no idle connections available, Pool.conn will create +// a new connRequest and put it on the p.connRequests list. +type connRequest struct { + conn *poolConn + err error +} + +// conn returns a newly-opened or cached *poolConn +func (p *Pool) conn() (*poolConn, error) { + p.mu.Lock() + if p.closed { + p.mu.Unlock() + return nil, errPoolClosed + } + // If p.maxOpen > 0 and the number of open connections is over the limit + // and there are no free connection, make a request and wait. + if p.maxOpen > 0 && p.numOpen >= p.maxOpen && len(p.freeConn) == 0 { + // Make the connRequest channel. It's buffered so that the + // connectionOpener doesn't block while waiting for the req to be read. + req := make(chan connRequest, 1) + p.connRequests = append(p.connRequests, req) + p.mu.Unlock() + ret := <-req + return ret.conn, ret.err + } + if c := len(p.freeConn); c > 0 { + conn := p.freeConn[0] + copy(p.freeConn, p.freeConn[1:]) + p.freeConn = p.freeConn[:c-1] + conn.inUse = true + p.mu.Unlock() + return conn, nil + } + p.numOpen++ // optimistically + p.mu.Unlock() + ci, err := NewConnection(p.opts) + if err != nil { + p.mu.Lock() + p.numOpen-- // correct for earlier optimism + p.mu.Unlock() + return nil, err + } + p.mu.Lock() + pc := &poolConn{ + p: p, + ci: ci, + } + p.addDepLocked(pc, pc) + pc.inUse = true + p.mu.Unlock() + return pc, nil +} + +// connIfFree returns (wanted, nil) if wanted is still a valid conn and +// isn't in use. +// +// The error is errConnClosed if the connection if the requested connection +// is invalid because it's been closed. +// +// The error is errConnBusy if the connection is in use. +func (p *Pool) connIfFree(wanted *poolConn) (*poolConn, error) { + p.mu.Lock() + defer p.mu.Unlock() + if wanted.pmuClosed { + return nil, errConnClosed + } + if wanted.inUse { + return nil, errConnBusy + } + idx := -1 + for ii, v := range p.freeConn { + if v == wanted { + idx = ii + break + } + } + if idx >= 0 { + p.freeConn = append(p.freeConn[:idx], p.freeConn[idx+1:]...) + wanted.inUse = true + return wanted, nil + } + + panic("connIfFree call requested a non-closed, non-busy, non-free conn") +} + +// noteUnusedCursor notes that si is no longer used and should +// be closed whenever possible (when c is next not in use), unless c is +// already closed. +func (p *Pool) noteUnusedCursor(c *poolConn, ci *Cursor) { + p.mu.Lock() + defer p.mu.Unlock() + if c.inUse { + c.onPut = append(c.onPut, func() { + ci.Close() + }) + } else { + c.Lock() + defer c.Unlock() + if !c.finalClosed { + ci.Close() + } + } +} + +// debugGetPut determines whether getConn & putConn calls' stack traces +// are returned for more verbose crashes. +const debugGetPut = false + +// putConn adds a connection to the free pool. +// err is optionally the last error that occurred on this connection. +func (p *Pool) putConn(pc *poolConn, err error) { + p.mu.Lock() + if !pc.inUse { + if debugGetPut { + fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", pc, stack(), p.lastPut[pc]) + } + panic("gorethink: connection returned that was never out") + } + if debugGetPut { + p.lastPut[pc] = stack() + } + pc.inUse = false + for _, fn := range pc.onPut { + fn() + } + pc.onPut = nil + if err != nil && pc.ci.bad { + // Don't reuse bad connections. + // Since the conn is considered bad and is being discarded, treat it + // as closed. Don't decrement the open count here, finalClose will + // take care of that. + p.maybeOpenNewConnections() + p.mu.Unlock() + pc.Close() + return + } + added := p.putConnPoolLocked(pc, nil) + p.mu.Unlock() + if !added { + pc.Close() + } +} + +// Satisfy a connRequest or put the poolConn in the idle pool and return true +// or return false. +// putConnPoolLocked will satisfy a connRequest if there is one, or it will +// return the *poolConn to the freeConn list if err == nil and the idle +// connection limit will not be exceeded. +// If err != nil, the value of pc is ignored. +// If err == nil, then pc must not equal nil. +// If a connRequest was fulfilled or the *poolConn was placed in the +// freeConn list, then true is returned, otherwise false is returned. +func (p *Pool) putConnPoolLocked(pc *poolConn, err error) bool { + if c := len(p.connRequests); c > 0 { + req := p.connRequests[0] + // This copy is O(n) but in practice faster than a linked list. + // TODO: consider compacting it down less often and + // moving the base instead? + copy(p.connRequests, p.connRequests[1:]) + p.connRequests = p.connRequests[:c-1] + if err == nil { + pc.inUse = true + } + req <- connRequest{ + conn: pc, + err: err, + } + return true + } else if err == nil && !p.closed && p.maxIdleConnsLocked() > len(p.freeConn) { + p.freeConn = append(p.freeConn, pc) + return true + } + return false +} + +// addDep notes that x now depends on dep, and x's finalClose won't be +// called until all of x's dependencies are removed with removeDep. +func (p *Pool) addDep(x finalCloser, dep interface{}) { + //println(fmt.Sprintf("addDep(%T %p, %T %p)", x, x, dep, dep)) + p.mu.Lock() + defer p.mu.Unlock() + p.addDepLocked(x, dep) +} + +func (p *Pool) addDepLocked(x finalCloser, dep interface{}) { + if p.dep == nil { + p.dep = make(map[finalCloser]depSet) + } + xdep := p.dep[x] + if xdep == nil { + xdep = make(depSet) + p.dep[x] = xdep + } + xdep[dep] = true +} + +// removeDep notes that x no longer depends on dep. +// If x still has dependencies, nil is returned. +// If x no longer has any dependencies, its finalClose method will be +// called and its error value will be returned. +func (p *Pool) removeDep(x finalCloser, dep interface{}) error { + p.mu.Lock() + fn := p.removeDepLocked(x, dep) + p.mu.Unlock() + return fn() +} + +func (p *Pool) removeDepLocked(x finalCloser, dep interface{}) func() error { + //println(fmt.Sprintf("removeDep(%T %p, %T %p)", x, x, dep, dep)) + xdep, ok := p.dep[x] + if !ok { + panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x)) + } + l0 := len(xdep) + delete(xdep, dep) + switch len(xdep) { + case l0: + // Nothing removed. Shouldn't happen. + panic(fmt.Sprintf("unpaired removeDep: no %T dep on %T", dep, x)) + case 0: + // No more dependencies. + delete(p.dep, x) + return x.finalClose + default: + // Dependencies remain. + return func() error { return nil } + } +} + +// Query execution functions + +// Exec executes a query without waiting for any response. +func (p *Pool) Exec(q Query) error { + var err error + for i := 0; i < maxBadConnRetries; i++ { + err = p.exec(q) + if err != ErrBadConn { + break + } + } + return err +} +func (p *Pool) exec(q Query) (err error) { + pc, err := p.conn() + if err != nil { + return err + } + defer func() { + p.putConn(pc, err) + }() + + pc.Lock() + _, _, err = pc.ci.Query(q) + pc.Unlock() + + if err != nil { + return err + } + return nil +} + +// Query executes a query and waits for the response +func (p *Pool) Query(q Query) (*Cursor, error) { + var cursor *Cursor + var err error + for i := 0; i < maxBadConnRetries; i++ { + cursor, err = p.query(q) + if err != ErrBadConn { + break + } + } + return cursor, err +} +func (p *Pool) query(query Query) (*Cursor, error) { + ci, err := p.conn() + if err != nil { + return nil, err + } + return p.queryConn(ci, ci.releaseConn, query) +} + +// queryConn executes a query on the given connection. +// The connection gets released by the releaseConn function. +func (p *Pool) queryConn(pc *poolConn, releaseConn func(error), q Query) (*Cursor, error) { + pc.Lock() + _, cursor, err := pc.ci.Query(q) + pc.Unlock() + if err != nil { + releaseConn(err) + return nil, err + } + + cursor.releaseConn = releaseConn + + return cursor, nil +} + +// Helper functions + +func stack() string { + var buf [2 << 10]byte + return string(buf[:runtime.Stack(buf[:], false)]) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pool_conn.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool_conn.go new file mode 100644 index 0000000..300b735 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pool_conn.go @@ -0,0 +1,75 @@ +package gorethink + +import ( + "errors" + "sync" +) + +// ErrBadConn should be returned by a connection operation to signal to the +// pool that a driver.Conn is in a bad state (such as the server +// having earlier closed the connection) and the pool should retry on a +// new connection. +// +// To prevent duplicate operations, ErrBadConn should NOT be returned +// if there's a possibility that the database server might have +// performed the operation. Even if the server sends back an error, +// you shouldn't return ErrBadConn. +var ErrBadConn = errors.New("gorethink: bad connection") + +type poolConn struct { + p *Pool + + sync.Mutex // guards following + ci *Connection + closed bool + finalClosed bool // ci.Close has been called + + // guarded by p.mu + inUse bool + onPut []func() // code (with p.mu held) run when conn is next returned + pmuClosed bool // same as closed, but guarded by p.mu, for connIfFree +} + +func (pc *poolConn) releaseConn(err error) { + pc.p.putConn(pc, err) +} + +// the pc.p's Mutex is held. +func (pc *poolConn) closePoolLocked() func() error { + pc.Lock() + defer pc.Unlock() + if pc.closed { + return func() error { return errors.New("gorethink: duplicate driverConn close") } + } + pc.closed = true + return pc.p.removeDepLocked(pc, pc) +} + +func (pc *poolConn) Close() error { + pc.Lock() + if pc.closed { + pc.Unlock() + return errors.New("gorethink: duplicate driverConn close") + } + pc.closed = true + pc.Unlock() // not defer; removeDep finalClose calls may need to lock + // And now updates that require holding pc.mu.Lock. + pc.p.mu.Lock() + pc.pmuClosed = true + fn := pc.p.removeDepLocked(pc, pc) + pc.p.mu.Unlock() + return fn() +} + +func (pc *poolConn) finalClose() error { + pc.Lock() + err := pc.ci.Close() + pc.ci = nil + pc.finalClosed = true + pc.Unlock() + pc.p.mu.Lock() + pc.p.numOpen-- + pc.p.maybeOpenNewConnections() + pc.p.mu.Unlock() + return err +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go new file mode 100644 index 0000000..343d689 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go @@ -0,0 +1,219 @@ +package gorethink + +import ( + "encoding/base64" + "math" + "strconv" + "time" + + "github.com/dancannon/gorethink/types" + + "fmt" +) + +func convertPseudotype(obj map[string]interface{}, opts map[string]interface{}) (interface{}, error) { + if reqlType, ok := obj["$reql_type$"]; ok { + if reqlType == "TIME" { + // load timeFormat, set to native if the option was not set + timeFormat := "native" + if opt, ok := opts["time_format"]; ok { + if sopt, ok := opt.(string); ok { + timeFormat = sopt + } else { + return nil, fmt.Errorf("Invalid time_format run option \"%s\".", opt) + } + } + + if timeFormat == "native" { + return reqlTimeToNativeTime(obj["epoch_time"].(float64), obj["timezone"].(string)) + } else if timeFormat == "raw" { + return obj, nil + } else { + return nil, fmt.Errorf("Unknown time_format run option \"%s\".", reqlType) + } + } else if reqlType == "GROUPED_DATA" { + // load groupFormat, set to native if the option was not set + groupFormat := "native" + if opt, ok := opts["group_format"]; ok { + if sopt, ok := opt.(string); ok { + groupFormat = sopt + } else { + return nil, fmt.Errorf("Invalid group_format run option \"%s\".", opt) + } + } + + if groupFormat == "native" { + return reqlGroupedDataToObj(obj) + } else if groupFormat == "raw" { + return obj, nil + } else { + return nil, fmt.Errorf("Unknown group_format run option \"%s\".", reqlType) + } + } else if reqlType == "BINARY" { + binaryFormat := "native" + if opt, ok := opts["binary_format"]; ok { + if sopt, ok := opt.(string); ok { + binaryFormat = sopt + } else { + return nil, fmt.Errorf("Invalid binary_format run option \"%s\".", opt) + } + } + + if binaryFormat == "native" { + return reqlBinaryToNativeBytes(obj) + } else if binaryFormat == "raw" { + return obj, nil + } else { + return nil, fmt.Errorf("Unknown binary_format run option \"%s\".", reqlType) + } + } else if reqlType == "GEOMETRY" { + geometryFormat := "native" + if opt, ok := opts["geometry_format"]; ok { + if sopt, ok := opt.(string); ok { + geometryFormat = sopt + } else { + return nil, fmt.Errorf("Invalid geometry_format run option \"%s\".", opt) + } + } + + if geometryFormat == "native" { + return reqlGeometryToNativeGeometry(obj) + } else if geometryFormat == "raw" { + return obj, nil + } else { + return nil, fmt.Errorf("Unknown geometry_format run option \"%s\".", reqlType) + } + } else { + return obj, nil + } + } + + return obj, nil +} + +func recursivelyConvertPseudotype(obj interface{}, opts map[string]interface{}) (interface{}, error) { + var err error + + switch obj := obj.(type) { + case []interface{}: + for key, val := range obj { + obj[key], err = recursivelyConvertPseudotype(val, opts) + if err != nil { + return nil, err + } + } + case map[string]interface{}: + for key, val := range obj { + obj[key], err = recursivelyConvertPseudotype(val, opts) + if err != nil { + return nil, err + } + } + + pobj, err := convertPseudotype(obj, opts) + if err != nil { + return nil, err + } + + return pobj, nil + } + + return obj, nil +} + +// Pseudo-type helper functions + +func reqlTimeToNativeTime(timestamp float64, timezone string) (time.Time, error) { + sec, ms := math.Modf(timestamp) + + t := time.Unix(int64(sec), int64(ms*1000*1000*1000)) + + // Caclulate the timezone + if timezone != "" { + hours, err := strconv.Atoi(timezone[1:3]) + if err != nil { + return time.Time{}, err + } + minutes, err := strconv.Atoi(timezone[4:6]) + if err != nil { + return time.Time{}, err + } + tzOffset := ((hours * 60) + minutes) * 60 + if timezone[:1] == "-" { + tzOffset = 0 - tzOffset + } + + t = t.In(time.FixedZone(timezone, tzOffset)) + } + + return t, nil +} + +func reqlGroupedDataToObj(obj map[string]interface{}) (interface{}, error) { + if data, ok := obj["data"]; ok { + ret := []interface{}{} + for _, v := range data.([]interface{}) { + v := v.([]interface{}) + ret = append(ret, map[string]interface{}{ + "group": v[0], + "reduction": v[1], + }) + } + return ret, nil + } + return nil, fmt.Errorf("pseudo-type GROUPED_DATA object %v does not have the expected field \"data\"", obj) +} + +func reqlBinaryToNativeBytes(obj map[string]interface{}) (interface{}, error) { + if data, ok := obj["data"]; ok { + if data, ok := data.(string); ok { + b, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil, fmt.Errorf("error decoding pseudo-type BINARY object %v", obj) + } + + return b, nil + } + return nil, fmt.Errorf("pseudo-type BINARY object %v field \"data\" is not valid", obj) + } + return nil, fmt.Errorf("pseudo-type BINARY object %v does not have the expected field \"data\"", obj) +} + +func reqlGeometryToNativeGeometry(obj map[string]interface{}) (interface{}, error) { + if typ, ok := obj["type"]; !ok { + return nil, fmt.Errorf("pseudo-type GEOMETRY object %v does not have the expected field \"type\"", obj) + } else if typ, ok := typ.(string); !ok { + return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field \"type\" is not valid", obj) + } else if coords, ok := obj["coordinates"]; !ok { + return nil, fmt.Errorf("pseudo-type GEOMETRY object %v does not have the expected field \"coordinates\"", obj) + } else if typ == "Point" { + if point, err := types.UnmarshalPoint(coords); err != nil { + return nil, err + } else { + return types.Geometry{ + Type: "Point", + Point: point, + }, nil + } + } else if typ == "LineString" { + if line, err := types.UnmarshalLineString(coords); err != nil { + return nil, err + } else { + return types.Geometry{ + Type: "LineString", + Line: line, + }, nil + } + } else if typ == "Polygon" { + if lines, err := types.UnmarshalPolygon(coords); err != nil { + return nil, err + } else { + return types.Geometry{ + Type: "Polygon", + Lines: lines, + }, nil + } + } else { + return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field has unknown type %s", typ) + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go new file mode 100644 index 0000000..707354b --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.pb.go @@ -0,0 +1,1183 @@ +// Code generated by protoc-gen-go. +// source: ql2.proto +// DO NOT EDIT! + +package ql2 + +import proto "code.google.com/p/goprotobuf/proto" +import json "encoding/json" +import math "math" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +type VersionDummy_Version int32 + +const ( + VersionDummy_V0_1 VersionDummy_Version = 1063369270 + VersionDummy_V0_2 VersionDummy_Version = 1915781601 + VersionDummy_V0_3 VersionDummy_Version = 1601562686 +) + +var VersionDummy_Version_name = map[int32]string{ + 1063369270: "V0_1", + 1915781601: "V0_2", + 1601562686: "V0_3", +} +var VersionDummy_Version_value = map[string]int32{ + "V0_1": 1063369270, + "V0_2": 1915781601, + "V0_3": 1601562686, +} + +func (x VersionDummy_Version) Enum() *VersionDummy_Version { + p := new(VersionDummy_Version) + *p = x + return p +} +func (x VersionDummy_Version) String() string { + return proto.EnumName(VersionDummy_Version_name, int32(x)) +} +func (x VersionDummy_Version) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *VersionDummy_Version) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(VersionDummy_Version_value, data, "VersionDummy_Version") + if err != nil { + return err + } + *x = VersionDummy_Version(value) + return nil +} + +type VersionDummy_Protocol int32 + +const ( + VersionDummy_PROTOBUF VersionDummy_Protocol = 656407617 + VersionDummy_JSON VersionDummy_Protocol = 2120839367 +) + +var VersionDummy_Protocol_name = map[int32]string{ + 656407617: "PROTOBUF", + 2120839367: "JSON", +} +var VersionDummy_Protocol_value = map[string]int32{ + "PROTOBUF": 656407617, + "JSON": 2120839367, +} + +func (x VersionDummy_Protocol) Enum() *VersionDummy_Protocol { + p := new(VersionDummy_Protocol) + *p = x + return p +} +func (x VersionDummy_Protocol) String() string { + return proto.EnumName(VersionDummy_Protocol_name, int32(x)) +} +func (x VersionDummy_Protocol) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *VersionDummy_Protocol) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(VersionDummy_Protocol_value, data, "VersionDummy_Protocol") + if err != nil { + return err + } + *x = VersionDummy_Protocol(value) + return nil +} + +type Query_QueryType int32 + +const ( + Query_START Query_QueryType = 1 + Query_CONTINUE Query_QueryType = 2 + Query_STOP Query_QueryType = 3 + Query_NOREPLY_WAIT Query_QueryType = 4 +) + +var Query_QueryType_name = map[int32]string{ + 1: "START", + 2: "CONTINUE", + 3: "STOP", + 4: "NOREPLY_WAIT", +} +var Query_QueryType_value = map[string]int32{ + "START": 1, + "CONTINUE": 2, + "STOP": 3, + "NOREPLY_WAIT": 4, +} + +func (x Query_QueryType) Enum() *Query_QueryType { + p := new(Query_QueryType) + *p = x + return p +} +func (x Query_QueryType) String() string { + return proto.EnumName(Query_QueryType_name, int32(x)) +} +func (x Query_QueryType) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Query_QueryType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Query_QueryType_value, data, "Query_QueryType") + if err != nil { + return err + } + *x = Query_QueryType(value) + return nil +} + +type Frame_FrameType int32 + +const ( + Frame_POS Frame_FrameType = 1 + Frame_OPT Frame_FrameType = 2 +) + +var Frame_FrameType_name = map[int32]string{ + 1: "POS", + 2: "OPT", +} +var Frame_FrameType_value = map[string]int32{ + "POS": 1, + "OPT": 2, +} + +func (x Frame_FrameType) Enum() *Frame_FrameType { + p := new(Frame_FrameType) + *p = x + return p +} +func (x Frame_FrameType) String() string { + return proto.EnumName(Frame_FrameType_name, int32(x)) +} +func (x Frame_FrameType) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Frame_FrameType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Frame_FrameType_value, data, "Frame_FrameType") + if err != nil { + return err + } + *x = Frame_FrameType(value) + return nil +} + +type Response_ResponseType int32 + +const ( + Response_SUCCESS_ATOM Response_ResponseType = 1 + Response_SUCCESS_SEQUENCE Response_ResponseType = 2 + Response_SUCCESS_PARTIAL Response_ResponseType = 3 + Response_SUCCESS_FEED Response_ResponseType = 5 + Response_WAIT_COMPLETE Response_ResponseType = 4 + Response_SUCCESS_ATOM_FEED Response_ResponseType = 6 + Response_CLIENT_ERROR Response_ResponseType = 16 + Response_COMPILE_ERROR Response_ResponseType = 17 + Response_RUNTIME_ERROR Response_ResponseType = 18 +) + +var Response_ResponseType_name = map[int32]string{ + 1: "SUCCESS_ATOM", + 2: "SUCCESS_SEQUENCE", + 3: "SUCCESS_PARTIAL", + 5: "SUCCESS_FEED", + 4: "WAIT_COMPLETE", + 6: "SUCCESS_ATOM_FEED", + 16: "CLIENT_ERROR", + 17: "COMPILE_ERROR", + 18: "RUNTIME_ERROR", +} +var Response_ResponseType_value = map[string]int32{ + "SUCCESS_ATOM": 1, + "SUCCESS_SEQUENCE": 2, + "SUCCESS_PARTIAL": 3, + "SUCCESS_FEED": 5, + "WAIT_COMPLETE": 4, + "SUCCESS_ATOM_FEED": 6, + "CLIENT_ERROR": 16, + "COMPILE_ERROR": 17, + "RUNTIME_ERROR": 18, +} + +func (x Response_ResponseType) Enum() *Response_ResponseType { + p := new(Response_ResponseType) + *p = x + return p +} +func (x Response_ResponseType) String() string { + return proto.EnumName(Response_ResponseType_name, int32(x)) +} +func (x Response_ResponseType) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Response_ResponseType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Response_ResponseType_value, data, "Response_ResponseType") + if err != nil { + return err + } + *x = Response_ResponseType(value) + return nil +} + +type Datum_DatumType int32 + +const ( + Datum_R_NULL Datum_DatumType = 1 + Datum_R_BOOL Datum_DatumType = 2 + Datum_R_NUM Datum_DatumType = 3 + Datum_R_STR Datum_DatumType = 4 + Datum_R_ARRAY Datum_DatumType = 5 + Datum_R_OBJECT Datum_DatumType = 6 + Datum_R_JSON Datum_DatumType = 7 +) + +var Datum_DatumType_name = map[int32]string{ + 1: "R_NULL", + 2: "R_BOOL", + 3: "R_NUM", + 4: "R_STR", + 5: "R_ARRAY", + 6: "R_OBJECT", + 7: "R_JSON", +} +var Datum_DatumType_value = map[string]int32{ + "R_NULL": 1, + "R_BOOL": 2, + "R_NUM": 3, + "R_STR": 4, + "R_ARRAY": 5, + "R_OBJECT": 6, + "R_JSON": 7, +} + +func (x Datum_DatumType) Enum() *Datum_DatumType { + p := new(Datum_DatumType) + *p = x + return p +} +func (x Datum_DatumType) String() string { + return proto.EnumName(Datum_DatumType_name, int32(x)) +} +func (x Datum_DatumType) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Datum_DatumType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Datum_DatumType_value, data, "Datum_DatumType") + if err != nil { + return err + } + *x = Datum_DatumType(value) + return nil +} + +type Term_TermType int32 + +const ( + Term_DATUM Term_TermType = 1 + Term_MAKE_ARRAY Term_TermType = 2 + Term_MAKE_OBJ Term_TermType = 3 + Term_VAR Term_TermType = 10 + Term_JAVASCRIPT Term_TermType = 11 + Term_UUID Term_TermType = 169 + Term_HTTP Term_TermType = 153 + Term_ERROR Term_TermType = 12 + Term_IMPLICIT_VAR Term_TermType = 13 + Term_DB Term_TermType = 14 + Term_TABLE Term_TermType = 15 + Term_GET Term_TermType = 16 + Term_GET_ALL Term_TermType = 78 + Term_EQ Term_TermType = 17 + Term_NE Term_TermType = 18 + Term_LT Term_TermType = 19 + Term_LE Term_TermType = 20 + Term_GT Term_TermType = 21 + Term_GE Term_TermType = 22 + Term_NOT Term_TermType = 23 + Term_ADD Term_TermType = 24 + Term_SUB Term_TermType = 25 + Term_MUL Term_TermType = 26 + Term_DIV Term_TermType = 27 + Term_MOD Term_TermType = 28 + Term_APPEND Term_TermType = 29 + Term_PREPEND Term_TermType = 80 + Term_DIFFERENCE Term_TermType = 95 + Term_SET_INSERT Term_TermType = 88 + Term_SET_INTERSECTION Term_TermType = 89 + Term_SET_UNION Term_TermType = 90 + Term_SET_DIFFERENCE Term_TermType = 91 + Term_SLICE Term_TermType = 30 + Term_SKIP Term_TermType = 70 + Term_LIMIT Term_TermType = 71 + Term_INDEXES_OF Term_TermType = 87 + Term_CONTAINS Term_TermType = 93 + Term_GET_FIELD Term_TermType = 31 + Term_KEYS Term_TermType = 94 + Term_OBJECT Term_TermType = 143 + Term_HAS_FIELDS Term_TermType = 32 + Term_WITH_FIELDS Term_TermType = 96 + Term_PLUCK Term_TermType = 33 + Term_WITHOUT Term_TermType = 34 + Term_MERGE Term_TermType = 35 + Term_BETWEEN Term_TermType = 36 + Term_REDUCE Term_TermType = 37 + Term_MAP Term_TermType = 38 + Term_FILTER Term_TermType = 39 + Term_CONCAT_MAP Term_TermType = 40 + Term_ORDER_BY Term_TermType = 41 + Term_DISTINCT Term_TermType = 42 + Term_COUNT Term_TermType = 43 + Term_IS_EMPTY Term_TermType = 86 + Term_UNION Term_TermType = 44 + Term_NTH Term_TermType = 45 + Term_BRACKET Term_TermType = 170 + Term_INNER_JOIN Term_TermType = 48 + Term_OUTER_JOIN Term_TermType = 49 + Term_EQ_JOIN Term_TermType = 50 + Term_ZIP Term_TermType = 72 + Term_RANGE Term_TermType = 173 + Term_INSERT_AT Term_TermType = 82 + Term_DELETE_AT Term_TermType = 83 + Term_CHANGE_AT Term_TermType = 84 + Term_SPLICE_AT Term_TermType = 85 + Term_COERCE_TO Term_TermType = 51 + Term_TYPE_OF Term_TermType = 52 + Term_UPDATE Term_TermType = 53 + Term_DELETE Term_TermType = 54 + Term_REPLACE Term_TermType = 55 + Term_INSERT Term_TermType = 56 + Term_DB_CREATE Term_TermType = 57 + Term_DB_DROP Term_TermType = 58 + Term_DB_LIST Term_TermType = 59 + Term_TABLE_CREATE Term_TermType = 60 + Term_TABLE_DROP Term_TermType = 61 + Term_TABLE_LIST Term_TermType = 62 + Term_CONFIG Term_TermType = 174 + Term_STATUS Term_TermType = 175 + Term_WAIT Term_TermType = 177 + Term_RECONFIGURE Term_TermType = 176 + Term_REBALANCE Term_TermType = 179 + Term_SYNC Term_TermType = 138 + Term_INDEX_CREATE Term_TermType = 75 + Term_INDEX_DROP Term_TermType = 76 + Term_INDEX_LIST Term_TermType = 77 + Term_INDEX_STATUS Term_TermType = 139 + Term_INDEX_WAIT Term_TermType = 140 + Term_INDEX_RENAME Term_TermType = 156 + Term_FUNCALL Term_TermType = 64 + Term_BRANCH Term_TermType = 65 + Term_ANY Term_TermType = 66 + Term_ALL Term_TermType = 67 + Term_FOR_EACH Term_TermType = 68 + Term_FUNC Term_TermType = 69 + Term_ASC Term_TermType = 73 + Term_DESC Term_TermType = 74 + Term_INFO Term_TermType = 79 + Term_MATCH Term_TermType = 97 + Term_UPCASE Term_TermType = 141 + Term_DOWNCASE Term_TermType = 142 + Term_SAMPLE Term_TermType = 81 + Term_DEFAULT Term_TermType = 92 + Term_JSON Term_TermType = 98 + Term_TO_JSON_STRING Term_TermType = 172 + Term_ISO8601 Term_TermType = 99 + Term_TO_ISO8601 Term_TermType = 100 + Term_EPOCH_TIME Term_TermType = 101 + Term_TO_EPOCH_TIME Term_TermType = 102 + Term_NOW Term_TermType = 103 + Term_IN_TIMEZONE Term_TermType = 104 + Term_DURING Term_TermType = 105 + Term_DATE Term_TermType = 106 + Term_TIME_OF_DAY Term_TermType = 126 + Term_TIMEZONE Term_TermType = 127 + Term_YEAR Term_TermType = 128 + Term_MONTH Term_TermType = 129 + Term_DAY Term_TermType = 130 + Term_DAY_OF_WEEK Term_TermType = 131 + Term_DAY_OF_YEAR Term_TermType = 132 + Term_HOURS Term_TermType = 133 + Term_MINUTES Term_TermType = 134 + Term_SECONDS Term_TermType = 135 + Term_TIME Term_TermType = 136 + Term_MONDAY Term_TermType = 107 + Term_TUESDAY Term_TermType = 108 + Term_WEDNESDAY Term_TermType = 109 + Term_THURSDAY Term_TermType = 110 + Term_FRIDAY Term_TermType = 111 + Term_SATURDAY Term_TermType = 112 + Term_SUNDAY Term_TermType = 113 + Term_JANUARY Term_TermType = 114 + Term_FEBRUARY Term_TermType = 115 + Term_MARCH Term_TermType = 116 + Term_APRIL Term_TermType = 117 + Term_MAY Term_TermType = 118 + Term_JUNE Term_TermType = 119 + Term_JULY Term_TermType = 120 + Term_AUGUST Term_TermType = 121 + Term_SEPTEMBER Term_TermType = 122 + Term_OCTOBER Term_TermType = 123 + Term_NOVEMBER Term_TermType = 124 + Term_DECEMBER Term_TermType = 125 + Term_LITERAL Term_TermType = 137 + Term_GROUP Term_TermType = 144 + Term_SUM Term_TermType = 145 + Term_AVG Term_TermType = 146 + Term_MIN Term_TermType = 147 + Term_MAX Term_TermType = 148 + Term_SPLIT Term_TermType = 149 + Term_UNGROUP Term_TermType = 150 + Term_RANDOM Term_TermType = 151 + Term_CHANGES Term_TermType = 152 + Term_ARGS Term_TermType = 154 + Term_BINARY Term_TermType = 155 + Term_GEOJSON Term_TermType = 157 + Term_TO_GEOJSON Term_TermType = 158 + Term_POINT Term_TermType = 159 + Term_LINE Term_TermType = 160 + Term_POLYGON Term_TermType = 161 + Term_DISTANCE Term_TermType = 162 + Term_INTERSECTS Term_TermType = 163 + Term_INCLUDES Term_TermType = 164 + Term_CIRCLE Term_TermType = 165 + Term_GET_INTERSECTING Term_TermType = 166 + Term_FILL Term_TermType = 167 + Term_GET_NEAREST Term_TermType = 168 + Term_POLYGON_SUB Term_TermType = 171 +) + +var Term_TermType_name = map[int32]string{ + 1: "DATUM", + 2: "MAKE_ARRAY", + 3: "MAKE_OBJ", + 10: "VAR", + 11: "JAVASCRIPT", + 169: "UUID", + 153: "HTTP", + 12: "ERROR", + 13: "IMPLICIT_VAR", + 14: "DB", + 15: "TABLE", + 16: "GET", + 78: "GET_ALL", + 17: "EQ", + 18: "NE", + 19: "LT", + 20: "LE", + 21: "GT", + 22: "GE", + 23: "NOT", + 24: "ADD", + 25: "SUB", + 26: "MUL", + 27: "DIV", + 28: "MOD", + 29: "APPEND", + 80: "PREPEND", + 95: "DIFFERENCE", + 88: "SET_INSERT", + 89: "SET_INTERSECTION", + 90: "SET_UNION", + 91: "SET_DIFFERENCE", + 30: "SLICE", + 70: "SKIP", + 71: "LIMIT", + 87: "INDEXES_OF", + 93: "CONTAINS", + 31: "GET_FIELD", + 94: "KEYS", + 143: "OBJECT", + 32: "HAS_FIELDS", + 96: "WITH_FIELDS", + 33: "PLUCK", + 34: "WITHOUT", + 35: "MERGE", + 36: "BETWEEN", + 37: "REDUCE", + 38: "MAP", + 39: "FILTER", + 40: "CONCAT_MAP", + 41: "ORDER_BY", + 42: "DISTINCT", + 43: "COUNT", + 86: "IS_EMPTY", + 44: "UNION", + 45: "NTH", + 170: "BRACKET", + 48: "INNER_JOIN", + 49: "OUTER_JOIN", + 50: "EQ_JOIN", + 72: "ZIP", + 173: "RANGE", + 82: "INSERT_AT", + 83: "DELETE_AT", + 84: "CHANGE_AT", + 85: "SPLICE_AT", + 51: "COERCE_TO", + 52: "TYPE_OF", + 53: "UPDATE", + 54: "DELETE", + 55: "REPLACE", + 56: "INSERT", + 57: "DB_CREATE", + 58: "DB_DROP", + 59: "DB_LIST", + 60: "TABLE_CREATE", + 61: "TABLE_DROP", + 62: "TABLE_LIST", + 174: "CONFIG", + 175: "STATUS", + 177: "WAIT", + 176: "RECONFIGURE", + 179: "REBALANCE", + 138: "SYNC", + 75: "INDEX_CREATE", + 76: "INDEX_DROP", + 77: "INDEX_LIST", + 139: "INDEX_STATUS", + 140: "INDEX_WAIT", + 156: "INDEX_RENAME", + 64: "FUNCALL", + 65: "BRANCH", + 66: "ANY", + 67: "ALL", + 68: "FOR_EACH", + 69: "FUNC", + 73: "ASC", + 74: "DESC", + 79: "INFO", + 97: "MATCH", + 141: "UPCASE", + 142: "DOWNCASE", + 81: "SAMPLE", + 92: "DEFAULT", + 98: "JSON", + 172: "TO_JSON_STRING", + 99: "ISO8601", + 100: "TO_ISO8601", + 101: "EPOCH_TIME", + 102: "TO_EPOCH_TIME", + 103: "NOW", + 104: "IN_TIMEZONE", + 105: "DURING", + 106: "DATE", + 126: "TIME_OF_DAY", + 127: "TIMEZONE", + 128: "YEAR", + 129: "MONTH", + 130: "DAY", + 131: "DAY_OF_WEEK", + 132: "DAY_OF_YEAR", + 133: "HOURS", + 134: "MINUTES", + 135: "SECONDS", + 136: "TIME", + 107: "MONDAY", + 108: "TUESDAY", + 109: "WEDNESDAY", + 110: "THURSDAY", + 111: "FRIDAY", + 112: "SATURDAY", + 113: "SUNDAY", + 114: "JANUARY", + 115: "FEBRUARY", + 116: "MARCH", + 117: "APRIL", + 118: "MAY", + 119: "JUNE", + 120: "JULY", + 121: "AUGUST", + 122: "SEPTEMBER", + 123: "OCTOBER", + 124: "NOVEMBER", + 125: "DECEMBER", + 137: "LITERAL", + 144: "GROUP", + 145: "SUM", + 146: "AVG", + 147: "MIN", + 148: "MAX", + 149: "SPLIT", + 150: "UNGROUP", + 151: "RANDOM", + 152: "CHANGES", + 154: "ARGS", + 155: "BINARY", + 157: "GEOJSON", + 158: "TO_GEOJSON", + 159: "POINT", + 160: "LINE", + 161: "POLYGON", + 162: "DISTANCE", + 163: "INTERSECTS", + 164: "INCLUDES", + 165: "CIRCLE", + 166: "GET_INTERSECTING", + 167: "FILL", + 168: "GET_NEAREST", + 171: "POLYGON_SUB", +} +var Term_TermType_value = map[string]int32{ + "DATUM": 1, + "MAKE_ARRAY": 2, + "MAKE_OBJ": 3, + "VAR": 10, + "JAVASCRIPT": 11, + "UUID": 169, + "HTTP": 153, + "ERROR": 12, + "IMPLICIT_VAR": 13, + "DB": 14, + "TABLE": 15, + "GET": 16, + "GET_ALL": 78, + "EQ": 17, + "NE": 18, + "LT": 19, + "LE": 20, + "GT": 21, + "GE": 22, + "NOT": 23, + "ADD": 24, + "SUB": 25, + "MUL": 26, + "DIV": 27, + "MOD": 28, + "APPEND": 29, + "PREPEND": 80, + "DIFFERENCE": 95, + "SET_INSERT": 88, + "SET_INTERSECTION": 89, + "SET_UNION": 90, + "SET_DIFFERENCE": 91, + "SLICE": 30, + "SKIP": 70, + "LIMIT": 71, + "INDEXES_OF": 87, + "CONTAINS": 93, + "GET_FIELD": 31, + "KEYS": 94, + "OBJECT": 143, + "HAS_FIELDS": 32, + "WITH_FIELDS": 96, + "PLUCK": 33, + "WITHOUT": 34, + "MERGE": 35, + "BETWEEN": 36, + "REDUCE": 37, + "MAP": 38, + "FILTER": 39, + "CONCAT_MAP": 40, + "ORDER_BY": 41, + "DISTINCT": 42, + "COUNT": 43, + "IS_EMPTY": 86, + "UNION": 44, + "NTH": 45, + "BRACKET": 170, + "INNER_JOIN": 48, + "OUTER_JOIN": 49, + "EQ_JOIN": 50, + "ZIP": 72, + "RANGE": 173, + "INSERT_AT": 82, + "DELETE_AT": 83, + "CHANGE_AT": 84, + "SPLICE_AT": 85, + "COERCE_TO": 51, + "TYPE_OF": 52, + "UPDATE": 53, + "DELETE": 54, + "REPLACE": 55, + "INSERT": 56, + "DB_CREATE": 57, + "DB_DROP": 58, + "DB_LIST": 59, + "TABLE_CREATE": 60, + "TABLE_DROP": 61, + "TABLE_LIST": 62, + "CONFIG": 174, + "STATUS": 175, + "WAIT": 177, + "RECONFIGURE": 176, + "REBALANCE": 179, + "SYNC": 138, + "INDEX_CREATE": 75, + "INDEX_DROP": 76, + "INDEX_LIST": 77, + "INDEX_STATUS": 139, + "INDEX_WAIT": 140, + "INDEX_RENAME": 156, + "FUNCALL": 64, + "BRANCH": 65, + "ANY": 66, + "ALL": 67, + "FOR_EACH": 68, + "FUNC": 69, + "ASC": 73, + "DESC": 74, + "INFO": 79, + "MATCH": 97, + "UPCASE": 141, + "DOWNCASE": 142, + "SAMPLE": 81, + "DEFAULT": 92, + "JSON": 98, + "TO_JSON_STRING": 172, + "ISO8601": 99, + "TO_ISO8601": 100, + "EPOCH_TIME": 101, + "TO_EPOCH_TIME": 102, + "NOW": 103, + "IN_TIMEZONE": 104, + "DURING": 105, + "DATE": 106, + "TIME_OF_DAY": 126, + "TIMEZONE": 127, + "YEAR": 128, + "MONTH": 129, + "DAY": 130, + "DAY_OF_WEEK": 131, + "DAY_OF_YEAR": 132, + "HOURS": 133, + "MINUTES": 134, + "SECONDS": 135, + "TIME": 136, + "MONDAY": 107, + "TUESDAY": 108, + "WEDNESDAY": 109, + "THURSDAY": 110, + "FRIDAY": 111, + "SATURDAY": 112, + "SUNDAY": 113, + "JANUARY": 114, + "FEBRUARY": 115, + "MARCH": 116, + "APRIL": 117, + "MAY": 118, + "JUNE": 119, + "JULY": 120, + "AUGUST": 121, + "SEPTEMBER": 122, + "OCTOBER": 123, + "NOVEMBER": 124, + "DECEMBER": 125, + "LITERAL": 137, + "GROUP": 144, + "SUM": 145, + "AVG": 146, + "MIN": 147, + "MAX": 148, + "SPLIT": 149, + "UNGROUP": 150, + "RANDOM": 151, + "CHANGES": 152, + "ARGS": 154, + "BINARY": 155, + "GEOJSON": 157, + "TO_GEOJSON": 158, + "POINT": 159, + "LINE": 160, + "POLYGON": 161, + "DISTANCE": 162, + "INTERSECTS": 163, + "INCLUDES": 164, + "CIRCLE": 165, + "GET_INTERSECTING": 166, + "FILL": 167, + "GET_NEAREST": 168, + "POLYGON_SUB": 171, +} + +func (x Term_TermType) Enum() *Term_TermType { + p := new(Term_TermType) + *p = x + return p +} +func (x Term_TermType) String() string { + return proto.EnumName(Term_TermType_name, int32(x)) +} +func (x Term_TermType) MarshalJSON() ([]byte, error) { + return json.Marshal(x.String()) +} +func (x *Term_TermType) UnmarshalJSON(data []byte) error { + value, err := proto.UnmarshalJSONEnum(Term_TermType_value, data, "Term_TermType") + if err != nil { + return err + } + *x = Term_TermType(value) + return nil +} + +type VersionDummy struct { + XXX_unrecognized []byte `json:"-"` +} + +func (m *VersionDummy) Reset() { *m = VersionDummy{} } +func (m *VersionDummy) String() string { return proto.CompactTextString(m) } +func (*VersionDummy) ProtoMessage() {} + +type Query struct { + Type *Query_QueryType `protobuf:"varint,1,opt,name=type,enum=Query_QueryType" json:"type,omitempty"` + Query *Term `protobuf:"bytes,2,opt,name=query" json:"query,omitempty"` + Token *int64 `protobuf:"varint,3,opt,name=token" json:"token,omitempty"` + OBSOLETENoreply *bool `protobuf:"varint,4,opt,name=OBSOLETE_noreply,def=0" json:"OBSOLETE_noreply,omitempty"` + AcceptsRJson *bool `protobuf:"varint,5,opt,name=accepts_r_json,def=0" json:"accepts_r_json,omitempty"` + GlobalOptargs []*Query_AssocPair `protobuf:"bytes,6,rep,name=global_optargs" json:"global_optargs,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Query) Reset() { *m = Query{} } +func (m *Query) String() string { return proto.CompactTextString(m) } +func (*Query) ProtoMessage() {} + +const Default_Query_OBSOLETENoreply bool = false +const Default_Query_AcceptsRJson bool = false + +func (m *Query) GetType() Query_QueryType { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *Query) GetQuery() *Term { + if m != nil { + return m.Query + } + return nil +} + +func (m *Query) GetToken() int64 { + if m != nil && m.Token != nil { + return *m.Token + } + return 0 +} + +func (m *Query) GetOBSOLETENoreply() bool { + if m != nil && m.OBSOLETENoreply != nil { + return *m.OBSOLETENoreply + } + return Default_Query_OBSOLETENoreply +} + +func (m *Query) GetAcceptsRJson() bool { + if m != nil && m.AcceptsRJson != nil { + return *m.AcceptsRJson + } + return Default_Query_AcceptsRJson +} + +func (m *Query) GetGlobalOptargs() []*Query_AssocPair { + if m != nil { + return m.GlobalOptargs + } + return nil +} + +type Query_AssocPair struct { + Key *string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Val *Term `protobuf:"bytes,2,opt,name=val" json:"val,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Query_AssocPair) Reset() { *m = Query_AssocPair{} } +func (m *Query_AssocPair) String() string { return proto.CompactTextString(m) } +func (*Query_AssocPair) ProtoMessage() {} + +func (m *Query_AssocPair) GetKey() string { + if m != nil && m.Key != nil { + return *m.Key + } + return "" +} + +func (m *Query_AssocPair) GetVal() *Term { + if m != nil { + return m.Val + } + return nil +} + +type Frame struct { + Type *Frame_FrameType `protobuf:"varint,1,opt,name=type,enum=Frame_FrameType" json:"type,omitempty"` + Pos *int64 `protobuf:"varint,2,opt,name=pos" json:"pos,omitempty"` + Opt *string `protobuf:"bytes,3,opt,name=opt" json:"opt,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Frame) Reset() { *m = Frame{} } +func (m *Frame) String() string { return proto.CompactTextString(m) } +func (*Frame) ProtoMessage() {} + +func (m *Frame) GetType() Frame_FrameType { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *Frame) GetPos() int64 { + if m != nil && m.Pos != nil { + return *m.Pos + } + return 0 +} + +func (m *Frame) GetOpt() string { + if m != nil && m.Opt != nil { + return *m.Opt + } + return "" +} + +type Backtrace struct { + Frames []*Frame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Backtrace) Reset() { *m = Backtrace{} } +func (m *Backtrace) String() string { return proto.CompactTextString(m) } +func (*Backtrace) ProtoMessage() {} + +func (m *Backtrace) GetFrames() []*Frame { + if m != nil { + return m.Frames + } + return nil +} + +type Response struct { + Type *Response_ResponseType `protobuf:"varint,1,opt,name=type,enum=Response_ResponseType" json:"type,omitempty"` + Token *int64 `protobuf:"varint,2,opt,name=token" json:"token,omitempty"` + Response []*Datum `protobuf:"bytes,3,rep,name=response" json:"response,omitempty"` + Backtrace *Backtrace `protobuf:"bytes,4,opt,name=backtrace" json:"backtrace,omitempty"` + Profile *Datum `protobuf:"bytes,5,opt,name=profile" json:"profile,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Response) Reset() { *m = Response{} } +func (m *Response) String() string { return proto.CompactTextString(m) } +func (*Response) ProtoMessage() {} + +func (m *Response) GetType() Response_ResponseType { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *Response) GetToken() int64 { + if m != nil && m.Token != nil { + return *m.Token + } + return 0 +} + +func (m *Response) GetResponse() []*Datum { + if m != nil { + return m.Response + } + return nil +} + +func (m *Response) GetBacktrace() *Backtrace { + if m != nil { + return m.Backtrace + } + return nil +} + +func (m *Response) GetProfile() *Datum { + if m != nil { + return m.Profile + } + return nil +} + +type Datum struct { + Type *Datum_DatumType `protobuf:"varint,1,opt,name=type,enum=Datum_DatumType" json:"type,omitempty"` + RBool *bool `protobuf:"varint,2,opt,name=r_bool" json:"r_bool,omitempty"` + RNum *float64 `protobuf:"fixed64,3,opt,name=r_num" json:"r_num,omitempty"` + RStr *string `protobuf:"bytes,4,opt,name=r_str" json:"r_str,omitempty"` + RArray []*Datum `protobuf:"bytes,5,rep,name=r_array" json:"r_array,omitempty"` + RObject []*Datum_AssocPair `protobuf:"bytes,6,rep,name=r_object" json:"r_object,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Datum) Reset() { *m = Datum{} } +func (m *Datum) String() string { return proto.CompactTextString(m) } +func (*Datum) ProtoMessage() {} + +var extRange_Datum = []proto.ExtensionRange{ + {10000, 20000}, +} + +func (*Datum) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_Datum +} +func (m *Datum) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *Datum) GetType() Datum_DatumType { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *Datum) GetRBool() bool { + if m != nil && m.RBool != nil { + return *m.RBool + } + return false +} + +func (m *Datum) GetRNum() float64 { + if m != nil && m.RNum != nil { + return *m.RNum + } + return 0 +} + +func (m *Datum) GetRStr() string { + if m != nil && m.RStr != nil { + return *m.RStr + } + return "" +} + +func (m *Datum) GetRArray() []*Datum { + if m != nil { + return m.RArray + } + return nil +} + +func (m *Datum) GetRObject() []*Datum_AssocPair { + if m != nil { + return m.RObject + } + return nil +} + +type Datum_AssocPair struct { + Key *string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Val *Datum `protobuf:"bytes,2,opt,name=val" json:"val,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Datum_AssocPair) Reset() { *m = Datum_AssocPair{} } +func (m *Datum_AssocPair) String() string { return proto.CompactTextString(m) } +func (*Datum_AssocPair) ProtoMessage() {} + +func (m *Datum_AssocPair) GetKey() string { + if m != nil && m.Key != nil { + return *m.Key + } + return "" +} + +func (m *Datum_AssocPair) GetVal() *Datum { + if m != nil { + return m.Val + } + return nil +} + +type Term struct { + Type *Term_TermType `protobuf:"varint,1,opt,name=type,enum=Term_TermType" json:"type,omitempty"` + Datum *Datum `protobuf:"bytes,2,opt,name=datum" json:"datum,omitempty"` + Args []*Term `protobuf:"bytes,3,rep,name=args" json:"args,omitempty"` + Optargs []*Term_AssocPair `protobuf:"bytes,4,rep,name=optargs" json:"optargs,omitempty"` + XXX_extensions map[int32]proto.Extension `json:"-"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Term) Reset() { *m = Term{} } +func (m *Term) String() string { return proto.CompactTextString(m) } +func (*Term) ProtoMessage() {} + +var extRange_Term = []proto.ExtensionRange{ + {10000, 20000}, +} + +func (*Term) ExtensionRangeArray() []proto.ExtensionRange { + return extRange_Term +} +func (m *Term) ExtensionMap() map[int32]proto.Extension { + if m.XXX_extensions == nil { + m.XXX_extensions = make(map[int32]proto.Extension) + } + return m.XXX_extensions +} + +func (m *Term) GetType() Term_TermType { + if m != nil && m.Type != nil { + return *m.Type + } + return 0 +} + +func (m *Term) GetDatum() *Datum { + if m != nil { + return m.Datum + } + return nil +} + +func (m *Term) GetArgs() []*Term { + if m != nil { + return m.Args + } + return nil +} + +func (m *Term) GetOptargs() []*Term_AssocPair { + if m != nil { + return m.Optargs + } + return nil +} + +type Term_AssocPair struct { + Key *string `protobuf:"bytes,1,opt,name=key" json:"key,omitempty"` + Val *Term `protobuf:"bytes,2,opt,name=val" json:"val,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *Term_AssocPair) Reset() { *m = Term_AssocPair{} } +func (m *Term_AssocPair) String() string { return proto.CompactTextString(m) } +func (*Term_AssocPair) ProtoMessage() {} + +func (m *Term_AssocPair) GetKey() string { + if m != nil && m.Key != nil { + return *m.Key + } + return "" +} + +func (m *Term_AssocPair) GetVal() *Term { + if m != nil { + return m.Val + } + return nil +} + +func init() { + proto.RegisterEnum("VersionDummy_Version", VersionDummy_Version_name, VersionDummy_Version_value) + proto.RegisterEnum("VersionDummy_Protocol", VersionDummy_Protocol_name, VersionDummy_Protocol_value) + proto.RegisterEnum("Query_QueryType", Query_QueryType_name, Query_QueryType_value) + proto.RegisterEnum("Frame_FrameType", Frame_FrameType_name, Frame_FrameType_value) + proto.RegisterEnum("Response_ResponseType", Response_ResponseType_name, Response_ResponseType_value) + proto.RegisterEnum("Datum_DatumType", Datum_DatumType_name, Datum_DatumType_value) + proto.RegisterEnum("Term_TermType", Term_TermType_name, Term_TermType_value) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto new file mode 100644 index 0000000..113ea7d --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/ql2/ql2.proto @@ -0,0 +1,777 @@ +//////////////////////////////////////////////////////////////////////////////// +// THE HIGH-LEVEL VIEW // +//////////////////////////////////////////////////////////////////////////////// + +// Process: When you first open a connection, send the magic number +// for the version of the protobuf you're targeting (in the [Version] +// enum). This should **NOT** be sent as a protobuf; just send the +// little-endian 32-bit integer over the wire raw. This number should +// only be sent once per connection. + +// The magic number shall be followed by an authorization key. The +// first 4 bytes are the length of the key to be sent as a little-endian +// 32-bit integer, followed by the key string. Even if there is no key, +// an empty string should be sent (length 0 and no data). + +// Following the authorization key, the client shall send a magic number +// for the communication protocol they want to use (in the [Protocol] +// enum). This shall be a little-endian 32-bit integer. + +// The server will then respond with a NULL-terminated string response. +// "SUCCESS" indicates that the connection has been accepted. Any other +// response indicates an error, and the response string should describe +// the error. + +// Next, for each query you want to send, construct a [Query] protobuf +// and serialize it to a binary blob. Send the blob's size to the +// server encoded as a little-endian 32-bit integer, followed by the +// blob itself. You will recieve a [Response] protobuf back preceded +// by its own size, once again encoded as a little-endian 32-bit +// integer. You can see an example exchange below in **EXAMPLE**. + +// A query consists of a [Term] to evaluate and a unique-per-connection +// [token]. + +// Tokens are used for two things: +// * Keeping track of which responses correspond to which queries. +// * Batched queries. Some queries return lots of results, so we send back +// batches of <1000, and you need to send a [CONTINUE] query with the same +// token to get more results from the original query. +//////////////////////////////////////////////////////////////////////////////// + +message VersionDummy { // We need to wrap it like this for some + // non-conforming protobuf libraries + // This enum contains the magic numbers for your version. See **THE HIGH-LEVEL + // VIEW** for what to do with it. + enum Version { + V0_1 = 0x3f61ba36; + V0_2 = 0x723081e1; // Authorization key during handshake + V0_3 = 0x5f75e83e; // Authorization key and protocol during handshake + } + + // The protocol to use after the handshake, specified in V0_3 + enum Protocol { + PROTOBUF = 0x271ffc41; + JSON = 0x7e6970c7; + } +} + +// You send one of: +// * A [START] query with a [Term] to evaluate and a unique-per-connection token. +// * A [CONTINUE] query with the same token as a [START] query that returned +// [SUCCESS_PARTIAL] in its [Response]. +// * A [STOP] query with the same token as a [START] query that you want to stop. +// * A [NOREPLY_WAIT] query with a unique per-connection token. The server answers +// with a [WAIT_COMPLETE] [Response]. +message Query { + enum QueryType { + START = 1; // Start a new query. + CONTINUE = 2; // Continue a query that returned [SUCCESS_PARTIAL] + // (see [Response]). + STOP = 3; // Stop a query partway through executing. + NOREPLY_WAIT = 4; + // Wait for noreply operations to finish. + } + optional QueryType type = 1; + // A [Term] is how we represent the operations we want a query to perform. + optional Term query = 2; // only present when [type] = [START] + optional int64 token = 3; + // This flag is ignored on the server. `noreply` should be added + // to `global_optargs` instead (the key "noreply" should map to + // either true or false). + optional bool OBSOLETE_noreply = 4 [default = false]; + + // If this is set to [true], then [Datum] values will sometimes be + // of [DatumType] [R_JSON] (see below). This can provide enormous + // speedups in languages with poor protobuf libraries. + optional bool accepts_r_json = 5 [default = false]; + + message AssocPair { + optional string key = 1; + optional Term val = 2; + } + repeated AssocPair global_optargs = 6; +} + +// A backtrace frame (see `backtrace` in Response below) +message Frame { + enum FrameType { + POS = 1; // Error occured in a positional argument. + OPT = 2; // Error occured in an optional argument. + } + optional FrameType type = 1; + optional int64 pos = 2; // The index of the positional argument. + optional string opt = 3; // The name of the optional argument. +} +message Backtrace { + repeated Frame frames = 1; +} + +// You get back a response with the same [token] as your query. +message Response { + enum ResponseType { + // These response types indicate success. + SUCCESS_ATOM = 1; // Query returned a single RQL datatype. + SUCCESS_SEQUENCE = 2; // Query returned a sequence of RQL datatypes. + SUCCESS_PARTIAL = 3; // Query returned a partial sequence of RQL + // datatypes. If you send a [CONTINUE] query with + // the same token as this response, you will get + // more of the sequence. Keep sending [CONTINUE] + // queries until you get back [SUCCESS_SEQUENCE]. + SUCCESS_FEED = 5; // Like [SUCCESS_PARTIAL] but for feeds. + WAIT_COMPLETE = 4; // A [NOREPLY_WAIT] query completed. + SUCCESS_ATOM_FEED = 6; // Like [SUCCESS_FEED] but a singleton. + + // These response types indicate failure. + CLIENT_ERROR = 16; // Means the client is buggy. An example is if the + // client sends a malformed protobuf, or tries to + // send [CONTINUE] for an unknown token. + COMPILE_ERROR = 17; // Means the query failed during parsing or type + // checking. For example, if you pass too many + // arguments to a function. + RUNTIME_ERROR = 18; // Means the query failed at runtime. An example is + // if you add together two values from a table, but + // they turn out at runtime to be booleans rather + // than numbers. + } + optional ResponseType type = 1; + optional int64 token = 2; // Indicates what [Query] this response corresponds to. + + // [response] contains 1 RQL datum if [type] is [SUCCESS_ATOM], or many RQL + // data if [type] is [SUCCESS_SEQUENCE] or [SUCCESS_PARTIAL]. It contains 1 + // error message (of type [R_STR]) in all other cases. + repeated Datum response = 3; + + // If [type] is [CLIENT_ERROR], [TYPE_ERROR], or [RUNTIME_ERROR], then a + // backtrace will be provided. The backtrace says where in the query the + // error occured. Ideally this information will be presented to the user as + // a pretty-printed version of their query with the erroneous section + // underlined. A backtrace is a series of 0 or more [Frame]s, each of which + // specifies either the index of a positional argument or the name of an + // optional argument. (Those words will make more sense if you look at the + // [Term] message below.) + optional Backtrace backtrace = 4; // Contains n [Frame]s when you get back an error. + + // If the [global_optargs] in the [Query] that this [Response] is a + // response to contains a key "profile" which maps to a static value of + // true then [profile] will contain a [Datum] which provides profiling + // information about the execution of the query. This field should be + // returned to the user along with the result that would normally be + // returned (a datum or a cursor). In official drivers this is accomplished + // by putting them inside of an object with "value" mapping to the return + // value and "profile" mapping to the profile object. + optional Datum profile = 5; +} + +// A [Datum] is a chunk of data that can be serialized to disk or returned to +// the user in a Response. Currently we only support JSON types, but we may +// support other types in the future (e.g., a date type or an integer type). +message Datum { + enum DatumType { + R_NULL = 1; + R_BOOL = 2; + R_NUM = 3; // a double + R_STR = 4; + R_ARRAY = 5; + R_OBJECT = 6; + // This [DatumType] will only be used if [accepts_r_json] is + // set to [true] in [Query]. [r_str] will be filled with a + // JSON encoding of the [Datum]. + R_JSON = 7; // uses r_str + } + optional DatumType type = 1; + optional bool r_bool = 2; + optional double r_num = 3; + optional string r_str = 4; + + repeated Datum r_array = 5; + message AssocPair { + optional string key = 1; + optional Datum val = 2; + } + repeated AssocPair r_object = 6; + + extensions 10000 to 20000; +} + +// A [Term] is either a piece of data (see **Datum** above), or an operator and +// its operands. If you have a [Datum], it's stored in the member [datum]. If +// you have an operator, its positional arguments are stored in [args] and its +// optional arguments are stored in [optargs]. +// +// A note about type signatures: +// We use the following notation to denote types: +// arg1_type, arg2_type, argrest_type... -> result_type +// So, for example, if we have a function `avg` that takes any number of +// arguments and averages them, we might write: +// NUMBER... -> NUMBER +// Or if we had a function that took one number modulo another: +// NUMBER, NUMBER -> NUMBER +// Or a function that takes a table and a primary key of any Datum type, then +// retrieves the entry with that primary key: +// Table, DATUM -> OBJECT +// Some arguments must be provided as literal values (and not the results of sub +// terms). These are marked with a `!`. +// Optional arguments are specified within curly braces as argname `:` value +// type (e.x `{use_outdated:BOOL}`) +// Many RQL operations are polymorphic. For these, alterantive type signatures +// are separated by `|`. +// +// The RQL type hierarchy is as follows: +// Top +// DATUM +// NULL +// BOOL +// NUMBER +// STRING +// OBJECT +// SingleSelection +// ARRAY +// Sequence +// ARRAY +// Stream +// StreamSelection +// Table +// Database +// Function +// Ordering - used only by ORDER_BY +// Pathspec -- an object, string, or array that specifies a path +// Error +message Term { + enum TermType { + // A RQL datum, stored in `datum` below. + DATUM = 1; + + MAKE_ARRAY = 2; // DATUM... -> ARRAY + // Evaluate the terms in [optargs] and make an object + MAKE_OBJ = 3; // {...} -> OBJECT + + // * Compound types + + // Takes an integer representing a variable and returns the value stored + // in that variable. It's the responsibility of the client to translate + // from their local representation of a variable to a unique _non-negative_ + // integer for that variable. (We do it this way instead of letting + // clients provide variable names as strings to discourage + // variable-capturing client libraries, and because it's more efficient + // on the wire.) + VAR = 10; // !NUMBER -> DATUM + // Takes some javascript code and executes it. + JAVASCRIPT = 11; // STRING {timeout: !NUMBER} -> DATUM | + // STRING {timeout: !NUMBER} -> Function(*) + UUID = 169; // () -> DATUM + + // Takes an HTTP URL and gets it. If the get succeeds and + // returns valid JSON, it is converted into a DATUM + HTTP = 153; // STRING {data: OBJECT | STRING, + // timeout: !NUMBER, + // method: STRING, + // params: OBJECT, + // header: OBJECT | ARRAY, + // attempts: NUMBER, + // redirects: NUMBER, + // verify: BOOL, + // page: FUNC | STRING, + // page_limit: NUMBER, + // auth: OBJECT, + // result_format: STRING, + // } -> STRING | STREAM + + // Takes a string and throws an error with that message. + // Inside of a `default` block, you can omit the first + // argument to rethrow whatever error you catch (this is most + // useful as an argument to the `default` filter optarg). + ERROR = 12; // STRING -> Error | -> Error + // Takes nothing and returns a reference to the implicit variable. + IMPLICIT_VAR = 13; // -> DATUM + + // * Data Operators + // Returns a reference to a database. + DB = 14; // STRING -> Database + // Returns a reference to a table. + TABLE = 15; // Database, STRING, {use_outdated:BOOL, identifier_format:STRING} -> Table + // STRING, {use_outdated:BOOL, identifier_format:STRING} -> Table + // Gets a single element from a table by its primary or a secondary key. + GET = 16; // Table, STRING -> SingleSelection | Table, NUMBER -> SingleSelection | + // Table, STRING -> NULL | Table, NUMBER -> NULL | + GET_ALL = 78; // Table, DATUM..., {index:!STRING} => ARRAY + + // Simple DATUM Ops + EQ = 17; // DATUM... -> BOOL + NE = 18; // DATUM... -> BOOL + LT = 19; // DATUM... -> BOOL + LE = 20; // DATUM... -> BOOL + GT = 21; // DATUM... -> BOOL + GE = 22; // DATUM... -> BOOL + NOT = 23; // BOOL -> BOOL + // ADD can either add two numbers or concatenate two arrays. + ADD = 24; // NUMBER... -> NUMBER | STRING... -> STRING + SUB = 25; // NUMBER... -> NUMBER + MUL = 26; // NUMBER... -> NUMBER + DIV = 27; // NUMBER... -> NUMBER + MOD = 28; // NUMBER, NUMBER -> NUMBER + + // DATUM Array Ops + // Append a single element to the end of an array (like `snoc`). + APPEND = 29; // ARRAY, DATUM -> ARRAY + // Prepend a single element to the end of an array (like `cons`). + PREPEND = 80; // ARRAY, DATUM -> ARRAY + //Remove the elements of one array from another array. + DIFFERENCE = 95; // ARRAY, ARRAY -> ARRAY + + // DATUM Set Ops + // Set ops work on arrays. They don't use actual sets and thus have + // performance characteristics you would expect from arrays rather than + // from sets. All set operations have the post condition that they + // array they return contains no duplicate values. + SET_INSERT = 88; // ARRAY, DATUM -> ARRAY + SET_INTERSECTION = 89; // ARRAY, ARRAY -> ARRAY + SET_UNION = 90; // ARRAY, ARRAY -> ARRAY + SET_DIFFERENCE = 91; // ARRAY, ARRAY -> ARRAY + + SLICE = 30; // Sequence, NUMBER, NUMBER -> Sequence + SKIP = 70; // Sequence, NUMBER -> Sequence + LIMIT = 71; // Sequence, NUMBER -> Sequence + INDEXES_OF = 87; // Sequence, DATUM -> Sequence | Sequence, Function(1) -> Sequence + CONTAINS = 93; // Sequence, DATUM -> BOOL | Sequence, Function(1) -> BOOL + + // Stream/Object Ops + // Get a particular field from an object, or map that over a + // sequence. + GET_FIELD = 31; // OBJECT, STRING -> DATUM + // | Sequence, STRING -> Sequence + // Return an array containing the keys of the object. + KEYS = 94; // OBJECT -> ARRAY + // Creates an object + OBJECT = 143; // STRING, DATUM, ... -> OBJECT + // Check whether an object contains all the specified fields, + // or filters a sequence so that all objects inside of it + // contain all the specified fields. + HAS_FIELDS = 32; // OBJECT, Pathspec... -> BOOL + // x.with_fields(...) <=> x.has_fields(...).pluck(...) + WITH_FIELDS = 96; // Sequence, Pathspec... -> Sequence + // Get a subset of an object by selecting some attributes to preserve, + // or map that over a sequence. (Both pick and pluck, polymorphic.) + PLUCK = 33; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT + // Get a subset of an object by selecting some attributes to discard, or + // map that over a sequence. (Both unpick and without, polymorphic.) + WITHOUT = 34; // Sequence, Pathspec... -> Sequence | OBJECT, Pathspec... -> OBJECT + // Merge objects (right-preferential) + MERGE = 35; // OBJECT... -> OBJECT | Sequence -> Sequence + + // Sequence Ops + // Get all elements of a sequence between two values. + // Half-open by default, but the openness of either side can be + // changed by passing 'closed' or 'open for `right_bound` or + // `left_bound`. + BETWEEN = 36; // StreamSelection, DATUM, DATUM, {index:!STRING, right_bound:STRING, left_bound:STRING} -> StreamSelection + REDUCE = 37; // Sequence, Function(2) -> DATUM + MAP = 38; // Sequence, Function(1) -> Sequence + // The arity of the function should be + // Sequence..., Function(sizeof...(Sequence)) -> Sequence + + // Filter a sequence with either a function or a shortcut + // object (see API docs for details). The body of FILTER is + // wrapped in an implicit `.default(false)`, and you can + // change the default value by specifying the `default` + // optarg. If you make the default `r.error`, all errors + // caught by `default` will be rethrown as if the `default` + // did not exist. + FILTER = 39; // Sequence, Function(1), {default:DATUM} -> Sequence | + // Sequence, OBJECT, {default:DATUM} -> Sequence + // Map a function over a sequence and then concatenate the results together. + CONCAT_MAP = 40; // Sequence, Function(1) -> Sequence + // Order a sequence based on one or more attributes. + ORDER_BY = 41; // Sequence, (!STRING | Ordering)... -> Sequence + // Get all distinct elements of a sequence (like `uniq`). + DISTINCT = 42; // Sequence -> Sequence + // Count the number of elements in a sequence, or only the elements that match + // a given filter. + COUNT = 43; // Sequence -> NUMBER | Sequence, DATUM -> NUMBER | Sequence, Function(1) -> NUMBER + IS_EMPTY = 86; // Sequence -> BOOL + // Take the union of multiple sequences (preserves duplicate elements! (use distinct)). + UNION = 44; // Sequence... -> Sequence + // Get the Nth element of a sequence. + NTH = 45; // Sequence, NUMBER -> DATUM + // do NTH or GET_FIELD depending on target object + BRACKET = 170; // Sequence | OBJECT, NUMBER | STRING -> DATUM + // OBSOLETE_GROUPED_MAPREDUCE = 46; + // OBSOLETE_GROUPBY = 47; + + INNER_JOIN = 48; // Sequence, Sequence, Function(2) -> Sequence + OUTER_JOIN = 49; // Sequence, Sequence, Function(2) -> Sequence + // An inner-join that does an equality comparison on two attributes. + EQ_JOIN = 50; // Sequence, !STRING, Sequence, {index:!STRING} -> Sequence + ZIP = 72; // Sequence -> Sequence + RANGE = 173; // -> Sequence [0, +inf) + // NUMBER -> Sequence [0, a) + // NUMBER, NUMBER -> Sequence [a, b) + + // Array Ops + // Insert an element in to an array at a given index. + INSERT_AT = 82; // ARRAY, NUMBER, DATUM -> ARRAY + // Remove an element at a given index from an array. + DELETE_AT = 83; // ARRAY, NUMBER -> ARRAY | + // ARRAY, NUMBER, NUMBER -> ARRAY + // Change the element at a given index of an array. + CHANGE_AT = 84; // ARRAY, NUMBER, DATUM -> ARRAY + // Splice one array in to another array. + SPLICE_AT = 85; // ARRAY, NUMBER, ARRAY -> ARRAY + + // * Type Ops + // Coerces a datum to a named type (e.g. "bool"). + // If you previously used `stream_to_array`, you should use this instead + // with the type "array". + COERCE_TO = 51; // Top, STRING -> Top + // Returns the named type of a datum (e.g. TYPE_OF(true) = "BOOL") + TYPE_OF = 52; // Top -> STRING + + // * Write Ops (the OBJECTs contain data about number of errors etc.) + // Updates all the rows in a selection. Calls its Function with the row + // to be updated, and then merges the result of that call. + UPDATE = 53; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | + // SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | + // StreamSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | + // SingleSelection, OBJECT, {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT + // Deletes all the rows in a selection. + DELETE = 54; // StreamSelection, {durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection -> OBJECT + // Replaces all the rows in a selection. Calls its Function with the row + // to be replaced, and then discards it and stores the result of that + // call. + REPLACE = 55; // StreamSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT | SingleSelection, Function(1), {non_atomic:BOOL, durability:STRING, return_changes:BOOL} -> OBJECT + // Inserts into a table. If `conflict` is replace, overwrites + // entries with the same primary key. If `conflict` is + // update, does an update on the entry. If `conflict` is + // error, or is omitted, conflicts will trigger an error. + INSERT = 56; // Table, OBJECT, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT | Table, Sequence, {conflict:STRING, durability:STRING, return_changes:BOOL} -> OBJECT + + // * Administrative OPs + // Creates a database with a particular name. + DB_CREATE = 57; // STRING -> OBJECT + // Drops a database with a particular name. + DB_DROP = 58; // STRING -> OBJECT + // Lists all the databases by name. (Takes no arguments) + DB_LIST = 59; // -> ARRAY + // Creates a table with a particular name in a particular + // database. (You may omit the first argument to use the + // default database.) + TABLE_CREATE = 60; // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT + // Database, STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT + // STRING, {primary_key:STRING, shards:NUMBER, replicas:NUMBER, primary_replica_tag:STRING} -> OBJECT + // STRING, {primary_key:STRING, shards:NUMBER, replicas:OBJECT, primary_replica_tag:STRING} -> OBJECT + // Drops a table with a particular name from a particular + // database. (You may omit the first argument to use the + // default database.) + TABLE_DROP = 61; // Database, STRING -> OBJECT + // STRING -> OBJECT + // Lists all the tables in a particular database. (You may + // omit the first argument to use the default database.) + TABLE_LIST = 62; // Database -> ARRAY + // -> ARRAY + // Returns the row in the `rethinkdb.table_config` or `rethinkdb.db_config` table + // that corresponds to the given database or table. + CONFIG = 174; // Database -> SingleSelection + // Table -> SingleSelection + // Returns the row in the `rethinkdb.table_status` table that corresponds to the + // given table. + STATUS = 175; // Table -> SingleSelection + // Called on a table, waits for that table to be ready for read/write operations. + // Called on a database, waits for all of the tables in the database to be ready. + // Returns the corresponding row or rows from the `rethinkdb.table_status` table. + WAIT = 177; // Table -> OBJECT + // Database -> OBJECT + // Generates a new config for the given table, or all tables in the given database + // The `shards` and `replicas` arguments are required + RECONFIGURE = 176; // Database, {shards:NUMBER, replicas:NUMBER[, primary_replica_tag:STRING, dry_run:BOOLEAN]} -> OBJECT + // Database, {shards:NUMBER, replicas:OBJECT[, primary_replica_tag:STRING, dry_run:BOOLEAN]} -> OBJECT + // Table, {shards:NUMBER, replicas:NUMBER[, primary_replica_tag:STRING, dry_run:BOOLEAN]} -> OBJECT + // Table, {shards:NUMBER, replicas:OBJECT[, primary_replica_tag:STRING, dry_run:BOOLEAN]} -> OBJECT + // Balances the table's shards but leaves everything else the same. Can also be + // applied to an entire database at once. + REBALANCE = 179; // Table -> OBJECT + // Database -> OBJECT + + // Ensures that previously issued soft-durability writes are complete and + // written to disk. + SYNC = 138; // Table -> OBJECT + + // * Secondary indexes OPs + // Creates a new secondary index with a particular name and definition. + INDEX_CREATE = 75; // Table, STRING, Function(1), {multi:BOOL} -> OBJECT + // Drops a secondary index with a particular name from the specified table. + INDEX_DROP = 76; // Table, STRING -> OBJECT + // Lists all secondary indexes on a particular table. + INDEX_LIST = 77; // Table -> ARRAY + // Gets information about whether or not a set of indexes are ready to + // be accessed. Returns a list of objects that look like this: + // {index:STRING, ready:BOOL[, blocks_processed:NUMBER, blocks_total:NUMBER]} + INDEX_STATUS = 139; // Table, STRING... -> ARRAY + // Blocks until a set of indexes are ready to be accessed. Returns the + // same values INDEX_STATUS. + INDEX_WAIT = 140; // Table, STRING... -> ARRAY + // Renames the given index to a new name + INDEX_RENAME = 156; // Table, STRING, STRING, {overwrite:BOOL} -> OBJECT + + // * Control Operators + // Calls a function on data + FUNCALL = 64; // Function(*), DATUM... -> DATUM + // Executes its first argument, and returns its second argument if it + // got [true] or its third argument if it got [false] (like an `if` + // statement). + BRANCH = 65; // BOOL, Top, Top -> Top + // Returns true if any of its arguments returns true (short-circuits). + // (Like `or` in most languages.) + ANY = 66; // BOOL... -> BOOL + // Returns true if all of its arguments return true (short-circuits). + // (Like `and` in most languages.) + ALL = 67; // BOOL... -> BOOL + // Calls its Function with each entry in the sequence + // and executes the array of terms that Function returns. + FOR_EACH = 68; // Sequence, Function(1) -> OBJECT + +//////////////////////////////////////////////////////////////////////////////// +////////// Special Terms +//////////////////////////////////////////////////////////////////////////////// + + // An anonymous function. Takes an array of numbers representing + // variables (see [VAR] above), and a [Term] to execute with those in + // scope. Returns a function that may be passed an array of arguments, + // then executes the Term with those bound to the variable names. The + // user will never construct this directly. We use it internally for + // things like `map` which take a function. The "arity" of a [Function] is + // the number of arguments it takes. + // For example, here's what `_X_.map{|x| x+2}` turns into: + // Term { + // type = MAP; + // args = [_X_, + // Term { + // type = Function; + // args = [Term { + // type = DATUM; + // datum = Datum { + // type = R_ARRAY; + // r_array = [Datum { type = R_NUM; r_num = 1; }]; + // }; + // }, + // Term { + // type = ADD; + // args = [Term { + // type = VAR; + // args = [Term { + // type = DATUM; + // datum = Datum { type = R_NUM; + // r_num = 1}; + // }]; + // }, + // Term { + // type = DATUM; + // datum = Datum { type = R_NUM; r_num = 2; }; + // }]; + // }]; + // }]; + FUNC = 69; // ARRAY, Top -> ARRAY -> Top + + // Indicates to ORDER_BY that this attribute is to be sorted in ascending order. + ASC = 73; // !STRING -> Ordering + // Indicates to ORDER_BY that this attribute is to be sorted in descending order. + DESC = 74; // !STRING -> Ordering + + // Gets info about anything. INFO is most commonly called on tables. + INFO = 79; // Top -> OBJECT + + // `a.match(b)` returns a match object if the string `a` + // matches the regular expression `b`. + MATCH = 97; // STRING, STRING -> DATUM + + // Change the case of a string. + UPCASE = 141; // STRING -> STRING + DOWNCASE = 142; // STRING -> STRING + + // Select a number of elements from sequence with uniform distribution. + SAMPLE = 81; // Sequence, NUMBER -> Sequence + + // Evaluates its first argument. If that argument returns + // NULL or throws an error related to the absence of an + // expected value (for instance, accessing a non-existent + // field or adding NULL to an integer), DEFAULT will either + // return its second argument or execute it if it's a + // function. If the second argument is a function, it will be + // passed either the text of the error or NULL as its + // argument. + DEFAULT = 92; // Top, Top -> Top + + // Parses its first argument as a json string and returns it as a + // datum. + JSON = 98; // STRING -> DATUM + // Returns the datum as a JSON string. + // N.B.: we would really prefer this be named TO_JSON and that exists as + // an alias in Python and JavaScript drivers; however it conflicts with the + // standard `to_json` method defined by Ruby's standard json library. + TO_JSON_STRING = 172; // DATUM -> STRING + + // Parses its first arguments as an ISO 8601 time and returns it as a + // datum. + ISO8601 = 99; // STRING -> PSEUDOTYPE(TIME) + // Prints a time as an ISO 8601 time. + TO_ISO8601 = 100; // PSEUDOTYPE(TIME) -> STRING + + // Returns a time given seconds since epoch in UTC. + EPOCH_TIME = 101; // NUMBER -> PSEUDOTYPE(TIME) + // Returns seconds since epoch in UTC given a time. + TO_EPOCH_TIME = 102; // PSEUDOTYPE(TIME) -> NUMBER + + // The time the query was received by the server. + NOW = 103; // -> PSEUDOTYPE(TIME) + // Puts a time into an ISO 8601 timezone. + IN_TIMEZONE = 104; // PSEUDOTYPE(TIME), STRING -> PSEUDOTYPE(TIME) + // a.during(b, c) returns whether a is in the range [b, c) + DURING = 105; // PSEUDOTYPE(TIME), PSEUDOTYPE(TIME), PSEUDOTYPE(TIME) -> BOOL + // Retrieves the date portion of a time. + DATE = 106; // PSEUDOTYPE(TIME) -> PSEUDOTYPE(TIME) + // x.time_of_day == x.date - x + TIME_OF_DAY = 126; // PSEUDOTYPE(TIME) -> NUMBER + // Returns the timezone of a time. + TIMEZONE = 127; // PSEUDOTYPE(TIME) -> STRING + + // These access the various components of a time. + YEAR = 128; // PSEUDOTYPE(TIME) -> NUMBER + MONTH = 129; // PSEUDOTYPE(TIME) -> NUMBER + DAY = 130; // PSEUDOTYPE(TIME) -> NUMBER + DAY_OF_WEEK = 131; // PSEUDOTYPE(TIME) -> NUMBER + DAY_OF_YEAR = 132; // PSEUDOTYPE(TIME) -> NUMBER + HOURS = 133; // PSEUDOTYPE(TIME) -> NUMBER + MINUTES = 134; // PSEUDOTYPE(TIME) -> NUMBER + SECONDS = 135; // PSEUDOTYPE(TIME) -> NUMBER + + // Construct a time from a date and optional timezone or a + // date+time and optional timezone. + TIME = 136; // NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) | + // NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, STRING -> PSEUDOTYPE(TIME) | + + // Constants for ISO 8601 days of the week. + MONDAY = 107; // -> 1 + TUESDAY = 108; // -> 2 + WEDNESDAY = 109; // -> 3 + THURSDAY = 110; // -> 4 + FRIDAY = 111; // -> 5 + SATURDAY = 112; // -> 6 + SUNDAY = 113; // -> 7 + + // Constants for ISO 8601 months. + JANUARY = 114; // -> 1 + FEBRUARY = 115; // -> 2 + MARCH = 116; // -> 3 + APRIL = 117; // -> 4 + MAY = 118; // -> 5 + JUNE = 119; // -> 6 + JULY = 120; // -> 7 + AUGUST = 121; // -> 8 + SEPTEMBER = 122; // -> 9 + OCTOBER = 123; // -> 10 + NOVEMBER = 124; // -> 11 + DECEMBER = 125; // -> 12 + + // Indicates to MERGE to replace the other object rather than merge it. + LITERAL = 137; // JSON -> Merging + + // SEQUENCE, STRING -> GROUPED_SEQUENCE | SEQUENCE, FUNCTION -> GROUPED_SEQUENCE + GROUP = 144; + SUM = 145; + AVG = 146; + MIN = 147; + MAX = 148; + + // `str.split()` splits on whitespace + // `str.split(" ")` splits on spaces only + // `str.split(" ", 5)` splits on spaces with at most 5 results + // `str.split(nil, 5)` splits on whitespace with at most 5 results + SPLIT = 149; // STRING -> ARRAY | STRING, STRING -> ARRAY | STRING, STRING, NUMBER -> ARRAY | STRING, NULL, NUMBER -> ARRAY + + UNGROUP = 150; // GROUPED_DATA -> ARRAY + + // Takes a range of numbers and returns a random number within the range + RANDOM = 151; // NUMBER, NUMBER {float:BOOL} -> DATUM + + CHANGES = 152; // TABLE -> STREAM + ARGS = 154; // ARRAY -> SPECIAL (used to splice arguments) + + // BINARY is client-only at the moment, it is not supported on the server + BINARY = 155; // STRING -> PSEUDOTYPE(BINARY) + + GEOJSON = 157; // OBJECT -> PSEUDOTYPE(GEOMETRY) + TO_GEOJSON = 158; // PSEUDOTYPE(GEOMETRY) -> OBJECT + POINT = 159; // NUMBER, NUMBER -> PSEUDOTYPE(GEOMETRY) + LINE = 160; // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY) + POLYGON = 161; // (ARRAY | PSEUDOTYPE(GEOMETRY))... -> PSEUDOTYPE(GEOMETRY) + DISTANCE = 162; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) {geo_system:STRING, unit:STRING} -> NUMBER + INTERSECTS = 163; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL + INCLUDES = 164; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> BOOL + CIRCLE = 165; // PSEUDOTYPE(GEOMETRY), NUMBER {num_vertices:NUMBER, geo_system:STRING, unit:STRING, fill:BOOL} -> PSEUDOTYPE(GEOMETRY) + GET_INTERSECTING = 166; // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING} -> StreamSelection + FILL = 167; // PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY) + GET_NEAREST = 168; // TABLE, PSEUDOTYPE(GEOMETRY) {index:!STRING, max_results:NUM, max_dist:NUM, geo_system:STRING, unit:STRING} -> ARRAY + POLYGON_SUB = 171; // PSEUDOTYPE(GEOMETRY), PSEUDOTYPE(GEOMETRY) -> PSEUDOTYPE(GEOMETRY) + } + optional TermType type = 1; + + // This is only used when type is DATUM. + optional Datum datum = 2; + + repeated Term args = 3; // Holds the positional arguments of the query. + message AssocPair { + optional string key = 1; + optional Term val = 2; + } + repeated AssocPair optargs = 4; // Holds the optional arguments of the query. + // (Note that the order of the optional arguments doesn't matter; think of a + // Hash.) + + extensions 10000 to 20000; +} + +//////////////////////////////////////////////////////////////////////////////// +// EXAMPLE // +//////////////////////////////////////////////////////////////////////////////// +// ```ruby +// r.table('tbl', {:use_outdated => true}).insert([{:id => 0}, {:id => 1}]) +// ``` +// Would turn into: +// Term { +// type = INSERT; +// args = [Term { +// type = TABLE; +// args = [Term { +// type = DATUM; +// datum = Datum { type = R_STR; r_str = "tbl"; }; +// }]; +// optargs = [["use_outdated", +// Term { +// type = DATUM; +// datum = Datum { type = R_BOOL; r_bool = true; }; +// }]]; +// }, +// Term { +// type = MAKE_ARRAY; +// args = [Term { +// type = DATUM; +// datum = Datum { type = R_OBJECT; r_object = [["id", 0]]; }; +// }, +// Term { +// type = DATUM; +// datum = Datum { type = R_OBJECT; r_object = [["id", 1]]; }; +// }]; +// }] +// } +// And the server would reply: +// Response { +// type = SUCCESS_ATOM; +// token = 1; +// response = [Datum { type = R_OBJECT; r_object = [["inserted", 2]]; }]; +// } +// Or, if there were an error: +// Response { +// type = RUNTIME_ERROR; +// token = 1; +// response = [Datum { type = R_STR; r_str = "The table `tbl` doesn't exist!"; }]; +// backtrace = [Frame { type = POS; pos = 0; }, Frame { type = POS; pos = 0; }]; +// } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go new file mode 100644 index 0000000..927871d --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query.go @@ -0,0 +1,274 @@ +package gorethink + +import ( + "fmt" + "strconv" + "strings" + + p "github.com/dancannon/gorethink/ql2" +) + +type Query struct { + Type p.Query_QueryType + Token int64 + Term *Term + Opts map[string]interface{} +} + +func (q *Query) build() []interface{} { + res := []interface{}{int(q.Type)} + if q.Term != nil { + res = append(res, q.Term.build()) + } + + if len(q.Opts) > 0 { + res = append(res, q.Opts) + } + + return res +} + +type termsList []Term +type termsObj map[string]Term +type Term struct { + name string + rootTerm bool + termType p.Term_TermType + data interface{} + args []Term + optArgs map[string]Term +} + +// build takes the query tree and prepares it to be sent as a JSON +// expression +func (t Term) build() interface{} { + switch t.termType { + case p.Term_DATUM: + return t.data + case p.Term_MAKE_OBJ: + res := map[string]interface{}{} + for k, v := range t.optArgs { + res[k] = v.build() + } + return res + case p.Term_BINARY: + if len(t.args) == 0 { + return map[string]interface{}{ + "$reql_type$": "BINARY", + "data": t.data, + } + } + } + + args := []interface{}{} + optArgs := map[string]interface{}{} + + for _, v := range t.args { + args = append(args, v.build()) + } + + for k, v := range t.optArgs { + optArgs[k] = v.build() + } + + ret := []interface{}{int(t.termType)} + + if len(args) > 0 { + ret = append(ret, args) + } + if len(optArgs) > 0 { + ret = append(ret, optArgs) + } + + return ret +} + +// String returns a string representation of the query tree +func (t Term) String() string { + switch t.termType { + case p.Term_MAKE_ARRAY: + return fmt.Sprintf("[%s]", strings.Join(argsToStringSlice(t.args), ", ")) + case p.Term_MAKE_OBJ: + return fmt.Sprintf("{%s}", strings.Join(optArgsToStringSlice(t.optArgs), ", ")) + case p.Term_FUNC: + // Get string representation of each argument + args := []string{} + for _, v := range t.args[0].args { + args = append(args, fmt.Sprintf("var_%d", v.data)) + } + + return fmt.Sprintf("func(%s r.Term) r.Term { return %s }", + strings.Join(args, ", "), + t.args[1].String(), + ) + case p.Term_VAR: + return fmt.Sprintf("var_%s", t.args[0]) + case p.Term_IMPLICIT_VAR: + return "r.Row" + case p.Term_DATUM: + switch v := t.data.(type) { + case string: + return strconv.Quote(v) + default: + return fmt.Sprintf("%v", v) + } + case p.Term_BINARY: + if len(t.args) == 0 { + return fmt.Sprintf("r.binary()") + } + } + + if t.rootTerm { + return fmt.Sprintf("r.%s(%s)", t.name, strings.Join(allArgsToStringSlice(t.args, t.optArgs), ", ")) + } + return fmt.Sprintf("%s.%s(%s)", t.args[0].String(), t.name, strings.Join(allArgsToStringSlice(t.args[1:], t.optArgs), ", ")) +} + +type OptArgs interface { + toMap() map[string]interface{} +} + +type WriteResponse struct { + Errors int `gorethink:"errors"` + Inserted int `gorethink:"inserted"` + Updated int `gorethink:"updadte"` + Unchanged int `gorethink:"unchanged"` + Replaced int `gorethink:"replaced"` + Renamed int `gorethink:"renamed"` + Skipped int `gorethink:"skipped"` + Deleted int `gorethink:"deleted"` + Created int `gorethink:"created"` + DBsCreated int `gorethink:"dbs_created"` + TablesCreated int `gorethink:"tables_created"` + Dropped int `gorethink:"dropped"` + DBsDropped int `gorethink:"dbs_dropped"` + TablesDropped int `gorethink:"tables_dropped"` + GeneratedKeys []string `gorethink:"generated_keys"` + FirstError string `gorethink:"first_error"` // populated if Errors > 0 + ConfigChanges []WriteChanges `gorethink:"config_changes"` + Changes []WriteChanges +} + +type WriteChanges struct { + NewValue interface{} `gorethink:"new_val"` + OldValue interface{} `gorethink:"old_val"` +} + +type RunOpts struct { + Db interface{} `gorethink:"db,omitempty"` + Profile interface{} `gorethink:"profile,omitempty"` + UseOutdated interface{} `gorethink:"use_outdated,omitempty"` + ArrayLimit interface{} `gorethink:"array_limit,omitempty"` + TimeFormat interface{} `gorethink:"time_format,omitempty"` + GroupFormat interface{} `gorethink:"group_format,omitempty"` + BinaryFormat interface{} `gorethink:"binary_format,omitempty"` + GeometryFormat interface{} `gorethink:"geometry_format,omitempty"` + + MinBatchRows interface{} `gorethink:"min_batch_rows,omitempty"` + MaxBatchRows interface{} `gorethink:"max_batch_rows,omitempty"` + MaxBatchBytes interface{} `gorethink:"max_batch_bytes,omitempty"` + MaxBatchSeconds interface{} `gorethink:"max_batch_seconds,omitempty"` + FirstBatchScaledownFactor interface{} `gorethink:"first_batch_scaledown_factor,omitempty"` +} + +func (o *RunOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Run runs a query using the given connection. +// +// rows, err := query.Run(sess) +// if err != nil { +// // error +// } +// +// var doc MyDocumentType +// for rows.Next(&doc) { +// // Do something with document +// } +func (t Term) Run(s *Session, optArgs ...RunOpts) (*Cursor, error) { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + q := newStartQuery(s, t, opts) + + return s.pool.Query(q) +} + +// RunWrite runs a query using the given connection but unlike Run automatically +// scans the result into a variable of type WriteResponse. This function should be used +// if you are running a write query (such as Insert, Update, TableCreate, etc...) +// +// res, err := r.Db("database").Table("table").Insert(doc).RunWrite(sess) +func (t Term) RunWrite(s *Session, optArgs ...RunOpts) (WriteResponse, error) { + var response WriteResponse + res, err := t.Run(s, optArgs...) + if err == nil { + err = res.One(&response) + } + return response, err +} + +// ExecOpts inherits its options from RunOpts, the only difference is the +// addition of the NoReply field. +// +// When NoReply is true it causes the driver not to wait to receive the result +// and return immediately. +type ExecOpts struct { + Db interface{} `gorethink:"db,omitempty"` + Profile interface{} `gorethink:"profile,omitempty"` + UseOutdated interface{} `gorethink:"use_outdated,omitempty"` + ArrayLimit interface{} `gorethink:"array_limit,omitempty"` + TimeFormat interface{} `gorethink:"time_format,omitempty"` + GroupFormat interface{} `gorethink:"group_format,omitempty"` + BinaryFormat interface{} `gorethink:"binary_format,omitempty"` + GeometryFormat interface{} `gorethink:"geometry_format,omitempty"` + + MinBatchRows interface{} `gorethink:"min_batch_rows,omitempty"` + MaxBatchRows interface{} `gorethink:"max_batch_rows,omitempty"` + MaxBatchBytes interface{} `gorethink:"max_batch_bytes,omitempty"` + MaxBatchSeconds interface{} `gorethink:"max_batch_seconds,omitempty"` + FirstBatchScaledownFactor interface{} `gorethink:"first_batch_scaledown_factor,omitempty"` + + NoReply interface{} `gorethink:"noreply,omitempty"` +} + +func (o *ExecOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Exec runs the query but does not return the result. Exec will still wait for +// the response to be received unless the NoReply field is true. +// +// res, err := r.Db("database").Table("table").Insert(doc).Exec(sess, r.ExecOpts{ +// NoReply: true, +// }) +func (t Term) Exec(s *Session, optArgs ...ExecOpts) error { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + q := newStartQuery(s, t, opts) + + return s.pool.Exec(q) +} + +func newStartQuery(s *Session, t Term, opts map[string]interface{}) Query { + queryOpts := map[string]interface{}{} + for k, v := range opts { + queryOpts[k] = Expr(v).build() + } + if s.opts.Database != "" { + queryOpts["db"] = Db(s.opts.Database).build() + } + + // Construct query + return Query{ + Type: p.Query_START, + Term: &t, + Opts: queryOpts, + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go new file mode 100644 index 0000000..26b9a2a --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin.go @@ -0,0 +1,52 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Config can be used to read and/or update the configurations for individual +// tables or databases. +func (t Term) Config() Term { + return constructMethodTerm(t, "Config", p.Term_CONFIG, []interface{}{}, map[string]interface{}{}) +} + +// Rebalance rebalances the shards of a table. When called on a database, all +// the tables in that database will be rebalanced. +func (t Term) Rebalance() Term { + return constructMethodTerm(t, "Rebalance", p.Term_REBALANCE, []interface{}{}, map[string]interface{}{}) +} + +type ReconfigureOpts struct { + Shards interface{} `gorethink:"shards,omitempty"` + Replicas interface{} `gorethink:"replicas,omitempty"` + PrimaryTag interface{} `gorethink:"primary_replicas_tag,omitempty"` + DryRun interface{} `gorethink:"dry_run,omitempty"` +} + +func (o *ReconfigureOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Reconfigure a table's sharding and replication. +func (t Term) Reconfigure(opts ReconfigureOpts) Term { + return constructMethodTerm(t, "Reconfigure", p.Term_RECONFIGURE, []interface{}{}, opts.toMap()) +} + +// Status return the status of a table +func (t Term) Status() Term { + return constructMethodTerm(t, "Status", p.Term_STATUS, []interface{}{}, map[string]interface{}{}) +} + +// Wait for a table or all the tables in a database to be ready. A table may be +// temporarily unavailable after creation, rebalancing or reconfiguring. The +// wait command blocks until the given table (or database) is fully up to date. +func Wait() Term { + return constructRootTerm("Wait", p.Term_WAIT, []interface{}{}, map[string]interface{}{}) +} + +// Wait for a table or all the tables in a database to be ready. A table may be +// temporarily unavailable after creation, rebalancing or reconfiguring. The +// wait command blocks until the given table (or database) is fully up to date. +func (t Term) Wait() Term { + return constructMethodTerm(t, "Wait", p.Term_WAIT, []interface{}{}, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go new file mode 100644 index 0000000..a74d6fe --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_admin_test.go @@ -0,0 +1,91 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestAdminDbConfig(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + + // Test index rename + query := Db("test").Table("test").Config() + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["name"], test.Equals, "test") +} + +func (s *RethinkSuite) TestAdminTableConfig(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + + // Test index rename + query := Db("test").Config() + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["name"], test.Equals, "test") +} + +func (s *RethinkSuite) TestAdminTableStatus(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + + // Test index rename + query := Db("test").Table("test").Status() + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["name"], test.Equals, "test") + c.Assert(response["status"], test.NotNil) +} + +func (s *RethinkSuite) TestAdminWait(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + + // Test index rename + query := Wait() + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["ready"].(float64) > 0, test.Equals, true) +} + +func (s *RethinkSuite) TestAdminStatus(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + + // Test index rename + query := Db("test").Table("test").Wait() + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + var response map[string]interface{} + err = res.One(&response) + c.Assert(err, test.IsNil) + + c.Assert(response["ready"], test.Equals, float64(1)) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go new file mode 100644 index 0000000..ae387fe --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation.go @@ -0,0 +1,133 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Aggregation +// These commands are used to compute smaller values from large sequences. + +// Reduce produces a single value from a sequence through repeated application +// of a reduction function +// +// It takes one argument of type `func (r.Term, r.Term) interface{}`, for +// example this query sums all elements in an array: +// +// r.Expr([]int{1,3,6}).Reduce(func (left, right r.Term) interface{} { +// return left.Add(right) +// }) +func (t Term) Reduce(args ...interface{}) Term { + return constructMethodTerm(t, "Reduce", p.Term_REDUCE, funcWrapArgs(args), map[string]interface{}{}) +} + +type DistinctOpts struct { + Index interface{} `gorethink:"index,omitempty"` +} + +func (o *DistinctOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Remove duplicate elements from the sequence. +func (t Term) Distinct(optArgs ...DistinctOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Distinct", p.Term_DISTINCT, []interface{}{}, opts) +} + +// Takes a stream and partitions it into multiple groups based on the +// fields or functions provided. Commands chained after group will be +// called on each of these grouped sub-streams, producing grouped data. +func (t Term) Group(fieldOrFunctions ...interface{}) Term { + return constructMethodTerm(t, "Group", p.Term_GROUP, funcWrapArgs(fieldOrFunctions), map[string]interface{}{}) +} + +// Takes a stream and partitions it into multiple groups based on the +// fields or functions provided. Commands chained after group will be +// called on each of these grouped sub-streams, producing grouped data. +func (t Term) GroupByIndex(index interface{}, fieldOrFunctions ...interface{}) Term { + return constructMethodTerm(t, "Group", p.Term_GROUP, funcWrapArgs(fieldOrFunctions), map[string]interface{}{ + "index": index, + }) +} + +func (t Term) Ungroup(args ...interface{}) Term { + return constructMethodTerm(t, "Ungroup", p.Term_UNGROUP, args, map[string]interface{}{}) +} + +//Returns whether or not a sequence contains all the specified values, or if +//functions are provided instead, returns whether or not a sequence contains +//values matching all the specified functions. +func (t Term) Contains(args ...interface{}) Term { + return constructMethodTerm(t, "Contains", p.Term_CONTAINS, args, map[string]interface{}{}) +} + +// Aggregators +// These standard aggregator objects are to be used in conjunction with group_by. + +// Count the number of elements in the sequence. With a single argument, +// count the number of elements equal to it. If the argument is a function, +// it is equivalent to calling filter before count. +func (t Term) Count(args ...interface{}) Term { + return constructMethodTerm(t, "Count", p.Term_COUNT, funcWrapArgs(args), map[string]interface{}{}) +} + +// Sums all the elements of a sequence. If called with a field name, sums all +// the values of that field in the sequence, skipping elements of the sequence +// that lack that field. If called with a function, calls that function on every +// element of the sequence and sums the results, skipping elements of the +// sequence where that function returns null or a non-existence error. +func (t Term) Sum(args ...interface{}) Term { + return constructMethodTerm(t, "Sum", p.Term_SUM, funcWrapArgs(args), map[string]interface{}{}) +} + +// Averages all the elements of a sequence. If called with a field name, averages +// all the values of that field in the sequence, skipping elements of the sequence +// that lack that field. If called with a function, calls that function on every +// element of the sequence and averages the results, skipping elements of the +// sequence where that function returns null or a non-existence error. +func (t Term) Avg(args ...interface{}) Term { + return constructMethodTerm(t, "Avg", p.Term_AVG, funcWrapArgs(args), map[string]interface{}{}) +} + +// Finds the minimum of a sequence. If called with a field name, finds the element +// of that sequence with the smallest value in that field. If called with a function, +// calls that function on every element of the sequence and returns the element +// which produced the smallest value, ignoring any elements where the function +// returns null or produces a non-existence error. +func (t Term) Min(args ...interface{}) Term { + return constructMethodTerm(t, "Min", p.Term_MIN, funcWrapArgs(args), map[string]interface{}{}) +} + +// Finds the minimum of a sequence. If called with a field name, finds the element +// of that sequence with the smallest value in that field. If called with a function, +// calls that function on every element of the sequence and returns the element +// which produced the smallest value, ignoring any elements where the function +// returns null or produces a non-existence error. +func (t Term) MinIndex(index interface{}, args ...interface{}) Term { + return constructMethodTerm(t, "Min", p.Term_MIN, funcWrapArgs(args), map[string]interface{}{ + "index": index, + }) +} + +// Finds the maximum of a sequence. If called with a field name, finds the element +// of that sequence with the largest value in that field. If called with a function, +// calls that function on every element of the sequence and returns the element +// which produced the largest value, ignoring any elements where the function +// returns null or produces a non-existence error. +func (t Term) Max(args ...interface{}) Term { + return constructMethodTerm(t, "Max", p.Term_MAX, funcWrapArgs(args), map[string]interface{}{}) +} + +// Finds the maximum of a sequence. If called with a field name, finds the element +// of that sequence with the largest value in that field. If called with a function, +// calls that function on every element of the sequence and returns the element +// which produced the largest value, ignoring any elements where the function +// returns null or produces a non-existence error. +func (t Term) MaxIndex(index interface{}, args ...interface{}) Term { + return constructMethodTerm(t, "Max", p.Term_MAX, funcWrapArgs(args), map[string]interface{}{ + "index": index, + }) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go new file mode 100644 index 0000000..209c3c6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_aggregation_test.go @@ -0,0 +1,320 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestAggregationReduce(c *test.C) { + var response int + query := Expr(arr).Reduce(func(acc, val Term) Term { + return acc.Add(val) + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 45) +} + +func (s *RethinkSuite) TestAggregationExprCount(c *test.C) { + var response int + query := Expr(arr).Count() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 9) +} + +func (s *RethinkSuite) TestAggregationDistinct(c *test.C) { + var response []int + query := Expr(darr).Distinct() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 5) +} + +func (s *RethinkSuite) TestAggregationGroupMapReduce(c *test.C) { + var response []interface{} + query := Expr(objList).Group(func(row Term) Term { + return row.Field("id").Mod(2).Eq(0) + }).Map(func(row Term) Term { + return row.Field("num") + }).Reduce(func(acc, num Term) Term { + return acc.Add(num) + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"reduction": 135, "group": false}, + map[string]interface{}{"reduction": 70, "group": true}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupMapReduceUngroup(c *test.C) { + var response []interface{} + query := Expr(objList).Group(func(row Term) Term { + return row.Field("id").Mod(2).Eq(0) + }).Map(func(row Term) Term { + return row.Field("num") + }).Reduce(func(acc, num Term) Term { + return acc.Add(num) + }).Ungroup().OrderBy("reduction") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"reduction": 70, "group": true}, + map[string]interface{}{"reduction": 135, "group": false}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupMapReduceTable(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("TestAggregationGroupedMapReduceTable").Exec(sess) + + // Insert rows + err := Db("test").Table("TestAggregationGroupedMapReduceTable").Insert(objList).Exec(sess) + c.Assert(err, test.IsNil) + + var response []interface{} + query := Db("test").Table("TestAggregationGroupedMapReduceTable").Group(func(row Term) Term { + return row.Field("id").Mod(2).Eq(0) + }).Map(func(row Term) Term { + return row.Field("num") + }).Reduce(func(acc, num Term) Term { + return acc.Add(num) + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"reduction": 135, "group": false}, + map[string]interface{}{"reduction": 70, "group": true}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupCount(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": 1, "reduction": []interface{}{ + map[string]interface{}{"id": 1, "num": 0, "g1": 1, "g2": 1}, + map[string]interface{}{"num": 15, "g1": 1, "g2": 1, "id": 6}, + map[string]interface{}{"id": 7, "num": 0, "g1": 1, "g2": 2}, + }}, + map[string]interface{}{"group": 2, "reduction": []interface{}{ + map[string]interface{}{"g1": 2, "g2": 2, "id": 2, "num": 5}, + map[string]interface{}{"num": 0, "g1": 2, "g2": 3, "id": 4}, + map[string]interface{}{"num": 100, "g1": 2, "g2": 3, "id": 5}, + map[string]interface{}{"g2": 3, "id": 9, "num": 25, "g1": 2}, + }}, + map[string]interface{}{"group": 3, "reduction": []interface{}{ + map[string]interface{}{"num": 10, "g1": 3, "g2": 2, "id": 3}, + }}, + map[string]interface{}{"group": 4, "reduction": []interface{}{ + map[string]interface{}{"id": 8, "num": 50, "g1": 4, "g2": 2}, + }}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupSum(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1").Sum("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": 1, "reduction": 15}, + map[string]interface{}{"reduction": 130, "group": 2}, + map[string]interface{}{"reduction": 10, "group": 3}, + map[string]interface{}{"group": 4, "reduction": 50}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupAvg(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1").Avg("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": 1, "reduction": 5}, + map[string]interface{}{"group": 2, "reduction": 32.5}, + map[string]interface{}{"group": 3, "reduction": 10}, + map[string]interface{}{"group": 4, "reduction": 50}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupMin(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1").Min("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": 1, "reduction": map[string]interface{}{"id": 1, "num": 0, "g1": 1, "g2": 1}}, + map[string]interface{}{"reduction": map[string]interface{}{"num": 0, "g1": 2, "g2": 3, "id": 4}, "group": 2}, + map[string]interface{}{"group": 3, "reduction": map[string]interface{}{"num": 10, "g1": 3, "g2": 2, "id": 3}}, + map[string]interface{}{"group": 4, "reduction": map[string]interface{}{"g2": 2, "id": 8, "num": 50, "g1": 4}}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupMax(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1").Max("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"reduction": map[string]interface{}{"num": 15, "g1": 1, "g2": 1, "id": 6}, "group": 1}, + map[string]interface{}{"group": 2, "reduction": map[string]interface{}{"num": 100, "g1": 2, "g2": 3, "id": 5}}, + map[string]interface{}{"group": 3, "reduction": map[string]interface{}{"num": 10, "g1": 3, "g2": 2, "id": 3}}, + map[string]interface{}{"group": 4, "reduction": map[string]interface{}{"g2": 2, "id": 8, "num": 50, "g1": 4}}, + }) +} + +func (s *RethinkSuite) TestAggregationMin(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table2").Exec(sess) + Db("test").Table("Table2").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table2").Insert(objList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("Table2").MinIndex("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 1, "g1": 1, "g2": 1, "num": 0}) +} + +func (s *RethinkSuite) TestAggregationMaxIndex(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table2").Exec(sess) + Db("test").Table("Table2").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table2").Insert(objList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("Table2").MaxIndex("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 5, "g1": 2, "g2": 3, "num": 100}) +} + +func (s *RethinkSuite) TestAggregationMultipleGroupSum(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1", "g2").Sum("num") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": []interface{}{1, 1}, "reduction": 15}, + map[string]interface{}{"reduction": 0, "group": []interface{}{1, 2}}, + map[string]interface{}{"group": []interface{}{2, 2}, "reduction": 5}, + map[string]interface{}{"reduction": 125, "group": []interface{}{2, 3}}, + map[string]interface{}{"group": []interface{}{3, 2}, "reduction": 10}, + map[string]interface{}{"group": []interface{}{4, 2}, "reduction": 50}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupChained(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1").Max("num").Field("g2") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": 1, "reduction": 1}, + map[string]interface{}{"group": 2, "reduction": 3}, + map[string]interface{}{"group": 3, "reduction": 2}, + map[string]interface{}{"group": 4, "reduction": 2}, + }) +} + +func (s *RethinkSuite) TestAggregationGroupUngroup(c *test.C) { + var response []interface{} + query := Expr(objList).Group("g1", "g2").Max("num").Ungroup() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"group": []interface{}{1, 1}, "reduction": map[string]interface{}{"g1": 1, "g2": 1, "id": 6, "num": 15}}, + map[string]interface{}{"group": []interface{}{1, 2}, "reduction": map[string]interface{}{"g1": 1, "g2": 2, "id": 7, "num": 0}}, + map[string]interface{}{"group": []interface{}{2, 2}, "reduction": map[string]interface{}{"g1": 2, "g2": 2, "id": 2, "num": 5}}, + map[string]interface{}{"group": []interface{}{2, 3}, "reduction": map[string]interface{}{"g1": 2, "g2": 3, "id": 5, "num": 100}}, + map[string]interface{}{"group": []interface{}{3, 2}, "reduction": map[string]interface{}{"g2": 2, "id": 3, "num": 10, "g1": 3}}, + map[string]interface{}{"reduction": map[string]interface{}{"num": 50, "g1": 4, "g2": 2, "id": 8}, "group": []interface{}{4, 2}}, + }) +} + +func (s *RethinkSuite) TestAggregationContains(c *test.C) { + var response interface{} + query := Expr(arr).Contains(2) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go new file mode 100644 index 0000000..bbfa430 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control.go @@ -0,0 +1,276 @@ +package gorethink + +import ( + "encoding/base64" + + "reflect" + + p "github.com/dancannon/gorethink/ql2" +) + +var byteSliceType = reflect.TypeOf([]byte(nil)) + +// Expr converts any value to an expression. Internally it uses the `json` +// module to convert any literals, so any type annotations or methods understood +// by that module can be used. If the value cannot be converted, an error is +// returned at query .Run(session) time. +// +// If you want to call expression methods on an object that is not yet an +// expression, this is the function you want. +func Expr(val interface{}) Term { + return expr(val, 20) +} + +func expr(val interface{}, depth int) Term { + if depth <= 0 { + panic("Maximum nesting depth limit exceeded") + } + + if val == nil { + return Term{ + termType: p.Term_DATUM, + data: nil, + } + } + + switch val := val.(type) { + case Term: + return val + case []interface{}: + vals := make([]Term, len(val)) + for i, v := range val { + vals[i] = expr(v, depth) + } + + return makeArray(vals) + case map[string]interface{}: + vals := make(map[string]Term, len(val)) + for k, v := range val { + vals[k] = expr(v, depth) + } + + return makeObject(vals) + default: + // Use reflection to check for other types + valType := reflect.TypeOf(val) + valValue := reflect.ValueOf(val) + + switch valType.Kind() { + case reflect.Func: + return makeFunc(val) + case reflect.Struct, reflect.Ptr: + data, err := encode(val) + + if err != nil || data == nil { + return Term{ + termType: p.Term_DATUM, + data: nil, + } + } + + return expr(data, depth-1) + + case reflect.Slice, reflect.Array: + // Check if slice is a byte slice + if valType.Elem().Kind() == reflect.Uint8 { + data, err := encode(val) + + if err != nil || data == nil { + return Term{ + termType: p.Term_DATUM, + data: nil, + } + } + + return expr(data, depth-1) + } else { + vals := make([]Term, valValue.Len()) + for i := 0; i < valValue.Len(); i++ { + vals[i] = expr(valValue.Index(i).Interface(), depth) + } + + return makeArray(vals) + } + case reflect.Map: + vals := make(map[string]Term, len(valValue.MapKeys())) + for _, k := range valValue.MapKeys() { + vals[k.String()] = expr(valValue.MapIndex(k).Interface(), depth) + } + + return makeObject(vals) + default: + return Term{ + termType: p.Term_DATUM, + data: val, + } + } + } +} + +// Create a JavaScript expression. +func Js(jssrc interface{}) Term { + return constructRootTerm("Js", p.Term_JAVASCRIPT, []interface{}{jssrc}, map[string]interface{}{}) +} + +type HttpOpts struct { + // General Options + Timeout interface{} `gorethink:"timeout,omitempty"` + Reattempts interface{} `gorethink:"reattempts,omitempty"` + Redirects interface{} `gorethink:"redirect,omitempty"` + Verify interface{} `gorethink:"verify,omitempty"` + ResultFormat interface{} `gorethink:"resul_format,omitempty"` + + // Request Options + Method interface{} `gorethink:"method,omitempty"` + Auth interface{} `gorethink:"auth,omitempty"` + Params interface{} `gorethink:"params,omitempty"` + Header interface{} `gorethink:"header,omitempty"` + Data interface{} `gorethink:"data,omitempty"` + + // Pagination + Page interface{} `gorethink:"page,omitempty"` + PageLimit interface{} `gorethink:"page_limit,omitempty"` +} + +func (o *HttpOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Parse a JSON string on the server. +func Http(url interface{}, optArgs ...HttpOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructRootTerm("Http", p.Term_HTTP, []interface{}{url}, opts) +} + +// Parse a JSON string on the server. +func Json(args ...interface{}) Term { + return constructRootTerm("Json", p.Term_JSON, args, map[string]interface{}{}) +} + +// Throw a runtime error. If called with no arguments inside the second argument +// to `default`, re-throw the current error. +func Error(args ...interface{}) Term { + return constructRootTerm("Error", p.Term_ERROR, args, map[string]interface{}{}) +} + +// Args is a special term usd to splice an array of arguments into another term. +// This is useful when you want to call a varadic term such as GetAll with a set +// of arguments provided at runtime. +func Args(args ...interface{}) Term { + return constructRootTerm("Args", p.Term_ARGS, args, map[string]interface{}{}) +} + +// Binary encapsulates binary data within a query. +func Binary(data interface{}) Term { + var b []byte + + switch data := data.(type) { + case Term: + return constructRootTerm("Binary", p.Term_BINARY, []interface{}{data}, map[string]interface{}{}) + case []byte: + b = data + default: + typ := reflect.TypeOf(data) + if (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array) && + typ.Elem().Kind() == reflect.Uint8 { + return Binary(reflect.ValueOf(data).Bytes()) + } + panic("Unsupported binary type") + } + + return binaryTerm(base64.StdEncoding.EncodeToString(b)) +} + +func binaryTerm(data string) Term { + t := constructRootTerm("Binary", p.Term_BINARY, []interface{}{}, map[string]interface{}{}) + t.data = data + + return t +} + +// Evaluate the expr in the context of one or more value bindings. The type of +// the result is the type of the value returned from expr. +func (t Term) Do(args ...interface{}) Term { + newArgs := []interface{}{} + newArgs = append(newArgs, funcWrap(args[len(args)-1])) + newArgs = append(newArgs, t) + newArgs = append(newArgs, args[:len(args)-1]...) + + return constructRootTerm("Do", p.Term_FUNCALL, newArgs, map[string]interface{}{}) +} + +// Evaluate the expr in the context of one or more value bindings. The type of +// the result is the type of the value returned from expr. +func Do(args ...interface{}) Term { + newArgs := []interface{}{} + newArgs = append(newArgs, funcWrap(args[len(args)-1])) + newArgs = append(newArgs, args[:len(args)-1]...) + + return constructRootTerm("Do", p.Term_FUNCALL, newArgs, map[string]interface{}{}) +} + +// Evaluate one of two control paths based on the value of an expression. +// branch is effectively an if renamed due to language constraints. +// +// The type of the result is determined by the type of the branch that gets executed. +func Branch(args ...interface{}) Term { + return constructRootTerm("Branch", p.Term_BRANCH, args, map[string]interface{}{}) +} + +// ForEach loops over a sequence, evaluating the given write query for each element. +// +// It takes one argument of type `func (r.Term) interface{}`, for +// example clones a table: +// +// r.Table("table").ForEach(func (row r.Term) interface{} { +// return r.Table("new_table").Insert(row) +// }) +func (t Term) ForEach(args ...interface{}) Term { + return constructMethodTerm(t, "Foreach", p.Term_FOR_EACH, funcWrapArgs(args), map[string]interface{}{}) +} + +// Range generates a stream of sequential integers in a specified range. It +// accepts 0, 1, or 2 arguments, all of which should be numbers. +func Range(args ...interface{}) Term { + return constructRootTerm("Range", p.Term_RANGE, args, map[string]interface{}{}) +} + +// Handle non-existence errors. Tries to evaluate and return its first argument. +// If an error related to the absence of a value is thrown in the process, or if +// its first argument returns null, returns its second argument. (Alternatively, +// the second argument may be a function which will be called with either the +// text of the non-existence error or null.) +func (t Term) Default(args ...interface{}) Term { + return constructMethodTerm(t, "Default", p.Term_DEFAULT, args, map[string]interface{}{}) +} + +// Converts a value of one type into another. +// +// You can convert: a selection, sequence, or object into an ARRAY, an array of +// pairs into an OBJECT, and any DATUM into a STRING. +func (t Term) CoerceTo(args ...interface{}) Term { + return constructMethodTerm(t, "CoerceTo", p.Term_COERCE_TO, args, map[string]interface{}{}) +} + +// Gets the type of a value. +func (t Term) TypeOf(args ...interface{}) Term { + return constructMethodTerm(t, "TypeOf", p.Term_TYPE_OF, args, map[string]interface{}{}) +} + +// Gets the type of a value. +func (t Term) ToJSON() Term { + return constructMethodTerm(t, "ToJSON", p.Term_TO_JSON_STRING, []interface{}{}, map[string]interface{}{}) +} + +// Get information about a RQL value. +func (t Term) Info(args ...interface{}) Term { + return constructMethodTerm(t, "Info", p.Term_INFO, args, map[string]interface{}{}) +} + +// UUID returns a UUID (universally unique identifier), a string that can be used as a unique ID. +func UUID(args ...interface{}) Term { + return constructRootTerm("UUID", p.Term_UUID, []interface{}{}, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go new file mode 100644 index 0000000..51a8291 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_control_test.go @@ -0,0 +1,448 @@ +package gorethink + +import ( + "bytes" + "testing" + "time" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestControlExprNil(c *test.C) { + var response interface{} + query := Expr(nil) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.Equals, ErrEmptyResult) + c.Assert(response, test.Equals, nil) +} + +func (s *RethinkSuite) TestControlExprSimple(c *test.C) { + var response int + query := Expr(1) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestControlExprList(c *test.C) { + var response []interface{} + query := Expr(narr) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + 1, 2, 3, 4, 5, 6, []interface{}{ + 7.1, 7.2, 7.3, + }, + }) +} + +func (s *RethinkSuite) TestControlExprObj(c *test.C) { + var response map[string]interface{} + query := Expr(nobj) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "A": 1, + "B": 2, + "C": map[string]interface{}{ + "1": 3, + "2": 4, + }, + }) +} + +func (s *RethinkSuite) TestControlStruct(c *test.C) { + var response map[string]interface{} + query := Expr(str) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "id": "A", + "B": 1, + "D": map[string]interface{}{"D2": "2", "D1": 1}, + "E": []interface{}{"E1", "E2", "E3", 4}, + "F": map[string]interface{}{ + "XA": 2, + "XB": "B", + "XC": []interface{}{"XC1", "XC2"}, + "XD": map[string]interface{}{ + "YA": 3, + "YB": map[string]interface{}{ + "1": "1", + "2": "2", + "3": 3, + }, + "YC": map[string]interface{}{ + "YC1": "YC1", + }, + "YD": map[string]interface{}{ + "YD1": "YD1", + }, + }, + "XE": "XE", + "XF": []interface{}{"XE1", "XE2"}, + }, + }) +} + +func (s *RethinkSuite) TestControlMapTypeAlias(c *test.C) { + var response TMap + query := Expr(TMap{"A": 1, "B": 2}) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, TMap{"A": 1, "B": 2}) +} + +func (s *RethinkSuite) TestControlStringTypeAlias(c *test.C) { + var response TStr + query := Expr(TStr("Hello")) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, TStr("Hello")) +} + +func (s *RethinkSuite) TestControlExprTypes(c *test.C) { + var response []interface{} + query := Expr([]interface{}{int64(1), uint64(1), float64(1.0), int32(1), uint32(1), float32(1), "1", true, false}) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{int64(1), uint64(1), float64(1.0), int32(1), uint32(1), float32(1), "1", true, false}) +} + +func (s *RethinkSuite) TestControlJs(c *test.C) { + var response int + query := Js("1;") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestControlHttp(c *test.C) { + if testing.Short() { + c.Skip("-short set") + } + + var response map[string]interface{} + query := Http("httpbin.org/get?data=1") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response["args"], jsonEquals, map[string]interface{}{ + "data": "1", + }) +} + +func (s *RethinkSuite) TestControlJson(c *test.C) { + var response []int + query := Json("[1,2,3]") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3}) +} + +func (s *RethinkSuite) TestControlError(c *test.C) { + query := Error("An error occurred") + err := query.Exec(sess) + c.Assert(err, test.NotNil) + + c.Assert(err, test.NotNil) + c.Assert(err, test.FitsTypeOf, RqlRuntimeError{}) + + c.Assert(err.Error(), test.Equals, "gorethink: An error occurred in: \nr.Error(\"An error occurred\")") +} + +func (s *RethinkSuite) TestControlDoNothing(c *test.C) { + var response []interface{} + query := Do([]interface{}{map[string]interface{}{"a": 1}, map[string]interface{}{"a": 2}, map[string]interface{}{"a": 3}}) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{map[string]interface{}{"a": 1}, map[string]interface{}{"a": 2}, map[string]interface{}{"a": 3}}) +} + +func (s *RethinkSuite) TestControlArgs(c *test.C) { + var response time.Time + query := Time(Args(Expr([]interface{}{2014, 7, 12, "Z"}))) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response.Unix(), test.Equals, int64(1405123200)) +} + +func (s *RethinkSuite) TestControlBinaryByteArray(c *test.C) { + var response []byte + + query := Binary([]byte("Hello World")) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response, []byte("Hello World")), test.Equals, true) +} + +type byteArray []byte + +func (s *RethinkSuite) TestControlBinaryByteArrayAlias(c *test.C) { + var response []byte + + query := Binary(byteArray("Hello World")) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response, []byte("Hello World")), test.Equals, true) +} + +func (s *RethinkSuite) TestControlBinaryExpr(c *test.C) { + var response []byte + + query := Expr([]byte("Hello World")) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response, []byte("Hello World")), test.Equals, true) +} + +func (s *RethinkSuite) TestControlBinaryExprAlias(c *test.C) { + var response []byte + + query := Expr(byteArray("Hello World")) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response, []byte("Hello World")), test.Equals, true) +} + +func (s *RethinkSuite) TestControlBinaryTerm(c *test.C) { + var response []byte + + query := Binary(Expr([]byte("Hello World"))) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response, []byte("Hello World")), test.Equals, true) +} + +func (s *RethinkSuite) TestControlBinaryElemTerm(c *test.C) { + var response map[string]interface{} + + query := Expr(map[string]interface{}{ + "bytes": []byte("Hello World"), + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(bytes.Equal(response["bytes"].([]byte), []byte("Hello World")), test.Equals, true) +} + +func (s *RethinkSuite) TestControlDo(c *test.C) { + var response []interface{} + query := Do([]interface{}{ + map[string]interface{}{"a": 1}, + map[string]interface{}{"a": 2}, + map[string]interface{}{"a": 3}, + }, func(row Term) Term { + return row.Field("a") + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3}) +} + +func (s *RethinkSuite) TestControlDoWithExpr(c *test.C) { + var response []interface{} + query := Expr([]interface{}{ + map[string]interface{}{"a": 1}, + map[string]interface{}{"a": 2}, + map[string]interface{}{"a": 3}, + }).Do(func(row Term) Term { + return row.Field("a") + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3}) +} + +func (s *RethinkSuite) TestControlBranchSimple(c *test.C) { + var response int + query := Branch( + true, + 1, + 2, + ) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestControlBranchWithMapExpr(c *test.C) { + var response []interface{} + query := Expr([]interface{}{1, 2, 3}).Map(Branch( + Row.Eq(2), + Row.Sub(1), + Row.Add(1), + )) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{2, 1, 4}) +} + +func (s *RethinkSuite) TestControlDefault(c *test.C) { + var response []interface{} + query := Expr(defaultObjList).Map(func(row Term) Term { + return row.Field("a").Default(1) + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 1}) +} + +func (s *RethinkSuite) TestControlCoerceTo(c *test.C) { + var response string + query := Expr(1).CoerceTo("STRING") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "1") +} + +func (s *RethinkSuite) TestControlTypeOf(c *test.C) { + var response string + query := Expr(1).TypeOf() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "NUMBER") +} + +func (s *RethinkSuite) TestControlRangeNoArgs(c *test.C) { + var response []int + query := Range().Limit(100) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(len(response), test.Equals, 100) +} + +func (s *RethinkSuite) TestControlRangeSingleArgs(c *test.C) { + var response []int + query := Range(4) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []int{0, 1, 2, 3}) +} + +func (s *RethinkSuite) TestControlRangeTwoArgs(c *test.C) { + var response []int + query := Range(4, 6) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []int{4, 5}) +} + +func (s *RethinkSuite) TestControlToJSON(c *test.C) { + var response string + query := Expr([]int{4, 5}).ToJSON() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "[4,5]") +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go new file mode 100644 index 0000000..e2833c3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db.go @@ -0,0 +1,30 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Create a database. A RethinkDB database is a collection of tables, similar to +// relational databases. +// +// If successful, the operation returns an object: {created: 1}. If a database +// with the same name already exists the operation throws RqlRuntimeError. +// +// Note: that you can only use alphanumeric characters and underscores for the database name. +func DbCreate(args ...interface{}) Term { + return constructRootTerm("DbCreate", p.Term_DB_CREATE, args, map[string]interface{}{}) +} + +// Drop a database. The database, all its tables, and corresponding data will be +// deleted. +// +// If successful, the operation returns the object {dropped: 1}. If the specified +// database doesn't exist a RqlRuntimeError is thrown. +func DbDrop(args ...interface{}) Term { + return constructRootTerm("DbDrop", p.Term_DB_DROP, args, map[string]interface{}{}) +} + +// List all database names in the system. +func DbList(args ...interface{}) Term { + return constructRootTerm("DbList", p.Term_DB_LIST, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go new file mode 100644 index 0000000..66d53e1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_db_test.go @@ -0,0 +1,57 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestDbCreate(c *test.C) { + // Delete the test2 database if it already exists + DbDrop("test").Exec(sess) + + // Test database creation + query := DbCreate("test") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.DBsCreated, jsonEquals, 1) +} + +func (s *RethinkSuite) TestDbList(c *test.C) { + var response []interface{} + + // create database + DbCreate("test").Exec(sess) + + // Try and find it in the list + success := false + res, err := DbList().Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.FitsTypeOf, []interface{}{}) + + for _, db := range response { + if db == "test" { + success = true + } + } + + c.Assert(success, test.Equals, true) +} + +func (s *RethinkSuite) TestDbDelete(c *test.C) { + // Delete the test2 database if it already exists + DbCreate("test").Exec(sess) + + // Test database creation + query := DbDrop("test") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.DBsDropped, jsonEquals, 1) + + // Ensure that there is still a test DB after the test has finished + DbCreate("test").Exec(sess) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go new file mode 100644 index 0000000..2cf73d7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial.go @@ -0,0 +1,170 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// CircleOpts describes the optional arguments for a Circle operation +type CircleOpts struct { + NumVertices interface{} `gorethink:"num_vertices,omitempty"` + GeoSystem interface{} `gorethink:"geo_system,omitempty"` + Unit interface{} `gorethink:"unit,omitempty"` + Fill interface{} `gorethink:"fill,omitempty"` +} + +func (o *CircleOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Circle constructs a circular line or polygon. A circle in RethinkDB is +// a polygon or line approximating a circle of a given radius around a given +// center, consisting of a specified number of vertices (default 32). +func Circle(point, radius interface{}, optArgs ...CircleOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + return constructRootTerm("Circle", p.Term_CIRCLE, []interface{}{point, radius}, opts) +} + +// DistanceOpts describes the optional arguments for a Distance operation +type DistanceOpts struct { + GeoSystem interface{} `gorethink:"geo_system,omitempty"` + Unit interface{} `gorethink:"unit,omitempty"` +} + +func (o *DistanceOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Distance calculates the Haversine distance between two points. At least one +// of the geometry objects specified must be a point. +func (t Term) Distance(point interface{}, optArgs ...DistanceOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + return constructMethodTerm(t, "Distance", p.Term_DISTANCE, []interface{}{point}, opts) +} + +// Distance calculates the Haversine distance between two points. At least one +// of the geometry objects specified must be a point. +func Distance(point1, point2 interface{}, optArgs ...DistanceOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + return constructRootTerm("Distance", p.Term_DISTANCE, []interface{}{point1, point2}, opts) +} + +// Fill converts a Line object into a Polygon object. If the last point does not +// specify the same coordinates as the first point, polygon will close the +// polygon by connecting them +func (t Term) Fill() Term { + return constructMethodTerm(t, "Fill", p.Term_FILL, []interface{}{}, map[string]interface{}{}) +} + +// Geojson converts a GeoJSON object to a ReQL geometry object. +func Geojson(args ...interface{}) Term { + return constructRootTerm("Geojson", p.Term_GEOJSON, args, map[string]interface{}{}) +} + +// ToGeojson converts a ReQL geometry object to a GeoJSON object. +func (t Term) ToGeojson(args ...interface{}) Term { + return constructMethodTerm(t, "ToGeojson", p.Term_TO_GEOJSON, args, map[string]interface{}{}) +} + +// GetIntersectingOpts describes the optional arguments for a GetIntersecting operation +type GetIntersectingOpts struct { + Index interface{} `gorethink:"index,omitempty"` +} + +func (o *GetIntersectingOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// GetIntersecting gets all documents where the given geometry object intersects +// the geometry object of the requested geospatial index. +func (t Term) GetIntersecting(args interface{}, optArgs ...GetIntersectingOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + return constructMethodTerm(t, "GetIntersecting", p.Term_GET_INTERSECTING, []interface{}{args}, opts) +} + +// GetIntersectingOpts describes the optional arguments for a GetIntersecting operation +type GetNearestOpts struct { + Index interface{} `gorethink:"index,omitempty"` + MaxResults interface{} `gorethink:"max_results,omitempty"` + MaxDist interface{} `gorethink:"max_dist,omitempty"` + Unit interface{} `gorethink:"unit,omitempty"` + GeoSystem interface{} `gorethink:"geo_system,omitempty"` +} + +func (o *GetNearestOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// GetNearest gets all documents where the specified geospatial index is within a +// certain distance of the specified point (default 100 kilometers). +func (t Term) GetNearest(point interface{}, optArgs ...GetNearestOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + + return constructMethodTerm(t, "GetNearest", p.Term_GET_NEAREST, []interface{}{point}, opts) +} + +// Includes tests whether a geometry object is completely contained within another. +// When applied to a sequence of geometry objects, includes acts as a filter, +// returning a sequence of objects from the sequence that include the argument. +func (t Term) Includes(args ...interface{}) Term { + return constructMethodTerm(t, "Includes", p.Term_INCLUDES, args, map[string]interface{}{}) +} + +// Intersects tests whether two geometry objects intersect with one another. +// When applied to a sequence of geometry objects, intersects acts as a filter, +// returning a sequence of objects from the sequence that intersect with the +// argument. +func (t Term) Intersects(args ...interface{}) Term { + return constructMethodTerm(t, "Intersects", p.Term_INTERSECTS, args, map[string]interface{}{}) +} + +// Line constructs a geometry object of type Line. The line can be specified in +// one of two ways: +// - Two or more two-item arrays, specifying longitude and latitude numbers of +// the line's vertices; +// - Two or more Point objects specifying the line's vertices. +func Line(args ...interface{}) Term { + return constructRootTerm("Line", p.Term_LINE, args, map[string]interface{}{}) +} + +// Point constructs a geometry object of type Point. The point is specified by +// two floating point numbers, the longitude (−180 to 180) and latitude +// (−90 to 90) of the point on a perfect sphere. +func Point(lon, lat interface{}) Term { + return constructRootTerm("Point", p.Term_POINT, []interface{}{lon, lat}, map[string]interface{}{}) +} + +// Polygon constructs a geometry object of type Polygon. The Polygon can be +// specified in one of two ways: +// - Three or more two-item arrays, specifying longitude and latitude numbers of the polygon's vertices; +// - Three or more Point objects specifying the polygon's vertices. +func Polygon(args ...interface{}) Term { + return constructRootTerm("Polygon", p.Term_POLYGON, args, map[string]interface{}{}) +} + +// PolygonSub "punches a hole" out of the parent polygon using the polygon passed +// to the function. +// polygon1.PolygonSub(polygon2) -> polygon +// In the example above polygon2 must be completely contained within polygon1 +// and must have no holes itself (it must not be the output of polygon_sub itself). +func (t Term) PolygonSub(args ...interface{}) Term { + return constructMethodTerm(t, "PolygonSub", p.Term_POLYGON_SUB, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go new file mode 100644 index 0000000..a576bd6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go @@ -0,0 +1,530 @@ +package gorethink + +import ( + "github.com/dancannon/gorethink/types" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestGeospatialDecodeGeometryPseudoType(c *test.C) { + var response types.Geometry + res, err := Expr(map[string]interface{}{ + "$reql_type$": "GEOMETRY", + "type": "Polygon", + "coordinates": []interface{}{ + []interface{}{ + []interface{}{-122.423246, 37.779388}, + []interface{}{-122.423246, 37.329898}, + []interface{}{-121.88642, 37.329898}, + []interface{}{-121.88642, 37.779388}, + []interface{}{-122.423246, 37.779388}, + }, + }, + }).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.779388}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialEncodeGeometryPseudoType(c *test.C) { + encoded, err := encode(types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.779388}, + }, + }, + }) + c.Assert(err, test.IsNil) + c.Assert(encoded, test.DeepEquals, map[string]interface{}{ + "$reql_type$": "GEOMETRY", + "type": "Polygon", + "coordinates": []interface{}{ + []interface{}{ + []interface{}{-122.423246, 37.779388}, + []interface{}{-122.423246, 37.329898}, + []interface{}{-121.88642, 37.329898}, + []interface{}{-121.88642, 37.779388}, + []interface{}{-122.423246, 37.779388}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialCircle(c *test.C) { + var response types.Geometry + res, err := Circle([]float64{-122.423246, 37.779388}, 10).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, + types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, + types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, + types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, + types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, + types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, + types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, + types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, + types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, + types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, + types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, + types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, + types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, + types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, + types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, + types.Point{Lon: -122.423246, Lat: 37.779478096334365}, + types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, + types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, + types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, + types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, + types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, + types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, + types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, + types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, + types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, + types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, + types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, + types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, + types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, + types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, + types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialCirclePoint(c *test.C) { + var response types.Geometry + res, err := Circle(Point(-122.423246, 37.779388), 10).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, + types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, + types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, + types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, + types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, + types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, + types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, + types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, + types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, + types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, + types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, + types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, + types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, + types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, + types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, + types.Point{Lon: -122.423246, Lat: 37.779478096334365}, + types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, + types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, + types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, + types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, + types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, + types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, + types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, + types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, + types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, + types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, + types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, + types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, + types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, + types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, + types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialCirclePointFill(c *test.C) { + var response types.Geometry + res, err := Circle(Point(-122.423246, 37.779388), 10, CircleOpts{Fill: true}).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, + types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, + types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, + types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, + types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, + types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, + types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, + types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, + types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, + types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, + types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, + types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, + types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, + types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, + types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, + types.Point{Lon: -122.423246, Lat: 37.779478096334365}, + types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, + types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, + types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, + types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, + types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, + types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, + types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, + types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, + types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, + types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, + types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, + types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, + types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, + types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, + types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, + types.Point{Lon: -122.423246, Lat: 37.77929790366427}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialPointDistanceMethod(c *test.C) { + var response float64 + res, err := Point(-122.423246, 37.779388).Distance(Point(-117.220406, 32.719464)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 734125.249602186) +} + +func (s *RethinkSuite) TestGeospatialPointDistanceRoot(c *test.C) { + var response float64 + res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 734125.249602186) +} + +func (s *RethinkSuite) TestGeospatialPointDistanceRootKm(c *test.C) { + var response float64 + res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464), DistanceOpts{Unit: "km"}).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 734.125249602186) +} + +func (s *RethinkSuite) TestGeospatialFill(c *test.C) { + var response types.Geometry + res, err := Line( + []float64{-122.423246, 37.779388}, + []float64{-122.423246, 37.329898}, + []float64{-121.886420, 37.329898}, + []float64{-121.886420, 37.779388}, + ).Fill().Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.779388}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialGeojson(c *test.C) { + var response types.Geometry + res, err := Geojson(map[string]interface{}{ + "type": "Point", + "coordinates": []interface{}{-122.423246, 37.779388}, + }).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Point", + Point: types.Point{Lon: -122.423246, Lat: 37.779388}, + }) +} + +func (s *RethinkSuite) TestGeospatialToGeojson(c *test.C) { + var response map[string]interface{} + res, err := Point(-122.423246, 37.779388).ToGeojson().Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, map[string]interface{}{ + "type": "Point", + "coordinates": []interface{}{-122.423246, 37.779388}, + }) +} + +func (s *RethinkSuite) TestGeospatialGetIntersecting(c *test.C) { + // Setup table + Db("test").TableDrop("geospatial").Run(sess) + Db("test").TableCreate("geospatial").Run(sess) + Db("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ + Geo: true, + }).Run(sess) + Db("test").Table("geospatial").Insert([]interface{}{ + map[string]interface{}{"area": Circle(Point(-117.220406, 32.719464), 100000)}, + map[string]interface{}{"area": Circle(Point(-100.220406, 20.719464), 100000)}, + map[string]interface{}{"area": Circle(Point(-117.200406, 32.723464), 100000)}, + }).Run(sess) + + var response []interface{} + res, err := Db("test").Table("geospatial").GetIntersecting( + Circle(Point(-117.220406, 32.719464), 100000), + GetIntersectingOpts{ + Index: "area", + }, + ).Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 2) +} + +func (s *RethinkSuite) TestGeospatialGetNearest(c *test.C) { + // Setup table + Db("test").TableDrop("geospatial").Run(sess) + Db("test").TableCreate("geospatial").Run(sess) + Db("test").Table("geospatial").IndexCreate("area", IndexCreateOpts{ + Geo: true, + }).Run(sess) + Db("test").Table("geospatial").Insert([]interface{}{ + map[string]interface{}{"area": Circle(Point(-117.220406, 32.719464), 100000)}, + map[string]interface{}{"area": Circle(Point(-100.220406, 20.719464), 100000)}, + map[string]interface{}{"area": Circle(Point(-115.210306, 32.733364), 100000)}, + }).Run(sess) + + var response []interface{} + res, err := Db("test").Table("geospatial").GetNearest( + Point(-117.220406, 32.719464), + GetNearestOpts{ + Index: "area", + MaxDist: 1, + }, + ).Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.HasLen, 1) +} + +func (s *RethinkSuite) TestGeospatialIncludesTrue(c *test.C) { + var response bool + res, err := Polygon( + Point(-122.4, 37.7), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.7), + ).Includes(Point(-122.3, 37.4)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestGeospatialIncludesFalse(c *test.C) { + var response bool + res, err := Polygon( + Point(-122.4, 37.7), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.7), + ).Includes(Point(100.3, 37.4)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestGeospatialIntersectsTrue(c *test.C) { + var response bool + res, err := Polygon( + Point(-122.4, 37.7), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.7), + ).Intersects(Polygon( + Point(-122.3, 37.4), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.4), + )).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestGeospatialIntersectsFalse(c *test.C) { + var response bool + res, err := Polygon( + Point(-122.4, 37.7), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.7), + ).Intersects(Polygon( + Point(-102.4, 37.7), + Point(-102.4, 37.3), + Point(-101.8, 37.3), + Point(-101.8, 37.7), + )).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestGeospatialLineLatLon(c *test.C) { + var response types.Geometry + res, err := Line([]float64{-122.423246, 37.779388}, []float64{-121.886420, 37.329898}).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "LineString", + Line: types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -121.886420, Lat: 37.329898}, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialLinePoint(c *test.C) { + var response types.Geometry + res, err := Line(Point(-122.423246, 37.779388), Point(-121.886420, 37.329898)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "LineString", + Line: types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -121.886420, Lat: 37.329898}, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialPoint(c *test.C) { + var response types.Geometry + res, err := Point(-122.423246, 37.779388).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Point", + Point: types.Point{Lon: -122.423246, Lat: 37.779388}, + }) +} + +func (s *RethinkSuite) TestGeospatialPolygon(c *test.C) { + var response types.Geometry + res, err := Polygon(Point(-122.423246, 37.779388), Point(-122.423246, 37.329898), Point(-121.886420, 37.329898)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.423246, Lat: 37.779388}, + types.Point{Lon: -122.423246, Lat: 37.329898}, + types.Point{Lon: -121.88642, Lat: 37.329898}, + types.Point{Lon: -122.423246, Lat: 37.779388}, + }, + }, + }) +} + +func (s *RethinkSuite) TestGeospatialPolygonSub(c *test.C) { + var response types.Geometry + res, err := Polygon( + Point(-122.4, 37.7), + Point(-122.4, 37.3), + Point(-121.8, 37.3), + Point(-121.8, 37.7), + ).PolygonSub(Polygon( + Point(-122.3, 37.4), + Point(-122.3, 37.6), + Point(-122.0, 37.6), + Point(-122.0, 37.4), + )).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, types.Geometry{ + Type: "Polygon", + Lines: types.Lines{ + types.Line{ + types.Point{Lon: -122.4, Lat: 37.7}, + types.Point{Lon: -122.4, Lat: 37.3}, + types.Point{Lon: -121.8, Lat: 37.3}, + types.Point{Lon: -121.8, Lat: 37.7}, + types.Point{Lon: -122.4, Lat: 37.7}, + }, + types.Line{ + types.Point{Lon: -122.3, Lat: 37.4}, + types.Point{Lon: -122.3, Lat: 37.6}, + types.Point{Lon: -122, Lat: 37.6}, + types.Point{Lon: -122, Lat: 37.4}, + types.Point{Lon: -122.3, Lat: 37.4}, + }, + }, + }) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go new file mode 100644 index 0000000..bb6f9cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join.go @@ -0,0 +1,45 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Returns the inner product of two sequences (e.g. a table, a filter result) +// filtered by the predicate. The query compares each row of the left sequence +// with each row of the right sequence to find all pairs of rows which satisfy +// the predicate. When the predicate is satisfied, each matched pair of rows +// of both sequences are combined into a result row. +func (t Term) InnerJoin(args ...interface{}) Term { + return constructMethodTerm(t, "InnerJoin", p.Term_INNER_JOIN, args, map[string]interface{}{}) +} + +// Computes a left outer join by retaining each row in the left table even if no +// match was found in the right table. +func (t Term) OuterJoin(args ...interface{}) Term { + return constructMethodTerm(t, "OuterJoin", p.Term_OUTER_JOIN, args, map[string]interface{}{}) +} + +type EqJoinOpts struct { + Index interface{} `gorethink:"index,omitempty"` +} + +func (o *EqJoinOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// An efficient join that looks up elements in the right table by primary key. +// +// Optional arguments: "index" (string - name of the index to use in right table instead of the primary key) +func (t Term) EqJoin(left, right interface{}, optArgs ...EqJoinOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "EqJoin", p.Term_EQ_JOIN, []interface{}{funcWrap(left), right}, opts) +} + +// Used to 'zip' up the result of a join by merging the 'right' fields into 'left' +// fields of each member of the sequence. +func (t Term) Zip(args ...interface{}) Term { + return constructMethodTerm(t, "Zip", p.Term_ZIP, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go new file mode 100644 index 0000000..4bc521c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_join_test.go @@ -0,0 +1,177 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestJoinInnerJoin(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Join1").Exec(sess) + Db("test").TableCreate("Join2").Exec(sess) + + // Insert rows + Db("test").Table("Join1").Insert(joinTable1).Exec(sess) + Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Join1").InnerJoin(Db("test").Table("Join2"), func(a, b Term) Term { + return a.Field("id").Eq(b.Field("id")) + }) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + err = res.All(&response) + + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{ + "right": map[string]interface{}{"title": "goof", "id": 0}, + "left": map[string]interface{}{"name": "bob", "id": 0}, + }, + map[string]interface{}{ + "right": map[string]interface{}{"title": "lmoe", "id": 2}, + "left": map[string]interface{}{"name": "joe", "id": 2}, + }, + }) +} + +func (s *RethinkSuite) TestJoinInnerJoinZip(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Join1").Exec(sess) + Db("test").TableCreate("Join2").Exec(sess) + + // Insert rows + Db("test").Table("Join1").Insert(joinTable1).Exec(sess) + Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Join1").InnerJoin(Db("test").Table("Join2"), func(a, b Term) Term { + return a.Field("id").Eq(b.Field("id")) + }).Zip() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"title": "goof", "name": "bob", "id": 0}, + map[string]interface{}{"title": "lmoe", "name": "joe", "id": 2}, + }) +} + +func (s *RethinkSuite) TestJoinOuterJoinZip(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Join1").Exec(sess) + Db("test").TableCreate("Join2").Exec(sess) + + // Insert rows + Db("test").Table("Join1").Insert(joinTable1).Exec(sess) + Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Join1").OuterJoin(Db("test").Table("Join2"), func(a, b Term) Term { + return a.Field("id").Eq(b.Field("id")) + }).Zip().OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"title": "goof", "name": "bob", "id": 0}, + map[string]interface{}{"name": "tom", "id": 1}, + map[string]interface{}{"title": "lmoe", "name": "joe", "id": 2}, + }) +} + +func (s *RethinkSuite) TestJoinEqJoinZip(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Join1").Exec(sess) + Db("test").TableCreate("Join2").Exec(sess) + + // Insert rows + Db("test").Table("Join1").Insert(joinTable1).Exec(sess) + Db("test").Table("Join2").Insert(joinTable2).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Join1").EqJoin("id", Db("test").Table("Join2")).Zip() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"title": "goof", "name": "bob", "id": 0}, + map[string]interface{}{"title": "lmoe", "name": "joe", "id": 2}, + }) +} + +func (s *RethinkSuite) TestJoinEqJoinDiffIdsZip(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Join1").Exec(sess) + err := Db("test").TableCreate("Join3", TableCreateOpts{ + PrimaryKey: "it", + }).Exec(sess) + c.Assert(err, test.IsNil) + Db("test").Table("Join3").IndexCreate("it").Exec(sess) + + // Insert rows + Db("test").Table("Join1").Delete().Exec(sess) + Db("test").Table("Join3").Delete().Exec(sess) + Db("test").Table("Join1").Insert(joinTable1).Exec(sess) + Db("test").Table("Join3").Insert(joinTable3).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Join1").EqJoin("id", Db("test").Table("Join3"), EqJoinOpts{ + Index: "it", + }).Zip() + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"title": "goof", "name": "bob", "id": 0, "it": 0}, + map[string]interface{}{"title": "lmoe", "name": "joe", "id": 2, "it": 2}, + }) +} + +func (s *RethinkSuite) TestOrderByJoinEq(c *test.C) { + type Map map[string]interface{} + var err error + + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + Db("test").TableCreate("test2").Exec(sess) + tab := Db("test").Table("test") + tab2 := Db("test").Table("test2") + + // insert rows + err = tab.Insert(Map{"S": "s1", "T": 2}).Exec(sess) + err = tab.Insert(Map{"S": "s1", "T": 1}).Exec(sess) + err = tab.Insert(Map{"S": "s1", "T": 3}).Exec(sess) + err = tab.Insert(Map{"S": "s2", "T": 3}).Exec(sess) + c.Assert(err, test.IsNil) + + err = tab2.Insert(Map{"id": "s1", "N": "Rob"}).Exec(sess) + err = tab2.Insert(Map{"id": "s2", "N": "Zar"}).Exec(sess) + c.Assert(err, test.IsNil) + + // Test query + var response []Map + res, err := tab.OrderBy("T").EqJoin("S", tab2).Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Check(len(response), test.Equals, 4, test.Commentf("%v", response)) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go new file mode 100644 index 0000000..5770a40 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation.go @@ -0,0 +1,111 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Returns the currently visited document. +var Row = constructRootTerm("Doc", p.Term_IMPLICIT_VAR, []interface{}{}, map[string]interface{}{}) + +func Literal(args ...interface{}) Term { + return constructRootTerm("Literal", p.Term_LITERAL, args, map[string]interface{}{}) +} + +// Get a single field from an object. If called on a sequence, gets that field +// from every object in the sequence, skipping objects that lack it. +func (t Term) Field(args ...interface{}) Term { + return constructMethodTerm(t, "Field", p.Term_GET_FIELD, args, map[string]interface{}{}) +} + +// Test if an object has all of the specified fields. An object has a field if +// it has the specified key and that key maps to a non-null value. For instance, +// the object `{'a':1,'b':2,'c':null}` has the fields `a` and `b`. +func (t Term) HasFields(args ...interface{}) Term { + return constructMethodTerm(t, "HasFields", p.Term_HAS_FIELDS, args, map[string]interface{}{}) +} + +// Plucks out one or more attributes from either an object or a sequence of +// objects (projection). +func (t Term) Pluck(args ...interface{}) Term { + return constructMethodTerm(t, "Pluck", p.Term_PLUCK, args, map[string]interface{}{}) +} + +// The opposite of pluck; takes an object or a sequence of objects, and returns +// them with the specified paths removed. +func (t Term) Without(args ...interface{}) Term { + return constructMethodTerm(t, "Without", p.Term_WITHOUT, args, map[string]interface{}{}) +} + +// Merge two objects together to construct a new object with properties from both. +// Gives preference to attributes from other when there is a conflict. +func (t Term) Merge(args ...interface{}) Term { + return constructMethodTerm(t, "Merge", p.Term_MERGE, funcWrapArgs(args), map[string]interface{}{}) +} + +// Append a value to an array. +func (t Term) Append(args ...interface{}) Term { + return constructMethodTerm(t, "Append", p.Term_APPEND, args, map[string]interface{}{}) +} + +// Prepend a value to an array. +func (t Term) Prepend(args ...interface{}) Term { + return constructMethodTerm(t, "Prepend", p.Term_PREPEND, args, map[string]interface{}{}) +} + +// Remove the elements of one array from another array. +func (t Term) Difference(args ...interface{}) Term { + return constructMethodTerm(t, "Difference", p.Term_DIFFERENCE, args, map[string]interface{}{}) +} + +// Add a value to an array and return it as a set (an array with distinct values). +func (t Term) SetInsert(args ...interface{}) Term { + return constructMethodTerm(t, "SetInsert", p.Term_SET_INSERT, args, map[string]interface{}{}) +} + +// Add a several values to an array and return it as a set (an array with +// distinct values). +func (t Term) SetUnion(args ...interface{}) Term { + return constructMethodTerm(t, "SetUnion", p.Term_SET_UNION, args, map[string]interface{}{}) +} + +// Intersect two arrays returning values that occur in both of them as a set (an +// array with distinct values). +func (t Term) SetIntersection(args ...interface{}) Term { + return constructMethodTerm(t, "SetIntersection", p.Term_SET_INTERSECTION, args, map[string]interface{}{}) +} + +// Remove the elements of one array from another and return them as a set (an +// array with distinct values). +func (t Term) SetDifference(args ...interface{}) Term { + return constructMethodTerm(t, "SetDifference", p.Term_SET_DIFFERENCE, args, map[string]interface{}{}) +} + +// Insert a value in to an array at a given index. Returns the modified array. +func (t Term) InsertAt(args ...interface{}) Term { + return constructMethodTerm(t, "InsertAt", p.Term_INSERT_AT, args, map[string]interface{}{}) +} + +// Insert several values in to an array at a given index. Returns the modified array. +func (t Term) SpliceAt(args ...interface{}) Term { + return constructMethodTerm(t, "SpliceAt", p.Term_SPLICE_AT, args, map[string]interface{}{}) +} + +// Remove an element from an array at a given index. Returns the modified array. +func (t Term) DeleteAt(args ...interface{}) Term { + return constructMethodTerm(t, "DeleteAt", p.Term_DELETE_AT, args, map[string]interface{}{}) +} + +// Change a value in an array at a given index. Returns the modified array. +func (t Term) ChangeAt(args ...interface{}) Term { + return constructMethodTerm(t, "ChangeAt", p.Term_CHANGE_AT, args, map[string]interface{}{}) +} + +// Return an array containing all of the object's keys. +func (t Term) Keys(args ...interface{}) Term { + return constructMethodTerm(t, "Keys", p.Term_KEYS, args, map[string]interface{}{}) +} + +// Creates an object from a list of key-value pairs, where the keys must be strings. +func Object(args ...interface{}) Term { + return constructRootTerm("Object", p.Term_OBJECT, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go new file mode 100644 index 0000000..4de8889 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_manipulation_test.go @@ -0,0 +1,318 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestManipulationDocField(c *test.C) { + query := Expr(map[string]interface{}{"a": 1}).Do(Row.Field("a")) + + var response int + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestManipulationPluck(c *test.C) { + query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Pluck("a", "c") + + var response map[string]interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"a": 1, "c": 3}) +} + +func (s *RethinkSuite) TestManipulationWithout(c *test.C) { + query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Pluck("a", "c") + + var response map[string]interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"a": 1, "c": 3}) +} + +func (s *RethinkSuite) TestManipulationMerge(c *test.C) { + query := Expr(map[string]interface{}{"a": 1, "c": 3}).Merge(map[string]interface{}{"b": 2}) + + var response map[string]interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"a": 1, "b": 2, "c": 3}) +} + +func (s *RethinkSuite) TestManipulationMergeLiteral(c *test.C) { + query := Expr(map[string]interface{}{ + "a": map[string]interface{}{ + "aa": map[string]interface{}{ + "aaa": 1, + "aab": 2, + }, + "ab": map[string]interface{}{ + "aba": 3, + "abb": 4, + }, + }, + }).Merge(map[string]interface{}{"a": map[string]interface{}{"ab": Literal()}}) + + var response map[string]interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"a": map[string]interface{}{"aa": map[string]interface{}{"aab": 2, "aaa": 1}}}) +} + +func (s *RethinkSuite) TestManipulationAppend(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).Append(4).Append(5) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4, 5}) +} + +func (s *RethinkSuite) TestManipulationPrepend(c *test.C) { + query := Expr([]interface{}{3, 4, 5}).Prepend(2).Prepend(1) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4, 5}) +} + +func (s *RethinkSuite) TestManipulationDifference(c *test.C) { + query := Expr([]interface{}{3, 4, 5}).Difference([]interface{}{3, 4}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{5}) +} + +func (s *RethinkSuite) TestManipulationSetInsert(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).SetInsert(3).SetInsert(4) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4}) +} + +func (s *RethinkSuite) TestManipulationSetUnion(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).SetUnion([]interface{}{3, 4}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4}) +} + +func (s *RethinkSuite) TestManipulationSetIntersection(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).SetIntersection([]interface{}{2, 3, 3, 4}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{2, 3}) +} + +func (s *RethinkSuite) TestManipulationSetDifference(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).SetDifference([]interface{}{2, 3, 4, 4}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1}) +} + +func (s *RethinkSuite) TestManipulationHasFieldsTrue(c *test.C) { + query := Expr(map[string]interface{}{"a": 1}).HasFields("a") + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestManipulationHasFieldsNested(c *test.C) { + query := Expr(map[string]interface{}{"a": map[string]interface{}{"b": 1}}).HasFields(map[string]interface{}{"a": map[string]interface{}{"b": true}}) + + var response bool + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestManipulationHasFieldsNestedShort(c *test.C) { + query := Expr(map[string]interface{}{"a": map[string]interface{}{"b": 1}}).HasFields(map[string]interface{}{"a": "b"}) + + var response bool + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestManipulationHasFieldsFalse(c *test.C) { + query := Expr(map[string]interface{}{"a": 1}).HasFields("b") + + var response bool + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestManipulationInsertAt(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).InsertAt(1, 1.5) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 1.5, 2, 3}) +} + +func (s *RethinkSuite) TestManipulationSpliceAt(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).SpliceAt(1, []interface{}{1.25, 1.5, 1.75}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 1.25, 1.5, 1.75, 2, 3}) +} + +func (s *RethinkSuite) TestManipulationDeleteAt(c *test.C) { + query := Expr([]interface{}{1, 2, 3}).DeleteAt(1) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 3}) +} + +func (s *RethinkSuite) TestManipulationDeleteAtRange(c *test.C) { + query := Expr([]interface{}{1, 2, 3, 4}).DeleteAt(1, 3) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 4}) +} + +func (s *RethinkSuite) TestManipulationChangeAt(c *test.C) { + query := Expr([]interface{}{1, 5, 3, 4}).ChangeAt(1, 2) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4}) +} + +func (s *RethinkSuite) TestManipulationKeys(c *test.C) { + query := Expr(map[string]interface{}{"a": 1, "b": 2, "c": 3}).Keys() + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{"a", "b", "c"}) +} + +func (s *RethinkSuite) TestManipulationObject(c *test.C) { + query := Object("a", 1, "b", 2) + + var response interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{ + "a": 1, + "b": 2, + }) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go new file mode 100644 index 0000000..6efe273 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math.go @@ -0,0 +1,177 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Add sums two numbers or concatenates two arrays. +func (t Term) Add(args ...interface{}) Term { + return constructMethodTerm(t, "Add", p.Term_ADD, args, map[string]interface{}{}) +} + +// Add sums two numbers or concatenates two arrays. +func Add(args ...interface{}) Term { + return constructRootTerm("Add", p.Term_ADD, args, map[string]interface{}{}) +} + +// Sub subtracts two numbers. +func (t Term) Sub(args ...interface{}) Term { + return constructMethodTerm(t, "Sub", p.Term_SUB, args, map[string]interface{}{}) +} + +// Sub subtracts two numbers. +func Sub(args ...interface{}) Term { + return constructRootTerm("Sub", p.Term_SUB, args, map[string]interface{}{}) +} + +// Mul multiplies two numbers. +func (t Term) Mul(args ...interface{}) Term { + return constructMethodTerm(t, "Mul", p.Term_MUL, args, map[string]interface{}{}) +} + +func Mul(args ...interface{}) Term { + return constructRootTerm("Mul", p.Term_MUL, args, map[string]interface{}{}) +} + +// Div divides two numbers. +func (t Term) Div(args ...interface{}) Term { + return constructMethodTerm(t, "Div", p.Term_DIV, args, map[string]interface{}{}) +} + +// Div divides two numbers. +func Div(args ...interface{}) Term { + return constructRootTerm("Div", p.Term_DIV, args, map[string]interface{}{}) +} + +// Mod divides two numbers and returns the remainder. +func (t Term) Mod(args ...interface{}) Term { + return constructMethodTerm(t, "Mod", p.Term_MOD, args, map[string]interface{}{}) +} + +// Mod divides two numbers and returns the remainder. +func Mod(args ...interface{}) Term { + return constructRootTerm("Mod", p.Term_MOD, args, map[string]interface{}{}) +} + +// And performs a logical and on two values. +func (t Term) And(args ...interface{}) Term { + return constructMethodTerm(t, "And", p.Term_ALL, args, map[string]interface{}{}) +} + +// And performs a logical and on two values. +func And(args ...interface{}) Term { + return constructRootTerm("And", p.Term_ALL, args, map[string]interface{}{}) +} + +// Or performs a logical or on two values. +func (t Term) Or(args ...interface{}) Term { + return constructMethodTerm(t, "Or", p.Term_ANY, args, map[string]interface{}{}) +} + +// Or performs a logical or on two values. +func Or(args ...interface{}) Term { + return constructRootTerm("Or", p.Term_ANY, args, map[string]interface{}{}) +} + +// Eq returns true if two values are equal. +func (t Term) Eq(args ...interface{}) Term { + return constructMethodTerm(t, "Eq", p.Term_EQ, args, map[string]interface{}{}) +} + +// Eq returns true if two values are equal. +func Eq(args ...interface{}) Term { + return constructRootTerm("Eq", p.Term_EQ, args, map[string]interface{}{}) +} + +// Ne returns true if two values are not equal. +func (t Term) Ne(args ...interface{}) Term { + return constructMethodTerm(t, "Ne", p.Term_NE, args, map[string]interface{}{}) +} + +// Ne returns true if two values are not equal. +func Ne(args ...interface{}) Term { + return constructRootTerm("Ne", p.Term_NE, args, map[string]interface{}{}) +} + +// Gt returns true if the first value is greater than the second. +func (t Term) Gt(args ...interface{}) Term { + return constructMethodTerm(t, "Gt", p.Term_GT, args, map[string]interface{}{}) +} + +// Gt returns true if the first value is greater than the second. +func Gt(args ...interface{}) Term { + return constructRootTerm("Gt", p.Term_GT, args, map[string]interface{}{}) +} + +// Ge returns true if the first value is greater than or equal to the second. +func (t Term) Ge(args ...interface{}) Term { + return constructMethodTerm(t, "Ge", p.Term_GE, args, map[string]interface{}{}) +} + +// Ge returns true if the first value is greater than or equal to the second. +func Ge(args ...interface{}) Term { + return constructRootTerm("Ge", p.Term_GE, args, map[string]interface{}{}) +} + +// Lt returns true if the first value is less than the second. +func (t Term) Lt(args ...interface{}) Term { + return constructMethodTerm(t, "Lt", p.Term_LT, args, map[string]interface{}{}) +} + +// Lt returns true if the first value is less than the second. +func Lt(args ...interface{}) Term { + return constructRootTerm("Lt", p.Term_LT, args, map[string]interface{}{}) +} + +// Le returns true if the first value is less than or equal to the second. +func (t Term) Le(args ...interface{}) Term { + return constructMethodTerm(t, "Le", p.Term_LE, args, map[string]interface{}{}) +} + +// Le returns true if the first value is less than or equal to the second. +func Le(args ...interface{}) Term { + return constructRootTerm("Le", p.Term_LE, args, map[string]interface{}{}) +} + +// Not performs a logical not on a value. +func (t Term) Not(args ...interface{}) Term { + return constructMethodTerm(t, "Not", p.Term_NOT, args, map[string]interface{}{}) +} + +// Not performs a logical not on a value. +func Not(args ...interface{}) Term { + return constructRootTerm("Not", p.Term_NOT, args, map[string]interface{}{}) +} + +type RandomOpts struct { + Float interface{} `gorethink:"float,omitempty"` +} + +func (o *RandomOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Generate a random number between the given bounds. If no arguments are +// given, the result will be a floating-point number in the range [0,1). +// +// When passing a single argument, r.random(x), the result will be in the +// range [0,x), and when passing two arguments, r.random(x,y), the range is +// [x,y). If x and y are equal, an error will occur, unless generating a +// floating-point number, for which x will be returned. +// +// Note: The last argument given will always be the 'open' side of the range, +// but when generating a floating-point number, the 'open' side may be less +// than the 'closed' side. +func (t Term) Random(args ...interface{}) Term { + var opts = map[string]interface{}{} + + // Look for options map + if len(args) > 0 { + if possibleOpts, ok := args[len(args)-1].(RandomOpts); ok { + opts = possibleOpts.toMap() + args = args[:len(args)-1] + } + } + + return constructMethodTerm(t, "Random", p.Term_RANDOM, args, opts) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go new file mode 100644 index 0000000..0b9945c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_math_test.go @@ -0,0 +1,252 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestMathAdd(c *test.C) { + query := Expr(1).Add(2) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 3) +} + +func (s *RethinkSuite) TestMathSub(c *test.C) { + query := Expr(2).Sub(1) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestMathSubNegative(c *test.C) { + query := Expr(1).Sub(2) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, -1) +} + +func (s *RethinkSuite) TestMathMul(c *test.C) { + query := Expr(5).Mul(4) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 20) +} + +func (s *RethinkSuite) TestMathDiv(c *test.C) { + query := Expr(8).Div(4) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 2) +} + +func (s *RethinkSuite) TestMathMod(c *test.C) { + query := Expr(7).Mod(2) + + var response int + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, 1) +} + +func (s *RethinkSuite) TestMathEqTrue(c *test.C) { + query := Expr(1).Eq(1) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestMathEqFalse(c *test.C) { + query := Expr(1).Eq(2) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestMathEqStringTrue(c *test.C) { + query := Expr("test").Eq("test") + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestCompareLt(c *test.C) { + query := Expr(2).Lt(1) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestCompareLe(c *test.C) { + query := Expr(2).Le(1) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestCompareLeEqual(c *test.C) { + query := Expr(2).Le(2) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestCompareGt(c *test.C) { + query := Expr(2).Gt(1) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestCompareGe(c *test.C) { + query := Expr(2).Ge(1) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestCompareGeEqual(c *test.C) { + query := Expr(2).Le(2) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestBoolNotTrue(c *test.C) { + query := Expr(true).Not() + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, false) +} + +func (s *RethinkSuite) TestBoolAnd(c *test.C) { + query := Expr(true).And(true) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestBoolOr(c *test.C) { + query := Expr(true).Or(false) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestBoolDeMorgan(c *test.C) { + query := Expr(true).And(false).Eq(Expr(true).Not().Or(Expr(false).Not()).Not()) + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go new file mode 100644 index 0000000..d4cfb8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go @@ -0,0 +1,108 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Reference a database. +func Db(args ...interface{}) Term { + return constructRootTerm("Db", p.Term_DB, args, map[string]interface{}{}) +} + +type TableOpts struct { + UseOutdated interface{} `gorethink:"use_outdated,omitempty"` +} + +func (o *TableOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Select all documents in a table. This command can be chained with other +// commands to do further processing on the data. +// +// Optional arguments (see http://www.rethinkdb.com/api/#js:selecting_data-table for more information): +// "use_outdated" (boolean - defaults to false) +func Table(name interface{}, optArgs ...TableOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructRootTerm("Table", p.Term_TABLE, []interface{}{name}, opts) +} + +// Select all documents in a table. This command can be chained with other +// commands to do further processing on the data. +// +// Optional arguments (see http://www.rethinkdb.com/api/#js:selecting_data-table for more information): +// "use_outdated" (boolean - defaults to false) +func (t Term) Table(name interface{}, optArgs ...TableOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Table", p.Term_TABLE, []interface{}{name}, opts) +} + +// Get a document by primary key. If nothing was found, RethinkDB will return a nil value. +func (t Term) Get(args ...interface{}) Term { + return constructMethodTerm(t, "Get", p.Term_GET, args, map[string]interface{}{}) +} + +// Get all documents where the given value matches the value of the primary index. +func (t Term) GetAll(keys ...interface{}) Term { + return constructMethodTerm(t, "GetAll", p.Term_GET_ALL, keys, map[string]interface{}{}) +} + +// Get all documents where the given value matches the value of the requested index. +func (t Term) GetAllByIndex(index interface{}, keys ...interface{}) Term { + return constructMethodTerm(t, "GetAll", p.Term_GET_ALL, keys, map[string]interface{}{"index": index}) +} + +type BetweenOpts struct { + Index interface{} `gorethink:"index,omitempty"` + LeftBound interface{} `gorethink:"left_bound,omitempty"` + RightBound interface{} `gorethink:"right_bound,omitempty"` +} + +func (o *BetweenOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Get all documents between two keys. Accepts three optional arguments: `index`, +// `left_bound`, and `right_bound`. If `index` is set to the name of a secondary +// index, `between` will return all documents where that index's value is in the +// specified range (it uses the primary key by default). `left_bound` or +// `right_bound` may be set to `open` or `closed` to indicate whether or not to +// include that endpoint of the range (by default, `left_bound` is closed and +// `right_bound` is open). +func (t Term) Between(lowerKey, upperKey interface{}, optArgs ...BetweenOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Between", p.Term_BETWEEN, []interface{}{lowerKey, upperKey}, opts) +} + +type FilterOpts struct { + Default interface{} `gorethink:"default,omitempty"` +} + +func (o *FilterOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Get all the documents for which the given predicate is true. +// +// Filter can be called on a sequence, selection, or a field containing an array +// of elements. The return type is the same as the type on which the function was +// called on. The body of every filter is wrapped in an implicit `.default(false)`, +// and the default value can be changed by passing the optional argument `default`. +// Setting this optional argument to `r.error()` will cause any non-existence +// errors to abort the filter. +func (t Term) Filter(f interface{}, optArgs ...FilterOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Filter", p.Term_FILTER, []interface{}{funcWrap(f)}, opts) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go new file mode 100644 index 0000000..03ebc02 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select_test.go @@ -0,0 +1,467 @@ +package gorethink + +import ( + "fmt" + "math/rand" + "testing" + "time" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestSelectGet(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("Table1").Get(6) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) +} + +func (s *RethinkSuite) TestSelectGetAll(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + Db("test").Table("Table1").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table1").GetAll(6).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + }) +} + +func (s *RethinkSuite) TestSelectGetAllMultiple(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + Db("test").Table("Table1").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table1").GetAll(1, 2, 3).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + }) +} + +func (s *RethinkSuite) TestSelectGetAllByIndex(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + Db("test").Table("Table1").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("Table1").GetAllByIndex("num", 15).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) +} + +func (s *RethinkSuite) TestSelectGetAllMultipleByIndex(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table2").Exec(sess) + Db("test").Table("Table2").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table2").Insert(objList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("Table2").GetAllByIndex("num", 15).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 6, "g1": 1, "g2": 1, "num": 15}) +} + +func (s *RethinkSuite) TestSelectGetAllCompoundIndex(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableDrop("TableCompound").Exec(sess) + Db("test").TableCreate("TableCompound").Exec(sess) + write, err := Db("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { + return []interface{}{row.Field("first_name"), row.Field("last_name")} + }).RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(write.Created, test.Equals, 1) + + // Insert rows + Db("test").Table("TableCompound").Insert(nameList).Exec(sess) + + // Test query + var response interface{} + query := Db("test").Table("TableCompound").GetAllByIndex("full_name", []interface{}{"John", "Smith"}) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, map[string]interface{}{"id": 1, "first_name": "John", "last_name": "Smith", "gender": "M"}) +} + +func (s *RethinkSuite) TestSelectBetween(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table1").Between(1, 3).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestSelectBetweenWithIndex(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table2").Exec(sess) + Db("test").Table("Table2").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table2").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table2").Between(10, 50, BetweenOpts{ + Index: "num", + }).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestSelectBetweenWithOptions(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table2").Exec(sess) + Db("test").Table("Table2").IndexCreate("num").Exec(sess) + + // Insert rows + Db("test").Table("Table2").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table2").Between(10, 50, BetweenOpts{ + Index: "num", + RightBound: "closed", + }).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestSelectFilterImplicit(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table1").Filter(Row.Field("num").Ge(50)).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + }) +} + +func (s *RethinkSuite) TestSelectFilterFunc(c *test.C) { + // Ensure table + database exist + DbCreate("test").Exec(sess) + Db("test").TableCreate("Table1").Exec(sess) + + // Insert rows + Db("test").Table("Table1").Insert(objList).Exec(sess) + + // Test query + var response []interface{} + query := Db("test").Table("Table1").Filter(func(row Term) Term { + return row.Field("num").Ge(50) + }).OrderBy("id") + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + }) +} + +func (s *RethinkSuite) TestSelectManyRows(c *test.C) { + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + for i := 0; i < 100; i++ { + data := []interface{}{} + + for j := 0; j < 100; j++ { + data = append(data, map[string]interface{}{ + "i": i, + "j": j, + }) + } + + Db("test").Table("TestMany").Insert(data).Run(sess) + } + + // Test query + res, err := Db("test").Table("TestMany").Run(sess, RunOpts{ + MaxBatchRows: 1, + }) + c.Assert(err, test.IsNil) + + var n int + var response map[string]interface{} + for res.Next(&response) { + n++ + } + + c.Assert(res.Err(), test.IsNil) + c.Assert(n, test.Equals, 10000) +} + +func (s *RethinkSuite) TestConcurrentSelectManyWorkers(c *test.C) { + if testing.Short() { + c.Skip("Skipping long test") + } + + rand.Seed(time.Now().UnixNano()) + sess, _ := Connect(ConnectOpts{ + Address: url, + AuthKey: authKey, + + MaxOpen: 200, + MaxIdle: 200, + }) + + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableDrop("TestConcurrent").RunWrite(sess) + Db("test").TableCreate("TestConcurrent").RunWrite(sess) + Db("test").TableDrop("TestConcurrent2").RunWrite(sess) + Db("test").TableCreate("TestConcurrent2").RunWrite(sess) + + // Insert rows + for j := 0; j < 200; j++ { + Db("test").Table("TestConcurrent").Insert(map[string]interface{}{ + "id": j, + "i": j, + }).Run(sess) + Db("test").Table("TestConcurrent2").Insert(map[string]interface{}{ + "j": j, + "k": j * 2, + }).Run(sess) + } + + // Test queries concurrently + numQueries := 1000 + numWorkers := 10 + queryChan := make(chan int) + doneChan := make(chan error) + + // Start workers + for i := 0; i < numWorkers; i++ { + go func() { + for _ = range queryChan { + res, err := Db("test").Table("TestConcurrent2").EqJoin("j", Db("test").Table("TestConcurrent")).Zip().Run(sess) + if err != nil { + doneChan <- err + return + } + + var response []map[string]interface{} + err = res.All(&response) + if err != nil { + doneChan <- err + return + } + if err := res.Close(); err != nil { + doneChan <- err + return + } + + if len(response) != 200 { + doneChan <- fmt.Errorf("expected response length 200, received %d", len(response)) + return + } + + res, err = Db("test").Table("TestConcurrent").Get(response[rand.Intn(len(response))]["id"]).Run(sess) + if err != nil { + doneChan <- err + return + } + + err = res.All(&response) + if err != nil { + doneChan <- err + return + } + if err := res.Close(); err != nil { + doneChan <- err + return + } + + if len(response) != 1 { + doneChan <- fmt.Errorf("expected response length 1, received %d", len(response)) + return + } + + doneChan <- nil + } + }() + } + + go func() { + for i := 0; i < numQueries; i++ { + queryChan <- i + } + }() + + for i := 0; i < numQueries; i++ { + ret := <-doneChan + if ret != nil { + c.Fatalf("non-nil error returned (%s)", ret) + } + } +} + +func (s *RethinkSuite) TestConcurrentSelectManyRows(c *test.C) { + if testing.Short() { + c.Skip("Skipping long test") + } + + // Ensure table + database exist + DbCreate("test").RunWrite(sess) + Db("test").TableCreate("TestMany").RunWrite(sess) + Db("test").Table("TestMany").Delete().RunWrite(sess) + + // Insert rows + for i := 0; i < 100; i++ { + Db("test").Table("TestMany").Insert(map[string]interface{}{ + "i": i, + }).Run(sess) + } + + // Test queries concurrently + attempts := 10 + waitChannel := make(chan error, attempts) + + for i := 0; i < attempts; i++ { + go func(i int, c chan error) { + res, err := Db("test").Table("TestMany").Run(sess) + if err != nil { + c <- err + return + } + + var response []map[string]interface{} + err = res.All(&response) + if err != nil { + c <- err + return + } + + if len(response) != 100 { + c <- fmt.Errorf("expected response length 100, received %d", len(response)) + return + } + + c <- nil + }(i, waitChannel) + } + + for i := 0; i < attempts; i++ { + ret := <-waitChannel + if ret != nil { + c.Fatalf("non-nil error returned (%s)", ret) + } + } +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go new file mode 100644 index 0000000..23b5de6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string.go @@ -0,0 +1,35 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Match against a regular expression. Returns a match object containing the +// matched string, that string's start/end position, and the capture groups. +// +// Expr("id:0,name:mlucy,foo:bar").Match("name:(\\w+)").Field("groups").Nth(0).Field("str") +func (t Term) Match(args ...interface{}) Term { + return constructMethodTerm(t, "Match", p.Term_MATCH, args, map[string]interface{}{}) +} + +// Splits a string into substrings. Splits on whitespace when called with no arguments. +// When called with a separator, splits on that separator. When called with a separator +// and a maximum number of splits, splits on that separator at most max_splits times. +// (Can be called with null as the separator if you want to split on whitespace while still +// specifying max_splits.) +// +// Mimics the behavior of Python's string.split in edge cases, except for splitting on the +// empty string, which instead produces an array of single-character strings. +func (t Term) Split(args ...interface{}) Term { + return constructMethodTerm(t, "Split", p.Term_SPLIT, funcWrapArgs(args), map[string]interface{}{}) +} + +// Upcases a string. +func (t Term) Upcase(args ...interface{}) Term { + return constructMethodTerm(t, "Upcase", p.Term_UPCASE, args, map[string]interface{}{}) +} + +// Downcases a string. +func (t Term) Downcase(args ...interface{}) Term { + return constructMethodTerm(t, "Downcase", p.Term_DOWNCASE, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go new file mode 100644 index 0000000..a11e796 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_string_test.go @@ -0,0 +1,91 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestStringMatchSuccess(c *test.C) { + query := Expr("id:0,name:mlucy,foo:bar").Match("name:(\\w+)").Field("groups").Nth(0).Field("str") + + var response string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "mlucy") +} + +func (s *RethinkSuite) TestStringMatchFail(c *test.C) { + query := Expr("id:0,foo:bar").Match("name:(\\w+)") + + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + c.Assert(res.IsNil(), test.Equals, true) +} + +func (s *RethinkSuite) TestStringSplit(c *test.C) { + query := Expr("a,b,c").Split(",") + + var response []string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []string{"a", "b", "c"}) +} + +func (s *RethinkSuite) TestStringSplitMax(c *test.C) { + query := Expr("a,b,c").Split(",", 1) + + var response []string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []string{"a", "b,c"}) +} + +func (s *RethinkSuite) TestStringSplitWhitespace(c *test.C) { + query := Expr("a b c").Split() + + var response []string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.DeepEquals, []string{"a", "b", "c"}) +} + +func (s *RethinkSuite) TestStringMatchUpcase(c *test.C) { + query := Expr("tESt").Upcase() + + var response string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "TEST") +} + +func (s *RethinkSuite) TestStringMatchDowncase(c *test.C) { + query := Expr("tESt").Downcase() + + var response string + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "test") +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go new file mode 100644 index 0000000..6df9f97 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table.go @@ -0,0 +1,143 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +type TableCreateOpts struct { + PrimaryKey interface{} `gorethink:"primary_key,omitempty"` + Durability interface{} `gorethink:"durability,omitempty"` + CacheSize interface{} `gorethink:"cache_size,omitempty"` + DataCenter interface{} `gorethink:"datacenter,omitempty"` +} + +func (o *TableCreateOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Create a table. A RethinkDB table is a collection of JSON documents. +// +// If successful, the operation returns an object: {created: 1}. If a table with +// the same name already exists, the operation throws RqlRuntimeError. +// +// Note: that you can only use alphanumeric characters and underscores for the +// table name. +// +// r.Db("database").TableCreate("table", "durability", "soft").Run(sess) +func (t Term) TableCreate(name interface{}, optArgs ...TableCreateOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "TableCreate", p.Term_TABLE_CREATE, []interface{}{name}, opts) +} + +// Drop a table. The table and all its data will be deleted. +// +// If successful, the operation returns an object: {dropped: 1}. If the specified +// table doesn't exist a RqlRuntimeError is thrown. +func (t Term) TableDrop(args ...interface{}) Term { + return constructMethodTerm(t, "TableDrop", p.Term_TABLE_DROP, args, map[string]interface{}{}) +} + +// List all table names in a database. +func (t Term) TableList(args ...interface{}) Term { + return constructMethodTerm(t, "TableList", p.Term_TABLE_LIST, args, map[string]interface{}{}) +} + +type IndexCreateOpts struct { + Multi interface{} `gorethink:"multi,omitempty"` + Geo interface{} `gorethink:"geo,omitempty"` +} + +func (o *IndexCreateOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Create a new secondary index on this table. +// +// A multi index can be created by passing an optional multi argument. Multi indexes +// functions should return arrays and allow you to query based on whether a value +// is present in the returned array. The example would allow us to get heroes who +// possess a specific ability (the field 'abilities' is an array). +func (t Term) IndexCreate(name interface{}, optArgs ...IndexCreateOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "IndexCreate", p.Term_INDEX_CREATE, []interface{}{name}, opts) +} + +// Create a new secondary index on this table based on the value of the function +// passed. +// +// A compound index can be created by returning an array of values to use as the secondary index key. +func (t Term) IndexCreateFunc(name, f interface{}, optArgs ...IndexCreateOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "IndexCreate", p.Term_INDEX_CREATE, []interface{}{name, funcWrap(f)}, opts) +} + +// Delete a previously created secondary index of this table. +func (t Term) IndexDrop(args ...interface{}) Term { + return constructMethodTerm(t, "IndexDrop", p.Term_INDEX_DROP, args, map[string]interface{}{}) +} + +// List all the secondary indexes of this table. +func (t Term) IndexList(args ...interface{}) Term { + return constructMethodTerm(t, "IndexList", p.Term_INDEX_LIST, args, map[string]interface{}{}) +} + +type IndexRenameOpts struct { + Overwrite interface{} `gorethink:"overwrite,omitempty"` +} + +func (o *IndexRenameOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// IndexRename renames an existing secondary index on a table. If the optional +// argument overwrite is specified as True, a previously existing index with the +// new name will be deleted and the index will be renamed. If overwrite is False +// (the default) an error will be raised if the new index name already exists. +func (t Term) IndexRename(oldName, newName interface{}, optArgs ...IndexRenameOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "IndexRename", p.Term_INDEX_RENAME, []interface{}{oldName, newName}, opts) +} + +// Get the status of the specified indexes on this table, or the status of all +// indexes on this table if no indexes are specified. +func (t Term) IndexStatus(args ...interface{}) Term { + return constructMethodTerm(t, "IndexStatus", p.Term_INDEX_STATUS, args, map[string]interface{}{}) +} + +// Wait for the specified indexes on this table to be ready, or for all indexes +// on this table to be ready if no indexes are specified. +func (t Term) IndexWait(args ...interface{}) Term { + return constructMethodTerm(t, "IndexWait", p.Term_INDEX_WAIT, args, map[string]interface{}{}) +} + +type ChangesOpts struct { + Squash interface{} `gorethink:"squash,omitempty"` +} + +func (o *ChangesOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Takes a table and returns an infinite stream of objects representing changes to that table. +// Whenever an insert, delete, update or replace is performed on the table, an object of the form +// {old_val:..., new_val:...} will be added to the stream. For an insert, old_val will be +// null, and for a delete, new_val will be null. +func (t Term) Changes(optArgs ...ChangesOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Changes", p.Term_CHANGES, []interface{}{}, opts) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go new file mode 100644 index 0000000..a8f6db4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_table_test.go @@ -0,0 +1,214 @@ +package gorethink + +import ( + "sync" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestTableCreate(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + + // Test database creation + query := Db("test").TableCreate("test") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.TablesCreated, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableCreatePrimaryKey(c *test.C) { + Db("test").TableDrop("testOpts").Exec(sess) + + // Test database creation + query := Db("test").TableCreate("testOpts", TableCreateOpts{ + PrimaryKey: "it", + }) + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.TablesCreated, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableCreateSoftDurability(c *test.C) { + Db("test").TableDrop("testOpts").Exec(sess) + + // Test database creation + query := Db("test").TableCreate("testOpts", TableCreateOpts{ + Durability: "soft", + }) + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.TablesCreated, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableCreateSoftMultipleOpts(c *test.C) { + Db("test").TableDrop("testOpts").Exec(sess) + + // Test database creation + query := Db("test").TableCreate("testOpts", TableCreateOpts{ + PrimaryKey: "it", + Durability: "soft", + }) + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.TablesCreated, jsonEquals, 1) + + Db("test").TableDrop("test").Exec(sess) +} + +func (s *RethinkSuite) TestTableList(c *test.C) { + var response []interface{} + + Db("test").TableCreate("test").Exec(sess) + + // Try and find it in the list + success := false + res, err := Db("test").TableList().Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.FitsTypeOf, []interface{}{}) + + for _, db := range response { + if db == "test" { + success = true + } + } + + c.Assert(success, test.Equals, true) +} + +func (s *RethinkSuite) TestTableDelete(c *test.C) { + Db("test").TableCreate("test").Exec(sess) + + // Test database creation + query := Db("test").TableDrop("test") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.TablesDropped, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableIndexCreate(c *test.C) { + Db("test").TableCreate("test").Exec(sess) + Db("test").Table("test").IndexDrop("test").Exec(sess) + + // Test database creation + query := Db("test").Table("test").IndexCreate("test", IndexCreateOpts{ + Multi: true, + }) + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.Created, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableCompoundIndexCreate(c *test.C) { + DbCreate("test").Exec(sess) + Db("test").TableDrop("TableCompound").Exec(sess) + Db("test").TableCreate("TableCompound").Exec(sess) + response, err := Db("test").Table("TableCompound").IndexCreateFunc("full_name", func(row Term) interface{} { + return []interface{}{row.Field("first_name"), row.Field("last_name")} + }).RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.Created, test.Equals, 1) +} + +func (s *RethinkSuite) TestTableIndexList(c *test.C) { + var response []interface{} + + Db("test").TableCreate("test").Exec(sess) + Db("test").Table("test").IndexCreate("test").Exec(sess) + + // Try and find it in the list + success := false + res, err := Db("test").Table("test").IndexList().Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.FitsTypeOf, []interface{}{}) + + for _, db := range response { + if db == "test" { + success = true + } + } + + c.Assert(success, test.Equals, true) +} + +func (s *RethinkSuite) TestTableIndexDelete(c *test.C) { + Db("test").TableCreate("test").Exec(sess) + Db("test").Table("test").IndexCreate("test").Exec(sess) + + // Test database creation + query := Db("test").Table("test").IndexDrop("test") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.Dropped, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableIndexRename(c *test.C) { + Db("test").TableDrop("test").Exec(sess) + Db("test").TableCreate("test").Exec(sess) + Db("test").Table("test").IndexCreate("test").Exec(sess) + + // Test index rename + query := Db("test").Table("test").IndexRename("test", "test2") + + response, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(response.Renamed, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTableChanges(c *test.C) { + Db("test").TableDrop("changes").Exec(sess) + Db("test").TableCreate("changes").Exec(sess) + + var n int + + res, err := Db("test").Table("changes").Changes().Run(sess) + if err != nil { + c.Fatal(err.Error()) + } + + wg := &sync.WaitGroup{} + wg.Add(1) + + // Use goroutine to wait for changes. Prints the first 10 results + go func() { + var response interface{} + for n < 10 && res.Next(&response) { + n++ + } + + if res.Err() != nil { + c.Fatal(res.Err()) + } + + wg.Done() + }() + + Db("test").Table("changes").Insert(map[string]interface{}{"n": 1}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 2}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 3}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 4}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 5}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 6}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 7}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 8}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 9}).Exec(sess) + Db("test").Table("changes").Insert(map[string]interface{}{"n": 10}).Exec(sess) + + wg.Wait() + + c.Assert(n, test.Equals, 10) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go new file mode 100644 index 0000000..fb73bf5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_test.go @@ -0,0 +1,50 @@ +package gorethink + +import test "gopkg.in/check.v1" + +func (s *RethinkSuite) TestQueryRun(c *test.C) { + var response string + + res, err := Expr("Test").Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "Test") +} + +func (s *RethinkSuite) TestQueryExec(c *test.C) { + err := Expr("Test").Exec(sess) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TestQueryProfile(c *test.C) { + var response string + + res, err := Expr("Test").Run(sess, RunOpts{ + Profile: true, + }) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(res.Profile(), test.NotNil) + c.Assert(response, test.Equals, "Test") +} + +func (s *RethinkSuite) TestQueryRunRawTime(c *test.C) { + var response map[string]interface{} + + res, err := Now().Run(sess, RunOpts{ + TimeFormat: "raw", + }) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response["$reql_type$"], test.NotNil) + c.Assert(response["$reql_type$"], test.Equals, "TIME") +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go new file mode 100644 index 0000000..5ebc09e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time.go @@ -0,0 +1,170 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +// Returns a time object representing the current time in UTC +func Now(args ...interface{}) Term { + return constructRootTerm("Now", p.Term_NOW, args, map[string]interface{}{}) +} + +// Create a time object for a specific time +func Time(args ...interface{}) Term { + return constructRootTerm("Time", p.Term_TIME, args, map[string]interface{}{}) +} + +// Returns a time object based on seconds since epoch +func EpochTime(args ...interface{}) Term { + return constructRootTerm("EpochTime", p.Term_EPOCH_TIME, args, map[string]interface{}{}) +} + +type ISO8601Opts struct { + DefaultTimezone interface{} `gorethink:"default_timezone,omitempty"` +} + +func (o *ISO8601Opts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Returns a time object based on an ISO8601 formatted date-time string +// +// Optional arguments (see http://www.rethinkdb.com/api/#js:dates_and_times-iso8601 for more information): +// "default_timezone" (string) +func ISO8601(date interface{}, optArgs ...ISO8601Opts) Term { + + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructRootTerm("ISO8601", p.Term_ISO8601, []interface{}{date}, opts) +} + +// Returns a new time object with a different time zone. While the time +// stays the same, the results returned by methods such as hours() will +// change since they take the timezone into account. The timezone argument +// has to be of the ISO 8601 format. +func (t Term) InTimezone(args ...interface{}) Term { + return constructMethodTerm(t, "InTimezone", p.Term_IN_TIMEZONE, args, map[string]interface{}{}) +} + +// Returns the timezone of the time object +func (t Term) Timezone(args ...interface{}) Term { + return constructMethodTerm(t, "Timezone", p.Term_TIMEZONE, args, map[string]interface{}{}) +} + +type DuringOpts struct { + LeftBound interface{} `gorethink:"left_bound,omitempty"` + RightBound interface{} `gorethink:"right_bound,omitempty"` +} + +func (o *DuringOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Returns true if a time is between two other times +// (by default, inclusive for the start, exclusive for the end). +// +// Optional arguments (see http://www.rethinkdb.com/api/#js:dates_and_times-during for more information): +// "left_bound" and "right_bound" ("open" for exclusive or "closed" for inclusive) +func (t Term) During(startTime, endTime interface{}, optArgs ...DuringOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "During", p.Term_DURING, []interface{}{startTime, endTime}, opts) +} + +// Return a new time object only based on the day, month and year +// (ie. the same day at 00:00). +func (t Term) Date(args ...interface{}) Term { + return constructMethodTerm(t, "Date", p.Term_DATE, args, map[string]interface{}{}) +} + +// Return the number of seconds elapsed since the beginning of the +// day stored in the time object. +func (t Term) TimeOfDay(args ...interface{}) Term { + return constructMethodTerm(t, "TimeOfDay", p.Term_TIME_OF_DAY, args, map[string]interface{}{}) +} + +// Return the year of a time object. +func (t Term) Year(args ...interface{}) Term { + return constructMethodTerm(t, "Year", p.Term_YEAR, args, map[string]interface{}{}) +} + +// Return the month of a time object as a number between 1 and 12. +// For your convenience, the terms r.January(), r.February() etc. are +// defined and map to the appropriate integer. +func (t Term) Month(args ...interface{}) Term { + return constructMethodTerm(t, "Month", p.Term_MONTH, args, map[string]interface{}{}) +} + +// Return the day of a time object as a number between 1 and 31. +func (t Term) Day(args ...interface{}) Term { + return constructMethodTerm(t, "Day", p.Term_DAY, args, map[string]interface{}{}) +} + +// Return the day of week of a time object as a number between +// 1 and 7 (following ISO 8601 standard). For your convenience, +// the terms r.Monday(), r.Tuesday() etc. are defined and map to +// the appropriate integer. +func (t Term) DayOfWeek(args ...interface{}) Term { + return constructMethodTerm(t, "DayOfWeek", p.Term_DAY_OF_WEEK, args, map[string]interface{}{}) +} + +// Return the day of the year of a time object as a number between +// 1 and 366 (following ISO 8601 standard). +func (t Term) DayOfYear(args ...interface{}) Term { + return constructMethodTerm(t, "DayOfYear", p.Term_DAY_OF_YEAR, args, map[string]interface{}{}) +} + +// Return the hour in a time object as a number between 0 and 23. +func (t Term) Hours(args ...interface{}) Term { + return constructMethodTerm(t, "Hours", p.Term_HOURS, args, map[string]interface{}{}) +} + +// Return the minute in a time object as a number between 0 and 59. +func (t Term) Minutes(args ...interface{}) Term { + return constructMethodTerm(t, "Minutes", p.Term_MINUTES, args, map[string]interface{}{}) +} + +// Return the seconds in a time object as a number between 0 and +// 59.999 (double precision). +func (t Term) Seconds(args ...interface{}) Term { + return constructMethodTerm(t, "Seconds", p.Term_SECONDS, args, map[string]interface{}{}) +} + +// Convert a time object to its iso 8601 format. +func (t Term) ToISO8601(args ...interface{}) Term { + return constructMethodTerm(t, "ToISO8601", p.Term_TO_ISO8601, args, map[string]interface{}{}) +} + +// Convert a time object to its epoch time. +func (t Term) ToEpochTime(args ...interface{}) Term { + return constructMethodTerm(t, "ToEpochTime", p.Term_TO_EPOCH_TIME, args, map[string]interface{}{}) +} + +var ( + // Days + Monday = constructRootTerm("Monday", p.Term_MONDAY, []interface{}{}, map[string]interface{}{}) + Tuesday = constructRootTerm("Tuesday", p.Term_TUESDAY, []interface{}{}, map[string]interface{}{}) + Wednesday = constructRootTerm("Wednesday", p.Term_WEDNESDAY, []interface{}{}, map[string]interface{}{}) + Thursday = constructRootTerm("Thursday", p.Term_THURSDAY, []interface{}{}, map[string]interface{}{}) + Friday = constructRootTerm("Friday", p.Term_FRIDAY, []interface{}{}, map[string]interface{}{}) + Saturday = constructRootTerm("Saturday", p.Term_SATURDAY, []interface{}{}, map[string]interface{}{}) + Sunday = constructRootTerm("Sunday", p.Term_SUNDAY, []interface{}{}, map[string]interface{}{}) + + // Months + January = constructRootTerm("January", p.Term_JANUARY, []interface{}{}, map[string]interface{}{}) + February = constructRootTerm("February", p.Term_FEBRUARY, []interface{}{}, map[string]interface{}{}) + March = constructRootTerm("March", p.Term_MARCH, []interface{}{}, map[string]interface{}{}) + April = constructRootTerm("April", p.Term_APRIL, []interface{}{}, map[string]interface{}{}) + May = constructRootTerm("May", p.Term_MAY, []interface{}{}, map[string]interface{}{}) + June = constructRootTerm("June", p.Term_JUNE, []interface{}{}, map[string]interface{}{}) + July = constructRootTerm("July", p.Term_JULY, []interface{}{}, map[string]interface{}{}) + August = constructRootTerm("August", p.Term_AUGUST, []interface{}{}, map[string]interface{}{}) + September = constructRootTerm("September", p.Term_SEPTEMBER, []interface{}{}, map[string]interface{}{}) + October = constructRootTerm("October", p.Term_OCTOBER, []interface{}{}, map[string]interface{}{}) + November = constructRootTerm("November", p.Term_NOVEMBER, []interface{}{}, map[string]interface{}{}) + December = constructRootTerm("December", p.Term_DECEMBER, []interface{}{}, map[string]interface{}{}) +) diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go new file mode 100644 index 0000000..17205a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_time_test.go @@ -0,0 +1,137 @@ +package gorethink + +import ( + "time" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestTimeTime(c *test.C) { + var response time.Time + res, err := Time(1986, 11, 3, 12, 30, 15, "Z").Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response.Equal(time.Date(1986, 11, 3, 12, 30, 15, 0, time.UTC)), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeTimeMillisecond(c *test.C) { + var response time.Time + res, err := Time(1986, 11, 3, 12, 30, 15.679, "Z").Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response.Equal(time.Date(1986, 11, 3, 12, 30, 15, 679.00002*1000*1000, time.UTC)), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeEpochTime(c *test.C) { + var response time.Time + res, err := EpochTime(531360000).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response.Equal(time.Date(1986, 11, 3, 0, 0, 0, 0, time.UTC)), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeExpr(c *test.C) { + var response time.Time + t := time.Unix(531360000, 0) + res, err := Expr(Expr(t)).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TestTimeExprMillisecond(c *test.C) { + var response time.Time + t := time.Unix(531360000, 679000000) + res, err := Expr(t).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + c.Assert(err, test.IsNil) + c.Assert(float64(response.UnixNano()), test.Equals, float64(t.UnixNano())) +} + +func (s *RethinkSuite) TestTimeISO8601(c *test.C) { + var t1, t2 time.Time + t2, _ = time.Parse("2006-01-02T15:04:05-07:00", "1986-11-03T08:30:00-07:00") + res, err := ISO8601("1986-11-03T08:30:00-07:00").Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&t1) + c.Assert(err, test.IsNil) + c.Assert(t1.Equal(t2), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeInTimezone(c *test.C) { + loc, err := time.LoadLocation("MST") + c.Assert(err, test.IsNil) + var response []time.Time + res, err2 := Expr([]interface{}{Now(), Now().InTimezone("-07:00")}).Run(sess) + c.Assert(err2, test.IsNil) + + err = res.All(&response) + c.Assert(err, test.IsNil) + c.Assert(response[1].Equal(response[0].In(loc)), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeBetween(c *test.C) { + var response interface{} + + times := Expr([]interface{}{ + Time(1986, 9, 3, 12, 30, 15, "Z"), + Time(1986, 10, 3, 12, 30, 15, "Z"), + Time(1986, 11, 3, 12, 30, 15, "Z"), + Time(1986, 12, 3, 12, 30, 15, "Z"), + }) + res, err := times.Filter(func(row Term) Term { + return row.During(Time(1986, 9, 3, 12, 30, 15, "Z"), Time(1986, 11, 3, 12, 30, 15, "Z")) + }).Count().Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(int(response.(float64)), test.Equals, 2) +} + +func (s *RethinkSuite) TestTimeYear(c *test.C) { + var response interface{} + + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Year().Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(int(response.(float64)), test.Equals, 1986) +} + +func (s *RethinkSuite) TestTimeMonth(c *test.C) { + var response interface{} + + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Month().Eq(December).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response.(bool), test.Equals, true) +} + +func (s *RethinkSuite) TestTimeDay(c *test.C) { + var response interface{} + + res, err := Time(1986, 12, 3, 12, 30, 15, "Z").Day().Eq(Wednesday).Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response.(bool), test.Equals, true) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go new file mode 100644 index 0000000..30240aa --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation.go @@ -0,0 +1,159 @@ +package gorethink + +import p "github.com/dancannon/gorethink/ql2" + +// Map transform each element of the sequence by applying the given mapping +// function. It takes two arguments, a sequence and a function of type +// `func (r.Term) interface{}`. +// +// For example this query doubles each element in an array: +// +// r.Map([]int{1,3,6}, func (row r.Term) interface{} { +// return row.Mul(2) +// }) +func Map(args ...interface{}) Term { + if len(args) > 0 { + args = append(args[:len(args)-1], funcWrapArgs(args[len(args)-1:])...) + } + + return constructRootTerm("Map", p.Term_MAP, funcWrapArgs(args), map[string]interface{}{}) +} + +// Map transforms each element of the sequence by applying the given mapping +// function. It takes one argument of type `func (r.Term) interface{}`. +// +// For example this query doubles each element in an array: +// +// r.Expr([]int{1,3,6}).Map(func (row r.Term) interface{} { +// return row.Mul(2) +// }) +func (t Term) Map(args ...interface{}) Term { + return constructMethodTerm(t, "Map", p.Term_MAP, funcWrapArgs(args), map[string]interface{}{}) +} + +// Takes a sequence of objects and a list of fields. If any objects in the +// sequence don't have all of the specified fields, they're dropped from the +// sequence. The remaining objects have the specified fields plucked out. +// (This is identical to `HasFields` followed by `Pluck` on a sequence.) +func (t Term) WithFields(args ...interface{}) Term { + return constructMethodTerm(t, "WithFields", p.Term_WITH_FIELDS, args, map[string]interface{}{}) +} + +// Flattens a sequence of arrays returned by the mapping function into a single +// sequence. +func (t Term) ConcatMap(args ...interface{}) Term { + return constructMethodTerm(t, "ConcatMap", p.Term_CONCAT_MAP, funcWrapArgs(args), map[string]interface{}{}) +} + +type OrderByOpts struct { + Index interface{} `gorethink:"index,omitempty"` +} + +func (o *OrderByOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Sort the sequence by document values of the given key(s). +// To specify the index to use for ordering us a last argument in the following form: +// +// OrderByOpts{Index: "index-name"} +// +// OrderBy defaults to ascending ordering. To explicitly specify the ordering, +// wrap the attribute with either Asc or Desc. +// +// query.OrderBy("name") +// query.OrderBy(Asc("name")) +// query.OrderBy(Desc("name")) +func (t Term) OrderBy(args ...interface{}) Term { + var opts = map[string]interface{}{} + + // Look for options map + if len(args) > 0 { + if possibleOpts, ok := args[len(args)-1].(OrderByOpts); ok { + opts = possibleOpts.toMap() + args = args[:len(args)-1] + } + } + + for k, arg := range args { + if t, ok := arg.(Term); !(ok && (t.termType == p.Term_DESC || t.termType == p.Term_ASC)) { + args[k] = funcWrap(arg) + } + } + + return constructMethodTerm(t, "OrderBy", p.Term_ORDER_BY, args, opts) +} + +func Desc(args ...interface{}) Term { + return constructRootTerm("Desc", p.Term_DESC, funcWrapArgs(args), map[string]interface{}{}) +} + +func Asc(args ...interface{}) Term { + return constructRootTerm("Asc", p.Term_ASC, funcWrapArgs(args), map[string]interface{}{}) +} + +// Skip a number of elements from the head of the sequence. +func (t Term) Skip(args ...interface{}) Term { + return constructMethodTerm(t, "Skip", p.Term_SKIP, args, map[string]interface{}{}) +} + +// End the sequence after the given number of elements. +func (t Term) Limit(args ...interface{}) Term { + return constructMethodTerm(t, "Limit", p.Term_LIMIT, args, map[string]interface{}{}) +} + +type SliceOpts struct { + LeftBound interface{} `gorethink:"left_bound,omitempty"` + RightBound interface{} `gorethink:"right_bound,omitempty"` +} + +func (o *SliceOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Trim the sequence to within the bounds provided. +func (t Term) Slice(args ...interface{}) Term { + var opts = map[string]interface{}{} + + // Look for options map + if len(args) > 0 { + if possibleOpts, ok := args[len(args)-1].(SliceOpts); ok { + opts = possibleOpts.toMap() + args = args[:len(args)-1] + } + } + + return constructMethodTerm(t, "Slice", p.Term_SLICE, args, opts) +} + +// AtIndex gets a single field from an object or the nth element from a sequence. +func (t Term) AtIndex(args ...interface{}) Term { + return constructMethodTerm(t, "AtIndex", p.Term_BRACKET, args, map[string]interface{}{}) +} + +// Nth gets the nth element from a sequence. +func (t Term) Nth(args ...interface{}) Term { + return constructMethodTerm(t, "Nth", p.Term_NTH, args, map[string]interface{}{}) +} + +// Get the indexes of an element in a sequence. If the argument is a predicate, +// get the indexes of all elements matching it. +func (t Term) IndexesOf(args ...interface{}) Term { + return constructMethodTerm(t, "IndexesOf", p.Term_INDEXES_OF, funcWrapArgs(args), map[string]interface{}{}) +} + +// Test if a sequence is empty. +func (t Term) IsEmpty(args ...interface{}) Term { + return constructMethodTerm(t, "IsEmpty", p.Term_IS_EMPTY, args, map[string]interface{}{}) +} + +// Concatenate two sequences. +func (t Term) Union(args ...interface{}) Term { + return constructMethodTerm(t, "Union", p.Term_UNION, args, map[string]interface{}{}) +} + +// Select a given number of elements from a sequence with uniform random +// distribution. Selection is done without replacement. +func (t Term) Sample(args ...interface{}) Term { + return constructMethodTerm(t, "Sample", p.Term_SAMPLE, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go new file mode 100644 index 0000000..e138e6e --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_transformation_test.go @@ -0,0 +1,426 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestTransformationMapImplicit(c *test.C) { + query := Expr(arr).Map(Row.Add(1)) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{2, 3, 4, 5, 6, 7, 8, 9, 10}) +} + +func (s *RethinkSuite) TestTransformationMapFunc(c *test.C) { + query := Expr(arr).Map(func(row Term) Term { + return row.Add(1) + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{2, 3, 4, 5, 6, 7, 8, 9, 10}) +} + +func (s *RethinkSuite) TestTransformationWithFields(c *test.C) { + query := Expr(objList).WithFields("id", "num").OrderBy("id") + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1}, + map[string]interface{}{"num": 5, "id": 2}, + map[string]interface{}{"num": 10, "id": 3}, + map[string]interface{}{"num": 0, "id": 4}, + map[string]interface{}{"num": 100, "id": 5}, + map[string]interface{}{"num": 15, "id": 6}, + map[string]interface{}{"num": 0, "id": 7}, + map[string]interface{}{"num": 50, "id": 8}, + map[string]interface{}{"num": 25, "id": 9}, + }) +} + +func (s *RethinkSuite) TestTransformationConcatMap(c *test.C) { + query := Expr(objList).ConcatMap(func(row Term) Term { + return Expr([]interface{}{row.Field("num")}) + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{0, 5, 10, 0, 100, 15, 0, 50, 25}) +} + +func (s *RethinkSuite) TestTransformationVariadicMap(c *test.C) { + query := Range(5).Map(Range(5), func(a, b Term) interface{} { + return []interface{}{a, b} + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, [][]int{ + {0, 0}, + {1, 1}, + {2, 2}, + {3, 3}, + {4, 4}, + }) +} + +func (s *RethinkSuite) TestTransformationVariadicRootMap(c *test.C) { + query := Map(Range(5), Range(5), func(a, b Term) interface{} { + return []interface{}{a, b} + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, [][]int{ + {0, 0}, + {1, 1}, + {2, 2}, + {3, 3}, + {4, 4}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByDesc(c *test.C) { + query := Expr(noDupNumObjList).OrderBy(Desc("num")) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByAsc(c *test.C) { + query := Expr(noDupNumObjList).OrderBy(Asc("num")) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByIndex(c *test.C) { + Db("test").TableCreate("OrderByIndex").Exec(sess) + Db("test").Table("test").IndexDrop("OrderByIndex").Exec(sess) + + // Test database creation + Db("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(sess) + Db("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(sess) + + query := Db("test").Table("OrderByIndex").OrderBy(OrderByOpts{ + Index: "test", + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByIndexAsc(c *test.C) { + Db("test").TableCreate("OrderByIndex").Exec(sess) + Db("test").Table("test").IndexDrop("OrderByIndex").Exec(sess) + + // Test database creation + Db("test").Table("OrderByIndex").IndexCreateFunc("test", Row.Field("num")).Exec(sess) + Db("test").Table("OrderByIndex").Insert(noDupNumObjList).Exec(sess) + + query := Db("test").Table("OrderByIndex").OrderBy(OrderByOpts{ + Index: Asc("test"), + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByMultiple(c *test.C) { + query := Expr(objList).OrderBy(Desc("num"), Asc("id")) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 0, "id": 4, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 0, "id": 7, "g2": 2, "g1": 1}, + }) +} + +func (s *RethinkSuite) TestTransformationOrderByFunc(c *test.C) { + query := Expr(objList).OrderBy(func(row Term) Term { + return row.Field("num").Add(row.Field("id")) + }) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{ + map[string]interface{}{"num": 0, "id": 1, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 0, "id": 4, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 5, "id": 2, "g2": 2, "g1": 2}, + map[string]interface{}{"num": 0, "id": 7, "g2": 2, "g1": 1}, + map[string]interface{}{"num": 10, "id": 3, "g2": 2, "g1": 3}, + map[string]interface{}{"num": 15, "id": 6, "g2": 1, "g1": 1}, + map[string]interface{}{"num": 25, "id": 9, "g2": 3, "g1": 2}, + map[string]interface{}{"num": 50, "id": 8, "g2": 2, "g1": 4}, + map[string]interface{}{"num": 100, "id": 5, "g2": 3, "g1": 2}, + }) +} + +func (s *RethinkSuite) TestTransformationSkip(c *test.C) { + query := Expr(arr).Skip(7) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{8, 9}) +} + +func (s *RethinkSuite) TestTransformationLimit(c *test.C) { + query := Expr(arr).Limit(2) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2}) +} + +func (s *RethinkSuite) TestTransformationSlice(c *test.C) { + query := Expr(arr).Slice(4) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{5, 6, 7, 8, 9}) +} + +func (s *RethinkSuite) TestTransformationSliceRight(c *test.C) { + query := Expr(arr).Slice(5, 6) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{6}) +} + +func (s *RethinkSuite) TestTransformationSliceOpts(c *test.C) { + query := Expr(arr).Slice(4, SliceOpts{LeftBound: "open"}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{6, 7, 8, 9}) +} + +func (s *RethinkSuite) TestTransformationSliceRightOpts(c *test.C) { + query := Expr(arr).Slice(5, 6, SliceOpts{RightBound: "closed"}) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{6, 7}) +} + +func (s *RethinkSuite) TestTransformationNth(c *test.C) { + query := Expr(arr).Nth(2) + + var response interface{} + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, 3) +} + +func (s *RethinkSuite) TestTransformationAtIndexNth(c *test.C) { + query := Expr([]interface{}{1}).AtIndex(Expr(0)) + + var response interface{} + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTransformationAtIndexField(c *test.C) { + query := Expr(map[string]interface{}{"foo": 1}).AtIndex(Expr("foo")) + + var response interface{} + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, 1) +} + +func (s *RethinkSuite) TestTransformationAtIndexArrayField(c *test.C) { + query := Expr([]interface{}{1}).AtIndex(Expr("foo")) + + _, err := query.Run(sess) + c.Assert(err, test.NotNil) +} + +func (s *RethinkSuite) TestTransformationIndexesOf(c *test.C) { + query := Expr(arr).IndexesOf(2) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1}) +} + +func (s *RethinkSuite) TestTransformationIsEmpty(c *test.C) { + query := Expr([]interface{}{}).IsEmpty() + + var response bool + r, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = r.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, true) +} + +func (s *RethinkSuite) TestTransformationUnion(c *test.C) { + query := Expr(arr).Union(arr) + + var response []interface{} + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.All(&response) + + c.Assert(err, test.IsNil) + c.Assert(response, jsonEquals, []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go new file mode 100644 index 0000000..149b23d --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write.go @@ -0,0 +1,111 @@ +package gorethink + +import ( + p "github.com/dancannon/gorethink/ql2" +) + +type InsertOpts struct { + Durability interface{} `gorethink:"durability,omitempty"` + ReturnChanges interface{} `gorethink:"return_changes,omitempty"` + CacheSize interface{} `gorethink:"cache_size,omitempty"` + Conflict interface{} `gorethink:"conflict,omitempty"` +} + +func (o *InsertOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Insert JSON documents into a table. Accepts a single JSON document or an array +// of documents. You may also pass the optional argument durability with value +// 'hard' or 'soft', to override the table or query's default durability setting, +// or the optional argument return_changes, which will return the value of the row +// you're inserting when set to true. +// +// table.Insert(map[string]interface{}{"name": "Joe", "email": "joe@example.com"}).RunWrite(sess) +// table.Insert([]interface{}{map[string]interface{}{"name": "Joe"}, map[string]interface{}{"name": "Paul"}}).RunWrite(sess) +func (t Term) Insert(arg interface{}, optArgs ...InsertOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Insert", p.Term_INSERT, []interface{}{Expr(arg)}, opts) +} + +type UpdateOpts struct { + Durability interface{} `gorethink:"durability,omitempty"` + ReturnChanges interface{} `gorethink:"return_changes,omitempty"` + NotAtomic interface{} `gorethink:"non_atomic,omitempty"` +} + +func (o *UpdateOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Update JSON documents in a table. Accepts a JSON document, a RQL expression, +// or a combination of the two. The optional argument durability with value +// 'hard' or 'soft' will override the table or query's default durability setting. +// The optional argument return_changes will return the old and new values of the +// row you're modifying when set to true (only valid for single-row updates). +// The optional argument non_atomic lets you permit non-atomic updates. +func (t Term) Update(arg interface{}, optArgs ...UpdateOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Update", p.Term_UPDATE, []interface{}{funcWrap(arg)}, opts) +} + +type ReplaceOpts struct { + Durability interface{} `gorethink:"durability,omitempty"` + ReturnChanges interface{} `gorethink:"return_changes,omitempty"` + NotAtomic interface{} `gorethink:"non_atomic,omitempty"` +} + +func (o *ReplaceOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Replace documents in a table. Accepts a JSON document or a RQL expression, +// and replaces the original document with the new one. The new document must +// have the same primary key as the original document. The optional argument +// durability with value 'hard' or 'soft' will override the table or query's +// default durability setting. The optional argument return_changes will return +// the old and new values of the row you're modifying when set to true (only +// valid for single-row replacements). The optional argument non_atomic lets +// you permit non-atomic updates. +func (t Term) Replace(arg interface{}, optArgs ...ReplaceOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Replace", p.Term_REPLACE, []interface{}{funcWrap(arg)}, opts) +} + +type DeleteOpts struct { + Durability interface{} `gorethink:"durability,omitempty"` + ReturnChanges interface{} `gorethink:"return_changes,omitempty"` +} + +func (o *DeleteOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Delete one or more documents from a table. The optional argument return_changes +// will return the old value of the row you're deleting when set to true (only +// valid for single-row deletes). The optional argument durability with value +// 'hard' or 'soft' will override the table or query's default durability setting. +func (t Term) Delete(optArgs ...DeleteOpts) Term { + opts := map[string]interface{}{} + if len(optArgs) >= 1 { + opts = optArgs[0].toMap() + } + return constructMethodTerm(t, "Delete", p.Term_DELETE, []interface{}{}, opts) +} + +// Sync ensures that writes on a given table are written to permanent storage. +// Queries that specify soft durability (Durability: "soft") do not give such +// guarantees, so sync can be used to ensure the state of these queries. A call +// to sync does not return until all previous writes to the table are persisted. +func (t Term) Sync(args ...interface{}) Term { + return constructMethodTerm(t, "Sync", p.Term_SYNC, args, map[string]interface{}{}) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go new file mode 100644 index 0000000..50df7c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_write_test.go @@ -0,0 +1,99 @@ +package gorethink + +import ( + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestWriteInsert(c *test.C) { + query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(sess) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TestWriteInsertChanges(c *test.C) { + query := Db("test").Table("test").Insert([]interface{}{ + map[string]interface{}{"num": 1}, + map[string]interface{}{"num": 2}, + }, InsertOpts{ReturnChanges: true}) + res, err := query.RunWrite(sess) + c.Assert(err, test.IsNil) + c.Assert(res.Inserted, test.Equals, 2) + c.Assert(len(res.Changes), test.Equals, 2) +} + +func (s *RethinkSuite) TestWriteInsertStruct(c *test.C) { + var response map[string]interface{} + o := object{ + Name: "map[string]interface{}ect 3", + Attrs: []attr{ + attr{ + Name: "Attr 2", + Value: "Value", + }, + }, + } + + query := Db("test").Table("test").Insert(o) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response["inserted"], test.Equals, float64(1)) +} + +func (s *RethinkSuite) TestWriteInsertStructPointer(c *test.C) { + var response map[string]interface{} + o := object{ + Name: "map[string]interface{}ect 3", + Attrs: []attr{ + attr{ + Name: "Attr 2", + Value: "Value", + }, + }, + } + + query := Db("test").Table("test").Insert(&o) + res, err := query.Run(sess) + c.Assert(err, test.IsNil) + + err = res.One(&response) + + c.Assert(err, test.IsNil) + c.Assert(response["inserted"], test.Equals, float64(1)) +} + +func (s *RethinkSuite) TestWriteUpdate(c *test.C) { + query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(sess) + c.Assert(err, test.IsNil) + + // Update the first row in the table + query = Db("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) + _, err = query.Run(sess) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TestWriteReplace(c *test.C) { + query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(sess) + c.Assert(err, test.IsNil) + + // Replace the first row in the table + query = Db("test").Table("test").Sample(1).Update(map[string]interface{}{"num": 2}) + _, err = query.Run(sess) + c.Assert(err, test.IsNil) +} + +func (s *RethinkSuite) TestWriteDelete(c *test.C) { + query := Db("test").Table("test").Insert(map[string]interface{}{"num": 1}) + _, err := query.Run(sess) + c.Assert(err, test.IsNil) + + // Delete the first row in the table + query = Db("test").Table("test").Sample(1).Delete() + _, err = query.Run(sess) + c.Assert(err, test.IsNil) +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go new file mode 100644 index 0000000..86d9711 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/session.go @@ -0,0 +1,140 @@ +package gorethink + +import ( + "sync" + "time" + + p "github.com/dancannon/gorethink/ql2" +) + +type Session struct { + opts ConnectOpts + pool *Pool + + // Response cache, used for batched responses + sync.Mutex + closed bool + token int64 +} + +type ConnectOpts struct { + Address string `gorethink:"address,omitempty"` + Database string `gorethink:"database,omitempty"` + AuthKey string `gorethink:"authkey,omitempty"` + Timeout time.Duration `gorethink:"timeout,omitempty"` + + MaxIdle int `gorethink:"max_idle,omitempty"` + MaxOpen int `gorethink:"max_open,omitempty"` +} + +func (o *ConnectOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Connect creates a new database session. +// +// Supported arguments include Address, Database, Timeout, Authkey. Pool +// options include MaxIdle, MaxOpen. +// +// By default maxIdle and maxOpen are set to 1: passing values greater +// than the default (e.g. MaxIdle: "10", MaxOpen: "20") will provide a +// pool of re-usable connections. +// +// Basic connection example: +// +// var session *r.Session +// session, err := r.Connect(r.ConnectOpts{ +// Address: "localhost:28015", +// Database: "test", +// AuthKey: "14daak1cad13dj", +// }) +func Connect(opts ConnectOpts) (*Session, error) { + // Connect + s := &Session{ + opts: opts, + } + err := s.Reconnect() + if err != nil { + return nil, err + } + + return s, nil +} + +type CloseOpts struct { + NoReplyWait bool `gorethink:"noreplyWait,omitempty"` +} + +func (o *CloseOpts) toMap() map[string]interface{} { + return optArgsToMap(o) +} + +// Reconnect closes and re-opens a session. +func (s *Session) Reconnect(optArgs ...CloseOpts) error { + var err error + + if err = s.Close(optArgs...); err != nil { + return err + } + + s.pool, err = NewPool(&s.opts) + if err != nil { + return err + } + + // Ping connection to check it is valid + err = s.pool.Ping() + if err != nil { + return err + } + + s.closed = false + + return nil +} + +// Close closes the session +func (s *Session) Close(optArgs ...CloseOpts) error { + if s.closed { + return nil + } + + if len(optArgs) >= 1 { + if optArgs[0].NoReplyWait { + s.NoReplyWait() + } + } + + if s.pool != nil { + s.pool.Close() + } + s.pool = nil + s.closed = true + + return nil +} + +// SetMaxIdleConns sets the maximum number of connections in the idle +// connection pool. +func (s *Session) SetMaxIdleConns(n int) { + s.pool.SetMaxIdleConns(n) +} + +// SetMaxOpenConns sets the maximum number of open connections to the database. +func (s *Session) SetMaxOpenConns(n int) { + s.pool.SetMaxOpenConns(n) +} + +// NoReplyWait ensures that previous queries with the noreply flag have been +// processed by the server. Note that this guarantee only applies to queries +// run on the given connection +func (s *Session) NoReplyWait() error { + return s.pool.Exec(Query{ + Type: p.Query_NOREPLY_WAIT, + }) +} + +// Use changes the default database used +func (s *Session) Use(database string) { + s.opts.Database = database +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go new file mode 100644 index 0000000..1b09041 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/session_test.go @@ -0,0 +1,70 @@ +package gorethink + +import ( + "os" + + test "gopkg.in/check.v1" +) + +func (s *RethinkSuite) TestSessionConnect(c *test.C) { + session, err := Connect(ConnectOpts{ + Address: url, + AuthKey: os.Getenv("RETHINKDB_AUTHKEY"), + }) + c.Assert(err, test.IsNil) + + row, err := Expr("Hello World").Run(session) + c.Assert(err, test.IsNil) + + var response string + err = row.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "Hello World") +} + +func (s *RethinkSuite) TestSessionReconnect(c *test.C) { + session, err := Connect(ConnectOpts{ + Address: url, + AuthKey: os.Getenv("RETHINKDB_AUTHKEY"), + }) + c.Assert(err, test.IsNil) + + row, err := Expr("Hello World").Run(session) + c.Assert(err, test.IsNil) + + var response string + err = row.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "Hello World") + + err = session.Reconnect() + c.Assert(err, test.IsNil) + + row, err = Expr("Hello World 2").Run(session) + c.Assert(err, test.IsNil) + + err = row.One(&response) + c.Assert(err, test.IsNil) + c.Assert(response, test.Equals, "Hello World 2") +} + +func (s *RethinkSuite) TestSessionConnectError(c *test.C) { + var err error + _, err = Connect(ConnectOpts{ + Address: "nonexistanturl", + }) + c.Assert(err, test.NotNil) +} + +func (s *RethinkSuite) TestSessionConnectDatabase(c *test.C) { + session, err := Connect(ConnectOpts{ + Address: url, + AuthKey: os.Getenv("RETHINKDB_AUTHKEY"), + Database: "test2", + }) + c.Assert(err, test.IsNil) + + _, err = Table("test2").Run(session) + c.Assert(err, test.NotNil) + c.Assert(err.Error(), test.Equals, "gorethink: Database `test2` does not exist. in: \nr.Table(\"test2\")") +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go new file mode 100644 index 0000000..93c220d --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go @@ -0,0 +1,214 @@ +package types + +import "fmt" + +type Geometry struct { + Type string + Point Point + Line Line + Lines Lines +} + +func (g Geometry) MarshalRQL() (interface{}, error) { + switch g.Type { + case "Point": + return g.Point.MarshalRQL() + case "LineString": + return g.Line.MarshalRQL() + case "Polygon": + return g.Lines.MarshalRQL() + default: + return nil, fmt.Errorf("pseudo-type GEOMETRY object field 'type' %s is not valid", g.Type) + } +} + +func (g *Geometry) UnmarshalRQL(data interface{}) error { + m, ok := data.(map[string]interface{}) + if !ok { + return fmt.Errorf("pseudo-type GEOMETRY object is not valid") + } + + typ, ok := m["type"] + if !ok { + return fmt.Errorf("pseudo-type GEOMETRY object is not valid, expects 'type' field") + } + coords, ok := m["coordinates"] + if !ok { + return fmt.Errorf("pseudo-type GEOMETRY object is not valid, expects 'coordinates' field") + } + + var err error + switch typ { + case "Point": + g.Type = "Point" + g.Point, err = UnmarshalPoint(coords) + case "LineString": + g.Type = "LineString" + g.Line, err = UnmarshalLineString(coords) + case "Polygon": + g.Type = "Polygon" + g.Lines, err = UnmarshalPolygon(coords) + default: + return fmt.Errorf("pseudo-type GEOMETRY object has invalid type") + } + + if err != nil { + return err + } + + return nil +} + +type Point struct { + Lon float64 + Lat float64 +} +type Line []Point +type Lines []Line + +func (p Point) Coords() interface{} { + return []interface{}{p.Lon, p.Lat} +} + +func (p Point) MarshalRQL() (interface{}, error) { + return map[string]interface{}{ + "$reql_type$": "GEOMETRY", + "coordinates": p.Coords(), + "type": "Point", + }, nil +} + +func (p *Point) UnmarshalRQL(data interface{}) error { + g := &Geometry{} + err := g.UnmarshalRQL(data) + if err != nil { + return err + } + if g.Type != "Point" { + return fmt.Errorf("pseudo-type GEOMETRY object has type %s, expected type %s", g.Type, "Point") + } + + p.Lat = g.Point.Lat + p.Lon = g.Point.Lon + + return nil +} + +func (l Line) Coords() interface{} { + coords := make([]interface{}, len(l)) + for i, point := range l { + coords[i] = point.Coords() + } + return coords +} + +func (l Line) MarshalRQL() (interface{}, error) { + return map[string]interface{}{ + "$reql_type$": "GEOMETRY", + "coordinates": l.Coords(), + "type": "LineString", + }, nil +} + +func (l *Line) UnmarshalRQL(data interface{}) error { + g := &Geometry{} + err := g.UnmarshalRQL(data) + if err != nil { + return err + } + if g.Type != "LineString" { + return fmt.Errorf("pseudo-type GEOMETRY object has type %s, expected type %s", g.Type, "LineString") + } + + *l = g.Line + + return nil +} + +func (l Lines) Coords() interface{} { + coords := make([]interface{}, len(l)) + for i, line := range l { + coords[i] = line.Coords() + } + return coords +} + +func (l Lines) MarshalRQL() (interface{}, error) { + return map[string]interface{}{ + "$reql_type$": "GEOMETRY", + "coordinates": l.Coords(), + "type": "Polygon", + }, nil +} + +func (l *Lines) UnmarshalRQL(data interface{}) error { + g := &Geometry{} + err := g.UnmarshalRQL(data) + if err != nil { + return err + } + if g.Type != "Polygon" { + return fmt.Errorf("pseudo-type GEOMETRY object has type %s, expected type %s", g.Type, "Polygon") + } + + *l = g.Lines + + return nil +} + +func UnmarshalPoint(v interface{}) (Point, error) { + coords, ok := v.([]interface{}) + if !ok { + return Point{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + if len(coords) != 2 { + return Point{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + lon, ok := coords[0].(float64) + if !ok { + return Point{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + lat, ok := coords[1].(float64) + if !ok { + return Point{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + + return Point{ + Lon: lon, + Lat: lat, + }, nil +} + +func UnmarshalLineString(v interface{}) (Line, error) { + points, ok := v.([]interface{}) + if !ok { + return Line{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + + var err error + line := make(Line, len(points)) + for i, coords := range points { + line[i], err = UnmarshalPoint(coords) + if err != nil { + return Line{}, err + } + } + return line, nil +} + +func UnmarshalPolygon(v interface{}) (Lines, error) { + lines, ok := v.([]interface{}) + if !ok { + return Lines{}, fmt.Errorf("pseudo-type GEOMETRY object field 'coordinates' is not valid") + } + + var err error + polygon := make(Lines, len(lines)) + for i, line := range lines { + polygon[i], err = UnmarshalLineString(line) + if err != nil { + return Lines{}, err + } + } + return polygon, nil +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go new file mode 100644 index 0000000..52367d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/utils.go @@ -0,0 +1,246 @@ +package gorethink + +import ( + "reflect" + "strings" + "sync/atomic" + "time" + + "github.com/dancannon/gorethink/encoding" + + "code.google.com/p/goprotobuf/proto" + p "github.com/dancannon/gorethink/ql2" +) + +// Helper functions for constructing terms + +// constructRootTerm is an alias for creating a new term. +func constructRootTerm(name string, termType p.Term_TermType, args []interface{}, optArgs map[string]interface{}) Term { + return Term{ + name: name, + rootTerm: true, + termType: termType, + args: convertTermList(args), + optArgs: convertTermObj(optArgs), + } +} + +// constructMethodTerm is an alias for creating a new term. Unlike constructRootTerm +// this function adds the previous expression in the tree to the argument list to +// create a method term. +func constructMethodTerm(prevVal Term, name string, termType p.Term_TermType, args []interface{}, optArgs map[string]interface{}) Term { + args = append([]interface{}{prevVal}, args...) + + return Term{ + name: name, + rootTerm: false, + termType: termType, + args: convertTermList(args), + optArgs: convertTermObj(optArgs), + } +} + +// Helper functions for creating internal RQL types + +// makeArray takes a slice of terms and produces a single MAKE_ARRAY term +func makeArray(args termsList) Term { + return Term{ + name: "[...]", + termType: p.Term_MAKE_ARRAY, + args: args, + } +} + +// makeObject takes a map of terms and produces a single MAKE_OBJECT term +func makeObject(args termsObj) Term { + return Term{ + name: "{...}", + termType: p.Term_MAKE_OBJ, + optArgs: args, + } +} + +var nextVarId int64 + +func makeFunc(f interface{}) Term { + value := reflect.ValueOf(f) + valueType := value.Type() + + var argNums = make([]interface{}, valueType.NumIn()) + var args = make([]reflect.Value, valueType.NumIn()) + for i := 0; i < valueType.NumIn(); i++ { + // Get a slice of the VARs to use as the function arguments + args[i] = reflect.ValueOf(constructRootTerm("var", p.Term_VAR, []interface{}{nextVarId}, map[string]interface{}{})) + argNums[i] = nextVarId + atomic.AddInt64(&nextVarId, 1) + + // make sure all input arguments are of type Term + if valueType.In(i).String() != "gorethink.Term" { + panic("Function argument is not of type Term") + } + } + + if valueType.NumOut() != 1 { + panic("Function does not have a single return value") + } + + body := value.Call(args)[0].Interface() + argsArr := makeArray(convertTermList(argNums)) + + return constructRootTerm("func", p.Term_FUNC, []interface{}{argsArr, body}, map[string]interface{}{}) +} + +func funcWrap(value interface{}) Term { + val := Expr(value) + + if implVarScan(val) && val.termType != p.Term_ARGS { + return makeFunc(func(x Term) Term { + return val + }) + } + return val +} + +func funcWrapArgs(args []interface{}) []interface{} { + for i, arg := range args { + args[i] = funcWrap(arg) + } + + return args +} + +// implVarScan recursivly checks a value to see if it contains an +// IMPLICIT_VAR term. If it does it returns true +func implVarScan(value Term) bool { + if value.termType == p.Term_IMPLICIT_VAR { + return true + } + for _, v := range value.args { + if implVarScan(v) { + return true + } + } + + for _, v := range value.optArgs { + if implVarScan(v) { + return true + } + } + + return false +} + +// Convert an opt args struct to a map. +func optArgsToMap(optArgs OptArgs) map[string]interface{} { + data, err := encode(optArgs) + + if err == nil && data != nil { + if m, ok := data.(map[string]interface{}); ok { + return m + } + } + + return map[string]interface{}{} +} + +// Convert a list into a slice of terms +func convertTermList(l []interface{}) termsList { + terms := make(termsList, len(l)) + for i, v := range l { + terms[i] = Expr(v) + } + + return terms +} + +// Convert a map into a map of terms +func convertTermObj(o map[string]interface{}) termsObj { + terms := make(termsObj, len(o)) + for k, v := range o { + terms[k] = Expr(v) + } + + return terms +} + +func mergeArgs(args ...interface{}) []interface{} { + newArgs := []interface{}{} + + for _, arg := range args { + switch v := arg.(type) { + case []interface{}: + newArgs = append(newArgs, v...) + default: + newArgs = append(newArgs, v) + } + } + + return newArgs +} + +// Helper functions for debugging + +func allArgsToStringSlice(args termsList, optArgs termsObj) []string { + allArgs := make([]string, len(args)+len(optArgs)) + i := 0 + + for _, v := range args { + allArgs[i] = v.String() + i++ + } + for k, v := range optArgs { + allArgs[i] = k + "=" + v.String() + i++ + } + + return allArgs +} + +func argsToStringSlice(args termsList) []string { + allArgs := make([]string, len(args)) + + for i, v := range args { + allArgs[i] = v.String() + } + + return allArgs +} + +func optArgsToStringSlice(optArgs termsObj) []string { + allArgs := make([]string, len(optArgs)) + i := 0 + + for k, v := range optArgs { + allArgs[i] = k + "=" + v.String() + i++ + } + + return allArgs +} + +func prefixLines(s string, prefix string) (result string) { + for _, line := range strings.Split(s, "\n") { + result += prefix + line + "\n" + } + return +} + +func protobufToString(p proto.Message, indentLevel int) string { + return prefixLines(proto.MarshalTextString(p), strings.Repeat(" ", indentLevel)) +} + +var timeType = reflect.TypeOf(time.Time{}) +var termType = reflect.TypeOf(Term{}) + +func encode(data interface{}) (interface{}, error) { + if _, ok := data.(Term); ok { + return data, nil + } + + v, err := encoding.Encode(data) + if err != nil { + return nil, err + } + + return v, nil +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml b/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml new file mode 100644 index 0000000..f03209c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml @@ -0,0 +1,37 @@ +box: wercker/golang +# Services +services: + - dancannon/rethinkdb@0.4.0 +# Build definition +build: + # The steps that will be executed on build + steps: + # Sets the go workspace and places you package + # at the right place in the workspace tree + - pjvds/setup-go-workspace + + - script: + name: Populate cache + code: |- + if test -d "$WERCKER_CACHE_DIR/go-pkg-cache"; then rsync -avzv --exclude "$WERCKER_SOURCE_DIR" "$WERCKER_CACHE_DIR/go-pkg-cache/" "$GOPATH/" ; fi + # Gets the dependencies + - script: + name: get dependencies + code: | + cd $WERCKER_SOURCE_DIR + go version + go get ./... + # Build the project + - script: + name: build + code: | + go build ./... + # Test the project + - script: + name: Test + code: |- + go get -u gopkg.in/check.v1 + - script: + name: Store cache + code: |- + rsync -avzv --exclude "$WERCKER_SOURCE_DIR" "$GOPATH/" "$WERCKER_CACHE_DIR/go-pkg-cache/" diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml b/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml new file mode 100644 index 0000000..7c38172 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml @@ -0,0 +1,7 @@ +language: go + +go: + - 1.0 + - 1.1 + - 1.2 + - tip diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE b/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE new file mode 100644 index 0000000..c52f931 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2011 Dmitry Chestnykh + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/README.md b/Godeps/_workspace/src/github.com/dchest/uniuri/README.md new file mode 100644 index 0000000..1be9671 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/README.md @@ -0,0 +1,83 @@ +Package uniuri +===================== + +[![Build Status](https://travis-ci.org/dchest/uniuri.png)](https://travis-ci.org/dchest/uniuri) + +```go +import "github.com/dchest/uniuri" +``` + +Package uniuri generates random strings good for use in URIs to identify +unique objects. + +Example usage: + +```go +s := uniuri.New() // s is now "apHCJBl7L1OmC57n" +``` + +A standard string created by New() is 16 bytes in length and consists of +Latin upper and lowercase letters, and numbers (from the set of 62 allowed +characters), which means that it has ~95 bits of entropy. To get more +entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving +~119 bits of entropy, or any other desired length. + +Functions read from crypto/rand random source, and panic if they fail to +read from it. + + +Constants +--------- + +```go +const ( + // Standard length of uniuri string to achive ~95 bits of entropy. + StdLen = 16 + // Length of uniurl string to achive ~119 bits of entropy, closest + // to what can be losslessly converted to UUIDv4 (122 bits). + UUIDLen = 20 +) +``` + + + +Variables +--------- + +```go +var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +``` + +Standard characters allowed in uniuri string. + + +Functions +--------- + +### func New + +```go +func New() string +``` + +New returns a new random string of the standard length, consisting of +standard characters. + +### func NewLen + +```go +func NewLen(length int) string +``` + +NewLen returns a new random string of the provided length, consisting of +standard characters. + +### func NewLenChars + +```go +func NewLenChars(length int, chars []byte) string +``` + +NewLenChars returns a new random string of the provided length, consisting +of the provided byte slice of allowed characters (maximum 256). + diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go new file mode 100644 index 0000000..c2b8ae1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go @@ -0,0 +1,75 @@ +// Package uniuri generates random strings good for use in URIs to identify +// unique objects. +// +// Example usage: +// +// s := uniuri.New() // s is now "apHCJBl7L1OmC57n" +// +// A standard string created by New() is 16 bytes in length and consists of +// Latin upper and lowercase letters, and numbers (from the set of 62 allowed +// characters), which means that it has ~95 bits of entropy. To get more +// entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving +// ~119 bits of entropy, or any other desired length. +// +// Functions read from crypto/rand random source, and panic if they fail to +// read from it. +package uniuri + +import ( + "crypto/rand" + "io" +) + +const ( + // Standard length of uniuri string to achive ~95 bits of entropy. + StdLen = 16 + // Length of uniurl string to achive ~119 bits of entropy, closest + // to what can be losslessly converted to UUIDv4 (122 bits). + UUIDLen = 20 +) + +// Standard characters allowed in uniuri string. +var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") + +// New returns a new random string of the standard length, consisting of +// standard characters. +func New() string { + return NewLenChars(StdLen, StdChars) +} + +// NewLen returns a new random string of the provided length, consisting of +// standard characters. +func NewLen(length int) string { + return NewLenChars(length, StdChars) +} + +// NewLenChars returns a new random string of the provided length, consisting +// of the provided byte slice of allowed characters (maximum 256). +func NewLenChars(length int, chars []byte) string { + b := make([]byte, length) + r := make([]byte, length+(length/4)) // storage for random bytes. + clen := len(chars) + if clen > 256 { + panic("uniuri: maximum length of charset for NewLenChars is 256") + } + maxrb := 256 - (256 % clen) + i := 0 + for { + if _, err := io.ReadFull(rand.Reader, r); err != nil { + panic("error reading from random source: " + err.Error()) + } + for _, rb := range r { + c := int(rb) + if c > maxrb { + // Skip this number to avoid modulo bias. + continue + } + b[i] = chars[c%clen] + i++ + if i == length { + return string(b) + } + } + } + panic("unreachable") +} diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go new file mode 100644 index 0000000..17b3ba1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go @@ -0,0 +1,69 @@ +package uniuri + +import "testing" + +func validateChars(t *testing.T, u string, chars []byte) { + for _, c := range u { + var present bool + for _, a := range StdChars { + if rune(a) == c { + present = true + } + } + if !present { + t.Fatalf("chars not allowed in %q", u) + } + } +} + +func TestNew(t *testing.T) { + u := New() + // Check length + if len(u) != StdLen { + t.Fatalf("wrong length: expected %d, got %d", StdLen, len(u)) + } + // Check that only allowed characters are present + validateChars(t, u, StdChars) + + // Generate 1000 uniuris and check that they are unique + uris := make([]string, 1000) + for i, _ := range uris { + uris[i] = New() + } + for i, u := range uris { + for j, u2 := range uris { + if i != j && u == u2 { + t.Fatalf("not unique: %d:%q and %d:%q", i, j, u, u2) + } + } + } +} + +func TestNewLenChars(t *testing.T) { + length := 10 + chars := []byte("01234567") + u := NewLenChars(length, chars) + + // Check length + if len(u) != length { + t.Fatalf("wrong length: expected %d, got %d", StdLen, len(u)) + } + // Check that only allowed characters are present + validateChars(t, u, chars) + + // Check that two generated strings are different + u2 := NewLenChars(length, chars) + if u == u2 { + t.Fatalf("not unique: %q and %q", u, u2) + } +} + +func TestNewLenCharsMaxLength(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("didn't panic") + } + }() + chars := make([]byte, 257) + NewLenChars(32, chars) +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go new file mode 100644 index 0000000..ce78eff --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/commandinfo.go @@ -0,0 +1,45 @@ +// Copyright 2014 Gary Burd +// +// 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. + +package internal + +import ( + "strings" +) + +const ( + WatchState = 1 << iota + MultiState + SubscribeState + MonitorState +) + +type CommandInfo struct { + Set, Clear int +} + +var commandInfos = map[string]CommandInfo{ + "WATCH": {Set: WatchState}, + "UNWATCH": {Clear: WatchState}, + "MULTI": {Set: MultiState}, + "EXEC": {Clear: WatchState | MultiState}, + "DISCARD": {Clear: WatchState | MultiState}, + "PSUBSCRIBE": {Set: SubscribeState}, + "SUBSCRIBE": {Set: SubscribeState}, + "MONITOR": {Set: MonitorState}, +} + +func LookupCommandInfo(commandName string) CommandInfo { + return commandInfos[strings.ToUpper(commandName)] +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go new file mode 100644 index 0000000..5f955c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/internal/redistest/testdb.go @@ -0,0 +1,65 @@ +// Copyright 2014 Gary Burd +// +// 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. + +// Package redistest contains utilities for writing Redigo tests. +package redistest + +import ( + "errors" + "time" + + "github.com/garyburd/redigo/redis" +) + +type testConn struct { + redis.Conn +} + +func (t testConn) Close() error { + _, err := t.Conn.Do("SELECT", "9") + if err != nil { + return nil + } + _, err = t.Conn.Do("FLUSHDB") + if err != nil { + return err + } + return t.Conn.Close() +} + +// Dial dials the local Redis server and selects database 9. To prevent +// stomping on real data, DialTestDB fails if database 9 contains data. The +// returned connection flushes database 9 on close. +func Dial() (redis.Conn, error) { + c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) + if err != nil { + return nil, err + } + + _, err = c.Do("SELECT", "9") + if err != nil { + return nil, err + } + + n, err := redis.Int(c.Do("DBSIZE")) + if err != nil { + return nil, err + } + + if n != 0 { + return nil, errors.New("database #9 is not empty, test can not continue") + } + + return testConn{c}, nil +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go new file mode 100644 index 0000000..ac0e971 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn.go @@ -0,0 +1,455 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "net" + "strconv" + "sync" + "time" +) + +// conn is the low-level implementation of Conn +type conn struct { + + // Shared + mu sync.Mutex + pending int + err error + conn net.Conn + + // Read + readTimeout time.Duration + br *bufio.Reader + + // Write + writeTimeout time.Duration + bw *bufio.Writer + + // Scratch space for formatting argument length. + // '*' or '$', length, "\r\n" + lenScratch [32]byte + + // Scratch space for formatting integers and floats. + numScratch [40]byte +} + +// Dial connects to the Redis server at the given network and address. +func Dial(network, address string) (Conn, error) { + dialer := xDialer{} + return dialer.Dial(network, address) +} + +// DialTimeout acts like Dial but takes timeouts for establishing the +// connection to the server, writing a command and reading a reply. +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { + netDialer := net.Dialer{Timeout: connectTimeout} + dialer := xDialer{ + NetDial: netDialer.Dial, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + } + return dialer.Dial(network, address) +} + +// A Dialer specifies options for connecting to a Redis server. +type xDialer struct { + // NetDial specifies the dial function for creating TCP connections. If + // NetDial is nil, then net.Dial is used. + NetDial func(network, addr string) (net.Conn, error) + + // ReadTimeout specifies the timeout for reading a single command + // reply. If ReadTimeout is zero, then no timeout is used. + ReadTimeout time.Duration + + // WriteTimeout specifies the timeout for writing a single command. If + // WriteTimeout is zero, then no timeout is used. + WriteTimeout time.Duration +} + +// Dial connects to the Redis server at address on the named network. +func (d *xDialer) Dial(network, address string) (Conn, error) { + dial := d.NetDial + if dial == nil { + dial = net.Dial + } + netConn, err := dial(network, address) + if err != nil { + return nil, err + } + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: d.ReadTimeout, + writeTimeout: d.WriteTimeout, + }, nil +} + +// NewConn returns a new Redigo connection for the given net connection. +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { + return &conn{ + conn: netConn, + bw: bufio.NewWriter(netConn), + br: bufio.NewReader(netConn), + readTimeout: readTimeout, + writeTimeout: writeTimeout, + } +} + +func (c *conn) Close() error { + c.mu.Lock() + err := c.err + if c.err == nil { + c.err = errors.New("redigo: closed") + err = c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) fatal(err error) error { + c.mu.Lock() + if c.err == nil { + c.err = err + // Close connection to force errors on subsequent calls and to unblock + // other reader or writer. + c.conn.Close() + } + c.mu.Unlock() + return err +} + +func (c *conn) Err() error { + c.mu.Lock() + err := c.err + c.mu.Unlock() + return err +} + +func (c *conn) writeLen(prefix byte, n int) error { + c.lenScratch[len(c.lenScratch)-1] = '\n' + c.lenScratch[len(c.lenScratch)-2] = '\r' + i := len(c.lenScratch) - 3 + for { + c.lenScratch[i] = byte('0' + n%10) + i -= 1 + n = n / 10 + if n == 0 { + break + } + } + c.lenScratch[i] = prefix + _, err := c.bw.Write(c.lenScratch[i:]) + return err +} + +func (c *conn) writeString(s string) error { + c.writeLen('$', len(s)) + c.bw.WriteString(s) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeBytes(p []byte) error { + c.writeLen('$', len(p)) + c.bw.Write(p) + _, err := c.bw.WriteString("\r\n") + return err +} + +func (c *conn) writeInt64(n int64) error { + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) +} + +func (c *conn) writeFloat64(n float64) error { + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) +} + +func (c *conn) writeCommand(cmd string, args []interface{}) (err error) { + c.writeLen('*', 1+len(args)) + err = c.writeString(cmd) + for _, arg := range args { + if err != nil { + break + } + switch arg := arg.(type) { + case string: + err = c.writeString(arg) + case []byte: + err = c.writeBytes(arg) + case int: + err = c.writeInt64(int64(arg)) + case int64: + err = c.writeInt64(arg) + case float64: + err = c.writeFloat64(arg) + case bool: + if arg { + err = c.writeString("1") + } else { + err = c.writeString("0") + } + case nil: + err = c.writeString("") + default: + var buf bytes.Buffer + fmt.Fprint(&buf, arg) + err = c.writeBytes(buf.Bytes()) + } + } + return err +} + +type protocolError string + +func (pe protocolError) Error() string { + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) +} + +func (c *conn) readLine() ([]byte, error) { + p, err := c.br.ReadSlice('\n') + if err == bufio.ErrBufferFull { + return nil, protocolError("long response line") + } + if err != nil { + return nil, err + } + i := len(p) - 2 + if i < 0 || p[i] != '\r' { + return nil, protocolError("bad response line terminator") + } + return p[:i], nil +} + +// parseLen parses bulk string and array lengths. +func parseLen(p []byte) (int, error) { + if len(p) == 0 { + return -1, protocolError("malformed length") + } + + if p[0] == '-' && len(p) == 2 && p[1] == '1' { + // handle $-1 and $-1 null replies. + return -1, nil + } + + var n int + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return -1, protocolError("illegal bytes in length") + } + n += int(b - '0') + } + + return n, nil +} + +// parseInt parses an integer reply. +func parseInt(p []byte) (interface{}, error) { + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + + var negate bool + if p[0] == '-' { + negate = true + p = p[1:] + if len(p) == 0 { + return 0, protocolError("malformed integer") + } + } + + var n int64 + for _, b := range p { + n *= 10 + if b < '0' || b > '9' { + return 0, protocolError("illegal bytes in length") + } + n += int64(b - '0') + } + + if negate { + n = -n + } + return n, nil +} + +var ( + okReply interface{} = "OK" + pongReply interface{} = "PONG" +) + +func (c *conn) readReply() (interface{}, error) { + line, err := c.readLine() + if err != nil { + return nil, err + } + if len(line) == 0 { + return nil, protocolError("short response line") + } + switch line[0] { + case '+': + switch { + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': + // Avoid allocation for frequent "+OK" response. + return okReply, nil + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': + // Avoid allocation in PING command benchmarks :) + return pongReply, nil + default: + return string(line[1:]), nil + } + case '-': + return Error(string(line[1:])), nil + case ':': + return parseInt(line[1:]) + case '$': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + p := make([]byte, n) + _, err = io.ReadFull(c.br, p) + if err != nil { + return nil, err + } + if line, err := c.readLine(); err != nil { + return nil, err + } else if len(line) != 0 { + return nil, protocolError("bad bulk string format") + } + return p, nil + case '*': + n, err := parseLen(line[1:]) + if n < 0 || err != nil { + return nil, err + } + r := make([]interface{}, n) + for i := range r { + r[i], err = c.readReply() + if err != nil { + return nil, err + } + } + return r, nil + } + return nil, protocolError("unexpected response line") +} + +func (c *conn) Send(cmd string, args ...interface{}) error { + c.mu.Lock() + c.pending += 1 + c.mu.Unlock() + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.writeCommand(cmd, args); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Flush() error { + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + if err := c.bw.Flush(); err != nil { + return c.fatal(err) + } + return nil +} + +func (c *conn) Receive() (reply interface{}, err error) { + if c.readTimeout != 0 { + c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) + } + if reply, err = c.readReply(); err != nil { + return nil, c.fatal(err) + } + // When using pub/sub, the number of receives can be greater than the + // number of sends. To enable normal use of the connection after + // unsubscribing from all channels, we do not decrement pending to a + // negative value. + // + // The pending field is decremented after the reply is read to handle the + // case where Receive is called before Send. + c.mu.Lock() + if c.pending > 0 { + c.pending -= 1 + } + c.mu.Unlock() + if err, ok := reply.(Error); ok { + return nil, err + } + return +} + +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { + c.mu.Lock() + pending := c.pending + c.pending = 0 + c.mu.Unlock() + + if cmd == "" && pending == 0 { + return nil, nil + } + + if c.writeTimeout != 0 { + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) + } + + if cmd != "" { + c.writeCommand(cmd, args) + } + + if err := c.bw.Flush(); err != nil { + return nil, c.fatal(err) + } + + if c.readTimeout != 0 { + c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) + } + + if cmd == "" { + reply := make([]interface{}, pending) + for i := range reply { + r, e := c.readReply() + if e != nil { + return nil, c.fatal(e) + } + reply[i] = r + } + return reply, nil + } + + var err error + var reply interface{} + for i := 0; i <= pending; i++ { + var e error + if reply, e = c.readReply(); e != nil { + return nil, c.fatal(e) + } + if e, ok := reply.(Error); ok && err == nil { + err = e + } + } + return reply, err +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go new file mode 100644 index 0000000..8003701 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/conn_test.go @@ -0,0 +1,542 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis_test + +import ( + "bufio" + "bytes" + "math" + "net" + "reflect" + "strings" + "testing" + "time" + + "github.com/garyburd/redigo/internal/redistest" + "github.com/garyburd/redigo/redis" +) + +var writeTests = []struct { + args []interface{} + expected string +}{ + { + []interface{}{"SET", "key", "value"}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", + }, + { + []interface{}{"SET", "key", "value"}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", + }, + { + []interface{}{"SET", "key", byte(100)}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", + }, + { + []interface{}{"SET", "key", 100}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", + }, + { + []interface{}{"SET", "key", int64(math.MinInt64)}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n", + }, + { + []interface{}{"SET", "key", float64(1349673917.939762)}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n", + }, + { + []interface{}{"SET", "key", ""}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", + }, + { + []interface{}{"SET", "key", nil}, + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", + }, + { + []interface{}{"ECHO", true, false}, + "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n", + }, +} + +func TestWrite(t *testing.T) { + for _, tt := range writeTests { + var buf bytes.Buffer + rw := bufio.ReadWriter{Writer: bufio.NewWriter(&buf)} + c := redis.NewConnBufio(rw) + err := c.Send(tt.args[0].(string), tt.args[1:]...) + if err != nil { + t.Errorf("Send(%v) returned error %v", tt.args, err) + continue + } + rw.Flush() + actual := buf.String() + if actual != tt.expected { + t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected) + } + } +} + +var errorSentinel = &struct{}{} + +var readTests = []struct { + reply string + expected interface{} +}{ + { + "+OK\r\n", + "OK", + }, + { + "+PONG\r\n", + "PONG", + }, + { + "@OK\r\n", + errorSentinel, + }, + { + "$6\r\nfoobar\r\n", + []byte("foobar"), + }, + { + "$-1\r\n", + nil, + }, + { + ":1\r\n", + int64(1), + }, + { + ":-2\r\n", + int64(-2), + }, + { + "*0\r\n", + []interface{}{}, + }, + { + "*-1\r\n", + nil, + }, + { + "*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n", + []interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")}, + }, + { + "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n", + []interface{}{[]byte("foo"), nil, []byte("bar")}, + }, + + { + // "x" is not a valid length + "$x\r\nfoobar\r\n", + errorSentinel, + }, + { + // -2 is not a valid length + "$-2\r\n", + errorSentinel, + }, + { + // "x" is not a valid integer + ":x\r\n", + errorSentinel, + }, + { + // missing \r\n following value + "$6\r\nfoobar", + errorSentinel, + }, + { + // short value + "$6\r\nxx", + errorSentinel, + }, + { + // long value + "$6\r\nfoobarx\r\n", + errorSentinel, + }, +} + +func TestRead(t *testing.T) { + for _, tt := range readTests { + rw := bufio.ReadWriter{ + Reader: bufio.NewReader(strings.NewReader(tt.reply)), + Writer: bufio.NewWriter(nil), // writer need to support Flush + } + c := redis.NewConnBufio(rw) + actual, err := c.Receive() + if tt.expected == errorSentinel { + if err == nil { + t.Errorf("Receive(%q) did not return expected error", tt.reply) + } + } else { + if err != nil { + t.Errorf("Receive(%q) returned error %v", tt.reply, err) + continue + } + if !reflect.DeepEqual(actual, tt.expected) { + t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected) + } + } + } +} + +var testCommands = []struct { + args []interface{} + expected interface{} +}{ + { + []interface{}{"PING"}, + "PONG", + }, + { + []interface{}{"SET", "foo", "bar"}, + "OK", + }, + { + []interface{}{"GET", "foo"}, + []byte("bar"), + }, + { + []interface{}{"GET", "nokey"}, + nil, + }, + { + []interface{}{"MGET", "nokey", "foo"}, + []interface{}{nil, []byte("bar")}, + }, + { + []interface{}{"INCR", "mycounter"}, + int64(1), + }, + { + []interface{}{"LPUSH", "mylist", "foo"}, + int64(1), + }, + { + []interface{}{"LPUSH", "mylist", "bar"}, + int64(2), + }, + { + []interface{}{"LRANGE", "mylist", 0, -1}, + []interface{}{[]byte("bar"), []byte("foo")}, + }, + { + []interface{}{"MULTI"}, + "OK", + }, + { + []interface{}{"LRANGE", "mylist", 0, -1}, + "QUEUED", + }, + { + []interface{}{"PING"}, + "QUEUED", + }, + { + []interface{}{"EXEC"}, + []interface{}{ + []interface{}{[]byte("bar"), []byte("foo")}, + "PONG", + }, + }, +} + +func TestDoCommands(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + for _, cmd := range testCommands { + actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...) + if err != nil { + t.Errorf("Do(%v) returned error %v", cmd.args, err) + continue + } + if !reflect.DeepEqual(actual, cmd.expected) { + t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected) + } + } +} + +func TestPipelineCommands(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + for _, cmd := range testCommands { + if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { + t.Fatalf("Send(%v) returned error %v", cmd.args, err) + } + } + if err := c.Flush(); err != nil { + t.Errorf("Flush() returned error %v", err) + } + for _, cmd := range testCommands { + actual, err := c.Receive() + if err != nil { + t.Fatalf("Receive(%v) returned error %v", cmd.args, err) + } + if !reflect.DeepEqual(actual, cmd.expected) { + t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) + } + } +} + +func TestBlankCommmand(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + for _, cmd := range testCommands { + if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { + t.Fatalf("Send(%v) returned error %v", cmd.args, err) + } + } + reply, err := redis.Values(c.Do("")) + if err != nil { + t.Fatalf("Do() returned error %v", err) + } + if len(reply) != len(testCommands) { + t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands)) + } + for i, cmd := range testCommands { + actual := reply[i] + if !reflect.DeepEqual(actual, cmd.expected) { + t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) + } + } +} + +func TestRecvBeforeSend(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + done := make(chan struct{}) + go func() { + c.Receive() + close(done) + }() + time.Sleep(time.Millisecond) + c.Send("PING") + c.Flush() + <-done + _, err = c.Do("") + if err != nil { + t.Fatalf("error=%v", err) + } +} + +func TestError(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + c.Do("SET", "key", "val") + _, err = c.Do("HSET", "key", "fld", "val") + if err == nil { + t.Errorf("Expected err for HSET on string key.") + } + if c.Err() != nil { + t.Errorf("Conn has Err()=%v, expect nil", c.Err()) + } + _, err = c.Do("SET", "key", "val") + if err != nil { + t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err) + } +} + +func TestReadDeadline(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("net.Listen returned %v", err) + } + defer l.Close() + + go func() { + for { + c, err := l.Accept() + if err != nil { + return + } + go func() { + time.Sleep(time.Second) + c.Write([]byte("+OK\r\n")) + c.Close() + }() + } + }() + + c1, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) + if err != nil { + t.Fatalf("redis.Dial returned %v", err) + } + defer c1.Close() + + _, err = c1.Do("PING") + if err == nil { + t.Fatalf("c1.Do() returned nil, expect error") + } + if c1.Err() == nil { + t.Fatalf("c1.Err() = nil, expect error") + } + + c2, err := redis.DialTimeout(l.Addr().Network(), l.Addr().String(), 0, time.Millisecond, 0) + if err != nil { + t.Fatalf("redis.Dial returned %v", err) + } + defer c2.Close() + + c2.Send("PING") + c2.Flush() + _, err = c2.Receive() + if err == nil { + t.Fatalf("c2.Receive() returned nil, expect error") + } + if c2.Err() == nil { + t.Fatalf("c2.Err() = nil, expect error") + } +} + +// Connect to local instance of Redis running on the default port. +func ExampleDial(x int) { + c, err := redis.Dial("tcp", ":6379") + if err != nil { + // handle error + } + defer c.Close() +} + +// TextExecError tests handling of errors in a transaction. See +// http://redis.io/topics/transactions for information on how Redis handles +// errors in a transaction. +func TestExecError(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + // Execute commands that fail before EXEC is called. + + c.Do("ZADD", "k0", 0, 0) + c.Send("MULTI") + c.Send("NOTACOMMAND", "k0", 0, 0) + c.Send("ZINCRBY", "k0", 0, 0) + v, err := c.Do("EXEC") + if err == nil { + t.Fatalf("EXEC returned values %v, expected error", v) + } + + // Execute commands that fail after EXEC is called. The first command + // returns an error. + + c.Do("ZADD", "k1", 0, 0) + c.Send("MULTI") + c.Send("HSET", "k1", 0, 0) + c.Send("ZINCRBY", "k1", 0, 0) + v, err = c.Do("EXEC") + if err != nil { + t.Fatalf("EXEC returned error %v", err) + } + + vs, err := redis.Values(v, nil) + if err != nil { + t.Fatalf("Values(v) returned error %v", err) + } + + if len(vs) != 2 { + t.Fatalf("len(vs) == %d, want 2", len(vs)) + } + + if _, ok := vs[0].(error); !ok { + t.Fatalf("first result is type %T, expected error", vs[0]) + } + + if _, ok := vs[1].([]byte); !ok { + t.Fatalf("second result is type %T, expected []byte", vs[2]) + } + + // Execute commands that fail after EXEC is called. The second command + // returns an error. + + c.Do("ZADD", "k2", 0, 0) + c.Send("MULTI") + c.Send("ZINCRBY", "k2", 0, 0) + c.Send("HSET", "k2", 0, 0) + v, err = c.Do("EXEC") + if err != nil { + t.Fatalf("EXEC returned error %v", err) + } + + vs, err = redis.Values(v, nil) + if err != nil { + t.Fatalf("Values(v) returned error %v", err) + } + + if len(vs) != 2 { + t.Fatalf("len(vs) == %d, want 2", len(vs)) + } + + if _, ok := vs[0].([]byte); !ok { + t.Fatalf("first result is type %T, expected []byte", vs[0]) + } + + if _, ok := vs[1].(error); !ok { + t.Fatalf("second result is type %T, expected error", vs[2]) + } +} + +func BenchmarkDoEmpty(b *testing.B) { + b.StopTimer() + c, err := redistest.Dial() + if err != nil { + b.Fatal(err) + } + defer c.Close() + b.StartTimer() + for i := 0; i < b.N; i++ { + if _, err := c.Do(""); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDoPing(b *testing.B) { + b.StopTimer() + c, err := redistest.Dial() + if err != nil { + b.Fatal(err) + } + defer c.Close() + b.StartTimer() + for i := 0; i < b.N; i++ { + if _, err := c.Do("PING"); err != nil { + b.Fatal(err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go new file mode 100644 index 0000000..1ae6f0c --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/doc.go @@ -0,0 +1,169 @@ +// Copyright 2012 Gary Burd +// +// 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. + +// Package redis is a client for the Redis database. +// +// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more +// documentation about this package. +// +// Connections +// +// The Conn interface is the primary interface for working with Redis. +// Applications create connections by calling the Dial, DialWithTimeout or +// NewConn functions. In the future, functions will be added for creating +// sharded and other types of connections. +// +// The application must call the connection Close method when the application +// is done with the connection. +// +// Executing Commands +// +// The Conn interface has a generic method for executing Redis commands: +// +// Do(commandName string, args ...interface{}) (reply interface{}, err error) +// +// The Redis command reference (http://redis.io/commands) lists the available +// commands. An example of using the Redis APPEND command is: +// +// n, err := conn.Do("APPEND", "key", "value") +// +// The Do method converts command arguments to binary strings for transmission +// to the server as follows: +// +// Go Type Conversion +// []byte Sent as is +// string Sent as is +// int, int64 strconv.FormatInt(v) +// float64 strconv.FormatFloat(v, 'g', -1, 64) +// bool true -> "1", false -> "0" +// nil "" +// all other types fmt.Print(v) +// +// Redis command reply types are represented using the following Go types: +// +// Redis type Go type +// error redis.Error +// integer int64 +// simple string string +// bulk string []byte or nil if value not present. +// array []interface{} or nil if value not present. +// +// Use type assertions or the reply helper functions to convert from +// interface{} to the specific Go type for the command result. +// +// Pipelining +// +// Connections support pipelining using the Send, Flush and Receive methods. +// +// Send(commandName string, args ...interface{}) error +// Flush() error +// Receive() (reply interface{}, err error) +// +// Send writes the command to the connection's output buffer. Flush flushes the +// connection's output buffer to the server. Receive reads a single reply from +// the server. The following example shows a simple pipeline. +// +// c.Send("SET", "foo", "bar") +// c.Send("GET", "foo") +// c.Flush() +// c.Receive() // reply from SET +// v, err = c.Receive() // reply from GET +// +// The Do method combines the functionality of the Send, Flush and Receive +// methods. The Do method starts by writing the command and flushing the output +// buffer. Next, the Do method receives all pending replies including the reply +// for the command just sent by Do. If any of the received replies is an error, +// then Do returns the error. If there are no errors, then Do returns the last +// reply. If the command argument to the Do method is "", then the Do method +// will flush the output buffer and receive pending replies without sending a +// command. +// +// Use the Send and Do methods to implement pipelined transactions. +// +// c.Send("MULTI") +// c.Send("INCR", "foo") +// c.Send("INCR", "bar") +// r, err := c.Do("EXEC") +// fmt.Println(r) // prints [1, 1] +// +// Concurrency +// +// Connections do not support concurrent calls to the write methods (Send, +// Flush) or concurrent calls to the read method (Receive). Connections do +// allow a concurrent reader and writer. +// +// Because the Do method combines the functionality of Send, Flush and Receive, +// the Do method cannot be called concurrently with the other methods. +// +// For full concurrent access to Redis, use the thread-safe Pool to get and +// release connections from within a goroutine. +// +// Publish and Subscribe +// +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. +// +// c.Send("SUBSCRIBE", "example") +// c.Flush() +// for { +// reply, err := c.Receive() +// if err != nil { +// return err +// } +// // process pushed message +// } +// +// The PubSubConn type wraps a Conn with convenience methods for implementing +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods +// send and flush a subscription management command. The receive method +// converts a pushed message to convenient types for use in a type switch. +// +// psc := redis.PubSubConn{c} +// psc.Subscribe("example") +// for { +// switch v := psc.Receive().(type) { +// case redis.Message: +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) +// case redis.Subscription: +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) +// case error: +// return v +// } +// } +// +// Reply Helpers +// +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply +// to a value of a specific type. To allow convenient wrapping of calls to the +// connection Do and Receive methods, the functions take a second argument of +// type error. If the error is non-nil, then the helper function returns the +// error. If the error is nil, the function converts the reply to the specified +// type: +// +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) +// if err != nil { +// // handle error return from c.Do or type conversion error. +// } +// +// The Scan function converts elements of a array reply to Go types: +// +// var value1 int +// var value2 string +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) +// if err != nil { +// // handle error +// } +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { +// // handle error +// } +package redis diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go new file mode 100644 index 0000000..129b86d --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/log.go @@ -0,0 +1,117 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "bytes" + "fmt" + "log" +) + +// NewLoggingConn returns a logging wrapper around a connection. +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { + if prefix != "" { + prefix = prefix + "." + } + return &loggingConn{conn, logger, prefix} +} + +type loggingConn struct { + Conn + logger *log.Logger + prefix string +} + +func (c *loggingConn) Close() error { + err := c.Conn.Close() + var buf bytes.Buffer + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) + c.logger.Output(2, buf.String()) + return err +} + +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { + const chop = 32 + switch v := v.(type) { + case []byte: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case string: + if len(v) > chop { + fmt.Fprintf(buf, "%q...", v[:chop]) + } else { + fmt.Fprintf(buf, "%q", v) + } + case []interface{}: + if len(v) == 0 { + buf.WriteString("[]") + } else { + sep := "[" + fin := "]" + if len(v) > chop { + v = v[:chop] + fin = "...]" + } + for _, vv := range v { + buf.WriteString(sep) + c.printValue(buf, vv) + sep = ", " + } + buf.WriteString(fin) + } + default: + fmt.Fprint(buf, v) + } +} + +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { + var buf bytes.Buffer + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) + if method != "Receive" { + buf.WriteString(commandName) + for _, arg := range args { + buf.WriteString(", ") + c.printValue(&buf, arg) + } + } + buf.WriteString(") -> (") + if method != "Send" { + c.printValue(&buf, reply) + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "%v)", err) + c.logger.Output(3, buf.String()) +} + +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { + reply, err := c.Conn.Do(commandName, args...) + c.print("Do", commandName, args, reply, err) + return reply, err +} + +func (c *loggingConn) Send(commandName string, args ...interface{}) error { + err := c.Conn.Send(commandName, args...) + c.print("Send", commandName, args, nil, err) + return err +} + +func (c *loggingConn) Receive() (interface{}, error) { + reply, err := c.Conn.Receive() + c.print("Receive", "", nil, reply, err) + return reply, err +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go new file mode 100644 index 0000000..9daf2e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool.go @@ -0,0 +1,389 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "bytes" + "container/list" + "crypto/rand" + "crypto/sha1" + "errors" + "io" + "strconv" + "sync" + "time" + + "github.com/garyburd/redigo/internal" +) + +var nowFunc = time.Now // for testing + +// ErrPoolExhausted is returned from a pool connection method (Do, Send, +// Receive, Flush, Err) when the maximum number of database connections in the +// pool has been reached. +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") + +var ( + errPoolClosed = errors.New("redigo: connection pool closed") + errConnClosed = errors.New("redigo: connection closed") +) + +// Pool maintains a pool of connections. The application calls the Get method +// to get a connection from the pool and the connection's Close method to +// return the connection's resources to the pool. +// +// The following example shows how to use a pool in a web application. The +// application creates a pool at application startup and makes it available to +// request handlers using a global variable. +// +// func newPool(server, password string) *redis.Pool { +// return &redis.Pool{ +// MaxIdle: 3, +// IdleTimeout: 240 * time.Second, +// Dial: func () (redis.Conn, error) { +// c, err := redis.Dial("tcp", server) +// if err != nil { +// return nil, err +// } +// if _, err := c.Do("AUTH", password); err != nil { +// c.Close() +// return nil, err +// } +// return c, err +// }, +// TestOnBorrow: func(c redis.Conn, t time.Time) error { +// _, err := c.Do("PING") +// return err +// }, +// } +// } +// +// var ( +// pool *redis.Pool +// redisServer = flag.String("redisServer", ":6379", "") +// redisPassword = flag.String("redisPassword", "", "") +// ) +// +// func main() { +// flag.Parse() +// pool = newPool(*redisServer, *redisPassword) +// ... +// } +// +// A request handler gets a connection from the pool and closes the connection +// when the handler is done: +// +// func serveHome(w http.ResponseWriter, r *http.Request) { +// conn := pool.Get() +// defer conn.Close() +// .... +// } +// +type Pool struct { + + // Dial is an application supplied function for creating and configuring a + // connection + Dial func() (Conn, error) + + // TestOnBorrow is an optional application supplied function for checking + // the health of an idle connection before the connection is used again by + // the application. Argument t is the time that the connection was returned + // to the pool. If the function returns an error, then the connection is + // closed. + TestOnBorrow func(c Conn, t time.Time) error + + // Maximum number of idle connections in the pool. + MaxIdle int + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxIdle limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + + // mu protects fields defined below. + mu sync.Mutex + cond *sync.Cond + closed bool + active int + + // Stack of idleConn with most recently used at the front. + idle list.List +} + +type idleConn struct { + c Conn + t time.Time +} + +// NewPool creates a new pool. This function is deprecated. Applications should +// initialize the Pool fields directly as shown in example. +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { + return &Pool{Dial: newFn, MaxIdle: maxIdle} +} + +// Get gets a connection. The application must close the returned connection. +// This method always returns a valid connection so that applications can defer +// error handling to the first use of the connection. If there is an error +// getting an underlying connection, then the connection Err, Do, Send, Flush +// and Receive methods return that error. +func (p *Pool) Get() Conn { + c, err := p.get() + if err != nil { + return errorConnection{err} + } + return &pooledConnection{p: p, c: c} +} + +// ActiveCount returns the number of active connections in the pool. +func (p *Pool) ActiveCount() int { + p.mu.Lock() + active := p.active + p.mu.Unlock() + return active +} + +// Close releases the resources used by the pool. +func (p *Pool) Close() error { + p.mu.Lock() + idle := p.idle + p.idle.Init() + p.closed = true + p.active -= idle.Len() + if p.cond != nil { + p.cond.Broadcast() + } + p.mu.Unlock() + for e := idle.Front(); e != nil; e = e.Next() { + e.Value.(idleConn).c.Close() + } + return nil +} + +// release decrements the active count and signals waiters. The caller must +// hold p.mu during the call. +func (p *Pool) release() { + p.active -= 1 + if p.cond != nil { + p.cond.Signal() + } +} + +// get prunes stale connections and returns a connection from the idle list or +// creates a new connection. +func (p *Pool) get() (Conn, error) { + p.mu.Lock() + + // Prune stale connections. + + if timeout := p.IdleTimeout; timeout > 0 { + for i, n := 0, p.idle.Len(); i < n; i++ { + e := p.idle.Back() + if e == nil { + break + } + ic := e.Value.(idleConn) + if ic.t.Add(timeout).After(nowFunc()) { + break + } + p.idle.Remove(e) + p.release() + p.mu.Unlock() + ic.c.Close() + p.mu.Lock() + } + } + + for { + + // Get idle connection. + + for i, n := 0, p.idle.Len(); i < n; i++ { + e := p.idle.Front() + if e == nil { + break + } + ic := e.Value.(idleConn) + p.idle.Remove(e) + test := p.TestOnBorrow + p.mu.Unlock() + if test == nil || test(ic.c, ic.t) == nil { + return ic.c, nil + } + ic.c.Close() + p.mu.Lock() + p.release() + } + + // Check for pool closed before dialing a new connection. + + if p.closed { + p.mu.Unlock() + return nil, errors.New("redigo: get on closed pool") + } + + // Dial new connection if under limit. + + if p.MaxActive == 0 || p.active < p.MaxActive { + dial := p.Dial + p.active += 1 + p.mu.Unlock() + c, err := dial() + if err != nil { + p.mu.Lock() + p.release() + p.mu.Unlock() + c = nil + } + return c, err + } + + if !p.Wait { + p.mu.Unlock() + return nil, ErrPoolExhausted + } + + if p.cond == nil { + p.cond = sync.NewCond(&p.mu) + } + p.cond.Wait() + } +} + +func (p *Pool) put(c Conn, forceClose bool) error { + err := c.Err() + p.mu.Lock() + if !p.closed && err == nil && !forceClose { + p.idle.PushFront(idleConn{t: nowFunc(), c: c}) + if p.idle.Len() > p.MaxIdle { + c = p.idle.Remove(p.idle.Back()).(idleConn).c + } else { + c = nil + } + } + + if c == nil { + if p.cond != nil { + p.cond.Signal() + } + p.mu.Unlock() + return nil + } + + p.release() + p.mu.Unlock() + return c.Close() +} + +type pooledConnection struct { + p *Pool + c Conn + state int +} + +var ( + sentinel []byte + sentinelOnce sync.Once +) + +func initSentinel() { + p := make([]byte, 64) + if _, err := rand.Read(p); err == nil { + sentinel = p + } else { + h := sha1.New() + io.WriteString(h, "Oops, rand failed. Use time instead.") + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) + sentinel = h.Sum(nil) + } +} + +func (pc *pooledConnection) Close() error { + c := pc.c + if _, ok := c.(errorConnection); ok { + return nil + } + pc.c = errorConnection{errConnClosed} + + if pc.state&internal.MultiState != 0 { + c.Send("DISCARD") + pc.state &^= (internal.MultiState | internal.WatchState) + } else if pc.state&internal.WatchState != 0 { + c.Send("UNWATCH") + pc.state &^= internal.WatchState + } + if pc.state&internal.SubscribeState != 0 { + c.Send("UNSUBSCRIBE") + c.Send("PUNSUBSCRIBE") + // To detect the end of the message stream, ask the server to echo + // a sentinel value and read until we see that value. + sentinelOnce.Do(initSentinel) + c.Send("ECHO", sentinel) + c.Flush() + for { + p, err := c.Receive() + if err != nil { + break + } + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { + pc.state &^= internal.SubscribeState + break + } + } + } + c.Do("") + pc.p.put(c, pc.state != 0) + return nil +} + +func (pc *pooledConnection) Err() error { + return pc.c.Err() +} + +func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + ci := internal.LookupCommandInfo(commandName) + pc.state = (pc.state | ci.Set) &^ ci.Clear + return pc.c.Do(commandName, args...) +} + +func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { + ci := internal.LookupCommandInfo(commandName) + pc.state = (pc.state | ci.Set) &^ ci.Clear + return pc.c.Send(commandName, args...) +} + +func (pc *pooledConnection) Flush() error { + return pc.c.Flush() +} + +func (pc *pooledConnection) Receive() (reply interface{}, err error) { + return pc.c.Receive() +} + +type errorConnection struct{ err error } + +func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } +func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } +func (ec errorConnection) Err() error { return ec.err } +func (ec errorConnection) Close() error { return ec.err } +func (ec errorConnection) Flush() error { return ec.err } +func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go new file mode 100644 index 0000000..1fe305f --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pool_test.go @@ -0,0 +1,674 @@ +// Copyright 2011 Gary Burd +// +// 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. + +package redis_test + +import ( + "errors" + "io" + "reflect" + "sync" + "testing" + "time" + + "github.com/garyburd/redigo/internal/redistest" + "github.com/garyburd/redigo/redis" +) + +type poolTestConn struct { + d *poolDialer + err error + redis.Conn +} + +func (c *poolTestConn) Close() error { c.d.open -= 1; return nil } +func (c *poolTestConn) Err() error { return c.err } + +func (c *poolTestConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { + if commandName == "ERR" { + c.err = args[0].(error) + commandName = "PING" + } + if commandName != "" { + c.d.commands = append(c.d.commands, commandName) + } + return c.Conn.Do(commandName, args...) +} + +func (c *poolTestConn) Send(commandName string, args ...interface{}) error { + c.d.commands = append(c.d.commands, commandName) + return c.Conn.Send(commandName, args...) +} + +type poolDialer struct { + t *testing.T + dialed int + open int + commands []string + dialErr error +} + +func (d *poolDialer) dial() (redis.Conn, error) { + d.dialed += 1 + if d.dialErr != nil { + return nil, d.dialErr + } + c, err := redistest.Dial() + if err != nil { + return nil, err + } + d.open += 1 + return &poolTestConn{d: d, Conn: c}, nil +} + +func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) { + if d.dialed != dialed { + d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) + } + if d.open != open { + d.t.Errorf("%s: open=%d, want %d", message, d.open, open) + } + if active := p.ActiveCount(); active != open { + d.t.Errorf("%s: active=%d, want %d", message, active, open) + } +} + +func TestPoolReuse(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + Dial: d.dial, + } + + for i := 0; i < 10; i++ { + c1 := p.Get() + c1.Do("PING") + c2 := p.Get() + c2.Do("PING") + c1.Close() + c2.Close() + } + + d.check("before close", p, 2, 2) + p.Close() + d.check("after close", p, 2, 0) +} + +func TestPoolMaxIdle(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + Dial: d.dial, + } + for i := 0; i < 10; i++ { + c1 := p.Get() + c1.Do("PING") + c2 := p.Get() + c2.Do("PING") + c3 := p.Get() + c3.Do("PING") + c1.Close() + c2.Close() + c3.Close() + } + d.check("before close", p, 12, 2) + p.Close() + d.check("after close", p, 12, 0) +} + +func TestPoolError(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + Dial: d.dial, + } + + c := p.Get() + c.Do("ERR", io.EOF) + if c.Err() == nil { + t.Errorf("expected c.Err() != nil") + } + c.Close() + + c = p.Get() + c.Do("ERR", io.EOF) + c.Close() + + d.check(".", p, 2, 0) +} + +func TestPoolClose(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + Dial: d.dial, + } + + c1 := p.Get() + c1.Do("PING") + c2 := p.Get() + c2.Do("PING") + c3 := p.Get() + c3.Do("PING") + + c1.Close() + if _, err := c1.Do("PING"); err == nil { + t.Errorf("expected error after connection closed") + } + + c2.Close() + c2.Close() + + p.Close() + + d.check("after pool close", p, 3, 1) + + if _, err := c1.Do("PING"); err == nil { + t.Errorf("expected error after connection and pool closed") + } + + c3.Close() + + d.check("after conn close", p, 3, 0) + + c1 = p.Get() + if _, err := c1.Do("PING"); err == nil { + t.Errorf("expected error after pool closed") + } +} + +func TestPoolTimeout(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + IdleTimeout: 300 * time.Second, + Dial: d.dial, + } + + now := time.Now() + redis.SetNowFunc(func() time.Time { return now }) + defer redis.SetNowFunc(time.Now) + + c := p.Get() + c.Do("PING") + c.Close() + + d.check("1", p, 1, 1) + + now = now.Add(p.IdleTimeout) + + c = p.Get() + c.Do("PING") + c.Close() + + d.check("2", p, 2, 1) + + p.Close() +} + +func TestPoolConcurrenSendReceive(t *testing.T) { + p := &redis.Pool{ + Dial: redistest.Dial, + } + c := p.Get() + done := make(chan error, 1) + go func() { + _, err := c.Receive() + done <- err + }() + c.Send("PING") + c.Flush() + err := <-done + if err != nil { + t.Fatalf("Receive() returned error %v", err) + } + _, err = c.Do("") + if err != nil { + t.Fatalf("Do() returned error %v", err) + } + c.Close() + p.Close() +} + +func TestPoolBorrowCheck(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + Dial: d.dial, + TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, + } + + for i := 0; i < 10; i++ { + c := p.Get() + c.Do("PING") + c.Close() + } + d.check("1", p, 10, 1) + p.Close() +} + +func TestPoolMaxActive(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + MaxActive: 2, + Dial: d.dial, + } + c1 := p.Get() + c1.Do("PING") + c2 := p.Get() + c2.Do("PING") + + d.check("1", p, 2, 2) + + c3 := p.Get() + if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { + t.Errorf("expected pool exhausted") + } + + c3.Close() + d.check("2", p, 2, 2) + c2.Close() + d.check("3", p, 2, 2) + + c3 = p.Get() + if _, err := c3.Do("PING"); err != nil { + t.Errorf("expected good channel, err=%v", err) + } + c3.Close() + + d.check("4", p, 2, 2) + p.Close() +} + +func TestPoolMonitorCleanup(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + MaxActive: 2, + Dial: d.dial, + } + c := p.Get() + c.Send("MONITOR") + c.Close() + + d.check("", p, 1, 0) + p.Close() +} + +func TestPoolPubSubCleanup(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + MaxActive: 2, + Dial: d.dial, + } + + c := p.Get() + c.Send("SUBSCRIBE", "x") + c.Close() + + want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + c = p.Get() + c.Send("PSUBSCRIBE", "x*") + c.Close() + + want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + p.Close() +} + +func TestPoolTransactionCleanup(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 2, + MaxActive: 2, + Dial: d.dial, + } + + c := p.Get() + c.Do("WATCH", "key") + c.Do("PING") + c.Close() + + want := []string{"WATCH", "PING", "UNWATCH"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + c = p.Get() + c.Do("WATCH", "key") + c.Do("UNWATCH") + c.Do("PING") + c.Close() + + want = []string{"WATCH", "UNWATCH", "PING"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + c = p.Get() + c.Do("WATCH", "key") + c.Do("MULTI") + c.Do("PING") + c.Close() + + want = []string{"WATCH", "MULTI", "PING", "DISCARD"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + c = p.Get() + c.Do("WATCH", "key") + c.Do("MULTI") + c.Do("DISCARD") + c.Do("PING") + c.Close() + + want = []string{"WATCH", "MULTI", "DISCARD", "PING"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + c = p.Get() + c.Do("WATCH", "key") + c.Do("MULTI") + c.Do("EXEC") + c.Do("PING") + c.Close() + + want = []string{"WATCH", "MULTI", "EXEC", "PING"} + if !reflect.DeepEqual(d.commands, want) { + t.Errorf("got commands %v, want %v", d.commands, want) + } + d.commands = nil + + p.Close() +} + +func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { + errs := make(chan error, 10) + for i := 0; i < cap(errs); i++ { + go func() { + c := p.Get() + _, err := c.Do(cmd, args...) + errs <- err + c.Close() + }() + } + + // Wait for goroutines to block. + time.Sleep(time.Second / 4) + + return errs +} + +func TestWaitPool(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + defer p.Close() + c := p.Get() + errs := startGoroutines(p, "PING") + d.check("before close", p, 1, 1) + c.Close() + timeout := time.After(2 * time.Second) + for i := 0; i < cap(errs); i++ { + select { + case err := <-errs: + if err != nil { + t.Fatal(err) + } + case <-timeout: + t.Fatalf("timeout waiting for blocked goroutine %d", i) + } + } + d.check("done", p, 1, 1) +} + +func TestWaitPoolClose(t *testing.T) { + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + c := p.Get() + if _, err := c.Do("PING"); err != nil { + t.Fatal(err) + } + errs := startGoroutines(p, "PING") + d.check("before close", p, 1, 1) + p.Close() + timeout := time.After(2 * time.Second) + for i := 0; i < cap(errs); i++ { + select { + case err := <-errs: + switch err { + case nil: + t.Fatal("blocked goroutine did not get error") + case redis.ErrPoolExhausted: + t.Fatal("blocked goroutine got pool exhausted error") + } + case <-timeout: + t.Fatal("timeout waiting for blocked goroutine") + } + } + c.Close() + d.check("done", p, 1, 0) +} + +func TestWaitPoolCommandError(t *testing.T) { + testErr := errors.New("test") + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + defer p.Close() + c := p.Get() + errs := startGoroutines(p, "ERR", testErr) + d.check("before close", p, 1, 1) + c.Close() + timeout := time.After(2 * time.Second) + for i := 0; i < cap(errs); i++ { + select { + case err := <-errs: + if err != nil { + t.Fatal(err) + } + case <-timeout: + t.Fatalf("timeout waiting for blocked goroutine %d", i) + } + } + d.check("done", p, cap(errs), 0) +} + +func TestWaitPoolDialError(t *testing.T) { + testErr := errors.New("test") + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: 1, + MaxActive: 1, + Dial: d.dial, + Wait: true, + } + defer p.Close() + c := p.Get() + errs := startGoroutines(p, "ERR", testErr) + d.check("before close", p, 1, 1) + + d.dialErr = errors.New("dial") + c.Close() + + nilCount := 0 + errCount := 0 + timeout := time.After(2 * time.Second) + for i := 0; i < cap(errs); i++ { + select { + case err := <-errs: + switch err { + case nil: + nilCount++ + case d.dialErr: + errCount++ + default: + t.Fatalf("expected dial error or nil, got %v", err) + } + case <-timeout: + t.Fatalf("timeout waiting for blocked goroutine %d", i) + } + } + if nilCount != 1 { + t.Errorf("expected one nil error, got %d", nilCount) + } + if errCount != cap(errs)-1 { + t.Errorf("expected %d dial erors, got %d", cap(errs)-1, errCount) + } + d.check("done", p, cap(errs), 0) +} + +// Borrowing requires us to iterate over the idle connections, unlock the pool, +// and perform a blocking operation to check the connection still works. If +// TestOnBorrow fails, we must reacquire the lock and continue iteration. This +// test ensures that iteration will work correctly if multiple threads are +// iterating simultaneously. +func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { + count := 100 + + // First we'll Create a pool where the pilfering of idle connections fails. + d := poolDialer{t: t} + p := &redis.Pool{ + MaxIdle: count, + MaxActive: count, + Dial: d.dial, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + return errors.New("No way back into the real world.") + }, + } + defer p.Close() + + // Fill the pool with idle connections. + b1 := sync.WaitGroup{} + b1.Add(count) + b2 := sync.WaitGroup{} + b2.Add(count) + for i := 0; i < count; i++ { + go func() { + c := p.Get() + if c.Err() != nil { + t.Errorf("pool get failed: %v", c.Err()) + } + b1.Done() + b1.Wait() + c.Close() + b2.Done() + }() + } + b2.Wait() + if d.dialed != count { + t.Errorf("Expected %d dials, got %d", count, d.dialed) + } + + // Spawn a bunch of goroutines to thrash the pool. + b2.Add(count) + for i := 0; i < count; i++ { + go func() { + c := p.Get() + if c.Err() != nil { + t.Errorf("pool get failed: %v", c.Err()) + } + c.Close() + b2.Done() + }() + } + b2.Wait() + if d.dialed != count*2 { + t.Errorf("Expected %d dials, got %d", count*2, d.dialed) + } +} + +func BenchmarkPoolGet(b *testing.B) { + b.StopTimer() + p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + c := p.Get() + if err := c.Err(); err != nil { + b.Fatal(err) + } + c.Close() + defer p.Close() + b.StartTimer() + for i := 0; i < b.N; i++ { + c = p.Get() + c.Close() + } +} + +func BenchmarkPoolGetErr(b *testing.B) { + b.StopTimer() + p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + c := p.Get() + if err := c.Err(); err != nil { + b.Fatal(err) + } + c.Close() + defer p.Close() + b.StartTimer() + for i := 0; i < b.N; i++ { + c = p.Get() + if err := c.Err(); err != nil { + b.Fatal(err) + } + c.Close() + } +} + +func BenchmarkPoolGetPing(b *testing.B) { + b.StopTimer() + p := redis.Pool{Dial: redistest.Dial, MaxIdle: 2} + c := p.Get() + if err := c.Err(); err != nil { + b.Fatal(err) + } + c.Close() + defer p.Close() + b.StartTimer() + for i := 0; i < b.N; i++ { + c = p.Get() + if _, err := c.Do("PING"); err != nil { + b.Fatal(err) + } + c.Close() + } +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go new file mode 100644 index 0000000..f079042 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub.go @@ -0,0 +1,129 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "errors" +) + +// Subscription represents a subscribe or unsubscribe notification. +type Subscription struct { + + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" + Kind string + + // The channel that was changed. + Channel string + + // The current number of subscriptions for connection. + Count int +} + +// Message represents a message notification. +type Message struct { + + // The originating channel. + Channel string + + // The message data. + Data []byte +} + +// PMessage represents a pmessage notification. +type PMessage struct { + + // The matched pattern. + Pattern string + + // The originating channel. + Channel string + + // The message data. + Data []byte +} + +// PubSubConn wraps a Conn with convenience methods for subscribers. +type PubSubConn struct { + Conn Conn +} + +// Close closes the connection. +func (c PubSubConn) Close() error { + return c.Conn.Close() +} + +// Subscribe subscribes the connection to the specified channels. +func (c PubSubConn) Subscribe(channel ...interface{}) error { + c.Conn.Send("SUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PSubscribe subscribes the connection to the given patterns. +func (c PubSubConn) PSubscribe(channel ...interface{}) error { + c.Conn.Send("PSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Unsubscribe unsubscribes the connection from the given channels, or from all +// of them if none is given. +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { + c.Conn.Send("UNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// PUnsubscribe unsubscribes the connection from the given patterns, or from all +// of them if none is given. +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { + c.Conn.Send("PUNSUBSCRIBE", channel...) + return c.Conn.Flush() +} + +// Receive returns a pushed message as a Subscription, Message, PMessage or +// error. The return value is intended to be used directly in a type switch as +// illustrated in the PubSubConn example. +func (c PubSubConn) Receive() interface{} { + reply, err := Values(c.Conn.Receive()) + if err != nil { + return err + } + + var kind string + reply, err = Scan(reply, &kind) + if err != nil { + return err + } + + switch kind { + case "message": + var m Message + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { + return err + } + return m + case "pmessage": + var pm PMessage + if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { + return err + } + return pm + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": + s := Subscription{Kind: kind} + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { + return err + } + return s + } + return errors.New("redigo: unknown pubsub notification") +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go new file mode 100644 index 0000000..707f5a4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/pubsub_test.go @@ -0,0 +1,143 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis_test + +import ( + "fmt" + "net" + "reflect" + "sync" + "testing" + "time" + + "github.com/garyburd/redigo/internal/redistest" + "github.com/garyburd/redigo/redis" +) + +func publish(channel, value interface{}) { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + c.Do("PUBLISH", channel, value) +} + +// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine. +func ExamplePubSubConn() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + var wg sync.WaitGroup + wg.Add(2) + + psc := redis.PubSubConn{Conn: c} + + // This goroutine receives and prints pushed notifications from the server. + // The goroutine exits when the connection is unsubscribed from all + // channels or there is an error. + go func() { + defer wg.Done() + for { + switch n := psc.Receive().(type) { + case redis.Message: + fmt.Printf("Message: %s %s\n", n.Channel, n.Data) + case redis.PMessage: + fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data) + case redis.Subscription: + fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) + if n.Count == 0 { + return + } + case error: + fmt.Printf("error: %v\n", n) + return + } + } + }() + + // This goroutine manages subscriptions for the connection. + go func() { + defer wg.Done() + + psc.Subscribe("example") + psc.PSubscribe("p*") + + // The following function calls publish a message using another + // connection to the Redis server. + publish("example", "hello") + publish("example", "world") + publish("pexample", "foo") + publish("pexample", "bar") + + // Unsubscribe from all connections. This will cause the receiving + // goroutine to exit. + psc.Unsubscribe() + psc.PUnsubscribe() + }() + + wg.Wait() + + // Output: + // Subscription: subscribe example 1 + // Subscription: psubscribe p* 2 + // Message: example hello + // Message: example world + // PMessage: p* pexample foo + // PMessage: p* pexample bar + // Subscription: unsubscribe example 1 + // Subscription: punsubscribe p* 0 +} + +func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { + actual := c.Receive() + if !reflect.DeepEqual(actual, expected) { + t.Errorf("%s = %v, want %v", message, actual, expected) + } +} + +func TestPushed(t *testing.T) { + pc, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer pc.Close() + + nc, err := net.Dial("tcp", ":6379") + if err != nil { + t.Fatal(err) + } + defer nc.Close() + nc.SetReadDeadline(time.Now().Add(4 * time.Second)) + + c := redis.PubSubConn{Conn: redis.NewConn(nc, 0, 0)} + + c.Subscribe("c1") + expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) + c.Subscribe("c2") + expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}) + c.PSubscribe("p1") + expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}) + c.PSubscribe("p2") + expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}) + c.PUnsubscribe() + expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}) + expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}) + + pc.Do("PUBLISH", "c1", "hello") + expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go new file mode 100644 index 0000000..c90a48e --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/redis.go @@ -0,0 +1,44 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +// Error represents an error returned in a command reply. +type Error string + +func (err Error) Error() string { return string(err) } + +// Conn represents a connection to a Redis server. +type Conn interface { + // Close closes the connection. + Close() error + + // Err returns a non-nil value if the connection is broken. The returned + // value is either the first non-nil value returned from the underlying + // network connection or a protocol parsing error. Applications should + // close broken connections. + Err() error + + // Do sends a command to the server and returns the received reply. + Do(commandName string, args ...interface{}) (reply interface{}, err error) + + // Send writes the command to the client's output buffer. + Send(commandName string, args ...interface{}) error + + // Flush flushes the output buffer to the Redis server. + Flush() error + + // Receive receives a single reply from the Redis server + Receive() (reply interface{}, err error) +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go new file mode 100644 index 0000000..5648f93 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply.go @@ -0,0 +1,312 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "errors" + "fmt" + "strconv" +) + +// ErrNil indicates that a reply value is nil. +var ErrNil = errors.New("redigo: nil returned") + +// Int is a helper that converts a command reply to an integer. If err is not +// equal to nil, then Int returns 0, err. Otherwise, Int converts the +// reply to an int as follows: +// +// Reply type Result +// integer int(reply), nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) +} + +// Int64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + return reply, nil + case []byte: + n, err := strconv.ParseInt(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) +} + +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") + +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the +// reply to an int64 as follows: +// +// Reply type Result +// integer reply, nil +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int64: + if reply < 0 { + return 0, errNegativeInt + } + return uint64(reply), nil + case []byte: + n, err := strconv.ParseUint(string(reply), 10, 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) +} + +// Float64 is a helper that converts a command reply to 64 bit float. If err is +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts +// the reply to an int as follows: +// +// Reply type Result +// bulk string parsed reply, nil +// nil 0, ErrNil +// other 0, error +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case []byte: + n, err := strconv.ParseFloat(string(reply), 64) + return n, err + case nil: + return 0, ErrNil + case Error: + return 0, reply + } + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) +} + +// String is a helper that converts a command reply to a string. If err is not +// equal to nil, then String returns "", err. Otherwise String converts the +// reply to a string as follows: +// +// Reply type Result +// bulk string string(reply), nil +// simple string reply, nil +// nil "", ErrNil +// other "", error +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + switch reply := reply.(type) { + case []byte: + return string(reply), nil + case string: + return reply, nil + case nil: + return "", ErrNil + case Error: + return "", reply + } + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) +} + +// Bytes is a helper that converts a command reply to a slice of bytes. If err +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts +// the reply to a slice of bytes as follows: +// +// Reply type Result +// bulk string reply, nil +// simple string []byte(reply), nil +// nil nil, ErrNil +// other nil, error +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + return reply, nil + case string: + return []byte(reply), nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) +} + +// Bool is a helper that converts a command reply to a boolean. If err is not +// equal to nil, then Bool returns false, err. Otherwise Bool converts the +// reply to boolean as follows: +// +// Reply type Result +// integer value != 0, nil +// bulk string strconv.ParseBool(reply) +// nil false, ErrNil +// other false, error +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case int64: + return reply != 0, nil + case []byte: + return strconv.ParseBool(string(reply)) + case nil: + return false, ErrNil + case Error: + return false, reply + } + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) +} + +// MultiBulk is deprecated. Use Values. +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } + +// Values is a helper that converts an array command reply to a []interface{}. +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values +// converts the reply as follows: +// +// Reply type Result +// array reply, nil +// nil nil, ErrNil +// other nil, error +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) +} + +// Strings is a helper that converts an array command reply to a []string. If +// err is not equal to nil, then Strings returns nil, err. Nil array items are +// converted to "" in the output slice. Strings returns an error if an array +// item is not a bulk string or nil. +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + p, ok := reply[i].([]byte) + if !ok { + return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i]) + } + result[i] = string(p) + } + return result, nil + case nil: + return nil, ErrNil + case Error: + return nil, reply + } + return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply) +} + +// Ints is a helper that converts an array command reply to a []int. If +// err is not equal to nil, then Ints returns nil, err. +func Ints(reply interface{}, err error) ([]int, error) { + var ints []int + if reply == nil { + return ints, ErrNil + } + values, err := Values(reply, err) + if err != nil { + return ints, err + } + if err := ScanSlice(values, &ints); err != nil { + return ints, err + } + return ints, nil +} + +// StringMap is a helper that converts an array of strings (alternating key, value) +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. +// Requires an even number of values in result. +func StringMap(result interface{}, err error) (map[string]string, error) { + values, err := Values(result, err) + if err != nil { + return nil, err + } + if len(values)%2 != 0 { + return nil, errors.New("redigo: StringMap expects even number of values result") + } + m := make(map[string]string, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, okKey := values[i].([]byte) + value, okValue := values[i+1].([]byte) + if !okKey || !okValue { + return nil, errors.New("redigo: ScanMap key not a bulk string value") + } + m[string(key)] = string(value) + } + return m, nil +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go new file mode 100644 index 0000000..92744c5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/reply_test.go @@ -0,0 +1,166 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/garyburd/redigo/internal/redistest" + "github.com/garyburd/redigo/redis" +) + +type valueError struct { + v interface{} + err error +} + +func ve(v interface{}, err error) valueError { + return valueError{v, err} +} + +var replyTests = []struct { + name interface{} + actual valueError + expected valueError +}{ + { + "ints([v1, v2])", + ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), + ve([]int{4, 5}, nil), + }, + { + "ints(nil)", + ve(redis.Ints(nil, nil)), + ve([]int(nil), redis.ErrNil), + }, + { + "strings([v1, v2])", + ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), + ve([]string{"v1", "v2"}, nil), + }, + { + "strings(nil)", + ve(redis.Strings(nil, nil)), + ve([]string(nil), redis.ErrNil), + }, + { + "values([v1, v2])", + ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), + ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), + }, + { + "values(nil)", + ve(redis.Values(nil, nil)), + ve([]interface{}(nil), redis.ErrNil), + }, + { + "float64(1.0)", + ve(redis.Float64([]byte("1.0"), nil)), + ve(float64(1.0), nil), + }, + { + "float64(nil)", + ve(redis.Float64(nil, nil)), + ve(float64(0.0), redis.ErrNil), + }, + { + "uint64(1)", + ve(redis.Uint64(int64(1), nil)), + ve(uint64(1), nil), + }, + { + "uint64(-1)", + ve(redis.Uint64(int64(-1), nil)), + ve(uint64(0), redis.ErrNegativeInt), + }, +} + +func TestReply(t *testing.T) { + for _, rt := range replyTests { + if rt.actual.err != rt.expected.err { + t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err) + continue + } + if !reflect.DeepEqual(rt.actual.v, rt.expected.v) { + t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v) + } + } +} + +// dial wraps DialTestDB() with a more suitable function name for examples. +func dial() (redis.Conn, error) { + return redistest.Dial() +} + +func ExampleBool() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Do("SET", "foo", 1) + exists, _ := redis.Bool(c.Do("EXISTS", "foo")) + fmt.Printf("%#v\n", exists) + // Output: + // true +} + +func ExampleInt() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Do("SET", "k1", 1) + n, _ := redis.Int(c.Do("GET", "k1")) + fmt.Printf("%#v\n", n) + n, _ = redis.Int(c.Do("INCR", "k1")) + fmt.Printf("%#v\n", n) + // Output: + // 1 + // 2 +} + +func ExampleInts() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Do("SADD", "set_with_integers", 4, 5, 6) + ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers")) + fmt.Printf("%#v\n", ints) + // Output: + // []int{4, 5, 6} +} + +func ExampleString() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Do("SET", "hello", "world") + s, err := redis.String(c.Do("GET", "hello")) + fmt.Printf("%#v\n", s) + // Output: + // "world" +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go new file mode 100644 index 0000000..8c9cfa1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan.go @@ -0,0 +1,513 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "sync" +) + +func ensureLen(d reflect.Value, n int) { + if n > d.Cap() { + d.Set(reflect.MakeSlice(d.Type(), n, n)) + } else { + d.SetLen(n) + } +} + +func cannotConvert(d reflect.Value, s interface{}) error { + return fmt.Errorf("redigo: Scan cannot convert from %s to %s", + reflect.TypeOf(s), d.Type()) +} + +func convertAssignBytes(d reflect.Value, s []byte) (err error) { + switch d.Type().Kind() { + case reflect.Float32, reflect.Float64: + var x float64 + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) + d.SetFloat(x) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + var x int64 + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) + d.SetInt(x) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + var x uint64 + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) + d.SetUint(x) + case reflect.Bool: + var x bool + x, err = strconv.ParseBool(string(s)) + d.SetBool(x) + case reflect.String: + d.SetString(string(s)) + case reflect.Slice: + if d.Type().Elem().Kind() != reflect.Uint8 { + err = cannotConvert(d, s) + } else { + d.SetBytes(s) + } + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignInt(d reflect.Value, s int64) (err error) { + switch d.Type().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + d.SetInt(s) + if d.Int() != s { + err = strconv.ErrRange + d.SetInt(0) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if s < 0 { + err = strconv.ErrRange + } else { + x := uint64(s) + d.SetUint(x) + if d.Uint() != x { + err = strconv.ErrRange + d.SetUint(0) + } + } + case reflect.Bool: + d.SetBool(s != 0) + default: + err = cannotConvert(d, s) + } + return +} + +func convertAssignValue(d reflect.Value, s interface{}) (err error) { + switch s := s.(type) { + case []byte: + err = convertAssignBytes(d, s) + case int64: + err = convertAssignInt(d, s) + default: + err = cannotConvert(d, s) + } + return err +} + +func convertAssignValues(d reflect.Value, s []interface{}) error { + if d.Type().Kind() != reflect.Slice { + return cannotConvert(d, s) + } + ensureLen(d, len(s)) + for i := 0; i < len(s); i++ { + if err := convertAssignValue(d.Index(i), s[i]); err != nil { + return err + } + } + return nil +} + +func convertAssign(d interface{}, s interface{}) (err error) { + // Handle the most common destination types using type switches and + // fall back to reflection for all other types. + switch s := s.(type) { + case nil: + // ingore + case []byte: + switch d := d.(type) { + case *string: + *d = string(s) + case *int: + *d, err = strconv.Atoi(string(s)) + case *bool: + *d, err = strconv.ParseBool(string(s)) + case *[]byte: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignBytes(d.Elem(), s) + } + } + case int64: + switch d := d.(type) { + case *int: + x := int(s) + if int64(x) != s { + err = strconv.ErrRange + x = 0 + } + *d = x + case *bool: + *d = s != 0 + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignInt(d.Elem(), s) + } + } + case []interface{}: + switch d := d.(type) { + case *[]interface{}: + *d = s + case *interface{}: + *d = s + case nil: + // skip value + default: + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { + err = cannotConvert(d, s) + } else { + err = convertAssignValues(d.Elem(), s) + } + } + case Error: + err = s + default: + err = cannotConvert(reflect.ValueOf(d), s) + } + return +} + +// Scan copies from src to the values pointed at by dest. +// +// The values pointed at by dest must be an integer, float, boolean, string, +// []byte, interface{} or slices of these types. Scan uses the standard strconv +// package to convert bulk strings to numeric and boolean types. +// +// If a dest value is nil, then the corresponding src value is skipped. +// +// If a src element is nil, then the corresponding dest value is not modified. +// +// To enable easy use of Scan in a loop, Scan returns the slice of src +// following the copied values. +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { + if len(src) < len(dest) { + return nil, errors.New("redigo: Scan array short") + } + var err error + for i, d := range dest { + err = convertAssign(d, src[i]) + if err != nil { + break + } + } + return src[len(dest):], err +} + +type fieldSpec struct { + name string + index []int + //omitEmpty bool +} + +type structSpec struct { + m map[string]*fieldSpec + l []*fieldSpec +} + +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { + return ss.m[string(name)] +} + +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + switch { + case f.PkgPath != "": + // Ignore unexported fields. + case f.Anonymous: + // TODO: Handle pointers. Requires change to decoder and + // protection against infinite recursion. + if f.Type.Kind() == reflect.Struct { + compileStructSpec(f.Type, depth, append(index, i), ss) + } + default: + fs := &fieldSpec{name: f.Name} + tag := f.Tag.Get("redis") + p := strings.Split(tag, ",") + if len(p) > 0 { + if p[0] == "-" { + continue + } + if len(p[0]) > 0 { + fs.name = p[0] + } + for _, s := range p[1:] { + switch s { + //case "omitempty": + // fs.omitempty = true + default: + panic(errors.New("redigo: unknown field flag " + s + " for type " + t.Name())) + } + } + } + d, found := depth[fs.name] + if !found { + d = 1 << 30 + } + switch { + case len(index) == d: + // At same depth, remove from result. + delete(ss.m, fs.name) + j := 0 + for i := 0; i < len(ss.l); i++ { + if fs.name != ss.l[i].name { + ss.l[j] = ss.l[i] + j += 1 + } + } + ss.l = ss.l[:j] + case len(index) < d: + fs.index = make([]int, len(index)+1) + copy(fs.index, index) + fs.index[len(index)] = i + depth[fs.name] = len(index) + ss.m[fs.name] = fs + ss.l = append(ss.l, fs) + } + } + } +} + +var ( + structSpecMutex sync.RWMutex + structSpecCache = make(map[reflect.Type]*structSpec) + defaultFieldSpec = &fieldSpec{} +) + +func structSpecForType(t reflect.Type) *structSpec { + + structSpecMutex.RLock() + ss, found := structSpecCache[t] + structSpecMutex.RUnlock() + if found { + return ss + } + + structSpecMutex.Lock() + defer structSpecMutex.Unlock() + ss, found = structSpecCache[t] + if found { + return ss + } + + ss = &structSpec{m: make(map[string]*fieldSpec)} + compileStructSpec(t, make(map[string]int), nil, ss) + structSpecCache[t] = ss + return ss +} + +var errScanStructValue = errors.New("redigo: ScanStruct value must be non-nil pointer to a struct") + +// ScanStruct scans alternating names and values from src to a struct. The +// HGETALL and CONFIG GET commands return replies in this format. +// +// ScanStruct uses exported field names to match values in the response. Use +// 'redis' field tag to override the name: +// +// Field int `redis:"myName"` +// +// Fields with the tag redis:"-" are ignored. +// +// Integer, float, boolean, string and []byte fields are supported. Scan uses the +// standard strconv package to convert bulk string values to numeric and +// boolean types. +// +// If a src element is nil, then the corresponding field is not modified. +func ScanStruct(src []interface{}, dest interface{}) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanStructValue + } + d = d.Elem() + if d.Kind() != reflect.Struct { + return errScanStructValue + } + ss := structSpecForType(d.Type()) + + if len(src)%2 != 0 { + return errors.New("redigo: ScanStruct expects even number of values in values") + } + + for i := 0; i < len(src); i += 2 { + s := src[i+1] + if s == nil { + continue + } + name, ok := src[i].([]byte) + if !ok { + return errors.New("redigo: ScanStruct key not a bulk string value") + } + fs := ss.fieldSpec(name) + if fs == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return err + } + } + return nil +} + +var ( + errScanSliceValue = errors.New("redigo: ScanSlice dest must be non-nil pointer to a struct") +) + +// ScanSlice scans src to the slice pointed to by dest. The elements the dest +// slice must be integer, float, boolean, string, struct or pointer to struct +// values. +// +// Struct fields must be integer, float, boolean or string values. All struct +// fields are used unless a subset is specified using fieldNames. +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { + d := reflect.ValueOf(dest) + if d.Kind() != reflect.Ptr || d.IsNil() { + return errScanSliceValue + } + d = d.Elem() + if d.Kind() != reflect.Slice { + return errScanSliceValue + } + + isPtr := false + t := d.Type().Elem() + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { + isPtr = true + t = t.Elem() + } + + if t.Kind() != reflect.Struct { + ensureLen(d, len(src)) + for i, s := range src { + if s == nil { + continue + } + if err := convertAssignValue(d.Index(i), s); err != nil { + return err + } + } + return nil + } + + ss := structSpecForType(t) + fss := ss.l + if len(fieldNames) > 0 { + fss = make([]*fieldSpec, len(fieldNames)) + for i, name := range fieldNames { + fss[i] = ss.m[name] + if fss[i] == nil { + return errors.New("redigo: ScanSlice bad field name " + name) + } + } + } + + if len(fss) == 0 { + return errors.New("redigo: ScanSlice no struct fields") + } + + n := len(src) / len(fss) + if n*len(fss) != len(src) { + return errors.New("redigo: ScanSlice length not a multiple of struct field count") + } + + ensureLen(d, n) + for i := 0; i < n; i++ { + d := d.Index(i) + if isPtr { + if d.IsNil() { + d.Set(reflect.New(t)) + } + d = d.Elem() + } + for j, fs := range fss { + s := src[i*len(fss)+j] + if s == nil { + continue + } + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { + return err + } + } + } + return nil +} + +// Args is a helper for constructing command arguments from structured values. +type Args []interface{} + +// Add returns the result of appending value to args. +func (args Args) Add(value ...interface{}) Args { + return append(args, value...) +} + +// AddFlat returns the result of appending the flattened value of v to args. +// +// Maps are flattened by appending the alternating keys and map values to args. +// +// Slices are flattened by appending the slice elements to args. +// +// Structs are flattened by appending the alternating names and values of +// exported fields to args. If v is a nil struct pointer, then nothing is +// appended. The 'redis' field tag overrides struct field names. See ScanStruct +// for more information on the use of the 'redis' field tag. +// +// Other types are appended to args as is. +func (args Args) AddFlat(v interface{}) Args { + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.Struct: + args = flattenStruct(args, rv) + case reflect.Slice: + for i := 0; i < rv.Len(); i++ { + args = append(args, rv.Index(i).Interface()) + } + case reflect.Map: + for _, k := range rv.MapKeys() { + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) + } + case reflect.Ptr: + if rv.Type().Elem().Kind() == reflect.Struct { + if !rv.IsNil() { + args = flattenStruct(args, rv.Elem()) + } + } else { + args = append(args, v) + } + default: + args = append(args, v) + } + return args +} + +func flattenStruct(args Args, v reflect.Value) Args { + ss := structSpecForType(v.Type()) + for _, fs := range ss.l { + fv := v.FieldByIndex(fs.index) + args = append(args, fs.name, fv.Interface()) + } + return args +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go new file mode 100644 index 0000000..b57dd89 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/scan_test.go @@ -0,0 +1,412 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis_test + +import ( + "fmt" + "github.com/garyburd/redigo/redis" + "math" + "reflect" + "testing" +) + +var scanConversionTests = []struct { + src interface{} + dest interface{} +}{ + {[]byte("-inf"), math.Inf(-1)}, + {[]byte("+inf"), math.Inf(1)}, + {[]byte("0"), float64(0)}, + {[]byte("3.14159"), float64(3.14159)}, + {[]byte("3.14"), float32(3.14)}, + {[]byte("-100"), int(-100)}, + {[]byte("101"), int(101)}, + {int64(102), int(102)}, + {[]byte("103"), uint(103)}, + {int64(104), uint(104)}, + {[]byte("105"), int8(105)}, + {int64(106), int8(106)}, + {[]byte("107"), uint8(107)}, + {int64(108), uint8(108)}, + {[]byte("0"), false}, + {int64(0), false}, + {[]byte("f"), false}, + {[]byte("1"), true}, + {int64(1), true}, + {[]byte("t"), true}, + {[]byte("hello"), "hello"}, + {[]byte("world"), []byte("world")}, + {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}}, + {[]interface{}{[]byte("foo")}, []string{"foo"}}, + {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}}, + {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}}, + {[]interface{}{[]byte("1")}, []int{1}}, + {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}}, + {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}}, + {[]interface{}{[]byte("1")}, []byte{1}}, + {[]interface{}{[]byte("1")}, []bool{true}}, +} + +func TestScanConversion(t *testing.T) { + for _, tt := range scanConversionTests { + values := []interface{}{tt.src} + dest := reflect.New(reflect.TypeOf(tt.dest)) + values, err := redis.Scan(values, dest.Interface()) + if err != nil { + t.Errorf("Scan(%v) returned error %v", tt, err) + continue + } + if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) { + t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest) + } + } +} + +var scanConversionErrorTests = []struct { + src interface{} + dest interface{} +}{ + {[]byte("1234"), byte(0)}, + {int64(1234), byte(0)}, + {[]byte("-1"), byte(0)}, + {int64(-1), byte(0)}, + {[]byte("junk"), false}, + {redis.Error("blah"), false}, +} + +func TestScanConversionError(t *testing.T) { + for _, tt := range scanConversionErrorTests { + values := []interface{}{tt.src} + dest := reflect.New(reflect.TypeOf(tt.dest)) + values, err := redis.Scan(values, dest.Interface()) + if err == nil { + t.Errorf("Scan(%v) did not return error", tt) + } + } +} + +func ExampleScan() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Send("HMSET", "album:1", "title", "Red", "rating", 5) + c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) + c.Send("HMSET", "album:3", "title", "Beat") + c.Send("LPUSH", "albums", "1") + c.Send("LPUSH", "albums", "2") + c.Send("LPUSH", "albums", "3") + values, err := redis.Values(c.Do("SORT", "albums", + "BY", "album:*->rating", + "GET", "album:*->title", + "GET", "album:*->rating")) + if err != nil { + panic(err) + } + + for len(values) > 0 { + var title string + rating := -1 // initialize to illegal value to detect nil. + values, err = redis.Scan(values, &title, &rating) + if err != nil { + panic(err) + } + if rating == -1 { + fmt.Println(title, "not-rated") + } else { + fmt.Println(title, rating) + } + } + // Output: + // Beat not-rated + // Earthbound 1 + // Red 5 +} + +type s0 struct { + X int + Y int `redis:"y"` + Bt bool +} + +type s1 struct { + X int `redis:"-"` + I int `redis:"i"` + U uint `redis:"u"` + S string `redis:"s"` + P []byte `redis:"p"` + B bool `redis:"b"` + Bt bool + Bf bool + s0 +} + +var scanStructTests = []struct { + title string + reply []string + value interface{} +}{ + {"basic", + []string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"}, + &s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}}, + }, +} + +func TestScanStruct(t *testing.T) { + for _, tt := range scanStructTests { + + var reply []interface{} + for _, v := range tt.reply { + reply = append(reply, []byte(v)) + } + + value := reflect.New(reflect.ValueOf(tt.value).Type().Elem()) + + if err := redis.ScanStruct(reply, value.Interface()); err != nil { + t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err) + } + + if !reflect.DeepEqual(value.Interface(), tt.value) { + t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value) + } + } +} + +func TestBadScanStructArgs(t *testing.T) { + x := []interface{}{"A", "b"} + test := func(v interface{}) { + if err := redis.ScanStruct(x, v); err == nil { + t.Errorf("Expect error for ScanStruct(%T, %T)", x, v) + } + } + + test(nil) + + var v0 *struct{} + test(v0) + + var v1 int + test(&v1) + + x = x[:1] + v2 := struct{ A string }{} + test(&v2) +} + +var scanSliceTests = []struct { + src []interface{} + fieldNames []string + ok bool + dest interface{} +}{ + { + []interface{}{[]byte("1"), nil, []byte("-1")}, + nil, + true, + []int{1, 0, -1}, + }, + { + []interface{}{[]byte("1"), nil, []byte("2")}, + nil, + true, + []uint{1, 0, 2}, + }, + { + []interface{}{[]byte("-1")}, + nil, + false, + []uint{1}, + }, + { + []interface{}{[]byte("hello"), nil, []byte("world")}, + nil, + true, + [][]byte{[]byte("hello"), nil, []byte("world")}, + }, + { + []interface{}{[]byte("hello"), nil, []byte("world")}, + nil, + true, + []string{"hello", "", "world"}, + }, + { + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, + nil, + true, + []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, + }, + { + []interface{}{[]byte("a1"), []byte("b1")}, + nil, + false, + []struct{ A, B, C string }{{"a1", "b1", ""}}, + }, + { + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, + nil, + true, + []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, + }, + { + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, + []string{"A", "B"}, + true, + []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}}, + }, + { + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, + nil, + false, + []struct{}{}, + }, +} + +func TestScanSlice(t *testing.T) { + for _, tt := range scanSliceTests { + + typ := reflect.ValueOf(tt.dest).Type() + dest := reflect.New(typ) + + err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...) + if tt.ok != (err == nil) { + t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err) + continue + } + if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) { + t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest) + } + } +} + +func ExampleScanSlice() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + c.Send("HMSET", "album:1", "title", "Red", "rating", 5) + c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) + c.Send("HMSET", "album:3", "title", "Beat", "rating", 4) + c.Send("LPUSH", "albums", "1") + c.Send("LPUSH", "albums", "2") + c.Send("LPUSH", "albums", "3") + values, err := redis.Values(c.Do("SORT", "albums", + "BY", "album:*->rating", + "GET", "album:*->title", + "GET", "album:*->rating")) + if err != nil { + panic(err) + } + + var albums []struct { + Title string + Rating int + } + if err := redis.ScanSlice(values, &albums); err != nil { + panic(err) + } + fmt.Printf("%v\n", albums) + // Output: + // [{Earthbound 1} {Beat 4} {Red 5}] +} + +var argsTests = []struct { + title string + actual redis.Args + expected redis.Args +}{ + {"struct ptr", + redis.Args{}.AddFlat(&struct { + I int `redis:"i"` + U uint `redis:"u"` + S string `redis:"s"` + P []byte `redis:"p"` + Bt bool + Bf bool + }{ + -1234, 5678, "hello", []byte("world"), true, false, + }), + redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "Bt", true, "Bf", false}, + }, + {"struct", + redis.Args{}.AddFlat(struct{ I int }{123}), + redis.Args{"I", 123}, + }, + {"slice", + redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2), + redis.Args{1, "a", "b", "c", 2}, + }, +} + +func TestArgs(t *testing.T) { + for _, tt := range argsTests { + if !reflect.DeepEqual(tt.actual, tt.expected) { + t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected) + } + } +} + +func ExampleArgs() { + c, err := dial() + if err != nil { + panic(err) + } + defer c.Close() + + var p1, p2 struct { + Title string `redis:"title"` + Author string `redis:"author"` + Body string `redis:"body"` + } + + p1.Title = "Example" + p1.Author = "Gary" + p1.Body = "Hello" + + if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil { + panic(err) + } + + m := map[string]string{ + "title": "Example2", + "author": "Steve", + "body": "Map", + } + + if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil { + panic(err) + } + + for _, id := range []string{"id1", "id2"} { + + v, err := redis.Values(c.Do("HGETALL", id)) + if err != nil { + panic(err) + } + + if err := redis.ScanStruct(v, &p2); err != nil { + panic(err) + } + + fmt.Printf("%+v\n", p2) + } + + // Output: + // {Title:Example Author:Gary Body:Hello} + // {Title:Example2 Author:Steve Body:Map} +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go new file mode 100644 index 0000000..78605a9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script.go @@ -0,0 +1,86 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "crypto/sha1" + "encoding/hex" + "io" + "strings" +) + +// Script encapsulates the source, hash and key count for a Lua script. See +// http://redis.io/commands/eval for information on scripts in Redis. +type Script struct { + keyCount int + src string + hash string +} + +// NewScript returns a new script object. If keyCount is greater than or equal +// to zero, then the count is automatically inserted in the EVAL command +// argument list. If keyCount is less than zero, then the application supplies +// the count as the first value in the keysAndArgs argument to the Do, Send and +// SendHash methods. +func NewScript(keyCount int, src string) *Script { + h := sha1.New() + io.WriteString(h, src) + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} +} + +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { + var args []interface{} + if s.keyCount < 0 { + args = make([]interface{}, 1+len(keysAndArgs)) + args[0] = spec + copy(args[1:], keysAndArgs) + } else { + args = make([]interface{}, 2+len(keysAndArgs)) + args[0] = spec + args[1] = s.keyCount + copy(args[2:], keysAndArgs) + } + return args +} + +// Do evaluates the script. Under the covers, Do optimistically evaluates the +// script using the EVALSHA command. If the command fails because the script is +// not loaded, then Do evaluates the script using the EVAL command (thus +// causing the script to load). +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) + } + return v, err +} + +// SendHash evaluates the script without waiting for the reply. The script is +// evaluated with the EVALSHA command. The application must ensure that the +// script is loaded by a previous call to Send, Do or Load methods. +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) +} + +// Send evaluates the script without waiting for the reply. +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) +} + +// Load loads the script without evaluating it. +func (s *Script) Load(c Conn) error { + _, err := c.Do("SCRIPT", "LOAD", s.src) + return err +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go new file mode 100644 index 0000000..c9635bf --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/script_test.go @@ -0,0 +1,93 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis_test + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/garyburd/redigo/internal/redistest" + "github.com/garyburd/redigo/redis" +) + +func ExampleScript(c redis.Conn, reply interface{}, err error) { + // Initialize a package-level variable with a script. + var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`) + + // In a function, use the script Do method to evaluate the script. The Do + // method optimistically uses the EVALSHA command. If the script is not + // loaded, then the Do method falls back to the EVAL command. + reply, err = getScript.Do(c, "foo") +} + +func TestScript(t *testing.T) { + c, err := redistest.Dial() + if err != nil { + t.Fatalf("error connection to database, %v", err) + } + defer c.Close() + + // To test fall back in Do, we make script unique by adding comment with current time. + script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano()) + s := redis.NewScript(2, script) + reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")} + + v, err := s.Do(c, "key1", "key2", "arg1", "arg2") + if err != nil { + t.Errorf("s.Do(c, ...) returned %v", err) + } + + if !reflect.DeepEqual(v, reply) { + t.Errorf("s.Do(c, ..); = %v, want %v", v, reply) + } + + err = s.Load(c) + if err != nil { + t.Errorf("s.Load(c) returned %v", err) + } + + err = s.SendHash(c, "key1", "key2", "arg1", "arg2") + if err != nil { + t.Errorf("s.SendHash(c, ...) returned %v", err) + } + + err = c.Flush() + if err != nil { + t.Errorf("c.Flush() returned %v", err) + } + + v, err = c.Receive() + if !reflect.DeepEqual(v, reply) { + t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply) + } + + err = s.Send(c, "key1", "key2", "arg1", "arg2") + if err != nil { + t.Errorf("s.Send(c, ...) returned %v", err) + } + + err = c.Flush() + if err != nil { + t.Errorf("c.Flush() returned %v", err) + } + + v, err = c.Receive() + if !reflect.DeepEqual(v, reply) { + t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply) + } + +} diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go new file mode 100644 index 0000000..b959a11 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/test_test.go @@ -0,0 +1,38 @@ +// Copyright 2012 Gary Burd +// +// 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. + +package redis + +import ( + "bufio" + "net" + "time" +) + +func SetNowFunc(f func() time.Time) { + nowFunc = f +} + +type nopCloser struct{ net.Conn } + +func (nopCloser) Close() error { return nil } + +// NewConnBufio is a hook for tests. +func NewConnBufio(rw bufio.ReadWriter) Conn { + return &conn{br: rw.Reader, bw: rw.Writer, conn: nopCloser{}} +} + +var ( + ErrNegativeInt = errNegativeInt +) diff --git a/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go new file mode 100644 index 0000000..1d86ee6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/garyburd/redigo/redis/zpop_example_test.go @@ -0,0 +1,113 @@ +// Copyright 2013 Gary Burd +// +// 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. + +package redis_test + +import ( + "fmt" + "github.com/garyburd/redigo/redis" +) + +// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. +func zpop(c redis.Conn, key string) (result string, err error) { + + defer func() { + // Return connection to normal state on error. + if err != nil { + c.Do("DISCARD") + } + }() + + // Loop until transaction is successful. + for { + if _, err := c.Do("WATCH", key); err != nil { + return "", err + } + + members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) + if err != nil { + return "", err + } + if len(members) != 1 { + return "", redis.ErrNil + } + + c.Send("MULTI") + c.Send("ZREM", key, members[0]) + queued, err := c.Do("EXEC") + if err != nil { + return "", err + } + + if queued != nil { + result = members[0] + break + } + } + + return result, nil +} + +// zpopScript pops a value from a ZSET. +var zpopScript = redis.NewScript(1, ` + local r = redis.call('ZRANGE', KEYS[1], 0, 0) + if r ~= nil then + r = r[1] + redis.call('ZREM', KEYS[1], r) + end + return r +`) + +// This example implements ZPOP as described at +// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. +func Example_zpop() { + c, err := dial() + if err != nil { + fmt.Println(err) + return + } + defer c.Close() + + // Add test data using a pipeline. + + for i, member := range []string{"red", "blue", "green"} { + c.Send("ZADD", "zset", i, member) + } + if _, err := c.Do(""); err != nil { + fmt.Println(err) + return + } + + // Pop using WATCH/MULTI/EXEC + + v, err := zpop(c, "zset") + if err != nil { + fmt.Println(err) + return + } + fmt.Println(v) + + // Pop using a script. + + v, err = redis.String(zpopScript.Do(c, "zset")) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(v) + + // Output: + // red + // blue +} diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/LICENSE b/Godeps/_workspace/src/github.com/gokyle/hotp/LICENSE new file mode 100644 index 0000000..208158a --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/README.md b/Godeps/_workspace/src/github.com/gokyle/hotp/README.md new file mode 100644 index 0000000..b81a5ac --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/README.md @@ -0,0 +1,192 @@ +## `hotp` + +This package implements the RFC 4226 OATH-HOTP algorithm; these +passwords derived from the HMAC-SHA1 of an internal counter. They +are presented as (typically) 6 or 8-digit numeric passphrases. + +This package was designed to be interoperable with the Google +Authenticator app and YubiKeys programmed in OATH-HOTP mode. + +Also provided is the `hotpgen` command-line program. This generates +a QR code suitable for use with the Google Authenticator application +alongside a text file containing the URL for the QR code. For more +information, see the README in the file. + +See also the [godocs](https://godoc.org/github.com/gokyle/hotp/) +for this package. The [hotpweb](https://github.com/gokyle/hotpweb/) +package provides a simple webapp demonstrating the use of the Google +Authenticator interaction. + + +### Storing Keys + +> **These keys are cryptographic secrets.** Please store them with +> all due caution! For example, they could be encrypted in a database +> using [cryptobox](https://github.com/cryptobox/gocryptobox/). + +The HOTP keys can be serialised with the `Marshal` function; this preserves +a "snapshot", so to speak, of the key value. Serialisation is done +in DER-format: + +``` +SEQUENCE { + OCTET STRING + INTEGER + INTEGER +} +``` + +Serialised key values can be parsed with the `Unmarshal` function; +as a serialised key value is a snapshot, the counter state at the +time of marshalling will be restored. + +If the key values are to be stored in a database, the key and counter +values must be preserved. To avoid any potention issues, the counter +value should be stored using the `Counter` method (i.e., as an +`uint64`) and key values restored with `NewHOTP`. *It is strongly +recommended that the `Key` field be stored securely.* The `Digit` +field can be stored as constant in the program, and used whenever +key values are loaded. + + +### Example Usages + +#### Case 1: Google Authenticator + +A server that wants to generate a new HOTP authentication for users +can generate a new random HOTP source; this example saves a QR code +to a file. The user can scan this QR code in with the app on their +phone and use it to generate codes for the server. + + // Generate a new, random 6-digit HOTP source with an initial + // counter of 0 (the second argument, if true, will randomise + // the counter). + otp, err := GenerateHOTP(6, false) + if err != nil { + // error handling elided + } + + qrCode, err := otp.QR("user@example.net") + if err != nil { + // error handling elided + } + + err = ioutil.WriteFile("user@example.net.png", qrCode + if err != nil { + // error handling elided + } + +After the user has imported this QR code, they can immediately begin +using codes for it. The `Check` method on an OTP source will check +whether the code is valid; if it isn't, the counter won't be +decremented to prevent the server from falling out of sync. If the +either side is suspected of falling out of sync, the `Scan` method +will look ahead a certain window of values. If it finds a valid +value, the counter is updated and the two will be in sync again. + +The Google Authenticator app on Android also provides users a means +to "Check key value"; the `IntegrityCheck` method will provide the +the two values shown here (the initial code and the current counter) +that may be used to verify the integrity of the key value. + +In the `testdata` directory, there are three files with the base name +of "gauth_example" that contain the HOTP key values used in the +test suite. The PNG image may be scanned in using a mobile phone, +the text file contains the URL that the QR code is based on, and +the `.key` file may be used with the `hotpcli` program. The first +several codes produced by this URL are: + +* 023667, counter = 0 +* 641344, counter = 1 +* 419615, counter = 2 +* 692589, counter = 3 +* 237233, counter = 4 +* 711695, counter = 5 +* 620195, counter = 6 + +The codes may be checked against the app to ensure they are correct; +these values are used in the test suite to ensure interoperability. + + +#### Case 2: YubiKey + +A YubiKey programmed in "OATH-HOTP" mode can also be used with this +package. The YubiKey user will need to provide their key, and +optionally their token identifier for additional security. If the +token is used across multiple sites, the `Scan` function will need +to be used (with a probably generous window) to sync the counters +initially. + +When reading input from a YubiKey, the `YubiKey` method takes as +input the output directly from the token, and splits it into the +code, the token identity, and a boolean indicating whether it was +valid output. Note that `YubiKey` **does not** check whether the +code is correct; this ensures that the user can check the code with +whatever means is appropriate (i.e. `Scan` or `Check`). + +In the `testdata` directory, there is a configuration file containing +the paramters for the YubiKey used to generate the test suite. This +may be used to program a YubiKey to verify the package's interoperability. +The first several codes produced by this configuration are (split +into raw output from yubikey / the code / the counter): + +* cccc52345777705179, 705179, counter = 0 +* cccc52345777404068, 404068, counter = 1 +* cccc52345777490143, 490143, counter = 2 +* cccc52345777739740, 739740, counter = 3 +* cccc52345777043269, 043269, counter = 4 +* cccc52345777035666, 035666, counter = 5 +* cccc52345777725326, 725325, counter = 6 + + +### TODO + +* Add example code. + + +### Test Coverage + +Test coverage is currently at 100%. + +#### Current test status + +[![Build Status](https://drone.io/github.com/gokyle/hotp/status.png)](https://drone.io/github.com/gokyle/hotp/latest) + + +### References + +* [RFC 4226 - *HOTP: An HMAC-Based One-Time Password Algorithm*](http://www.ietf.org/rfc/rfc4226.txt) +is the specification of the OATH-HOTP algorithm. A copy is provided +in the package's source directory. + +* The [Key URI Format](https://code.google.com/p/google-authenticator/wiki/KeyUriFormat) +page on the Google Authenticator wiki documents the URI format for +use with the Google Authenticator app. This package follows that +format when generating URLs (and by extension, QR codes). + +* The [YubiKey manual](http://www.yubico.com/wp-content/uploads/2013/07/YubiKey-Manual-v3_1.pdf) +contains documentation on the YubiKey HOTP format. + + +### Author + +`hotp` was written by Kyle Isom . + + +### License + +``` +Copyright (c) 2013 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +``` diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/doc.go b/Godeps/_workspace/src/github.com/gokyle/hotp/doc.go new file mode 100644 index 0000000..721b2ee --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/doc.go @@ -0,0 +1,27 @@ +/* +Package hotp implements the RFC 4226 OATH-HOTP algorithm; these +passwords derived from the HMAC-SHA1 of an internal counter. They +are presented as (typically) 6 or 8-digit numeric passphrases. + +The package provides facilities for interacting with YubiKeys +programmed in OATH-HOTP mode, as well as with the Google +Authenticator application. The package also provides QR-code +generation for new OTPs. +*/ +package hotp + +/* + Copyright (c) 2013 Kyle Isom + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotp.go b/Godeps/_workspace/src/github.com/gokyle/hotp/hotp.go new file mode 100644 index 0000000..230b726 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotp.go @@ -0,0 +1,315 @@ +package hotp + +import ( + "code.google.com/p/rsc/qr" + "crypto/hmac" + "crypto/rand" + "crypto/sha1" + "crypto/subtle" + "encoding/asn1" + "encoding/base32" + "encoding/binary" + "errors" + "fmt" + "io" + "math" + "math/big" + "net/url" + "strconv" +) + +// RFC 4226 specifies the counter as being 8 bytes. +const ctrSize = 8 + +// ErrInvalidHOTPURL is returned via FromURL; it indicates a malformed +// HOTP otpauth URL. +var ErrInvalidHOTPURL = errors.New("hotp: invalid HOTP url") + +// PRNG is the source of random data; this is used by GenerateHOTP +// and should be a cryptographically-secure PRNG. +var PRNG = rand.Reader + +// HOTP represents a new key value for generating one-time passwords; +// it contains the key used to construct one-time passwords and the +// counter state used in the OTP generation. Digits contains the +// number of digits that generated OTPs should output. Key is a +// cryptographic secret, and should be treated as such. +type HOTP struct { + Key []byte + counter *[ctrSize]byte + Digits int +} + +// Counter returns the HOTP's 8-byte counter as an unsigned 64-bit +// integer. +func (otp HOTP) Counter() uint64 { + counter := binary.BigEndian.Uint64(otp.counter[:]) + return counter +} + +// Increment will increment an HOTP source's counter. This is useful +// for providers like the Google Authenticator app, which immediately +// increments the counter and uses the 0 counter value as an integrity +// check. +func (otp HOTP) Increment() { + for i := ctrSize - 1; i >= 0; i-- { + if otp.counter[i]++; otp.counter[i] != 0 { + return + } + } +} + +// OTP generates a new one-time password. +func (otp HOTP) OTP() string { + h := hmac.New(sha1.New, otp.Key) + h.Write(otp.counter[:]) + otp.Increment() + hash := h.Sum(nil) + result := truncate(hash) + + mod := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(otp.Digits)), nil) + mod = mod.Mod(big.NewInt(result), mod) + fmtStr := fmt.Sprintf("%%0%dd", otp.Digits) + return fmt.Sprintf(fmtStr, mod.Uint64()) +} + +func (otp *HOTP) setCounter(counter uint64) bool { + if otp.counter == nil { + otp.counter = new([ctrSize]byte) + } + binary.BigEndian.PutUint64(otp.counter[:], counter) + return true +} + +// NewHOTP intialises a new HOTP instance with the key and counter +// values. No check is done on the digits, but typical values are 6 +// and 8. +func NewHOTP(key []byte, counter uint64, digits int) *HOTP { + otp := &HOTP{ + Key: key, + Digits: digits, + } + otp.counter = new([ctrSize]byte) + binary.BigEndian.PutUint64(otp.counter[:], counter) + + return otp +} + +// URL returns a suitable URL, such as for the Google Authenticator +// app. The label is used by these apps to identify the service to +// which this OTP belongs. The digits value is ignored by the Google +// authenticator app, and is therefore elided in the resulting URL. +func (otp *HOTP) URL(label string) string { + secret := base32.StdEncoding.EncodeToString(otp.Key) + u := url.URL{} + v := url.Values{} + u.Scheme = "otpauth" + u.Host = "hotp" + u.Path = label + v.Add("secret", secret) + v.Add("counter", fmt.Sprintf("%d", otp.Counter())) + u.RawQuery = v.Encode() + return u.String() +} + +// QR generates a byte slice containing the a QR code encoded as a +// PNG with level Q error correction. +func (otp *HOTP) QR(label string) ([]byte, error) { + u := otp.URL(label) + code, err := qr.Encode(u, qr.Q) + if err != nil { + return nil, err + } + return code.PNG(), nil +} + +// truncate contains the DT function from the RFC; this is used to +// deterministically select a sequence of 4 bytes from the HMAC +// counter hash. +func truncate(in []byte) int64 { + offset := int(in[len(in)-1] & 0xF) + p := in[offset : offset+4] + var binCode int32 + binCode = int32((p[0] & 0x7f)) << 24 + binCode += int32((p[1] & 0xff)) << 16 + binCode += int32((p[2] & 0xff)) << 8 + binCode += int32((p[3] & 0xff)) + return int64(binCode) & 0x7FFFFFFF +} + +// FromURL parses a new HOTP from a URL string. It returns the OTP, +// the label associated with the OTP, and any errors that occurred. +func FromURL(urlString string) (*HOTP, string, error) { + u, err := url.Parse(urlString) + if err != nil { + return nil, "", err + } + + if u.Scheme != "otpauth" { + return nil, "", ErrInvalidHOTPURL + } else if u.Host != "hotp" { + return nil, "", ErrInvalidHOTPURL + } + + v := u.Query() + if len(v) == 0 { + return nil, "", ErrInvalidHOTPURL + } + if v.Get("secret") == "" { + return nil, "", ErrInvalidHOTPURL + } else if algo := v.Get("algorithm"); algo != "" && algo != "SHA1" { + return nil, "", ErrInvalidHOTPURL + } + + var identity string + if len(u.Path) > 1 { + identity = u.Path[1:] + } + + var counter uint64 + if ctr := v.Get("counter"); ctr != "" { + counter, err = strconv.ParseUint(ctr, 10, 64) + if err != nil { + return nil, "", ErrInvalidHOTPURL + } + } + + secret, err := base32.StdEncoding.DecodeString(v.Get("secret")) + if err != nil { + return nil, "", ErrInvalidHOTPURL + } + + var digits int64 = 6 + if v.Get("digits") != "" { + digits, err = strconv.ParseInt(v.Get("digits"), 10, 8) + if err != nil { + return nil, "", ErrInvalidHOTPURL + } + } + + otp := NewHOTP(secret, counter, int(digits)) + return otp, identity, nil +} + +// GenerateHOTP will generate a randomised HOTP source; if the +// randCounter parameter is true, the counter will be randomised. +func GenerateHOTP(digits int, randCounter bool) (*HOTP, error) { + key := make([]byte, sha1.Size) + _, err := io.ReadFull(PRNG, key) + if err != nil { + return nil, err + } + + var counter uint64 + if randCounter { + ctr, err := rand.Int(PRNG, big.NewInt(int64(math.MaxInt64))) + if err != nil { + return nil, err + } + counter = ctr.Uint64() + } + + return NewHOTP(key, counter, digits), nil +} + +// YubiKey reads an OATH-HOTP string as returned by a YubiKey, and +// returns three values. The first value contains the actual OTP, the +// second value contains the YubiKey's token identifier, and the final +// value indicates whether the input string was a valid YubiKey +// OTP. This does not check whether the code is correct or not, it +// only ensures that it is well-formed output from a token and +// splits the output into the code and the public identity. +func (otp *HOTP) YubiKey(in string) (string, string, bool) { + if len(in) < otp.Digits { + return "", "", false + } + + otpStart := len(in) - otp.Digits + code := in[otpStart:] + pubid := in[:otpStart] + return code, pubid, true +} + +// IntegrityCheck returns two values, the base OTP and the current +// counter. This is used, for example, with the Google Authenticator +// app's "Check key value" function and can be used to verify that +// the application and the provider are in sync. +func (otp *HOTP) IntegrityCheck() (string, uint64) { + h := hmac.New(sha1.New, otp.Key) + counter := make([]byte, 8) + h.Write(counter) + hash := h.Sum(nil) + result := truncate(hash) + + mod := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(otp.Digits)), nil) + mod = mod.Mod(big.NewInt(result), mod) + fmtStr := fmt.Sprintf("%%0%dd", otp.Digits) + return fmt.Sprintf(fmtStr, mod.Uint64()), otp.Counter() +} + +// Scan takes a code input (i.e. from the user), and scans ahead +// within a certain window of counter values. This can be used in the +// case where the server's counter and the user's counter have fallen +// out of sync. +func (otp *HOTP) Scan(code string, window int) bool { + var valid bool + codeBytes := []byte(code) + counter := otp.Counter() + + for i := 0; i < window; i++ { + genCode := []byte(otp.OTP()) + if subtle.ConstantTimeCompare(codeBytes, genCode) == 1 { + valid = true + break + } + } + if !valid { + otp.setCounter(counter) + } + return valid +} + +// Check takes an input code and verifies it against the OTP. If +// successful, the counter is incremented. +func (otp *HOTP) Check(code string) bool { + codeBytes := []byte(code) + genCode := []byte(otp.OTP()) + if subtle.ConstantTimeCompare(codeBytes, genCode) != 1 { + otp.setCounter(otp.Counter() - 1) + return false + } + return true +} + +// Marshal serialises an HOTP key value as a DER-encoded byte slice. +func Marshal(otp *HOTP) ([]byte, error) { + var asnHOTP struct { + Key []byte + Counter *big.Int + Digits int + } + asnHOTP.Key = otp.Key[:] + asnHOTP.Counter = new(big.Int).SetUint64(otp.Counter()) + asnHOTP.Digits = otp.Digits + return asn1.Marshal(asnHOTP) +} + +// Unmarshal parses a DER-encoded serialised HOTP key value. +func Unmarshal(in []byte) (otp *HOTP, err error) { + var asnHOTP struct { + Key []byte + Counter *big.Int + Digits int + } + _, err = asn1.Unmarshal(in, &asnHOTP) + if err != nil { + return + } + + otp = &HOTP{ + Key: asnHOTP.Key[:], + Digits: asnHOTP.Digits, + } + otp.setCounter(asnHOTP.Counter.Uint64()) + return +} diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotp_test.go b/Godeps/_workspace/src/github.com/gokyle/hotp/hotp_test.go new file mode 100644 index 0000000..36b2c11 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotp_test.go @@ -0,0 +1,452 @@ +package hotp + +import "fmt" +import "testing" +import "bytes" +import "io" + +var testKey = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20} + +func newZeroHOTP() *HOTP { + return NewHOTP(testKey, 0, 6) +} + +// This test verifies that the counter increment works as expected. As per the RFC, +// the counter should be treated as an 8-byte big-endian unsigned integer. +func TestIncrement(t *testing.T) { + otp := newZeroHOTP() + for i := 0; i < 32; i++ { + if otp.Counter() != uint64(i) { + fmt.Printf("hotp: counter should be %d, is %d\n", + i, otp.Counter()) + fmt.Printf("\tcounter state: %x\n", *otp.counter) + t.FailNow() + } + otp.Increment() + } +} + +var sha1Hmac = []byte{ + 0x1f, 0x86, 0x98, 0x69, 0x0e, + 0x02, 0xca, 0x16, 0x61, 0x85, + 0x50, 0xef, 0x7f, 0x19, 0xda, + 0x8e, 0x94, 0x5b, 0x55, 0x5a, +} + +var truncExpect int64 = 0x50ef7f19 + +// This test runs through the truncation example given in the RFC. +func TestTruncate(t *testing.T) { + if result := truncate(sha1Hmac); result != truncExpect { + fmt.Printf("hotp: expected truncate -> %d, saw %d\n", + truncExpect, result) + t.FailNow() + } + + sha1Hmac[19]++ + if result := truncate(sha1Hmac); result == truncExpect { + fmt.Println("hotp: expected truncation to fail") + t.FailNow() + } +} + +var rfcKey = []byte("12345678901234567890") +var rfcExpected = []string{ + "755224", + "287082", + "359152", + "969429", + "338314", + "254676", + "287922", + "162583", + "399871", + "520489", +} + +// This test runs through the test cases presented in the RFC, and +// ensures that this implementation is in compliance. +func TestRFC(t *testing.T) { + otp := NewHOTP(rfcKey, 0, 6) + for i := 0; i < len(rfcExpected); i++ { + code := otp.OTP() + if code == "" { + fmt.Printf("hotp: failed to produce an OTP\n") + t.FailNow() + } else if code != rfcExpected[i] { + fmt.Printf("hotp: invalid OTP\n") + fmt.Printf("\tExpected: %s\n", rfcExpected[i]) + fmt.Printf("\t Actual: %s\n", code) + t.FailNow() + } + } +} + +// This test uses a different key than the test cases in the RFC, +// but runs through the same test cases to ensure that they fail as +// expected. +func TestBadRFC(t *testing.T) { + otp := NewHOTP(testKey, 0, 6) + for i := 0; i < len(rfcExpected); i++ { + code := otp.OTP() + if code == "" { + fmt.Printf("hotp: failed to produce an OTP\n") + t.FailNow() + } else if code == rfcExpected[i] { + fmt.Printf("hotp: should not have received a valid OTP\n") + t.FailNow() + } + } +} + +// This test takes input from a test YubiKey and ensures that the +// YubiKey functionality works as expected. +func TestYubiKey(t *testing.T) { + ykKey := []byte{ + 0xd4, 0xbe, 0x97, 0xac, 0xe3, + 0x31, 0x72, 0x95, 0xd8, 0x95, + 0xeb, 0xd6, 0xb2, 0xec, 0xa6, + 0x78, 0x49, 0x79, 0x4d, 0xb3, + } + otp := NewHOTP(ykKey, 0, 6) + out := []string{ + "cccc52345777705179", + "cccc52345777404068", + "cccc52345777490143", + "cccc52345777739740", + "cccc52345777043269", + "cccc52345777035666", + "cccc52345777725326", + } + + codes := []string{ + "705179", + "404068", + "490143", + "739740", + "043269", + "035666", + "725326", + } + + ykpub := "cccc52345777" + + if _, _, ok := otp.YubiKey("abcd"); ok { + fmt.Println("hotp: accepted invalid YubiKey input") + t.FailNow() + } + + for i := 0; i < len(out); i++ { + code := otp.OTP() + if ykCode, pubid, ok := otp.YubiKey(out[i]); !ok { + fmt.Printf("hotp: invalid YubiKey OTP\n") + t.FailNow() + } else if ykCode != code && code != codes[i] { + fmt.Printf("hotp: YubiKey did not produce valid OTP\n") + t.FailNow() + } else if ykpub != pubid { + fmt.Printf("hotp: invalid YubiKey public ID\n") + t.FailNow() + } + } + + code, counter := otp.IntegrityCheck() + if code != codes[0] { + fmt.Println("hotp: YubiKey integrity check fails (bad code)") + t.FailNow() + } else if counter != uint64(len(out)) { + fmt.Println("hotp: YubiKey integrity check fails (bad counter)") + t.FailNow() + } +} + +// This test generates a new HOTP, outputs the URL for that HOTP, +// and attempts to parse that URL. It verifies that the two HOTPs +// are the same, and that they produce the same output. +func TestURL(t *testing.T) { + var ident = "testuser@foo" + otp := NewHOTP(testKey, 0, 6) + url := otp.URL("testuser@foo") + otp2, id, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse HOTP URL\n") + t.FailNow() + } else if id != ident { + fmt.Printf("hotp: bad label\n") + fmt.Printf("\texpected: %s\n", ident) + fmt.Printf("\t actual: %s\n", id) + t.FailNow() + } else if otp2.Counter() != otp.Counter() { + fmt.Printf("hotp: OTP counters aren't synced\n") + fmt.Printf("\toriginal: %d\n", otp.Counter()) + fmt.Printf("\t second: %d\n", otp2.Counter()) + t.FailNow() + } + + code1 := otp.OTP() + code2 := otp2.OTP() + if code1 != code2 { + fmt.Printf("hotp: mismatched OTPs\n") + fmt.Printf("\texpected: %s\n", code1) + fmt.Printf("\t actual: %s\n", code2) + } + + // There's not much we can do test the QR code, except to + // ensure it doesn't fail. + _, err = otp.QR(ident) + if err != nil { + fmt.Printf("hotp: failed to generate QR code PNG (%v)\n", err) + t.FailNow() + } + + // This should fail because the maximum size of an alphanumeric + // QR code with the lowest-level of error correction should + // max out at 4296 bytes. 8k may be a bit overkill... but it + // gets the job done. The value is read from the PRNG to + // increase the likelihood that the returned data is + // uncompressible. + var tooBigIdent = make([]byte, 8192) + _, err = io.ReadFull(PRNG, tooBigIdent) + if err != nil { + fmt.Printf("hotp: failed to read identity (%v)\n", err) + t.FailNow() + } else if _, err = otp.QR(string(tooBigIdent)); err == nil { + fmt.Println("hotp: QR code should fail to encode oversized URL") + t.FailNow() + } +} + +// This test attempts a variety of invalid urls against the parser +// to ensure they fail. +func TestBadURL(t *testing.T) { + var urlList = []string{ + "http://google.com", + "", + "-", + "foo", + "otpauth:/foo/bar/baz", + "://", + "otpauth://totp/foo@bar?secret=ABCD", + "otpauth://hotp/secret=bar", + "otpauth://hotp/?secret=QUJDRA&algorithm=SHA256", + "otpauth://hotp/?digits=", + "otpauth://hotp/?secret=123", + "otpauth://hotp/?secret=MFRGGZDF&digits=ABCD", + "otpauth://hotp/?secret=MFRGGZDF&counter=ABCD", + } + + for i := range urlList { + if _, _, err := FromURL(urlList[i]); err == nil { + fmt.Println("hotp: URL should not have parsed successfully") + fmt.Printf("\turl was: %s\n", urlList[i]) + t.FailNow() + } + } +} + +// This test uses a url generated with the `hotpgen` tool; this url +// was imported into the Google Authenticator app and the resulting +// codes generated by the app are checked here to verify interoperability. +func TestGAuth(t *testing.T) { + url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L" + expected := []string{ + "023667", + "641344", + "419615", + "692589", + "237233", + "711695", + "620195", + } + + otp, label, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse HOTP URL\n") + t.FailNow() + } else if label != "kyle" { + fmt.Printf("hotp: invalid label") + t.FailNow() + } + otp.Increment() + + // Validate codes + for i := 1; i < len(expected); i++ { + code := otp.OTP() + if otp.Counter() != uint64(i+1) { + fmt.Printf("hotp: invalid OTP counter (should be %d but is %d)", + i, otp.Counter()) + t.FailNow() + } else if code != expected[i] { + fmt.Println("hotp: invalid OTP") + t.FailNow() + } + } + + // Validate integrity check + code, counter := otp.IntegrityCheck() + if code != expected[0] { + fmt.Println("hotp: invalid integrity code") + t.FailNow() + } else if counter != uint64(len(expected)) { + fmt.Println("hotp: invalid integrity counter") + t.FailNow() + } +} + +// This test verifies that a scan will successfully sync and update +// the OTP counter. +func TestScan(t *testing.T) { + url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L" + expected := []string{ + "023667", + "641344", + "419615", + "692589", + "237233", + "711695", + "620195", + } + + otp, _, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse HOTP URL\n") + t.FailNow() + } + + if !otp.Scan(expected[4], 10) { + fmt.Println("hotp: scan should have found code") + t.FailNow() + } + + if otp.Counter() != 5 { + fmt.Println("hotp: counter was not properly synced") + t.FailNow() + } +} + +// This test verifies that a scan that does not successfully sync +// will not update the OTP counter. +func TestBadScan(t *testing.T) { + url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L" + expected := []string{ + "023667", + "641344", + "419615", + "692589", + "237233", + "711695", + "620195", + } + + otp, _, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse HOTP URL\n") + t.FailNow() + } + + if otp.Scan(expected[6], 3) { + fmt.Println("hotp: scan should not have found code") + t.FailNow() + } + + if otp.Counter() != 0 { + fmt.Println("hotp: counter was not properly synced") + t.FailNow() + } +} + +// This test ensures that a valid code is recognised and increments +// the counter. +func TestCheck(t *testing.T) { + otp := NewHOTP(rfcKey, 0, 6) + if !otp.Check(rfcExpected[0]) { + fmt.Println("hotp: Check failed when it should have succeeded") + t.FailNow() + } + + if otp.Counter() != 1 { + fmt.Println("hotp: counter should have been incremented") + t.FailNow() + } +} + +// This test ensures that an invalid code is recognised as such and +// does not increment the counter. +func TestBadCheck(t *testing.T) { + otp := NewHOTP(rfcKey, 0, 6) + if otp.Check(rfcExpected[1]) { + fmt.Println("hotp: Check succeeded when it should have failed") + t.FailNow() + } + + if otp.Counter() != 0 { + fmt.Println("hotp: counter should not have been incremented") + t.FailNow() + } +} + +func TestSerialisation(t *testing.T) { + otp := NewHOTP(rfcKey, 123456, 8) + out, err := Marshal(otp) + if err != nil { + fmt.Printf("hotp: failed to marshal HOTP (%v)\n", err) + t.FailNow() + } + + otp2, err := Unmarshal(out) + if err != nil { + fmt.Printf("hotp: failed to unmarshal HOTP (%v)\n", err) + t.FailNow() + } + + if otp.Counter() != otp2.Counter() { + fmt.Println("hotp: serialisation failed to preserve counter") + t.FailNow() + } + + if _, err = Unmarshal([]byte{0x0}); err == nil { + fmt.Println("hotp: Unmarshal should have failed") + t.FailNow() + } +} + +func TestGenerateHOTP(t *testing.T) { + _, err := GenerateHOTP(6, false) + if err != nil { + fmt.Printf("hotp: failed to generate random key value (%v)\n", err) + t.FailNow() + } + + _, err = GenerateHOTP(8, true) + if err != nil { + fmt.Printf("hotp: failed to generate random key value (%v)\n", err) + t.FailNow() + } + + PRNG = new(bytes.Buffer) + _, err = GenerateHOTP(8, true) + if err == nil { + fmt.Println("hotp: should have failed to generate random key value") + t.FailNow() + } + + // should read just enough for the key + PRNG = bytes.NewBufferString("abcdeabcdeabcdeabcde") + _, err = GenerateHOTP(8, true) + if err == nil { + fmt.Println("hotp: should have failed to generate random key value") + t.FailNow() + } + +} + +func BenchmarkFromURL(b *testing.B) { + url := "otpauth://hotp/kyle?counter=0&secret=EXZLUP7IGHQ673ZCP32RTLRU2N427Z6L" + for i := 0; i < b.N; i++ { + _, _, err := FromURL(url) + if err != nil { + fmt.Printf("hotp: failed to parse url (%v)\n", err) + b.FailNow() + } + } +} diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/README b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/README new file mode 100644 index 0000000..07dd3fa --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/README @@ -0,0 +1,43 @@ +hotpcli: OATH-HOTP command line tool + +hotpcli reads the DER-encoded key file specified and prints out the next code, +updating the key file with the new key. + + +Usage: + hotpcli [-cnw] [-k keyfile] [-u url] + -c Print out an integrity check; this + displays a baseline key value and + the current counter for synchronisation. + -k keyfile Specify the file containing the + key; defaults to hotp.key. + -n Don't update the counter in the key + file. + -u url An OATH-HOTP URL containing the + key-value information to use for + the key. + -w Write the url specified with -u to the + key file and exit. + +The program automatically updates the key file with the new counter; +the -n option will allow this behaviour to be overridden. To generate +a key from a url, pass the -w and -u options; the counter will not be +updated. + + +LICENSE + +Copyright (c) 2013 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/cli.go b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/cli.go new file mode 100644 index 0000000..77d448a --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpcli/cli.go @@ -0,0 +1,77 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gokyle/hotp" + "io/ioutil" +) + +func main() { + check := flag.Bool("c", false, "do integrity check") + noUpdate := flag.Bool("n", false, "don't update counter") + keyFile := flag.String("k", "hotp.key", "key file") + url := flag.String("u", "", "URL to load new key from") + write := flag.Bool("w", false, "only write URL-loaded key to file") + flag.Parse() + + var otp *hotp.HOTP + if *url != "" { + var err error + otp, _, err = hotp.FromURL(*url) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + + if *write { + out, err := hotp.Marshal(otp) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + + err = ioutil.WriteFile(*keyFile, out, 0600) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + + return + } + } else { + in, err := ioutil.ReadFile(*keyFile) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + + otp, err = hotp.Unmarshal(in) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + } + + if *check { + code, counter := otp.IntegrityCheck() + fmt.Println(" code:", code) + fmt.Println("counter:", counter) + } else { + fmt.Println(otp.OTP()) + } + + if !*noUpdate { + out, err := hotp.Marshal(otp) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + + err = ioutil.WriteFile(*keyFile, out, 0600) + if err != nil { + fmt.Printf("[!] %v\n", err.Error()) + return + } + } +} diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/LICENSE b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/LICENSE new file mode 100644 index 0000000..208158a --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2013 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/README b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/README new file mode 100644 index 0000000..6393689 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/README @@ -0,0 +1,42 @@ +hotpgen: generate new HOTP key values from the command line + +Usage: + + hotpgen [-d digits] [-p password] [-r] [label] + -d digits Specify the number of digits to + use, typically 6 or 8; defaults + to 6. + + -r Randomise the initial counter; + defaults to false as this is + not supported by all clients. + + label An optional label for the key; + for example, an email address + or username. + +The program will dump out three files; a PNG image, a text file, +and a DER-encoded key file that can be used with hotpcli. The text +file contains the URL encoded in the QR code; the QR code may be +displayed to users to scan into their mobile apps. If a label is +provided, the files are named by the label (e.g. label.png and +label.txt); otherwise, the files are named as the base32-encoded +URL. + + +LICENSE + +Copyright (c) 2013 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/hotpgen.go b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/hotpgen.go new file mode 100644 index 0000000..6cea5ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/hotpgen/hotpgen.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/base32" + "flag" + "fmt" + "github.com/gokyle/hotp" + "io/ioutil" +) + +func main() { + digits := flag.Int("d", 6, "number of digits") + doRand := flag.Bool("r", false, "randomise counter") + flag.Parse() + + var label string + if flag.NArg() == 1 { + label = flag.Arg(0) + } + + otp, err := hotp.GenerateHOTP(*digits, *doRand) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } + + url := otp.URL(label) + png, err := otp.QR(label) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } + + filename := label + if label == "" { + filename = base32.StdEncoding.EncodeToString([]byte(url)) + } + err = ioutil.WriteFile(filename+".png", png, 0644) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } + + err = ioutil.WriteFile(filename+".txt", []byte(url), 0644) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } + + keyFile, err := hotp.Marshal(otp) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } + err = ioutil.WriteFile(filename+".key", keyFile, 0644) + if err != nil { + fmt.Printf("! %v\n", err.Error()) + return + } +} diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/rfc4226.txt b/Godeps/_workspace/src/github.com/gokyle/hotp/rfc4226.txt new file mode 100644 index 0000000..b852364 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gokyle/hotp/rfc4226.txt @@ -0,0 +1,2075 @@ + + + + + + +Network Working Group D. M'Raihi +Request for Comments: 4226 VeriSign +Category: Informational M. Bellare + UCSD + F. Hoornaert + Vasco + D. Naccache + Gemplus + O. Ranen + Aladdin + December 2005 + + + HOTP: An HMAC-Based One-Time Password Algorithm + +Status of This Memo + + This memo provides information for the Internet community. It does + not specify an Internet standard of any kind. Distribution of this + memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2005). + +Abstract + + This document describes an algorithm to generate one-time password + values, based on Hashed Message Authentication Code (HMAC). A + security analysis of the algorithm is presented, and important + parameters related to the secure deployment of the algorithm are + discussed. The proposed algorithm can be used across a wide range of + network applications ranging from remote Virtual Private Network + (VPN) access, Wi-Fi network logon to transaction-oriented Web + applications. + + This work is a joint effort by the OATH (Open AuTHentication) + membership to specify an algorithm that can be freely distributed to + the technical community. The authors believe that a common and + shared algorithm will facilitate adoption of two-factor + authentication on the Internet by enabling interoperability across + commercial and open-source implementations. + + + + + + + + + +M'Raihi, et al. Informational [Page 1] + +RFC 4226 HOTP Algorithm December 2005 + + +Table of Contents + + 1. Overview ........................................................3 + 2. Introduction ....................................................3 + 3. Requirements Terminology ........................................4 + 4. Algorithm Requirements ..........................................4 + 5. HOTP Algorithm ..................................................5 + 5.1. Notation and Symbols .......................................5 + 5.2. Description ................................................6 + 5.3. Generating an HOTP Value ...................................6 + 5.4. Example of HOTP Computation for Digit = 6 ..................7 + 6. Security Considerations .........................................8 + 7. Security Requirements ...........................................9 + 7.1. Authentication Protocol Requirements .......................9 + 7.2. Validation of HOTP Values .................................10 + 7.3. Throttling at the Server ..................................10 + 7.4. Resynchronization of the Counter ..........................11 + 7.5. Management of Shared Secrets ..............................11 + 8. Composite Shared Secrets .......................................14 + 9. Bi-Directional Authentication ..................................14 + 10. Conclusion ....................................................15 + 11. Acknowledgements ..............................................15 + 12. Contributors ..................................................15 + 13. References ....................................................15 + 13.1. Normative References .....................................15 + 13.2. Informative References ...................................16 + Appendix A - HOTP Algorithm Security: Detailed Analysis ...........17 + A.1. Definitions and Notations .................................17 + A.2. The Idealized Algorithm: HOTP-IDEAL .......................17 + A.3. Model of Security .........................................18 + A.4. Security of the Ideal Authentication Algorithm ............19 + A.4.1. From Bits to Digits ................................19 + A.4.2. Brute Force Attacks ................................21 + A.4.3. Brute force attacks are the best possible attacks ..22 + A.5. Security Analysis of HOTP .................................23 + Appendix B - SHA-1 Attacks ........................................25 + B.1. SHA-1 Status ..............................................25 + B.2. HMAC-SHA-1 Status .........................................26 + B.3. HOTP Status ...............................................26 + Appendix C - HOTP Algorithm: Reference Implementation .............27 + Appendix D - HOTP Algorithm: Test Values ..........................32 + Appendix E - Extensions ...........................................33 + E.1. Number of Digits ..........................................33 + E.2. Alphanumeric Values .......................................33 + E.3. Sequence of HOTP values ...................................34 + E.4. A Counter-Based Resynchronization Method ..................34 + E.5. Data Field ................................................35 + + + + +M'Raihi, et al. Informational [Page 2] + +RFC 4226 HOTP Algorithm December 2005 + + +1. Overview + + The document introduces first the context around an algorithm that + generates one-time password values based on HMAC [BCK1] and, thus, is + named the HMAC-Based One-Time Password (HOTP) algorithm. In Section + 4, the algorithm requirements are listed and in Section 5, the HOTP + algorithm is described. Sections 6 and 7 focus on the algorithm + security. Section 8 proposes some extensions and improvements, and + Section 10 concludes this document. In Appendix A, the interested + reader will find a detailed, full-fledged analysis of the algorithm + security: an idealized version of the algorithm is evaluated, and + then the HOTP algorithm security is analyzed. + +2. Introduction + + Today, deployment of two-factor authentication remains extremely + limited in scope and scale. Despite increasingly higher levels of + threats and attacks, most Internet applications still rely on weak + authentication schemes for policing user access. The lack of + interoperability among hardware and software technology vendors has + been a limiting factor in the adoption of two-factor authentication + technology. In particular, the absence of open specifications has + led to solutions where hardware and software components are tightly + coupled through proprietary technology, resulting in high-cost + solutions, poor adoption, and limited innovation. + + In the last two years, the rapid rise of network threats has exposed + the inadequacies of static passwords as the primary mean of + authentication on the Internet. At the same time, the current + approach that requires an end user to carry an expensive, single- + function device that is only used to authenticate to the network is + clearly not the right answer. For two-factor authentication to + propagate on the Internet, it will have to be embedded in more + flexible devices that can work across a wide range of applications. + + The ability to embed this base technology while ensuring broad + interoperability requires that it be made freely available to the + broad technical community of hardware and software developers. Only + an open-system approach will ensure that basic two-factor + authentication primitives can be built into the next generation of + consumer devices such as USB mass storage devices, IP phones, and + personal digital assistants. + + One-Time Password is certainly one of the simplest and most popular + forms of two-factor authentication for securing network access. For + example, in large enterprises, Virtual Private Network access often + requires the use of One-Time Password tokens for remote user + authentication. One-Time Passwords are often preferred to stronger + + + +M'Raihi, et al. Informational [Page 3] + +RFC 4226 HOTP Algorithm December 2005 + + + forms of authentication such as Public-Key Infrastructure (PKI) or + biometrics because an air-gap device does not require the + installation of any client desktop software on the user machine, + therefore allowing them to roam across multiple machines including + home computers, kiosks, and personal digital assistants. + + This document proposes a simple One-Time Password algorithm that can + be implemented by any hardware manufacturer or software developer to + create interoperable authentication devices and software agents. The + algorithm is event-based so that it can be embedded in high-volume + devices such as Java smart cards, USB dongles, and GSM SIM cards. + The presented algorithm is made freely available to the developer + community under the terms and conditions of the IETF Intellectual + Property Rights [RFC3979]. + + The authors of this document are members of the Open AuTHentication + initiative [OATH]. The initiative was created in 2004 to facilitate + collaboration among strong authentication technology providers. + +3. Requirements Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + +4. Algorithm Requirements + + This section presents the main requirements that drove this algorithm + design. A lot of emphasis was placed on end-consumer usability as + well as the ability for the algorithm to be implemented by low-cost + hardware that may provide minimal user interface capabilities. In + particular, the ability to embed the algorithm into high-volume SIM + and Java cards was a fundamental prerequisite. + + R1 - The algorithm MUST be sequence- or counter-based: one of the + goals is to have the HOTP algorithm embedded in high-volume devices + such as Java smart cards, USB dongles, and GSM SIM cards. + + R2 - The algorithm SHOULD be economical to implement in hardware by + minimizing requirements on battery, number of buttons, computational + horsepower, and size of LCD display. + + R3 - The algorithm MUST work with tokens that do not support any + numeric input, but MAY also be used with more sophisticated devices + such as secure PIN-pads. + + R4 - The value displayed on the token MUST be easily read and entered + by the user: This requires the HOTP value to be of reasonable length. + + + +M'Raihi, et al. Informational [Page 4] + +RFC 4226 HOTP Algorithm December 2005 + + + The HOTP value must be at least a 6-digit value. It is also + desirable that the HOTP value be 'numeric only' so that it can be + easily entered on restricted devices such as phones. + + R5 - There MUST be user-friendly mechanisms available to + resynchronize the counter. Section 7.4 and Appendix E.4 details the + resynchronization mechanism proposed in this document + + R6 - The algorithm MUST use a strong shared secret. The length of + the shared secret MUST be at least 128 bits. This document + RECOMMENDs a shared secret length of 160 bits. + +5. HOTP Algorithm + + In this section, we introduce the notation and describe the HOTP + algorithm basic blocks -- the base function to compute an HMAC-SHA-1 + value and the truncation method to extract an HOTP value. + +5.1. Notation and Symbols + + A string always means a binary string, meaning a sequence of zeros + and ones. + + If s is a string, then |s| denotes its length. + + If n is a number, then |n| denotes its absolute value. + + If s is a string, then s[i] denotes its i-th bit. We start numbering + the bits at 0, so s = s[0]s[1]...s[n-1] where n = |s| is the length + of s. + + Let StToNum (String to Number) denote the function that as input a + string s returns the number whose binary representation is s. (For + example, StToNum(110) = 6.) + + Here is a list of symbols used in this document. + + Symbol Represents + ------------------------------------------------------------------- + C 8-byte counter value, the moving factor. This counter + MUST be synchronized between the HOTP generator (client) + and the HOTP validator (server). + + K shared secret between client and server; each HOTP + generator has a different and unique secret K. + + T throttling parameter: the server will refuse connections + from a user after T unsuccessful authentication attempts. + + + +M'Raihi, et al. Informational [Page 5] + +RFC 4226 HOTP Algorithm December 2005 + + + + s resynchronization parameter: the server will attempt to + verify a received authenticator across s consecutive + counter values. + + Digit number of digits in an HOTP value; system parameter. + +5.2. Description + + The HOTP algorithm is based on an increasing counter value and a + static symmetric key known only to the token and the validation + service. In order to create the HOTP value, we will use the HMAC- + SHA-1 algorithm, as defined in RFC 2104 [BCK2]. + + As the output of the HMAC-SHA-1 calculation is 160 bits, we must + truncate this value to something that can be easily entered by a + user. + + HOTP(K,C) = Truncate(HMAC-SHA-1(K,C)) + + Where: + + - Truncate represents the function that converts an HMAC-SHA-1 + value into an HOTP value as defined in Section 5.3. + + The Key (K), the Counter (C), and Data values are hashed high-order + byte first. + + The HOTP values generated by the HOTP generator are treated as big + endian. + +5.3. Generating an HOTP Value + + We can describe the operations in 3 distinct steps: + + Step 1: Generate an HMAC-SHA-1 value Let HS = HMAC-SHA-1(K,C) // HS + is a 20-byte string + + Step 2: Generate a 4-byte string (Dynamic Truncation) + Let Sbits = DT(HS) // DT, defined below, + // returns a 31-bit string + + Step 3: Compute an HOTP value + Let Snum = StToNum(Sbits) // Convert S to a number in + 0...2^{31}-1 + Return D = Snum mod 10^Digit // D is a number in the range + 0...10^{Digit}-1 + + + + +M'Raihi, et al. Informational [Page 6] + +RFC 4226 HOTP Algorithm December 2005 + + + The Truncate function performs Step 2 and Step 3, i.e., the dynamic + truncation and then the reduction modulo 10^Digit. The purpose of + the dynamic offset truncation technique is to extract a 4-byte + dynamic binary code from a 160-bit (20-byte) HMAC-SHA-1 result. + + DT(String) // String = String[0]...String[19] + Let OffsetBits be the low-order 4 bits of String[19] + Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15 + Let P = String[OffSet]...String[OffSet+3] + Return the Last 31 bits of P + + The reason for masking the most significant bit of P is to avoid + confusion about signed vs. unsigned modulo computations. Different + processors perform these operations differently, and masking out the + signed bit removes all ambiguity. + + Implementations MUST extract a 6-digit code at a minimum and possibly + 7 and 8-digit code. Depending on security requirements, Digit = 7 or + more SHOULD be considered in order to extract a longer HOTP value. + + The following paragraph is an example of using this technique for + Digit = 6, i.e., that a 6-digit HOTP value is calculated from the + HMAC value. + +5.4. Example of HOTP Computation for Digit = 6 + + The following code example describes the extraction of a dynamic + binary code given that hmac_result is a byte array with the HMAC- + SHA-1 result: + + int offset = hmac_result[19] & 0xf ; + int bin_code = (hmac_result[offset] & 0x7f) << 24 + | (hmac_result[offset+1] & 0xff) << 16 + | (hmac_result[offset+2] & 0xff) << 8 + | (hmac_result[offset+3] & 0xff) ; + + SHA-1 HMAC Bytes (Example) + + ------------------------------------------------------------- + | Byte Number | + ------------------------------------------------------------- + |00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19| + ------------------------------------------------------------- + | Byte Value | + ------------------------------------------------------------- + |1f|86|98|69|0e|02|ca|16|61|85|50|ef|7f|19|da|8e|94|5b|55|5a| + -------------------------------***********----------------++| + + + + +M'Raihi, et al. Informational [Page 7] + +RFC 4226 HOTP Algorithm December 2005 + + + * The last byte (byte 19) has the hex value 0x5a. + * The value of the lower 4 bits is 0xa (the offset value). + * The offset value is byte 10 (0xa). + * The value of the 4 bytes starting at byte 10 is 0x50ef7f19, + which is the dynamic binary code DBC1. + * The MSB of DBC1 is 0x50 so DBC2 = DBC1 = 0x50ef7f19 . + * HOTP = DBC2 modulo 10^6 = 872921. + + We treat the dynamic binary code as a 31-bit, unsigned, big-endian + integer; the first byte is masked with a 0x7f. + + We then take this number modulo 1,000,000 (10^6) to generate the 6- + digit HOTP value 872921 decimal. + +6. Security Considerations + + The conclusion of the security analysis detailed in the Appendix is + that, for all practical purposes, the outputs of the Dynamic + Truncation (DT) on distinct counter inputs are uniformly and + independently distributed 31-bit strings. + + The security analysis then details the impact of the conversion from + a string to an integer and the final reduction modulo 10^Digit, where + Digit is the number of digits in an HOTP value. + + The analysis demonstrates that these final steps introduce a + negligible bias, which does not impact the security of the HOTP + algorithm, in the sense that the best possible attack against the + HOTP function is the brute force attack. + + Assuming an adversary is able to observe numerous protocol exchanges + and collect sequences of successful authentication values. This + adversary, trying to build a function F to generate HOTP values based + on his observations, will not have a significant advantage over a + random guess. + + The logical conclusion is simply that the best strategy will once + again be to perform a brute force attack to enumerate and try all the + possible values. + + Considering the security analysis in the Appendix of this document, + without loss of generality, we can approximate closely the security + of the HOTP algorithm by the following formula: + + Sec = sv/10^Digit + + + + + + +M'Raihi, et al. Informational [Page 8] + +RFC 4226 HOTP Algorithm December 2005 + + + Where: + - Sec is the probability of success of the adversary; + - s is the look-ahead synchronization window size; + - v is the number of verification attempts; + - Digit is the number of digits in HOTP values. + + Obviously, we can play with s, T (the Throttling parameter that would + limit the number of attempts by an attacker), and Digit until + achieving a certain level of security, still preserving the system + usability. + +7. Security Requirements + + Any One-Time Password algorithm is only as secure as the application + and the authentication protocols that implement it. Therefore, this + section discusses the critical security requirements that our choice + of algorithm imposes on the authentication protocol and validation + software. + + The parameters T and s discussed in this section have a significant + impact on the security -- further details in Section 6 elaborate on + the relations between these parameters and their impact on the system + security. + + It is also important to remark that the HOTP algorithm is not a + substitute for encryption and does not provide for the privacy of + data transmission. Other mechanisms should be used to defeat attacks + aimed at breaking confidentiality and privacy of transactions. + +7.1. Authentication Protocol Requirements + + We introduce in this section some requirements for a protocol P + implementing HOTP as the authentication method between a prover and a + verifier. + + RP1 - P MUST support two-factor authentication, i.e., the + communication and verification of something you know (secret code + such as a Password, Pass phrase, PIN code, etc.) and something you + have (token). The secret code is known only to the user and usually + entered with the One-Time Password value for authentication purpose + (two-factor authentication). + + RP2 - P SHOULD NOT be vulnerable to brute force attacks. This + implies that a throttling/lockout scheme is RECOMMENDED on the + validation server side. + + RP3 - P SHOULD be implemented over a secure channel in order to + protect users' privacy and avoid replay attacks. + + + +M'Raihi, et al. Informational [Page 9] + +RFC 4226 HOTP Algorithm December 2005 + + +7.2. Validation of HOTP Values + + The HOTP client (hardware or software token) increments its counter + and then calculates the next HOTP value HOTP client. If the value + received by the authentication server matches the value calculated by + the client, then the HOTP value is validated. In this case, the + server increments the counter value by one. + + If the value received by the server does not match the value + calculated by the client, the server initiate the resynch protocol + (look-ahead window) before it requests another pass. + + If the resynch fails, the server asks then for another + authentication pass of the protocol to take place, until the + maximum number of authorized attempts is reached. + + If and when the maximum number of authorized attempts is reached, the + server SHOULD lock out the account and initiate a procedure to inform + the user. + +7.3. Throttling at the Server + + Truncating the HMAC-SHA-1 value to a shorter value makes a brute + force attack possible. Therefore, the authentication server needs to + detect and stop brute force attacks. + + We RECOMMEND setting a throttling parameter T, which defines the + maximum number of possible attempts for One-Time Password validation. + The validation server manages individual counters per HOTP device in + order to take note of any failed attempt. We RECOMMEND T not to be + too large, particularly if the resynchronization method used on the + server is window-based, and the window size is large. T SHOULD be + set as low as possible, while still ensuring that usability is not + significantly impacted. + + Another option would be to implement a delay scheme to avoid a brute + force attack. After each failed attempt A, the authentication server + would wait for an increased T*A number of seconds, e.g., say T = 5, + then after 1 attempt, the server waits for 5 seconds, at the second + failed attempt, it waits for 5*2 = 10 seconds, etc. + + The delay or lockout schemes MUST be across login sessions to prevent + attacks based on multiple parallel guessing techniques. + + + + + + + + +M'Raihi, et al. Informational [Page 10] + +RFC 4226 HOTP Algorithm December 2005 + + +7.4. Resynchronization of the Counter + + Although the server's counter value is only incremented after a + successful HOTP authentication, the counter on the token is + incremented every time a new HOTP is requested by the user. Because + of this, the counter values on the server and on the token might be + out of synchronization. + + We RECOMMEND setting a look-ahead parameter s on the server, which + defines the size of the look-ahead window. In a nutshell, the server + can recalculate the next s HOTP-server values, and check them against + the received HOTP client. + + Synchronization of counters in this scenario simply requires the + server to calculate the next HOTP values and determine if there is a + match. Optionally, the system MAY require the user to send a + sequence of (say, 2, 3) HOTP values for resynchronization purpose, + since forging a sequence of consecutive HOTP values is even more + difficult than guessing a single HOTP value. + + The upper bound set by the parameter s ensures the server does not go + on checking HOTP values forever (causing a denial-of-service attack) + and also restricts the space of possible solutions for an attacker + trying to manufacture HOTP values. s SHOULD be set as low as + possible, while still ensuring that usability is not impacted. + +7.5. Management of Shared Secrets + + The operations dealing with the shared secrets used to generate and + verify OTP values must be performed securely, in order to mitigate + risks of any leakage of sensitive information. We describe in this + section different modes of operations and techniques to perform these + different operations with respect to the state of the art in data + security. + + We can consider two different avenues for generating and storing + (securely) shared secrets in the Validation system: + + * Deterministic Generation: secrets are derived from a master + seed, both at provisioning and verification stages and generated + on-the-fly whenever it is required. + * Random Generation: secrets are generated randomly at + provisioning stage and must be stored immediately and kept + secure during their life cycle. + + + + + + + +M'Raihi, et al. Informational [Page 11] + +RFC 4226 HOTP Algorithm December 2005 + + + Deterministic Generation + ------------------------ + + A possible strategy is to derive the shared secrets from a master + secret. The master secret will be stored at the server only. A + tamper-resistant device MUST be used to store the master key and + derive the shared secrets from the master key and some public + information. The main benefit would be to avoid the exposure of the + shared secrets at any time and also avoid specific requirements on + storage, since the shared secrets could be generated on-demand when + needed at provisioning and validation time. + + We distinguish two different cases: + + - A single master key MK is used to derive the shared secrets; + each HOTP device has a different secret, K_i = SHA-1 (MK,i) + where i stands for a public piece of information that identifies + uniquely the HOTP device such as a serial number, a token ID, + etc. Obviously, this is in the context of an application or + service -- different application or service providers will have + different secrets and settings. + - Several master keys MK_i are used and each HOTP device stores a + set of different derived secrets, {K_i,j = SHA-1(MK_i,j)} where + j stands for a public piece of information identifying the + device. The idea would be to store ONLY the active master key + at the validation server, in the Hardware Security Module (HSM), + and keep in a safe place, using secret sharing methods such as + [Shamir] for instance. In this case, if a master secret MK_i is + compromised, then it is possible to switch to another secret + without replacing all the devices. + + The drawback in the deterministic case is that the exposure of the + master secret would obviously enable an attacker to rebuild any + shared secret based on correct public information. The revocation of + all secrets would be required, or switching to a new set of secrets + in the case of multiple master keys. + + On the other hand, the device used to store the master key(s) and + generate the shared secrets MUST be tamper resistant. Furthermore, + the HSM will not be exposed outside the security perimeter of the + validation system, therefore reducing the risk of leakage. + + + + + + + + + + +M'Raihi, et al. Informational [Page 12] + +RFC 4226 HOTP Algorithm December 2005 + + + Random Generation + ----------------- + + The shared secrets are randomly generated. We RECOMMEND following + the recommendations in [RFC4086] and selecting a good and secure + random source for generating these secrets. A (true) random + generator requires a naturally occurring source of randomness. + Practically, there are two possible avenues to consider for the + generation of the shared secrets: + + * Hardware-based generators: they exploit the randomness that + occurs in physical phenomena. A nice implementation can be based on + oscillators and built in such ways that active attacks are more + difficult to perform. + + * Software-based generators: designing a good software random + generator is not an easy task. A simple, but efficient, + implementation should be based on various sources and apply to the + sampled sequence a one-way function such as SHA-1. + + We RECOMMEND selecting proven products, being hardware or software + generators, for the computation of shared secrets. + + We also RECOMMEND storing the shared secrets securely, and more + specifically encrypting the shared secrets when stored using tamper- + resistant hardware encryption and exposing them only when required: + for example, the shared secret is decrypted when needed to verify an + HOTP value, and re-encrypted immediately to limit exposure in the RAM + for a short period of time. The data store holding the shared + secrets MUST be in a secure area, to avoid as much as possible direct + attack on the validation system and secrets database. + + Particularly, access to the shared secrets should be limited to + programs and processes required by the validation system only. We + will not elaborate on the different security mechanisms to put in + place, but obviously, the protection of shared secrets is of the + uttermost importance. + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 13] + +RFC 4226 HOTP Algorithm December 2005 + + +8. Composite Shared Secrets + + It may be desirable to include additional authentication factors in + the shared secret K. These additional factors can consist of any + data known at the token but not easily obtained by others. Examples + of such data include: + + * PIN or Password obtained as user input at the token + * Phone number + * Any unique identifier programmatically available at the token + + In this scenario, the composite shared secret K is constructed during + the provisioning process from a random seed value combined with one + or more additional authentication factors. The server could either + build on-demand or store composite secrets -- in any case, depending + on implementation choice, the token only stores the seed value. When + the token performs the HOTP calculation, it computes K from the seed + value and the locally derived or input values of the other + authentication factors. + + The use of composite shared secrets can strengthen HOTP-based + authentication systems through the inclusion of additional + authentication factors at the token. To the extent that the token is + a trusted device, this approach has the further benefit of not + requiring exposure of the authentication factors (such as the user + input PIN) to other devices. + +9. Bi-Directional Authentication + + Interestingly enough, the HOTP client could also be used to + authenticate the validation server, claiming that it is a genuine + entity knowing the shared secret. + + Since the HOTP client and the server are synchronized and share the + same secret (or a method to recompute it), a simple 3-pass protocol + could be put in place: + 1- The end user enter the TokenID and a first OTP value OTP1; + 2- The server checks OTP1 and if correct, sends back OTP2; + 3- The end user checks OTP2 using his HOTP device and if correct, + uses the web site. + + Obviously, as indicated previously, all the OTP communications have + to take place over a secure channel, e.g., SSL/TLS, IPsec + connections. + + + + + + + +M'Raihi, et al. Informational [Page 14] + +RFC 4226 HOTP Algorithm December 2005 + + +10. Conclusion + + This document describes HOTP, a HMAC-based One-Time Password + algorithm. It also recommends the preferred implementation and + related modes of operations for deploying the algorithm. + + The document also exhibits elements of security and demonstrates that + the HOTP algorithm is practical and sound, the best possible attack + being a brute force attack that can be prevented by careful + implementation of countermeasures in the validation server. + + Eventually, several enhancements have been proposed, in order to + improve security if needed for specific applications. + +11. Acknowledgements + + The authors would like to thank Siddharth Bajaj, Alex Deacon, Loren + Hart, and Nico Popp for their help during the conception and + redaction of this document. + +12. Contributors + + The authors of this document would like to emphasize the role of + three persons who have made a key contribution to this document: + + - Laszlo Elteto is system architect with SafeNet, Inc. + + - Ernesto Frutos is director of Engineering with Authenex, Inc. + + - Fred McClain is Founder and CTO with Boojum Mobile, Inc. + + Without their advice and valuable inputs, this document would not be + the same. + +13. References + +13.1. Normative References + + [BCK1] M. Bellare, R. Canetti and H. Krawczyk, "Keyed Hash + Functions and Message Authentication", Proceedings of + Crypto'96, LNCS Vol. 1109, pp. 1-15. + + [BCK2] Krawczyk, H., Bellare, M., and R. Canetti, "HMAC: Keyed- + Hashing for Message Authentication", RFC 2104, February + 1997. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + + +M'Raihi, et al. Informational [Page 15] + +RFC 4226 HOTP Algorithm December 2005 + + + [RFC3979] Bradner, S., "Intellectual Property Rights in IETF + Technology", BCP 79, RFC 3979, March 2005. + + [RFC4086] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC 4086, + June 2005. + +13.2. Informative References + + [OATH] Initiative for Open AuTHentication + http://www.openauthentication.org + + [PrOo] B. Preneel and P. van Oorschot, "MD-x MAC and building + fast MACs from hash functions", Advances in Cryptology + CRYPTO '95, Lecture Notes in Computer Science Vol. 963, D. + Coppersmith ed., Springer-Verlag, 1995. + + [Crack] Crack in SHA-1 code 'stuns' security gurus + http://www.eetimes.com/showArticle.jhtml? + articleID=60402150 + + [Sha1] Bruce Schneier. SHA-1 broken. February 15, 2005. + http://www.schneier.com/blog/archives/2005/02/ + sha1_broken.html + + [Res] Researchers: Digital encryption standard flawed + http://news.com.com/ + Researchers+Digital+encryption+standard+flawed/ + 2100-1002-5579881.html?part=dht&tag=ntop&tag=nl.e703 + + [Shamir] How to Share a Secret, by Adi Shamir. In Communications + of the ACM, Vol. 22, No. 11, pp. 612-613, November, 1979. + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 16] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix A - HOTP Algorithm Security: Detailed Analysis + + The security analysis of the HOTP algorithm is summarized in this + section. We first detail the best attack strategies, and then + elaborate on the security under various assumptions and the impact of + the truncation and make some recommendations regarding the number of + digits. + + We focus this analysis on the case where Digit = 6, i.e., an HOTP + function that produces 6-digit values, which is the bare minimum + recommended in this document. + +A.1. Definitions and Notations + + We denote by {0,1}^l the set of all strings of length l. + + Let Z_{n} = {0,.., n - 1}. + + Let IntDiv(a,b) denote the integer division algorithm that takes + input integers a, b where a >= b >= 1 and returns integers (q,r) + + the quotient and remainder, respectively, of the division of a by b. + (Thus, a = bq + r and 0 <= r < b.) + + Let H: {0,1}^k x {0,1}^c --> {0,1}^n be the base function that takes + a k-bit key K and c-bit counter C and returns an n-bit output H(K,C). + (In the case of HOTP, H is HMAC-SHA-1; we use this formal definition + for generalizing our proof of security.) + +A.2. The Idealized Algorithm: HOTP-IDEAL + + We now define an idealized counterpart of the HOTP algorithm. In + this algorithm, the role of H is played by a random function that + forms the key. + + To be more precise, let Maps(c,n) denote the set of all functions + mapping from {0,1}^c to {0,1}^n. The idealized algorithm has key + space Maps(c,n), so that a "key" for such an algorithm is a function + h from {0,1}^c to {0,1}^n. We imagine this key (function) to be + drawn at random. It is not feasible to implement this idealized + algorithm, since the key, being a function from {0,1}^c to {0,1}^n, + is way too large to even store. So why consider it? + + Our security analysis will show that as long as H satisfies a certain + well-accepted assumption, the security of the actual and idealized + algorithms is for all practical purposes the same. The task that + really faces us, then, is to assess the security of the idealized + algorithm. + + + +M'Raihi, et al. Informational [Page 17] + +RFC 4226 HOTP Algorithm December 2005 + + + In analyzing the idealized algorithm, we are concentrating on + assessing the quality of the design of the algorithm itself, + independently of HMAC-SHA-1. This is in fact the important issue. + +A.3. Model of Security + + The model exhibits the type of threats or attacks that are being + considered and enables one to assess the security of HOTP and HOTP- + IDEAL. We denote ALG as either HOTP or HOTP-IDEAL for the purpose of + this security analysis. + + The scenario we are considering is that a user and server share a key + K for ALG. Both maintain a counter C, initially zero, and the user + authenticates itself by sending ALG(K,C) to the server. The latter + accepts if this value is correct. + + In order to protect against accidental increment of the user counter, + the server, upon receiving a value z, will accept as long as z equals + ALG(K,i) for some i in the range C,...,C + s-1, where s is the + resynchronization parameter and C is the server counter. If it + accepts with some value of i, it then increments its counter to i+1. + If it does not accept, it does not change its counter value. + + The model we specify captures what an adversary can do and what it + needs to achieve in order to "win". First, the adversary is assumed + to be able to eavesdrop, meaning, to see the authenticator + transmitted by the user. Second, the adversary wins if it can get + the server to accept an authenticator relative to a counter value for + which the user has never transmitted an authenticator. + + The formal adversary, which we denote by B, starts out knowing which + algorithm ALG is being used, knowing the system design, and knowing + all system parameters. The one and only thing it is not given a + priori is the key K shared between the user and the server. + + The model gives B full control of the scheduling of events. It has + access to an authenticator oracle representing the user. By calling + this oracle, the adversary can ask the user to authenticate itself + and get back the authenticator in return. It can call this oracle as + often as it wants and when it wants, using the authenticators it + accumulates to perhaps "learn" how to make authenticators itself. At + any time, it may also call a verification oracle, supplying the + latter with a candidate authenticator of its choice. It wins if the + server accepts this accumulator. + + Consider the following game involving an adversary B that is + attempting to compromise the security of an authentication algorithm + ALG: K x {0,1}^c --> R. + + + +M'Raihi, et al. Informational [Page 18] + +RFC 4226 HOTP Algorithm December 2005 + + + Initializations - A key K is selected at random from K, a counter C + is initialized to 0, and the Boolean value win is set to false. + + Game execution - Adversary B is provided with the two following + oracles: + + Oracle AuthO() + -------------- + A = ALG(K,C) + C = C + 1 + Return O to B + + Oracle VerO(A) + -------------- + i = C + While (i <= C + s - 1 and Win == FALSE) do + If A == ALG(K,i) then Win = TRUE; C = i + 1 + Else i = i + 1 + Return Win to B + + AuthO() is the authenticator oracle and VerO(A) is the verification + oracle. + + Upon execution, B queries the two oracles at will. Let Adv(B) be the + probability that win gets set to true in the above game. This is the + probability that the adversary successfully impersonates the user. + + Our goal is to assess how large this value can be as a function of + the number v of verification queries made by B, the number a of + authenticator oracle queries made by B, and the running time t of B. + This will tell us how to set the throttle, which effectively upper + bounds v. + +A.4. Security of the Ideal Authentication Algorithm + + This section summarizes the security analysis of HOTP-IDEAL, starting + with the impact of the conversion modulo 10^Digit and then focusing + on the different possible attacks. + +A.4.1. From Bits to Digits + + The dynamic offset truncation of a random n-bit string yields a + random 31-bit string. What happens to the distribution when it is + taken modulo m = 10^Digit, as done in HOTP? + + + + + + + +M'Raihi, et al. Informational [Page 19] + +RFC 4226 HOTP Algorithm December 2005 + + + The following lemma estimates the biases in the outputs in this case. + + Lemma 1 + ------- + Let N >= m >= 1 be integers, and let (q,r) = IntDiv(N,m). For z in + Z_{m} let: + + P_{N,m}(z) = Pr [x mod m = z : x randomly pick in Z_{n}] + + Then for any z in Z_{m} + + P_{N,m}(z) = (q + 1) / N if 0 <= z < r + q / N if r <= z < m + + Proof of Lemma 1 + ---------------- + Let the random variable X be uniformly distributed over Z_{N}. Then: + + P_{N,m}(z) = Pr [X mod m = z] + + = Pr [X < mq] * Pr [X mod m = z| X < mq] + + Pr [mq <= X < N] * Pr [X mod m = z| mq <= X < N] + + = mq/N * 1/m + + (N - mq)/N * 1 / (N - mq) if 0 <= z < N - mq + 0 if N - mq <= z <= m + + = q/N + + r/N * 1 / r if 0 <= z < N - mq + 0 if r <= z <= m + + Simplifying yields the claimed equation. + + Let N = 2^31, d = 6, and m = 10^d. If x is chosen at random from + Z_{N} (meaning, is a random 31-bit string), then reducing it to a 6- + digit number by taking x mod m does not yield a random 6-digit + number. + + Rather, x mod m is distributed as shown in the following table: + + Values Probability that each appears as output + ---------------------------------------------------------------- + 0,1,...,483647 2148/2^31 roughly equals to 1.00024045/10^6 + 483648,...,999999 2147/2^31 roughly equals to 0.99977478/10^6 + + If X is uniformly distributed over Z_{2^31} (meaning, is a random + 31-bit string), then the above shows the probabilities for different + outputs of X mod 10^6. The first set of values appears with + + + +M'Raihi, et al. Informational [Page 20] + +RFC 4226 HOTP Algorithm December 2005 + + + probability slightly greater than 10^-6, the rest with probability + slightly less, meaning that the distribution is slightly non-uniform. + + However, as the table above indicates, the bias is small, and as we + will see later, negligible: the probabilities are very close to + 10^-6. + +A.4.2. Brute Force Attacks + + If the authenticator consisted of d random digits, then a brute force + attack using v verification attempts would succeed with probability + sv/10^Digit. + + However, an adversary can exploit the bias in the outputs of + HOTP-IDEAL, predicted by Lemma 1, to mount a slightly better attack. + + Namely, it makes authentication attempts with authenticators that are + the most likely values, meaning the ones in the range 0,...,r - 1, + where (q,r) = IntDiv(2^31,10^Digit). + + The following specifies an adversary in our model of security that + mounts the attack. It estimates the success probability as a + function of the number of verification queries. + + For simplicity, we assume that the number of verification queries is + at most r. With N = 2^31 and m = 10^6, we have r = 483,648, and the + throttle value is certainly less than this, so this assumption is not + much of a restriction. + + Proposition 1 + ------------- + + Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Assume + s <= m. The brute-force-attack adversary B-bf attacks HOTP using v + <= r verification oracle queries. This adversary makes no + authenticator oracle queries, and succeeds with probability + + Adv(B-bf) = 1 - (1 - v(q+1)/2^31)^s + + which is roughly equal to + + sv * (q+1)/2^31 + + With m = 10^6 we get q = 2,147. In that case, the brute force attack + using v verification attempts succeeds with probability + + Adv(B-bf) roughly = sv * 2148/2^31 = sv * 1.00024045/10^6 + + + + +M'Raihi, et al. Informational [Page 21] + +RFC 4226 HOTP Algorithm December 2005 + + + As this equation shows, the resynchronization parameter s has a + significant impact in that the adversary's success probability is + proportional to s. This means that s cannot be made too large + without compromising security. + +A.4.3. Brute force attacks are the best possible attacks. + + A central question is whether there are attacks any better than the + brute force one. In particular, the brute force attack did not + attempt to collect authenticators sent by the user and try to + cryptanalyze them in an attempt to learn how to better construct + authenticators. Would doing this help? Is there some way to "learn" + how to build authenticators that result in a higher success rate than + given by the brute-force attack? + + The following says the answer to these questions is no. No matter + what strategy the adversary uses, and even if it sees, and tries to + exploit, the authenticators from authentication attempts of the user, + its success probability will not be above that of the brute force + attack -- this is true as long as the number of authentications it + observes is not incredibly large. This is valuable information + regarding the security of the scheme. + + Proposition 2 ------------- Suppose m = 10^Digit < 2^31, and let + (q,r) = IntDiv(2^31,m). Let B be any adversary attacking HOTP-IDEAL + using v verification oracle queries and a <= 2^c - s authenticator + oracle queries. Then + + Adv(B) < = sv * (q+1)/ 2^31 + + Note: This result is conditional on the adversary not seeing more + than 2^c - s authentications performed by the user, which is hardly + restrictive as long as c is large enough. + + With m = 10^6, we get q = 2,147. In that case, Proposition 2 says + that any adversary B attacking HOTP-IDEAL and making v verification + attempts succeeds with probability at most + + Equation 1 + ---------- + sv * 2148/2^31 roughly = sv * 1.00024045/10^6 + + Meaning, B's success rate is not more than that achieved by the brute + force attack. + + + + + + + +M'Raihi, et al. Informational [Page 22] + +RFC 4226 HOTP Algorithm December 2005 + + +A.5. Security Analysis of HOTP + + We have analyzed, in the previous sections, the security of the + idealized counterparts HOTP-IDEAL of the actual authentication + algorithm HOTP. We now show that, under appropriate and well- + believed assumption on H, the security of the actual algorithms is + essentially the same as that of its idealized counterpart. + + The assumption in question is that H is a secure pseudorandom + function, or PRF, meaning that its input-output values are + indistinguishable from those of a random function in practice. + + Consider an adversary A that is given an oracle for a function f: + {0,1}^c --> {0, 1}^n and eventually outputs a bit. We denote Adv(A) + as the prf-advantage of A, which represents how well the adversary + does at distinguishing the case where its oracle is H(K,.) from the + case where its oracle is a random function of {0,1}^c to {0,1}^n. + + One possible attack is based on exhaustive search for the key K. If + A runs for t steps and T denotes the time to perform one computation + of H, its prf-advantage from this attack turns out to be (t/T)2^-k. + Another possible attack is a birthday one [PrOo], whereby A can + attain advantage p^2/2^n in p oracle queries and running time about + pT. + + Our assumption is that these are the best possible attacks. This + translates into the following. + + Assumption 1 + ------------ + + Let T denotes the time to perform one computation of H. Then if A is + any adversary with running time at most t and making at most p oracle + queries, + + Adv(A) <= (t/T)/2^k + p^2/2^n + + In practice, this assumption means that H is very secure as PRF. For + example, given that k = n = 160, an attacker with running time 2^60 + and making 2^40 oracle queries has advantage at most (about) 2^-80. + + Theorem 1 + --------- + + Suppose m = 10^Digit < 2^31, and let (q,r) = IntDiv(2^31,m). Let B + be any adversary attacking HOTP using v verification oracle queries, + + + + + +M'Raihi, et al. Informational [Page 23] + +RFC 4226 HOTP Algorithm December 2005 + + + a <= 2^c - s authenticator oracle queries, and running time t. Let T + denote the time to perform one computation of H. If Assumption 1 is + true, then + + Adv(B) <= sv * (q + 1)/2^31 + (t/T)/2^k + ((sv + a)^2)/2^n + + In practice, the (t/T)2^-k + ((sv + a)^2)2^-n term is much smaller + than the sv(q + 1)/2^n term, so that the above says that for all + practical purposes the success rate of an adversary attacking HOTP is + sv(q + 1)/2^n, just as for HOTP-IDEAL, meaning the HOTP algorithm is + in practice essentially as good as its idealized counterpart. + + In the case m = 10^6 of a 6-digit output, this means that an + adversary making v authentication attempts will have a success rate + that is at most that of Equation 1. + + For example, consider an adversary with running time at most 2^60 + that sees at most 2^40 authentication attempts of the user. Both + these choices are very generous to the adversary, who will typically + not have these resources, but we are saying that even such a powerful + adversary will not have more success than indicated by Equation 1. + + We can safely assume sv <= 2^40 due to the throttling and bounds on + s. So: + + (t/T)/2^k + ((sv + a)^2)/2^n <= 2^60/2^160 + (2^41)^2/2^160 + roughly <= 2^-78 + + which is much smaller than the success probability of Equation 1 and + negligible compared to it. + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 24] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix B - SHA-1 Attacks + + This sections addresses the impact of the recent attacks on SHA-1 on + the security of the HMAC-SHA-1-based HOTP. We begin with some + discussion of the situation of SHA-1 and then discuss the relevance + to HMAC-SHA-1 and HOTP. Cited references are in Section 13. + +B.1. SHA-1 Status + + A collision for a hash function h means a pair x,y of different + inputs such that h(x)=h(y). Since SHA-1 outputs 160 bits, a birthday + attack finds a collision in 2^{80} trials. (A trial means one + computation of the function.) This was thought to be the best + possible until Wang, Yin, and Yu announced on February 15, 2005, that + they had an attack finding collisions in 2^{69} trials. + + Is SHA-1 broken? For most practical purposes, we would say probably + not, since the resources needed to mount the attack are huge. Here + is one way to get a sense of it: we can estimate it is about the same + as the time we would need to factor a 760-bit RSA modulus, and this + is currently considered out of reach. + + Burr of NIST is quoted in [Crack] as saying "Large national + intelligence agencies could do this in a reasonable amount of time + with a few million dollars in computer time". However, the + computation may be out of reach of all but such well-funded agencies. + + One should also ask what impact finding SHA-1 collisions actually has + on security of real applications such as signatures. To exploit a + collision x,y to forge signatures, you need to somehow obtain a + signature of x and then you can forge a signature of y. How damaging + this is depends on the content of y: the y created by the attack may + not be meaningful in the application context. Also, one needs a + chosen-message attack to get the signature of x. This seems possible + in some contexts, but not others. Overall, it is not clear that the + impact on the security of signatures is significant. + + Indeed, one can read in the press that SHA-1 is "broken" [Sha1] and + that encryption and SSL are "broken" [Res]. The media have a + tendency to magnify events: it would hardly be interesting to + announce in the news that a team of cryptanalysts did very + interesting theoretical work in attacking SHA-1. + + Cryptographers are excited too. But mainly because this is an + important theoretical breakthrough. Attacks can only get better with + time: it is therefore important to monitor any progress in hash + functions cryptanalysis and be prepared for any really practical + break with a sound migration plan for the future. + + + +M'Raihi, et al. Informational [Page 25] + +RFC 4226 HOTP Algorithm December 2005 + + +B.2. HMAC-SHA-1 Status + + The new attacks on SHA-1 have no impact on the security of + HMAC-SHA-1. The best attack on the latter remains one needing a + sender to authenticate 2^{80} messages before an adversary can create + a forgery. Why? + + HMAC is not a hash function. It is a message authentication code + (MAC) that uses a hash function internally. A MAC depends on a + secret key, while hash functions don't. What one needs to worry + about with a MAC is forgery, not collisions. HMAC was designed so + that collisions in the hash function (here SHA-1) do not yield + forgeries for HMAC. + + Recall that HMAC-SHA-1(K,x) = SHA-1(K_o,SHA-1(K_i,x)) where the keys + K_o,K_i are derived from K. Suppose the attacker finds a pair x,y + such that SHA-1(K_i,x) = SHA-1(K_i,y). (Call this a hidden-key + collision.) Then if it can obtain the MAC of x (itself a tall + order), it can forge the MAC of y. (These values are the same.) But + finding hidden-key collisions is harder than finding collisions, + because the attacker does not know the hidden key K_i. All it may + have is some outputs of HMAC-SHA-1 with key K. To date, there are no + claims or evidence that the recent attacks on SHA-1 extend to find + hidden-key collisions. + + Historically, the HMAC design has already proven itself in this + regard. MD5 is considered broken in that collisions in this hash + function can be found relatively easily. But there is still no + attack on HMAC-MD5 better than the trivial 2^{64} time birthday one. + (MD5 outputs 128 bits, not 160.) We are seeing this strength of HMAC + coming into play again in the SHA-1 context. + +B.3. HOTP Status + + Since no new weakness has surfaced in HMAC-SHA-1, there is no impact + on HOTP. The best attacks on HOTP remain those described in the + document, namely, to try to guess output values. + + The security proof of HOTP requires that HMAC-SHA-1 behave like a + pseudorandom function. The quality of HMAC-SHA-1 as a pseudorandom + function is not impacted by the new attacks on SHA-1, and so neither + is this proven guarantee. + + + + + + + + + +M'Raihi, et al. Informational [Page 26] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix C - HOTP Algorithm: Reference Implementation + + /* + * OneTimePasswordAlgorithm.java + * OATH Initiative, + * HOTP one-time password algorithm + * + */ + + /* Copyright (C) 2004, OATH. All rights reserved. + * + * License to copy and use this software is granted provided that it + * is identified as the "OATH HOTP Algorithm" in all material + * mentioning or referencing this software or this function. + * + * License is also granted to make and use derivative works provided + * that such works are identified as + * "derived from OATH HOTP algorithm" + * in all material mentioning or referencing the derived work. + * + * OATH (Open AuTHentication) and its members make no + * representations concerning either the merchantability of this + * software or the suitability of this software for any particular + * purpose. + * + * It is provided "as is" without express or implied warranty + * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS + * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + + package org.openauthentication.otp; + + import java.io.IOException; + import java.io.File; + import java.io.DataInputStream; + import java.io.FileInputStream ; + import java.lang.reflect.UndeclaredThrowableException; + + import java.security.GeneralSecurityException; + import java.security.NoSuchAlgorithmException; + import java.security.InvalidKeyException; + + import javax.crypto.Mac; + import javax.crypto.spec.SecretKeySpec; + + + + +M'Raihi, et al. Informational [Page 27] + +RFC 4226 HOTP Algorithm December 2005 + + + /** + * This class contains static methods that are used to calculate the + * One-Time Password (OTP) using + * JCE to provide the HMAC-SHA-1. + * + * @author Loren Hart + * @version 1.0 + */ + public class OneTimePasswordAlgorithm { + private OneTimePasswordAlgorithm() {} + + // These are used to calculate the check-sum digits. + // 0 1 2 3 4 5 6 7 8 9 + private static final int[] doubleDigits = + { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 }; + + /** + * Calculates the checksum using the credit card algorithm. + * This algorithm has the advantage that it detects any single + * mistyped digit and any single transposition of + * adjacent digits. + * + * @param num the number to calculate the checksum for + * @param digits number of significant places in the number + * + * @return the checksum of num + */ + public static int calcChecksum(long num, int digits) { + boolean doubleDigit = true; + int total = 0; + while (0 < digits--) { + int digit = (int) (num % 10); + num /= 10; + if (doubleDigit) { + digit = doubleDigits[digit]; + } + total += digit; + doubleDigit = !doubleDigit; + } + int result = total % 10; + if (result > 0) { + result = 10 - result; + } + return result; + } + + /** + * This method uses the JCE to provide the HMAC-SHA-1 + + + +M'Raihi, et al. Informational [Page 28] + +RFC 4226 HOTP Algorithm December 2005 + + + * algorithm. + * HMAC computes a Hashed Message Authentication Code and + * in this case SHA1 is the hash algorithm used. + * + * @param keyBytes the bytes to use for the HMAC-SHA-1 key + * @param text the message or text to be authenticated. + * + * @throws NoSuchAlgorithmException if no provider makes + * either HmacSHA1 or HMAC-SHA-1 + * digest algorithms available. + * @throws InvalidKeyException + * The secret provided was not a valid HMAC-SHA-1 key. + * + */ + + public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) + throws NoSuchAlgorithmException, InvalidKeyException + { + // try { + Mac hmacSha1; + try { + hmacSha1 = Mac.getInstance("HmacSHA1"); + } catch (NoSuchAlgorithmException nsae) { + hmacSha1 = Mac.getInstance("HMAC-SHA-1"); + } + SecretKeySpec macKey = + new SecretKeySpec(keyBytes, "RAW"); + hmacSha1.init(macKey); + return hmacSha1.doFinal(text); + // } catch (GeneralSecurityException gse) { + // throw new UndeclaredThrowableException(gse); + // } + } + + private static final int[] DIGITS_POWER + // 0 1 2 3 4 5 6 7 8 + = {1,10,100,1000,10000,100000,1000000,10000000,100000000}; + + /** + * This method generates an OTP value for the given + * set of parameters. + * + * @param secret the shared secret + * @param movingFactor the counter, time, or other value that + * changes on a per use basis. + * @param codeDigits the number of digits in the OTP, not + * including the checksum, if any. + * @param addChecksum a flag that indicates if a checksum digit + + + +M'Raihi, et al. Informational [Page 29] + +RFC 4226 HOTP Algorithm December 2005 + + + * should be appended to the OTP. + * @param truncationOffset the offset into the MAC result to + * begin truncation. If this value is out of + * the range of 0 ... 15, then dynamic + * truncation will be used. + * Dynamic truncation is when the last 4 + * bits of the last byte of the MAC are + * used to determine the start offset. + * @throws NoSuchAlgorithmException if no provider makes + * either HmacSHA1 or HMAC-SHA-1 + * digest algorithms available. + * @throws InvalidKeyException + * The secret provided was not + * a valid HMAC-SHA-1 key. + * + * @return A numeric String in base 10 that includes + * {@link codeDigits} digits plus the optional checksum + * digit if requested. + */ + static public String generateOTP(byte[] secret, + long movingFactor, + int codeDigits, + boolean addChecksum, + int truncationOffset) + throws NoSuchAlgorithmException, InvalidKeyException + { + // put movingFactor value into text byte array + String result = null; + int digits = addChecksum ? (codeDigits + 1) : codeDigits; + byte[] text = new byte[8]; + for (int i = text.length - 1; i >= 0; i--) { + text[i] = (byte) (movingFactor & 0xff); + movingFactor >>= 8; + } + + // compute hmac hash + byte[] hash = hmac_sha1(secret, text); + + // put selected bytes into result int + int offset = hash[hash.length - 1] & 0xf; + if ( (0<=truncationOffset) && + (truncationOffset<(hash.length-4)) ) { + offset = truncationOffset; + } + int binary = + ((hash[offset] & 0x7f) << 24) + | ((hash[offset + 1] & 0xff) << 16) + | ((hash[offset + 2] & 0xff) << 8) + + + +M'Raihi, et al. Informational [Page 30] + +RFC 4226 HOTP Algorithm December 2005 + + + | (hash[offset + 3] & 0xff); + + int otp = binary % DIGITS_POWER[codeDigits]; + if (addChecksum) { + otp = (otp * 10) + calcChecksum(otp, codeDigits); + } + result = Integer.toString(otp); + while (result.length() < digits) { + result = "0" + result; + } + return result; + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 31] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix D - HOTP Algorithm: Test Values + + The following test data uses the ASCII string + "12345678901234567890" for the secret: + + Secret = 0x3132333435363738393031323334353637383930 + + Table 1 details for each count, the intermediate HMAC value. + + Count Hexadecimal HMAC-SHA-1(secret, count) + 0 cc93cf18508d94934c64b65d8ba7667fb7cde4b0 + 1 75a48a19d4cbe100644e8ac1397eea747a2d33ab + 2 0bacb7fa082fef30782211938bc1c5e70416ff44 + 3 66c28227d03a2d5529262ff016a1e6ef76557ece + 4 a904c900a64b35909874b33e61c5938a8e15ed1c + 5 a37e783d7b7233c083d4f62926c7a25f238d0316 + 6 bc9cd28561042c83f219324d3c607256c03272ae + 7 a4fb960c0bc06e1eabb804e5b397cdc4b45596fa + 8 1b3c89f65e6c9e883012052823443f048b4332db + 9 1637409809a679dc698207310c8c7fc07290d9e5 + + Table 2 details for each count the truncated values (both in + hexadecimal and decimal) and then the HOTP value. + + Truncated + Count Hexadecimal Decimal HOTP + 0 4c93cf18 1284755224 755224 + 1 41397eea 1094287082 287082 + 2 82fef30 137359152 359152 + 3 66ef7655 1726969429 969429 + 4 61c5938a 1640338314 338314 + 5 33c083d4 868254676 254676 + 6 7256c032 1918287922 287922 + 7 4e5b397 82162583 162583 + 8 2823443f 673399871 399871 + 9 2679dc69 645520489 520489 + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 32] + +RFC 4226 HOTP Algorithm December 2005 + + +Appendix E - Extensions + + + We introduce in this section several enhancements to the HOTP + algorithm. These are not recommended extensions or part of the + standard algorithm, but merely variations that could be used for + customized implementations. + +E.1. Number of Digits + + A simple enhancement in terms of security would be to extract more + digits from the HMAC-SHA-1 value. + + For instance, calculating the HOTP value modulo 10^8 to build an 8- + digit HOTP value would reduce the probability of success of the + adversary from sv/10^6 to sv/10^8. + + This could give the opportunity to improve usability, e.g., by + increasing T and/or s, while still achieving a better security + overall. For instance, s = 10 and 10v/10^8 = v/10^7 < v/10^6 which + is the theoretical optimum for 6-digit code when s = 1. + +E.2. Alphanumeric Values + + Another option is to use A-Z and 0-9 values; or rather a subset of 32 + symbols taken from the alphanumerical alphabet in order to avoid any + confusion between characters: 0, O, and Q as well as l, 1, and I are + very similar, and can look the same on a small display. + + The immediate consequence is that the security is now in the order of + sv/32^6 for a 6-digit HOTP value and sv/32^8 for an 8-digit HOTP + value. + + 32^6 > 10^9 so the security of a 6-alphanumeric HOTP code is slightly + better than a 9-digit HOTP value, which is the maximum length of an + HOTP code supported by the proposed algorithm. + + 32^8 > 10^12 so the security of an 8-alphanumeric HOTP code is + significantly better than a 9-digit HOTP value. + + Depending on the application and token/interface used for displaying + and entering the HOTP value, the choice of alphanumeric values could + be a simple and efficient way to improve security at a reduced cost + and impact on users. + + + + + + + +M'Raihi, et al. Informational [Page 33] + +RFC 4226 HOTP Algorithm December 2005 + + +E.3. Sequence of HOTP Values + + As we suggested for the resynchronization to enter a short sequence + (say, 2 or 3) of HOTP values, we could generalize the concept to the + protocol, and add a parameter L that would define the length of the + HOTP sequence to enter. + + Per default, the value L SHOULD be set to 1, but if security needs to + be increased, users might be asked (possibly for a short period of + time, or a specific operation) to enter L HOTP values. + + This is another way, without increasing the HOTP length or using + alphanumeric values to tighten security. + + Note: The system MAY also be programmed to request synchronization on + a regular basis (e.g., every night, twice a week, etc.) and to + achieve this purpose, ask for a sequence of L HOTP values. + +E.4. A Counter-Based Resynchronization Method + + In this case, we assume that the client can access and send not only + the HOTP value but also other information, more specifically, the + counter value. + + A more efficient and secure method for resynchronization is possible + in this case. The client application will not send the HOTP-client + value only, but the HOTP-client and the related C-client counter + value, the HOTP value acting as a message authentication code of the + counter. + + Resynchronization Counter-based Protocol (RCP) + ---------------------------------------------- + + The server accepts if the following are all true, where C-server is + its own current counter value: + + 1) C-client >= C-server + 2) C-client - C-server <= s + 3) Check that HOTP client is valid HOTP(K,C-Client) + 4) If true, the server sets C to C-client + 1 and client is + authenticated + + In this case, there is no need for managing a look-ahead window + anymore. The probability of success of the adversary is only v/10^6 + or roughly v in one million. A side benefit is obviously to be able + to increase s "infinitely" and therefore improve the system usability + without impacting the security. + + + + +M'Raihi, et al. Informational [Page 34] + +RFC 4226 HOTP Algorithm December 2005 + + + This resynchronization protocol SHOULD be used whenever the related + impact on the client and server applications is deemed acceptable. + +E.5. Data Field + + Another interesting option is the introduction of a Data field, which + would be used for generating the One-Time Password values: HOTP (K, + C, [Data]) where Data is an optional field that can be the + concatenation of various pieces of identity-related information, + e.g., Data = Address | PIN. + + We could also use a Timer, either as the only moving factor or in + combination with the Counter -- in this case, e.g., Data = Timer, + where Timer could be the UNIX-time (GMT seconds since 1/1/1970) + divided by some factor (8, 16, 32, etc.) in order to give a specific + time step. The time window for the One-Time Password is then equal + to the time step multiplied by the resynchronization parameter as + defined before. For example, if we take 64 seconds as the time step + and 7 for the resynchronization parameter, we obtain an acceptance + window of +/- 3 minutes. + + Using a Data field opens for more flexibility in the algorithm + implementation, provided that the Data field is clearly specified. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +M'Raihi, et al. Informational [Page 35] + +RFC 4226 HOTP Algorithm December 2005 + + +Authors' Addresses + + David M'Raihi (primary contact for sending comments and questions) + VeriSign, Inc. + 685 E. Middlefield Road + Mountain View, CA 94043 USA + + Phone: 1-650-426-3832 + EMail: dmraihi@verisign.com + + + Mihir Bellare + Dept of Computer Science and Engineering, Mail Code 0114 + University of California at San Diego + 9500 Gilman Drive + La Jolla, CA 92093, USA + + EMail: mihir@cs.ucsd.edu + + + Frank Hoornaert + VASCO Data Security, Inc. + Koningin Astridlaan 164 + 1780 Wemmel, Belgium + + EMail: frh@vasco.com + + + David Naccache + Gemplus Innovation + 34 rue Guynemer, 92447, + Issy les Moulineaux, France + and + Information Security Group, + Royal Holloway, + University of London, Egham, + Surrey TW20 0EX, UK + + EMail: david.naccache@gemplus.com, david.naccache@rhul.ac.uk + + + Ohad Ranen + Aladdin Knowledge Systems Ltd. + 15 Beit Oved Street + Tel Aviv, Israel 61110 + + EMail: Ohad.Ranen@ealaddin.com + + + + +M'Raihi, et al. Informational [Page 36] + +RFC 4226 HOTP Algorithm December 2005 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2005). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at ietf- + ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +M'Raihi, et al. Informational [Page 37] + diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.key b/Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.key new file mode 100644 index 0000000000000000000000000000000000000000..629a279d721c21857684ea00b336fce3f358abc2 GIT binary patch literal 30 mcmXqzVG&XNw9Ed5;luaumFm7qt~0q@x&HZSCPoG(Mm7M?{tT7? literal 0 HcmV?d00001 diff --git a/Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.png b/Godeps/_workspace/src/github.com/gokyle/hotp/testdata/gauth_example.png new file mode 100644 index 0000000000000000000000000000000000000000..3adeef9e97d0766e1d61ae2efcdfcb6ad2de6116 GIT binary patch literal 2537 zcmZWrO-NKx7=1FNCF*(%hJxA{_!DkoHiMy=1(qfht*oJR6PAt`L6`|q6uE0_A%VGQ z)i#)L(JH1zwrV4^nw3iz((9b_efP~By`VGizVGjzbH4A+tLx*Vo$V*vlO*XJyF5IZ zBrPxb*Mf~?;lh>I_&8e`xmCF_cfYc9XZ~I?G1&{B)3cSz!(xB`qxrstrAm3WuRQml ze`f9J*CaXdY;1UF>TyeX|Ju3y&iSqK)c4J|6NTf2GdJ6|Q>?)Ot%I3;KBXNU9clN! z!GVLB>1pmPzrep>Lxat(+x5nqG_CEe@6w8Hc)h+`${{Ypz%7eP&(hZFyJ9{V<+kah z#h132d4Re16-YLCb82VkY5@V2ZDnP*w7uNg+Nzv+dLT`$TCMJ(T;Ij^0qx^z{b&{) zQSjP|u2pdclx+psIhLo|dKU*6qa>J8$46i=1QNOzQksvOuGQav>>_L_Fyz`NMyT09 z8f3N6MqZ1sG;-2eOUVUnI9m#T#tatzIlB0B(g;OX#wSXAm!->Li5c>%7#3ur@a5Mx zKu0uKG9p{8WEQ zI;r$0mZLb$n)hBhV9Nj~(=p?@h7WNXK_nt1X%Wl9wD3i;^ur#3h~2>O=o_jvlLU|? z)S-zQjoO^k(vk`!;_`OcGojmG0)bnQVjnqiW5Jig#pul4u}>&Vvy!plKtglxHHc!3DewY1N+ zWK=Klky&A@S!9(R%85q=%)^5ruImFXOf#NwYksIp_Kq=DRCMV~Sj>yuF^fqZg zSrV;HbsDM0sO#AKToElz^Gl&cOqieHieVM>Y#`)CaFgKLAT-hIS%rt|fL8HTFbH{T zg?o(_zWo05w!>yaf5AlL888f>Wh2Pg(LKW5Nc2JKL>0%;1xOR>z^^C8(gcWZm*yLd zU2aR`!ekMqpYakW_5Kj;6fs<#Dh5F;_CF_@4eqF1_he~+J{tx$KBYbPw{g>iF5~)@ zP9s(96GguwoH84saZVOxmyrEn;Bz+lSS+!l!nGfF8me?e&5UtmL>c-taL=)0 n8^ueDZ|eN1mHSZsTThanA5VJj{`p= len(keys) { + return nil, invalidPath + } + if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil { + return nil, invalidPath + } + parts = append(parts, pathPart{ + path: path, + field: field, + index: int(index64), + }) + path = make([]string, 0) + + // Get the next struct type, dropping ptrs. + if field.typ.Kind() == reflect.Ptr { + t = field.typ.Elem() + } else { + t = field.typ + } + if t.Kind() == reflect.Slice { + t = t.Elem() + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + } + } else if field.typ.Kind() == reflect.Struct { + t = field.typ + } else if field.typ.Kind() == reflect.Ptr && field.typ.Elem().Kind() == reflect.Struct { + t = field.typ.Elem() + } + } + // Add the remaining. + parts = append(parts, pathPart{ + path: path, + field: field, + index: -1, + }) + return parts, nil +} + +// get returns a cached structInfo, creating it if necessary. +func (c *cache) get(t reflect.Type) *structInfo { + c.l.RLock() + info := c.m[t] + c.l.RUnlock() + if info == nil { + info = c.create(t, nil) + c.l.Lock() + c.m[t] = info + c.l.Unlock() + } + return info +} + +// creat creates a structInfo with meta-data about a struct. +func (c *cache) create(t reflect.Type, info *structInfo) *structInfo { + if info == nil { + info = &structInfo{fields: []*fieldInfo{}} + } + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.Anonymous { + ft := field.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if ft.Kind() == reflect.Struct { + c.create(ft, info) + } + } + c.createField(field, info) + } + return info +} + +// createField creates a fieldInfo for the given field. +func (c *cache) createField(field reflect.StructField, info *structInfo) { + alias := fieldAlias(field, c.tag) + if alias == "-" { + // Ignore this field. + return + } + // Check if the type is supported and don't cache it if not. + // First let's get the basic type. + isSlice, isStruct := false, false + ft := field.Type + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + if isSlice = ft.Kind() == reflect.Slice; isSlice { + ft = ft.Elem() + if ft.Kind() == reflect.Ptr { + ft = ft.Elem() + } + } + if isStruct = ft.Kind() == reflect.Struct; !isStruct { + if conv := c.conv[ft]; conv == nil { + // Type is not supported. + return + } + } + + info.fields = append(info.fields, &fieldInfo{ + typ: field.Type, + name: field.Name, + ss: isSlice && isStruct, + alias: alias, + }) +} + +// ---------------------------------------------------------------------------- + +type structInfo struct { + fields []*fieldInfo +} + +func (i *structInfo) get(alias string) *fieldInfo { + for _, field := range i.fields { + if strings.EqualFold(field.alias, alias) { + return field + } + } + return nil +} + +type fieldInfo struct { + typ reflect.Type + name string // field name in the struct. + ss bool // true if this is a slice of structs. + alias string +} + +type pathPart struct { + field *fieldInfo + path []string // path to the field: walks structs using field names. + index int // struct index in slices of structs. +} + +// ---------------------------------------------------------------------------- + +// fieldAlias parses a field tag to get a field alias. +func fieldAlias(field reflect.StructField, tagName string) string { + var alias string + if tag := field.Tag.Get(tagName); tag != "" { + // For now tags only support the name but let's folow the + // comma convention from encoding/json and others. + if idx := strings.Index(tag, ","); idx == -1 { + alias = tag + } else { + alias = tag[:idx] + } + } + if alias == "" { + alias = field.Name + } + return alias +} diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/converter.go b/Godeps/_workspace/src/github.com/gorilla/schema/converter.go new file mode 100644 index 0000000..6dccc78 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/schema/converter.go @@ -0,0 +1,145 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schema + +import ( + "reflect" + "strconv" +) + +type Converter func(string) reflect.Value + +var ( + invalidValue = reflect.Value{} + boolType = reflect.TypeOf(false) + float32Type = reflect.TypeOf(float32(0)) + float64Type = reflect.TypeOf(float64(0)) + intType = reflect.TypeOf(int(0)) + int8Type = reflect.TypeOf(int8(0)) + int16Type = reflect.TypeOf(int16(0)) + int32Type = reflect.TypeOf(int32(0)) + int64Type = reflect.TypeOf(int64(0)) + stringType = reflect.TypeOf("") + uintType = reflect.TypeOf(uint(0)) + uint8Type = reflect.TypeOf(uint8(0)) + uint16Type = reflect.TypeOf(uint16(0)) + uint32Type = reflect.TypeOf(uint32(0)) + uint64Type = reflect.TypeOf(uint64(0)) +) + +// Default converters for basic types. +var converters = map[reflect.Type]Converter{ + boolType: convertBool, + float32Type: convertFloat32, + float64Type: convertFloat64, + intType: convertInt, + int8Type: convertInt8, + int16Type: convertInt16, + int32Type: convertInt32, + int64Type: convertInt64, + stringType: convertString, + uintType: convertUint, + uint8Type: convertUint8, + uint16Type: convertUint16, + uint32Type: convertUint32, + uint64Type: convertUint64, +} + +func convertBool(value string) reflect.Value { + if value == "on" { + return reflect.ValueOf(true) + } else if v, err := strconv.ParseBool(value); err == nil { + return reflect.ValueOf(v) + } + return invalidValue +} + +func convertFloat32(value string) reflect.Value { + if v, err := strconv.ParseFloat(value, 32); err == nil { + return reflect.ValueOf(float32(v)) + } + return invalidValue +} + +func convertFloat64(value string) reflect.Value { + if v, err := strconv.ParseFloat(value, 64); err == nil { + return reflect.ValueOf(v) + } + return invalidValue +} + +func convertInt(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 0); err == nil { + return reflect.ValueOf(int(v)) + } + return invalidValue +} + +func convertInt8(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 8); err == nil { + return reflect.ValueOf(int8(v)) + } + return invalidValue +} + +func convertInt16(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 16); err == nil { + return reflect.ValueOf(int16(v)) + } + return invalidValue +} + +func convertInt32(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 32); err == nil { + return reflect.ValueOf(int32(v)) + } + return invalidValue +} + +func convertInt64(value string) reflect.Value { + if v, err := strconv.ParseInt(value, 10, 64); err == nil { + return reflect.ValueOf(v) + } + return invalidValue +} + +func convertString(value string) reflect.Value { + return reflect.ValueOf(value) +} + +func convertUint(value string) reflect.Value { + if v, err := strconv.ParseUint(value, 10, 0); err == nil { + return reflect.ValueOf(uint(v)) + } + return invalidValue +} + +func convertUint8(value string) reflect.Value { + if v, err := strconv.ParseUint(value, 10, 8); err == nil { + return reflect.ValueOf(uint8(v)) + } + return invalidValue +} + +func convertUint16(value string) reflect.Value { + if v, err := strconv.ParseUint(value, 10, 16); err == nil { + return reflect.ValueOf(uint16(v)) + } + return invalidValue +} + +func convertUint32(value string) reflect.Value { + if v, err := strconv.ParseUint(value, 10, 32); err == nil { + return reflect.ValueOf(uint32(v)) + } + return invalidValue +} + +func convertUint64(value string) reflect.Value { + if v, err := strconv.ParseUint(value, 10, 64); err == nil { + return reflect.ValueOf(v) + } + return invalidValue +} diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go b/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go new file mode 100644 index 0000000..6d880f1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/schema/decoder.go @@ -0,0 +1,246 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schema + +import ( + "errors" + "fmt" + "reflect" + "strings" +) + +// NewDecoder returns a new Decoder. +func NewDecoder() *Decoder { + return &Decoder{cache: newCache()} +} + +// Decoder decodes values from a map[string][]string to a struct. +type Decoder struct { + cache *cache + zeroEmpty bool + ignoreUnknownKeys bool +} + +// SetAliasTag changes the tag used to locate custome field aliases. +// The default tag is "schema". +func (d *Decoder) SetAliasTag(tag string) { + d.cache.tag = tag +} + +// ZeroEmpty controls the behaviour when the decoder encounters empty values +// in a map. +// If z is true and a key in the map has the empty string as a value +// then the corresponding struct field is set to the zero value. +// If z is false then empty strings are ignored. +// +// The default value is false, that is empty values do not change +// the value of the struct field. +func (d *Decoder) ZeroEmpty(z bool) { + d.zeroEmpty = z +} + +// IgnoreUnknownKeys controls the behaviour when the decoder encounters unknown +// keys in the map. +// If i is true and an unknown field is encountered, it is ignored. This is +// similar to how unknown keys are handled by encoding/json. +// If i is false then Decode will return an error. Note that any valid keys +// will still be decoded in to the target struct. +// +// To preserve backwards compatibility, the default value is false. +func (d *Decoder) IgnoreUnknownKeys(i bool) { + d.ignoreUnknownKeys = i +} + +// RegisterConverter registers a converter function for a custom type. +func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) { + d.cache.conv[reflect.TypeOf(value)] = converterFunc +} + +// Decode decodes a map[string][]string to a struct. +// +// The first parameter must be a pointer to a struct. +// +// The second parameter is a map, typically url.Values from an HTTP request. +// Keys are "paths" in dotted notation to the struct fields and nested structs. +// +// See the package documentation for a full explanation of the mechanics. +func (d *Decoder) Decode(dst interface{}, src map[string][]string) error { + v := reflect.ValueOf(dst) + if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct { + return errors.New("schema: interface must be a pointer to struct") + } + v = v.Elem() + t := v.Type() + errors := MultiError{} + for path, values := range src { + if parts, err := d.cache.parsePath(path, t); err == nil { + if err = d.decode(v, path, parts, values); err != nil { + errors[path] = err + } + } else if !d.ignoreUnknownKeys { + errors[path] = fmt.Errorf("schema: invalid path %q", path) + } + } + if len(errors) > 0 { + return errors + } + return nil +} + +// decode fills a struct field using a parsed path. +func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, + values []string) error { + // Get the field walking the struct fields by index. + for _, name := range parts[0].path { + if v.Type().Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + v = v.FieldByName(name) + } + + // Don't even bother for unexported fields. + if !v.CanSet() { + return nil + } + + // Dereference if needed. + t := v.Type() + if t.Kind() == reflect.Ptr { + t = t.Elem() + if v.IsNil() { + v.Set(reflect.New(t)) + } + v = v.Elem() + } + + // Slice of structs. Let's go recursive. + if len(parts) > 1 { + idx := parts[0].index + if v.IsNil() || v.Len() < idx+1 { + value := reflect.MakeSlice(t, idx+1, idx+1) + if v.Len() < idx+1 { + // Resize it. + reflect.Copy(value, v) + } + v.Set(value) + } + return d.decode(v.Index(idx), path, parts[1:], values) + } + + // Simple case. + if t.Kind() == reflect.Slice { + var items []reflect.Value + elemT := t.Elem() + isPtrElem := elemT.Kind() == reflect.Ptr + if isPtrElem { + elemT = elemT.Elem() + } + conv := d.cache.conv[elemT] + if conv == nil { + return fmt.Errorf("schema: converter not found for %v", elemT) + } + for key, value := range values { + if value == "" { + if d.zeroEmpty { + items = append(items, reflect.Zero(elemT)) + } + } else if item := conv(value); item.IsValid() { + if isPtrElem { + ptr := reflect.New(elemT) + ptr.Elem().Set(item) + item = ptr + } + items = append(items, item) + } else { + if strings.Contains(value, ",") { + values := strings.Split(value, ",") + for _, value := range values { + if value == "" { + if d.zeroEmpty { + items = append(items, reflect.Zero(elemT)) + } + } else if item := conv(value); item.IsValid() { + if isPtrElem { + ptr := reflect.New(elemT) + ptr.Elem().Set(item) + item = ptr + } + items = append(items, item) + } else { + return ConversionError{path, key} + } + } + + } else { + return ConversionError{path, key} + } + } + } + value := reflect.Append(reflect.MakeSlice(t, 0, 0), items...) + v.Set(value) + } else { + val := "" + // Use the last value provided if any values were provided + if len(values) > 0 { + val = values[len(values)-1] + } + + if val == "" { + if d.zeroEmpty { + v.Set(reflect.Zero(t)) + } + } else if conv := d.cache.conv[t]; conv != nil { + if value := conv(val); value.IsValid() { + v.Set(value) + } else { + return ConversionError{path, -1} + } + } else { + return fmt.Errorf("schema: converter not found for %v", t) + } + } + return nil +} + +// Errors --------------------------------------------------------------------- + +// ConversionError stores information about a failed conversion. +type ConversionError struct { + Key string // key from the source map. + Index int // index for multi-value fields; -1 for single-value fields. +} + +func (e ConversionError) Error() string { + if e.Index < 0 { + return fmt.Sprintf("schema: error converting value for %q", e.Key) + } + return fmt.Sprintf("schema: error converting value for index %d of %q", + e.Index, e.Key) +} + +// MultiError stores multiple decoding errors. +// +// Borrowed from the App Engine SDK. +type MultiError map[string]error + +func (e MultiError) Error() string { + s := "" + for _, err := range e { + s = err.Error() + break + } + switch len(e) { + case 0: + return "(0 errors)" + case 1: + return s + case 2: + return s + " (and 1 other error)" + } + return fmt.Sprintf("%s (and %d other errors)", s, len(e)-1) +} diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go b/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go new file mode 100644 index 0000000..0188a24 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/schema/decoder_test.go @@ -0,0 +1,1079 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package schema + +import ( + //"reflect" + "testing" +) + +// All cases we want to cover, in a nutshell. +type S1 struct { + F01 int `schema:"f1"` + F02 *int `schema:"f2"` + F03 []int `schema:"f3"` + F04 []*int `schema:"f4"` + F05 *[]int `schema:"f5"` + F06 *[]*int `schema:"f6"` + F07 S2 `schema:"f7"` + F08 *S1 `schema:"f8"` + F09 int `schema:"-"` + F10 []S1 `schema:"f10"` + F11 []*S1 `schema:"f11"` + F12 *[]S1 `schema:"f12"` + F13 *[]*S1 `schema:"f13"` + F14 int `schema:"f14"` +} + +type S2 struct { + F01 *[]*int `schema:"f1"` +} + +func TestAll(t *testing.T) { + v := map[string][]string{ + "f1": {"1"}, + "f2": {"2"}, + "f3": {"31", "32"}, + "f4": {"41", "42"}, + "f5": {"51", "52"}, + "f6": {"61", "62"}, + "f7.f1": {"71", "72"}, + "f8.f8.f7.f1": {"81", "82"}, + "f9": {"9"}, + "f10.0.f10.0.f6": {"101", "102"}, + "f10.0.f10.1.f6": {"103", "104"}, + "f11.0.f11.0.f6": {"111", "112"}, + "f11.0.f11.1.f6": {"113", "114"}, + "f12.0.f12.0.f6": {"121", "122"}, + "f12.0.f12.1.f6": {"123", "124"}, + "f13.0.f13.0.f6": {"131", "132"}, + "f13.0.f13.1.f6": {"133", "134"}, + "f14": {}, + } + f2 := 2 + f41, f42 := 41, 42 + f61, f62 := 61, 62 + f71, f72 := 71, 72 + f81, f82 := 81, 82 + f101, f102, f103, f104 := 101, 102, 103, 104 + f111, f112, f113, f114 := 111, 112, 113, 114 + f121, f122, f123, f124 := 121, 122, 123, 124 + f131, f132, f133, f134 := 131, 132, 133, 134 + e := S1{ + F01: 1, + F02: &f2, + F03: []int{31, 32}, + F04: []*int{&f41, &f42}, + F05: &[]int{51, 52}, + F06: &[]*int{&f61, &f62}, + F07: S2{ + F01: &[]*int{&f71, &f72}, + }, + F08: &S1{ + F08: &S1{ + F07: S2{ + F01: &[]*int{&f81, &f82}, + }, + }, + }, + F09: 0, + F10: []S1{ + S1{ + F10: []S1{ + S1{F06: &[]*int{&f101, &f102}}, + S1{F06: &[]*int{&f103, &f104}}, + }, + }, + }, + F11: []*S1{ + &S1{ + F11: []*S1{ + &S1{F06: &[]*int{&f111, &f112}}, + &S1{F06: &[]*int{&f113, &f114}}, + }, + }, + }, + F12: &[]S1{ + S1{ + F12: &[]S1{ + S1{F06: &[]*int{&f121, &f122}}, + S1{F06: &[]*int{&f123, &f124}}, + }, + }, + }, + F13: &[]*S1{ + &S1{ + F13: &[]*S1{ + &S1{F06: &[]*int{&f131, &f132}}, + &S1{F06: &[]*int{&f133, &f134}}, + }, + }, + }, + F14: 0, + } + + s := &S1{} + _ = NewDecoder().Decode(s, v) + + vals := func(values []*int) []int { + r := make([]int, len(values)) + for k, v := range values { + r[k] = *v + } + return r + } + + if s.F01 != e.F01 { + t.Errorf("f1: expected %v, got %v", e.F01, s.F01) + } + if s.F02 == nil { + t.Errorf("f2: expected %v, got nil", *e.F02) + } else if *s.F02 != *e.F02 { + t.Errorf("f2: expected %v, got %v", *e.F02, *s.F02) + } + if s.F03 == nil { + t.Errorf("f3: expected %v, got nil", e.F03) + } else if len(s.F03) != 2 || s.F03[0] != e.F03[0] || s.F03[1] != e.F03[1] { + t.Errorf("f3: expected %v, got %v", e.F03, s.F03) + } + if s.F04 == nil { + t.Errorf("f4: expected %v, got nil", e.F04) + } else { + if len(s.F04) != 2 || *(s.F04)[0] != *(e.F04)[0] || *(s.F04)[1] != *(e.F04)[1] { + t.Errorf("f4: expected %v, got %v", vals(e.F04), vals(s.F04)) + } + } + if s.F05 == nil { + t.Errorf("f5: expected %v, got nil", e.F05) + } else { + sF05, eF05 := *s.F05, *e.F05 + if len(sF05) != 2 || sF05[0] != eF05[0] || sF05[1] != eF05[1] { + t.Errorf("f5: expected %v, got %v", eF05, sF05) + } + } + if s.F06 == nil { + t.Errorf("f6: expected %v, got nil", vals(*e.F06)) + } else { + sF06, eF06 := *s.F06, *e.F06 + if len(sF06) != 2 || *(sF06)[0] != *(eF06)[0] || *(sF06)[1] != *(eF06)[1] { + t.Errorf("f6: expected %v, got %v", vals(eF06), vals(sF06)) + } + } + if s.F07.F01 == nil { + t.Errorf("f7.f1: expected %v, got nil", vals(*e.F07.F01)) + } else { + sF07, eF07 := *s.F07.F01, *e.F07.F01 + if len(sF07) != 2 || *(sF07)[0] != *(eF07)[0] || *(sF07)[1] != *(eF07)[1] { + t.Errorf("f7.f1: expected %v, got %v", vals(eF07), vals(sF07)) + } + } + if s.F08 == nil { + t.Errorf("f8: got nil") + } else if s.F08.F08 == nil { + t.Errorf("f8.f8: got nil") + } else if s.F08.F08.F07.F01 == nil { + t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F08.F08.F07.F01)) + } else { + sF08, eF08 := *s.F08.F08.F07.F01, *e.F08.F08.F07.F01 + if len(sF08) != 2 || *(sF08)[0] != *(eF08)[0] || *(sF08)[1] != *(eF08)[1] { + t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF08), vals(sF08)) + } + } + if s.F09 != e.F09 { + t.Errorf("f9: expected %v, got %v", e.F09, s.F09) + } + if s.F10 == nil { + t.Errorf("f10: got nil") + } else if len(s.F10) != 1 { + t.Errorf("f10: expected 1 element, got %v", s.F10) + } else { + if len(s.F10[0].F10) != 2 { + t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10) + } else { + sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06 + if sF10 == nil { + t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) + } else { + if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { + t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) + } + } + sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06 + if sF10 == nil { + t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) + } else { + if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { + t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) + } + } + } + } + if s.F11 == nil { + t.Errorf("f11: got nil") + } else if len(s.F11) != 1 { + t.Errorf("f11: expected 1 element, got %v", s.F11) + } else { + if len(s.F11[0].F11) != 2 { + t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11) + } else { + sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06 + if sF11 == nil { + t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) + } else { + if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { + t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) + } + } + sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06 + if sF11 == nil { + t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) + } else { + if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { + t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) + } + } + } + } + if s.F12 == nil { + t.Errorf("f12: got nil") + } else if len(*s.F12) != 1 { + t.Errorf("f12: expected 1 element, got %v", *s.F12) + } else { + sF12, eF12 := *(s.F12), *(e.F12) + if len(*sF12[0].F12) != 2 { + t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12) + } else { + sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06 + if sF122 == nil { + t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) + } else { + if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { + t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) + } + } + sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06 + if sF122 == nil { + t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) + } else { + if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { + t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) + } + } + } + } + if s.F13 == nil { + t.Errorf("f13: got nil") + } else if len(*s.F13) != 1 { + t.Errorf("f13: expected 1 element, got %v", *s.F13) + } else { + sF13, eF13 := *(s.F13), *(e.F13) + if len(*sF13[0].F13) != 2 { + t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13) + } else { + sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06 + if sF132 == nil { + t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) + } else { + if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { + t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) + } + } + sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06 + if sF132 == nil { + t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) + } else { + if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { + t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) + } + } + } + } + if s.F14 != e.F14 { + t.Errorf("f14: expected %v, got %v", e.F14, s.F14) + } +} + +func BenchmarkAll(b *testing.B) { + v := map[string][]string{ + "f1": {"1"}, + "f2": {"2"}, + "f3": {"31", "32"}, + "f4": {"41", "42"}, + "f5": {"51", "52"}, + "f6": {"61", "62"}, + "f7.f1": {"71", "72"}, + "f8.f8.f7.f1": {"81", "82"}, + "f9": {"9"}, + "f10.0.f10.0.f6": {"101", "102"}, + "f10.0.f10.1.f6": {"103", "104"}, + "f11.0.f11.0.f6": {"111", "112"}, + "f11.0.f11.1.f6": {"113", "114"}, + "f12.0.f12.0.f6": {"121", "122"}, + "f12.0.f12.1.f6": {"123", "124"}, + "f13.0.f13.0.f6": {"131", "132"}, + "f13.0.f13.1.f6": {"133", "134"}, + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + s := &S1{} + _ = NewDecoder().Decode(s, v) + } +} + +// ---------------------------------------------------------------------------- + +type S3 struct { + F01 bool + F02 float32 + F03 float64 + F04 int + F05 int8 + F06 int16 + F07 int32 + F08 int64 + F09 string + F10 uint + F11 uint8 + F12 uint16 + F13 uint32 + F14 uint64 +} + +func TestDefaultConverters(t *testing.T) { + v := map[string][]string{ + "F01": {"true"}, + "F02": {"4.2"}, + "F03": {"4.3"}, + "F04": {"-42"}, + "F05": {"-43"}, + "F06": {"-44"}, + "F07": {"-45"}, + "F08": {"-46"}, + "F09": {"foo"}, + "F10": {"42"}, + "F11": {"43"}, + "F12": {"44"}, + "F13": {"45"}, + "F14": {"46"}, + } + e := S3{ + F01: true, + F02: 4.2, + F03: 4.3, + F04: -42, + F05: -43, + F06: -44, + F07: -45, + F08: -46, + F09: "foo", + F10: 42, + F11: 43, + F12: 44, + F13: 45, + F14: 46, + } + s := &S3{} + _ = NewDecoder().Decode(s, v) + if s.F01 != e.F01 { + t.Errorf("F01: expected %v, got %v", e.F01, s.F01) + } + if s.F02 != e.F02 { + t.Errorf("F02: expected %v, got %v", e.F02, s.F02) + } + if s.F03 != e.F03 { + t.Errorf("F03: expected %v, got %v", e.F03, s.F03) + } + if s.F04 != e.F04 { + t.Errorf("F04: expected %v, got %v", e.F04, s.F04) + } + if s.F05 != e.F05 { + t.Errorf("F05: expected %v, got %v", e.F05, s.F05) + } + if s.F06 != e.F06 { + t.Errorf("F06: expected %v, got %v", e.F06, s.F06) + } + if s.F07 != e.F07 { + t.Errorf("F07: expected %v, got %v", e.F07, s.F07) + } + if s.F08 != e.F08 { + t.Errorf("F08: expected %v, got %v", e.F08, s.F08) + } + if s.F09 != e.F09 { + t.Errorf("F09: expected %v, got %v", e.F09, s.F09) + } + if s.F10 != e.F10 { + t.Errorf("F10: expected %v, got %v", e.F10, s.F10) + } + if s.F11 != e.F11 { + t.Errorf("F11: expected %v, got %v", e.F11, s.F11) + } + if s.F12 != e.F12 { + t.Errorf("F12: expected %v, got %v", e.F12, s.F12) + } + if s.F13 != e.F13 { + t.Errorf("F13: expected %v, got %v", e.F13, s.F13) + } + if s.F14 != e.F14 { + t.Errorf("F14: expected %v, got %v", e.F14, s.F14) + } +} + +func TestOn(t *testing.T) { + v := map[string][]string{ + "F01": {"on"}, + } + s := S3{} + err := NewDecoder().Decode(&s, v) + if err != nil { + t.Fatal(err) + } + if !s.F01 { + t.Fatal("Value was not set to true") + } +} + +// ---------------------------------------------------------------------------- + +func TestInlineStruct(t *testing.T) { + s1 := &struct { + F01 bool + }{} + s2 := &struct { + F01 int + }{} + v1 := map[string][]string{ + "F01": {"true"}, + } + v2 := map[string][]string{ + "F01": {"42"}, + } + decoder := NewDecoder() + _ = decoder.Decode(s1, v1) + if s1.F01 != true { + t.Errorf("s1: expected %v, got %v", true, s1.F01) + } + _ = decoder.Decode(s2, v2) + if s2.F01 != 42 { + t.Errorf("s2: expected %v, got %v", 42, s2.F01) + } +} + +// ---------------------------------------------------------------------------- + +type Foo struct { + F01 int + F02 Bar + Bif []Baz +} + +type Bar struct { + F01 string + F02 string + F03 string + F14 string + S05 string + Str string +} + +type Baz struct { + F99 []string +} + +func TestSimpleExample(t *testing.T) { + data := map[string][]string{ + "F01": {"1"}, + "F02.F01": {"S1"}, + "F02.F02": {"S2"}, + "F02.F03": {"S3"}, + "F02.F14": {"S4"}, + "F02.S05": {"S5"}, + "F02.Str": {"Str"}, + "Bif.0.F99": {"A", "B", "C"}, + } + + e := &Foo{ + F01: 1, + F02: Bar{ + F01: "S1", + F02: "S2", + F03: "S3", + F14: "S4", + S05: "S5", + Str: "Str", + }, + Bif: []Baz{{ + F99: []string{"A", "B", "C"}}, + }, + } + + s := &Foo{} + _ = NewDecoder().Decode(s, data) + + if s.F01 != e.F01 { + t.Errorf("F01: expected %v, got %v", e.F01, s.F01) + } + if s.F02.F01 != e.F02.F01 { + t.Errorf("F02.F01: expected %v, got %v", e.F02.F01, s.F02.F01) + } + if s.F02.F02 != e.F02.F02 { + t.Errorf("F02.F02: expected %v, got %v", e.F02.F02, s.F02.F02) + } + if s.F02.F03 != e.F02.F03 { + t.Errorf("F02.F03: expected %v, got %v", e.F02.F03, s.F02.F03) + } + if s.F02.F14 != e.F02.F14 { + t.Errorf("F02.F14: expected %v, got %v", e.F02.F14, s.F02.F14) + } + if s.F02.S05 != e.F02.S05 { + t.Errorf("F02.S05: expected %v, got %v", e.F02.S05, s.F02.S05) + } + if s.F02.Str != e.F02.Str { + t.Errorf("F02.Str: expected %v, got %v", e.F02.Str, s.F02.Str) + } + if len(s.Bif) != len(e.Bif) { + t.Errorf("Bif len: expected %d, got %d", len(e.Bif), len(s.Bif)) + } else { + if len(s.Bif[0].F99) != len(e.Bif[0].F99) { + t.Errorf("Bif[0].F99 len: expected %d, got %d", len(e.Bif[0].F99), len(s.Bif[0].F99)) + } + } +} + +// ---------------------------------------------------------------------------- + +type S4 struct { + F01 int64 + F02 float64 + F03 bool +} + +func TestConversionError(t *testing.T) { + data := map[string][]string{ + "F01": {"foo"}, + "F02": {"bar"}, + "F03": {"baz"}, + } + s := &S4{} + e := NewDecoder().Decode(s, data) + + m := e.(MultiError) + if len(m) != 3 { + t.Errorf("Expected 3 errors, got %v", m) + } +} + +// ---------------------------------------------------------------------------- + +type S5 struct { + F01 []string +} + +func TestEmptyValue(t *testing.T) { + data := map[string][]string{ + "F01": {"", "foo"}, + } + s := &S5{} + NewDecoder().Decode(s, data) + if len(s.F01) != 1 { + t.Errorf("Expected 1 values in F01") + } +} + +func TestEmptyValueZeroEmpty(t *testing.T) { + data := map[string][]string{ + "F01": {"", "foo"}, + } + s := S5{} + d := NewDecoder() + d.ZeroEmpty(true) + err := d.Decode(&s, data) + if err != nil { + t.Fatal(err) + } + if len(s.F01) != 2 { + t.Errorf("Expected 1 values in F01") + } +} + +// ---------------------------------------------------------------------------- + +type S6 struct { + id string +} + +func TestUnexportedField(t *testing.T) { + data := map[string][]string{ + "id": {"identifier"}, + } + s := &S6{} + NewDecoder().Decode(s, data) + if s.id != "" { + t.Errorf("Unexported field expected to be ignored") + } +} + +// ---------------------------------------------------------------------------- + +type S7 struct { + ID string +} + +func TestMultipleValues(t *testing.T) { + data := map[string][]string{ + "ID": {"0", "1"}, + } + + s := S7{} + NewDecoder().Decode(&s, data) + if s.ID != "1" { + t.Errorf("Last defined value must be used when multiple values for same field are provided") + } +} + +type S8 struct { + ID string `json:"id"` +} + +func TestSetAliasTag(t *testing.T) { + data := map[string][]string{ + "id": {"foo"}, + } + + s := S8{} + dec := NewDecoder() + dec.SetAliasTag("json") + dec.Decode(&s, data) + if s.ID != "foo" { + t.Fatalf("Bad value: got %q, want %q", s.ID, "foo") + } +} + +func TestZeroEmpty(t *testing.T) { + data := map[string][]string{ + "F01": {""}, + "F03": {"true"}, + } + s := S4{1, 1, false} + d := NewDecoder() + d.ZeroEmpty(true) + + err := d.Decode(&s, data) + if err != nil { + t.Fatal(err) + } + if s.F01 != 0 { + t.Errorf("F01: got %v, want %v", s.F01, 0) + } + if s.F02 != 1 { + t.Errorf("F02: got %v, want %v", s.F02, 1) + } + if s.F03 != true { + t.Errorf("F03: got %v, want %v", s.F03, true) + } +} + +func TestNoZeroEmpty(t *testing.T) { + data := map[string][]string{ + "F01": {""}, + "F03": {"true"}, + } + s := S4{1, 1, false} + d := NewDecoder() + d.ZeroEmpty(false) + err := d.Decode(&s, data) + if err != nil { + t.Fatal(err) + } + if s.F01 != 1 { + t.Errorf("F01: got %v, want %v", s.F01, 1) + } + if s.F02 != 1 { + t.Errorf("F02: got %v, want %v", s.F02, 1) + } + if s.F03 != true { + t.Errorf("F03: got %v, want %v", s.F03, true) + } +} + +// ---------------------------------------------------------------------------- + +type S9 struct { + Id string +} + +type S10 struct { + S9 +} + +func TestEmbeddedField(t *testing.T) { + data := map[string][]string{ + "Id": {"identifier"}, + } + s := &S10{} + NewDecoder().Decode(s, data) + if s.Id != "identifier" { + t.Errorf("Missing support for embedded fields") + } +} + +type S11 struct { + S10 +} + +func TestMultipleLevelEmbeddedField(t *testing.T) { + data := map[string][]string{ + "Id": {"identifier"}, + } + s := &S11{} + err := NewDecoder().Decode(s, data) + if s.Id != "identifier" { + t.Errorf("Missing support for multiple-level embedded fields (%v)", err) + } +} + +func TestInvalidPath(t *testing.T) { + data := map[string][]string{ + "Foo.Bar": {"baz"}, + } + s := S9{} + err := NewDecoder().Decode(&s, data) + expectedErr := `schema: invalid path "Foo.Bar"` + if err.Error() != expectedErr { + t.Fatalf("got %q, want %q", err, expectedErr) + } +} + +func TestInvalidPathIgnoreUnknownKeys(t *testing.T) { + data := map[string][]string{ + "Foo.Bar": {"baz"}, + } + s := S9{} + dec := NewDecoder() + dec.IgnoreUnknownKeys(true) + err := dec.Decode(&s, data) + if err != nil { + t.Fatal(err) + } +} + +// ---------------------------------------------------------------------------- + +type S1NT struct { + F1 int + F2 *int + F3 []int + F4 []*int + F5 *[]int + F6 *[]*int + F7 S2 + F8 *S1 + F9 int `schema:"-"` + F10 []S1 + F11 []*S1 + F12 *[]S1 + F13 *[]*S1 +} + +func TestAllNT(t *testing.T) { + v := map[string][]string{ + "f1": {"1"}, + "f2": {"2"}, + "f3": {"31", "32"}, + "f4": {"41", "42"}, + "f5": {"51", "52"}, + "f6": {"61", "62"}, + "f7.f1": {"71", "72"}, + "f8.f8.f7.f1": {"81", "82"}, + "f9": {"9"}, + "f10.0.f10.0.f6": {"101", "102"}, + "f10.0.f10.1.f6": {"103", "104"}, + "f11.0.f11.0.f6": {"111", "112"}, + "f11.0.f11.1.f6": {"113", "114"}, + "f12.0.f12.0.f6": {"121", "122"}, + "f12.0.f12.1.f6": {"123", "124"}, + "f13.0.f13.0.f6": {"131", "132"}, + "f13.0.f13.1.f6": {"133", "134"}, + } + f2 := 2 + f41, f42 := 41, 42 + f61, f62 := 61, 62 + f71, f72 := 71, 72 + f81, f82 := 81, 82 + f101, f102, f103, f104 := 101, 102, 103, 104 + f111, f112, f113, f114 := 111, 112, 113, 114 + f121, f122, f123, f124 := 121, 122, 123, 124 + f131, f132, f133, f134 := 131, 132, 133, 134 + e := S1NT{ + F1: 1, + F2: &f2, + F3: []int{31, 32}, + F4: []*int{&f41, &f42}, + F5: &[]int{51, 52}, + F6: &[]*int{&f61, &f62}, + F7: S2{ + F01: &[]*int{&f71, &f72}, + }, + F8: &S1{ + F08: &S1{ + F07: S2{ + F01: &[]*int{&f81, &f82}, + }, + }, + }, + F9: 0, + F10: []S1{ + S1{ + F10: []S1{ + S1{F06: &[]*int{&f101, &f102}}, + S1{F06: &[]*int{&f103, &f104}}, + }, + }, + }, + F11: []*S1{ + &S1{ + F11: []*S1{ + &S1{F06: &[]*int{&f111, &f112}}, + &S1{F06: &[]*int{&f113, &f114}}, + }, + }, + }, + F12: &[]S1{ + S1{ + F12: &[]S1{ + S1{F06: &[]*int{&f121, &f122}}, + S1{F06: &[]*int{&f123, &f124}}, + }, + }, + }, + F13: &[]*S1{ + &S1{ + F13: &[]*S1{ + &S1{F06: &[]*int{&f131, &f132}}, + &S1{F06: &[]*int{&f133, &f134}}, + }, + }, + }, + } + + s := &S1NT{} + _ = NewDecoder().Decode(s, v) + + vals := func(values []*int) []int { + r := make([]int, len(values)) + for k, v := range values { + r[k] = *v + } + return r + } + + if s.F1 != e.F1 { + t.Errorf("f1: expected %v, got %v", e.F1, s.F1) + } + if s.F2 == nil { + t.Errorf("f2: expected %v, got nil", *e.F2) + } else if *s.F2 != *e.F2 { + t.Errorf("f2: expected %v, got %v", *e.F2, *s.F2) + } + if s.F3 == nil { + t.Errorf("f3: expected %v, got nil", e.F3) + } else if len(s.F3) != 2 || s.F3[0] != e.F3[0] || s.F3[1] != e.F3[1] { + t.Errorf("f3: expected %v, got %v", e.F3, s.F3) + } + if s.F4 == nil { + t.Errorf("f4: expected %v, got nil", e.F4) + } else { + if len(s.F4) != 2 || *(s.F4)[0] != *(e.F4)[0] || *(s.F4)[1] != *(e.F4)[1] { + t.Errorf("f4: expected %v, got %v", vals(e.F4), vals(s.F4)) + } + } + if s.F5 == nil { + t.Errorf("f5: expected %v, got nil", e.F5) + } else { + sF5, eF5 := *s.F5, *e.F5 + if len(sF5) != 2 || sF5[0] != eF5[0] || sF5[1] != eF5[1] { + t.Errorf("f5: expected %v, got %v", eF5, sF5) + } + } + if s.F6 == nil { + t.Errorf("f6: expected %v, got nil", vals(*e.F6)) + } else { + sF6, eF6 := *s.F6, *e.F6 + if len(sF6) != 2 || *(sF6)[0] != *(eF6)[0] || *(sF6)[1] != *(eF6)[1] { + t.Errorf("f6: expected %v, got %v", vals(eF6), vals(sF6)) + } + } + if s.F7.F01 == nil { + t.Errorf("f7.f1: expected %v, got nil", vals(*e.F7.F01)) + } else { + sF7, eF7 := *s.F7.F01, *e.F7.F01 + if len(sF7) != 2 || *(sF7)[0] != *(eF7)[0] || *(sF7)[1] != *(eF7)[1] { + t.Errorf("f7.f1: expected %v, got %v", vals(eF7), vals(sF7)) + } + } + if s.F8 == nil { + t.Errorf("f8: got nil") + } else if s.F8.F08 == nil { + t.Errorf("f8.f8: got nil") + } else if s.F8.F08.F07.F01 == nil { + t.Errorf("f8.f8.f7.f1: expected %v, got nil", vals(*e.F8.F08.F07.F01)) + } else { + sF8, eF8 := *s.F8.F08.F07.F01, *e.F8.F08.F07.F01 + if len(sF8) != 2 || *(sF8)[0] != *(eF8)[0] || *(sF8)[1] != *(eF8)[1] { + t.Errorf("f8.f8.f7.f1: expected %v, got %v", vals(eF8), vals(sF8)) + } + } + if s.F9 != e.F9 { + t.Errorf("f9: expected %v, got %v", e.F9, s.F9) + } + if s.F10 == nil { + t.Errorf("f10: got nil") + } else if len(s.F10) != 1 { + t.Errorf("f10: expected 1 element, got %v", s.F10) + } else { + if len(s.F10[0].F10) != 2 { + t.Errorf("f10.0.f10: expected 1 element, got %v", s.F10[0].F10) + } else { + sF10, eF10 := *s.F10[0].F10[0].F06, *e.F10[0].F10[0].F06 + if sF10 == nil { + t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) + } else { + if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { + t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) + } + } + sF10, eF10 = *s.F10[0].F10[1].F06, *e.F10[0].F10[1].F06 + if sF10 == nil { + t.Errorf("f10.0.f10.0.f6: expected %v, got nil", vals(eF10)) + } else { + if len(sF10) != 2 || *(sF10)[0] != *(eF10)[0] || *(sF10)[1] != *(eF10)[1] { + t.Errorf("f10.0.f10.0.f6: expected %v, got %v", vals(eF10), vals(sF10)) + } + } + } + } + if s.F11 == nil { + t.Errorf("f11: got nil") + } else if len(s.F11) != 1 { + t.Errorf("f11: expected 1 element, got %v", s.F11) + } else { + if len(s.F11[0].F11) != 2 { + t.Errorf("f11.0.f11: expected 1 element, got %v", s.F11[0].F11) + } else { + sF11, eF11 := *s.F11[0].F11[0].F06, *e.F11[0].F11[0].F06 + if sF11 == nil { + t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) + } else { + if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { + t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) + } + } + sF11, eF11 = *s.F11[0].F11[1].F06, *e.F11[0].F11[1].F06 + if sF11 == nil { + t.Errorf("f11.0.f11.0.f6: expected %v, got nil", vals(eF11)) + } else { + if len(sF11) != 2 || *(sF11)[0] != *(eF11)[0] || *(sF11)[1] != *(eF11)[1] { + t.Errorf("f11.0.f11.0.f6: expected %v, got %v", vals(eF11), vals(sF11)) + } + } + } + } + if s.F12 == nil { + t.Errorf("f12: got nil") + } else if len(*s.F12) != 1 { + t.Errorf("f12: expected 1 element, got %v", *s.F12) + } else { + sF12, eF12 := *(s.F12), *(e.F12) + if len(*sF12[0].F12) != 2 { + t.Errorf("f12.0.f12: expected 1 element, got %v", *sF12[0].F12) + } else { + sF122, eF122 := *(*sF12[0].F12)[0].F06, *(*eF12[0].F12)[0].F06 + if sF122 == nil { + t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) + } else { + if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { + t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) + } + } + sF122, eF122 = *(*sF12[0].F12)[1].F06, *(*eF12[0].F12)[1].F06 + if sF122 == nil { + t.Errorf("f12.0.f12.0.f6: expected %v, got nil", vals(eF122)) + } else { + if len(sF122) != 2 || *(sF122)[0] != *(eF122)[0] || *(sF122)[1] != *(eF122)[1] { + t.Errorf("f12.0.f12.0.f6: expected %v, got %v", vals(eF122), vals(sF122)) + } + } + } + } + if s.F13 == nil { + t.Errorf("f13: got nil") + } else if len(*s.F13) != 1 { + t.Errorf("f13: expected 1 element, got %v", *s.F13) + } else { + sF13, eF13 := *(s.F13), *(e.F13) + if len(*sF13[0].F13) != 2 { + t.Errorf("f13.0.f13: expected 1 element, got %v", *sF13[0].F13) + } else { + sF132, eF132 := *(*sF13[0].F13)[0].F06, *(*eF13[0].F13)[0].F06 + if sF132 == nil { + t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) + } else { + if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { + t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) + } + } + sF132, eF132 = *(*sF13[0].F13)[1].F06, *(*eF13[0].F13)[1].F06 + if sF132 == nil { + t.Errorf("f13.0.f13.0.f6: expected %v, got nil", vals(eF132)) + } else { + if len(sF132) != 2 || *(sF132)[0] != *(eF132)[0] || *(sF132)[1] != *(eF132)[1] { + t.Errorf("f13.0.f13.0.f6: expected %v, got %v", vals(eF132), vals(sF132)) + } + } + } + } +} + +// ---------------------------------------------------------------------------- + +type S12A struct { + ID []int +} + +func TestCSVSlice(t *testing.T) { + data := map[string][]string{ + "ID": {"0,1"}, + } + + s := S12A{} + NewDecoder().Decode(&s, data) + if len(s.ID) != 2 { + t.Errorf("Expected two values in the result list, got %+v", s.ID) + } + if s.ID[0] != 0 || s.ID[1] != 1 { + t.Errorf("Expected []{0, 1} got %+v", s) + } +} + +type S12B struct { + ID []string +} + +//Decode should not split on , into a slice for string only +func TestCSVStringSlice(t *testing.T) { + data := map[string][]string{ + "ID": {"0,1"}, + } + + s := S12B{} + NewDecoder().Decode(&s, data) + if len(s.ID) != 1 { + t.Errorf("Expected one value in the result list, got %+v", s.ID) + } + if s.ID[0] != "0,1" { + t.Errorf("Expected []{0, 1} got %+v", s) + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/schema/doc.go b/Godeps/_workspace/src/github.com/gorilla/schema/doc.go new file mode 100644 index 0000000..a8df5a1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/schema/doc.go @@ -0,0 +1,123 @@ +// Copyright 2012 The Gorilla Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package gorilla/schema fills a struct with form values. + +The basic usage is really simple. Given this struct: + + type Person struct { + Name string + Phone string + } + +...we can fill it passing a map to the Load() function: + + values := map[string][]string{ + "Name": {"John"}, + "Phone": {"999-999-999"}, + } + person := new(Person) + decoder := schema.NewDecoder() + decoder.Decode(person, values) + +This is just a simple example and it doesn't make a lot of sense to create +the map manually. Typically it will come from a http.Request object and +will be of type url.Values: http.Request.Form or http.Request.MultipartForm: + + func MyHandler(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + + if err != nil { + // Handle error + } + + decoder := schema.NewDecoder() + // r.PostForm is a map of our POST form values + err := decoder.Decode(person, r.PostForm) + + if err != nil { + // Handle error + } + + // Do something with person.Name or person.Phone + } + +Note: it is a good idea to set a Decoder instance as a package global, +because it caches meta-data about structs, and a instance can be shared safely: + + var decoder = schema.NewDecoder() + +To define custom names for fields, use a struct tag "schema". To not populate +certain fields, use a dash for the name and it will be ignored: + + type Person struct { + Name string `schema:"name"` // custom name + Phone string `schema:"phone"` // custom name + Admin bool `schema:"-"` // this field is never set + } + +The supported field types in the destination struct are: + + * bool + * float variants (float32, float64) + * int variants (int, int8, int16, int32, int64) + * string + * uint variants (uint, uint8, uint16, uint32, uint64) + * struct + * a pointer to one of the above types + * a slice or a pointer to a slice of one of the above types + +Non-supported types are simply ignored, however custom types can be registered +to be converted. + +To fill nested structs, keys must use a dotted notation as the "path" for the +field. So for example, to fill the struct Person below: + + type Phone struct { + Label string + Number string + } + + type Person struct { + Name string + Phone Phone + } + +...the source map must have the keys "Name", "Phone.Label" and "Phone.Number". +This means that an HTML form to fill a Person struct must look like this: + +
      + + + +
      + +Single values are filled using the first value for a key from the source map. +Slices are filled using all values for a key from the source map. So to fill +a Person with multiple Phone values, like: + + type Person struct { + Name string + Phones []Phone + } + +...an HTML form that accepts three Phone values would look like this: + +
      + + + + + + + +
      + +Notice that only for slices of structs the slice index is required. +This is needed for disambiguation: if the nested struct also had a slice +field, we could not translate multiple values to it if we did not use an +index for the parent struct. +*/ +package schema diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore b/Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml b/Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml new file mode 100644 index 0000000..8687342 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.1 + - 1.2 + - tip diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS b/Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS new file mode 100644 index 0000000..b003eca --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/AUTHORS @@ -0,0 +1,8 @@ +# This is the official list of Gorilla WebSocket authors for copyright +# purposes. +# +# Please keep the list sorted. + +Gary Burd +Joachim Bauch + diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE b/Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE new file mode 100644 index 0000000..9171c97 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/README.md b/Godeps/_workspace/src/github.com/gorilla/websocket/README.md new file mode 100644 index 0000000..9ad75a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/README.md @@ -0,0 +1,59 @@ +# Gorilla WebSocket + +Gorilla WebSocket is a [Go](http://golang.org/) implementation of the +[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. + +### Documentation + +* [API Reference](http://godoc.org/github.com/gorilla/websocket) +* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) +* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch) + +### Status + +The Gorilla WebSocket package provides a complete and tested implementation of +the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The +package API is stable. + +### Installation + + go get github.com/gorilla/websocket + +### Protocol Compliance + +The Gorilla WebSocket package passes the server tests in the [Autobahn Test +Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn +subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). + +### Gorilla WebSocket compared with other packages + + + + + + + + + + + + + + + + + + +
      github.com/gorillagolang.org/x/net
      RFC 6455 Features
      Passes Autobahn Test SuiteYesNo
      Receive fragmented messageYesNo, see note 1
      Send close messageYesNo
      Send pings and receive pongsYesNo
      Get the type of a received data messageYesYes, see note 2
      Other Features
      Limit size of received messageYesNo
      Read message using io.ReaderYesNo, see note 3
      Write message using io.WriteCloserYesNo, see note 3
      + +Notes: + +1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html). +2. The application can get the type of a received data message by implementing + a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal) + function. +3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries. + Read returns when the input buffer is full or a frame boundary is + encountered. Each call to Write sends a single frame message. The Gorilla + io.Reader and io.WriteCloser operate on a single WebSocket message. + diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go new file mode 100644 index 0000000..f66fc36 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/bench_test.go @@ -0,0 +1,19 @@ +// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "testing" +) + +func BenchmarkMaskBytes(b *testing.B) { + var key [4]byte + data := make([]byte, 1024) + pos := 0 + for i := 0; i < b.N; i++ { + pos = maskBytes(key, pos, data) + } + b.SetBytes(int64(len(data))) +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/client.go b/Godeps/_workspace/src/github.com/gorilla/websocket/client.go new file mode 100644 index 0000000..c25d24f --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/client.go @@ -0,0 +1,235 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/tls" + "errors" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// ErrBadHandshake is returned when the server response to opening handshake is +// invalid. +var ErrBadHandshake = errors.New("websocket: bad handshake") + +// NewClient creates a new client connection using the given net connection. +// The URL u specifies the host and request URI. Use requestHeader to specify +// the origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies +// (Cookie). Use the response.Header to get the selected subprotocol +// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). +// +// If the WebSocket handshake fails, ErrBadHandshake is returned along with a +// non-nil *http.Response so that callers can handle redirects, authentication, +// etc. +func NewClient(netConn net.Conn, u *url.URL, requestHeader http.Header, readBufSize, writeBufSize int) (c *Conn, response *http.Response, err error) { + challengeKey, err := generateChallengeKey() + if err != nil { + return nil, nil, err + } + acceptKey := computeAcceptKey(challengeKey) + + c = newConn(netConn, false, readBufSize, writeBufSize) + p := c.writeBuf[:0] + p = append(p, "GET "...) + p = append(p, u.RequestURI()...) + p = append(p, " HTTP/1.1\r\nHost: "...) + p = append(p, u.Host...) + // "Upgrade" is capitalized for servers that do not use case insensitive + // comparisons on header tokens. + p = append(p, "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...) + p = append(p, challengeKey...) + p = append(p, "\r\n"...) + for k, vs := range requestHeader { + for _, v := range vs { + p = append(p, k...) + p = append(p, ": "...) + p = append(p, v...) + p = append(p, "\r\n"...) + } + } + p = append(p, "\r\n"...) + + if _, err := netConn.Write(p); err != nil { + return nil, nil, err + } + + resp, err := http.ReadResponse(c.br, &http.Request{Method: "GET", URL: u}) + if err != nil { + return nil, nil, err + } + if resp.StatusCode != 101 || + !strings.EqualFold(resp.Header.Get("Upgrade"), "websocket") || + !strings.EqualFold(resp.Header.Get("Connection"), "upgrade") || + resp.Header.Get("Sec-Websocket-Accept") != acceptKey { + return nil, resp, ErrBadHandshake + } + c.subprotocol = resp.Header.Get("Sec-Websocket-Protocol") + return c, resp, nil +} + +// A Dialer contains options for connecting to WebSocket server. +type Dialer struct { + // NetDial specifies the dial function for creating TCP connections. If + // NetDial is nil, net.Dial is used. + NetDial func(network, addr string) (net.Conn, error) + + // TLSClientConfig specifies the TLS configuration to use with tls.Client. + // If nil, the default configuration is used. + TLSClientConfig *tls.Config + + // HandshakeTimeout specifies the duration for the handshake to complete. + HandshakeTimeout time.Duration + + // Input and output buffer sizes. If the buffer size is zero, then a + // default value of 4096 is used. + ReadBufferSize, WriteBufferSize int + + // Subprotocols specifies the client's requested subprotocols. + Subprotocols []string +} + +var errMalformedURL = errors.New("malformed ws or wss URL") + +// parseURL parses the URL. The url.Parse function is not used here because +// url.Parse mangles the path. +func parseURL(s string) (*url.URL, error) { + // From the RFC: + // + // ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ] + // wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ] + // + // We don't use the net/url parser here because the dialer interface does + // not provide a way for applications to work around percent deocding in + // the net/url parser. + + var u url.URL + switch { + case strings.HasPrefix(s, "ws://"): + u.Scheme = "ws" + s = s[len("ws://"):] + case strings.HasPrefix(s, "wss://"): + u.Scheme = "wss" + s = s[len("wss://"):] + default: + return nil, errMalformedURL + } + + u.Host = s + u.Opaque = "/" + if i := strings.Index(s, "/"); i >= 0 { + u.Host = s[:i] + u.Opaque = s[i:] + } + + return &u, nil +} + +func hostPortNoPort(u *url.URL) (hostPort, hostNoPort string) { + hostPort = u.Host + hostNoPort = u.Host + if i := strings.LastIndex(u.Host, ":"); i > strings.LastIndex(u.Host, "]") { + hostNoPort = hostNoPort[:i] + } else { + if u.Scheme == "wss" { + hostPort += ":443" + } else { + hostPort += ":80" + } + } + return hostPort, hostNoPort +} + +// DefaultDialer is a dialer with all fields set to the default zero values. +var DefaultDialer *Dialer + +// Dial creates a new client connection. Use requestHeader to specify the +// origin (Origin), subprotocols (Sec-WebSocket-Protocol) and cookies (Cookie). +// Use the response.Header to get the selected subprotocol +// (Sec-WebSocket-Protocol) and cookies (Set-Cookie). +// +// If the WebSocket handshake fails, ErrBadHandshake is returned along with a +// non-nil *http.Response so that callers can handle redirects, authentication, +// etc. +func (d *Dialer) Dial(urlStr string, requestHeader http.Header) (*Conn, *http.Response, error) { + u, err := parseURL(urlStr) + if err != nil { + return nil, nil, err + } + + hostPort, hostNoPort := hostPortNoPort(u) + + if d == nil { + d = &Dialer{} + } + + var deadline time.Time + if d.HandshakeTimeout != 0 { + deadline = time.Now().Add(d.HandshakeTimeout) + } + + netDial := d.NetDial + if netDial == nil { + netDialer := &net.Dialer{Deadline: deadline} + netDial = netDialer.Dial + } + + netConn, err := netDial("tcp", hostPort) + if err != nil { + return nil, nil, err + } + + defer func() { + if netConn != nil { + netConn.Close() + } + }() + + if err := netConn.SetDeadline(deadline); err != nil { + return nil, nil, err + } + + if u.Scheme == "wss" { + cfg := d.TLSClientConfig + if cfg == nil { + cfg = &tls.Config{ServerName: hostNoPort} + } else if cfg.ServerName == "" { + shallowCopy := *cfg + cfg = &shallowCopy + cfg.ServerName = hostNoPort + } + tlsConn := tls.Client(netConn, cfg) + netConn = tlsConn + if err := tlsConn.Handshake(); err != nil { + return nil, nil, err + } + if !cfg.InsecureSkipVerify { + if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { + return nil, nil, err + } + } + } + + if len(d.Subprotocols) > 0 { + h := http.Header{} + for k, v := range requestHeader { + h[k] = v + } + h.Set("Sec-Websocket-Protocol", strings.Join(d.Subprotocols, ", ")) + requestHeader = h + } + + conn, resp, err := NewClient(netConn, u, requestHeader, d.ReadBufferSize, d.WriteBufferSize) + if err != nil { + return nil, resp, err + } + + netConn.SetDeadline(time.Time{}) + netConn = nil // to avoid close in defer. + return conn, resp, nil +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go new file mode 100644 index 0000000..8c608f6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/client_server_test.go @@ -0,0 +1,249 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/tls" + "crypto/x509" + "io" + "net" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + "time" +) + +var cstUpgrader = Upgrader{ + Subprotocols: []string{"p0", "p1"}, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { + http.Error(w, reason.Error(), status) + }, +} + +var cstDialer = Dialer{ + Subprotocols: []string{"p1", "p2"}, + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +type cstHandler struct{ *testing.T } + +type Server struct { + *httptest.Server + URL string +} + +func newServer(t *testing.T) *Server { + var s Server + s.Server = httptest.NewServer(cstHandler{t}) + s.URL = "ws" + s.Server.URL[len("http"):] + return &s +} + +func newTLSServer(t *testing.T) *Server { + var s Server + s.Server = httptest.NewTLSServer(cstHandler{t}) + s.URL = "ws" + s.Server.URL[len("http"):] + return &s +} + +func (t cstHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + t.Logf("method %s not allowed", r.Method) + http.Error(w, "method not allowed", 405) + return + } + subprotos := Subprotocols(r) + if !reflect.DeepEqual(subprotos, cstDialer.Subprotocols) { + t.Logf("subprotols=%v, want %v", subprotos, cstDialer.Subprotocols) + http.Error(w, "bad protocol", 400) + return + } + ws, err := cstUpgrader.Upgrade(w, r, http.Header{"Set-Cookie": {"sessionID=1234"}}) + if err != nil { + t.Logf("Upgrade: %v", err) + return + } + defer ws.Close() + + if ws.Subprotocol() != "p1" { + t.Logf("Subprotocol() = %s, want p1", ws.Subprotocol()) + ws.Close() + return + } + op, rd, err := ws.NextReader() + if err != nil { + t.Logf("NextReader: %v", err) + return + } + wr, err := ws.NextWriter(op) + if err != nil { + t.Logf("NextWriter: %v", err) + return + } + if _, err = io.Copy(wr, rd); err != nil { + t.Logf("NextWriter: %v", err) + return + } + if err := wr.Close(); err != nil { + t.Logf("Close: %v", err) + return + } +} + +func sendRecv(t *testing.T, ws *Conn) { + const message = "Hello World!" + if err := ws.SetWriteDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatalf("SetWriteDeadline: %v", err) + } + if err := ws.WriteMessage(TextMessage, []byte(message)); err != nil { + t.Fatalf("WriteMessage: %v", err) + } + if err := ws.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatalf("SetReadDeadline: %v", err) + } + _, p, err := ws.ReadMessage() + if err != nil { + t.Fatalf("ReadMessage: %v", err) + } + if string(p) != message { + t.Fatalf("message=%s, want %s", p, message) + } +} + +func TestDial(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, _, err := cstDialer.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +func TestDialTLS(t *testing.T) { + s := newTLSServer(t) + defer s.Close() + + certs := x509.NewCertPool() + for _, c := range s.TLS.Certificates { + roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) + if err != nil { + t.Fatalf("error parsing server's root cert: %v", err) + } + for _, root := range roots { + certs.AddCert(root) + } + } + + u, _ := url.Parse(s.URL) + d := cstDialer + d.NetDial = func(network, addr string) (net.Conn, error) { return net.Dial(network, u.Host) } + d.TLSClientConfig = &tls.Config{RootCAs: certs} + ws, _, err := d.Dial("wss://example.com/", nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +func xTestDialTLSBadCert(t *testing.T) { + s := newTLSServer(t) + defer s.Close() + + ws, _, err := cstDialer.Dial(s.URL, nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } +} + +func xTestDialTLSNoVerify(t *testing.T) { + s := newTLSServer(t) + defer s.Close() + + d := cstDialer + d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + ws, _, err := d.Dial(s.URL, nil) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + sendRecv(t, ws) +} + +func TestDialTimeout(t *testing.T) { + s := newServer(t) + defer s.Close() + + d := cstDialer + d.HandshakeTimeout = -1 + ws, _, err := d.Dial(s.URL, nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } +} + +func TestDialBadScheme(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, _, err := cstDialer.Dial(s.Server.URL, nil) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } +} + +func TestDialBadOrigin(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {"bad"}}) + if err == nil { + ws.Close() + t.Fatalf("Dial: nil") + } + if resp == nil { + t.Fatalf("resp=nil, err=%v", err) + } + if resp.StatusCode != http.StatusForbidden { + t.Fatalf("status=%d, want %d", resp.StatusCode, http.StatusForbidden) + } +} + +func TestHandshake(t *testing.T) { + s := newServer(t) + defer s.Close() + + ws, resp, err := cstDialer.Dial(s.URL, http.Header{"Origin": {s.URL}}) + if err != nil { + t.Fatalf("Dial: %v", err) + } + defer ws.Close() + + var sessionID string + for _, c := range resp.Cookies() { + if c.Name == "sessionID" { + sessionID = c.Value + } + } + if sessionID != "1234" { + t.Error("Set-Cookie not received from the server.") + } + + if ws.Subprotocol() != "p1" { + t.Errorf("ws.Subprotocol() = %s, want p1", ws.Subprotocol()) + } + sendRecv(t, ws) +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go new file mode 100644 index 0000000..d2f2ebd --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/client_test.go @@ -0,0 +1,63 @@ +// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "net/url" + "reflect" + "testing" +) + +var parseURLTests = []struct { + s string + u *url.URL +}{ + {"ws://example.com/", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, + {"ws://example.com", &url.URL{Scheme: "ws", Host: "example.com", Opaque: "/"}}, + {"ws://example.com:7777/", &url.URL{Scheme: "ws", Host: "example.com:7777", Opaque: "/"}}, + {"wss://example.com/", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/"}}, + {"wss://example.com/a/b", &url.URL{Scheme: "wss", Host: "example.com", Opaque: "/a/b"}}, + {"ss://example.com/a/b", nil}, +} + +func TestParseURL(t *testing.T) { + for _, tt := range parseURLTests { + u, err := parseURL(tt.s) + if tt.u != nil && err != nil { + t.Errorf("parseURL(%q) returned error %v", tt.s, err) + continue + } + if tt.u == nil && err == nil { + t.Errorf("parseURL(%q) did not return error", tt.s) + continue + } + if !reflect.DeepEqual(u, tt.u) { + t.Errorf("parseURL(%q) returned %v, want %v", tt.s, u, tt.u) + continue + } + } +} + +var hostPortNoPortTests = []struct { + u *url.URL + hostPort, hostNoPort string +}{ + {&url.URL{Scheme: "ws", Host: "example.com"}, "example.com:80", "example.com"}, + {&url.URL{Scheme: "wss", Host: "example.com"}, "example.com:443", "example.com"}, + {&url.URL{Scheme: "ws", Host: "example.com:7777"}, "example.com:7777", "example.com"}, + {&url.URL{Scheme: "wss", Host: "example.com:7777"}, "example.com:7777", "example.com"}, +} + +func TestHostPortNoPort(t *testing.T) { + for _, tt := range hostPortNoPortTests { + hostPort, hostNoPort := hostPortNoPort(tt.u) + if hostPort != tt.hostPort { + t.Errorf("hostPortNoPort(%v) returned hostPort %q, want %q", tt.u, hostPort, tt.hostPort) + } + if hostNoPort != tt.hostNoPort { + t.Errorf("hostPortNoPort(%v) returned hostNoPort %q, want %q", tt.u, hostNoPort, tt.hostNoPort) + } + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/conn.go b/Godeps/_workspace/src/github.com/gorilla/websocket/conn.go new file mode 100644 index 0000000..86c35e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/conn.go @@ -0,0 +1,825 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "encoding/binary" + "errors" + "io" + "io/ioutil" + "math/rand" + "net" + "strconv" + "time" +) + +const ( + maxFrameHeaderSize = 2 + 8 + 4 // Fixed header + length + mask + maxControlFramePayloadSize = 125 + finalBit = 1 << 7 + maskBit = 1 << 7 + writeWait = time.Second + + defaultReadBufferSize = 4096 + defaultWriteBufferSize = 4096 + + continuationFrame = 0 + noFrame = -1 +) + +// Close codes defined in RFC 6455, section 11.7. +const ( + CloseNormalClosure = 1000 + CloseGoingAway = 1001 + CloseProtocolError = 1002 + CloseUnsupportedData = 1003 + CloseNoStatusReceived = 1005 + CloseAbnormalClosure = 1006 + CloseInvalidFramePayloadData = 1007 + ClosePolicyViolation = 1008 + CloseMessageTooBig = 1009 + CloseMandatoryExtension = 1010 + CloseInternalServerErr = 1011 + CloseTLSHandshake = 1015 +) + +// The message types are defined in RFC 6455, section 11.8. +const ( + // TextMessage denotes a text data message. The text message payload is + // interpreted as UTF-8 encoded text data. + TextMessage = 1 + + // BinaryMessage denotes a binary data message. + BinaryMessage = 2 + + // CloseMessage denotes a close control message. The optional message + // payload contains a numeric code and text. Use the FormatCloseMessage + // function to format a close message payload. + CloseMessage = 8 + + // PingMessage denotes a ping control message. The optional message payload + // is UTF-8 encoded text. + PingMessage = 9 + + // PongMessage denotes a ping control message. The optional message payload + // is UTF-8 encoded text. + PongMessage = 10 +) + +// ErrCloseSent is returned when the application writes a message to the +// connection after sending a close message. +var ErrCloseSent = errors.New("websocket: close sent") + +// ErrReadLimit is returned when reading a message that is larger than the +// read limit set for the connection. +var ErrReadLimit = errors.New("websocket: read limit exceeded") + +// netError satisfies the net Error interface. +type netError struct { + msg string + temporary bool + timeout bool +} + +func (e *netError) Error() string { return e.msg } +func (e *netError) Temporary() bool { return e.temporary } +func (e *netError) Timeout() bool { return e.timeout } + +// closeError represents close frame. +type closeError struct { + code int + text string +} + +func (e *closeError) Error() string { + return "websocket: close " + strconv.Itoa(e.code) + " " + e.text +} + +var ( + errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true} + errUnexpectedEOF = &closeError{code: CloseAbnormalClosure, text: io.ErrUnexpectedEOF.Error()} + errBadWriteOpCode = errors.New("websocket: bad write message type") + errWriteClosed = errors.New("websocket: write closed") + errInvalidControlFrame = errors.New("websocket: invalid control frame") +) + +func hideTempErr(err error) error { + if e, ok := err.(net.Error); ok && e.Temporary() { + err = &netError{msg: e.Error(), timeout: e.Timeout()} + } + return err +} + +func isControl(frameType int) bool { + return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage +} + +func isData(frameType int) bool { + return frameType == TextMessage || frameType == BinaryMessage +} + +func maskBytes(key [4]byte, pos int, b []byte) int { + for i := range b { + b[i] ^= key[pos&3] + pos++ + } + return pos & 3 +} + +func newMaskKey() [4]byte { + n := rand.Uint32() + return [4]byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)} +} + +// Conn represents a WebSocket connection. +type Conn struct { + conn net.Conn + isServer bool + subprotocol string + + // Write fields + mu chan bool // used as mutex to protect write to conn and closeSent + closeSent bool // true if close message was sent + + // Message writer fields. + writeErr error + writeBuf []byte // frame is constructed in this buffer. + writePos int // end of data in writeBuf. + writeFrameType int // type of the current frame. + writeSeq int // incremented to invalidate message writers. + writeDeadline time.Time + + // Read fields + readErr error + br *bufio.Reader + readRemaining int64 // bytes remaining in current frame. + readFinal bool // true the current message has more frames. + readSeq int // incremented to invalidate message readers. + readLength int64 // Message size. + readLimit int64 // Maximum message size. + readMaskPos int + readMaskKey [4]byte + handlePong func(string) error + handlePing func(string) error +} + +func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int) *Conn { + mu := make(chan bool, 1) + mu <- true + + if readBufferSize == 0 { + readBufferSize = defaultReadBufferSize + } + if writeBufferSize == 0 { + writeBufferSize = defaultWriteBufferSize + } + + c := &Conn{ + isServer: isServer, + br: bufio.NewReaderSize(conn, readBufferSize), + conn: conn, + mu: mu, + readFinal: true, + writeBuf: make([]byte, writeBufferSize+maxFrameHeaderSize), + writeFrameType: noFrame, + writePos: maxFrameHeaderSize, + } + c.SetPingHandler(nil) + c.SetPongHandler(nil) + return c +} + +// Subprotocol returns the negotiated protocol for the connection. +func (c *Conn) Subprotocol() string { + return c.subprotocol +} + +// Close closes the underlying network connection without sending or waiting for a close frame. +func (c *Conn) Close() error { + return c.conn.Close() +} + +// LocalAddr returns the local network address. +func (c *Conn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +// RemoteAddr returns the remote network address. +func (c *Conn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +// Write methods + +func (c *Conn) write(frameType int, deadline time.Time, bufs ...[]byte) error { + <-c.mu + defer func() { c.mu <- true }() + + if c.closeSent { + return ErrCloseSent + } else if frameType == CloseMessage { + c.closeSent = true + } + + c.conn.SetWriteDeadline(deadline) + for _, buf := range bufs { + if len(buf) > 0 { + n, err := c.conn.Write(buf) + if n != len(buf) { + // Close on partial write. + c.conn.Close() + } + if err != nil { + return err + } + } + } + return nil +} + +// WriteControl writes a control message with the given deadline. The allowed +// message types are CloseMessage, PingMessage and PongMessage. +func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) error { + if !isControl(messageType) { + return errBadWriteOpCode + } + if len(data) > maxControlFramePayloadSize { + return errInvalidControlFrame + } + + b0 := byte(messageType) | finalBit + b1 := byte(len(data)) + if !c.isServer { + b1 |= maskBit + } + + buf := make([]byte, 0, maxFrameHeaderSize+maxControlFramePayloadSize) + buf = append(buf, b0, b1) + + if c.isServer { + buf = append(buf, data...) + } else { + key := newMaskKey() + buf = append(buf, key[:]...) + buf = append(buf, data...) + maskBytes(key, 0, buf[6:]) + } + + d := time.Hour * 1000 + if !deadline.IsZero() { + d = deadline.Sub(time.Now()) + if d < 0 { + return errWriteTimeout + } + } + + timer := time.NewTimer(d) + select { + case <-c.mu: + timer.Stop() + case <-timer.C: + return errWriteTimeout + } + defer func() { c.mu <- true }() + + if c.closeSent { + return ErrCloseSent + } else if messageType == CloseMessage { + c.closeSent = true + } + + c.conn.SetWriteDeadline(deadline) + n, err := c.conn.Write(buf) + if n != 0 && n != len(buf) { + c.conn.Close() + } + return err +} + +// NextWriter returns a writer for the next message to send. The writer's +// Close method flushes the complete message to the network. +// +// There can be at most one open writer on a connection. NextWriter closes the +// previous writer if the application has not already done so. +// +// The NextWriter method and the writers returned from the method cannot be +// accessed by more than one goroutine at a time. +func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { + if c.writeErr != nil { + return nil, c.writeErr + } + + if c.writeFrameType != noFrame { + if err := c.flushFrame(true, nil); err != nil { + return nil, err + } + } + + if !isControl(messageType) && !isData(messageType) { + return nil, errBadWriteOpCode + } + + c.writeFrameType = messageType + return messageWriter{c, c.writeSeq}, nil +} + +func (c *Conn) flushFrame(final bool, extra []byte) error { + length := c.writePos - maxFrameHeaderSize + len(extra) + + // Check for invalid control frames. + if isControl(c.writeFrameType) && + (!final || length > maxControlFramePayloadSize) { + c.writeSeq++ + c.writeFrameType = noFrame + c.writePos = maxFrameHeaderSize + return errInvalidControlFrame + } + + b0 := byte(c.writeFrameType) + if final { + b0 |= finalBit + } + b1 := byte(0) + if !c.isServer { + b1 |= maskBit + } + + // Assume that the frame starts at beginning of c.writeBuf. + framePos := 0 + if c.isServer { + // Adjust up if mask not included in the header. + framePos = 4 + } + + switch { + case length >= 65536: + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | 127 + binary.BigEndian.PutUint64(c.writeBuf[framePos+2:], uint64(length)) + case length > 125: + framePos += 6 + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | 126 + binary.BigEndian.PutUint16(c.writeBuf[framePos+2:], uint16(length)) + default: + framePos += 8 + c.writeBuf[framePos] = b0 + c.writeBuf[framePos+1] = b1 | byte(length) + } + + if !c.isServer { + key := newMaskKey() + copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) + maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:c.writePos]) + if len(extra) > 0 { + c.writeErr = errors.New("websocket: internal error, extra used in client mode") + return c.writeErr + } + } + + // Write the buffers to the connection. + c.writeErr = c.write(c.writeFrameType, c.writeDeadline, c.writeBuf[framePos:c.writePos], extra) + + // Setup for next frame. + c.writePos = maxFrameHeaderSize + c.writeFrameType = continuationFrame + if final { + c.writeSeq++ + c.writeFrameType = noFrame + } + return c.writeErr +} + +type messageWriter struct { + c *Conn + seq int +} + +func (w messageWriter) err() error { + c := w.c + if c.writeSeq != w.seq { + return errWriteClosed + } + if c.writeErr != nil { + return c.writeErr + } + return nil +} + +func (w messageWriter) ncopy(max int) (int, error) { + n := len(w.c.writeBuf) - w.c.writePos + if n <= 0 { + if err := w.c.flushFrame(false, nil); err != nil { + return 0, err + } + n = len(w.c.writeBuf) - w.c.writePos + } + if n > max { + n = max + } + return n, nil +} + +func (w messageWriter) write(final bool, p []byte) (int, error) { + if err := w.err(); err != nil { + return 0, err + } + + if len(p) > 2*len(w.c.writeBuf) && w.c.isServer { + // Don't buffer large messages. + err := w.c.flushFrame(final, p) + if err != nil { + return 0, err + } + return len(p), nil + } + + nn := len(p) + for len(p) > 0 { + n, err := w.ncopy(len(p)) + if err != nil { + return 0, err + } + copy(w.c.writeBuf[w.c.writePos:], p[:n]) + w.c.writePos += n + p = p[n:] + } + return nn, nil +} + +func (w messageWriter) Write(p []byte) (int, error) { + return w.write(false, p) +} + +func (w messageWriter) WriteString(p string) (int, error) { + if err := w.err(); err != nil { + return 0, err + } + + nn := len(p) + for len(p) > 0 { + n, err := w.ncopy(len(p)) + if err != nil { + return 0, err + } + copy(w.c.writeBuf[w.c.writePos:], p[:n]) + w.c.writePos += n + p = p[n:] + } + return nn, nil +} + +func (w messageWriter) ReadFrom(r io.Reader) (nn int64, err error) { + if err := w.err(); err != nil { + return 0, err + } + for { + if w.c.writePos == len(w.c.writeBuf) { + err = w.c.flushFrame(false, nil) + if err != nil { + break + } + } + var n int + n, err = r.Read(w.c.writeBuf[w.c.writePos:]) + w.c.writePos += n + nn += int64(n) + if err != nil { + if err == io.EOF { + err = nil + } + break + } + } + return nn, err +} + +func (w messageWriter) Close() error { + if err := w.err(); err != nil { + return err + } + return w.c.flushFrame(true, nil) +} + +// WriteMessage is a helper method for getting a writer using NextWriter, +// writing the message and closing the writer. +func (c *Conn) WriteMessage(messageType int, data []byte) error { + wr, err := c.NextWriter(messageType) + if err != nil { + return err + } + w := wr.(messageWriter) + if _, err := w.write(true, data); err != nil { + return err + } + if c.writeSeq == w.seq { + if err := c.flushFrame(true, nil); err != nil { + return err + } + } + return nil +} + +// SetWriteDeadline sets the write deadline on the underlying network +// connection. After a write has timed out, the websocket state is corrupt and +// all future writes will return an error. A zero value for t means writes will +// not time out. +func (c *Conn) SetWriteDeadline(t time.Time) error { + c.writeDeadline = t + return nil +} + +// Read methods + +// readFull is like io.ReadFull except that io.EOF is never returned. +func (c *Conn) readFull(p []byte) (err error) { + var n int + for n < len(p) && err == nil { + var nn int + nn, err = c.br.Read(p[n:]) + n += nn + } + if n == len(p) { + err = nil + } else if err == io.EOF { + err = errUnexpectedEOF + } + return +} + +func (c *Conn) advanceFrame() (int, error) { + + // 1. Skip remainder of previous frame. + + if c.readRemaining > 0 { + if _, err := io.CopyN(ioutil.Discard, c.br, c.readRemaining); err != nil { + return noFrame, err + } + } + + // 2. Read and parse first two bytes of frame header. + + var b [8]byte + if err := c.readFull(b[:2]); err != nil { + return noFrame, err + } + + final := b[0]&finalBit != 0 + frameType := int(b[0] & 0xf) + reserved := int((b[0] >> 4) & 0x7) + mask := b[1]&maskBit != 0 + c.readRemaining = int64(b[1] & 0x7f) + + if reserved != 0 { + return noFrame, c.handleProtocolError("unexpected reserved bits " + strconv.Itoa(reserved)) + } + + switch frameType { + case CloseMessage, PingMessage, PongMessage: + if c.readRemaining > maxControlFramePayloadSize { + return noFrame, c.handleProtocolError("control frame length > 125") + } + if !final { + return noFrame, c.handleProtocolError("control frame not final") + } + case TextMessage, BinaryMessage: + if !c.readFinal { + return noFrame, c.handleProtocolError("message start before final message frame") + } + c.readFinal = final + case continuationFrame: + if c.readFinal { + return noFrame, c.handleProtocolError("continuation after final message frame") + } + c.readFinal = final + default: + return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) + } + + // 3. Read and parse frame length. + + switch c.readRemaining { + case 126: + if err := c.readFull(b[:2]); err != nil { + return noFrame, err + } + c.readRemaining = int64(binary.BigEndian.Uint16(b[:2])) + case 127: + if err := c.readFull(b[:8]); err != nil { + return noFrame, err + } + c.readRemaining = int64(binary.BigEndian.Uint64(b[:8])) + } + + // 4. Handle frame masking. + + if mask != c.isServer { + return noFrame, c.handleProtocolError("incorrect mask flag") + } + + if mask { + c.readMaskPos = 0 + if err := c.readFull(c.readMaskKey[:]); err != nil { + return noFrame, err + } + } + + // 5. For text and binary messages, enforce read limit and return. + + if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { + + c.readLength += c.readRemaining + if c.readLimit > 0 && c.readLength > c.readLimit { + c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) + return noFrame, ErrReadLimit + } + + return frameType, nil + } + + // 6. Read control frame payload. + + var payload []byte + if c.readRemaining > 0 { + payload = make([]byte, c.readRemaining) + c.readRemaining = 0 + if err := c.readFull(payload); err != nil { + return noFrame, err + } + if c.isServer { + maskBytes(c.readMaskKey, 0, payload) + } + } + + // 7. Process control frame payload. + + switch frameType { + case PongMessage: + if err := c.handlePong(string(payload)); err != nil { + return noFrame, err + } + case PingMessage: + if err := c.handlePing(string(payload)); err != nil { + return noFrame, err + } + case CloseMessage: + c.WriteControl(CloseMessage, []byte{}, time.Now().Add(writeWait)) + closeCode := CloseNoStatusReceived + closeText := "" + if len(payload) >= 2 { + closeCode = int(binary.BigEndian.Uint16(payload)) + closeText = string(payload[2:]) + } + switch closeCode { + case CloseNormalClosure, CloseGoingAway: + return noFrame, io.EOF + default: + return noFrame, &closeError{code: closeCode, text: closeText} + } + } + + return frameType, nil +} + +func (c *Conn) handleProtocolError(message string) error { + c.WriteControl(CloseMessage, FormatCloseMessage(CloseProtocolError, message), time.Now().Add(writeWait)) + return errors.New("websocket: " + message) +} + +// NextReader returns the next data message received from the peer. The +// returned messageType is either TextMessage or BinaryMessage. +// +// There can be at most one open reader on a connection. NextReader discards +// the previous message if the application has not already consumed it. +// +// The NextReader method and the readers returned from the method cannot be +// accessed by more than one goroutine at a time. +func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { + + c.readSeq++ + c.readLength = 0 + + for c.readErr == nil { + frameType, err := c.advanceFrame() + if err != nil { + c.readErr = hideTempErr(err) + break + } + if frameType == TextMessage || frameType == BinaryMessage { + return frameType, messageReader{c, c.readSeq}, nil + } + } + return noFrame, nil, c.readErr +} + +type messageReader struct { + c *Conn + seq int +} + +func (r messageReader) Read(b []byte) (int, error) { + + if r.seq != r.c.readSeq { + return 0, io.EOF + } + + for r.c.readErr == nil { + + if r.c.readRemaining > 0 { + if int64(len(b)) > r.c.readRemaining { + b = b[:r.c.readRemaining] + } + n, err := r.c.br.Read(b) + r.c.readErr = hideTempErr(err) + if r.c.isServer { + r.c.readMaskPos = maskBytes(r.c.readMaskKey, r.c.readMaskPos, b[:n]) + } + r.c.readRemaining -= int64(n) + return n, r.c.readErr + } + + if r.c.readFinal { + r.c.readSeq++ + return 0, io.EOF + } + + frameType, err := r.c.advanceFrame() + switch { + case err != nil: + r.c.readErr = hideTempErr(err) + case frameType == TextMessage || frameType == BinaryMessage: + r.c.readErr = errors.New("websocket: internal error, unexpected text or binary in Reader") + } + } + + err := r.c.readErr + if err == io.EOF && r.seq == r.c.readSeq { + err = errUnexpectedEOF + } + return 0, err +} + +// ReadMessage is a helper method for getting a reader using NextReader and +// reading from that reader to a buffer. +func (c *Conn) ReadMessage() (messageType int, p []byte, err error) { + var r io.Reader + messageType, r, err = c.NextReader() + if err != nil { + return messageType, nil, err + } + p, err = ioutil.ReadAll(r) + return messageType, p, err +} + +// SetReadDeadline sets the read deadline on the underlying network connection. +// After a read has timed out, the websocket connection state is corrupt and +// all future reads will return an error. A zero value for t means reads will +// not time out. +func (c *Conn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +// SetReadLimit sets the maximum size for a message read from the peer. If a +// message exceeds the limit, the connection sends a close frame to the peer +// and returns ErrReadLimit to the application. +func (c *Conn) SetReadLimit(limit int64) { + c.readLimit = limit +} + +// SetPingHandler sets the handler for ping messages received from the peer. +// The default ping handler sends a pong to the peer. +func (c *Conn) SetPingHandler(h func(string) error) { + if h == nil { + h = func(message string) error { + c.WriteControl(PongMessage, []byte(message), time.Now().Add(writeWait)) + return nil + } + } + c.handlePing = h +} + +// SetPongHandler sets then handler for pong messages received from the peer. +// The default pong handler does nothing. +func (c *Conn) SetPongHandler(h func(string) error) { + if h == nil { + h = func(string) error { return nil } + } + c.handlePong = h +} + +// UnderlyingConn returns the internal net.Conn. This can be used to further +// modifications to connection specific flags. +func (c *Conn) UnderlyingConn() net.Conn { + return c.conn +} + +// FormatCloseMessage formats closeCode and text as a WebSocket close message. +func FormatCloseMessage(closeCode int, text string) []byte { + buf := make([]byte, 2+len(text)) + binary.BigEndian.PutUint16(buf, uint16(closeCode)) + copy(buf[2:], text) + return buf +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go new file mode 100644 index 0000000..1f1197e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/conn_test.go @@ -0,0 +1,238 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net" + "testing" + "testing/iotest" + "time" +) + +var _ net.Error = errWriteTimeout + +type fakeNetConn struct { + io.Reader + io.Writer +} + +func (c fakeNetConn) Close() error { return nil } +func (c fakeNetConn) LocalAddr() net.Addr { return nil } +func (c fakeNetConn) RemoteAddr() net.Addr { return nil } +func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } +func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil } +func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil } + +func TestFraming(t *testing.T) { + frameSizes := []int{0, 1, 2, 124, 125, 126, 127, 128, 129, 65534, 65535, 65536, 65537} + var readChunkers = []struct { + name string + f func(io.Reader) io.Reader + }{ + {"half", iotest.HalfReader}, + {"one", iotest.OneByteReader}, + {"asis", func(r io.Reader) io.Reader { return r }}, + } + + writeBuf := make([]byte, 65537) + for i := range writeBuf { + writeBuf[i] = byte(i) + } + + for _, isServer := range []bool{true, false} { + for _, chunker := range readChunkers { + + var connBuf bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) + rc := newConn(fakeNetConn{Reader: chunker.f(&connBuf), Writer: nil}, !isServer, 1024, 1024) + + for _, n := range frameSizes { + for _, iocopy := range []bool{true, false} { + name := fmt.Sprintf("s:%v, r:%s, n:%d c:%v", isServer, chunker.name, n, iocopy) + + w, err := wc.NextWriter(TextMessage) + if err != nil { + t.Errorf("%s: wc.NextWriter() returned %v", name, err) + continue + } + var nn int + if iocopy { + var n64 int64 + n64, err = io.Copy(w, bytes.NewReader(writeBuf[:n])) + nn = int(n64) + } else { + nn, err = w.Write(writeBuf[:n]) + } + if err != nil || nn != n { + t.Errorf("%s: w.Write(writeBuf[:n]) returned %d, %v", name, nn, err) + continue + } + err = w.Close() + if err != nil { + t.Errorf("%s: w.Close() returned %v", name, err) + continue + } + + opCode, r, err := rc.NextReader() + if err != nil || opCode != TextMessage { + t.Errorf("%s: NextReader() returned %d, r, %v", name, opCode, err) + continue + } + rbuf, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("%s: ReadFull() returned rbuf, %v", name, err) + continue + } + + if len(rbuf) != n { + t.Errorf("%s: len(rbuf) is %d, want %d", name, len(rbuf), n) + continue + } + + for i, b := range rbuf { + if byte(i) != b { + t.Errorf("%s: bad byte at offset %d", name, i) + break + } + } + } + } + } + } +} + +func TestControl(t *testing.T) { + const message = "this is a ping/pong messsage" + for _, isServer := range []bool{true, false} { + for _, isWriteControl := range []bool{true, false} { + name := fmt.Sprintf("s:%v, wc:%v", isServer, isWriteControl) + var connBuf bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &connBuf}, isServer, 1024, 1024) + rc := newConn(fakeNetConn{Reader: &connBuf, Writer: nil}, !isServer, 1024, 1024) + if isWriteControl { + wc.WriteControl(PongMessage, []byte(message), time.Now().Add(time.Second)) + } else { + w, err := wc.NextWriter(PongMessage) + if err != nil { + t.Errorf("%s: wc.NextWriter() returned %v", name, err) + continue + } + if _, err := w.Write([]byte(message)); err != nil { + t.Errorf("%s: w.Write() returned %v", name, err) + continue + } + if err := w.Close(); err != nil { + t.Errorf("%s: w.Close() returned %v", name, err) + continue + } + var actualMessage string + rc.SetPongHandler(func(s string) error { actualMessage = s; return nil }) + rc.NextReader() + if actualMessage != message { + t.Errorf("%s: pong=%q, want %q", name, actualMessage, message) + continue + } + } + } + } +} + +func TestCloseBeforeFinalFrame(t *testing.T) { + const bufSize = 512 + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(make([]byte, bufSize+bufSize/2)) + wc.WriteControl(CloseMessage, FormatCloseMessage(CloseNormalClosure, ""), time.Now().Add(10*time.Second)) + w.Close() + + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != errUnexpectedEOF { + t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) + } + _, _, err = rc.NextReader() + if err != io.EOF { + t.Fatalf("NextReader() returned %v, want %v", err, io.EOF) + } +} + +func TestEOFBeforeFinalFrame(t *testing.T) { + const bufSize = 512 + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, bufSize) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + + w, _ := wc.NextWriter(BinaryMessage) + w.Write(make([]byte, bufSize+bufSize/2)) + + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != errUnexpectedEOF { + t.Fatalf("io.Copy() returned %v, want %v", err, errUnexpectedEOF) + } + _, _, err = rc.NextReader() + if err != errUnexpectedEOF { + t.Fatalf("NextReader() returned %v, want %v", err, errUnexpectedEOF) + } +} + +func TestReadLimit(t *testing.T) { + + const readLimit = 512 + message := make([]byte, readLimit+1) + + var b1, b2 bytes.Buffer + wc := newConn(fakeNetConn{Reader: nil, Writer: &b1}, false, 1024, readLimit-2) + rc := newConn(fakeNetConn{Reader: &b1, Writer: &b2}, true, 1024, 1024) + rc.SetReadLimit(readLimit) + + // Send message at the limit with interleaved pong. + w, _ := wc.NextWriter(BinaryMessage) + w.Write(message[:readLimit-1]) + wc.WriteControl(PongMessage, []byte("this is a pong"), time.Now().Add(10*time.Second)) + w.Write(message[:1]) + w.Close() + + // Send message larger than the limit. + wc.WriteMessage(BinaryMessage, message[:readLimit+1]) + + op, _, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("1: NextReader() returned %d, %v", op, err) + } + op, r, err := rc.NextReader() + if op != BinaryMessage || err != nil { + t.Fatalf("2: NextReader() returned %d, %v", op, err) + } + _, err = io.Copy(ioutil.Discard, r) + if err != ErrReadLimit { + t.Fatalf("io.Copy() returned %v", err) + } +} + +func TestUnderlyingConn(t *testing.T) { + var b1, b2 bytes.Buffer + fc := fakeNetConn{Reader: &b1, Writer: &b2} + c := newConn(fc, true, 1024, 1024) + ul := c.UnderlyingConn() + if ul != fc { + t.Fatalf("Underlying conn is not what it should be.") + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/doc.go b/Godeps/_workspace/src/github.com/gorilla/websocket/doc.go new file mode 100644 index 0000000..0d2bd91 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/doc.go @@ -0,0 +1,148 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package websocket implements the WebSocket protocol defined in RFC 6455. +// +// Overview +// +// The Conn type represents a WebSocket connection. A server application uses +// the Upgrade function from an Upgrader object with a HTTP request handler +// to get a pointer to a Conn: +// +// var upgrader = websocket.Upgrader{ +// ReadBufferSize: 1024, +// WriteBufferSize: 1024, +// } +// +// func handler(w http.ResponseWriter, r *http.Request) { +// conn, err := upgrader.Upgrade(w, r, nil) +// if err != nil { +// log.Println(err) +// return +// } +// ... Use conn to send and receive messages. +// } +// +// Call the connection WriteMessage and ReadMessages methods to send and +// receive messages as a slice of bytes. This snippet of code shows how to echo +// messages using these methods: +// +// for { +// messageType, p, err := conn.ReadMessage() +// if err != nil { +// return +// } +// if err = conn.WriteMessage(messageType, p); err != nil { +// return err +// } +// } +// +// In above snippet of code, p is a []byte and messageType is an int with value +// websocket.BinaryMessage or websocket.TextMessage. +// +// An application can also send and receive messages using the io.WriteCloser +// and io.Reader interfaces. To send a message, call the connection NextWriter +// method to get an io.WriteCloser, write the message to the writer and close +// the writer when done. To receive a message, call the connection NextReader +// method to get an io.Reader and read until io.EOF is returned. This snippet +// snippet shows how to echo messages using the NextWriter and NextReader +// methods: +// +// for { +// messageType, r, err := conn.NextReader() +// if err != nil { +// return +// } +// w, err := conn.NextWriter(messageType) +// if err != nil { +// return err +// } +// if _, err := io.Copy(w, r); err != nil { +// return err +// } +// if err := w.Close(); err != nil { +// return err +// } +// } +// +// Data Messages +// +// The WebSocket protocol distinguishes between text and binary data messages. +// Text messages are interpreted as UTF-8 encoded text. The interpretation of +// binary messages is left to the application. +// +// This package uses the TextMessage and BinaryMessage integer constants to +// identify the two data message types. The ReadMessage and NextReader methods +// return the type of the received message. The messageType argument to the +// WriteMessage and NextWriter methods specifies the type of a sent message. +// +// It is the application's responsibility to ensure that text messages are +// valid UTF-8 encoded text. +// +// Control Messages +// +// The WebSocket protocol defines three types of control messages: close, ping +// and pong. Call the connection WriteControl, WriteMessage or NextWriter +// methods to send a control message to the peer. +// +// Connections handle received ping and pong messages by invoking a callback +// function set with SetPingHandler and SetPongHandler methods. These callback +// functions can be invoked from the ReadMessage method, the NextReader method +// or from a call to the data message reader returned from NextReader. +// +// Connections handle received close messages by returning an error from the +// ReadMessage method, the NextReader method or from a call to the data message +// reader returned from NextReader. +// +// Concurrency +// +// Connections do not support concurrent calls to the write methods +// (NextWriter, SetWriteDeadline, WriteMessage) or concurrent calls to the read +// methods methods (NextReader, SetReadDeadline, ReadMessage). Connections do +// support a concurrent reader and writer. +// +// The Close and WriteControl methods can be called concurrently with all other +// methods. +// +// Read is Required +// +// The application must read the connection to process ping and close messages +// sent from the peer. If the application is not otherwise interested in +// messages from the peer, then the application should start a goroutine to read +// and discard messages from the peer. A simple example is: +// +// func readLoop(c *websocket.Conn) { +// for { +// if _, _, err := c.NextReader(); err != nil { +// c.Close() +// break +// } +// } +// } +// +// Origin Considerations +// +// Web browsers allow Javascript applications to open a WebSocket connection to +// any host. It's up to the server to enforce an origin policy using the Origin +// request header sent by the browser. +// +// The Upgrader calls the function specified in the CheckOrigin field to check +// the origin. If the CheckOrigin function returns false, then the Upgrade +// method fails the WebSocket handshake with HTTP status 403. +// +// If the CheckOrigin field is nil, then the Upgrader uses a safe default: fail +// the handshake if the Origin request header is present and not equal to the +// Host request header. +// +// An application can allow connections from any origin by specifying a +// function that always returns true: +// +// var upgrader = websocket.Upgrader{ +// CheckOrigin: func(r *http.Request) bool { return true }, +// } +// +// The deprecated Upgrade function does not enforce an origin policy. It's the +// application's responsibility to check the Origin header before calling +// Upgrade. +package websocket diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md new file mode 100644 index 0000000..075ac15 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/README.md @@ -0,0 +1,13 @@ +# Test Server + +This package contains a server for the [Autobahn WebSockets Test Suite](http://autobahn.ws/testsuite). + +To test the server, run + + go run server.go + +and start the client test driver + + wstest -m fuzzingclient -s fuzzingclient.json + +When the client completes, it writes a report to reports/clients/index.html. diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json new file mode 100644 index 0000000..27d5a5b --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/fuzzingclient.json @@ -0,0 +1,14 @@ + +{ + "options": {"failByDrop": false}, + "outdir": "./reports/clients", + "servers": [ + {"agent": "ReadAllWriteMessage", "url": "ws://localhost:9000/m", "options": {"version": 18}}, + {"agent": "ReadAllWrite", "url": "ws://localhost:9000/r", "options": {"version": 18}}, + {"agent": "CopyFull", "url": "ws://localhost:9000/f", "options": {"version": 18}}, + {"agent": "CopyWriterOnly", "url": "ws://localhost:9000/c", "options": {"version": 18}} + ], + "cases": ["*"], + "exclude-cases": [], + "exclude-agent-cases": {} +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go new file mode 100644 index 0000000..d96ac84 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/autobahn/server.go @@ -0,0 +1,246 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Command server is a test server for the Autobahn WebSockets Test Suite. +package main + +import ( + "errors" + "flag" + "github.com/gorilla/websocket" + "io" + "log" + "net/http" + "time" + "unicode/utf8" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 4096, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +// echoCopy echoes messages from the client using io.Copy. +func echoCopy(w http.ResponseWriter, r *http.Request, writerOnly bool) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("Upgrade:", err) + return + } + defer conn.Close() + for { + mt, r, err := conn.NextReader() + if err != nil { + if err != io.EOF { + log.Println("NextReader:", err) + } + return + } + if mt == websocket.TextMessage { + r = &validator{r: r} + } + w, err := conn.NextWriter(mt) + if err != nil { + log.Println("NextWriter:", err) + return + } + if mt == websocket.TextMessage { + r = &validator{r: r} + } + if writerOnly { + _, err = io.Copy(struct{ io.Writer }{w}, r) + } else { + _, err = io.Copy(w, r) + } + if err != nil { + if err == errInvalidUTF8 { + conn.WriteControl(websocket.CloseMessage, + websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), + time.Time{}) + } + log.Println("Copy:", err) + return + } + err = w.Close() + if err != nil { + log.Println("Close:", err) + return + } + } +} + +func echoCopyWriterOnly(w http.ResponseWriter, r *http.Request) { + echoCopy(w, r, true) +} + +func echoCopyFull(w http.ResponseWriter, r *http.Request) { + echoCopy(w, r, false) +} + +// echoReadAll echoes messages from the client by reading the entire message +// with ioutil.ReadAll. +func echoReadAll(w http.ResponseWriter, r *http.Request, writeMessage bool) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println("Upgrade:", err) + return + } + defer conn.Close() + for { + mt, b, err := conn.ReadMessage() + if err != nil { + if err != io.EOF { + log.Println("NextReader:", err) + } + return + } + if mt == websocket.TextMessage { + if !utf8.Valid(b) { + conn.WriteControl(websocket.CloseMessage, + websocket.FormatCloseMessage(websocket.CloseInvalidFramePayloadData, ""), + time.Time{}) + log.Println("ReadAll: invalid utf8") + } + } + if writeMessage { + err = conn.WriteMessage(mt, b) + if err != nil { + log.Println("WriteMessage:", err) + } + } else { + w, err := conn.NextWriter(mt) + if err != nil { + log.Println("NextWriter:", err) + return + } + if _, err := w.Write(b); err != nil { + log.Println("Writer:", err) + return + } + if err := w.Close(); err != nil { + log.Println("Close:", err) + return + } + } + } +} + +func echoReadAllWriter(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, false) +} + +func echoReadAllWriteMessage(w http.ResponseWriter, r *http.Request) { + echoReadAll(w, r, true) +} + +func serveHome(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.Error(w, "Not found.", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + io.WriteString(w, "Echo Server") +} + +var addr = flag.String("addr", ":9000", "http service address") + +func main() { + flag.Parse() + http.HandleFunc("/", serveHome) + http.HandleFunc("/c", echoCopyWriterOnly) + http.HandleFunc("/f", echoCopyFull) + http.HandleFunc("/r", echoReadAllWriter) + http.HandleFunc("/m", echoReadAllWriteMessage) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} + +type validator struct { + state int + x rune + r io.Reader +} + +var errInvalidUTF8 = errors.New("invalid utf8") + +func (r *validator) Read(p []byte) (int, error) { + n, err := r.r.Read(p) + state := r.state + x := r.x + for _, b := range p[:n] { + state, x = decode(state, x, b) + if state == utf8Reject { + break + } + } + r.state = state + r.x = x + if state == utf8Reject || (err == io.EOF && state != utf8Accept) { + return n, errInvalidUTF8 + } + return n, err +} + +// UTF-8 decoder from http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +// +// Copyright (c) 2008-2009 Bjoern Hoehrmann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +var utf8d = [...]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9f + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // a0..bf + 8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0..df + 0xa, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // e0..ef + 0xb, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // f0..ff + 0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2 + 1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4 + 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6 + 1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // s7..s8 +} + +const ( + utf8Accept = 0 + utf8Reject = 1 +) + +func decode(state int, x rune, b byte) (int, rune) { + t := utf8d[b] + if state != utf8Accept { + x = rune(b&0x3f) | (x << 6) + } else { + x = rune((0xff >> t) & b) + } + state = int(utf8d[256+state*16+int(t)]) + return state, x +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md new file mode 100644 index 0000000..08fc3e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/README.md @@ -0,0 +1,19 @@ +# Chat Example + +This application shows how to use use the +[websocket](https://github.com/gorilla/websocket) package and +[jQuery](http://jquery.com) to implement a simple web chat application. + +## Running the example + +The example requires a working Go development environment. The [Getting +Started](http://golang.org/doc/install) page describes how to install the +development environment. + +Once you have Go up and running, you can download, build and run the example +using the following commands. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/chat` + $ go run *.go + diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go new file mode 100644 index 0000000..7cc0496 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/conn.go @@ -0,0 +1,106 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "github.com/gorilla/websocket" + "log" + "net/http" + "time" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// connection is an middleman between the websocket connection and the hub. +type connection struct { + // The websocket connection. + ws *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte +} + +// readPump pumps messages from the websocket connection to the hub. +func (c *connection) readPump() { + defer func() { + h.unregister <- c + c.ws.Close() + }() + c.ws.SetReadLimit(maxMessageSize) + c.ws.SetReadDeadline(time.Now().Add(pongWait)) + c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, message, err := c.ws.ReadMessage() + if err != nil { + break + } + h.broadcast <- message + } +} + +// write writes a message with the given message type and payload. +func (c *connection) write(mt int, payload []byte) error { + c.ws.SetWriteDeadline(time.Now().Add(writeWait)) + return c.ws.WriteMessage(mt, payload) +} + +// writePump pumps messages from the hub to the websocket connection. +func (c *connection) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.ws.Close() + }() + for { + select { + case message, ok := <-c.send: + if !ok { + c.write(websocket.CloseMessage, []byte{}) + return + } + if err := c.write(websocket.TextMessage, message); err != nil { + return + } + case <-ticker.C: + if err := c.write(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} + +// serverWs handles websocket requests from the peer. +func serveWs(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Println(err) + return + } + c := &connection{send: make(chan []byte, 256), ws: ws} + h.register <- c + go c.writePump() + c.readPump() +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html new file mode 100644 index 0000000..2959922 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/home.html @@ -0,0 +1,92 @@ + + + +Chat Example + + + + + +
      +
      + + +
      + + diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go new file mode 100644 index 0000000..449ba75 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/hub.go @@ -0,0 +1,51 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// hub maintains the set of active connections and broadcasts messages to the +// connections. +type hub struct { + // Registered connections. + connections map[*connection]bool + + // Inbound messages from the connections. + broadcast chan []byte + + // Register requests from the connections. + register chan *connection + + // Unregister requests from connections. + unregister chan *connection +} + +var h = hub{ + broadcast: make(chan []byte), + register: make(chan *connection), + unregister: make(chan *connection), + connections: make(map[*connection]bool), +} + +func (h *hub) run() { + for { + select { + case c := <-h.register: + h.connections[c] = true + case c := <-h.unregister: + if _, ok := h.connections[c]; ok { + delete(h.connections, c) + close(c.send) + } + case m := <-h.broadcast: + for c := range h.connections { + select { + case c.send <- m: + default: + close(c.send) + delete(h.connections, c) + } + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go new file mode 100644 index 0000000..3c4448d --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/chat/main.go @@ -0,0 +1,39 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "log" + "net/http" + "text/template" +) + +var addr = flag.String("addr", ":8080", "http service address") +var homeTempl = template.Must(template.ParseFiles("home.html")) + +func serveHome(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.Error(w, "Not found", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + homeTempl.Execute(w, r.Host) +} + +func main() { + flag.Parse() + go h.run() + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", serveWs) + err := http.ListenAndServe(*addr, nil) + if err != nil { + log.Fatal("ListenAndServe: ", err) + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md new file mode 100644 index 0000000..ca4931f --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/README.md @@ -0,0 +1,9 @@ +# File Watch example. + +This example sends a file to the browser client for display whenever the file is modified. + + $ go get github.com/gorilla/websocket + $ cd `go list -f '{{.Dir}}' github.com/gorilla/websocket/examples/filewatch` + $ go run main.go + # Open http://localhost:8080/ . + # Modify the file to see it update in the browser. diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go new file mode 100644 index 0000000..a2c7b85 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/examples/filewatch/main.go @@ -0,0 +1,193 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" + "text/template" + "time" + + "github.com/gorilla/websocket" +) + +const ( + // Time allowed to write the file to the client. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the client. + pongWait = 60 * time.Second + + // Send pings to client with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Poll file for changes with this period. + filePeriod = 10 * time.Second +) + +var ( + addr = flag.String("addr", ":8080", "http service address") + homeTempl = template.Must(template.New("").Parse(homeHTML)) + filename string + upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + } +) + +func readFileIfModified(lastMod time.Time) ([]byte, time.Time, error) { + fi, err := os.Stat(filename) + if err != nil { + return nil, lastMod, err + } + if !fi.ModTime().After(lastMod) { + return nil, lastMod, nil + } + p, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fi.ModTime(), err + } + return p, fi.ModTime(), nil +} + +func reader(ws *websocket.Conn) { + defer ws.Close() + ws.SetReadLimit(512) + ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil }) + for { + _, _, err := ws.ReadMessage() + if err != nil { + break + } + } +} + +func writer(ws *websocket.Conn, lastMod time.Time) { + lastError := "" + pingTicker := time.NewTicker(pingPeriod) + fileTicker := time.NewTicker(filePeriod) + defer func() { + pingTicker.Stop() + fileTicker.Stop() + ws.Close() + }() + for { + select { + case <-fileTicker.C: + var p []byte + var err error + + p, lastMod, err = readFileIfModified(lastMod) + + if err != nil { + if s := err.Error(); s != lastError { + lastError = s + p = []byte(lastError) + } + } else { + lastError = "" + } + + if p != nil { + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { + return + } + } + case <-pingTicker.C: + ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} + +func serveWs(w http.ResponseWriter, r *http.Request) { + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + if _, ok := err.(websocket.HandshakeError); !ok { + log.Println(err) + } + return + } + + var lastMod time.Time + if n, err := strconv.ParseInt(r.FormValue("lastMod"), 16, 64); err != nil { + lastMod = time.Unix(0, n) + } + + go writer(ws, lastMod) + reader(ws) +} + +func serveHome(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.Error(w, "Not found", 404) + return + } + if r.Method != "GET" { + http.Error(w, "Method not allowed", 405) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + p, lastMod, err := readFileIfModified(time.Time{}) + if err != nil { + p = []byte(err.Error()) + lastMod = time.Unix(0, 0) + } + var v = struct { + Host string + Data string + LastMod string + }{ + r.Host, + string(p), + strconv.FormatInt(lastMod.UnixNano(), 16), + } + homeTempl.Execute(w, &v) +} + +func main() { + flag.Parse() + if flag.NArg() != 1 { + log.Fatal("filename not specified") + } + filename = flag.Args()[0] + http.HandleFunc("/", serveHome) + http.HandleFunc("/ws", serveWs) + if err := http.ListenAndServe(*addr, nil); err != nil { + log.Fatal(err) + } +} + +const homeHTML = ` + + + WebSocket Example + + +
      {{.Data}}
      + + + +` diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/json.go b/Godeps/_workspace/src/github.com/gorilla/websocket/json.go new file mode 100644 index 0000000..18e62f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/json.go @@ -0,0 +1,57 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "encoding/json" + "io" +) + +// WriteJSON is deprecated, use c.WriteJSON instead. +func WriteJSON(c *Conn, v interface{}) error { + return c.WriteJSON(v) +} + +// WriteJSON writes the JSON encoding of v to the connection. +// +// See the documentation for encoding/json Marshal for details about the +// conversion of Go values to JSON. +func (c *Conn) WriteJSON(v interface{}) error { + w, err := c.NextWriter(TextMessage) + if err != nil { + return err + } + err1 := json.NewEncoder(w).Encode(v) + err2 := w.Close() + if err1 != nil { + return err1 + } + return err2 +} + +// ReadJSON is deprecated, use c.ReadJSON instead. +func ReadJSON(c *Conn, v interface{}) error { + return c.ReadJSON(v) +} + +// ReadJSON reads the next JSON-encoded message from the connection and stores +// it in the value pointed to by v. +// +// See the documentation for the encoding/json Unmarshal function for details +// about the conversion of JSON to a Go value. +func (c *Conn) ReadJSON(v interface{}) error { + _, r, err := c.NextReader() + if err != nil { + return err + } + err = json.NewDecoder(r).Decode(v) + if err == io.EOF { + // Decode returns io.EOF when the message is empty or all whitespace. + // Convert to io.ErrUnexpectedEOF so that application can distinguish + // between an error reading the JSON value and the connection closing. + err = io.ErrUnexpectedEOF + } + return err +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go new file mode 100644 index 0000000..1b7a5ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/json_test.go @@ -0,0 +1,119 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bytes" + "encoding/json" + "io" + "reflect" + "testing" +) + +func TestJSON(t *testing.T) { + var buf bytes.Buffer + c := fakeNetConn{&buf, &buf} + wc := newConn(c, true, 1024, 1024) + rc := newConn(c, false, 1024, 1024) + + var actual, expect struct { + A int + B string + } + expect.A = 1 + expect.B = "hello" + + if err := wc.WriteJSON(&expect); err != nil { + t.Fatal("write", err) + } + + if err := rc.ReadJSON(&actual); err != nil { + t.Fatal("read", err) + } + + if !reflect.DeepEqual(&actual, &expect) { + t.Fatal("equal", actual, expect) + } +} + +func TestPartialJsonRead(t *testing.T) { + var buf bytes.Buffer + c := fakeNetConn{&buf, &buf} + wc := newConn(c, true, 1024, 1024) + rc := newConn(c, false, 1024, 1024) + + var v struct { + A int + B string + } + v.A = 1 + v.B = "hello" + + messageCount := 0 + + // Partial JSON values. + + data, err := json.Marshal(v) + if err != nil { + t.Fatal(err) + } + for i := len(data) - 1; i >= 0; i-- { + if err := wc.WriteMessage(TextMessage, data[:i]); err != nil { + t.Fatal(err) + } + messageCount++ + } + + // Whitespace. + + if err := wc.WriteMessage(TextMessage, []byte(" ")); err != nil { + t.Fatal(err) + } + messageCount++ + + // Close. + + if err := wc.WriteMessage(CloseMessage, FormatCloseMessage(CloseNormalClosure, "")); err != nil { + t.Fatal(err) + } + + for i := 0; i < messageCount; i++ { + err := rc.ReadJSON(&v) + if err != io.ErrUnexpectedEOF { + t.Error("read", i, err) + } + } + + err = rc.ReadJSON(&v) + if err != io.EOF { + t.Error("final", err) + } +} + +func TestDeprecatedJSON(t *testing.T) { + var buf bytes.Buffer + c := fakeNetConn{&buf, &buf} + wc := newConn(c, true, 1024, 1024) + rc := newConn(c, false, 1024, 1024) + + var actual, expect struct { + A int + B string + } + expect.A = 1 + expect.B = "hello" + + if err := WriteJSON(wc, &expect); err != nil { + t.Fatal("write", err) + } + + if err := ReadJSON(rc, &actual); err != nil { + t.Fatal("read", err) + } + + if !reflect.DeepEqual(&actual, &expect) { + t.Fatal("equal", actual, expect) + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/server.go b/Godeps/_workspace/src/github.com/gorilla/websocket/server.go new file mode 100644 index 0000000..e56a004 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/server.go @@ -0,0 +1,247 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "bufio" + "errors" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// HandshakeError describes an error with the handshake from the peer. +type HandshakeError struct { + message string +} + +func (e HandshakeError) Error() string { return e.message } + +// Upgrader specifies parameters for upgrading an HTTP connection to a +// WebSocket connection. +type Upgrader struct { + // HandshakeTimeout specifies the duration for the handshake to complete. + HandshakeTimeout time.Duration + + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // size is zero, then a default value of 4096 is used. The I/O buffer sizes + // do not limit the size of the messages that can be sent or received. + ReadBufferSize, WriteBufferSize int + + // Subprotocols specifies the server's supported protocols in order of + // preference. If this field is set, then the Upgrade method negotiates a + // subprotocol by selecting the first match in this list with a protocol + // requested by the client. + Subprotocols []string + + // Error specifies the function for generating HTTP error responses. If Error + // is nil, then http.Error is used to generate the HTTP response. + Error func(w http.ResponseWriter, r *http.Request, status int, reason error) + + // CheckOrigin returns true if the request Origin header is acceptable. If + // CheckOrigin is nil, the host in the Origin header must not be set or + // must match the host of the request. + CheckOrigin func(r *http.Request) bool +} + +func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { + err := HandshakeError{reason} + if u.Error != nil { + u.Error(w, r, status, err) + } else { + http.Error(w, http.StatusText(status), status) + } + return nil, err +} + +// checkSameOrigin returns true if the origin is not set or is equal to the request host. +func checkSameOrigin(r *http.Request) bool { + origin := r.Header["Origin"] + if len(origin) == 0 { + return true + } + u, err := url.Parse(origin[0]) + if err != nil { + return false + } + return u.Host == r.Host +} + +func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { + if u.Subprotocols != nil { + clientProtocols := Subprotocols(r) + for _, serverProtocol := range u.Subprotocols { + for _, clientProtocol := range clientProtocols { + if clientProtocol == serverProtocol { + return clientProtocol + } + } + } + } else if responseHeader != nil { + return responseHeader.Get("Sec-Websocket-Protocol") + } + return "" +} + +// Upgrade upgrades the HTTP server connection to the WebSocket protocol. +// +// The responseHeader is included in the response to the client's upgrade +// request. Use the responseHeader to specify cookies (Set-Cookie) and the +// application negotiated subprotocol (Sec-Websocket-Protocol). +func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { + if values := r.Header["Sec-Websocket-Version"]; len(values) == 0 || values[0] != "13" { + return u.returnError(w, r, http.StatusBadRequest, "websocket: version != 13") + } + + if !tokenListContainsValue(r.Header, "Connection", "upgrade") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find connection header with token 'upgrade'") + } + + if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { + return u.returnError(w, r, http.StatusBadRequest, "websocket: could not find upgrade header with token 'websocket'") + } + + checkOrigin := u.CheckOrigin + if checkOrigin == nil { + checkOrigin = checkSameOrigin + } + if !checkOrigin(r) { + return u.returnError(w, r, http.StatusForbidden, "websocket: origin not allowed") + } + + challengeKey := r.Header.Get("Sec-Websocket-Key") + if challengeKey == "" { + return u.returnError(w, r, http.StatusBadRequest, "websocket: key missing or blank") + } + + subprotocol := u.selectSubprotocol(r, responseHeader) + + var ( + netConn net.Conn + br *bufio.Reader + err error + ) + + h, ok := w.(http.Hijacker) + if !ok { + return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") + } + var rw *bufio.ReadWriter + netConn, rw, err = h.Hijack() + if err != nil { + return u.returnError(w, r, http.StatusInternalServerError, err.Error()) + } + br = rw.Reader + + if br.Buffered() > 0 { + netConn.Close() + return nil, errors.New("websocket: client sent data before handshake is complete") + } + + c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize) + c.subprotocol = subprotocol + + p := c.writeBuf[:0] + p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) + p = append(p, computeAcceptKey(challengeKey)...) + p = append(p, "\r\n"...) + if c.subprotocol != "" { + p = append(p, "Sec-Websocket-Protocol: "...) + p = append(p, c.subprotocol...) + p = append(p, "\r\n"...) + } + for k, vs := range responseHeader { + if k == "Sec-Websocket-Protocol" { + continue + } + for _, v := range vs { + p = append(p, k...) + p = append(p, ": "...) + for i := 0; i < len(v); i++ { + b := v[i] + if b <= 31 { + // prevent response splitting. + b = ' ' + } + p = append(p, b) + } + p = append(p, "\r\n"...) + } + } + p = append(p, "\r\n"...) + + // Clear deadlines set by HTTP server. + netConn.SetDeadline(time.Time{}) + + if u.HandshakeTimeout > 0 { + netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) + } + if _, err = netConn.Write(p); err != nil { + netConn.Close() + return nil, err + } + if u.HandshakeTimeout > 0 { + netConn.SetWriteDeadline(time.Time{}) + } + + return c, nil +} + +// Upgrade upgrades the HTTP server connection to the WebSocket protocol. +// +// This function is deprecated, use websocket.Upgrader instead. +// +// The application is responsible for checking the request origin before +// calling Upgrade. An example implementation of the same origin policy is: +// +// if req.Header.Get("Origin") != "http://"+req.Host { +// http.Error(w, "Origin not allowed", 403) +// return +// } +// +// If the endpoint supports subprotocols, then the application is responsible +// for negotiating the protocol used on the connection. Use the Subprotocols() +// function to get the subprotocols requested by the client. Use the +// Sec-Websocket-Protocol response header to specify the subprotocol selected +// by the application. +// +// The responseHeader is included in the response to the client's upgrade +// request. Use the responseHeader to specify cookies (Set-Cookie) and the +// negotiated subprotocol (Sec-Websocket-Protocol). +// +// The connection buffers IO to the underlying network connection. The +// readBufSize and writeBufSize parameters specify the size of the buffers to +// use. Messages can be larger than the buffers. +// +// If the request is not a valid WebSocket handshake, then Upgrade returns an +// error of type HandshakeError. Applications should handle this error by +// replying to the client with an HTTP error response. +func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { + u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} + u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { + // don't return errors to maintain backwards compatibility + } + u.CheckOrigin = func(r *http.Request) bool { + // allow all connections by default + return true + } + return u.Upgrade(w, r, responseHeader) +} + +// Subprotocols returns the subprotocols requested by the client in the +// Sec-Websocket-Protocol header. +func Subprotocols(r *http.Request) []string { + h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) + if h == "" { + return nil + } + protocols := strings.Split(h, ",") + for i := range protocols { + protocols[i] = strings.TrimSpace(protocols[i]) + } + return protocols +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go new file mode 100644 index 0000000..ead0776 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/server_test.go @@ -0,0 +1,33 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "net/http" + "reflect" + "testing" +) + +var subprotocolTests = []struct { + h string + protocols []string +}{ + {"", nil}, + {"foo", []string{"foo"}}, + {"foo,bar", []string{"foo", "bar"}}, + {"foo, bar", []string{"foo", "bar"}}, + {" foo, bar", []string{"foo", "bar"}}, + {" foo, bar ", []string{"foo", "bar"}}, +} + +func TestSubprotocols(t *testing.T) { + for _, st := range subprotocolTests { + r := http.Request{Header: http.Header{"Sec-Websocket-Protocol": {st.h}}} + protocols := Subprotocols(&r) + if !reflect.DeepEqual(st.protocols, protocols) { + t.Errorf("SubProtocols(%q) returned %#v, want %#v", st.h, protocols, st.protocols) + } + } +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/util.go b/Godeps/_workspace/src/github.com/gorilla/websocket/util.go new file mode 100644 index 0000000..ffdc265 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/util.go @@ -0,0 +1,44 @@ +// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "io" + "net/http" + "strings" +) + +// tokenListContainsValue returns true if the 1#token header with the given +// name contains token. +func tokenListContainsValue(header http.Header, name string, value string) bool { + for _, v := range header[name] { + for _, s := range strings.Split(v, ",") { + if strings.EqualFold(value, strings.TrimSpace(s)) { + return true + } + } + } + return false +} + +var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + +func computeAcceptKey(challengeKey string) string { + h := sha1.New() + h.Write([]byte(challengeKey)) + h.Write(keyGUID) + return base64.StdEncoding.EncodeToString(h.Sum(nil)) +} + +func generateChallengeKey() (string, error) { + p := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, p); err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(p), nil +} diff --git a/Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go b/Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go new file mode 100644 index 0000000..91f70ce --- /dev/null +++ b/Godeps/_workspace/src/github.com/gorilla/websocket/util_test.go @@ -0,0 +1,34 @@ +// Copyright 2014 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "net/http" + "testing" +) + +var tokenListContainsValueTests = []struct { + value string + ok bool +}{ + {"WebSocket", true}, + {"WEBSOCKET", true}, + {"websocket", true}, + {"websockets", false}, + {"x websocket", false}, + {"websocket x", false}, + {"other,websocket,more", true}, + {"other, websocket, more", true}, +} + +func TestTokenListContainsValue(t *testing.T) { + for _, tt := range tokenListContainsValueTests { + h := http.Header{"Upgrade": {tt.value}} + ok := tokenListContainsValue(h, "Upgrade", "websocket") + if ok != tt.ok { + t.Errorf("tokenListContainsValue(h, n, %q) = %v, want %v", tt.value, ok, tt.ok) + } + } +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/.gitignore b/Godeps/_workspace/src/github.com/gyepisam/mcf/.gitignore new file mode 100644 index 0000000..be077f7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/.gitignore @@ -0,0 +1,2 @@ +*~ +.redo diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/@build.do b/Godeps/_workspace/src/github.com/gyepisam/mcf/@build.do new file mode 100644 index 0000000..5b7001a --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/@build.do @@ -0,0 +1,2 @@ +d=$(dirname $0) +xargs -L1 -i sh -c "go build $d/{}/*.go" < DIRS diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/@clean.do b/Godeps/_workspace/src/github.com/gyepisam/mcf/@clean.do new file mode 100644 index 0000000..184f3fd --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/@clean.do @@ -0,0 +1 @@ +find -type f -name '*~' -delete diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/@tests.do b/Godeps/_workspace/src/github.com/gyepisam/mcf/@tests.do new file mode 100644 index 0000000..634ab79 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/@tests.do @@ -0,0 +1 @@ +tail -n +2 DIRS | xargs -L1 -i sh -c "cd {} && go test -i && go test -v" diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/DIRS b/Godeps/_workspace/src/github.com/gyepisam/mcf/DIRS new file mode 100644 index 0000000..f46e659 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/DIRS @@ -0,0 +1,5 @@ +. +scrypt +bcrypt +pbkdf2 +test \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/Makefile b/Godeps/_workspace/src/github.com/gyepisam/mcf/Makefile new file mode 100644 index 0000000..912e76c --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/Makefile @@ -0,0 +1,9 @@ +.PHONY: clean + +clean: + find -type f -name '*~' -delete + +.PHONY: test +test: + go test + go test $$(basename $$PWD)/scrypt diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/README.md b/Godeps/_workspace/src/github.com/gyepisam/mcf/README.md new file mode 100644 index 0000000..d456ee7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/README.md @@ -0,0 +1,80 @@ +#Description + +mcf is a Go library for creating, verifying, upgrading and managing a variety of hashed password schemes. + +mcf provides a simple API for applications to use a variety of password +hashing schemes, including bcrypt, scrypt, and pbkdf2 as well a management +mechanism to easily and transparently set the default password +scheme, change schemes, or change scheme parameters such as work factors, +salt length, key length without rewriting the application. + +In addition to the separation of usage and policy model of the UNIX password +mechanism, mcf borrows the /etc/passwd format and generates passwords +in the [Modular Crypt Format \(MCF\)] (http://pythonhosted.org/passlib/modular_crypt_format.html). +,allows for the simultaneous existence of multiple types and generations of passwords. +As a text format, it provides for easy database storage and subsequent verification. + +Any application would benefit from the simplicity, ease and secure +defaults of this package. Applications and web sites that need to support +multiple password hashing mechanisms and/or need to allow for different +upgrade policies would find it especially useful. + +#Install + +mcf can be installed with the command: + + go get github.com/gyepisam/mcf + +#Example + +Here is a simple example on how to use mcf to generate pbkdf2 hashed passwords. +Note that the same example would work for any other scheme and multiple schemes +can exist concurrently. + + import "github.com/gyepisam/mcf" + import "github.com/gyepisam/mcf/pbkdf2" + + +To create a password, assuming you have the `username` and `plaintext` variables from a user signup session: + + passwd, err := mcf.Create(plaintext) + // error handling elided + // Store passwd in database + +To verify a password, assuming you have the `username` and `plaintext` variables from a user login session +and have retrieved the stored password from the database into the `passwd` variable: + + isValid, err := mcf.Verify(plaintext, passwd) + // error handling elided. + +The verification can be combined with automated password upgrades with code like this: + + isValid, err := mcf.Verify(plaintext, passwd) + // error handling elided. + + if !isValid { + // fail -- failed password verification + return false + } + + isCurrent, err := mcf.IsCurrent(passwd) + // error handling elided + + if !isCurrent { + newPasswd, err := mcf.Create(plaintext) + // error handling elided + // Update passwd in database + } + + // success! + +With this method, user passwords will automatically upgrade to use any new schemes or stronger work factors +set by policy through the management API. + +#Author + +mcf is written by Gyepi Sam and is released under a BSD license. + +I am welcome any and all feedback on this software. + +-Gyepi Sam diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/base64.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/base64.go new file mode 100644 index 0000000..d2bc540 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/base64.go @@ -0,0 +1,37 @@ +// Copied from crypto/bcrypt for testing. + +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +import "encoding/base64" + +const alphabet = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +var bcEncoding = base64.NewEncoding(alphabet) + +func base64Encode(src []byte) []byte { + n := bcEncoding.EncodedLen(len(src)) + dst := make([]byte, n) + bcEncoding.Encode(dst, src) + for dst[n-1] == '=' { + n-- + } + return dst[:n] +} + +func base64Decode(src []byte) ([]byte, error) { + numOfEquals := 4 - (len(src) % 4) + for i := 0; i < numOfEquals; i++ { + src = append(src, '=') + } + + dst := make([]byte, bcEncoding.DecodedLen(len(src))) + n, err := bcEncoding.Decode(dst, src) + if err != nil { + return nil, err + } + return dst[:n], nil +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt.go new file mode 100644 index 0000000..eab68c6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt.go @@ -0,0 +1,67 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bcrypt implements a password encoding mechanism for the mcf framework +package bcrypt + +import ( + "code.google.com/p/go.crypto/bcrypt" + "github.com/gyepisam/mcf" +) + +// DefaultCost is the base 2 logarithm of the Bcrypt work factor +// and is exported here for documentation purposes only. +// Use SetCost() to change it. +const DefaultCost = 12 + +type config struct { + Cost int +} + +func register(c config) error { + return mcf.Register(mcf.BCRYPT, &c) +} + +func init() { + err := register(config{DefaultCost}) + if err != nil { + panic(err) + } +} + +// SetCost sets the cost parameter of the Bcrypt algorithm. +// The value is the base 2 logarithm of the work factor. +func SetCost(cost int) error { + // punt and see if the underlying algorithm likes the new value! + _, err := bcrypt.GenerateFromPassword([]byte("password"), cost) + if err != nil { + return err + } + return register(config{cost}) +} + +func (c *config) Id() []byte { + return []byte("2a") // hashes are prefixed with ..., not "bcrypt" +} + +func (c *config) Create(plaintext []byte) (encoded []byte, err error) { + return bcrypt.GenerateFromPassword(plaintext, c.Cost) +} + +func (c *config) Verify(plaintext, encoded []byte) (isValid bool, err error) { + err = bcrypt.CompareHashAndPassword(encoded, plaintext) + isValid = err == nil + if err == bcrypt.ErrMismatchedHashAndPassword { + err = nil + } + return +} + +func (c *config) IsCurrent(encoded []byte) (isCurrent bool, err error) { + cost, err := bcrypt.Cost(encoded) + if err == nil { + isCurrent = cost >= c.Cost + } + return +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt_test.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt_test.go new file mode 100644 index 0000000..d0721a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/bcrypt/bcrypt_test.go @@ -0,0 +1,164 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package bcrypt + +// Test vector copied from https://code.google.com/p/bcryptnet/source/browse/src/BCryptNET.Tests/BCryptTest.cs +// and edited for Go. + +import ( + "bytes" + "crypto/rand" + "fmt" + "strings" + + "github.com/gyepisam/mcf" + "testing" +) + +var testVectors = []struct { + plain string + salt string + passwd string +}{ + {"", + "$2a$06$DCq7YPn5Rq63x1Lad4cll.", + "$2a$06$DCq7YPn5Rq63x1Lad4cll.TV4S6ytwfsfvkgY8jIucDrjc8deX1s."}, + {"", + "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.", + "$2a$08$HqWuK6/Ng6sg9gQzbLrgb.Tl.ZHfXLhvt/SgVyWhQqgqcZ7ZuUtye"}, + {"", + "$2a$10$k1wbIrmNyFAPwPVPSVa/ze", + "$2a$10$k1wbIrmNyFAPwPVPSVa/zecw2BCEnBwVS2GbrmgzxFUOqW9dk4TCW"}, + {"", + "$2a$12$k42ZFHFWqBp3vWli.nIn8u", + "$2a$12$k42ZFHFWqBp3vWli.nIn8uYyIkbvYRvodzbfbK18SSsY.CsIQPlxO"}, + {"a", + "$2a$06$m0CrhHm10qJ3lXRY.5zDGO", + "$2a$06$m0CrhHm10qJ3lXRY.5zDGO3rS2KdeeWLuGmsfGlMfOxih58VYVfxe"}, + {"a", + "$2a$08$cfcvVd2aQ8CMvoMpP2EBfe", + "$2a$08$cfcvVd2aQ8CMvoMpP2EBfeodLEkkFJ9umNEfPD18.hUF62qqlC/V."}, + {"a", + "$2a$10$k87L/MF28Q673VKh8/cPi.", + "$2a$10$k87L/MF28Q673VKh8/cPi.SUl7MU/rWuSiIDDFayrKk/1tBsSQu4u"}, + {"a", + "$2a$12$8NJH3LsPrANStV6XtBakCe", + "$2a$12$8NJH3LsPrANStV6XtBakCez0cKHXVxmvxIlcz785vxAIZrihHZpeS"}, + {"abc", + "$2a$06$If6bvum7DFjUnE9p2uDeDu", + "$2a$06$If6bvum7DFjUnE9p2uDeDu0YHzrHM6tf.iqN8.yx.jNN1ILEf7h0i"}, + {"abc", + "$2a$08$Ro0CUfOqk6cXEKf3dyaM7O", + "$2a$08$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm"}, + {"abc", + "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.", + "$2a$10$WvvTPHKwdBJ3uk0Z37EMR.hLA2W6N9AEBhEgrAOljy2Ae5MtaSIUi"}, + {"abc", + "$2a$12$EXRkfkdmXn2gzds2SSitu.", + "$2a$12$EXRkfkdmXn2gzds2SSitu.MW9.gAVqa9eLS1//RYtYCmB1eLHg.9q"}, + {"abcdefghijklmnopqrstuvwxyz", + "$2a$06$.rCVZVOThsIa97pEDOxvGu", + "$2a$06$.rCVZVOThsIa97pEDOxvGuRRgzG64bvtJ0938xuqzv18d3ZpQhstC"}, + {"abcdefghijklmnopqrstuvwxyz", + "$2a$08$aTsUwsyowQuzRrDqFflhge", + "$2a$08$aTsUwsyowQuzRrDqFflhgekJ8d9/7Z3GV3UcgvzQW3J5zMyrTvlz."}, + {"abcdefghijklmnopqrstuvwxyz", + "$2a$10$fVH8e28OQRj9tqiDXs1e1u", + "$2a$10$fVH8e28OQRj9tqiDXs1e1uxpsjN0c7II7YPKXua2NAKYvM6iQk7dq"}, + {"abcdefghijklmnopqrstuvwxyz", + "$2a$12$D4G5f18o7aMMfwasBL7Gpu", + "$2a$12$D4G5f18o7aMMfwasBL7GpuQWuP3pkrZrOAnqP.bmezbMng.QwJ/pG"}, + {"~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$06$fPIsBO8qRqkjj273rfaOI.", + "$2a$06$fPIsBO8qRqkjj273rfaOI.HtSV9jLDpTbZn782DC6/t7qT67P6FfO"}, + {"~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$08$Eq2r4G/76Wv39MzSX262hu", + "$2a$08$Eq2r4G/76Wv39MzSX262huzPz612MZiYHVUJe/OcOql2jo4.9UxTW"}, + {"~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$10$LgfYWkbzEvQ4JakH7rOvHe", + "$2a$10$LgfYWkbzEvQ4JakH7rOvHe0y8pHKF9OaFgwUZ2q7W2FFZmZzJYlfS"}, + {"~!@#$%^&*() ~!@#$%^&*()PNBFRD", + "$2a$12$WApznUOJfkEGSmYRfnkrPO", + "$2a$12$WApznUOJfkEGSmYRfnkrPOr466oFDCaj4b6HY3EXGvfxm43seyhgC"}, +} + +func TestVectors(t *testing.T) { + for i, v := range testVectors { + + x := strings.Split(v.salt[1:], "$") + if len(x) != 3 { + t.Fatalf("%d: invalid field structure: %s", i, v.salt) + } + costIn, saltIn := x[1], x[2] //index 0 is not interesting. + + salt, err := base64Decode([]byte(saltIn)) + + if err != nil { + t.Fatalf("%d: error decoding salt: %s", i, err) + } + var cost int + if _, err := fmt.Sscanf(costIn, "%02d", &cost); err != nil { + t.Fatalf("%d: error decoding param [%+v]: %s", i, costIn, err) + } + + // Hijack rand.Reader to feed salt to crypto/bcrypt. + // Need extra salt because SetCost generates a digest + // to test the new cost, and each of the 3 calls to it uses + // up one portion of salt. + rand.Reader = bytes.NewReader(bytes.Repeat(salt, 4)) + + err = SetCost(cost) + if err != nil { + t.Errorf("%d: SetCost: unexpected error: %s", i, err) + } + + encoded, err := mcf.Create(v.plain) + if err != nil { + // password must be at least 2 bytes otherwise crypto/blowfish complains. + // This really should be handled in crypto/bcrypt + if len(v.plain) < 3 { + continue + } + t.Errorf("%d: unexpected error: %s", i, err) + continue + } + + if want, got := v.passwd, encoded; want != got { + t.Errorf("%d: output mismatch. want: %s, got %s", i, want, got) + continue + } + + isValid, err := mcf.Verify(v.plain, encoded) + if err != nil { + t.Errorf("%d: verify: unexpected failure on %q: %s", i, encoded, err) + continue + } + if !isValid { + t.Errorf("%d: IsValid: expecting true got false", i) + continue + } + + for j, pair := range []struct { + cost int + answer bool + }{{cost, true}, {cost + 1, false}} { + + err := SetCost(pair.cost) + if err != nil { + t.Errorf("%d-%d: SetCost: unexpected error: %s", i, j, err) + } + + isCurrent, err := mcf.IsCurrent(encoded) + if err != nil { + t.Errorf("%d-%d: IsCurrent: unexpected failure: %", i, j, err) + continue + } + if isCurrent != pair.answer { + t.Errorf("%d-%d: IsCurrent: expecting %t got %t", i, j, pair.answer, isCurrent) + continue + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/bridge/bridge.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/bridge/bridge.go new file mode 100644 index 0000000..0971e20 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/bridge/bridge.go @@ -0,0 +1,122 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bridge simplifies the creation of a password encoder by abstracting out the generic parts. + +// Concrete implementations that use this package can be greatly simplified. +// See github.com/gyepisam/mcf/scrypt and github.com/gyepisam/mcf/pbkdf2 + +package bridge + +import ( + "crypto/subtle" + + "github.com/gyepisam/mcf/encoder" + "github.com/gyepisam/mcf/password" +) + +// Implementer represents a concrete implementation such as scrypt or pbkdf2 +type Implementer interface { + // Params encodes the parameters used by Key. + Params() string + + // Setparams restores parameters from string produced by Params. + SetParams(string) error + + // Produces required amount of salt. + Salt() ([]byte, error) + + // Key generates a key (hash, digest, key) using password, salt and implementer specific parameters. + Key(password, salt []byte) ([]byte, error) + + // AtLeast compares the implementer to the current implementer and determines whether the + // implementer parameters are at least as strong as the current. It is used to determine + // whether an encoded password needs to be re-encoded. + AtLeast(Implementer) bool +} + +// Encoder implements the encoder.Encoder interface using an Implementer to +// abstract implementation specific parts. +type Encoder struct { + name []byte + implementer func() Implementer +} + +// New takes an implementation name and a function that produces an Implementer +// and produces an encoder.Encoder that uses the Implementer. +func New(name []byte, fn func() Implementer) encoder.Encoder { + return &Encoder{name: name, implementer: fn} +} + +// Id returns the name of the encoder, which is the type of passwords it can handle. +func (enc *Encoder) Id() []byte { return enc.name } + +// Create produces an encoded password from a plaintext password using the current configuration. +// The application must store the encoded password for future use. +func (enc *Encoder) Create(plaintext []byte) (encoded []byte, err error) { + + imp := enc.implementer() + + passwd := password.New(enc.name) + passwd.Params = []byte(imp.Params()) + + passwd.Salt, err = imp.Salt() + if err != nil { + return + } + + passwd.Key, err = imp.Key(plaintext, passwd.Salt) + if err != nil { + return + } + + return passwd.Bytes(), nil +} + +// Verify returns true if the proffered plaintext password, +// when encoded using the same parameters, matches the encoded password. +func (enc *Encoder) Verify(plaintext, encoded []byte) (isValid bool, err error) { + + passwd := password.New(enc.name) + + err = passwd.Parse(encoded) + if err != nil { + return + } + + imp := enc.implementer() + err = imp.SetParams(string(passwd.Params)) + if err != nil { + return + } + + testKey, err := imp.Key(plaintext, passwd.Salt) + if err != nil { + return + } + + return subtle.ConstantTimeCompare(passwd.Key, testKey) == 1, nil +} + +// IsCurrent returns true if the parameters used to generate the encoded password +// are at least as good as those in params. +// If IsCurrent returns false the encoding is out of date and should be regenerated, +// the application should call mcf.Create() to produce a new encoding to replace the current one. +func (enc *Encoder) IsCurrent(encoded []byte) (isCurrent bool, err error) { + + passwd := password.New(enc.name) + + err = passwd.Parse(encoded) + if err != nil { + return + } + + imp := enc.implementer() + err = imp.SetParams(string(passwd.Params)) + if err != nil { + return + } + + return imp.AtLeast(enc.implementer()), nil +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/doc.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/doc.go new file mode 100644 index 0000000..59b5f85 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/doc.go @@ -0,0 +1,97 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Package mcf is a Go library for creating, verifying, upgrading and managing bcrypt, scrypt, and pbkdf2 password hashes. + +mcf provides a simple API for applications to use a variety of +password hashing schemes as well a management mechanism to easily and +transparently set the default password scheme, change schemes, or change +scheme parameters such as work factors, salt length, key length without +rewriting the application. + +Usage: + +Import the mcf package along with at least one encoder. +You can import as many encoders as desired. +The first encoder is used to encode all new passwords. +Subsequent encoders are used to decode. +There's an API to set the default encoder. + +Note that once an encoder has been superceded (is no longer the first imported encoding) +it must not be removed from the import lists until all existing instances of +that encoding have either been converted to a newer encoding or invalidated. + + import ( + "github.com/gyepisam/mcf" + "github.com/gyepisam/mcf/scrypt" + ) + + // A user provides a password at registration or signup. + username, plaintext := "alibaba", "dfj1A4finbfya9BFDL7d" + + // Create an encoding using the default + encoded, err := mcf.Create(plaintext) + // error handling elided + + // Insert encoded value and user info in database + user = db.NewUser() + user.Username = username + user.Password = encoded + err = user.Save() + // handle errors + +To authenticate the user: + + // A user provides a password at login. + username, plaintext := "alibaba", "dfj1A4finbfya9BFDL7d" + + user, err := db.FindUser("username = $1", username) + // error handling elided + + isValid, err := mcf.Verify(plaintext, user.Password) + // error handling elided + +When authentication succeeds, it is a useful practice to re-encode the password if it is out of date +with respect to current security policy. It is the best possible time (also, the only possible time) +to do this, since the plaintext password is available. The final part changes to something like: + + if isValid { + go func(plaintext, encoded, username string) { + isCurrent, err := mcf.IsCurrent(encoded) + // error handling elided but assumed to return. + if !isCurrent { + encoded, err := mcf.Create(plaintext) + // Update encoded value in database + } + } (plaintext, encoded, user.username) + + // Success + } + +The update is handled by a go routine since it would have the least impact on the user experience. +However, given its non-interactivity it is especially important to log errors that might occur. + +Changing work factors or implementing other policy changes is similarly simple: + + func init() { + config := scrypt.GetConfig() + config.LgN++ // double the default work factor + scrypt.SetConfig(config) + } + +Later, if you decide to switch to pbkdf2, just import it + + + _ "github.com/gyepisam/mcf/pbkdf2" + +and make sure it is the current encoder. + + mcf.SetDefault(mcf.PBKDF2) + +All subsequently created password will use the new scheme. If you also use the auto upgrade mechanism, then +users will be upgraded upon login as well. + +*/ +package mcf diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/encoder/encoder.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/encoder/encoder.go new file mode 100644 index 0000000..c0f43a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/encoder/encoder.go @@ -0,0 +1,27 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package encoder represents an interface that MCF password encoders must implement +package encoder + +// An Encoder encodes a plaintext password into a hashed format. +type Encoder interface { + // Id returns a set of bytes that identify this encoder's hashed passwords. + // It should be the same as the id used in the encoded password and should not include a separator. + Id() []byte + + // Create produces an encoded password from a plaintext password. + // The application must store the encoded password for future use. + Create(plaintext []byte) (encoded []byte, err error) + + // Verify returns true if the proffered plaintext password, + // when encoded using the same parameters, matches the encoded password. + Verify(plaintext, encoded []byte) (isValid bool, err error) + + // IsCurrent returns true if the parameters used to generate the encoded password + // are at least as good as those the encoder would use to generate a new encoded password. + // If IsCurrent returns false the encoding is out of date and should be regenerated, + // the application should call mcf.Create() to produce a new encoding to replace the current one. + IsCurrent(encoded []byte) (isCurrent bool, err error) +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/encoding.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/encoding.go new file mode 100644 index 0000000..372190e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/encoding.go @@ -0,0 +1,54 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mcf + +// An Encoding is a number for an encoder and is used to disambiguate amongst the various encoders. +// Not all encoders will be implemented, installed, or used in any given system. +type Encoding uint8 + +// List of known encodings. +const ( + BCRYPT Encoding = iota // import "github.com/gyepisam/mcf/bcrypt" + SCRYPT // import "github.com/gyepisam/mcf/scrypt" + PBKDF2 // import "github.com/gyepisam/mcf/pbkdf2" + //CRYPT // Not implemented yet + + maxEncoding +) + +// if you add an encoding above, name it below! + +func (e Encoding) String() string { + switch e { + case BCRYPT: + return "bcrypt" + case SCRYPT: + return "scrypt" + case PBKDF2: + return "pbkdf2" + /* case CRYPT: + return "crypt" */ + } + return "unknown" +} + +// IsValid returns true if the encoding is known. +func (e Encoding) IsValid() bool { + return e >= 0 && e < maxEncoding +} + +// ErrUnregisteredEncoding is returned when an unregistered encoding is encountered. +type ErrUnregisteredEncoding struct{ s string } + +func (e *ErrUnregisteredEncoding) Error() string { return e.s } + +// ErrInvalidEncoding is returned when an invalid encoding is encountered. +type ErrInvalidEncoding struct{ s string } + +func (e *ErrInvalidEncoding) Error() string { return e.s } + +func (e Encoding) errInvalid() error { + return &ErrInvalidEncoding{"invalid encoding: " + string(e)} +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/mcf.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/mcf.go new file mode 100644 index 0000000..59d65be --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/mcf.go @@ -0,0 +1,173 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mcf + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + "io" + + "github.com/gyepisam/mcf/encoder" +) + +type instance struct { + id []byte + encoder.Encoder +} + +var ( + encoders [maxEncoding]*instance + defaultEncoding = maxEncoding +) + +// ErrNoEncoder is returned if an encoded password does not match any known encoders. +// The encoded password is appended to the error message to aid in resolving the problem. +type ErrNoEncoder struct { + encoded string +} + +func (e *ErrNoEncoder) Error() string { + return fmt.Sprintf("No matching encoder found for: %q", e.encoded) +} + +// A SaltMiner is function that takes an int and produces that many random bytes. +// It exists to allow variation in the source of salt. +type SaltMiner func(int) ([]byte, error) + +// Register adds an encoder implementation to the list. +// It is expected that each encoder will call Register from an init() function. +// The first encoder imported becomes the default and is used to create new passwords. +// Subsequent imported encoders, if any, are used for decoding, where necessary. +// See SetDefault() to set the default encoder manually. +func Register(encoding Encoding, enc encoder.Encoder) error { + if !encoding.IsValid() { + return encoding.errInvalid() + } + + id := enc.Id() + if len(id) == 0 { + return fmt.Errorf("empty id: encoding=%s", encoding) + } + + encoders[encoding] = &instance{id: id, Encoder: enc} + + // default to first registered encoder. + if !defaultEncoding.IsValid() { + defaultEncoding = encoding + } + + return nil +} + +// SetDefault sets the default encoding used to create passwords. +// Since the first registered encoder is used as the default encoder, +// it is not necessary to call this routine unless you have multiple encoders +// imported, in which case, it is advisable to call this routine to avoid a dependency +// on the order of import statements. +func SetDefault(encoding Encoding) error { + if !encoding.IsValid() { + return encoding.errInvalid() + } + if encoders[encoding] == nil { + return &ErrUnregisteredEncoding{fmt.Sprintf("encoding [%s] not registered. Forgot to import?", encoding)} + } + + defaultEncoding = encoding + + return nil +} + +// Create takes a plaintext password and uses the default encoder to +// create an encoded password in Modular Crypt Format, which it returns. +// The application is expected to store this password in order to subsequently +// verify the plaintext password. +func Create(plaintext string) (encoded string, err error) { + + if !defaultEncoding.IsValid() { + err = errors.New("No encoders registered") + return + } + + enc := encoders[defaultEncoding] + //This should not happen, but use suspenders anyway. + if enc == nil { + panic(fmt.Sprintf("missing implementation for encoding [%s]", defaultEncoding)) + } + + b, err := enc.Create([]byte(plaintext)) + if err != nil { + return + } + + return string(b), nil +} + +func findInstance(encoded []byte) (Encoding, *instance) { + for i, e := range encoders { + if e == nil { + continue + } + + if len(encoded) > 0 && bytes.HasPrefix(encoded[1:], e.id) { + return Encoding(i), e + } + } + return maxEncoding, nil +} + +// Verify takes a plaintext password and a encoded password and returns true +// if the password, when encoded by the same encoder, using the same parameters, +// matches the encoded password. +func Verify(plaintext, encoded string) (isValid bool, err error) { + b := []byte(encoded) + _, enc := findInstance(b) + if enc == nil { + return false, &ErrNoEncoder{encoded} + } + return enc.Verify([]byte(plaintext), b) +} + +// IsCurrent returns true if the encoded password was generated by the current encoder with the current parameters. +// If it returns false, then the encoded password should be regenerated and replaced. +// Assuming that policy changes are always to increase security by using stronger hashes or increasing work factors, +// IsCurrent presents a mechanism to query an encoded password and determine whether it needs to be re-created. +func IsCurrent(encoded string) (isCurrent bool, err error) { + b := []byte(encoded) + encoding, enc := findInstance(b) + if enc == nil { + err = &ErrNoEncoder{encoded} + } else { + isCurrent, err = enc.IsCurrent(b) + + if err == nil && isCurrent { + // if the encoded password's scheme is not the default, + // then it is out of date. + isCurrent = encoding == defaultEncoding + } + } + return +} + +// Salt produces the specified number of random bytes. +// If minerFn is nil, the function generates bytes from rand.Reader. +// Otherwise minerFn is called and its results validated and returned. +func Salt(size int, minerFn SaltMiner) (salt []byte, err error) { + + if minerFn == nil { + salt = make([]byte, size) + _, err = io.ReadFull(rand.Reader, salt) + return + } + + salt, err = minerFn(size) + if err == nil { + if m, n := size, len(salt); m != n { + err = fmt.Errorf("%s: short salt read. want: %d, got %d", defaultEncoding, m, n) + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/password/password.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/password/password.go new file mode 100644 index 0000000..7d08ded --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/password/password.go @@ -0,0 +1,152 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package password manipulates passwords stored in Modular Crypt Format. +package password + +import ( + "bytes" + "crypto/subtle" + "encoding/base64" + "encoding/hex" + "fmt" +) + +//This separates password fields +const separator byte = '$' + +// Passwd is a password separated into components +type Passwd struct { + Name []byte + Params []byte + Salt []byte + Key []byte + + // Encoder is a function that encodes Salt and Key into serialized form + // Defaults to EncoderBase64, but can be changed to EncoderHex or other function. + Encoder func([]byte) []byte + + // Decoder deserializes the Salt and Key fields. + // The default recognizes Base64 or Hex. + // If you replace Encoder with something else, replace Decoder too, possibly with a fallback to the + // default. + Decoder func([]byte) ([]byte, error) +} + +// count of "interesting" fields. +const fieldCount = 4 + +//ErrorInputPassword is returned for input passwords that fail validation. +//The struct can be examined for a possible solution. +type ErrorInputPassword struct { + Msg string //Error message + Password string //Input password +} + +func (e ErrorInputPassword) Error() string { + return e.Msg +} + +// New returns a Passwd struct initialized with the default encoders. +func New(name []byte) *Passwd { + return &Passwd{Name: name, Decoder: decode, Encoder: EncodeBase64} +} + +// Parse extracts an encoded password in Modular Crypt Format into a Passwd structure. +// The input is validated and should match what String() produces. +func (p *Passwd) Parse(encoded []byte) (err error) { + + inputErr := func(format string, args ...interface{}) error { + return ErrorInputPassword{ + fmt.Sprintf("%s: "+format, append([]interface{}{string(p.Name)}, args...)...), + string(encoded), + } + } + + if len(encoded) == 0 { + return inputErr("empty password") + } + + if encoded[0] != separator { + return inputErr("password does not begin with separator") + } + + parts := bytes.Split(encoded[1:], []byte{separator}) + if len(parts) < fieldCount { + return inputErr("password has too few fields") + } + if len(parts) > fieldCount { + return inputErr("password has too many fields") + } + + if b := parts[0]; subtle.ConstantTimeCompare(b, p.Name) == 0 { + return inputErr("unexpected password type: %s", string(b)) + } + + p.Params = parts[1] + + p.Salt, err = p.Decoder(parts[2]) + if err != nil { + return + } + + p.Key, err = p.Decoder(parts[3]) + + return +} + +// Bytes produces an encoded password in Modular Crypt Format. +// The output can be stored and later used to verify the password. +func (p *Passwd) Bytes() []byte { + + in := [][]byte{p.Name, p.Params, p.Encoder(p.Salt), p.Encoder(p.Key)} + + n := 0 + for _, b := range in { + n += len(b) + } + + n += len(in) //add separator for each item + + out := make([]byte, n) + + n = 0 + for _, b := range in { + out[n] = separator + n++ + n += copy(out[n:], b) + } + + return out +} + +// determine input type and decode accordingly +func decode(encoded []byte) (dst []byte, err error) { + var b64NotHex = []byte("GHIJKLMNOPQRSTUVWXYZghijklmnopqrstuvwxyz+/-_") + if bytes.IndexAny(b64NotHex, string(encoded)) < 0 { + dst = make([]byte, hex.DecodedLen(len(encoded))) + n, err := hex.Decode(dst, encoded) + return dst[:n], err + } + + enc := base64.StdEncoding + dst = make([]byte, enc.DecodedLen(len(encoded))) + n, err := enc.Decode(dst, encoded) + return dst[:n], err +} + +// EncodeHex encodes the input bytes into hex format. +func EncodeHex(in []byte) (out []byte) { + out = make([]byte, hex.EncodedLen(len(in))) + hex.Encode(out, in) + return +} + +// EncodeBase64 encodes the input bytes into standard base64 format. +func EncodeBase64(in []byte) (out []byte) { + enc := base64.StdEncoding + out = make([]byte, enc.EncodedLen(len(in))) + enc.Encode(out, in) + return out +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2.go new file mode 100644 index 0000000..5f8b45e --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2.go @@ -0,0 +1,203 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pbkdf2 implements a password encoding mechanism for the mcf framework +package pbkdf2 + +import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "fmt" + "hash" + + "code.google.com/p/go.crypto/pbkdf2" + + "github.com/gyepisam/mcf" + "github.com/gyepisam/mcf/bridge" +) + +// Hash represents the HMAC hash function that the PBKDF2 algorithm uses as a pseudorandom function. +type Hash string + +// Hash implements the Stringer interface +func (h Hash) String() string { return string(h) } + +// Size is the output length of the hash function. +func (h Hash) Size() int { + hash, ok := hashes[h] + if !ok { + panic("unknown hash: " + string(h)) + } + return hash().Size() +} + +// Available hashes +const ( + SHA1 Hash = "SHA1" + SHA224 Hash = "SHA224" + SHA256 Hash = "SHA256" + SHA384 Hash = "SHA384" + SHA512 Hash = "SHA512" +) + +var hashes = map[Hash]func() hash.Hash{ + SHA1: sha1.New, + SHA224: sha256.New224, + SHA256: sha256.New, + SHA384: sha512.New384, + SHA512: sha512.New, +} + +// Config contains the parameters for the PBKDF2 algorithm along with associated values. +type Config struct { + // The Pseudo Random Function (prf) used by the PBKDF2 algorithm. + // When this value is changed, KeyLen will most likely need to change as well + // since hash functions generally produce output of differing lengths. + Hash Hash + + // Number of iteration rounds in the PBKDF2 algorithm. + // The RFC recommends at least 1000 + Iterations int + + // Length of key produced by algorithm in bytes. + // Defaults to the output length of the HMAC Hash. + KeyLen int + + // Size of salt in bytes. + // The RFC recommends at least 8 bytes. + SaltLen int +} + +// Default values. These are exported for documentation purposes. +// See GetConfig() and SetConfig() on how to change them. +const ( + DefaultIterations = 2000 + + DefaultSaltLen = 16 + + DefaultPrf = SHA1 +) + +// This value is exported for documentation purposes. +// See GetConfig() and SetConfig() on how to change it. +// Defined as a var to allow for non constant initialization. +var DefaultKeyLen = DefaultPrf.Size() + +// Returns the default configuration. +// To change default values, pass the modified output of GetConfig() to SetConfig(). +// See SetConfig() for an example. +func GetConfig() Config { + return Config{ + Iterations: DefaultIterations, + SaltLen: DefaultSaltLen, + Hash: DefaultPrf, + KeyLen: DefaultKeyLen, + } +} + +// SetConfig establishes a new default configuration for this algorithm. +// It is only necessary to set the configuration if this algorithm is the default algorithm. +// To change a configuration, +// Get a copy of the configuration +// +// config := pbkdf2.GetConfig() +// +// Change as necessary +// +// config.Iterations *= 1.5 // increase iterations by 50% +// config.Hash = pbkdf2.SHA256 // change hmac +// config.KeyLen= pbkdf2.SHA256.Size() +// +// then set +// +// err := pbkdf2.SetConfig(config) +// // error handling elided +func SetConfig(config Config) error { + err := (&config).validate() + if err != nil { + return err + } + return register(config) +} + +// SaltMine is a custom source of salt, which is normally unset. +// Change this to override the use of rand.Reader if you need to use a custom salt producer. +var SaltMine mcf.SaltMiner = nil + +func register(config Config) error { + + // Constructor for Implementer. Always return a fresh copy. + fn := func() bridge.Implementer { + c := config + return &c + } + + // the bridge handles the generic parts of the interface + enc := bridge.New([]byte("pbkdf2"), fn) + + return mcf.Register(mcf.PBKDF2, enc) +} + +func init() { + err := register(GetConfig()) + if err != nil { + panic(err) + } +} + +// ErrInvalidHash is returned when an invalid Hash is encountered. +// The name of the hash is printed in the Error() string and is also exported. +type ErrInvalidHash struct { + Hash Hash +} + +// ErrInvalidHash implements the Error interface. +func (e *ErrInvalidHash) Error() string { + return fmt.Sprintf("Invalid Hash: %s", e.Hash) +} + +func (c *Config) validate() error { + if _, ok := hashes[c.Hash]; !ok { + return &ErrInvalidHash{c.Hash} + } + return nil +} + +// Keep these together +// Note that Sscanf on %s breaks on space and must therefore be the last item (and the only string). +const format = "keylen=%d,iterations=%d,hmac=%s" + +// Params encodes algorithm parameters in a string for later use. +func (c *Config) Params() string { + return fmt.Sprintf(format, c.KeyLen, c.Iterations, c.Hash) +} + +// SetParams extracts encoded algorithm parameters from the output of Params(). +func (c *Config) SetParams(params string) error { + _, err := fmt.Sscanf(params, format, &c.KeyLen, &c.Iterations, &c.Hash) + if err != nil { + return err + } + return c.validate() +} + +// Salt produces SaltLen bytes of random data. +func (c *Config) Salt() ([]byte, error) { + return mcf.Salt(c.SaltLen, SaltMine) +} + +// Key generates a PBKDF2 digest from the password, salt and iteration count, +// using the Hash as a pseudorandom function. +func (c *Config) Key(password, salt []byte) ([]byte, error) { + return pbkdf2.Key(password, salt, c.Iterations, c.KeyLen, hashes[c.Hash]), nil +} + +// AtLeast compares the parameters for an encoded password to the current configuration +// and returns true if the encoded password configuration has the same or longer configuration +// parameter values. +func (c *Config) AtLeast(current_imp bridge.Implementer) bool { + current := current_imp.(*Config) // ok to panic if this fails. + return !(c.Iterations < current.Iterations || c.KeyLen < current.KeyLen || c.SaltLen < current.SaltLen) +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2_test.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2_test.go new file mode 100644 index 0000000..74f1c25 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/pbkdf2/pbkdf2_test.go @@ -0,0 +1,177 @@ +package pbkdf2 + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/gyepisam/mcf" + "github.com/gyepisam/mcf/password" +) + +var testVectors = []struct { + plain string + salt string + iterations int + key string +}{ + { + plain: "password", + salt: "salt", + iterations: 1, + key: "0c60c80f961f0e71f3a9b524af6012062fe037a6", + }, + { + plain: "password", + salt: "salt", + iterations: 2, + key: "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957", + }, + { + plain: "password", + salt: "salt", + iterations: 4096, + key: "4b007901b765489abead49d926f721d065a429c1", + }, + { + plain: "password", + salt: "salt", + iterations: 16777216, + key: "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984", + }, + { + plain: "passwordPASSWORDpassword", + salt: "saltSALTsaltSALTsaltSALTsaltSALTsalt", + iterations: 4096, + key: "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038", + }, + { + plain: "pass\x00word", + salt: "sa\x00lt", + iterations: 4096, + key: "56fa6aa75548099dcc37d7f03425e0c3", + }, + { + plain: "password", + salt: "ATHENA.MIT.EDUraeburn", + iterations: 1, + key: "cdedb5281bb2f801565a1122b2563515", + }, + { + plain: "password", + salt: "ATHENA.MIT.EDUraeburn", + iterations: 2, + key: "01dbee7f4a9e243e988b62c73cda935d", + }, + { + plain: "password", + salt: "ATHENA.MIT.EDUraeburn", + iterations: 1200, + key: "5c08eb61fdf71e4e4ec3cf6ba1f5512b", + }, + { + plain: "password", + salt: "\x12\x34\x56\x78\x78\x56\x34\x12", + iterations: 5, + key: "d1daa78615f287e6a1c8b120d7062a49", + }, + { + plain: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + salt: "pass phrase equals block size", + iterations: 1200, + key: "139c30c0966bc32ba55fdbf212530ac9", + }, + { + plain: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + salt: "pass phrase exceeds block size", + iterations: 1200, + key: "9ccad6d468770cd51b10e6a68721be61", + }, + { + plain: "\xf0\x9d\x84\x9e", /* g-clef (0xf09d849e) */ + salt: "EXAMPLE.COMpianist", + iterations: 50, + key: "6b9cf26d45455a43a5b8bb276a403b39", + }, +} + +func setConfig(keylen, iterations, saltlen int) (*Config, error) { + conf := GetConfig() + conf.KeyLen = keylen + conf.Iterations = iterations + conf.SaltLen = saltlen + return &conf, SetConfig(conf) +} + +func setSalt(salt string) { + SaltMine = func(n int) ([]byte, error) { return []byte(salt), nil } +} + +func TestVectors(t *testing.T) { + for i, v := range testVectors { + + key, err := hex.DecodeString(v.key) + if err != nil { + t.Errorf("%d: could not decode key: %s: %s", i, v.key, err) + continue + } + + config, err := setConfig(len(key), v.iterations, len(v.salt)) + if err != nil { + t.Errorf("%d: unexpected error setting config: %s", i, err) + } + + setSalt(v.salt) + + encoded, err := mcf.Create(v.plain) + if err != nil { + t.Errorf("%d: got unexpected error: %s", i, err) + } + + passwd := password.New([]byte("pbkdf2")) + err = passwd.Parse([]byte(encoded)) + if err != nil { + t.Errorf("%d: unexpected error creating password instance: %s", i, err) + } + + if p, q := []byte(config.Params()), passwd.Params; !bytes.Equal(p, q) { + t.Errorf("%d: params: expected %s, got %s", i, string(p), string(q)) + } + + if p, q := []byte(v.salt), passwd.Salt; !bytes.Equal(p, q) { + t.Errorf("%d: salt: expected %s, got %s", i, string(p), string(q)) + } + + if p, q := key, passwd.Key; !bytes.Equal(p, q) { + t.Errorf("%d: key: expected %x, got %x", i, p, q) + } + + isValid, err := mcf.Verify(v.plain, encoded) + if err != nil { + t.Errorf("%d: verify: unexpected failure on %q: %s", i, encoded, err) + continue + } + if !isValid { + t.Errorf("%d: verify - unexpectedly returned false", i) + continue + } + + // perturb configuration... + newConfig := *config + newConfig.KeyLen += 1 + + for j, c := range []*Config{config, &newConfig} { + setConfig(c.KeyLen, c.Iterations, c.SaltLen) + isCurrent, err := mcf.IsCurrent(encoded) + if err != nil { + t.Errorf("%d-%d: IsCurrent: unexpected failure: %", i, j, err) + continue + } + //old configuration says yes, new configuration says no + if answer := c == config; isCurrent != answer { + t.Errorf("%d-%d: IsCurrent: expecting %t got %t", i, j, answer, isCurrent) + continue + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/params.txt b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/params.txt new file mode 100644 index 0000000..6329906 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/params.txt @@ -0,0 +1,6 @@ +2 1 1 +2 10 10 +16 1 1 +16 100 100 +1024 8 16 +16384 8 1 diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt.go new file mode 100644 index 0000000..9d50f49 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt.go @@ -0,0 +1,148 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package scrypt implements a password encoding mechanism for the mcf framework +package scrypt + +import ( + "code.google.com/p/go.crypto/scrypt" + + "fmt" + + "github.com/gyepisam/mcf" + "github.com/gyepisam/mcf/bridge" +) + +// Circa 2014 work factors. +// These are exported to show default values. +// See GetConfig and SetConfig(...) to change them. +const ( + DefaultKeyLen = 32 + DefaultSaltLen = 16 + DefaultN = 1 << 16 + DefaultR = 10 + DefaultP = 2 +) + +// Config contains the scrypt algorithm parameters and other associated values. +// Use the GetConfig() and SetConfig() combination to change any desired parameters. +type Config struct { + KeyLen int //Key output size in bytes. + SaltLen int // Length of salt in bytes. + + N int // CPU/Memory cost. Must be a power of two. + R int // block size parameter. + P int // parallelization parameter. +} + +// Custom source of salt, normally unset. +// Set this if you need to override the user of rand.Reader and +// use a custom salt producer. +// Also useful for testing. +var SaltMine mcf.SaltMiner = nil + +// ErrInvalidParameter is returned by SetConfig if any of the provided parameters +// fail validation. The error message contains the name and value of the faulty +// parameter to aid in resolving the problem. +type ErrInvalidParameter struct { + Name string + Value int +} + +func (e ErrInvalidParameter) Error() string { + return fmt.Sprintf("parameter %s has invalid value: %d", e.Name, e.Value) +} + +// Config returns the default configuration used to create new scrypt password hashes. +// The return value can be modified and used as a parameter to SetConfig +func GetConfig() Config { + return Config{ + KeyLen: DefaultKeyLen, + SaltLen: DefaultSaltLen, + N: DefaultN, + R: DefaultR, + P: DefaultP, + } +} + +/* +SetConfig sets the default encoding parameters, salt length or key length. +It is best to modify a copy of the default configuration unless all parameters are changed. + +Here is an example that doubles the default work factor. + + config := scrypt.GetConfig() + config.N *= 2 + scrypt.SetConfig(config) + +*/ +func SetConfig(config Config) error { + c := &config + err := c.validate() + if err != nil { + return err + } + + return register(config) +} + +func register(config Config) error { + // Constructor function. Provide fresh copy each time. + fn := func() bridge.Implementer { + c := config + return &c + } + + enc := bridge.New([]byte("scrypt"), fn) + + return mcf.Register(mcf.SCRYPT, enc) +} + +func init() { + err := register(GetConfig()) + if err != nil { + panic(err) + } +} + +func (c *Config) validate() error { + //punt, cheat and see if the underlying algorithm complains! + _, err := c.Key([]byte("password"), []byte("salt")) + return err +} + +// Keep these together. +var format = "KeyLen=%d,N=%d,R=%d,P=%d" + +// Params returns the current digest algorithm parameters. +func (c *Config) Params() string { + return fmt.Sprintf(format, c.KeyLen, c.N, c.R, c.P) +} + +// SetParams sets the parameters for the digest algorithm. +func (c *Config) SetParams(s string) error { + _, err := fmt.Sscanf(s, format, &c.KeyLen, &c.N, &c.R, &c.P) + if err != nil { + return err + } + return c.validate() +} + +// Salt produces SaltLen bytes of random data. +func (c *Config) Salt() ([]byte, error) { + return mcf.Salt(c.SaltLen, SaltMine) +} + +// Key returns an scrypt digest of password and salt using the algorithm parameters: N, r, and p. +// The returned value is of length KeyLen. +func (c *Config) Key(plaintext []byte, salt []byte) (b []byte, err error) { + return scrypt.Key(plaintext, salt, c.N, c.R, c.P, c.KeyLen) +} + +// AtLeast returns true if the parameters used to generate the encoded password +// are at least as good as those currently in use. +func (c *Config) AtLeast(current_imp bridge.Implementer) bool { + current := current_imp.(*Config) // ok to panic + return !(c.N < current.N || c.R < current.R || c.P < current.P || c.KeyLen < current.KeyLen) +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt_test.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt_test.go new file mode 100644 index 0000000..42d1dec --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/scrypt_test.go @@ -0,0 +1,138 @@ +package scrypt + +import ( + "bytes" + "github.com/gyepisam/mcf" + "github.com/gyepisam/mcf/password" + + "testing" +) + +var testData = []struct { + plaintext string + encoded string +}{ + {"yssIi0AL", + "$scrypt$KeyLen=128,N=65536,R=10,P=2$PmxwHoNHjIILwrdOG8vA+A==$KRYMgbJr4vrYutrEjtueDDylXHQ2EoePyPoqtrDnil0jm1RfuyxT90/3gce5hw0/DTVmpcDnzkt1MWiK+zfS7+hh1EONspTZl8nGFLCsXcGiarrNKSSyRnsJN0DmSe20cfxodAB+DN1f84hxZbdmF20A2uFj36kE2ZKgTdlAYFE="}, + {"yssIi0AL", "$scrypt$KeyLen=128,N=16384,R=8,P=1$643873597251626754$e6b2da790d99bef794d6feb8ab7fda61f44251303da936ad75162454fe3017e80302ed0fdf654ade552906819aee1370278e852aa2ee9ca9b91e934c6337d6607c94a98e6c06b7279cbfc30d9c783d30e9958ae601dd90d57dcc162eebc513164bd717abe23e7b8281cc13865e0d7453ccd36ff9dbfcb7aadf2451da926c8413"}, +} + +var plaintext = "g5Dr58dvyD" +var salt = []byte("d8sYrQbgT") + +func roundTrip(t *testing.T, plaintext string) { + encoded, err := mcf.Create(plaintext) + if err != nil { + t.Fatal(err) + } + + ok, err := mcf.Verify(plaintext, encoded) + if err != nil { + t.Fatal(err) + } + + if !ok { + t.Fatalf("Verify(%q, %q) failed", plaintext, encoded) + } +} + +func TestVerificationExisting(t *testing.T) { + for _, slot := range testData { + valid, err := mcf.Verify(slot.plaintext, slot.encoded) + if err != nil { + panic(err) + } + if !valid { + t.Fatalf("#1 Verify(%q, %q) failed", slot.plaintext, slot.encoded) + } + } +} + +func TestRoundtrip(t *testing.T) { + roundTrip(t, plaintext) +} + +func setConfig(k, s, N, r, p int) error { + conf := GetConfig() + conf.KeyLen = k + conf.SaltLen = s + conf.N = N + conf.R = r + conf.P = p + + return SetConfig(conf) +} + +func TestCustomParameters(t *testing.T) { + + defaultConf := GetConfig() + conf := defaultConf + + conf.KeyLen = 128 + conf.N = 1 << 14 + conf.R = 8 + conf.P = 1 + + defaultParams := (&defaultConf).Params() + newParams := (&conf).Params() + + if defaultParams == newParams { + t.Fatalf("Expected different params, not: %s", newParams) + } + + err := SetConfig(conf) + if err != nil { + t.Fatal(err) + } + + roundTrip(t, plaintext) +} + +func setSalt(salt string) { + SaltMine = func(n int) ([]byte, error) { return []byte(salt), nil } +} + +func TestKey(t *testing.T) { + for i, v := range good { + + err := setConfig(len(v.output), len(v.salt), v.N, v.r, v.p) + if err != nil { + t.Errorf("%d: unexpected error setting config: %s", i, err) + } + + setSalt(v.salt) + + encoded, err := mcf.Create(v.password) + if err != nil { + t.Errorf("%d: got unexpected error: %s", i, err) + } + + passwd := password.New([]byte("scrypt")) + err = passwd.Parse([]byte(encoded)) + if err != nil { + t.Errorf("%d: unexpected error creating password instance: %s", err) + } + + if !bytes.Equal(passwd.Salt, []byte(v.salt)) { + t.Errorf("%d: salt: expected %s, got %s", i, v.salt, string(passwd.Salt)) + } + + if !bytes.Equal(passwd.Key, v.output) { + t.Errorf("%d: expected %x, got %x", i, v.output, passwd.Key) + } + } + + for i, v := range bad { + err := setConfig(32, len(v.salt), v.N, v.r, v.p) + if err == nil { + t.Errorf("%d: expected error, got nil", i) + } + + setSalt(v.salt) + + _, err = mcf.Create(v.password) + if err == nil { + t.Errorf("%d: expected error, got nil", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/vectors_test.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/vectors_test.go new file mode 100644 index 0000000..04c05a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/scrypt/vectors_test.go @@ -0,0 +1,136 @@ +// This file is a modified copy of the scrypt_test.go from crypto/scrypt +// which had/has the following copyright: + +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scrypt + +import "math" + +type testVector struct { + password string + salt string + N, r, p int + output []byte +} + +var good = []testVector{ + { + "password", + "salt", + 2, 10, 10, + []byte{ + 0x48, 0x2c, 0x85, 0x8e, 0x22, 0x90, 0x55, 0xe6, 0x2f, + 0x41, 0xe0, 0xec, 0x81, 0x9a, 0x5e, 0xe1, 0x8b, 0xdb, + 0x87, 0x25, 0x1a, 0x53, 0x4f, 0x75, 0xac, 0xd9, 0x5a, + 0xc5, 0xe5, 0xa, 0xa1, 0x5f, + }, + }, + { + "password", + "salt", + 16, 100, 100, + []byte{ + 0x88, 0xbd, 0x5e, 0xdb, 0x52, 0xd1, 0xdd, 0x0, 0x18, + 0x87, 0x72, 0xad, 0x36, 0x17, 0x12, 0x90, 0x22, 0x4e, + 0x74, 0x82, 0x95, 0x25, 0xb1, 0x8d, 0x73, 0x23, 0xa5, + 0x7f, 0x91, 0x96, 0x3c, 0x37, + }, + }, + { + "this is a long \000 password", + "and this is a long \000 salt", + 16384, 8, 1, + []byte{ + 0xc3, 0xf1, 0x82, 0xee, 0x2d, 0xec, 0x84, 0x6e, 0x70, + 0xa6, 0x94, 0x2f, 0xb5, 0x29, 0x98, 0x5a, 0x3a, 0x09, + 0x76, 0x5e, 0xf0, 0x4c, 0x61, 0x29, 0x23, 0xb1, 0x7f, + 0x18, 0x55, 0x5a, 0x37, 0x07, 0x6d, 0xeb, 0x2b, 0x98, + 0x30, 0xd6, 0x9d, 0xe5, 0x49, 0x26, 0x51, 0xe4, 0x50, + 0x6a, 0xe5, 0x77, 0x6d, 0x96, 0xd4, 0x0f, 0x67, 0xaa, + 0xee, 0x37, 0xe1, 0x77, 0x7b, 0x8a, 0xd5, 0xc3, 0x11, + 0x14, 0x32, 0xbb, 0x3b, 0x6f, 0x7e, 0x12, 0x64, 0x40, + 0x18, 0x79, 0xe6, 0x41, 0xae, + }, + }, + { + "p", + "s", + 2, 1, 1, + []byte{ + 0x48, 0xb0, 0xd2, 0xa8, 0xa3, 0x27, 0x26, 0x11, 0x98, + 0x4c, 0x50, 0xeb, 0xd6, 0x30, 0xaf, 0x52, + }, + }, + + { + "", + "", + 16, 1, 1, + []byte{ + 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, + 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b, + 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, + 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, + 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f, + 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, + 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, + 0x06, + }, + }, + { + "password", + "NaCl", + 1024, 8, 16, + []byte{ + 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, + 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe, 0x7c, 0x6a, + 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, + 0x63, 0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, + 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 0x09, 0x27, 0x9d, + 0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, + 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, + 0x40, + }, + }, + { + "pleaseletmein", "SodiumChloride", + 16384, 8, 1, + []byte{ + 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, + 0x1c, 0x06, 0xcd, 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8, + 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, + 0xf6, 0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, + 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 0x97, 0x05, 0x24, + 0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, + 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58, + 0x87, + }, + }, + /* + // Disabled: needs 1 GiB RAM and takes too long for a simple test. + { + "pleaseletmein", "SodiumChloride", + 1048576, 8, 1, + []byte{ + 0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, + 0xdb, 0xbe, 0x09, 0xcf, 0x70, 0xf8, 0x81, 0xec, 0x56, + 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee, + 0x98, 0x20, 0xad, 0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, + 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c, 0x6d, 0x92, 0x7c, + 0x40, 0xf4, 0xc3, 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, + 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f, 0xa7, 0x7a, 0x41, + 0xa4, + }, + }, + */ +} + +var bad = []testVector{ + {"p", "s", 0, 1, 1, nil}, // N == 0 + {"p", "s", 1, 1, 1, nil}, // N == 1 + {"p", "s", 7, 8, 1, nil}, // N is not power of 2 + {"p", "s", 16, math.MaxInt32 / 2, math.MaxInt32 / 2, nil}, // p * r too large +} diff --git a/Godeps/_workspace/src/github.com/gyepisam/mcf/test/test_test.go b/Godeps/_workspace/src/github.com/gyepisam/mcf/test/test_test.go new file mode 100644 index 0000000..9bf8f7f --- /dev/null +++ b/Godeps/_workspace/src/github.com/gyepisam/mcf/test/test_test.go @@ -0,0 +1,90 @@ +// Copyright 2014 Gyepi Sam. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//package test exists to test the overall system interaction. +package test // cannot be mcf or encoder + +import ( + "strings" + "testing" + + //import all encoders + "github.com/gyepisam/mcf" + _ "github.com/gyepisam/mcf/bcrypt" + _ "github.com/gyepisam/mcf/pbkdf2" + _ "github.com/gyepisam/mcf/scrypt" +) + +var plain = "password" + +var encodings = []struct { + id string + encoding mcf.Encoding +}{ + // order should not match import list. + {"$pbkdf2$", mcf.PBKDF2}, + {"$scrypt$", mcf.SCRYPT}, + {"$2a$", mcf.BCRYPT}, +} + +func TestEncoderInteraction(t *testing.T) { + + var list []string + + // Test that SetDefault works + for i, r := range encodings { + err := mcf.SetDefault(r.encoding) + if err != nil { + t.Errorf("%d-1: SetDefault %s: unexpected error: %s", i, r.encoding, err) + continue + } + + encoded, err := mcf.Create(plain) + if err != nil { + t.Errorf("%d-1: Create: unexpected error: %s", i, err) + continue + } + + isValid, err := mcf.Verify(plain, encoded) + if err != nil { + t.Errorf("%d-1: Verify: unexpected error: %s", i, err) + continue + } + + if !isValid { + t.Errorf("%d-1: Verify: unexpected failure on plain=%q encoded=%q, encoding=%q", i, plain, encoded, r.encoding) + continue + } + + if s := encoded; !strings.HasPrefix(s, r.id) { + t.Errorf("%d-1: Create: encoding prefix mismatch: s=%s, prefix=%s", i, s, r.id) + continue + } + + list = append(list, encoded) + } + + // Verification is independent of default setting + // and should work as long as the encoder exists. + for i, r := range encodings { + err := mcf.SetDefault(r.encoding) + if err != nil { + t.Errorf("%d-2: SetDefault %s: unexpected error: %s", i, r.encoding, err) + continue + } + + for _, encoded := range list { + isValid, err := mcf.Verify(plain, encoded) + if err != nil { + t.Errorf("%d-2: Verify: unexpected error: %s", i, err) + continue + } + + if !isValid { + t.Errorf("%d-2: Verify: unexpected failure on plain=%q encoded=%q, encoding=%q", i, plain, encoded, r.encoding) + continue + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack.go b/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack.go new file mode 100644 index 0000000..aa41380 --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack.go @@ -0,0 +1,89 @@ +package slack + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +type Client struct { + Url string +} + +type Message struct { + Text string `json:"text"` + Username string `json:"username"` + IconUrl string `json:"icon_url"` + IconEmoji string `json:"icon_emoji"` + Channel string `json:"channel"` + UnfurlLinks bool `json:"unfurl_links"` + Attachments []*Attachment `json:"attachments"` +} + +type Attachment struct { + Fallback string `json:"fallback"` + Text string `json:"text"` + Pretext string `json:"pretext"` + Color string `json:"color"` + Fields []*Field `json:"fields"` +} + +type Field struct { + Title string `json:"title"` + Value string `json:"value"` + Short bool `json:"short"` +} + +type SlackError struct { + Code int + Body string +} + +func (e *SlackError) Error() string { + return fmt.Sprintf("SlackError: %d %s", e.Code, e.Body) +} + +func NewClient(url string) *Client { + return &Client{url} +} + +func (c *Client) SendMessage(msg *Message) error { + + body, _ := json.Marshal(msg) + buf := bytes.NewReader(body) + + http.NewRequest("POST", c.Url, buf) + resp, err := http.Post(c.Url, "application/json", buf) + if err != nil { + return err + } + + if resp.StatusCode != 200 { + t, _ := ioutil.ReadAll(resp.Body) + return &SlackError{resp.StatusCode, string(t)} + } + + return nil +} + +func (m *Message) NewAttachment() *Attachment { + a := &Attachment{} + m.AddAttachment(a) + return a +} + +func (m *Message) AddAttachment(a *Attachment) { + m.Attachments = append(m.Attachments, a) +} + +func (a *Attachment) NewField() *Field { + f := &Field{} + a.AddField(f) + return f +} + +func (a *Attachment) AddField(f *Field) { + a.Fields = append(a.Fields, f) +} diff --git a/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack_test.go b/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack_test.go new file mode 100644 index 0000000..462bc92 --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slack-go/slack_test.go @@ -0,0 +1,50 @@ +package slack + +import ( + "flag" + "os/user" + "testing" +) + +var ( + client *Client +) + +func init() { + client = &Client{} + flag.StringVar(&client.Url, "url", "", "webhook url") + flag.Parse() + if client.Url == "" { + flag.PrintDefaults() + panic("\n=================\nYou need to specify -url flag\n=================\n\n") + } +} + +func TestSendMessage(t *testing.T) { + msg := &Message{} + msg.Channel = "#slack-go-test" + msg.Text = "Slack API Test from go" + user, _ := user.Current() + msg.Username = user.Username + client.SendMessage(msg) +} + +func TestSendMessageWithAttachement(t *testing.T) { + msg := &Message{} + msg.Channel = "#slack-go-test" + msg.Text = "Slack API Test from go - with attachment" + user, _ := user.Current() + msg.Username = user.Username + + attach := msg.NewAttachment() + attach.Text = "This is an attachment!" + attach.Pretext = "This is the pretext of an attachment" + attach.Color = "good" + attach.Fallback = "That's the fallback field" + + field := attach.NewField() + field.Title = "Field one" + field.Value = "Field one value" + + client.SendMessage(msg) +} diff --git a/Godeps/_workspace/src/github.com/johntdyer/slackrus/README.md b/Godeps/_workspace/src/github.com/johntdyer/slackrus/README.md new file mode 100644 index 0000000..428d1cf --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slackrus/README.md @@ -0,0 +1,57 @@ +slackrus +======== + +Slack hook for [Logrus](https://github.com/Sirupsen/logrus). + +## Use + +```go +package main + +import ( + logrus "github.com/Sirupsen/logrus" + "github.com/johntdyer/slackrus" + "os" +) + +func main() { + + logrus.SetFormatter(&logrus.JSONFormatter{}) + + logrus.SetOutput(os.Stderr) + + logrus.SetLevel(logrus.DebugLevel) + + logrus.AddHook(&slackrus.SlackrusHook{ + HookUrl: "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz", + AcceptedLevels: slackrus.LevelThreshold(logrus.DebugLevel), + Channel: "#slack-testing", + IconEmoji: ":ghost:", + Username: "foobot", + }) + + logrus.Warn("warn") + logrus.Info("info") + logrus.Debug("debug") +} + +``` + +## Parameters + +#### Required + * HookUrl + +#### Optional + * IconEmoji + * IconUrl + * Username + * Channel + +## Installation + + go get github.com/johntdyer/slackrus + +## Credits + +Based on hipchat handler by [nuboLAB](https://github.com/nubo/hiprus) diff --git a/Godeps/_workspace/src/github.com/johntdyer/slackrus/example/example.go b/Godeps/_workspace/src/github.com/johntdyer/slackrus/example/example.go new file mode 100644 index 0000000..3db580b --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slackrus/example/example.go @@ -0,0 +1,28 @@ +package main + +import ( + logrus "github.com/Sirupsen/logrus" + "github.com/johntdyer/slackrus" + "os" +) + +func main() { + + logrus.SetFormatter(&logrus.JSONFormatter{}) + + logrus.SetOutput(os.Stderr) + + logrus.SetLevel(logrus.DebugLevel) + + logrus.AddHook(&slackrus.SlackrusHook{ + HookURL: "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz", + AcceptedLevels: slackrus.LevelThreshold(logrus.DebugLevel), + Channel: "#slack-testing", + IconEmoji: ":ghost:", + Username: "foobot", + }) + + logrus.WithFields(logrus.Fields{"foo": "bar", "foo2": "bar2"}).Warn("this is a warn level message") + logrus.Info("this is an info level message") + logrus.Debug("this is a debug level message") +} diff --git a/Godeps/_workspace/src/github.com/johntdyer/slackrus/levels.go b/Godeps/_workspace/src/github.com/johntdyer/slackrus/levels.go new file mode 100644 index 0000000..7095e27 --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slackrus/levels.go @@ -0,0 +1,25 @@ +package slackrus + +import ( + "github.com/Sirupsen/logrus" +) + +// Supported log levels +var AllLevels = []logrus.Level{ + logrus.DebugLevel, + logrus.InfoLevel, + logrus.WarnLevel, + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, +} + +// LevelThreshold - Returns every logging level above and including the given parameter. +func LevelThreshold(l logrus.Level) []logrus.Level { + for i := range AllLevels { + if AllLevels[i] == l { + return AllLevels[i:] + } + } + return []logrus.Level{} +} diff --git a/Godeps/_workspace/src/github.com/johntdyer/slackrus/slackrus.go b/Godeps/_workspace/src/github.com/johntdyer/slackrus/slackrus.go new file mode 100644 index 0000000..6ab9691 --- /dev/null +++ b/Godeps/_workspace/src/github.com/johntdyer/slackrus/slackrus.go @@ -0,0 +1,109 @@ +// Package slackrus provides a Hipchat hook for the logrus loggin package. +package slackrus + +import ( + "github.com/Sirupsen/logrus" + "github.com/johntdyer/slack-go" +) + +// Project version +const ( + VERISON = "0.0.2" +) + +var ( + client *slack.Client +) + +// SlackrusHook is a logrus Hook for dispatching messages to the specified +// channel on Slack. +type SlackrusHook struct { + // Messages with a log level not contained in this array + // will not be dispatched. If nil, all messages will be dispatched. + AcceptedLevels []logrus.Level + HookURL string + IconURL string + Channel string + IconEmoji string + Username string + c *slack.Client +} + +// Levels sets which levels to sent to slack +func (sh *SlackrusHook) Levels() []logrus.Level { + if sh.AcceptedLevels == nil { + return AllLevels + } + return sh.AcceptedLevels +} + +// Fire - Sent event to slack +func (sh *SlackrusHook) Fire(e *logrus.Entry) error { + if sh.c == nil { + if err := sh.initClient(); err != nil { + return err + } + } + + color := "" + switch e.Level { + case logrus.DebugLevel: + color = "#9B30FF" + case logrus.InfoLevel: + color = "good" + case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: + color = "danger" + default: + color = "warning" + } + + msg := &slack.Message{ + Username: sh.Username, + Channel: sh.Channel, + } + + msg.IconEmoji = sh.IconEmoji + msg.IconUrl = sh.IconURL + + attach := msg.NewAttachment() + + // If there are fields we need to render them at attachments + if len(e.Data) > 0 { + + // Add a header above field data + attach.Text = "Message fields" + + for k, v := range e.Data { + slackField := &slack.Field{} + + if str, ok := v.(string); ok { + slackField.Title = k + slackField.Value = str + // If the field is <= 20 then we'll set it to short + if len(str) <= 20 { + slackField.Short = true + } + } + attach.AddField(slackField) + + } + attach.Pretext = e.Message + } else { + attach.Text = e.Message + } + attach.Fallback = e.Message + attach.Color = color + + return sh.c.SendMessage(msg) + +} + +func (sh *SlackrusHook) initClient() error { + sh.c = &slack.Client{sh.HookURL} + + if sh.Username == "" { + sh.Username = "SlackRus" + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/LICENSE b/Godeps/_workspace/src/github.com/lavab/flag/LICENSE new file mode 100644 index 0000000..fb0ca8c --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/lavab/flag/README.md b/Godeps/_workspace/src/github.com/lavab/flag/README.md new file mode 100644 index 0000000..ba63cf5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/README.md @@ -0,0 +1,169 @@ +Flag +=== + +Flag is a drop in replacement for Go's flag package with the addition to parse files and environment variables. If you support the [twelve-factor app methodology][], Flag complies with the third factor; "Store config in the environment". + +[twelve-factor app methodology]: http://12factor.net + +An example using a gopher: + +```go +$ cat > gopher.go + package main + + import ( + "fmt" + "github/namsral/flag" + ) + + var age int + + flag.IntVar(&age, "age", 0, "age of gopher") + flag.Parse() + + fmt.Print("age:", age) + +$ go run gopher.go -age 1 +age: 1 +``` + +Same code but using an environment variable: + +```go +$ export AGE=2 +$ go run gopher.go +age: 2 +``` + + +Same code but using a configuration file: + +```go +$ cat > gopher.conf +age 3 + +$ go run gopher.go -config gopher.conf +age: 3 +``` + +The following table shows how flags are translated to environment variables and configuration files: + +| Type | Flag | Environment | File | +| ------ | :------------ |:------------ |:------------ | +| int | -age 2 | AGE=2 | age 2 | +| bool | -female | FEMALE=true | female true | +| float | -length 175.5 | LENGTH=175.5 | length 175.5 | +| string | -name Gloria | NAME=Gloria | name Gloria | + +This package is a port of Go's [flag][] package from the standard library with the addition of two functions `ParseEnv` and `ParseFile`. + +[flag]: http://golang.org/src/pkg/flagconfiguration + + +Why? +--- + +Why not use one of the many INI, JSON or YAML parsers? + +I find it best practice to have simple configuration options to control the behaviour of an applications when it starts up. Use basic types like ints, floats and strings for configuration options and store more complex data structures in the "datastore" layer. + + +Usage +--- + +It's intended for projects which require a simple configuration made available through command-line flags, configuration files and shell environments. It's similar to the original `flag` package. + +Example: + +```go +import "github/namsral/flag" + +flag.String("config", "", "help message for config") +flag.Int("age", 24, "help message for age") + +flag.Parse() +``` + +Order of precedence: + +1. Command line options +2. Environment variables +3. Configuration file +4. Default values + + +#### Parsing Configuration Files + +Create a configuration file: + +```go +$ cat > /etc/command.conf +# empty newlines and lines beginning with a "#" character are ignored. +name bob + +# keys and values can also be separated by the "=" character +age=20 + +# booleans can me empty, set with 0, 1, true, false, etc +hacker +``` + +Add a "config" flag: + +```go +flag.String("config", "", "help message for config") +``` + +Run the command: + +```go +$ go run ./command.go -config /etc/command.conf +``` + +#### Parsing Environment Variables + +Prefix the environment variable with the name of your command in uppercase: + +```go +$ export AGE=44 +$ go run ./command.go +``` + +For more examples see the [examples][] directory in the project repository. + +[examples]: https://github.com/namsral/flag/tree/master/examples + +That's it. + + +License +--- + + +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/lavab/flag/example_test.go b/Godeps/_workspace/src/github.com/lavab/flag/example_test.go new file mode 100644 index 0000000..6cd66e0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/example_test.go @@ -0,0 +1,83 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// These examples demonstrate more intricate uses of the flag package. +package flag_test + +import ( + "errors" + "github.com/namsral/flag" + "fmt" + "strings" + "time" +) + +// Example 1: A single string flag called "species" with default value "gopher". +var species = flag.String("species", "gopher", "the species we are studying") + +// Example 2: Two flags sharing a variable, so we can have a shorthand. +// The order of initialization is undefined, so make sure both use the +// same default value. They must be set up with an init function. +var gopherType string + +func init() { + const ( + defaultGopher = "pocket" + usage = "the variety of gopher" + ) + flag.StringVar(&gopherType, "gopher_type", defaultGopher, usage) + flag.StringVar(&gopherType, "g", defaultGopher, usage+" (shorthand)") +} + +// Example 3: A user-defined flag type, a slice of durations. +type interval []time.Duration + +// String is the method to format the flag's value, part of the flag.Value interface. +// The String method's output will be used in diagnostics. +func (i *interval) String() string { + return fmt.Sprint(*i) +} + +// Set is the method to set the flag value, part of the flag.Value interface. +// Set's argument is a string to be parsed to set the flag. +// It's a comma-separated list, so we split it. +func (i *interval) Set(value string) error { + // If we wanted to allow the flag to be set multiple times, + // accumulating values, we would delete this if statement. + // That would permit usages such as + // -deltaT 10s -deltaT 15s + // and other combinations. + if len(*i) > 0 { + return errors.New("interval flag already set") + } + for _, dt := range strings.Split(value, ",") { + duration, err := time.ParseDuration(dt) + if err != nil { + return err + } + *i = append(*i, duration) + } + return nil +} + +// Define a flag to accumulate durations. Because it has a special type, +// we need to use the Var function and therefore create the flag during +// init. + +var intervalFlag interval + +func init() { + // Tie the command-line flag to the intervalFlag variable and + // set a usage message. + flag.Var(&intervalFlag, "deltaT", "comma-separated list of intervals to use between events") +} + +func Example() { + // All the interesting pieces are with the variables declared above, but + // to enable the flag package to see the flags defined there, one must + // execute, typically at the start of main (not init!): + // flag.Parse() + // We don't run it here because this is not a main function and + // the testing suite has already parsed the flags. +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.conf b/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.conf new file mode 100644 index 0000000..69b9033 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.conf @@ -0,0 +1,5 @@ +# this is a comment followed by an empty line + +length 175.5 +age 2 +name Gloria \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.go b/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.go new file mode 100644 index 0000000..1030005 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/examples/gopher.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/namsral/flag" + "fmt" + ) + +func main() { + var ( + config string + length float64 + age int + name string + female bool + ) + + flag.StringVar(&config, "config", "", "help message") + flag.StringVar(&name, "name", "", "help message") + flag.IntVar(&age, "age", 0, "help message") + flag.Float64Var(&length, "length", 0, "help message") + flag.BoolVar(&female, "female", false, "help message") + + flag.Parse() + + fmt.Println("length:", length) + fmt.Println("age:", age) + fmt.Println("name:", name) + fmt.Println("female:", female) +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/export_test.go b/Godeps/_workspace/src/github.com/lavab/flag/export_test.go new file mode 100644 index 0000000..16e2b8c --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/export_test.go @@ -0,0 +1,17 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package flag + +import "os" + +// Additional routines compiled into the package only during testing. + +// ResetForTesting clears all flag state and sets the usage function as directed. +// After calling ResetForTesting, parse errors in flag handling will not +// exit the program. +func ResetForTesting(usage func()) { + CommandLine = NewFlagSet(os.Args[0], ContinueOnError) + Usage = usage +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/flag.go b/Godeps/_workspace/src/github.com/lavab/flag/flag.go new file mode 100644 index 0000000..4f73199 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/flag.go @@ -0,0 +1,1154 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + Package flag implements command-line flag parsing. + + Usage: + + Define flags using flag.String(), Bool(), Int(), etc. + + This declares an integer flag, -flagname, stored in the pointer ip, with type *int. + import "flag" + var ip = flag.Int("flagname", 1234, "help message for flagname") + If you like, you can bind the flag to a variable using the Var() functions. + var flagvar int + func init() { + flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname") + } + Or you can create custom flags that satisfy the Value interface (with + pointer receivers) and couple them to flag parsing by + flag.Var(&flagVal, "name", "help message for flagname") + For such flags, the default value is just the initial value of the variable. + + After all flags are defined, call + flag.Parse() + to parse the command line into the defined flags. + + Flags may then be used directly. If you're using the flags themselves, + they are all pointers; if you bind to variables, they're values. + fmt.Println("ip has value ", *ip) + fmt.Println("flagvar has value ", flagvar) + + After parsing, the arguments after the flag are available as the + slice flag.Args() or individually as flag.Arg(i). + The arguments are indexed from 0 through flag.NArg()-1. + + Command line flag syntax: + -flag + -flag=x + -flag x // non-boolean flags only + One or two minus signs may be used; they are equivalent. + The last form is not permitted for boolean flags because the + meaning of the command + cmd -x * + will change if there is a file called 0, false, etc. You must + use the -flag=false form to turn off a boolean flag. + + Flag parsing stops just before the first non-flag argument + ("-" is a non-flag argument) or after the terminator "--". + + Integer flags accept 1234, 0664, 0x1234 and may be negative. + Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. + Duration flags accept any input valid for time.ParseDuration. + + The default set of command-line flags is controlled by + top-level functions. The FlagSet type allows one to define + independent sets of flags, such as to implement subcommands + in a command-line interface. The methods of FlagSet are + analogous to the top-level functions for the command-line + flag set. +*/ +package flag + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" + "time" + + "github.com/coreos/go-etcd/etcd" +) + +// ErrHelp is the error returned if the flag -help is invoked but no such flag is defined. +var ErrHelp = errors.New("flag: help requested") + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = boolValue(v) + return err +} + +func (b *boolValue) Get() interface{} { return bool(*b) } + +func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +// optional interface to indicate boolean flags that can be +// supplied without "=value" text +type boolFlag interface { + Value + IsBoolFlag() bool +} + +// -- int Value +type intValue int + +func newIntValue(val int, p *int) *intValue { + *p = val + return (*intValue)(p) +} + +func (i *intValue) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = intValue(v) + return err +} + +func (i *intValue) Get() interface{} { return int(*i) } + +func (i *intValue) String() string { return fmt.Sprintf("%v", *i) } + +// -- int64 Value +type int64Value int64 + +func newInt64Value(val int64, p *int64) *int64Value { + *p = val + return (*int64Value)(p) +} + +func (i *int64Value) Set(s string) error { + v, err := strconv.ParseInt(s, 0, 64) + *i = int64Value(v) + return err +} + +func (i *int64Value) Get() interface{} { return int64(*i) } + +func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) } + +// -- uint Value +type uintValue uint + +func newUintValue(val uint, p *uint) *uintValue { + *p = val + return (*uintValue)(p) +} + +func (i *uintValue) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uintValue(v) + return err +} + +func (i *uintValue) Get() interface{} { return uint(*i) } + +func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) } + +// -- uint64 Value +type uint64Value uint64 + +func newUint64Value(val uint64, p *uint64) *uint64Value { + *p = val + return (*uint64Value)(p) +} + +func (i *uint64Value) Set(s string) error { + v, err := strconv.ParseUint(s, 0, 64) + *i = uint64Value(v) + return err +} + +func (i *uint64Value) Get() interface{} { return uint64(*i) } + +func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) } + +// -- string Value +type stringValue string + +func newStringValue(val string, p *string) *stringValue { + *p = val + return (*stringValue)(p) +} + +func (s *stringValue) Set(val string) error { + *s = stringValue(val) + return nil +} + +func (s *stringValue) Get() interface{} { return string(*s) } + +func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) } + +// -- float64 Value +type float64Value float64 + +func newFloat64Value(val float64, p *float64) *float64Value { + *p = val + return (*float64Value)(p) +} + +func (f *float64Value) Set(s string) error { + v, err := strconv.ParseFloat(s, 64) + *f = float64Value(v) + return err +} + +func (f *float64Value) Get() interface{} { return float64(*f) } + +func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) } + +// -- time.Duration Value +type durationValue time.Duration + +func newDurationValue(val time.Duration, p *time.Duration) *durationValue { + *p = val + return (*durationValue)(p) +} + +func (d *durationValue) Set(s string) error { + v, err := time.ParseDuration(s) + *d = durationValue(v) + return err +} + +func (d *durationValue) Get() interface{} { return time.Duration(*d) } + +func (d *durationValue) String() string { return (*time.Duration)(d).String() } + +// Value is the interface to the dynamic value stored in a flag. +// (The default value is represented as a string.) +// +// If a Value has an IsBoolFlag() bool method returning true, +// the command-line parser makes -name equivalent to -name=true +// rather than using the next command-line argument. +type Value interface { + String() string + Set(string) error +} + +// Getter is an interface that allows the contents of a Value to be retrieved. +// It wraps the Value interface, rather than being part of it, because it +// appeared after Go 1 and its compatibility rules. All Value types provided +// by this package satisfy the Getter interface. +type Getter interface { + Value + Get() interface{} +} + +// ErrorHandling defines how to handle flag parsing errors. +type ErrorHandling int + +const ( + ContinueOnError ErrorHandling = iota + ExitOnError + PanicOnError +) + +// A FlagSet represents a set of defined flags. The zero value of a FlagSet +// has no name and has ContinueOnError error handling. +type FlagSet struct { + // Usage is the function called when an error occurs while parsing flags. + // The field is a function (not a method) that may be changed to point to + // a custom error handler. + Usage func() + + name string + parsed bool + actual map[string]*Flag + formal map[string]*Flag + args []string // arguments after flags + exitOnError bool // does the program exit if there's an error? + errorHandling ErrorHandling + output io.Writer // nil means stderr; use out() accessor +} + +// A Flag represents the state of a flag. +type Flag struct { + Name string // name as it appears on command line + Usage string // help message + Value Value // value as set + DefValue string // default value (as text); for usage message +} + +// sortFlags returns the flags as a slice in lexicographical sorted order. +func sortFlags(flags map[string]*Flag) []*Flag { + list := make(sort.StringSlice, len(flags)) + i := 0 + for _, f := range flags { + list[i] = f.Name + i++ + } + list.Sort() + result := make([]*Flag, len(list)) + for i, name := range list { + result[i] = flags[name] + } + return result +} + +func (f *FlagSet) out() io.Writer { + if f.output == nil { + return os.Stderr + } + return f.output +} + +// SetOutput sets the destination for usage and error messages. +// If output is nil, os.Stderr is used. +func (f *FlagSet) SetOutput(output io.Writer) { + f.output = output +} + +// VisitAll visits the flags in lexicographical order, calling fn for each. +// It visits all flags, even those not set. +func (f *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(f.formal) { + fn(flag) + } +} + +// VisitAll visits the command-line flags in lexicographical order, calling +// fn for each. It visits all flags, even those not set. +func VisitAll(fn func(*Flag)) { + CommandLine.VisitAll(fn) +} + +// Visit visits the flags in lexicographical order, calling fn for each. +// It visits only those flags that have been set. +func (f *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(f.actual) { + fn(flag) + } +} + +// Visit visits the command-line flags in lexicographical order, calling fn +// for each. It visits only those flags that have been set. +func Visit(fn func(*Flag)) { + CommandLine.Visit(fn) +} + +// Lookup returns the Flag structure of the named flag, returning nil if none exists. +func (f *FlagSet) Lookup(name string) *Flag { + return f.formal[name] +} + +// Lookup returns the Flag structure of the named command-line flag, +// returning nil if none exists. +func Lookup(name string) *Flag { + return CommandLine.formal[name] +} + +// Set sets the value of the named flag. +func (f *FlagSet) Set(name, value string) error { + flag, ok := f.formal[name] + if !ok { + return fmt.Errorf("no such flag -%v", name) + } + err := flag.Value.Set(value) + if err != nil { + return err + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return nil +} + +// Set sets the value of the named command-line flag. +func Set(name, value string) error { + return CommandLine.Set(name, value) +} + +// PrintDefaults prints, to standard error unless configured +// otherwise, the default values of all defined flags in the set. +func (f *FlagSet) PrintDefaults() { + f.VisitAll(func(flag *Flag) { + format := " -%s=%s: %s\n" + if _, ok := flag.Value.(*stringValue); ok { + // put quotes on the value + format = " -%s=%q: %s\n" + } + fmt.Fprintf(f.out(), format, flag.Name, flag.DefValue, flag.Usage) + }) +} + +// PrintDefaults prints to standard error the default values of all defined command-line flags. +func PrintDefaults() { + CommandLine.PrintDefaults() +} + +// defaultUsage is the default function to print a usage message. +func defaultUsage(f *FlagSet) { + if f.name == "" { + fmt.Fprintf(f.out(), "Usage:\n") + } else { + fmt.Fprintf(f.out(), "Usage of %s:\n", f.name) + } + f.PrintDefaults() +} + +// NOTE: Usage is not just defaultUsage(CommandLine) +// because it serves (via godoc flag Usage) as the example +// for how to write your own usage function. + +// Usage prints to standard error a usage message documenting all defined command-line flags. +// The function is a variable that may be changed to point to a custom function. +var Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + PrintDefaults() +} + +// NFlag returns the number of flags that have been set. +func (f *FlagSet) NFlag() int { return len(f.actual) } + +// NFlag returns the number of command-line flags that have been set. +func NFlag() int { return len(CommandLine.actual) } + +// Arg returns the i'th argument. Arg(0) is the first remaining argument +// after flags have been processed. +func (f *FlagSet) Arg(i int) string { + if i < 0 || i >= len(f.args) { + return "" + } + return f.args[i] +} + +// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument +// after flags have been processed. +func Arg(i int) string { + return CommandLine.Arg(i) +} + +// NArg is the number of arguments remaining after flags have been processed. +func (f *FlagSet) NArg() int { return len(f.args) } + +// NArg is the number of arguments remaining after flags have been processed. +func NArg() int { return len(CommandLine.args) } + +// Args returns the non-flag arguments. +func (f *FlagSet) Args() []string { return f.args } + +// Args returns the non-flag command-line arguments. +func Args() []string { return CommandLine.args } + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) { + f.Var(newBoolValue(value, p), name, usage) +} + +// BoolVar defines a bool flag with specified name, default value, and usage string. +// The argument p points to a bool variable in which to store the value of the flag. +func BoolVar(p *bool, name string, value bool, usage string) { + CommandLine.Var(newBoolValue(value, p), name, usage) +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func (f *FlagSet) Bool(name string, value bool, usage string) *bool { + p := new(bool) + f.BoolVar(p, name, value, usage) + return p +} + +// Bool defines a bool flag with specified name, default value, and usage string. +// The return value is the address of a bool variable that stores the value of the flag. +func Bool(name string, value bool, usage string) *bool { + return CommandLine.Bool(name, value, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func (f *FlagSet) IntVar(p *int, name string, value int, usage string) { + f.Var(newIntValue(value, p), name, usage) +} + +// IntVar defines an int flag with specified name, default value, and usage string. +// The argument p points to an int variable in which to store the value of the flag. +func IntVar(p *int, name string, value int, usage string) { + CommandLine.Var(newIntValue(value, p), name, usage) +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func (f *FlagSet) Int(name string, value int, usage string) *int { + p := new(int) + f.IntVar(p, name, value, usage) + return p +} + +// Int defines an int flag with specified name, default value, and usage string. +// The return value is the address of an int variable that stores the value of the flag. +func Int(name string, value int, usage string) *int { + return CommandLine.Int(name, value, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) { + f.Var(newInt64Value(value, p), name, usage) +} + +// Int64Var defines an int64 flag with specified name, default value, and usage string. +// The argument p points to an int64 variable in which to store the value of the flag. +func Int64Var(p *int64, name string, value int64, usage string) { + CommandLine.Var(newInt64Value(value, p), name, usage) +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func (f *FlagSet) Int64(name string, value int64, usage string) *int64 { + p := new(int64) + f.Int64Var(p, name, value, usage) + return p +} + +// Int64 defines an int64 flag with specified name, default value, and usage string. +// The return value is the address of an int64 variable that stores the value of the flag. +func Int64(name string, value int64, usage string) *int64 { + return CommandLine.Int64(name, value, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) { + f.Var(newUintValue(value, p), name, usage) +} + +// UintVar defines a uint flag with specified name, default value, and usage string. +// The argument p points to a uint variable in which to store the value of the flag. +func UintVar(p *uint, name string, value uint, usage string) { + CommandLine.Var(newUintValue(value, p), name, usage) +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func (f *FlagSet) Uint(name string, value uint, usage string) *uint { + p := new(uint) + f.UintVar(p, name, value, usage) + return p +} + +// Uint defines a uint flag with specified name, default value, and usage string. +// The return value is the address of a uint variable that stores the value of the flag. +func Uint(name string, value uint, usage string) *uint { + return CommandLine.Uint(name, value, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) { + f.Var(newUint64Value(value, p), name, usage) +} + +// Uint64Var defines a uint64 flag with specified name, default value, and usage string. +// The argument p points to a uint64 variable in which to store the value of the flag. +func Uint64Var(p *uint64, name string, value uint64, usage string) { + CommandLine.Var(newUint64Value(value, p), name, usage) +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 { + p := new(uint64) + f.Uint64Var(p, name, value, usage) + return p +} + +// Uint64 defines a uint64 flag with specified name, default value, and usage string. +// The return value is the address of a uint64 variable that stores the value of the flag. +func Uint64(name string, value uint64, usage string) *uint64 { + return CommandLine.Uint64(name, value, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func (f *FlagSet) StringVar(p *string, name string, value string, usage string) { + f.Var(newStringValue(value, p), name, usage) +} + +// StringVar defines a string flag with specified name, default value, and usage string. +// The argument p points to a string variable in which to store the value of the flag. +func StringVar(p *string, name string, value string, usage string) { + CommandLine.Var(newStringValue(value, p), name, usage) +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func (f *FlagSet) String(name string, value string, usage string) *string { + p := new(string) + f.StringVar(p, name, value, usage) + return p +} + +// String defines a string flag with specified name, default value, and usage string. +// The return value is the address of a string variable that stores the value of the flag. +func String(name string, value string, usage string) *string { + return CommandLine.String(name, value, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) { + f.Var(newFloat64Value(value, p), name, usage) +} + +// Float64Var defines a float64 flag with specified name, default value, and usage string. +// The argument p points to a float64 variable in which to store the value of the flag. +func Float64Var(p *float64, name string, value float64, usage string) { + CommandLine.Var(newFloat64Value(value, p), name, usage) +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func (f *FlagSet) Float64(name string, value float64, usage string) *float64 { + p := new(float64) + f.Float64Var(p, name, value, usage) + return p +} + +// Float64 defines a float64 flag with specified name, default value, and usage string. +// The return value is the address of a float64 variable that stores the value of the flag. +func Float64(name string, value float64, usage string) *float64 { + return CommandLine.Float64(name, value, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + f.Var(newDurationValue(value, p), name, usage) +} + +// DurationVar defines a time.Duration flag with specified name, default value, and usage string. +// The argument p points to a time.Duration variable in which to store the value of the flag. +func DurationVar(p *time.Duration, name string, value time.Duration, usage string) { + CommandLine.Var(newDurationValue(value, p), name, usage) +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration { + p := new(time.Duration) + f.DurationVar(p, name, value, usage) + return p +} + +// Duration defines a time.Duration flag with specified name, default value, and usage string. +// The return value is the address of a time.Duration variable that stores the value of the flag. +func Duration(name string, value time.Duration, usage string) *time.Duration { + return CommandLine.Duration(name, value, usage) +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func (f *FlagSet) Var(value Value, name string, usage string) { + // Remember the default value as a string; it won't change. + flag := &Flag{name, usage, value, value.String()} + _, alreadythere := f.formal[name] + if alreadythere { + var msg string + if f.name == "" { + msg = fmt.Sprintf("flag redefined: %s", name) + } else { + msg = fmt.Sprintf("%s flag redefined: %s", f.name, name) + } + fmt.Fprintln(f.out(), msg) + panic(msg) // Happens only if flags are declared with identical names + } + if f.formal == nil { + f.formal = make(map[string]*Flag) + } + f.formal[name] = flag +} + +// Var defines a flag with the specified name and usage string. The type and +// value of the flag are represented by the first argument, of type Value, which +// typically holds a user-defined implementation of Value. For instance, the +// caller could create a flag that turns a comma-separated string into a slice +// of strings by giving the slice the methods of Value; in particular, Set would +// decompose the comma-separated string into the slice. +func Var(value Value, name string, usage string) { + CommandLine.Var(value, name, usage) +} + +// failf prints to standard error a formatted error and usage message and +// returns the error. +func (f *FlagSet) failf(format string, a ...interface{}) error { + err := fmt.Errorf(format, a...) + fmt.Fprintln(f.out(), err) + f.usage() + return err +} + +// usage calls the Usage method for the flag set, or the usage function if +// the flag set is CommandLine. +func (f *FlagSet) usage() { + if f == CommandLine { + Usage() + } else if f.Usage == nil { + defaultUsage(f) + } else { + f.Usage() + } +} + +// parseOne parses one flag. It reports whether a flag was seen. +func (f *FlagSet) parseOne() (bool, error) { + if len(f.args) == 0 { + return false, nil + } + s := f.args[0] + if len(s) == 0 || s[0] != '-' || len(s) == 1 { + return false, nil + } + num_minuses := 1 + if s[1] == '-' { + num_minuses++ + if len(s) == 2 { // "--" terminates the flags + f.args = f.args[1:] + return false, nil + } + } + name := s[num_minuses:] + if len(name) == 0 || name[0] == '-' || name[0] == '=' { + return false, f.failf("bad flag syntax: %s", s) + } + + // it's a flag. does it have an argument? + f.args = f.args[1:] + has_value := false + value := "" + for i := 1; i < len(name); i++ { // equals cannot be first + if name[i] == '=' { + value = name[i+1:] + has_value = true + name = name[0:i] + break + } + } + m := f.formal + flag, alreadythere := m[name] // BUG + if !alreadythere { + if name == "help" || name == "h" { // special case for nice help message. + f.usage() + return false, ErrHelp + } + return false, f.failf("flag provided but not defined: -%s", name) + } + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if has_value { + if err := fv.Set(value); err != nil { + return false, f.failf("invalid boolean value %q for -%s: %v", value, name, err) + } + } else { + fv.Set("true") + } + } else { + // It must have a value, which might be the next argument. + if !has_value && len(f.args) > 0 { + // value is the next arg + has_value = true + value, f.args = f.args[0], f.args[1:] + } + if !has_value { + return false, f.failf("flag needs an argument: -%s", name) + } + if err := flag.Value.Set(value); err != nil { + return false, f.failf("invalid value %q for flag -%s: %v", value, name, err) + } + } + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + return true, nil +} + +// Parse parses flag definitions from the argument list, which should not +// include the command name. Must be called after all flags in the FlagSet +// are defined and before flags are accessed by the program. +// The return value will be ErrHelp if -help was set but not defined. +func (f *FlagSet) Parse(arguments []string) error { + f.parsed = true + f.args = arguments + for { + seen, err := f.parseOne() + if seen { + continue + } + if err == nil { + break + } + switch f.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + os.Exit(2) + case PanicOnError: + panic(err) + } + } + + // Parse environment variables + f.ParseEnv(os.Environ()) + + // Parse configuration from file + config_flag := f.actual["config"] + if config_flag != nil { + f.ParseFile(config_flag.Value.String()) + } + + // Parse configuration from etcd + etcd_address := f.actual["etcd_address"] + if etcd_address != nil { + f.ParseEtcd( + etcd_address, + f.actual["etcd_ca_file"], + f.actual["etcd_cert_file"], + f.actual["etcd_key_file"], + f.actual["etcd_path"], + ) + } + + return nil +} + +func (f *FlagSet) ParseEtcd(addressFlag *Flag, caFileFlag *Flag, certFileFlag *Flag, keyFileFlag *Flag, pathFlag *Flag) error { + // Prepare some local variables + var ( + addresses []string + addressString string + caFile string + certFile string + keyFile string + path string + ) + + // Parse the flags + if addressFlag != nil { + addressString = addressFlag.Value.String() + addresses = strings.Split(addressString, ",") + } + + if caFileFlag != nil { + caFile = caFileFlag.Value.String() + } + + if certFileFlag != nil { + certFile = certFileFlag.Value.String() + } + + if keyFileFlag != nil { + keyFile = keyFileFlag.Value.String() + } + + if pathFlag != nil { + path = pathFlag.Value.String() + } + + // Connect to the etcd server + var client *etcd.Client + + if caFile != "" && certFile != "" && keyFile != "" { + var err error + client, err = etcd.NewTLSClient(addresses, certFile, keyFile, caFile) + if err != nil { + return f.failf("Unable to create a new TLS etcd client: %s", err.Error()) + } + } else { + client = etcd.NewClient(addresses) + } + + // Perform the checks + m := f.formal + + for _, flag := range m { + name := flag.Name + + _, set := f.actual[name] + if set { + continue + } + + flag, alreadythere := m[name] + if !alreadythere { + if name == "help" || name == "h" { // special case for nice help message. + f.usage() + return ErrHelp + } + return f.failf("etcd setting provided but not defined: %s", name) + } + + key := strings.ToLower(flag.Name) + key = strings.Replace(key, "-", "_", -1) + + resp, err := client.Get(path+key, false, false) + if err != nil { + etcdErr, ok := err.(*etcd.EtcdError) + if ok { + if etcdErr.ErrorCode != 100 { + return f.failf("etcd error: %s", err.Error()) + } + } else { + return f.failf("not-related etcd error: %s", err.Error()) + } + continue + } + + value := resp.Node.Value + + has_value := false + if len(value) > 0 { + has_value = true + } + + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if has_value { + if err := fv.Set(value); err != nil { + return f.failf("invalid boolean value %q for etcd value %s: %v", value, name, err) + } + } else { + // flag without value is regarded a bool + fv.Set("true") + } + } else { + if !has_value { + return f.failf("environment variable needs an value: %s", name) + } + + if err := flag.Value.Set(value); err != nil { + return f.failf("invalid value %q for etcd value %s: %v", value, name, err) + } + } + + // update f.actual + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + } + + return nil +} + +// Parse flags from environment variables. Flags already set will be ignored. +func (f *FlagSet) ParseEnv(environ []string) error { + + m := f.formal + + env := make(map[string]string) + for _, s := range environ { + i := strings.Index(s, "=") + if i < 1 { + continue + } + env[s[0:i]] = s[i+1 : len(s)] + } + + for _, flag := range m { + name := flag.Name + _, set := f.actual[name] + if set { + continue + } + + flag, alreadythere := m[name] + if !alreadythere { + if name == "help" || name == "h" { // special case for nice help message. + f.usage() + return ErrHelp + } + return f.failf("environment variable provided but not defined: %s", name) + } + + env_key := strings.ToUpper(flag.Name) + env_key = strings.Replace(env_key, "-", "_", -1) + + value, is_set := env[env_key] + if !is_set { + continue + } + + has_value := false + if len(value) > 0 { + has_value = true + } + + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if has_value { + if err := fv.Set(value); err != nil { + return f.failf("invalid boolean value %q for environment variable %s: %v", value, name, err) + } + } else { + // flag without value is regarded a bool + fv.Set("true") + } + } else { + if !has_value { + return f.failf("environment variable needs an value: %s", name) + } + + if err := flag.Value.Set(value); err != nil { + return f.failf("invalid value %q for environment variable %s: %v", value, name, err) + } + } + + // update f.actual + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + + } + return nil +} + +// Parse flags from configuration file. Same format as commandline argumens, +// newlines and lines beginning with a "#" charater are ignored. Flags already +// set will be ignored. +func (f *FlagSet) ParseFile(path string) error { + + // Extract arguments from file + fp, err := os.Open(path) + if err != nil { + return err + } + defer fp.Close() + + scanner := bufio.NewScanner(fp) + for scanner.Scan() { + line := scanner.Text() + + // Ignore empty lines + if len(line) == 0 { + continue + } + + // Ignore comments + if line[:1] == "#" { + continue + } + + // Match `key=value` and `key value` + var name, value string + has_value := false + for i, v := range line { + if v == '=' || v == ' ' { + has_value = true + name, value = line[:i], line[i+1:] + break + } + } + + if has_value == false { + name = line + } + + // Ignore flag when already set; arguments have precedence over file + if f.actual[name] != nil { + continue + } + + m := f.formal + flag, alreadythere := m[name] + if !alreadythere { + if name == "help" || name == "h" { // special case for nice help message. + f.usage() + return ErrHelp + } + return f.failf("configuration variable provided but not defined: %s", name) + } + + if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg + if has_value { + if err := fv.Set(value); err != nil { + return f.failf("invalid boolean value %q for configuration variable %s: %v", value, name, err) + } + } else { + // flag without value is regarded a bool + fv.Set("true") + } + } else { + if !has_value { + return f.failf("configuration variable needs an argument: %s", name) + } + + if err := flag.Value.Set(value); err != nil { + return f.failf("invalid value %q for configuration variable %s: %v", value, name, err) + } + } + + // update f.actual + if f.actual == nil { + f.actual = make(map[string]*Flag) + } + f.actual[name] = flag + } + + if err := scanner.Err(); err != nil { + return err + } + + return nil +} + +// Parsed reports whether f.Parse has been called. +func (f *FlagSet) Parsed() bool { + return f.parsed +} + +// Parse parses the command-line flags from os.Args[1:]. Must be called +// after all flags are defined and before flags are accessed by the program. +func Parse() { + // Ignore errors; CommandLine is set for ExitOnError. + CommandLine.Parse(os.Args[1:]) +} + +// Parsed returns true if the command-line flags have been parsed. +func Parsed() bool { + return CommandLine.Parsed() +} + +// CommandLine is the default set of command-line flags, parsed from os.Args. +// The top-level functions such as BoolVar, Arg, and on are wrappers for the +// methods of CommandLine. +var CommandLine = NewFlagSet(os.Args[0], ExitOnError) + +// NewFlagSet returns a new, empty flag set with the specified name and +// error handling property. +func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { + f := &FlagSet{ + name: name, + errorHandling: errorHandling, + } + return f +} + +// Init sets the name and error handling property for a flag set. +// By default, the zero FlagSet uses an empty name and the +// ContinueOnError error handling policy. +func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { + f.name = name + f.errorHandling = errorHandling +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/flag_test.go b/Godeps/_workspace/src/github.com/lavab/flag/flag_test.go new file mode 100644 index 0000000..9fa5ee3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/flag_test.go @@ -0,0 +1,476 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package flag_test + +import ( + "bytes" + "fmt" + . "github.com/namsral/flag" + "os" + "sort" + "strings" + "syscall" + "testing" + "time" +) + +func boolString(s string) string { + if s == "0" { + return "false" + } + return "true" +} + +func TestEverything(t *testing.T) { + ResetForTesting(nil) + Bool("test_bool", false, "bool value") + Int("test_int", 0, "int value") + Int64("test_int64", 0, "int64 value") + Uint("test_uint", 0, "uint value") + Uint64("test_uint64", 0, "uint64 value") + String("test_string", "0", "string value") + Float64("test_float64", 0, "float64 value") + Duration("test_duration", 0, "time.Duration value") + + m := make(map[string]*Flag) + desired := "0" + visitor := func(f *Flag) { + if len(f.Name) > 5 && f.Name[0:5] == "test_" { + m[f.Name] = f + ok := false + switch { + case f.Value.String() == desired: + ok = true + case f.Name == "test_bool" && f.Value.String() == boolString(desired): + ok = true + case f.Name == "test_duration" && f.Value.String() == desired+"s": + ok = true + } + if !ok { + t.Error("Visit: bad value", f.Value.String(), "for", f.Name) + } + } + } + VisitAll(visitor) + if len(m) != 8 { + t.Error("VisitAll misses some flags") + for k, v := range m { + t.Log(k, *v) + } + } + m = make(map[string]*Flag) + Visit(visitor) + if len(m) != 0 { + t.Errorf("Visit sees unset flags") + for k, v := range m { + t.Log(k, *v) + } + } + // Now set all flags + Set("test_bool", "true") + Set("test_int", "1") + Set("test_int64", "1") + Set("test_uint", "1") + Set("test_uint64", "1") + Set("test_string", "1") + Set("test_float64", "1") + Set("test_duration", "1s") + desired = "1" + Visit(visitor) + if len(m) != 8 { + t.Error("Visit fails after set") + for k, v := range m { + t.Log(k, *v) + } + } + // Now test they're visited in sort order. + var flagNames []string + Visit(func(f *Flag) { flagNames = append(flagNames, f.Name) }) + if !sort.StringsAreSorted(flagNames) { + t.Errorf("flag names not sorted: %v", flagNames) + } +} + +func TestGet(t *testing.T) { + ResetForTesting(nil) + Bool("test_bool", true, "bool value") + Int("test_int", 1, "int value") + Int64("test_int64", 2, "int64 value") + Uint("test_uint", 3, "uint value") + Uint64("test_uint64", 4, "uint64 value") + String("test_string", "5", "string value") + Float64("test_float64", 6, "float64 value") + Duration("test_duration", 7, "time.Duration value") + + visitor := func(f *Flag) { + if len(f.Name) > 5 && f.Name[0:5] == "test_" { + g, ok := f.Value.(Getter) + if !ok { + t.Errorf("Visit: value does not satisfy Getter: %T", f.Value) + return + } + switch f.Name { + case "test_bool": + ok = g.Get() == true + case "test_int": + ok = g.Get() == int(1) + case "test_int64": + ok = g.Get() == int64(2) + case "test_uint": + ok = g.Get() == uint(3) + case "test_uint64": + ok = g.Get() == uint64(4) + case "test_string": + ok = g.Get() == "5" + case "test_float64": + ok = g.Get() == float64(6) + case "test_duration": + ok = g.Get() == time.Duration(7) + } + if !ok { + t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), f.Name) + } + } + } + VisitAll(visitor) +} + +func TestUsage(t *testing.T) { + called := false + ResetForTesting(func() { called = true }) + if CommandLine.Parse([]string{"-x"}) == nil { + t.Error("parse did not fail for unknown flag") + } + if !called { + t.Error("did not call Usage for unknown flag") + } +} + +func testParse(f *FlagSet, t *testing.T) { + if f.Parsed() { + t.Error("f.Parse() = true before Parse") + } + boolFlag := f.Bool("bool", false, "bool value") + bool2Flag := f.Bool("bool2", false, "bool2 value") + intFlag := f.Int("int", 0, "int value") + int64Flag := f.Int64("int64", 0, "int64 value") + uintFlag := f.Uint("uint", 0, "uint value") + uint64Flag := f.Uint64("uint64", 0, "uint64 value") + stringFlag := f.String("string", "0", "string value") + float64Flag := f.Float64("float64", 0, "float64 value") + durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") + extra := "one-extra-argument" + args := []string{ + "-bool", + "-bool2=true", + "--int", "22", + "--int64", "0x23", + "-uint", "24", + "--uint64", "25", + "-string", "hello", + "-float64", "2718e28", + "-duration", "2m", + extra, + } + if err := f.Parse(args); err != nil { + t.Fatal(err) + } + if !f.Parsed() { + t.Error("f.Parse() = false after Parse") + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } + if len(f.Args()) != 1 { + t.Error("expected one argument, got", len(f.Args())) + } else if f.Args()[0] != extra { + t.Errorf("expected argument %q got %q", extra, f.Args()[0]) + } +} + +func TestParse(t *testing.T) { + ResetForTesting(func() { t.Error("bad parse") }) + testParse(CommandLine, t) +} + +func TestFlagSetParse(t *testing.T) { + testParse(NewFlagSet("test", ContinueOnError), t) +} + +// Declare a user-defined flag type. +type flagVar []string + +func (f *flagVar) String() string { + return fmt.Sprint([]string(*f)) +} + +func (f *flagVar) Set(value string) error { + *f = append(*f, value) + return nil +} + +func TestUserDefined(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var v flagVar + flags.Var(&v, "v", "usage") + if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil { + t.Error(err) + } + if len(v) != 3 { + t.Fatal("expected 3 args; got ", len(v)) + } + expect := "[1 2 3]" + if v.String() != expect { + t.Errorf("expected value %q got %q", expect, v.String()) + } +} + +// Declare a user-defined boolean flag type. +type boolFlagVar struct { + count int +} + +func (b *boolFlagVar) String() string { + return fmt.Sprintf("%d", b.count) +} + +func (b *boolFlagVar) Set(value string) error { + if value == "true" { + b.count++ + } + return nil +} + +func (b *boolFlagVar) IsBoolFlag() bool { + return b.count < 4 +} + +func TestUserDefinedBool(t *testing.T) { + var flags FlagSet + flags.Init("test", ContinueOnError) + var b boolFlagVar + var err error + flags.Var(&b, "b", "usage") + if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil { + if b.count < 4 { + t.Error(err) + } + } + + if b.count != 4 { + t.Errorf("want: %d; got: %d", 4, b.count) + } + + if err == nil { + t.Error("expected error; got none") + } +} + +func TestSetOutput(t *testing.T) { + var flags FlagSet + var buf bytes.Buffer + flags.SetOutput(&buf) + flags.Init("test", ContinueOnError) + flags.Parse([]string{"-unknown"}) + if out := buf.String(); !strings.Contains(out, "-unknown") { + t.Logf("expected output mentioning unknown; got %q", out) + } +} + +// This tests that one can reset the flags. This still works but not well, and is +// superseded by FlagSet. +func TestChangingArgs(t *testing.T) { + ResetForTesting(func() { t.Fatal("bad parse") }) + oldArgs := os.Args + defer func() { os.Args = oldArgs }() + os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"} + before := Bool("before", false, "") + if err := CommandLine.Parse(os.Args[1:]); err != nil { + t.Fatal(err) + } + cmd := Arg(0) + os.Args = Args() + after := Bool("after", false, "") + Parse() + args := Args() + + if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" { + t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args) + } +} + +// Test that -help invokes the usage message and returns ErrHelp. +func TestHelp(t *testing.T) { + var helpCalled = false + fs := NewFlagSet("help test", ContinueOnError) + fs.Usage = func() { helpCalled = true } + var flag bool + fs.BoolVar(&flag, "flag", false, "regular flag") + // Regular flag invocation should work + err := fs.Parse([]string{"-flag=true"}) + if err != nil { + t.Fatal("expected no error; got ", err) + } + if !flag { + t.Error("flag was not set by -flag") + } + if helpCalled { + t.Error("help called for regular flag") + helpCalled = false // reset for next test + } + // Help flag should work as expected. + err = fs.Parse([]string{"-help"}) + if err == nil { + t.Fatal("error expected") + } + if err != ErrHelp { + t.Fatal("expected ErrHelp; got ", err) + } + if !helpCalled { + t.Fatal("help was not called") + } + // If we define a help flag, that should override. + var help bool + fs.BoolVar(&help, "help", false, "help flag") + helpCalled = false + err = fs.Parse([]string{"-help"}) + if err != nil { + t.Fatal("expected no error for defined -help; got ", err) + } + if helpCalled { + t.Fatal("help was called; should not have been for defined help flag") + } +} + +// Test parsing a environment variables +func TestParseEnv(t *testing.T) { + + syscall.Setenv("BOOL", "") + syscall.Setenv("BOOL2", "true") + syscall.Setenv("INT", "22") + syscall.Setenv("INT64", "0x23") + syscall.Setenv("UINT", "24") + syscall.Setenv("UINT64", "25") + syscall.Setenv("STRING", "hello") + syscall.Setenv("FLOAT64", "2718e28") + syscall.Setenv("DURATION", "2m") + + f := NewFlagSet(os.Args[0], ContinueOnError) + + boolFlag := f.Bool("bool", false, "bool value") + bool2Flag := f.Bool("bool2", false, "bool2 value") + intFlag := f.Int("int", 0, "int value") + int64Flag := f.Int64("int64", 0, "int64 value") + uintFlag := f.Uint("uint", 0, "uint value") + uint64Flag := f.Uint64("uint64", 0, "uint64 value") + stringFlag := f.String("string", "0", "string value") + float64Flag := f.Float64("float64", 0, "float64 value") + durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") + + err := f.ParseEnv(os.Environ()) + if err != nil { + t.Fatal("expected no error; got ", err) + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } +} + +// Test parsing a configuration file +func TestParseFile(t *testing.T) { + + f := NewFlagSet(os.Args[0], ContinueOnError) + + boolFlag := f.Bool("bool", false, "bool value") + bool2Flag := f.Bool("bool2", false, "bool2 value") + intFlag := f.Int("int", 0, "int value") + int64Flag := f.Int64("int64", 0, "int64 value") + uintFlag := f.Uint("uint", 0, "uint value") + uint64Flag := f.Uint64("uint64", 0, "uint64 value") + stringFlag := f.String("string", "0", "string value") + float64Flag := f.Float64("float64", 0, "float64 value") + durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") + + err := f.ParseFile("./testdata/test.conf") + if err != nil { + t.Fatal("expected no error; got ", err) + } + if *boolFlag != true { + t.Error("bool flag should be true, is ", *boolFlag) + } + if *bool2Flag != true { + t.Error("bool2 flag should be true, is ", *bool2Flag) + } + if *intFlag != 22 { + t.Error("int flag should be 22, is ", *intFlag) + } + if *int64Flag != 0x23 { + t.Error("int64 flag should be 0x23, is ", *int64Flag) + } + if *uintFlag != 24 { + t.Error("uint flag should be 24, is ", *uintFlag) + } + if *uint64Flag != 25 { + t.Error("uint64 flag should be 25, is ", *uint64Flag) + } + if *stringFlag != "hello" { + t.Error("string flag should be `hello`, is ", *stringFlag) + } + if *float64Flag != 2718e28 { + t.Error("float64 flag should be 2718e28, is ", *float64Flag) + } + if *durationFlag != 2*time.Minute { + t.Error("duration flag should be 2m, is ", *durationFlag) + } +} diff --git a/Godeps/_workspace/src/github.com/lavab/flag/testdata/test.conf b/Godeps/_workspace/src/github.com/lavab/flag/testdata/test.conf new file mode 100644 index 0000000..ecaa7c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/lavab/flag/testdata/test.conf @@ -0,0 +1,11 @@ +# this is a comment followed by an empty line + +bool +bool2=true +int 22 +int64 0x23 +uint 24 +uint64 25 +string hello +float64 2718e28 +duration 2m \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/.travis.yml b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/.travis.yml new file mode 100644 index 0000000..a09420b --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/.travis.yml @@ -0,0 +1,13 @@ +language: go +go: + - 1.2.2 + - 1.3.1 +env: + - GOARCH=amd64 + - GOARCH=386 +install: + - go get code.google.com/p/snappy-go/snappy +script: + - go test -v +notifications: + email: false diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/LICENSE b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/LICENSE new file mode 100644 index 0000000..89de354 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/LICENSE @@ -0,0 +1,17 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/README.md b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/README.md new file mode 100644 index 0000000..277264f --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/README.md @@ -0,0 +1,11 @@ +## go-snappystream + +a Go package for framed snappy streams. + +[![Build Status](https://secure.travis-ci.org/mreiferson/go-snappystream.png?branch=master)](http://travis-ci.org/mreiferson/go-snappystream) [![GoDoc](https://godoc.org/github.com/mreiferson/go-snappystream?status.svg)](https://godoc.org/github.com/mreiferson/go-snappystream) + +This package wraps [snappy-go][1] and supplies a `Reader` and `Writer` +for the snappy [framed stream format][2]. + +[1]: https://code.google.com/p/snappy-go/ +[2]: https://snappy.googlecode.com/svn/trunk/framing_format.txt diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/fixturedata_test.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/fixturedata_test.go new file mode 100644 index 0000000..fed03f5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/fixturedata_test.go @@ -0,0 +1,2701 @@ +package snappystream + +var testDataMan = []byte(` +.TH XARGS 1L \" -*- nroff -*- +.SH NAME +xargs \- build and execute command lines from standard input +.SH SYNOPSIS +.B xargs +[\-0prtx] [\-e[eof-str]] [\-i[replace-str]] [\-l[max-lines]] +[\-n max-args] [\-s max-chars] [\-P max-procs] [\-\-null] [\-\-eof[=eof-str]] +[\-\-replace[=replace-str]] [\-\-max-lines[=max-lines]] [\-\-interactive] +[\-\-max-chars=max-chars] [\-\-verbose] [\-\-exit] [\-\-max-procs=max-procs] +[\-\-max-args=max-args] [\-\-no-run-if-empty] [\-\-version] [\-\-help] +[command [initial-arguments]] +.SH DESCRIPTION +This manual page +documents the GNU version of +.BR xargs . +.B xargs +reads arguments from the standard input, delimited by blanks (which can be +protected with double or single quotes or a backslash) or newlines, +and executes the +.I command +(default is /bin/echo) one or more times with any +.I initial-arguments +followed by arguments read from standard input. Blank lines on the +standard input are ignored. +.P +.B xargs +exits with the following status: +.nf +0 if it succeeds +123 if any invocation of the command exited with status 1-125 +124 if the command exited with status 255 +125 if the command is killed by a signal +126 if the command cannot be run +127 if the command is not found +1 if some other error occurred. +.fi +.SS OPTIONS +.TP +.I "\-\-null, \-0" +Input filenames are terminated by a null character instead of by +whitespace, and the quotes and backslash are not special (every +character is taken literally). Disables the end of file string, which +is treated like any other argument. Useful when arguments might +contain white space, quote marks, or backslashes. The GNU find +\-print0 option produces input suitable for this mode. +.TP +.I "\-\-eof[=eof-str], \-e[eof-str]" +Set the end of file string to \fIeof-str\fR. If the end of file +string occurs as a line of input, the rest of the input is ignored. +If \fIeof-str\fR is omitted, there is no end of file string. If this +option is not given, the end of file string defaults to "_". +.TP +.I "\-\-help" +Print a summary of the options to +.B xargs +and exit. +.TP +.I "\-\-replace[=replace-str], \-i[replace-str]" +Replace occurences of \fIreplace-str\fR in the initial arguments with +names read from standard input. +Also, unquoted blanks do not terminate arguments. +If \fIreplace-str\fR is omitted, it +defaults to "{}" (like for 'find \-exec'). Implies \fI\-x\fP and +\fI\-l 1\fP. +.TP +.I "\-\-max-lines[=max-lines], -l[max-lines]" +Use at most \fImax-lines\fR nonblank input lines per command line; +\fImax-lines\fR defaults to 1 if omitted. Trailing blanks cause an +input line to be logically continued on the next input line. Implies +\fI\-x\fR. +.TP +.I "\-\-max-args=max-args, \-n max-args" +Use at most \fImax-args\fR arguments per command line. Fewer than +\fImax-args\fR arguments will be used if the size (see the \-s option) +is exceeded, unless the \-x option is given, in which case \fBxargs\fR +will exit. +.TP +.I "\-\-interactive, \-p" +Prompt the user about whether to run each command line and read a line +from the terminal. Only run the command line if the response starts +with 'y' or 'Y'. Implies \fI\-t\fR. +.TP +.I "\-\-no-run-if-empty, \-r" +If the standard input does not contain any nonblanks, do not run the +command. Normally, the command is run once even if there is no input. +.TP +.I "\-\-max-chars=max-chars, \-s max-chars" +Use at most \fImax-chars\fR characters per command line, including the +command and initial arguments and the terminating nulls at the ends of +the argument strings. The default is as large as possible, up to 20k +characters. +.TP +.I "\-\-verbose, \-t" +Print the command line on the standard error output before executing +it. +.TP +.I "\-\-version" +Print the version number of +.B xargs +and exit. +.TP +.I "\-\-exit, \-x" +Exit if the size (see the \fI\-s\fR option) is exceeded. +.TP +.I "\-\-max-procs=max-procs, \-P max-procs" +Run up to \fImax-procs\fR processes at a time; the default is 1. If +\fImax-procs\fR is 0, \fBxargs\fR will run as many processes as +possible at a time. Use the \fI\-n\fR option with \fI\-P\fR; +otherwise chances are that only one exec will be done. +.SH "SEE ALSO" +\fBfind\fP(1L), \fBlocate\fP(1L), \fBlocatedb\fP(5L), \fBupdatedb\fP(1) +\fBFinding Files\fP (on-line in Info, or printed)`) + +// curl -s https://api.github.com/users/mreiferson/repos +var testDataJSON = []byte(` +[ + { + "id": 19041094, + "name": "2014-talks", + "full_name": "mreiferson/2014-talks", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/2014-talks", + "description": "This is the official repository for slides and talks from GopherCon 2014", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/2014-talks", + "forks_url": "https://api.github.com/repos/mreiferson/2014-talks/forks", + "keys_url": "https://api.github.com/repos/mreiferson/2014-talks/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/2014-talks/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/2014-talks/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/2014-talks/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/2014-talks/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/2014-talks/events", + "assignees_url": "https://api.github.com/repos/mreiferson/2014-talks/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/2014-talks/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/2014-talks/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/2014-talks/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/2014-talks/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/2014-talks/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/2014-talks/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/2014-talks/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/2014-talks/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/2014-talks/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/2014-talks/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/2014-talks/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/2014-talks/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/2014-talks/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/2014-talks/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/2014-talks/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/2014-talks/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/2014-talks/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/2014-talks/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/2014-talks/merges", + "archive_url": "https://api.github.com/repos/mreiferson/2014-talks/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/2014-talks/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/2014-talks/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/2014-talks/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/2014-talks/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/2014-talks/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/2014-talks/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/2014-talks/releases{/id}", + "created_at": "2014-04-22T18:28:59Z", + "updated_at": "2014-04-26T03:10:39Z", + "pushed_at": "2014-04-25T14:46:35Z", + "git_url": "git://github.com/mreiferson/2014-talks.git", + "ssh_url": "git@github.com:mreiferson/2014-talks.git", + "clone_url": "https://github.com/mreiferson/2014-talks.git", + "svn_url": "https://github.com/mreiferson/2014-talks", + "homepage": null, + "size": 3596, + "stargazers_count": 0, + "watchers_count": 0, + "language": null, + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 3329246, + "name": "asyncdynamo", + "full_name": "mreiferson/asyncdynamo", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/asyncdynamo", + "description": "async Amazon DynamoDB library for Tornado", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/asyncdynamo", + "forks_url": "https://api.github.com/repos/mreiferson/asyncdynamo/forks", + "keys_url": "https://api.github.com/repos/mreiferson/asyncdynamo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/asyncdynamo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/asyncdynamo/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/asyncdynamo/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/asyncdynamo/events", + "assignees_url": "https://api.github.com/repos/mreiferson/asyncdynamo/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/asyncdynamo/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/asyncdynamo/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/asyncdynamo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/asyncdynamo/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/asyncdynamo/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/asyncdynamo/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/asyncdynamo/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/asyncdynamo/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/asyncdynamo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/asyncdynamo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/asyncdynamo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/asyncdynamo/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/asyncdynamo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/asyncdynamo/merges", + "archive_url": "https://api.github.com/repos/mreiferson/asyncdynamo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/asyncdynamo/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/asyncdynamo/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/asyncdynamo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/asyncdynamo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/asyncdynamo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/asyncdynamo/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/asyncdynamo/releases{/id}", + "created_at": "2012-02-01T21:32:54Z", + "updated_at": "2014-04-03T21:58:44Z", + "pushed_at": "2012-02-01T21:06:23Z", + "git_url": "git://github.com/mreiferson/asyncdynamo.git", + "ssh_url": "git@github.com:mreiferson/asyncdynamo.git", + "clone_url": "https://github.com/mreiferson/asyncdynamo.git", + "svn_url": "https://github.com/mreiferson/asyncdynamo", + "homepage": "", + "size": 73, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Python", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 2622445, + "name": "asyncmongo", + "full_name": "mreiferson/asyncmongo", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/asyncmongo", + "description": "An asynchronous library for accessing mongo with tornado.ioloop", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/asyncmongo", + "forks_url": "https://api.github.com/repos/mreiferson/asyncmongo/forks", + "keys_url": "https://api.github.com/repos/mreiferson/asyncmongo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/asyncmongo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/asyncmongo/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/asyncmongo/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/asyncmongo/events", + "assignees_url": "https://api.github.com/repos/mreiferson/asyncmongo/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/asyncmongo/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/asyncmongo/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/asyncmongo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/asyncmongo/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/asyncmongo/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/asyncmongo/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/asyncmongo/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/asyncmongo/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/asyncmongo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/asyncmongo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/asyncmongo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/asyncmongo/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/asyncmongo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/asyncmongo/merges", + "archive_url": "https://api.github.com/repos/mreiferson/asyncmongo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/asyncmongo/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/asyncmongo/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/asyncmongo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/asyncmongo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/asyncmongo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/asyncmongo/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/asyncmongo/releases{/id}", + "created_at": "2011-10-21T19:01:05Z", + "updated_at": "2013-01-04T11:58:26Z", + "pushed_at": "2011-10-21T19:02:46Z", + "git_url": "git://github.com/mreiferson/asyncmongo.git", + "ssh_url": "git@github.com:mreiferson/asyncmongo.git", + "clone_url": "https://github.com/mreiferson/asyncmongo.git", + "svn_url": "https://github.com/mreiferson/asyncmongo", + "homepage": "http://github.com/bitly/asyncmongo", + "size": 563, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Python", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 4554560, + "name": "blog.perplexedlabs.com", + "full_name": "mreiferson/blog.perplexedlabs.com", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/blog.perplexedlabs.com", + "description": "archive of posts", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com", + "forks_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/forks", + "keys_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/events", + "assignees_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/merges", + "archive_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/blog.perplexedlabs.com/releases{/id}", + "created_at": "2012-06-05T01:38:40Z", + "updated_at": "2014-04-27T23:44:56Z", + "pushed_at": "2014-04-27T23:44:56Z", + "git_url": "git://github.com/mreiferson/blog.perplexedlabs.com.git", + "ssh_url": "git@github.com:mreiferson/blog.perplexedlabs.com.git", + "clone_url": "https://github.com/mreiferson/blog.perplexedlabs.com.git", + "svn_url": "https://github.com/mreiferson/blog.perplexedlabs.com", + "homepage": "http://blog.perplexedlabs.com/", + "size": 668, + "stargazers_count": 1, + "watchers_count": 1, + "language": null, + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 2861903, + "name": "btpath", + "full_name": "mreiferson/btpath", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/btpath", + "description": "A* implementation/test app (1997)", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/btpath", + "forks_url": "https://api.github.com/repos/mreiferson/btpath/forks", + "keys_url": "https://api.github.com/repos/mreiferson/btpath/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/btpath/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/btpath/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/btpath/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/btpath/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/btpath/events", + "assignees_url": "https://api.github.com/repos/mreiferson/btpath/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/btpath/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/btpath/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/btpath/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/btpath/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/btpath/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/btpath/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/btpath/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/btpath/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/btpath/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/btpath/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/btpath/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/btpath/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/btpath/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/btpath/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/btpath/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/btpath/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/btpath/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/btpath/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/btpath/merges", + "archive_url": "https://api.github.com/repos/mreiferson/btpath/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/btpath/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/btpath/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/btpath/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/btpath/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/btpath/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/btpath/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/btpath/releases{/id}", + "created_at": "2011-11-27T17:23:02Z", + "updated_at": "2013-01-04T17:58:42Z", + "pushed_at": "2011-11-29T01:36:49Z", + "git_url": "git://github.com/mreiferson/btpath.git", + "ssh_url": "git@github.com:mreiferson/btpath.git", + "clone_url": "https://github.com/mreiferson/btpath.git", + "svn_url": "https://github.com/mreiferson/btpath", + "homepage": "", + "size": 88, + "stargazers_count": 1, + "watchers_count": 1, + "language": "C++", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 15747148, + "name": "chef-nsq", + "full_name": "mreiferson/chef-nsq", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/chef-nsq", + "description": "Chef Cookbook for NSQ", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/chef-nsq", + "forks_url": "https://api.github.com/repos/mreiferson/chef-nsq/forks", + "keys_url": "https://api.github.com/repos/mreiferson/chef-nsq/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/chef-nsq/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/chef-nsq/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/chef-nsq/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/chef-nsq/events", + "assignees_url": "https://api.github.com/repos/mreiferson/chef-nsq/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/chef-nsq/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/chef-nsq/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/chef-nsq/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/chef-nsq/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/chef-nsq/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/chef-nsq/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/chef-nsq/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/chef-nsq/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/chef-nsq/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/chef-nsq/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/chef-nsq/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/chef-nsq/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/chef-nsq/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/chef-nsq/merges", + "archive_url": "https://api.github.com/repos/mreiferson/chef-nsq/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/chef-nsq/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/chef-nsq/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/chef-nsq/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/chef-nsq/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/chef-nsq/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/chef-nsq/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/chef-nsq/releases{/id}", + "created_at": "2014-01-08T20:27:41Z", + "updated_at": "2014-04-28T14:15:50Z", + "pushed_at": "2014-04-28T04:31:58Z", + "git_url": "git://github.com/mreiferson/chef-nsq.git", + "ssh_url": "git@github.com:mreiferson/chef-nsq.git", + "clone_url": "https://github.com/mreiferson/chef-nsq.git", + "svn_url": "https://github.com/mreiferson/chef-nsq", + "homepage": null, + "size": 132, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": false, + "has_downloads": true, + "has_wiki": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 5287337, + "name": "dablooms", + "full_name": "mreiferson/dablooms", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/dablooms", + "description": "scaling, counting, bloom filter library", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/dablooms", + "forks_url": "https://api.github.com/repos/mreiferson/dablooms/forks", + "keys_url": "https://api.github.com/repos/mreiferson/dablooms/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/dablooms/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/dablooms/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/dablooms/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/dablooms/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/dablooms/events", + "assignees_url": "https://api.github.com/repos/mreiferson/dablooms/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/dablooms/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/dablooms/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/dablooms/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/dablooms/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/dablooms/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/dablooms/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/dablooms/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/dablooms/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/dablooms/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/dablooms/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/dablooms/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/dablooms/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/dablooms/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/dablooms/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/dablooms/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/dablooms/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/dablooms/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/dablooms/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/dablooms/merges", + "archive_url": "https://api.github.com/repos/mreiferson/dablooms/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/dablooms/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/dablooms/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/dablooms/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/dablooms/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/dablooms/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/dablooms/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/dablooms/releases{/id}", + "created_at": "2012-08-03T16:03:50Z", + "updated_at": "2013-03-08T15:37:44Z", + "pushed_at": "2013-03-08T15:37:44Z", + "git_url": "git://github.com/mreiferson/dablooms.git", + "ssh_url": "git@github.com:mreiferson/dablooms.git", + "clone_url": "https://github.com/mreiferson/dablooms.git", + "svn_url": "https://github.com/mreiferson/dablooms", + "homepage": "", + "size": 186, + "stargazers_count": 1, + "watchers_count": 1, + "language": "C", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 2861959, + "name": "dod", + "full_name": "mreiferson/dod", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/dod", + "description": "Do or Die - an incomplete real-time strategy game inspired by Warcraft (1997)", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/dod", + "forks_url": "https://api.github.com/repos/mreiferson/dod/forks", + "keys_url": "https://api.github.com/repos/mreiferson/dod/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/dod/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/dod/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/dod/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/dod/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/dod/events", + "assignees_url": "https://api.github.com/repos/mreiferson/dod/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/dod/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/dod/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/dod/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/dod/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/dod/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/dod/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/dod/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/dod/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/dod/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/dod/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/dod/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/dod/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/dod/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/dod/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/dod/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/dod/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/dod/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/dod/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/dod/merges", + "archive_url": "https://api.github.com/repos/mreiferson/dod/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/dod/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/dod/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/dod/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/dod/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/dod/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/dod/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/dod/releases{/id}", + "created_at": "2011-11-27T17:33:19Z", + "updated_at": "2014-05-13T00:56:53Z", + "pushed_at": "2011-11-29T02:08:57Z", + "git_url": "git://github.com/mreiferson/dod.git", + "ssh_url": "git@github.com:mreiferson/dod.git", + "clone_url": "https://github.com/mreiferson/dod.git", + "svn_url": "https://github.com/mreiferson/dod", + "homepage": "", + "size": 2044, + "stargazers_count": 1, + "watchers_count": 1, + "language": "C++", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 4515792, + "name": "doozer", + "full_name": "mreiferson/doozer", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/doozer", + "description": "Go client driver for doozerd, a consistent, distributed data store", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/doozer", + "forks_url": "https://api.github.com/repos/mreiferson/doozer/forks", + "keys_url": "https://api.github.com/repos/mreiferson/doozer/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/doozer/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/doozer/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/doozer/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/doozer/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/doozer/events", + "assignees_url": "https://api.github.com/repos/mreiferson/doozer/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/doozer/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/doozer/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/doozer/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/doozer/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/doozer/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/doozer/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/doozer/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/doozer/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/doozer/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/doozer/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/doozer/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/doozer/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/doozer/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/doozer/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/doozer/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/doozer/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/doozer/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/doozer/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/doozer/merges", + "archive_url": "https://api.github.com/repos/mreiferson/doozer/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/doozer/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/doozer/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/doozer/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/doozer/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/doozer/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/doozer/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/doozer/releases{/id}", + "created_at": "2012-06-01T03:41:14Z", + "updated_at": "2013-03-16T15:23:56Z", + "pushed_at": "2013-03-16T15:23:55Z", + "git_url": "git://github.com/mreiferson/doozer.git", + "ssh_url": "git@github.com:mreiferson/doozer.git", + "clone_url": "https://github.com/mreiferson/doozer.git", + "svn_url": "https://github.com/mreiferson/doozer", + "homepage": "https://github.com/ha/doozerd", + "size": 2584, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 3391437, + "name": "doozer-c", + "full_name": "mreiferson/doozer-c", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/doozer-c", + "description": "async C client library for doozerd", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/doozer-c", + "forks_url": "https://api.github.com/repos/mreiferson/doozer-c/forks", + "keys_url": "https://api.github.com/repos/mreiferson/doozer-c/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/doozer-c/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/doozer-c/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/doozer-c/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/doozer-c/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/doozer-c/events", + "assignees_url": "https://api.github.com/repos/mreiferson/doozer-c/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/doozer-c/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/doozer-c/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/doozer-c/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/doozer-c/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/doozer-c/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/doozer-c/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/doozer-c/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/doozer-c/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/doozer-c/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/doozer-c/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/doozer-c/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/doozer-c/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/doozer-c/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/doozer-c/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/doozer-c/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/doozer-c/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/doozer-c/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/doozer-c/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/doozer-c/merges", + "archive_url": "https://api.github.com/repos/mreiferson/doozer-c/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/doozer-c/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/doozer-c/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/doozer-c/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/doozer-c/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/doozer-c/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/doozer-c/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/doozer-c/releases{/id}", + "created_at": "2012-02-08T21:15:33Z", + "updated_at": "2014-04-03T21:58:49Z", + "pushed_at": "2012-11-21T16:46:25Z", + "git_url": "git://github.com/mreiferson/doozer-c.git", + "ssh_url": "git@github.com:mreiferson/doozer-c.git", + "clone_url": "https://github.com/mreiferson/doozer-c.git", + "svn_url": "https://github.com/mreiferson/doozer-c", + "homepage": "", + "size": 158, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 4515795, + "name": "doozerd", + "full_name": "mreiferson/doozerd", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/doozerd", + "description": "A consistent distributed data store.", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/doozerd", + "forks_url": "https://api.github.com/repos/mreiferson/doozerd/forks", + "keys_url": "https://api.github.com/repos/mreiferson/doozerd/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/doozerd/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/doozerd/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/doozerd/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/doozerd/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/doozerd/events", + "assignees_url": "https://api.github.com/repos/mreiferson/doozerd/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/doozerd/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/doozerd/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/doozerd/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/doozerd/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/doozerd/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/doozerd/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/doozerd/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/doozerd/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/doozerd/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/doozerd/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/doozerd/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/doozerd/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/doozerd/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/doozerd/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/doozerd/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/doozerd/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/doozerd/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/doozerd/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/doozerd/merges", + "archive_url": "https://api.github.com/repos/mreiferson/doozerd/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/doozerd/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/doozerd/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/doozerd/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/doozerd/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/doozerd/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/doozerd/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/doozerd/releases{/id}", + "created_at": "2012-06-01T03:41:32Z", + "updated_at": "2013-12-28T19:22:30Z", + "pushed_at": "2013-12-28T19:22:30Z", + "git_url": "git://github.com/mreiferson/doozerd.git", + "ssh_url": "git@github.com:mreiferson/doozerd.git", + "clone_url": "https://github.com/mreiferson/doozerd.git", + "svn_url": "https://github.com/mreiferson/doozerd", + "homepage": "", + "size": 3135, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 8172002, + "name": "e", + "full_name": "mreiferson/e", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/e", + "description": "Library containing high-performance datastructures and utilities for C++", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/e", + "forks_url": "https://api.github.com/repos/mreiferson/e/forks", + "keys_url": "https://api.github.com/repos/mreiferson/e/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/e/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/e/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/e/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/e/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/e/events", + "assignees_url": "https://api.github.com/repos/mreiferson/e/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/e/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/e/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/e/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/e/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/e/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/e/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/e/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/e/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/e/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/e/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/e/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/e/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/e/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/e/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/e/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/e/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/e/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/e/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/e/merges", + "archive_url": "https://api.github.com/repos/mreiferson/e/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/e/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/e/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/e/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/e/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/e/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/e/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/e/releases{/id}", + "created_at": "2013-02-13T02:42:55Z", + "updated_at": "2013-02-18T21:10:07Z", + "pushed_at": "2013-02-13T02:45:16Z", + "git_url": "git://github.com/mreiferson/e.git", + "ssh_url": "git@github.com:mreiferson/e.git", + "clone_url": "https://github.com/mreiferson/e.git", + "svn_url": "https://github.com/mreiferson/e", + "homepage": "", + "size": 437, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C++", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 2792604, + "name": "encfs-macfusion2", + "full_name": "mreiferson/encfs-macfusion2", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/encfs-macfusion2", + "description": "enhanced version of encfs-macfusion2 plugin http://code.google.com/p/encfs-macfusion2/", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/encfs-macfusion2", + "forks_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/forks", + "keys_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/events", + "assignees_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/merges", + "archive_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/encfs-macfusion2/releases{/id}", + "created_at": "2011-11-17T01:58:01Z", + "updated_at": "2013-10-22T06:29:03Z", + "pushed_at": "2011-11-17T02:13:15Z", + "git_url": "git://github.com/mreiferson/encfs-macfusion2.git", + "ssh_url": "git@github.com:mreiferson/encfs-macfusion2.git", + "clone_url": "https://github.com/mreiferson/encfs-macfusion2.git", + "svn_url": "https://github.com/mreiferson/encfs-macfusion2", + "homepage": "", + "size": 195, + "stargazers_count": 4, + "watchers_count": 4, + "language": "Objective-C", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 1, + "mirror_url": null, + "open_issues_count": 0, + "forks": 1, + "open_issues": 0, + "watchers": 4, + "default_branch": "master" + }, + { + "id": 5263991, + "name": "file2http", + "full_name": "mreiferson/file2http", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/file2http", + "description": "spray a line-oriented file at an HTTP endpoint", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/file2http", + "forks_url": "https://api.github.com/repos/mreiferson/file2http/forks", + "keys_url": "https://api.github.com/repos/mreiferson/file2http/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/file2http/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/file2http/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/file2http/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/file2http/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/file2http/events", + "assignees_url": "https://api.github.com/repos/mreiferson/file2http/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/file2http/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/file2http/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/file2http/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/file2http/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/file2http/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/file2http/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/file2http/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/file2http/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/file2http/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/file2http/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/file2http/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/file2http/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/file2http/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/file2http/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/file2http/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/file2http/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/file2http/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/file2http/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/file2http/merges", + "archive_url": "https://api.github.com/repos/mreiferson/file2http/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/file2http/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/file2http/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/file2http/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/file2http/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/file2http/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/file2http/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/file2http/releases{/id}", + "created_at": "2012-08-01T19:56:16Z", + "updated_at": "2013-01-11T13:21:40Z", + "pushed_at": "2012-12-21T15:44:32Z", + "git_url": "git://github.com/mreiferson/file2http.git", + "ssh_url": "git@github.com:mreiferson/file2http.git", + "clone_url": "https://github.com/mreiferson/file2http.git", + "svn_url": "https://github.com/mreiferson/file2http", + "homepage": "", + "size": 96, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 15291117, + "name": "gablog", + "full_name": "mreiferson/gablog", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/gablog", + "description": "Gopher Academy Blog -- fork of go.blog", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/gablog", + "forks_url": "https://api.github.com/repos/mreiferson/gablog/forks", + "keys_url": "https://api.github.com/repos/mreiferson/gablog/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/gablog/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/gablog/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/gablog/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/gablog/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/gablog/events", + "assignees_url": "https://api.github.com/repos/mreiferson/gablog/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/gablog/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/gablog/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/gablog/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/gablog/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/gablog/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/gablog/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/gablog/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/gablog/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/gablog/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/gablog/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/gablog/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/gablog/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/gablog/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/gablog/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/gablog/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/gablog/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/gablog/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/gablog/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/gablog/merges", + "archive_url": "https://api.github.com/repos/mreiferson/gablog/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/gablog/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/gablog/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/gablog/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/gablog/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/gablog/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/gablog/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/gablog/releases{/id}", + "created_at": "2013-12-18T18:38:37Z", + "updated_at": "2013-12-20T22:39:33Z", + "pushed_at": "2013-12-20T22:21:52Z", + "git_url": "git://github.com/mreiferson/gablog.git", + "ssh_url": "git@github.com:mreiferson/gablog.git", + "clone_url": "https://github.com/mreiferson/gablog.git", + "svn_url": "https://github.com/mreiferson/gablog", + "homepage": "http://blog.gopheracademy.com", + "size": 7911, + "stargazers_count": 0, + "watchers_count": 0, + "language": "CSS", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 12223286, + "name": "git-open-pull", + "full_name": "mreiferson/git-open-pull", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/git-open-pull", + "description": "convert a github issue into a pull request", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/git-open-pull", + "forks_url": "https://api.github.com/repos/mreiferson/git-open-pull/forks", + "keys_url": "https://api.github.com/repos/mreiferson/git-open-pull/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/git-open-pull/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/git-open-pull/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/git-open-pull/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/git-open-pull/events", + "assignees_url": "https://api.github.com/repos/mreiferson/git-open-pull/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/git-open-pull/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/git-open-pull/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/git-open-pull/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/git-open-pull/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/git-open-pull/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/git-open-pull/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/git-open-pull/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/git-open-pull/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/git-open-pull/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/git-open-pull/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/git-open-pull/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/git-open-pull/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/git-open-pull/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/git-open-pull/merges", + "archive_url": "https://api.github.com/repos/mreiferson/git-open-pull/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/git-open-pull/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/git-open-pull/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/git-open-pull/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/git-open-pull/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/git-open-pull/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/git-open-pull/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/git-open-pull/releases{/id}", + "created_at": "2013-08-19T17:45:47Z", + "updated_at": "2014-03-03T19:50:11Z", + "pushed_at": "2014-03-03T19:50:09Z", + "git_url": "git://github.com/mreiferson/git-open-pull.git", + "ssh_url": "git@github.com:mreiferson/git-open-pull.git", + "clone_url": "https://github.com/mreiferson/git-open-pull.git", + "svn_url": "https://github.com/mreiferson/git-open-pull", + "homepage": "https://github.com/jehiah/git-open-pull", + "size": 155, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Shell", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 9547968, + "name": "go-hostpool", + "full_name": "mreiferson/go-hostpool", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-hostpool", + "description": "Intelligently and flexibly pool among multiple hosts from your Go application", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/go-hostpool", + "forks_url": "https://api.github.com/repos/mreiferson/go-hostpool/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-hostpool/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-hostpool/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-hostpool/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-hostpool/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-hostpool/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-hostpool/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-hostpool/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-hostpool/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-hostpool/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-hostpool/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-hostpool/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-hostpool/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-hostpool/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-hostpool/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-hostpool/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-hostpool/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-hostpool/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-hostpool/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-hostpool/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-hostpool/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-hostpool/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-hostpool/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-hostpool/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-hostpool/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-hostpool/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-hostpool/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-hostpool/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-hostpool/releases{/id}", + "created_at": "2013-04-19T15:06:04Z", + "updated_at": "2013-04-30T14:17:45Z", + "pushed_at": "2013-04-30T14:17:44Z", + "git_url": "git://github.com/mreiferson/go-hostpool.git", + "ssh_url": "git@github.com:mreiferson/go-hostpool.git", + "clone_url": "https://github.com/mreiferson/go-hostpool.git", + "svn_url": "https://github.com/mreiferson/go-hostpool", + "homepage": null, + "size": 98, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 3488675, + "name": "go-httpclient", + "full_name": "mreiferson/go-httpclient", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-httpclient", + "description": "a Go HTTP client with timeouts", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-httpclient", + "forks_url": "https://api.github.com/repos/mreiferson/go-httpclient/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-httpclient/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-httpclient/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-httpclient/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-httpclient/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-httpclient/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-httpclient/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-httpclient/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-httpclient/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-httpclient/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-httpclient/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-httpclient/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-httpclient/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-httpclient/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-httpclient/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-httpclient/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-httpclient/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-httpclient/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-httpclient/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-httpclient/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-httpclient/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-httpclient/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-httpclient/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-httpclient/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-httpclient/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-httpclient/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-httpclient/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-httpclient/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-httpclient/releases{/id}", + "created_at": "2012-02-19T21:51:42Z", + "updated_at": "2014-07-19T16:41:18Z", + "pushed_at": "2014-04-25T16:53:03Z", + "git_url": "git://github.com/mreiferson/go-httpclient.git", + "ssh_url": "git@github.com:mreiferson/go-httpclient.git", + "clone_url": "https://github.com/mreiferson/go-httpclient.git", + "svn_url": "https://github.com/mreiferson/go-httpclient", + "homepage": "", + "size": 362, + "stargazers_count": 167, + "watchers_count": 167, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": false, + "forks_count": 21, + "mirror_url": null, + "open_issues_count": 0, + "forks": 21, + "open_issues": 0, + "watchers": 167, + "default_branch": "master" + }, + { + "id": 3924124, + "name": "go-install-as", + "full_name": "mreiferson/go-install-as", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-install-as", + "description": "a Go tool to install a package with a specific import path", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-install-as", + "forks_url": "https://api.github.com/repos/mreiferson/go-install-as/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-install-as/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-install-as/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-install-as/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-install-as/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-install-as/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-install-as/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-install-as/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-install-as/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-install-as/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-install-as/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-install-as/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-install-as/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-install-as/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-install-as/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-install-as/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-install-as/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-install-as/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-install-as/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-install-as/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-install-as/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-install-as/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-install-as/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-install-as/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-install-as/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-install-as/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-install-as/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-install-as/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-install-as/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-install-as/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-install-as/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-install-as/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-install-as/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-install-as/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-install-as/releases{/id}", + "created_at": "2012-04-04T00:17:37Z", + "updated_at": "2014-06-29T20:11:46Z", + "pushed_at": "2012-09-24T16:08:50Z", + "git_url": "git://github.com/mreiferson/go-install-as.git", + "ssh_url": "git@github.com:mreiferson/go-install-as.git", + "clone_url": "https://github.com/mreiferson/go-install-as.git", + "svn_url": "https://github.com/mreiferson/go-install-as", + "homepage": "", + "size": 107, + "stargazers_count": 53, + "watchers_count": 53, + "language": "Shell", + "has_issues": true, + "has_downloads": true, + "has_wiki": false, + "forks_count": 2, + "mirror_url": null, + "open_issues_count": 0, + "forks": 2, + "open_issues": 0, + "watchers": 53, + "default_branch": "master" + }, + { + "id": 4744067, + "name": "go-notify", + "full_name": "mreiferson/go-notify", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-notify", + "description": "a Go package to observe notable events in a decoupled fashion", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/go-notify", + "forks_url": "https://api.github.com/repos/mreiferson/go-notify/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-notify/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-notify/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-notify/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-notify/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-notify/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-notify/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-notify/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-notify/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-notify/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-notify/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-notify/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-notify/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-notify/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-notify/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-notify/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-notify/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-notify/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-notify/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-notify/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-notify/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-notify/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-notify/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-notify/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-notify/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-notify/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-notify/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-notify/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-notify/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-notify/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-notify/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-notify/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-notify/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-notify/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-notify/releases{/id}", + "created_at": "2012-06-21T20:30:43Z", + "updated_at": "2013-01-10T18:07:58Z", + "pushed_at": "2012-06-21T20:30:22Z", + "git_url": "git://github.com/mreiferson/go-notify.git", + "ssh_url": "git@github.com:mreiferson/go-notify.git", + "clone_url": "https://github.com/mreiferson/go-notify.git", + "svn_url": "https://github.com/mreiferson/go-notify", + "homepage": null, + "size": 68, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 12449360, + "name": "go-nsq", + "full_name": "mreiferson/go-nsq", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-nsq", + "description": "the official Go package for NSQ", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/go-nsq", + "forks_url": "https://api.github.com/repos/mreiferson/go-nsq/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-nsq/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-nsq/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-nsq/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-nsq/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-nsq/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-nsq/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-nsq/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-nsq/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-nsq/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-nsq/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-nsq/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-nsq/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-nsq/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-nsq/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-nsq/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-nsq/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-nsq/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-nsq/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-nsq/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-nsq/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-nsq/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-nsq/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-nsq/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-nsq/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-nsq/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-nsq/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-nsq/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-nsq/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-nsq/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-nsq/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-nsq/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-nsq/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-nsq/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-nsq/releases{/id}", + "created_at": "2013-08-29T02:07:54Z", + "updated_at": "2014-06-29T13:56:36Z", + "pushed_at": "2014-07-20T16:44:32Z", + "git_url": "git://github.com/mreiferson/go-nsq.git", + "ssh_url": "git@github.com:mreiferson/go-nsq.git", + "clone_url": "https://github.com/mreiferson/go-nsq.git", + "svn_url": "https://github.com/mreiferson/go-nsq", + "homepage": "", + "size": 1783, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": false, + "forks_count": 1, + "mirror_url": null, + "open_issues_count": 0, + "forks": 1, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 16654468, + "name": "go-options", + "full_name": "mreiferson/go-options", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-options", + "description": "a Go package to structure and resolve options", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-options", + "forks_url": "https://api.github.com/repos/mreiferson/go-options/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-options/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-options/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-options/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-options/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-options/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-options/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-options/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-options/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-options/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-options/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-options/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-options/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-options/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-options/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-options/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-options/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-options/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-options/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-options/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-options/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-options/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-options/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-options/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-options/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-options/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-options/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-options/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-options/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-options/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-options/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-options/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-options/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-options/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-options/releases{/id}", + "created_at": "2014-02-08T22:19:33Z", + "updated_at": "2014-02-16T00:39:59Z", + "pushed_at": "2014-02-16T00:39:58Z", + "git_url": "git://github.com/mreiferson/go-options.git", + "ssh_url": "git@github.com:mreiferson/go-options.git", + "clone_url": "https://github.com/mreiferson/go-options.git", + "svn_url": "https://github.com/mreiferson/go-options", + "homepage": null, + "size": 128, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 3924909, + "name": "go-simplejson", + "full_name": "mreiferson/go-simplejson", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-simplejson", + "description": "a Go package to interact with arbitrary JSON", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/go-simplejson", + "forks_url": "https://api.github.com/repos/mreiferson/go-simplejson/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-simplejson/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-simplejson/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-simplejson/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-simplejson/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-simplejson/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-simplejson/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-simplejson/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-simplejson/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-simplejson/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-simplejson/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-simplejson/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-simplejson/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-simplejson/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-simplejson/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-simplejson/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-simplejson/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-simplejson/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-simplejson/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-simplejson/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-simplejson/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-simplejson/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-simplejson/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-simplejson/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-simplejson/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-simplejson/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-simplejson/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-simplejson/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-simplejson/releases{/id}", + "created_at": "2012-04-04T02:36:33Z", + "updated_at": "2014-06-25T01:24:01Z", + "pushed_at": "2014-06-30T15:13:50Z", + "git_url": "git://github.com/mreiferson/go-simplejson.git", + "ssh_url": "git@github.com:mreiferson/go-simplejson.git", + "clone_url": "https://github.com/mreiferson/go-simplejson.git", + "svn_url": "https://github.com/mreiferson/go-simplejson", + "homepage": "", + "size": 210, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": false, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 8614089, + "name": "go-simplelog", + "full_name": "mreiferson/go-simplelog", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-simplelog", + "description": "a simple logging package for Go (inspired by Tornado)", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-simplelog", + "forks_url": "https://api.github.com/repos/mreiferson/go-simplelog/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-simplelog/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-simplelog/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-simplelog/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-simplelog/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-simplelog/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-simplelog/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-simplelog/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-simplelog/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-simplelog/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-simplelog/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-simplelog/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-simplelog/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-simplelog/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-simplelog/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-simplelog/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-simplelog/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-simplelog/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-simplelog/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-simplelog/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-simplelog/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-simplelog/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-simplelog/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-simplelog/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-simplelog/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-simplelog/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-simplelog/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-simplelog/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-simplelog/releases{/id}", + "created_at": "2013-03-06T21:53:48Z", + "updated_at": "2013-10-11T22:49:05Z", + "pushed_at": "2013-03-31T23:20:11Z", + "git_url": "git://github.com/mreiferson/go-simplelog.git", + "ssh_url": "git@github.com:mreiferson/go-simplelog.git", + "clone_url": "https://github.com/mreiferson/go-simplelog.git", + "svn_url": "https://github.com/mreiferson/go-simplelog", + "homepage": null, + "size": 140, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": false, + "forks_count": 1, + "mirror_url": null, + "open_issues_count": 0, + "forks": 1, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 12498288, + "name": "go-snappystream", + "full_name": "mreiferson/go-snappystream", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-snappystream", + "description": "a Go package for framed snappy streams", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-snappystream", + "forks_url": "https://api.github.com/repos/mreiferson/go-snappystream/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-snappystream/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-snappystream/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-snappystream/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-snappystream/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-snappystream/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-snappystream/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-snappystream/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-snappystream/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-snappystream/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-snappystream/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-snappystream/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-snappystream/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-snappystream/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-snappystream/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-snappystream/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-snappystream/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-snappystream/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-snappystream/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-snappystream/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-snappystream/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-snappystream/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-snappystream/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-snappystream/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-snappystream/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-snappystream/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-snappystream/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-snappystream/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-snappystream/releases{/id}", + "created_at": "2013-08-31T00:41:11Z", + "updated_at": "2014-07-20T07:52:45Z", + "pushed_at": "2013-09-17T21:00:14Z", + "git_url": "git://github.com/mreiferson/go-snappystream.git", + "ssh_url": "git@github.com:mreiferson/go-snappystream.git", + "clone_url": "https://github.com/mreiferson/go-snappystream.git", + "svn_url": "https://github.com/mreiferson/go-snappystream", + "homepage": null, + "size": 184, + "stargazers_count": 21, + "watchers_count": 21, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 1, + "mirror_url": null, + "open_issues_count": 0, + "forks": 1, + "open_issues": 0, + "watchers": 21, + "default_branch": "master" + }, + { + "id": 5183238, + "name": "go-stat", + "full_name": "mreiferson/go-stat", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-stat", + "description": "performant instrumentation/profiling for Go", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-stat", + "forks_url": "https://api.github.com/repos/mreiferson/go-stat/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-stat/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-stat/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-stat/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-stat/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-stat/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-stat/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-stat/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-stat/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-stat/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-stat/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-stat/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-stat/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-stat/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-stat/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-stat/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-stat/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-stat/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-stat/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-stat/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-stat/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-stat/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-stat/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-stat/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-stat/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-stat/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-stat/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-stat/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-stat/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-stat/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-stat/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-stat/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-stat/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-stat/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-stat/releases{/id}", + "created_at": "2012-07-25T19:03:42Z", + "updated_at": "2014-01-10T04:39:14Z", + "pushed_at": "2012-07-25T19:04:37Z", + "git_url": "git://github.com/mreiferson/go-stat.git", + "ssh_url": "git@github.com:mreiferson/go-stat.git", + "clone_url": "https://github.com/mreiferson/go-stat.git", + "svn_url": "https://github.com/mreiferson/go-stat", + "homepage": null, + "size": 96, + "stargazers_count": 1, + "watchers_count": 1, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 8662365, + "name": "go-ujson", + "full_name": "mreiferson/go-ujson", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/go-ujson", + "description": "a pure Go port of ultrajson", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/go-ujson", + "forks_url": "https://api.github.com/repos/mreiferson/go-ujson/forks", + "keys_url": "https://api.github.com/repos/mreiferson/go-ujson/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/go-ujson/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/go-ujson/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/go-ujson/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/go-ujson/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/go-ujson/events", + "assignees_url": "https://api.github.com/repos/mreiferson/go-ujson/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/go-ujson/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/go-ujson/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/go-ujson/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/go-ujson/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/go-ujson/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/go-ujson/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/go-ujson/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/go-ujson/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/go-ujson/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/go-ujson/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/go-ujson/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/go-ujson/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/go-ujson/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/go-ujson/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/go-ujson/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/go-ujson/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/go-ujson/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/go-ujson/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/go-ujson/merges", + "archive_url": "https://api.github.com/repos/mreiferson/go-ujson/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/go-ujson/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/go-ujson/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/go-ujson/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/go-ujson/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/go-ujson/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/go-ujson/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/go-ujson/releases{/id}", + "created_at": "2013-03-08T23:57:54Z", + "updated_at": "2014-06-26T01:50:40Z", + "pushed_at": "2013-11-10T19:49:16Z", + "git_url": "git://github.com/mreiferson/go-ujson.git", + "ssh_url": "git@github.com:mreiferson/go-ujson.git", + "clone_url": "https://github.com/mreiferson/go-ujson.git", + "svn_url": "https://github.com/mreiferson/go-ujson", + "homepage": "", + "size": 140, + "stargazers_count": 31, + "watchers_count": 31, + "language": "Go", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 8, + "mirror_url": null, + "open_issues_count": 0, + "forks": 8, + "open_issues": 0, + "watchers": 31, + "default_branch": "master" + }, + { + "id": 12815437, + "name": "godep", + "full_name": "mreiferson/godep", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/godep", + "description": "dependency tool for go", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/godep", + "forks_url": "https://api.github.com/repos/mreiferson/godep/forks", + "keys_url": "https://api.github.com/repos/mreiferson/godep/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/godep/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/godep/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/godep/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/godep/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/godep/events", + "assignees_url": "https://api.github.com/repos/mreiferson/godep/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/godep/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/godep/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/godep/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/godep/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/godep/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/godep/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/godep/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/godep/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/godep/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/godep/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/godep/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/godep/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/godep/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/godep/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/godep/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/godep/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/godep/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/godep/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/godep/merges", + "archive_url": "https://api.github.com/repos/mreiferson/godep/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/godep/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/godep/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/godep/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/godep/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/godep/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/godep/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/godep/releases{/id}", + "created_at": "2013-09-13T17:36:10Z", + "updated_at": "2014-03-21T02:53:20Z", + "pushed_at": "2014-01-05T18:07:02Z", + "git_url": "git://github.com/mreiferson/godep.git", + "ssh_url": "git@github.com:mreiferson/godep.git", + "clone_url": "https://github.com/mreiferson/godep.git", + "svn_url": "https://github.com/mreiferson/godep", + "homepage": "http://godoc.org/github.com/kr/godep", + "size": 196, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Go", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + }, + { + "id": 2862096, + "name": "hajiworld", + "full_name": "mreiferson/hajiworld", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/hajiworld", + "description": "super mario clone (1999)", + "fork": false, + "url": "https://api.github.com/repos/mreiferson/hajiworld", + "forks_url": "https://api.github.com/repos/mreiferson/hajiworld/forks", + "keys_url": "https://api.github.com/repos/mreiferson/hajiworld/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/hajiworld/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/hajiworld/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/hajiworld/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/hajiworld/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/hajiworld/events", + "assignees_url": "https://api.github.com/repos/mreiferson/hajiworld/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/hajiworld/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/hajiworld/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/hajiworld/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/hajiworld/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/hajiworld/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/hajiworld/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/hajiworld/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/hajiworld/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/hajiworld/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/hajiworld/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/hajiworld/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/hajiworld/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/hajiworld/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/hajiworld/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/hajiworld/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/hajiworld/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/hajiworld/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/hajiworld/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/hajiworld/merges", + "archive_url": "https://api.github.com/repos/mreiferson/hajiworld/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/hajiworld/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/hajiworld/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/hajiworld/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/hajiworld/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/hajiworld/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/hajiworld/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/hajiworld/releases{/id}", + "created_at": "2011-11-27T18:05:02Z", + "updated_at": "2014-01-08T14:10:43Z", + "pushed_at": "2011-11-29T02:49:49Z", + "git_url": "git://github.com/mreiferson/hajiworld.git", + "ssh_url": "git@github.com:mreiferson/hajiworld.git", + "clone_url": "https://github.com/mreiferson/hajiworld.git", + "svn_url": "https://github.com/mreiferson/hajiworld", + "homepage": "", + "size": 27872, + "stargazers_count": 1, + "watchers_count": 1, + "language": "C++", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 1, + "default_branch": "master" + }, + { + "id": 14853562, + "name": "homebrew", + "full_name": "mreiferson/homebrew", + "owner": { + "login": "mreiferson", + "id": 187441, + "avatar_url": "https://avatars.githubusercontent.com/u/187441?", + "gravatar_id": "dd56a8e1de66aeedb987397511f830e7", + "url": "https://api.github.com/users/mreiferson", + "html_url": "https://github.com/mreiferson", + "followers_url": "https://api.github.com/users/mreiferson/followers", + "following_url": "https://api.github.com/users/mreiferson/following{/other_user}", + "gists_url": "https://api.github.com/users/mreiferson/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mreiferson/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mreiferson/subscriptions", + "organizations_url": "https://api.github.com/users/mreiferson/orgs", + "repos_url": "https://api.github.com/users/mreiferson/repos", + "events_url": "https://api.github.com/users/mreiferson/events{/privacy}", + "received_events_url": "https://api.github.com/users/mreiferson/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/mreiferson/homebrew", + "description": "The missing package manager for OS X.", + "fork": true, + "url": "https://api.github.com/repos/mreiferson/homebrew", + "forks_url": "https://api.github.com/repos/mreiferson/homebrew/forks", + "keys_url": "https://api.github.com/repos/mreiferson/homebrew/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/mreiferson/homebrew/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/mreiferson/homebrew/teams", + "hooks_url": "https://api.github.com/repos/mreiferson/homebrew/hooks", + "issue_events_url": "https://api.github.com/repos/mreiferson/homebrew/issues/events{/number}", + "events_url": "https://api.github.com/repos/mreiferson/homebrew/events", + "assignees_url": "https://api.github.com/repos/mreiferson/homebrew/assignees{/user}", + "branches_url": "https://api.github.com/repos/mreiferson/homebrew/branches{/branch}", + "tags_url": "https://api.github.com/repos/mreiferson/homebrew/tags", + "blobs_url": "https://api.github.com/repos/mreiferson/homebrew/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/mreiferson/homebrew/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/mreiferson/homebrew/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/mreiferson/homebrew/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/mreiferson/homebrew/statuses/{sha}", + "languages_url": "https://api.github.com/repos/mreiferson/homebrew/languages", + "stargazers_url": "https://api.github.com/repos/mreiferson/homebrew/stargazers", + "contributors_url": "https://api.github.com/repos/mreiferson/homebrew/contributors", + "subscribers_url": "https://api.github.com/repos/mreiferson/homebrew/subscribers", + "subscription_url": "https://api.github.com/repos/mreiferson/homebrew/subscription", + "commits_url": "https://api.github.com/repos/mreiferson/homebrew/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/mreiferson/homebrew/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/mreiferson/homebrew/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/mreiferson/homebrew/issues/comments/{number}", + "contents_url": "https://api.github.com/repos/mreiferson/homebrew/contents/{+path}", + "compare_url": "https://api.github.com/repos/mreiferson/homebrew/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/mreiferson/homebrew/merges", + "archive_url": "https://api.github.com/repos/mreiferson/homebrew/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/mreiferson/homebrew/downloads", + "issues_url": "https://api.github.com/repos/mreiferson/homebrew/issues{/number}", + "pulls_url": "https://api.github.com/repos/mreiferson/homebrew/pulls{/number}", + "milestones_url": "https://api.github.com/repos/mreiferson/homebrew/milestones{/number}", + "notifications_url": "https://api.github.com/repos/mreiferson/homebrew/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/mreiferson/homebrew/labels{/name}", + "releases_url": "https://api.github.com/repos/mreiferson/homebrew/releases{/id}", + "created_at": "2013-12-02T05:20:40Z", + "updated_at": "2014-02-17T17:19:19Z", + "pushed_at": "2014-02-17T17:06:03Z", + "git_url": "git://github.com/mreiferson/homebrew.git", + "ssh_url": "git@github.com:mreiferson/homebrew.git", + "clone_url": "https://github.com/mreiferson/homebrew.git", + "svn_url": "https://github.com/mreiferson/homebrew", + "homepage": "http://brew.sh", + "size": 29725, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": false, + "has_downloads": false, + "has_wiki": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + } +] +`) diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader.go new file mode 100644 index 0000000..c7f8639 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader.go @@ -0,0 +1,321 @@ +package snappystream + +import ( + "bytes" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + + "code.google.com/p/snappy-go/snappy" +) + +// errMssingStreamID is returned from a reader when the source stream does not +// begin with a stream identifier block (4.1 Stream identifier). Its occurance +// signifies that the source byte stream is not snappy framed. +var errMissingStreamID = fmt.Errorf("missing stream identifier") + +type reader struct { + reader io.Reader + + err error + + seenStreamID bool + verifyChecksum bool + + buf bytes.Buffer + hdr []byte + src []byte + dst []byte +} + +// NewReader returns an io.Reader interface to the snappy framed stream format. +// +// It transparently handles reading the stream identifier (but does not proxy this +// to the caller), decompresses blocks, and (optionally) validates checksums. +// +// Internally, three buffers are maintained. The first two are for reading +// off the wrapped io.Reader and for holding the decompressed block (both are grown +// automatically and re-used and will never exceed the largest block size, 65536). The +// last buffer contains the *unread* decompressed bytes (and can grow indefinitely). +// +// The second param determines whether or not the reader will verify block +// checksums and can be enabled/disabled with the constants VerifyChecksum and SkipVerifyChecksum +// +// For each Read, the returned length will be up to the lesser of len(b) or 65536 +// decompressed bytes, regardless of the length of *compressed* bytes read +// from the wrapped io.Reader. +func NewReader(r io.Reader, verifyChecksum bool) io.Reader { + return &reader{ + reader: r, + + verifyChecksum: verifyChecksum, + + hdr: make([]byte, 4), + src: make([]byte, 4096), + dst: make([]byte, 4096), + } +} + +// WriteTo implements the io.WriterTo interface used by io.Copy. It writes +// decoded data from the underlying reader to w. WriteTo returns the number of +// bytes written along with any error encountered. +func (r *reader) WriteTo(w io.Writer) (int64, error) { + if r.err != nil { + return 0, r.err + } + + n, err := r.buf.WriteTo(w) + if err != nil { + // r.err doesn't need to be set because a write error occurred and the + // stream hasn't been corrupted. + return n, err + } + + // pass a bufferFallbackWriter to nextFrame so that write errors may be + // recovered from, allowing the unwritten stream to be read successfully. + wfallback := &bufferFallbackWriter{ + w: w, + buf: &r.buf, + } + for { + var m int + m, err = r.nextFrame(wfallback) + if wfallback.writerErr != nil && err == nil { + // a partial write was made before an error occurred and not all m + // bytes were writen to w. but decoded bytes were successfully + // buffered and reading can resume later. + n += wfallback.n + return n, wfallback.writerErr + } + n += int64(m) + if err == io.EOF { + return n, nil + } + if err != nil { + r.err = err + return n, err + } + } + panic("unreachable") +} + +// bufferFallbackWriter writes to an underlying io.Writer until an error +// occurs. If a error occurs in the underlying io.Writer the value is saved +// for later inspection while the bufferFallbackWriter silently starts +// buffering all data written to it. From the caller's perspective +// bufferFallbackWriter has the same Write behavior has a bytes.Buffer. +// +// bufferFallbackWriter is useful for the reader.WriteTo method because it +// allows internal decoding routines to avoid interruption (and subsequent +// stream corruption) due to writing errors. +type bufferFallbackWriter struct { + w io.Writer + buf *bytes.Buffer + n int64 // number of bytes successfully written to w + writerErr error // any error that ocurred writing to w +} + +// Write attempts to write b to the underlying io.Writer. If the underlying +// writer fails or has failed previously unwritten bytes are buffered +// internally. Write never returns an error but may panic with +// bytes.ErrTooLarge if the buffer grows too large. +func (w *bufferFallbackWriter) Write(b []byte) (int, error) { + if w.writerErr != nil { + return w.buf.Write(b) + } + n, err := w.w.Write(b) + w.n += int64(n) + if err != nil { + // begin buffering input. bytes.Buffer does not return errors and so we + // do not need complex error handling here. + w.writerErr = err + w.Write(b[n:]) + return len(b), nil + } + return n, nil +} + +func (r *reader) read(b []byte) (int, error) { + n, err := r.buf.Read(b) + r.err = err + return n, err +} + +func (r *reader) Read(b []byte) (int, error) { + if r.err != nil { + return 0, r.err + } + + if r.buf.Len() < len(b) { + _, r.err = r.nextFrame(&r.buf) + if r.err == io.EOF { + // fill b with any remaining bytes in the buffer. + return r.read(b) + } + if r.err != nil { + return 0, r.err + } + } + + return r.read(b) +} + +func (r *reader) nextFrame(w io.Writer) (int, error) { + for { + // read the 4-byte snappy frame header + _, err := io.ReadFull(r.reader, r.hdr) + if err != nil { + return 0, err + } + + // a stream identifier may appear anywhere and contains no information. + // it must appear at the beginning of the stream. when found, validate + // it and continue to the next block. + if r.hdr[0] == blockStreamIdentifier { + err := r.readStreamID() + if err != nil { + return 0, err + } + r.seenStreamID = true + continue + } + if !r.seenStreamID { + return 0, errMissingStreamID + } + + switch typ := r.hdr[0]; { + case typ == blockCompressed || typ == blockUncompressed: + return r.decodeBlock(w) + case typ == blockPadding || (0x80 <= typ && typ <= 0xfd): + // skip blocks whose data must not be inspected (4.4 Padding, and 4.6 + // Reserved skippable chunks). + err := r.discardBlock() + if err != nil { + return 0, err + } + continue + default: + // typ must be unskippable range 0x02-0x7f. Read the block in full + // and return an error (4.5 Reserved unskippable chunks). + err = r.discardBlock() + if err != nil { + return 0, err + } + return 0, fmt.Errorf("unrecognized unskippable frame %#x", r.hdr[0]) + } + } + panic("unreachable") +} + +// decodeDataBlock assumes r.hdr[0] to be either blockCompressed or +// blockUncompressed. +func (r *reader) decodeBlock(w io.Writer) (int, error) { + // read compressed block data and determine if uncompressed data is too + // large. + buf, err := r.readBlock() + if err != nil { + return 0, err + } + declen := len(buf[4:]) + if r.hdr[0] == blockCompressed { + declen, err = snappy.DecodedLen(buf[4:]) + if err != nil { + return 0, err + } + } + if declen > MaxBlockSize { + return 0, fmt.Errorf("decoded block data too large %d > %d", declen, MaxBlockSize) + } + + // decode data and verify its integrity using the little-endian crc32 + // preceding encoded data + crc32le, blockdata := buf[:4], buf[4:] + if r.hdr[0] == blockCompressed { + r.dst, err = snappy.Decode(r.dst, blockdata) + if err != nil { + return 0, err + } + blockdata = r.dst + } + if r.verifyChecksum { + checksum := unmaskChecksum(uint32(crc32le[0]) | uint32(crc32le[1])<<8 | uint32(crc32le[2])<<16 | uint32(crc32le[3])<<24) + actualChecksum := crc32.Checksum(blockdata, crcTable) + if checksum != actualChecksum { + return 0, fmt.Errorf("checksum does not match %x != %x", checksum, actualChecksum) + } + } + return w.Write(blockdata) +} + +func (r *reader) readStreamID() error { + // the length of the block is fixed so don't decode it from the header. + if !bytes.Equal(r.hdr, streamID[:4]) { + return fmt.Errorf("invalid stream identifier length") + } + + // read the identifier block data "sNaPpY" + block := r.src[:6] + _, err := noeof(io.ReadFull(r.reader, block)) + if err != nil { + return err + } + if !bytes.Equal(block, streamID[4:]) { + return fmt.Errorf("invalid stream identifier block") + } + return nil +} + +func (r *reader) discardBlock() error { + length := uint64(decodeLength(r.hdr[1:])) + _, err := noeof64(io.CopyN(ioutil.Discard, r.reader, int64(length))) + return err +} + +func (r *reader) readBlock() ([]byte, error) { + // check bounds on encoded length (+4 for checksum) + length := decodeLength(r.hdr[1:]) + if length > (maxEncodedBlockSize + 4) { + return nil, fmt.Errorf("encoded block data too large %d > %d", length, (maxEncodedBlockSize + 4)) + } + + if int(length) > len(r.src) { + r.src = make([]byte, length) + } + + buf := r.src[:length] + _, err := noeof(io.ReadFull(r.reader, buf)) + if err != nil { + return nil, err + } + + return buf, nil +} + +// decodeLength decodes a 24-bit (3-byte) little-endian length from b. +func decodeLength(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 +} + +func unmaskChecksum(c uint32) uint32 { + x := c - 0xa282ead8 + return ((x >> 17) | (x << 15)) +} + +// noeof is used after reads in situations where EOF signifies invalid +// formatting or corruption. +func noeof(n int, err error) (int, error) { + if err == io.EOF { + return n, io.ErrUnexpectedEOF + } + return n, err +} + +// noeof64 is used after long reads (e.g. io.Copy) in situations where io.EOF +// signifies invalid formatting or corruption. +func noeof64(n int64, err error) (int64, error) { + if err == io.EOF { + return n, io.ErrUnexpectedEOF + } + return n, err +} diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader_test.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader_test.go new file mode 100644 index 0000000..50f6985 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/reader_test.go @@ -0,0 +1,650 @@ +package snappystream + +import ( + "bytes" + "crypto/rand" + "fmt" + "io" + "io/ioutil" + "strings" + "testing" + + "code.google.com/p/snappy-go/snappy" +) + +// This test checks that padding and reserved skippable blocks are ignored by +// the reader. +func TestReader_skippable(t *testing.T) { + var buf bytes.Buffer + // write some blocks with injected padding/skippable blocks + w := NewWriter(&buf) + write := func(p []byte) (int, error) { + return w.Write(p) + } + writepad := func(b byte, n int) (int, error) { + return buf.Write(opaqueChunk(b, n)) + } + _, err := write([]byte("hello")) + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = writepad(0xfe, 100) // normal padding + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = write([]byte(" ")) + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = writepad(0xa0, 100) // reserved skippable block + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = writepad(0xfe, MaxBlockSize) // normal padding + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = write([]byte("padding")) + if err != nil { + t.Fatalf("write error: %v", err) + } + + p, err := ioutil.ReadAll(NewReader(&buf, true)) + if err != nil { + t.Fatalf("read error: %v", err) + } + if string(p) != "hello padding" { + t.Fatalf("read: unexpected content %q", string(p)) + } +} + +// This test checks that reserved unskippable blocks are cause decoder errors. +func TestReader_unskippable(t *testing.T) { + var buf bytes.Buffer + // write some blocks with injected padding/skippable blocks + w := NewWriter(&buf) + write := func(p []byte) (int, error) { + return w.Write(p) + } + writepad := func(b byte, n int) (int, error) { + return buf.Write(opaqueChunk(b, n)) + } + _, err := write([]byte("unskippable")) + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = writepad(0x50, 100) // unskippable reserved block + if err != nil { + t.Fatalf("write error: %v", err) + } + _, err = write([]byte(" blocks")) + if err != nil { + t.Fatalf("write error: %v", err) + } + + _, err = ioutil.ReadAll(NewReader(&buf, true)) + if err == nil { + t.Fatalf("read success") + } +} + +func TestReaderStreamID(t *testing.T) { + data := []byte("a snappy-framed data stream") + var buf bytes.Buffer + w := NewWriter(&buf) + _, err := w.Write(data) + if err != nil { + t.Fatal(err) + } + + stream := buf.Bytes() + + // sanity check: the stream can be decoded and starts with streamID + r := NewReader(bytes.NewReader(stream), true) + _, err = ioutil.ReadAll(r) + if err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.HasPrefix(stream, streamID) { + t.Fatal("missing stream id") + } + + // streamNoID is valid except for a missing the streamID block + streamNoID := bytes.TrimPrefix(stream, streamID) + r = NewReader(bytes.NewReader(streamNoID), true) + n, err := r.Read(make([]byte, 1)) + if err == nil { + t.Fatalf("read: expected an error reading input missing a stream identifier block") + } + if n != 0 { + t.Fatalf("read: read non-zero number of bytes %d", n) + } + n, err = r.Read(make([]byte, 1)) + if err == nil { + t.Fatalf("read: successful read after missing stream id error") + } + if n != 0 { + t.Fatalf("read: read non-zero number of bytes %d after missing stream id error", n) + } +} + +// This test validates the reader successfully decods a padding of maximal +// size, 2^24 - 1. +func TestReader_maxPad(t *testing.T) { + buf := bytes.NewReader(bytes.Join([][]byte{ + streamID, + compressedChunk(t, []byte("a maximal padding chunk")), + opaqueChunk(0xfe, (1<<24)-1), // normal padding + compressedChunk(t, []byte(" is decoded successfully")), + }, nil)) + r := NewReader(buf, true) + p, err := ioutil.ReadAll(r) + if err != nil { + t.Fatalf("read error: %v", err) + } + if string(p) != "a maximal padding chunk is decoded successfully" { + t.Fatalf("read: unexpected content %q", string(p)) + } +} + +// This test validates the reader successfully decodes a skippable chunk of +// maximal size, 2^24 - 1. +func TestReader_maxSkippable(t *testing.T) { + buf := bytes.NewReader(bytes.Join([][]byte{ + streamID, + compressedChunk(t, []byte("a maximal skippable chunk")), + opaqueChunk(0xce, (1<<24)-1), // reserved skippable chunk + compressedChunk(t, []byte(" is decoded successfully")), + }, nil)) + r := NewReader(buf, true) + p, err := ioutil.ReadAll(r) + if err != nil { + t.Fatalf("read error: %v", err) + } + if string(p) != "a maximal skippable chunk is decoded successfully" { + t.Fatalf("read: unexpected content %q", string(p)) + } +} + +// TestReader_maxBlock validates bounds checking on encoded and decoded data +// (4.2 Compressed Data). +func TestReader_maxBlock(t *testing.T) { + // decompressing a block with compressed length greater than MaxBlockSize + // should succeed. + buf := bytes.NewReader(bytes.Join([][]byte{ + streamID, + compressedChunkGreaterN(t, MaxBlockSize), + }, nil)) + r := NewReader(buf, true) + b, err := ioutil.ReadAll(r) + if err != nil { + t.Fatal(err) + } + if len(b) != MaxBlockSize { + t.Fatalf("bad read (%d bytes)", len(b)) + } + + // decompressing should fail if the block with decompressed length greater + // than MaxBlockSize. + buf = bytes.NewReader(bytes.Join([][]byte{ + streamID, + compressedChunk(t, make([]byte, MaxBlockSize+1)), + }, nil)) + r = NewReader(buf, true) + b, err = ioutil.ReadAll(r) + if err == nil { + t.Fatal("unexpected success") + } + if len(b) > 0 { + t.Fatalf("unexpected read %q", b) + } +} + +// This test validates the reader's behavior encountering unskippable chunks of +// maximal size, 2^24 - 1. The desired error to in this case is one reporting +// an unskippable chunk, not a length error. +func TestReader_maxUnskippable(t *testing.T) { + // the first block should be decoded successfully. + prefix := "a maximal unskippable chunk" + buf := bytes.NewReader(bytes.Join([][]byte{ + streamID, + compressedChunk(t, []byte(prefix)), + opaqueChunk(0x03, (1<<24)-1), // low end of the unskippable range + compressedChunk(t, []byte(" failure must be reported as such")), + }, nil)) + p := make([]byte, len(prefix)) + r := NewReader(buf, true) + n, err := r.Read(p) + if err != nil { + t.Fatalf("read error: %v", err) + } + if n != len(p) { + t.Fatalf("read: short read %d", n) + } + if string(p) != prefix { + t.Fatalf("read: bad value %q", p) + } + + n, err = r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if n > 0 { + t.Fatalf("read: read %d more bytes than expected", n) + } + if !strings.Contains(err.Error(), "unskippable") { + t.Fatalf("read error: %v", err) + } +} + +// This test validates errors returned when data blocks exceed size limits. +func TestReader_blockTooLarge(t *testing.T) { + // the compressed chunk size is within the allowed encoding size + // (maxEncodedBlockSize). but the uncompressed data is larger than allowed. + badstream := bytes.Join([][]byte{ + streamID, + compressedChunk(t, make([]byte, (1<<24)-5)), + }, nil) + r := NewReader(bytes.NewBuffer(badstream), true) + p := make([]byte, 1) + n, err := r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } + + // the compressed chunk size is within the allowed encoding size + // (maxEncodedBlockSize). but the uncompressed data is larger than allowed. + badstream = bytes.Join([][]byte{ + streamID, + uncompressedChunk(t, make([]byte, (1<<24)-5)), + }, nil) + r = NewReader(bytes.NewBuffer(badstream), true) + p = make([]byte, 1) + n, err = r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } +} + +// This test validates the reader's handling of corrupt chunks. +func TestReader_corruption(t *testing.T) { + // corruptID is a corrupt stream identifier + corruptID := append([]byte(nil), streamID...) + corruptID = bytes.Replace(streamID, []byte("p"), []byte("P"), -1) // corrupt "sNaPpY" data + badstream := corruptID + + r := NewReader(bytes.NewBuffer(badstream), true) + p := make([]byte, 1) + n, err := r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if err == io.EOF { + t.Fatalf("read: unexpected eof") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } + + corruptID = append([]byte(nil), streamID...) // corrupt the length + corruptID[1] = 0x00 + badstream = corruptID + + r = NewReader(bytes.NewBuffer(badstream), true) + p = make([]byte, 1) + n, err = r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if err == io.EOF { + t.Fatalf("read: unexpected eof") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } + + // chunk is a valid compressed block + chunk := compressedChunk(t, []byte("a data block")) + + // corrupt is a corrupt chunk + corrupt := append([]byte(nil), chunk...) + copy(corrupt[8:], make([]byte, 10)) // corrupt snappy-encoded data + badstream = bytes.Join([][]byte{ + streamID, + corrupt, + }, nil) + + r = NewReader(bytes.NewBuffer(badstream), true) + p = make([]byte, 1) + n, err = r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if err == io.EOF { + t.Fatalf("read: unexpected eof") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } + + corrupt = append([]byte(nil), chunk...) + copy(corrupt[4:8], make([]byte, 4)) // crc checksum failure + badstream = bytes.Join([][]byte{ + streamID, + corrupt, + }, nil) + + r = NewReader(bytes.NewBuffer(badstream), true) + p = make([]byte, 1) + n, err = r.Read(p) + if err == nil { + t.Fatalf("read: expected error") + } + if err == io.EOF { + t.Fatalf("read: unexpected eof") + } + if n != 0 { + t.Fatalf("read: read data from the stream") + } +} + +// This test ensures that reader returns io.ErrUnexpectedEOF at the appropriate +// times. io.EOF must be reserved for the case when all data has been +// successfully decoded. +func TestReader_unexpectedEOF(t *testing.T) { + var decodeBuffer [64 << 10]byte + + for _, test := range [][]byte{ + // truncated streamIDs + streamID[:4], + streamID[:len(streamID)-1], + + // truncated data blocks + bytes.Join([][]byte{ + streamID, + compressedChunk(t, bytes.Repeat([]byte("abc"), 100))[:2], + }, nil), + bytes.Join([][]byte{ + streamID, + compressedChunk(t, bytes.Repeat([]byte("abc"), 100))[:7], + }, nil), + + // truncated padding + bytes.Join([][]byte{ + streamID, + opaqueChunk(0xfe, 100)[:1], + }, nil), + bytes.Join([][]byte{ + streamID, + opaqueChunk(0xfe, 100)[:8], + }, nil), + + // truncated skippable chunk + bytes.Join([][]byte{ + streamID, + opaqueChunk(0xcf, 100)[:3], + }, nil), + bytes.Join([][]byte{ + streamID, + opaqueChunk(0xcf, 100)[:7], + }, nil), + + // truncated unskippable chunk + bytes.Join([][]byte{ + streamID, + opaqueChunk(0x03, 100)[:3], + }, nil), + bytes.Join([][]byte{ + streamID, + opaqueChunk(0x03, 100)[:5], + }, nil), + } { + r := NewReader(bytes.NewReader(test), true) + n, err := r.Read(decodeBuffer[:]) + if err == nil { + t.Errorf("read bad streamID: expected error") + } + if err != io.ErrUnexpectedEOF { + t.Errorf("read bad streamID: %v", err) + } + if n != 0 { + t.Errorf("read bad streamID: expected read length %d", n) + } + } +} + +var errNotEnoughEntropy = fmt.Errorf("inadequate entropy in PRNG") + +// compressedChunkGreaterN like compressedChunk produces a single, compressed, +// snappy-framed block. The returned block will have decoded length at most n +// and encoded length greater than n. +func compressedChunkGreaterN(t *testing.T, n int) []byte { + decoded := make([]byte, n) + var numTries int + var encoded []byte + for len(encoded) <= n && numTries < 3 { + numTries++ + nrd, err := io.ReadFull(rand.Reader, decoded) + if err != nil { + t.Errorf("crypto/rand: %v", err) + return nil + } + if nrd != n { + t.Errorf("crypto/rand: bad read (%d bytes)", nrd) + return nil + } + encoded, err = snappy.Encode(encoded[:cap(encoded)], decoded) + if err != nil { + t.Errorf("snappy: %v", err) + return nil + } + } + if len(encoded) <= n { + t.Error(errNotEnoughEntropy) + return nil + } + + return compressedChunk(t, decoded) +} + +// compressedChunk encodes b returning a single, compressed, snappy-framed +// block. compressedChunk can encode source data larger than allowed in the +// specification. +func compressedChunk(t *testing.T, src []byte) []byte { + encoded, err := snappy.Encode(nil, src) + if err != nil { + t.Errorf("snappy: %v", err) + return nil + } + + if len(encoded) > (1<<24)-5 { // account for the 4-byte checksum + t.Errorf("block data too large %d", len(src)) + return nil + } + + chunk := make([]byte, len(encoded)+8) + writeHeader(chunk[:8], blockCompressed, encoded, src) + copy(chunk[8:], encoded) + return chunk +} + +// uncompressedChunk encodes b returning a single, uncompressed, snappy-framed +// block. uncompressedChunk can encode chunks larger than allowed by the +// specification. +func uncompressedChunk(t *testing.T, src []byte) []byte { + if len(src) > (1<<24)-5 { // account for the 4-byte checksum + t.Errorf("block data too large %d", len(src)) + return nil + } + + chunk := make([]byte, len(src)+8) + writeHeader(chunk[:8], blockUncompressed, src, src) + copy(chunk[8:], src) + return chunk +} + +// opaqueChunk returns an opaque b chunk (e.g. padding 0xfe) with length n +// (total length, n+4 bytes). practically useless but good enough for testing. +// the first 4-bytes of data are random to ensure checksums are not being +// verified. +func opaqueChunk(b byte, n int) []byte { + if b == 0 { + b = 0xfe + } + + length := uint32(n) + lengthle := []byte{byte(length), byte(length >> 8), byte(length >> 16)} + checksum := make([]byte, 4) + _, err := rand.Read(checksum) + if err != nil { + panic(err) + } + padbytes := make([]byte, n-4) // let this panic if n < 4 + _, err = rand.Read(padbytes) + if err != nil { + panic(err) + } + + var h []byte + h = append(h, b) + h = append(h, lengthle...) + h = append(h, checksum...) + h = append(h, padbytes...) + return h +} + +func TestReaderWriteTo(t *testing.T) { + var encbuf bytes.Buffer + var decbuf bytes.Buffer + msg := "hello copy interface" + + w := NewWriter(&encbuf) + n, err := io.WriteString(w, msg) + if err != nil { + t.Fatalf("encode: %v", err) + } + if n != len(msg) { + t.Fatalf("encode: %v", err) + } + + r := NewReader(&encbuf, true) + n64, err := r.(*reader).WriteTo(&decbuf) + if err != nil { + t.Fatalf("decode: %v", err) + } + if n64 != int64(len(msg)) { + t.Fatalf("decode: decoded %d bytes %q", n64, decbuf.Bytes()) + } + + decmsg := decbuf.String() + if decmsg != msg { + t.Fatalf("decode: %q", decmsg) + } +} + +func TestReaderWriteToPreviousError(t *testing.T) { + // construct an io.Reader that returns an error on the first read and a + // valid snappy-framed stream on subsequent reads. + var stream io.Reader + stream = encodedString("hello") + stream = readErrorFirst(stream, fmt.Errorf("one time error")) + stream = NewReader(stream, true) + + var buf bytes.Buffer + + // attempt the first read from the stream. + n, err := stream.(*reader).WriteTo(&buf) + if err == nil { + t.Fatalf("error expected") + } + if n != 0 { + t.Fatalf("bytes written to buffer: %q", buf.Bytes()) + } + + // attempt a second read from the stream. + n, err = stream.(*reader).WriteTo(&buf) + if err == nil { + t.Fatalf("error expected") + } + if n != 0 { + t.Fatalf("bytes written to buffer: %q", buf.Bytes()) + } +} + +// readerErrorFirst is an io.Reader that returns an error on the first read. +// readerErrorFirst is used to test that a reader does not attempt to read +// after a read error occurs. +type readerErrorFirst struct { + r io.Reader + err error + count int +} + +func readErrorFirst(r io.Reader, err error) io.Reader { + return &readerErrorFirst{ + r: r, + err: err, + } +} + +func (r *readerErrorFirst) Read(b []byte) (int, error) { + r.count++ + if r.count == 1 { + return 0, r.err + } + return r.r.Read(b) +} + +func TestReaderWriteToWriteError(t *testing.T) { + origmsg := "hello" + stream := NewReader(encodedString(origmsg), true) + + // attempt to write the stream to an io.Writer that will not accept input. + n, err := stream.(*reader).WriteTo(unwritable(fmt.Errorf("cannot write to this writer"))) + if err == nil { + t.Fatalf("error expected") + } + if n != 0 { + t.Fatalf("reported %d written to an unwritable writer", n) + } + + // the decoded message can still be read successfully because the encoded + // stream was not corrupt/broken. + var buf bytes.Buffer + n, err = stream.(*reader).WriteTo(&buf) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if n != int64(len(origmsg)) { + t.Errorf("read %d bytes", n) + } + if buf.String() != origmsg { + t.Errorf("read %q", buf) + } +} + +// writerUnwritable is an io.Writer that always returns an error. +type writerUnwritable struct { + err error +} + +func (w *writerUnwritable) Write([]byte) (int, error) { + return 0, w.err +} + +func unwritable(err error) io.Writer { + return &writerUnwritable{err} +} + +func encodedString(s string) io.Reader { + var buf bytes.Buffer + w := NewWriter(&buf) + io.WriteString(w, s) + return &buf +} diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/readwrite_test.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/readwrite_test.go new file mode 100644 index 0000000..d775d92 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/readwrite_test.go @@ -0,0 +1,425 @@ +package snappystream + +import ( + "bytes" + "crypto/rand" + "io" + "io/ioutil" + "testing" +) + +const TestFileSize = 10 << 20 // 10MB + +// dummyBytesReader returns an io.Reader that avoids buffering optimizations +// in io.Copy. This can be considered a 'worst-case' io.Reader as far as writer +// frame alignment goes. +// +// Note: io.Copy uses a 32KB buffer internally as of Go 1.3, but that isn't +// part of its public API (undocumented). +func dummyBytesReader(p []byte) io.Reader { + return ioutil.NopCloser(bytes.NewReader(p)) +} + +func testWriteThenRead(t *testing.T, name string, bs []byte) { + var buf bytes.Buffer + w := NewWriter(&buf) + n, err := io.Copy(w, dummyBytesReader(bs)) + if err != nil { + t.Errorf("write %v: %v", name, err) + return + } + if n != int64(len(bs)) { + t.Errorf("write %v: wrote %d bytes (!= %d)", name, n, len(bs)) + return + } + + enclen := buf.Len() + + r := NewReader(&buf, true) + gotbs, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("read %v: %v", name, err) + return + } + n = int64(len(gotbs)) + if n != int64(len(bs)) { + t.Errorf("read %v: read %d bytes (!= %d)", name, n, len(bs)) + return + } + + if !bytes.Equal(gotbs, bs) { + t.Errorf("%v: unequal decompressed content", name) + return + } + + c := float64(len(bs)) / float64(enclen) + t.Logf("%v compression ratio %.03g (%d byte reduction)", name, c, len(bs)-enclen) +} + +func testBufferedWriteThenRead(t *testing.T, name string, bs []byte) { + var buf bytes.Buffer + w := NewBufferedWriter(&buf) + n, err := io.Copy(w, dummyBytesReader(bs)) + if err != nil { + t.Errorf("write %v: %v", name, err) + return + } + if n != int64(len(bs)) { + t.Errorf("write %v: wrote %d bytes (!= %d)", name, n, len(bs)) + return + } + err = w.Close() + if err != nil { + t.Errorf("close %v: %v", name, err) + return + } + + enclen := buf.Len() + + r := NewReader(&buf, true) + gotbs, err := ioutil.ReadAll(r) + if err != nil { + t.Errorf("read %v: %v", name, err) + return + } + n = int64(len(gotbs)) + if n != int64(len(bs)) { + t.Errorf("read %v: read %d bytes (!= %d)", name, n, len(bs)) + return + } + + if !bytes.Equal(gotbs, bs) { + t.Errorf("%v: unequal decompressed content", name) + return + } + + c := float64(len(bs)) / float64(enclen) + t.Logf("%v compression ratio %.03g (%d byte reduction)", name, c, len(bs)-enclen) +} + +func TestWriterReader(t *testing.T) { + testWriteThenRead(t, "simple", []byte("test")) + testWriteThenRead(t, "manpage", testDataMan) + testWriteThenRead(t, "json", testDataJSON) + + p := make([]byte, TestFileSize) + testWriteThenRead(t, "constant", p) + + _, err := rand.Read(p) + if err != nil { + t.Fatal(err) + } + testWriteThenRead(t, "random", p) + +} + +func TestBufferedWriterReader(t *testing.T) { + testBufferedWriteThenRead(t, "simple", []byte("test")) + testBufferedWriteThenRead(t, "manpage", testDataMan) + testBufferedWriteThenRead(t, "json", testDataJSON) + + p := make([]byte, TestFileSize) + testBufferedWriteThenRead(t, "constant", p) + + _, err := rand.Read(p) + if err != nil { + t.Fatal(err) + } + testBufferedWriteThenRead(t, "random", p) + +} + +func TestWriterChunk(t *testing.T) { + var buf bytes.Buffer + + in := make([]byte, 128000) + + w := NewWriter(&buf) + r := NewReader(&buf, VerifyChecksum) + + n, err := w.Write(in) + if err != nil { + t.Fatalf(err.Error()) + } + if n != len(in) { + t.Fatalf("wrote wrong amount %d != %d", n, len(in)) + } + + out := make([]byte, len(in)) + n, err = io.ReadFull(r, out) + if err != nil { + t.Fatal(err) + } + if n != len(in) { + t.Fatalf("read wrong amount %d != %d", n, len(in)) + } + + if !bytes.Equal(out, in) { + t.Fatalf("bytes not equal %v != %v", out, in) + } +} + +func BenchmarkWriterManpage(b *testing.B) { + benchmarkWriterBytes(b, testDataMan) +} +func BenchmarkBufferedWriterManpage(b *testing.B) { + benchmarkBufferedWriterBytes(b, testDataMan) +} +func BenchmarkBufferedWriterManpageNoCopy(b *testing.B) { + benchmarkBufferedWriterBytesNoCopy(b, testDataMan) +} + +func BenchmarkWriterJSON(b *testing.B) { + benchmarkWriterBytes(b, testDataJSON) +} +func BenchmarkBufferedWriterJSON(b *testing.B) { + benchmarkBufferedWriterBytes(b, testDataJSON) +} +func BenchmarkBufferedWriterJSONNoCopy(b *testing.B) { + benchmarkBufferedWriterBytesNoCopy(b, testDataJSON) +} + +// BenchmarkWriterRandom tests performance encoding effectively uncompressable +// data. +func BenchmarkWriterRandom(b *testing.B) { + benchmarkWriterBytes(b, randBytes(b, TestFileSize)) +} +func BenchmarkBufferedWriterRandom(b *testing.B) { + benchmarkBufferedWriterBytes(b, randBytes(b, TestFileSize)) +} +func BenchmarkBufferedWriterRandomNoCopy(b *testing.B) { + benchmarkBufferedWriterBytesNoCopy(b, randBytes(b, TestFileSize)) +} + +// BenchmarkWriterConstant tests performance encoding maximally compressible +// data. +func BenchmarkWriterConstant(b *testing.B) { + benchmarkWriterBytes(b, make([]byte, TestFileSize)) +} +func BenchmarkBufferedWriterConstant(b *testing.B) { + benchmarkBufferedWriterBytes(b, make([]byte, TestFileSize)) +} +func BenchmarkBufferedWriterConstantNoCopy(b *testing.B) { + benchmarkBufferedWriterBytesNoCopy(b, make([]byte, TestFileSize)) +} + +func benchmarkWriterBytes(b *testing.B, p []byte) { + enc := func() io.WriteCloser { + // wrap the normal writer so that it has a noop Close method. writer + // does not implement ReaderFrom so this does not impact performance. + return &nopWriteCloser{NewWriter(ioutil.Discard)} + } + benchmarkEncode(b, enc, p) +} +func benchmarkBufferedWriterBytes(b *testing.B, p []byte) { + enc := func() io.WriteCloser { + // the writer's ReaderFrom implemention will be used in the benchmark. + return NewBufferedWriter(ioutil.Discard) + } + benchmarkEncode(b, enc, p) +} +func benchmarkBufferedWriterBytesNoCopy(b *testing.B, p []byte) { + enc := func() io.WriteCloser { + // the writer is wrapped as to hide it's ReaderFrom implemention. + return &writeCloserNoCopy{NewBufferedWriter(ioutil.Discard)} + } + benchmarkEncode(b, enc, p) +} + +// benchmarkEncode benchmarks the speed at which bytes can be copied from +// bs into writers created by enc. +func benchmarkEncode(b *testing.B, enc func() io.WriteCloser, bs []byte) { + size := int64(len(bs)) + b.SetBytes(size) + b.StartTimer() + for i := 0; i < b.N; i++ { + w := enc() + n, err := io.Copy(w, dummyBytesReader(bs)) + if err != nil { + b.Fatal(err) + } + if n != size { + b.Fatalf("wrote wrong amount %d != %d", n, size) + } + err = w.Close() + if err != nil { + b.Fatalf("close: %v", err) + } + } + b.StopTimer() +} + +func BenchmarkReaderManpage(b *testing.B) { + encodeAndBenchmarkReader(b, testDataMan) +} +func BenchmarkReaderManpage_buffered(b *testing.B) { + encodeAndBenchmarkReader_buffered(b, testDataMan) +} +func BenchmarkReaderManpageNoCopy(b *testing.B) { + encodeAndBenchmarkReaderNoCopy(b, testDataMan) +} + +func BenchmarkReaderJSON(b *testing.B) { + encodeAndBenchmarkReader(b, testDataJSON) +} +func BenchmarkReaderJSON_buffered(b *testing.B) { + encodeAndBenchmarkReader_buffered(b, testDataJSON) +} +func BenchmarkReaderJSONNoCopy(b *testing.B) { + encodeAndBenchmarkReaderNoCopy(b, testDataJSON) +} + +// BenchmarkReaderRandom tests decoding of effectively uncompressable data. +func BenchmarkReaderRandom(b *testing.B) { + encodeAndBenchmarkReader(b, randBytes(b, TestFileSize)) +} +func BenchmarkReaderRandom_buffered(b *testing.B) { + encodeAndBenchmarkReader_buffered(b, randBytes(b, TestFileSize)) +} +func BenchmarkReaderRandomNoCopy(b *testing.B) { + encodeAndBenchmarkReaderNoCopy(b, randBytes(b, TestFileSize)) +} + +// BenchmarkReaderConstant tests decoding of maximally compressible data. +func BenchmarkReaderConstant(b *testing.B) { + encodeAndBenchmarkReader(b, make([]byte, TestFileSize)) +} +func BenchmarkReaderConstant_buffered(b *testing.B) { + encodeAndBenchmarkReader_buffered(b, make([]byte, TestFileSize)) +} +func BenchmarkReaderConstantNoCopy(b *testing.B) { + encodeAndBenchmarkReaderNoCopy(b, make([]byte, TestFileSize)) +} + +// encodeAndBenchmarkReader is a helper that benchmarks the package +// reader's performance given p encoded as a snappy framed stream. +// +// encodeAndBenchmarkReader benchmarks decoding of streams containing +// (multiple) short frames. +func encodeAndBenchmarkReader(b *testing.B, p []byte) { + enc, err := encodeStreamBytes(p, false) + if err != nil { + b.Fatalf("pre-benchmark compression: %v", err) + } + dec := func(r io.Reader) io.Reader { + return NewReader(r, VerifyChecksum) + } + benchmarkDecode(b, dec, int64(len(p)), enc) +} + +// encodeAndBenchmarkReader_buffered is a helper that benchmarks the +// package reader's performance given p encoded as a snappy framed stream. +// +// encodeAndBenchmarkReader_buffered benchmarks decoding of streams that +// contain at most one short frame (at the end). +func encodeAndBenchmarkReader_buffered(b *testing.B, p []byte) { + enc, err := encodeStreamBytes(p, true) + if err != nil { + b.Fatalf("pre-benchmark compression: %v", err) + } + dec := func(r io.Reader) io.Reader { + return NewReader(r, VerifyChecksum) + } + benchmarkDecode(b, dec, int64(len(p)), enc) +} + +// encodeAndBenchmarkReaderNoCopy is a helper that benchmarks the +// package reader's performance given p encoded as a snappy framed stream. +// encodeAndBenchmarReaderNoCopy avoids use of the reader's io.WriterTo +// interface. +// +// encodeAndBenchmarkReaderNoCopy benchmarks decoding of streams that +// contain at most one short frame (at the end). +func encodeAndBenchmarkReaderNoCopy(b *testing.B, p []byte) { + enc, err := encodeStreamBytes(p, true) + if err != nil { + b.Fatalf("pre-benchmark compression: %v", err) + } + dec := func(r io.Reader) io.Reader { + return ioutil.NopCloser(NewReader(r, VerifyChecksum)) + } + benchmarkDecode(b, dec, int64(len(p)), enc) +} + +// benchmarkDecode runs a benchmark that repeatedly decoded snappy +// framed bytes enc. The length of the decoded result in each iteration must +// equal size. +func benchmarkDecode(b *testing.B, dec func(io.Reader) io.Reader, size int64, enc []byte) { + b.SetBytes(int64(len(enc))) // BUG this is probably wrong + b.ResetTimer() + for i := 0; i < b.N; i++ { + r := dec(bytes.NewReader(enc)) + n, err := io.Copy(ioutil.Discard, r) + if err != nil { + b.Fatalf(err.Error()) + } + if n != size { + b.Fatalf("read wrong amount %d != %d", n, size) + } + } + b.StopTimer() +} + +// encodeStreamBytes is like encodeStream but operates on a byte slice. +// encodeStreamBytes ensures that long streams are not maximally compressed if +// buffer is false. +func encodeStreamBytes(b []byte, buffer bool) ([]byte, error) { + return encodeStream(dummyBytesReader(b), buffer) +} + +// encodeStream encodes data read from r as a snappy framed stream and returns +// the result as a byte slice. if buffer is true the bytes from r are buffered +// to improve the resulting slice's compression ratio. +func encodeStream(r io.Reader, buffer bool) ([]byte, error) { + var buf bytes.Buffer + if !buffer { + w := NewWriter(&buf) + _, err := io.Copy(w, r) + if err != nil { + return nil, err + } + return buf.Bytes(), nil + } + + w := NewBufferedWriter(&buf) + _, err := io.Copy(w, r) + if err != nil { + return nil, err + } + err = w.Close() + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// randBytes reads size bytes from the computer's cryptographic random source. +// the resulting bytes have approximately maximal entropy and are effectively +// uncompressible with any algorithm. +func randBytes(b *testing.B, size int) []byte { + randp := make([]byte, size) + _, err := io.ReadFull(rand.Reader, randp) + if err != nil { + b.Fatal(err) + } + return randp +} + +// writeCloserNoCopy is an io.WriteCloser that simply wraps another +// io.WriteCloser. This is useful for masking implementations for interfaces +// like ReaderFrom which may be opted into use inside functions like io.Copy. +type writeCloserNoCopy struct { + io.WriteCloser +} + +// nopWriteCloser is an io.WriteCloser that has a noop Close method. This type +// has the effect of masking the underlying writer's Close implementation if it +// has one, or satisfying interface implementations for writers that do not +// need to be closing. +type nopWriteCloser struct { + io.Writer +} + +func (w *nopWriteCloser) Close() error { + return nil +} diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/snappystream.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/snappystream.go new file mode 100644 index 0000000..07d21fc --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/snappystream.go @@ -0,0 +1,54 @@ +// snappystream wraps snappy-go and supplies a Reader and Writer +// for the snappy framed stream format: +// https://snappy.googlecode.com/svn/trunk/framing_format.txt +package snappystream + +import ( + "hash/crc32" + + "code.google.com/p/snappy-go/snappy" +) + +// Ext is the file extension for files whose content is a snappy framed stream. +const Ext = ".sz" + +// MediaType is the MIME type used to represent snappy framed content. +const MediaType = "application/x-snappy-framed" + +// ContentEncoding is the appropriate HTTP Content-Encoding header value for +// requests containing a snappy framed entity body. +const ContentEncoding = "x-snappy-framed" + +// MaxBlockSize is the maximum number of decoded bytes allowed to be +// represented in a snappy framed block (sections 4.2 and 4.3). +const MaxBlockSize = 65536 + +// maxEncodedBlockSize is the maximum number of encoded bytes in a framed +// block. +var maxEncodedBlockSize = uint32(snappy.MaxEncodedLen(MaxBlockSize)) + +const VerifyChecksum = true +const SkipVerifyChecksum = false + +// Block types defined by the snappy framed format specification. +const ( + blockCompressed = 0x00 + blockUncompressed = 0x01 + blockPadding = 0xfe + blockStreamIdentifier = 0xff +) + +// streamID is the stream identifier block that begins a valid snappy framed +// stream. +var streamID = []byte{0xff, 0x06, 0x00, 0x00, 0x73, 0x4e, 0x61, 0x50, 0x70, 0x59} + +// maskChecksum implements the checksum masking algorithm described by the spec. +func maskChecksum(c uint32) uint32 { + return ((c >> 15) | (c << 17)) + 0xa282ead8 +} + +var crcTable *crc32.Table + +func init() { + crcTable = crc32.MakeTable(crc32.Castagnoli) +} diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer.go new file mode 100644 index 0000000..5b2641f --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer.go @@ -0,0 +1,229 @@ +package snappystream + +import ( + "bufio" + "errors" + "fmt" + "hash/crc32" + "io" + + "code.google.com/p/snappy-go/snappy" +) + +var errClosed = fmt.Errorf("closed") + +// BufferedWriter is an io.WriteCloser with behavior similar to writers +// returned by NewWriter but it buffers written data, maximizing block size (to +// improve the output compression ratio) at the cost of speed. Benefits over +// NewWriter are most noticible when individual writes are small and when +// streams are long. +// +// Failure to call a BufferedWriter's Close or Flush methods after it is done +// being written to will likely result in missing data frames which will be +// undetectable in the decoding process. +// +// NOTE: BufferedWriter cannot be instantiated via struct literal and must +// use NewBufferedWriter (i.e. its zero value is not usable). +type BufferedWriter struct { + err error + w *writer + bw *bufio.Writer +} + +// NewBufferedWriter allocates and returns a BufferedWriter with an internal +// buffer of MaxBlockSize bytes. If an error occurs writing a block to w, all +// future writes will fail with the same error. After all data has been +// written, the client should call the Flush method to guarantee all data has +// been forwarded to the underlying io.Writer. +func NewBufferedWriter(w io.Writer) *BufferedWriter { + _w := NewWriter(w).(*writer) + return &BufferedWriter{ + w: _w, + bw: bufio.NewWriterSize(_w, MaxBlockSize), + } +} + +// ReadFrom implements the io.ReaderFrom interface used by io.Copy. It encodes +// data read from r as a snappy framed stream that is written to the underlying +// writer. ReadFrom returns the number number of bytes read, along with any +// error encountered (other than io.EOF). +func (w *BufferedWriter) ReadFrom(r io.Reader) (int64, error) { + if w.err != nil { + return 0, w.err + } + + var n int64 + n, w.err = w.bw.ReadFrom(r) + return n, w.err +} + +// Write buffers p internally, encoding and writing a block to the underlying +// buffer if the buffer grows beyond MaxBlockSize bytes. The returned int +// will be 0 if there was an error and len(p) otherwise. +func (w *BufferedWriter) Write(p []byte) (int, error) { + if w.err != nil { + return 0, w.err + } + + _, w.err = w.bw.Write(p) + if w.err != nil { + return 0, w.err + } + + return len(p), nil +} + +// Flush encodes and writes a block with the contents of w's internal buffer to +// the underlying writer even if the buffer does not contain a full block of +// data (MaxBlockSize bytes). +func (w *BufferedWriter) Flush() error { + if w.err == nil { + w.err = w.bw.Flush() + } + + return w.err +} + +// Close flushes w's internal buffer and tears down internal data structures. +// After a successful call to Close method calls on w return an error. Close +// makes no attempt to close the underlying writer. +func (w *BufferedWriter) Close() error { + if w.err != nil { + return w.err + } + + w.err = w.bw.Flush() + w.w = nil + w.bw = nil + + if w.err != nil { + return w.err + } + + w.err = errClosed + return nil +} + +type writer struct { + writer io.Writer + err error + + hdr []byte + dst []byte + + sentStreamID bool +} + +// NewWriter returns an io.Writer that writes its input to an underlying +// io.Writer encoded as a snappy framed stream. A stream identifier block is +// written to w preceding the first data block. The returned writer will never +// emit a block with length in bytes greater than MaxBlockSize+4 nor one +// containing more than MaxBlockSize bytes of (uncompressed) data. +// +// For each Write, the returned length will only ever be len(p) or 0, +// regardless of the length of *compressed* bytes written to the wrapped +// io.Writer. If the returned length is 0 then error will be non-nil. If +// len(p) exceeds 65536, the slice will be automatically chunked into smaller +// blocks which are all emitted before the call returns. +func NewWriter(w io.Writer) io.Writer { + return &writer{ + writer: w, + + hdr: make([]byte, 8), + dst: make([]byte, 4096), + } +} + +func (w *writer) Write(p []byte) (int, error) { + if w.err != nil { + return 0, w.err + } + + total := 0 + sz := MaxBlockSize + var n int + for i := 0; i < len(p); i += n { + if i+sz > len(p) { + sz = len(p) - i + } + + n, w.err = w.write(p[i : i+sz]) + if w.err != nil { + return 0, w.err + } + total += n + } + return total, nil +} + +// write attempts to encode p as a block and write it to the underlying writer. +// The returned int may not equal p's length if compression below +// MaxBlockSize-4 could not be achieved. +func (w *writer) write(p []byte) (int, error) { + var err error + + if len(p) > MaxBlockSize { + return 0, errors.New(fmt.Sprintf("block too large %d > %d", len(p), MaxBlockSize)) + } + + w.dst = w.dst[:cap(w.dst)] // Encode does dumb resize w/o context. reslice avoids alloc. + w.dst, err = snappy.Encode(w.dst, p) + if err != nil { + return 0, err + } + block := w.dst + n := len(p) + compressed := true + + // check for data which is better left uncompressed. this is determined if + // the encoded content is longer than the source. + if len(w.dst) >= len(p) { + compressed = false + block = p[:n] + } + + if !w.sentStreamID { + _, err := w.writer.Write(streamID) + if err != nil { + return 0, err + } + w.sentStreamID = true + } + + // set the block type + if compressed { + writeHeader(w.hdr, blockCompressed, block, p[:n]) + } else { + writeHeader(w.hdr, blockUncompressed, block, p[:n]) + } + + _, err = w.writer.Write(w.hdr) + if err != nil { + return 0, err + } + + _, err = w.writer.Write(block) + if err != nil { + return 0, err + } + + return n, nil +} + +// writeHeader panics if len(hdr) is less than 8. +func writeHeader(hdr []byte, btype byte, enc, dec []byte) { + hdr[0] = btype + + // 3 byte little endian length of encoded content + length := uint32(len(enc)) + 4 // +4 for checksum + hdr[1] = byte(length) + hdr[2] = byte(length >> 8) + hdr[3] = byte(length >> 16) + + // 4 byte little endian CRC32 checksum of decoded content + checksum := maskChecksum(crc32.Checksum(dec, crcTable)) + hdr[4] = byte(checksum) + hdr[5] = byte(checksum >> 8) + hdr[6] = byte(checksum >> 16) + hdr[7] = byte(checksum >> 24) +} diff --git a/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer_test.go b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer_test.go new file mode 100644 index 0000000..d16fd25 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mreiferson/go-snappystream/writer_test.go @@ -0,0 +1,126 @@ +package snappystream + +import ( + "bytes" + "io/ioutil" + "log" + "testing" +) + +// This test ensures that all BufferedWriter methods fail after Close has been +// called. +func TestBufferedWriterClose(t *testing.T) { + w := NewBufferedWriter(ioutil.Discard) + err := w.Close() + if err != nil { + log.Fatalf("closing empty BufferedWriter: %v", err) + } + err = w.Close() + if err == nil { + log.Fatalf("successful close after close") + } + err = w.Flush() + if err == nil { + log.Fatalf("successful flush after close") + } + _, err = w.Write([]byte("abc")) + if err == nil { + log.Fatalf("successful write after close") + } +} + +// This test simply checks that buffering has an effect in a situation where it +// is know it should. +func TestBufferedWriter_compression(t *testing.T) { + p := []byte("hello snappystream!") + n := 10 + + var shortbuf bytes.Buffer + w := NewWriter(&shortbuf) + for i := 0; i < n; i++ { + n, err := w.Write(p) + if err != nil { + t.Fatalf("writer error: %v", err) + } + if n != len(p) { + t.Fatalf("short write: %d", n) + } + } + + var buf bytes.Buffer + bw := NewBufferedWriter(&buf) + for i := 0; i < n; i++ { + n, err := bw.Write(p) + if err != nil { + t.Fatalf("buffered writer error: %v", err) + } + if n != len(p) { + t.Fatalf("short write: %d", n) + } + } + err := bw.Close() + if err != nil { + t.Fatalf("closing buffer: %v", err) + } + + uncompressed := int64(n) * int64(len(p)) + compressed := shortbuf.Len() + bufcompressed := buf.Len() + + if compressed <= bufcompressed { + t.Fatalf("no benefit from buffering (%d <= %d)", shortbuf.Len(), buf.Len()) + } + + c := float64(uncompressed) / float64(compressed) + bufc := float64(uncompressed) / float64(bufcompressed) + improved := bufc / c + + t.Logf("BufferedWriter compression ratio %g (%.03g factor improvement over %g)", bufc, improved, c) +} + +// This tests ensures flushing after every write is equivalent to using +// NewWriter directly. +func TestBufferedWriterFlush(t *testing.T) { + p := []byte("hello snappystream!") + n := 10 + + var shortbuf bytes.Buffer + w := NewWriter(&shortbuf) + for i := 0; i < n; i++ { + n, err := w.Write(p) + if err != nil { + t.Fatalf("writer error: %v", err) + } + if n != len(p) { + t.Fatalf("short write: %d", n) + } + } + + var buf bytes.Buffer + bw := NewBufferedWriter(&buf) + for i := 0; i < n; i++ { + n, err := bw.Write(p) + if err != nil { + t.Fatalf("buffered writer error: %v", err) + } + if n != len(p) { + t.Fatalf("short write: %d", n) + } + err = bw.Flush() + if err != nil { + t.Fatalf("flush: %v", err) + } + } + err := bw.Close() + if err != nil { + t.Fatalf("closing buffer: %v", err) + } + + if shortbuf.Len() != buf.Len() { + t.Fatalf("unexpected size: %d != %d", shortbuf.Len(), buf.Len()) + } + + if !bytes.Equal(shortbuf.Bytes(), buf.Bytes()) { + t.Fatalf("unexpected bytes") + } +} diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE b/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE new file mode 100644 index 0000000..fd1000d --- /dev/null +++ b/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2014 Carl Jackson, Sajith Appukuttan + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md b/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md new file mode 100644 index 0000000..1b41ee4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md @@ -0,0 +1,36 @@ +# goji/glogrus [![GoDoc](https://godoc.org/github.com/goji/glogrus?status.png)](https://godoc.org/github.com/goji/glogrus) + +glogrus provides structured logging via logrus for Goji. + +## Example + + +```go + +package main + +import( + "github.com/zenazn/goji" + "github.com/zenazn/goji/web/middleware" + "github.com/goji/glogrus" + "github.com/Sirupsen/logrus" +) + +func main() { + goji.Abandon(middleware.Logger) + + logr := logrus.New() + logr.Formatter = new(logrus.JSONFormatter) + goji.Use(glogrus.NewGlogrus(logr, "my-app-name")) + + goji.Get("/ping", yourHandler) + goji.Serve() +} + +``` + +- - - + + +#### Looking for hierarchical structured logging? +[slog](https://github.com/zenazn/slog) and [lunk](https://github.com/codahale/lunk) looks interesting. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go b/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go new file mode 100644 index 0000000..4a9e03d --- /dev/null +++ b/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go @@ -0,0 +1,80 @@ +package glogrus + +import ( + "fmt" + "net/http" + "time" + + "github.com/Sirupsen/logrus" + "github.com/zenazn/goji/web" + "github.com/zenazn/goji/web/middleware" +) + +// glogrus is a middleware handler that logs the +// request and response in a structured way +type glogrus struct { + h http.Handler + c *web.C + l *logrus.Logger + name string +} + +func (glogr glogrus) ServeHTTP(resp http.ResponseWriter, req *http.Request) { + start := time.Now() + reqID := middleware.GetReqID(*glogr.c) + glogr.l.WithFields(logrus.Fields{ + "req_id": reqID, + "uri": req.RequestURI, + "method": req.Method, + "remote": req.RemoteAddr, + }).Info("New request incoming") + lresp := wrapWriter(resp) + + glogr.h.ServeHTTP(lresp, req) + lresp.maybeWriteHeader() + + latency := float64(time.Since(start)) / float64(time.Millisecond) + + glogr.l.WithFields(logrus.Fields{ + "req_id": reqID, + "status": lresp.status(), + "method": req.Method, + "uri": req.RequestURI, + "remote": req.RemoteAddr, + "latency": fmt.Sprintf("%6.4f ms", latency), + "app": glogr.name, + }).Info("Finished serving the request") +} + +// NewGlogrus allows you to configure a goji middleware that logs all requests and responses +// using the structured logger logrus. It takes the logrus instance and the name of the app +// as the parameters and returns a middleware of type "func(c *web.C, http.Handler) http.Handler" +// +// Example: +// +// package main +// +// import( +// "github.com/zenazn/goji" +// "github.com/zenazn/goji/web/middleware" +// "github.com/goji/glogrus" +// "github.com/Sirupsen/logrus" +// ) +// +// func main() { +// goji.Abandon(middleware.Logger) +// +// logr := logrus.New() +// logr.Formatter = new(logrus.JSONFormatter) +// goji.Use(glogrus.NewGlogrus(logr, "my-app-name")) +// +// goji.Get("/ping", yourHandler) +// goji.Serve() +// } +// +func NewGlogrus(l *logrus.Logger, name string) func(*web.C, http.Handler) http.Handler { + fn := func(c *web.C, h http.Handler) http.Handler { + return glogrus{h: h, c: c, l: l, name: name} + } + return fn +} diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go b/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go new file mode 100644 index 0000000..16a887f --- /dev/null +++ b/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go @@ -0,0 +1,70 @@ +package glogrus + +import ( + "bufio" + "net" + "net/http" +) + +// wrapWriter returns a proxy that wraps ResponseWriter +func wrapWriter(w http.ResponseWriter) writerProxy { + bw := basicWriter{ResponseWriter: w} + return &bw +} + +// writerProxy is a proxy that wraps ResponseWriter +type writerProxy interface { + http.ResponseWriter + maybeWriteHeader() + status() int +} + +// basicWriter holds the status code and a +// flag in addition to http.ResponseWriter +type basicWriter struct { + http.ResponseWriter + wroteHeader bool + code int +} + +// WriteHeader stores the status code and writes header +func (b *basicWriter) WriteHeader(code int) { + if !b.wroteHeader { + b.code = code + b.wroteHeader = true + b.ResponseWriter.WriteHeader(code) + } +} + +// Write writes the bytes and calls MaybeWriteHeader +func (b *basicWriter) Write(buf []byte) (int, error) { + b.maybeWriteHeader() + return b.ResponseWriter.Write(buf) +} + +// maybeWriteHeader writes the header if it is not alredy set +func (b *basicWriter) maybeWriteHeader() { + if !b.wroteHeader { + b.WriteHeader(http.StatusOK) + } +} + +// status returns the status +func (b *basicWriter) status() int { + return b.code +} + +// unwrap returns the original http.ResponseWriter +func (b *basicWriter) Unwrap() http.ResponseWriter { + return b.ResponseWriter +} + +// Flush is used for the implementation of http.Flusher +func (b *basicWriter) Flush() { + b.ResponseWriter.(http.Flusher).Flush() +} + +// Hijack is used for the implementation of http.Hijacker +func (b *basicWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + return b.ResponseWriter.(http.Hijacker).Hijack() +} diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md b/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md new file mode 100644 index 0000000..8564e07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md @@ -0,0 +1,71 @@ + +v0.4.3 / 2014-07-31 +================== + + * fix res.Body.Close() defer + +v0.4.2 / 2014-07-31 +================== + + * remove implicit newline in .Write() + +v0.4.1 / 2014-07-31 +================== + + * fix when no tags are used + +v0.4.0 / 2014-07-25 +================== + + * add tag support to New() + * add varg support to .Tag() + +v0.3.0 / 2014-07-25 +================== + + * add io.Writer interface support + * add tag support. Closes #5 + * change User-Agent sent to loggly to match repo + +0.1.1 / 2014-07-10 +================== + + * fix: close the response body so it doesn't leak file descriptors + +0.3.0 / 2014-06-01 +================== + + * change timestamp to be milliseconds + +0.2.0 / 2014-05-31 +================== + + * add .Stdout option + * add simple example + * fix locking typo + * refactor locking + +0.1.0 / 2014-05-22 +================== + + * replace Debugging option with debug package + +0.0.5 / 2014-05-20 +================== + + * add mutexes + +0.0.4 / 2014-05-20 +================== + + * rename .Log() to .Send() + +0.0.3 / 2014-05-20 +================== + + * unix epoch timestamp + +0.0.2 / 2014-05-20 +================== + + * https diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md b/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md new file mode 100644 index 0000000..ac50e8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md @@ -0,0 +1,17 @@ +# go-loggly + + Loggly client for Go. + + View the [docs](http://godoc.org/github.com/segmentio/go-loggly). + +## Installation + + $ go get github.com/segmentio/go-loggly + +## Debug + + Enable verbose debugging output using the __DEBUG__ environment variable, for exmaple `DEBUG=loggly`. + +## License + + MIT diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go b/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go new file mode 100644 index 0000000..7a4d967 --- /dev/null +++ b/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go @@ -0,0 +1,306 @@ +package loggly + +import . "github.com/visionmedia/go-debug" +import . "encoding/json" +import "io/ioutil" +import "net/http" +import "strings" +import "bytes" +import "time" +import "sync" +import "fmt" +import "os" +import "io" + +const Version = "0.4.3" + +const api = "https://logs-01.loggly.com/bulk/{token}" + +type Message map[string]interface{} + +var debug = Debug("loggly") + +var nl = []byte{'\n'} + +type Level int + +const ( + DEBUG Level = iota + INFO + NOTICE + WARNING + ERROR + CRITICAL + ALERT + EMERGENCY +) + +// Loggly client. +type Client struct { + // Optionally output logs to the given writer. + Writer io.Writer + + // Log level defaulting to INFO. + Level Level + + // Size of buffer before flushing [100] + BufferSize int + + // Flush interval regardless of size [5s] + FlushInterval time.Duration + + // Loggly end-point. + Endpoint string + + // Token string. + Token string + + // Default properties. + Defaults Message + buffer [][]byte + tags []string + sync.Mutex +} + +// New returns a new loggly client with the given `token`. +// Optionally pass `tags` or set them later with `.Tag()`. +func New(token string, tags ...string) *Client { + host, err := os.Hostname() + defaults := Message{} + + if err == nil { + defaults["hostname"] = host + } + + c := &Client{ + Level: INFO, + BufferSize: 100, + FlushInterval: 5 * time.Second, + Token: token, + Endpoint: strings.Replace(api, "{token}", token, 1), + buffer: make([][]byte, 0), + Defaults: defaults, + } + + c.Tag(tags...) + + go c.start() + + return c +} + +// Send buffers `msg` for async sending. +func (c *Client) Send(msg Message) error { + if _, exists := msg["timestamp"]; !exists { + msg["timestamp"] = time.Now().UnixNano() / int64(time.Millisecond) + } + merge(msg, c.Defaults) + + json, err := Marshal(msg) + if err != nil { + return err + } + + c.Lock() + defer c.Unlock() + + if c.Writer != nil { + fmt.Fprintf(c.Writer, "%s\n", string(json)) + } + + c.buffer = append(c.buffer, json) + + debug("buffer (%d/%d) %v", len(c.buffer), c.BufferSize, msg) + + if len(c.buffer) >= c.BufferSize { + go c.Flush() + } + + return nil +} + +// Write raw data to loggly. +func (c *Client) Write(b []byte) (int, error) { + c.Lock() + defer c.Unlock() + + if c.Writer != nil { + fmt.Fprintf(c.Writer, "%s", b) + } + + c.buffer = append(c.buffer, b) + + debug("buffer (%d/%d) %q", len(c.buffer), c.BufferSize, b) + + if len(c.buffer) >= c.BufferSize { + go c.Flush() + } + + return len(b), nil +} + +// Debug log. +func (c *Client) Debug(t string, props ...Message) error { + if c.Level > DEBUG { + return nil + } + msg := Message{"level": "debug", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Info log. +func (c *Client) Info(t string, props ...Message) error { + if c.Level > INFO { + return nil + } + msg := Message{"level": "info", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Notice log. +func (c *Client) Notice(t string, props ...Message) error { + if c.Level > NOTICE { + return nil + } + msg := Message{"level": "notice", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Warning log. +func (c *Client) Warn(t string, props ...Message) error { + if c.Level > WARNING { + return nil + } + msg := Message{"level": "warning", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Error log. +func (c *Client) Error(t string, props ...Message) error { + if c.Level > ERROR { + return nil + } + msg := Message{"level": "error", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Critical log. +func (c *Client) Critical(t string, props ...Message) error { + if c.Level > CRITICAL { + return nil + } + msg := Message{"level": "critical", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Alert log. +func (c *Client) Alert(t string, props ...Message) error { + if c.Level > ALERT { + return nil + } + msg := Message{"level": "alert", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Emergency log. +func (c *Client) Emergency(t string, props ...Message) error { + if c.Level > EMERGENCY { + return nil + } + msg := Message{"level": "emergency", "type": t} + merge(msg, props...) + return c.Send(msg) +} + +// Flush the buffered messages. +func (c *Client) Flush() error { + c.Lock() + + if len(c.buffer) == 0 { + debug("no messages to flush") + c.Unlock() + return nil + } + + debug("flushing %d messages", len(c.buffer)) + body := bytes.Join(c.buffer, nl) + + c.buffer = nil + c.Unlock() + + client := &http.Client{} + debug("POST %s with %d bytes", c.Endpoint, len(body)) + req, err := http.NewRequest("POST", c.Endpoint, bytes.NewBuffer(body)) + if err != nil { + debug("error: %v", err) + return err + } + + req.Header.Add("User-Agent", "go-loggly (version: "+Version+")") + req.Header.Add("Content-Type", "text/plain") + req.Header.Add("Content-Length", string(len(body))) + + tags := c.tagsList() + if tags != "" { + req.Header.Add("X-Loggly-Tag", tags) + } + + res, err := client.Do(req) + if err != nil { + debug("error: %v", err) + return err + } + + defer res.Body.Close() + + debug("%d response", res.StatusCode) + if res.StatusCode >= 400 { + resp, _ := ioutil.ReadAll(res.Body) + debug("error: %s", string(resp)) + } + + return err +} + +// Tag adds the given `tags` for all logs. +func (c *Client) Tag(tags ...string) { + c.Lock() + defer c.Unlock() + + for _, tag := range tags { + c.tags = append(c.tags, tag) + } +} + +// Return a comma-delimited tag list string. +func (c *Client) tagsList() string { + c.Lock() + defer c.Unlock() + + return strings.Join(c.tags, ",") +} + +// Start flusher. +func (c *Client) start() { + for { + time.Sleep(c.FlushInterval) + debug("interval %v reached", c.FlushInterval) + c.Flush() + } +} + +// Merge others into a. +func merge(a Message, others ...Message) { + for _, msg := range others { + for k, v := range msg { + a[k] = v + } + } +} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md b/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md new file mode 100644 index 0000000..318ceb4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md @@ -0,0 +1,21 @@ + +v2.0.0 / 2014-10-22 +================== + + * remove live toggling feature. Closes #10 + +1.1.1 / 2014-07-07 +================== + + * fix: dispose socket. Closes #1 + +1.1.0 / 2014-06-29 +================== + + * add unix domain socket live debugging support + * add support for enabling/disabling at runtime + +0.1.0 / 2014-05-24 +================== + + * add global and debug relative deltas diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile new file mode 100644 index 0000000..16bc6d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile @@ -0,0 +1,8 @@ + +test: + @go test + +bench: + @go test -bench=. + +.PHONY: bench test \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md new file mode 100644 index 0000000..6560af8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md @@ -0,0 +1,75 @@ + +# go-debug + + Conditional debug logging for Go libraries. + + View the [docs](http://godoc.org/github.com/tj/go-debug). + +## Installation + +``` +$ go get github.com/tj/go-debug +``` + +## Example + +```go +package main + +import . "github.com/tj/go-debug" +import "time" + +var debug = Debug("single") + +func main() { + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} +``` + +If you run the program with the `DEBUG=*` environment variable you will see: + +``` +15:58:15.115 34us 33us single - sending mail +15:58:15.116 3us 3us single - send email to tobi@segment.io +15:58:15.116 1us 1us single - send email to loki@segment.io +15:58:15.116 1us 1us single - send email to jane@segment.io +15:58:15.620 504ms 504ms single - sending mail +15:58:15.620 6us 6us single - send email to tobi@segment.io +15:58:15.620 4us 4us single - send email to loki@segment.io +15:58:15.620 4us 4us single - send email to jane@segment.io +15:58:16.123 503ms 503ms single - sending mail +15:58:16.123 7us 7us single - send email to tobi@segment.io +15:58:16.123 4us 4us single - send email to loki@segment.io +15:58:16.123 4us 4us single - send email to jane@segment.io +15:58:16.625 501ms 501ms single - sending mail +15:58:16.625 4us 4us single - send email to tobi@segment.io +15:58:16.625 4us 4us single - send email to loki@segment.io +15:58:16.625 5us 5us single - send email to jane@segment.io +``` + +A timestamp and two deltas are displayed. The timestamp consists of hour, minute, second and microseconds. The left-most delta is relative to the previous debug call of any name, followed by a delta specific to that debug function. These may be useful to identify timing issues and potential bottlenecks. + +## The DEBUG environment variable + + Executables often support `--verbose` flags for conditional logging, however + libraries typically either require altering your code to enable logging, + or simply omit logging all together. go-debug allows conditional logging + to be enabled via the __DEBUG__ environment variable, where one or more + patterns may be specified. + + For example suppose your application has several models and you want + to output logs for users only, you might use `DEBUG=models:user`. In contrast + if you wanted to see what all database activity was you might use `DEBUG=models:*`, + or if you're love being swamped with logs: `DEBUG=*`. You may also specify a list of names delimited by a comma, for example `DEBUG=mongo,redis:*`. + + The name given _should_ be the package name, however you can use whatever you like. + +# License + +MIT \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go new file mode 100644 index 0000000..016ca46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go @@ -0,0 +1,128 @@ +package debug + +import ( + "fmt" + "io" + "math/rand" + "os" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +var ( + writer io.Writer = os.Stderr + reg *regexp.Regexp + m sync.Mutex + enabled = false +) + +// Debugger function. +type DebugFunction func(string, ...interface{}) + +// Terminal colors used at random. +var colors []string = []string{ + "31", + "32", + "33", + "34", + "35", + "36", +} + +// Initialize with DEBUG environment variable. +func init() { + env := os.Getenv("DEBUG") + + if "" != env { + Enable(env) + } +} + +// SetWriter replaces the default of os.Stderr with `w`. +func SetWriter(w io.Writer) { + m.Lock() + defer m.Unlock() + writer = w +} + +// Disable all pattern matching. This function is thread-safe. +func Disable() { + m.Lock() + defer m.Unlock() + enabled = false +} + +// Enable the given debug `pattern`. Patterns take a glob-like form, +// for example if you wanted to enable everything, just use "*", or +// if you had a library named mongodb you could use "mongodb:connection", +// or "mongodb:*". Multiple matches can be made with a comma, for +// example "mongo*,redis*". +// +// This function is thread-safe. +func Enable(pattern string) { + m.Lock() + defer m.Unlock() + pattern = regexp.QuoteMeta(pattern) + pattern = strings.Replace(pattern, "\\*", ".*?", -1) + pattern = strings.Replace(pattern, ",", "|", -1) + pattern = "^(" + pattern + ")$" + reg = regexp.MustCompile(pattern) + enabled = true +} + +// Debug creates a debug function for `name` which you call +// with printf-style arguments in your application or library. +func Debug(name string) DebugFunction { + prevGlobal := time.Now() + color := colors[rand.Intn(len(colors))] + prev := time.Now() + + return func(format string, args ...interface{}) { + if !enabled { + return + } + + if !reg.MatchString(name) { + return + } + + d := deltas(prevGlobal, prev, color) + fmt.Fprintf(writer, d+" \033["+color+"m"+name+"\033[0m - "+format+"\n", args...) + prevGlobal = time.Now() + prev = time.Now() + } +} + +// Return formatting for deltas. +func deltas(prevGlobal, prev time.Time, color string) string { + now := time.Now() + global := now.Sub(prevGlobal).Nanoseconds() + delta := now.Sub(prev).Nanoseconds() + ts := now.UTC().Format("15:04:05.000") + deltas := fmt.Sprintf("%s %-6s \033["+color+"m%-6s", ts, humanizeNano(global), humanizeNano(delta)) + return deltas +} + +// Humanize nanoseconds to a string. +func humanizeNano(n int64) string { + var suffix string + + switch { + case n > 1e9: + n /= 1e9 + suffix = "s" + case n > 1e6: + n /= 1e6 + suffix = "ms" + case n > 1e3: + n /= 1e3 + suffix = "us" + default: + suffix = "ns" + } + + return strconv.Itoa(int(n)) + suffix +} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go new file mode 100644 index 0000000..7ce2764 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go @@ -0,0 +1,152 @@ +package debug + +import "testing" +import "strings" +import "bytes" +import "time" + +func assertContains(t *testing.T, str, substr string) { + if !strings.Contains(str, substr) { + t.Fatalf("expected %q to contain %q", str, substr) + } +} + +func assertNotContains(t *testing.T, str, substr string) { + if strings.Contains(str, substr) { + t.Fatalf("expected %q to not contain %q", str, substr) + } +} + +func TestDefault(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + debug := Debug("foo") + debug("something") + debug("here") + debug("whoop") + + if buf.Len() != 0 { + t.Fatalf("buffer should be empty") + } +} + +func TestEnable(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo") + + debug := Debug("foo") + debug("something") + debug("here") + debug("whoop") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "something") + assertContains(t, str, "here") + assertContains(t, str, "whoop") +} + +func TestMultipleOneEnabled(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo") + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "foo") + assertNotContains(t, str, "bar") +} + +func TestMultipleEnabled(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo,bar") + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() == 0 { + t.Fatalf("buffer should have output") + } + + str := string(buf.Bytes()) + assertContains(t, str, "foo") + assertContains(t, str, "bar") +} + +func TestEnableDisable(t *testing.T) { + var b []byte + buf := bytes.NewBuffer(b) + SetWriter(buf) + + Enable("foo,bar") + Disable() + + foo := Debug("foo") + foo("foo") + + bar := Debug("bar") + bar("bar") + + if buf.Len() != 0 { + t.Fatalf("buffer should not have output") + } +} + +func ExampleEnable() { + Enable("mongo:connection") + Enable("mongo:*") + Enable("foo,bar,baz") + Enable("*") +} + +func ExampleDebug() { + var debug = Debug("single") + + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} + +func BenchmarkDisabled(b *testing.B) { + debug := Debug("something") + for i := 0; i < b.N; i++ { + debug("stuff") + } +} + +func BenchmarkNonMatch(b *testing.B) { + debug := Debug("something") + Enable("nonmatch") + for i := 0; i < b.N; i++ { + debug("stuff") + } +} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go new file mode 100644 index 0000000..81c3308 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go @@ -0,0 +1,25 @@ +package main + +import . "github.com/visionmedia/go-debug" +import "time" + +var a = Debug("multiple:a") +var b = Debug("multiple:b") +var c = Debug("multiple:c") + +func work(debug DebugFunction, delay time.Duration) { + for { + debug("doing stuff") + time.Sleep(delay) + } +} + +func main() { + q := make(chan bool) + + go work(a, 1000*time.Millisecond) + go work(b, 250*time.Millisecond) + go work(c, 100*time.Millisecond) + + <-q +} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go new file mode 100644 index 0000000..fccfe33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go @@ -0,0 +1,16 @@ +package main + +import . "github.com/visionmedia/go-debug" +import "time" + +var debug = Debug("single") + +func main() { + for { + debug("sending mail") + debug("send email to %s", "tobi@segment.io") + debug("send email to %s", "loki@segment.io") + debug("send email to %s", "jane@segment.io") + time.Sleep(500 * time.Millisecond) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/einhorn.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/einhorn.go new file mode 100644 index 0000000..082d1c4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/einhorn.go @@ -0,0 +1,21 @@ +// +build !windows + +package graceful + +import ( + "os" + "strconv" + "syscall" +) + +func init() { + // This is a little unfortunate: goji/bind already knows whether we're + // running under einhorn, but we don't want to introduce a dependency + // between the two packages. Since the check is short enough, inlining + // it here seems "fine." + mpid, err := strconv.Atoi(os.Getenv("EINHORN_MASTER_PID")) + if err != nil || mpid != os.Getppid() { + return + } + stdSignals = append(stdSignals, syscall.SIGUSR2) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/graceful.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/graceful.go new file mode 100644 index 0000000..19384ff --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/graceful.go @@ -0,0 +1,57 @@ +/* +Package graceful implements graceful shutdown for HTTP servers by closing idle +connections after receiving a signal. By default, this package listens for +interrupts (i.e., SIGINT), but when it detects that it is running under Einhorn +it will additionally listen for SIGUSR2 as well, giving your application +automatic support for graceful restarts/code upgrades. +*/ +package graceful + +import ( + "net" + "sync/atomic" + + "github.com/zenazn/goji/graceful/listener" +) + +// WrapListener wraps an arbitrary net.Listener for use with graceful shutdowns. +// In the background, it uses the listener sub-package to Wrap the listener in +// Deadline mode. If another mode of operation is desired, you should call +// listener.Wrap yourself: this function is smart enough to not double-wrap +// listeners. +func WrapListener(l net.Listener) net.Listener { + if lt, ok := l.(*listener.T); ok { + appendListener(lt) + return lt + } + + lt := listener.Wrap(l, listener.Deadline) + appendListener(lt) + return lt +} + +func appendListener(l *listener.T) { + mu.Lock() + defer mu.Unlock() + + listeners = append(listeners, l) +} + +const errClosing = "use of closed network connection" + +// During graceful shutdown, calls to Accept will start returning errors. This +// is inconvenient, since we know these sorts of errors are peaceful, so we +// silently swallow them. +func peacefulError(err error) error { + if atomic.LoadInt32(&closing) == 0 { + return err + } + // Unfortunately Go doesn't really give us a better way to select errors + // than this, so *shrug*. + if oe, ok := err.(*net.OpError); ok { + if oe.Op == "accept" && oe.Err.Error() == errClosing { + return nil + } + } + return err +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn.go new file mode 100644 index 0000000..3f797c5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn.go @@ -0,0 +1,149 @@ +package listener + +import ( + "errors" + "io" + "net" + "sync" + "time" +) + +type conn struct { + net.Conn + + shard *shard + mode mode + + mu sync.Mutex // Protects the state machine below + busy bool // connection is in use (i.e., not idle) + closed bool // connection is closed + disowned bool // if true, this connection is no longer under our management +} + +// This intentionally looks a lot like the one in package net. +var errClosing = errors.New("use of closed network connection") + +func (c *conn) init() error { + c.shard.wg.Add(1) + if shouldExit := c.shard.track(c); shouldExit { + c.Close() + return errClosing + } + return nil +} + +func (c *conn) Read(b []byte) (n int, err error) { + defer func() { + c.mu.Lock() + defer c.mu.Unlock() + + if c.disowned { + return + } + + // This protects against a Close/Read race. We're not really + // concerned about the general case (it's fundamentally racy), + // but are mostly trying to prevent a race between a new request + // getting read off the wire in one thread while the connection + // is being gracefully shut down in another. + if c.closed && err == nil { + n = 0 + err = errClosing + return + } + + if c.mode != Manual && !c.busy && !c.closed { + c.busy = true + c.shard.markInUse(c) + } + }() + + return c.Conn.Read(b) +} + +func (c *conn) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed || c.disowned { + return errClosing + } + + c.closed = true + c.shard.disown(c) + defer c.shard.wg.Done() + + return c.Conn.Close() +} + +func (c *conn) SetReadDeadline(t time.Time) error { + c.mu.Lock() + if !c.disowned && c.mode == Deadline { + defer c.markIdle() + } + c.mu.Unlock() + return c.Conn.SetReadDeadline(t) +} + +func (c *conn) ReadFrom(r io.Reader) (int64, error) { + return io.Copy(c.Conn, r) +} + +func (c *conn) markIdle() { + c.mu.Lock() + defer c.mu.Unlock() + + if !c.busy { + return + } + c.busy = false + + if exit := c.shard.markIdle(c); exit && !c.closed && !c.disowned { + c.closed = true + c.shard.disown(c) + defer c.shard.wg.Done() + c.Conn.Close() + return + } +} + +func (c *conn) markInUse() { + c.mu.Lock() + defer c.mu.Unlock() + + if !c.busy && !c.closed && !c.disowned { + c.busy = true + c.shard.markInUse(c) + } +} + +func (c *conn) closeIfIdle() error { + c.mu.Lock() + defer c.mu.Unlock() + + if !c.busy && !c.closed && !c.disowned { + c.closed = true + c.shard.disown(c) + defer c.shard.wg.Done() + return c.Conn.Close() + } + + return nil +} + +var errAlreadyDisowned = errors.New("listener: conn already disowned") + +func (c *conn) disown() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.disowned { + return errAlreadyDisowned + } + + c.shard.disown(c) + c.disowned = true + c.shard.wg.Done() + + return nil +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn_test.go new file mode 100644 index 0000000..cd5d878 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/conn_test.go @@ -0,0 +1,182 @@ +package listener + +import ( + "io" + "strings" + "testing" + "time" +) + +func TestManualRead(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + go c.AllowRead() + wc.Read(make([]byte, 1024)) + + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if !c.Closed() { + t.Error("Read() should not make connection not-idle") + } +} + +func TestAutomaticRead(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Automatic) + + go c.AllowRead() + wc.Read(make([]byte, 1024)) + + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if c.Closed() { + t.Error("expected Read() to mark connection as in-use") + } +} + +func TestDeadlineRead(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Deadline) + + go c.AllowRead() + if _, err := wc.Read(make([]byte, 1024)); err != nil { + t.Fatalf("error reading from connection: %v", err) + } + + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if c.Closed() { + t.Error("expected Read() to mark connection as in-use") + } +} + +func TestDisownedRead(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Deadline) + + if err := Disown(wc); err != nil { + t.Fatalf("unexpected error disowning conn: %v", err) + } + if err := l.Close(); err != nil { + t.Fatalf("unexpected error closing listener: %v", err) + } + if err := l.Drain(); err != nil { + t.Fatalf("unexpected error draining listener: %v", err) + } + + go c.AllowRead() + if _, err := wc.Read(make([]byte, 1024)); err != nil { + t.Fatalf("error reading from connection: %v", err) + } +} + +func TestCloseConn(t *testing.T) { + t.Parallel() + l, _, wc := singleConn(t, Deadline) + + if err := MarkInUse(wc); err != nil { + t.Fatalf("error marking conn in use: %v", err) + } + if err := wc.Close(); err != nil { + t.Errorf("error closing connection: %v", err) + } + // This will hang if wc.Close() doesn't un-track the connection + if err := l.Drain(); err != nil { + t.Errorf("error draining listener: %v", err) + } +} + +func TestManualReadDeadline(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + if err := MarkInUse(wc); err != nil { + t.Fatalf("error marking connection in use: %v", err) + } + if err := wc.SetReadDeadline(time.Now()); err != nil { + t.Fatalf("error setting read deadline: %v", err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if c.Closed() { + t.Error("SetReadDeadline() should not mark manual conn as idle") + } +} + +func TestAutomaticReadDeadline(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Automatic) + + if err := MarkInUse(wc); err != nil { + t.Fatalf("error marking connection in use: %v", err) + } + if err := wc.SetReadDeadline(time.Now()); err != nil { + t.Fatalf("error setting read deadline: %v", err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if c.Closed() { + t.Error("SetReadDeadline() should not mark automatic conn as idle") + } +} + +func TestDeadlineReadDeadline(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Deadline) + + if err := MarkInUse(wc); err != nil { + t.Fatalf("error marking connection in use: %v", err) + } + if err := wc.SetReadDeadline(time.Now()); err != nil { + t.Fatalf("error setting read deadline: %v", err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if !c.Closed() { + t.Error("SetReadDeadline() should mark deadline conn as idle") + } +} + +type readerConn struct { + fakeConn +} + +func (rc *readerConn) ReadFrom(r io.Reader) (int64, error) { + return 123, nil +} + +func TestReadFrom(t *testing.T) { + t.Parallel() + + l := makeFakeListener("net.Listener") + wl := Wrap(l, Manual) + c := &readerConn{ + fakeConn{ + read: make(chan struct{}), + write: make(chan struct{}), + closed: make(chan struct{}), + me: fakeAddr{"tcp", "local"}, + you: fakeAddr{"tcp", "remote"}, + }, + } + + go l.Enqueue(c) + wc, err := wl.Accept() + if err != nil { + t.Fatalf("error accepting connection: %v", err) + } + + // The io.MultiReader is a convenient hack to ensure that we're using + // our ReadFrom, not strings.Reader's WriteTo. + r := io.MultiReader(strings.NewReader("hello world")) + if _, err := io.Copy(wc, r); err != nil { + t.Fatalf("error copying: %v", err) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/fake_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/fake_test.go new file mode 100644 index 0000000..083f6a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/fake_test.go @@ -0,0 +1,123 @@ +package listener + +import ( + "net" + "time" +) + +type fakeAddr struct { + network, addr string +} + +func (f fakeAddr) Network() string { + return f.network +} +func (f fakeAddr) String() string { + return f.addr +} + +type fakeListener struct { + ch chan net.Conn + closed chan struct{} + addr net.Addr +} + +func makeFakeListener(addr string) *fakeListener { + a := fakeAddr{"tcp", addr} + return &fakeListener{ + ch: make(chan net.Conn), + closed: make(chan struct{}), + addr: a, + } +} + +func (f *fakeListener) Accept() (net.Conn, error) { + select { + case c := <-f.ch: + return c, nil + case <-f.closed: + return nil, errClosing + } +} +func (f *fakeListener) Close() error { + close(f.closed) + return nil +} + +func (f *fakeListener) Addr() net.Addr { + return f.addr +} + +func (f *fakeListener) Enqueue(c net.Conn) { + f.ch <- c +} + +type fakeConn struct { + read, write, closed chan struct{} + me, you net.Addr +} + +func makeFakeConn(me, you string) *fakeConn { + return &fakeConn{ + read: make(chan struct{}), + write: make(chan struct{}), + closed: make(chan struct{}), + me: fakeAddr{"tcp", me}, + you: fakeAddr{"tcp", you}, + } +} + +func (f *fakeConn) Read(buf []byte) (int, error) { + select { + case <-f.read: + return len(buf), nil + case <-f.closed: + return 0, errClosing + } +} + +func (f *fakeConn) Write(buf []byte) (int, error) { + select { + case <-f.write: + return len(buf), nil + case <-f.closed: + return 0, errClosing + } +} + +func (f *fakeConn) Close() error { + close(f.closed) + return nil +} + +func (f *fakeConn) LocalAddr() net.Addr { + return f.me +} +func (f *fakeConn) RemoteAddr() net.Addr { + return f.you +} +func (f *fakeConn) SetDeadline(t time.Time) error { + return nil +} +func (f *fakeConn) SetReadDeadline(t time.Time) error { + return nil +} +func (f *fakeConn) SetWriteDeadline(t time.Time) error { + return nil +} + +func (f *fakeConn) Closed() bool { + select { + case <-f.closed: + return true + default: + return false + } +} + +func (f *fakeConn) AllowRead() { + f.read <- struct{}{} +} +func (f *fakeConn) AllowWrite() { + f.write <- struct{}{} +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener.go new file mode 100644 index 0000000..1a0e59e --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener.go @@ -0,0 +1,178 @@ +/* +Package listener provides a way to incorporate graceful shutdown to any +net.Listener. + +This package provides low-level primitives, not a high-level API. If you're +looking for a package that provides graceful shutdown for HTTP servers, I +recommend this package's parent package, github.com/zenazn/goji/graceful. +*/ +package listener + +import ( + "errors" + "net" + "runtime" + "sync" + "sync/atomic" +) + +type mode int8 + +const ( + // Manual mode is completely manual: users must use use MarkIdle and + // MarkInUse to indicate when connections are busy servicing requests or + // are eligible for termination. + Manual mode = iota + // Automatic mode is what most users probably want: calling Read on a + // connection will mark it as in use, but users must manually call + // MarkIdle to indicate when connections may be safely closed. + Automatic + // Deadline mode is like automatic mode, except that calling + // SetReadDeadline on a connection will also mark it as being idle. This + // is useful for many servers like net/http, where SetReadDeadline is + // used to implement read timeouts on new requests. + Deadline +) + +// Wrap a net.Listener, returning a net.Listener which supports idle connection +// tracking and shutdown. Listeners can be placed in to one of three modes, +// exported as variables from this package: most users will probably want the +// "Automatic" mode. +func Wrap(l net.Listener, m mode) *T { + t := &T{ + l: l, + mode: m, + // To keep the expected contention rate constant we'd have to + // grow this as numcpu**2. In practice, CPU counts don't + // generally grow without bound, and contention is probably + // going to be small enough that nobody cares anyways. + shards: make([]shard, 2*runtime.NumCPU()), + } + for i := range t.shards { + t.shards[i].init(t) + } + return t +} + +// T is the type of this package's graceful listeners. +type T struct { + mu sync.Mutex + l net.Listener + + // TODO(carl): a count of currently outstanding connections. + connCount uint64 + shards []shard + + mode mode +} + +var _ net.Listener = &T{} + +// Accept waits for and returns the next connection to the listener. The +// returned net.Conn's idleness is tracked, and idle connections can be closed +// from the associated T. +func (t *T) Accept() (net.Conn, error) { + c, err := t.l.Accept() + if err != nil { + return nil, err + } + + connID := atomic.AddUint64(&t.connCount, 1) + shard := &t.shards[int(connID)%len(t.shards)] + wc := &conn{ + Conn: c, + shard: shard, + mode: t.mode, + } + + if err = wc.init(); err != nil { + return nil, err + } + return wc, nil +} + +// Addr returns the wrapped listener's network address. +func (t *T) Addr() net.Addr { + return t.l.Addr() +} + +// Close closes the wrapped listener. +func (t *T) Close() error { + return t.l.Close() +} + +// CloseIdle closes all connections that are currently marked as being idle. It, +// however, makes no attempt to wait for in-use connections to die, or to close +// connections which become idle in the future. Call this function if you're +// interested in shedding useless connections, but otherwise wish to continue +// serving requests. +func (t *T) CloseIdle() error { + for i := range t.shards { + t.shards[i].closeConns(false, false) + } + // Not sure if returning errors is actually useful here :/ + return nil +} + +// Drain immediately closes all idle connections, prevents new connections from +// being accepted, and waits for all outstanding connections to finish. +// +// Once a listener has been drained, there is no way to re-enable it. You +// probably want to Close the listener before draining it, otherwise new +// connections will be accepted and immediately closed. +func (t *T) Drain() error { + for i := range t.shards { + t.shards[i].closeConns(false, true) + } + for i := range t.shards { + t.shards[i].wait() + } + return nil +} + +// DrainAll closes all connections currently tracked by this listener (both idle +// and in-use connections), and prevents new connections from being accepted. +// Disowned connections are not closed. +func (t *T) DrainAll() error { + for i := range t.shards { + t.shards[i].closeConns(true, true) + } + for i := range t.shards { + t.shards[i].wait() + } + return nil +} + +var notManagedErr = errors.New("listener: passed net.Conn is not managed by this package") + +// Disown causes a connection to no longer be tracked by the listener. The +// passed connection must have been returned by a call to Accept from this +// listener. +func Disown(c net.Conn) error { + if cn, ok := c.(*conn); ok { + return cn.disown() + } + return notManagedErr +} + +// MarkIdle marks the given connection as being idle, and therefore eligible for +// closing at any time. The passed connection must have been returned by a call +// to Accept from this listener. +func MarkIdle(c net.Conn) error { + if cn, ok := c.(*conn); ok { + cn.markIdle() + return nil + } + return notManagedErr +} + +// MarkInUse marks this connection as being in use, removing it from the set of +// connections which are eligible for closing. The passed connection must have +// been returned by a call to Accept from this listener. +func MarkInUse(c net.Conn) error { + if cn, ok := c.(*conn); ok { + cn.markInUse() + return nil + } + return notManagedErr +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener_test.go new file mode 100644 index 0000000..dcefdaa --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/listener_test.go @@ -0,0 +1,156 @@ +package listener + +import ( + "net" + "testing" + "time" +) + +// Helper for tests acting on a single accepted connection +func singleConn(t *testing.T, m mode) (*T, *fakeConn, net.Conn) { + l := makeFakeListener("net.Listener") + wl := Wrap(l, m) + c := makeFakeConn("local", "remote") + + go l.Enqueue(c) + wc, err := wl.Accept() + if err != nil { + t.Fatalf("error accepting connection: %v", err) + } + return wl, c, wc +} + +func TestAddr(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + if a := l.Addr(); a.String() != "net.Listener" { + t.Errorf("addr was %v, wanted net.Listener", a) + } + + if c.LocalAddr() != wc.LocalAddr() { + t.Errorf("local addresses don't match: %v, %v", c.LocalAddr(), + wc.LocalAddr()) + } + if c.RemoteAddr() != wc.RemoteAddr() { + t.Errorf("remote addresses don't match: %v, %v", c.RemoteAddr(), + wc.RemoteAddr()) + } +} + +func TestBasicCloseIdle(t *testing.T) { + t.Parallel() + l, c, _ := singleConn(t, Manual) + + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if !c.Closed() { + t.Error("idle connection not closed") + } +} + +func TestMark(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + if err := MarkInUse(wc); err != nil { + t.Fatalf("error marking %v in-use: %v", wc, err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if c.Closed() { + t.Errorf("manually in-use connection was closed") + } + + if err := MarkIdle(wc); err != nil { + t.Fatalf("error marking %v idle: %v", wc, err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + if !c.Closed() { + t.Error("manually idle connection was not closed") + } +} + +func TestDisown(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + if err := Disown(wc); err != nil { + t.Fatalf("error disowning connection: %v", err) + } + if err := l.CloseIdle(); err != nil { + t.Fatalf("error closing idle connections: %v", err) + } + + if c.Closed() { + t.Errorf("disowned connection got closed") + } +} + +func TestDrain(t *testing.T) { + t.Parallel() + l, _, wc := singleConn(t, Manual) + + MarkInUse(wc) + start := time.Now() + go func() { + time.Sleep(50 * time.Millisecond) + MarkIdle(wc) + }() + if err := l.Drain(); err != nil { + t.Fatalf("error draining listener: %v", err) + } + end := time.Now() + if dt := end.Sub(start); dt < 50*time.Millisecond { + t.Errorf("expected at least 50ms wait, but got %v", dt) + } +} + +func TestDrainAll(t *testing.T) { + t.Parallel() + l, c, wc := singleConn(t, Manual) + + MarkInUse(wc) + if err := l.DrainAll(); err != nil { + t.Fatalf("error draining listener: %v", err) + } + if !c.Closed() { + t.Error("expected in-use connection to be closed") + } +} + +func TestErrors(t *testing.T) { + t.Parallel() + _, c, wc := singleConn(t, Manual) + if err := Disown(c); err == nil { + t.Error("expected error when disowning unmanaged net.Conn") + } + if err := MarkIdle(c); err == nil { + t.Error("expected error when marking unmanaged net.Conn idle") + } + if err := MarkInUse(c); err == nil { + t.Error("expected error when marking unmanaged net.Conn in use") + } + + if err := Disown(wc); err != nil { + t.Fatalf("unexpected error disowning socket: %v", err) + } + if err := Disown(wc); err == nil { + t.Error("expected error disowning socket twice") + } +} + +func TestClose(t *testing.T) { + t.Parallel() + l, c, _ := singleConn(t, Manual) + if err := l.Close(); err != nil { + t.Fatalf("error while closing listener: %v", err) + } + if c.Closed() { + t.Error("connection closed when listener was?") + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/race_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/race_test.go new file mode 100644 index 0000000..835d6b4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/race_test.go @@ -0,0 +1,103 @@ +package listener + +import ( + "fmt" + "math/rand" + "runtime" + "sync/atomic" + "testing" + "time" +) + +func init() { + // Just to make sure we get some variety + runtime.GOMAXPROCS(4 * runtime.NumCPU()) +} + +// Chosen by random die roll +const seed = 4611413766552969250 + +// This is mostly just fuzzing to see what happens. +func TestRace(t *testing.T) { + t.Parallel() + + l := makeFakeListener("net.Listener") + wl := Wrap(l, Automatic) + + var flag int32 + + go func() { + for i := 0; ; i++ { + laddr := fmt.Sprintf("local%d", i) + raddr := fmt.Sprintf("remote%d", i) + c := makeFakeConn(laddr, raddr) + go func() { + defer func() { + if r := recover(); r != nil { + if atomic.LoadInt32(&flag) != 0 { + return + } + panic(r) + } + }() + l.Enqueue(c) + }() + wc, err := wl.Accept() + if err != nil { + if atomic.LoadInt32(&flag) != 0 { + return + } + t.Fatalf("error accepting connection: %v", err) + } + + go func() { + for { + time.Sleep(50 * time.Millisecond) + c.AllowRead() + } + }() + + go func(i int64) { + rng := rand.New(rand.NewSource(i + seed)) + buf := make([]byte, 1024) + for j := 0; j < 1024; j++ { + if _, err := wc.Read(buf); err != nil { + if atomic.LoadInt32(&flag) != 0 { + // Peaceful; the connection has + // probably been closed while + // idle + return + } + t.Errorf("error reading in conn %d: %v", + i, err) + } + time.Sleep(time.Duration(rng.Intn(100)) * time.Millisecond) + // This one is to make sure the connection + // hasn't closed underneath us + if _, err := wc.Read(buf); err != nil { + t.Errorf("error reading in conn %d: %v", + i, err) + } + MarkIdle(wc) + time.Sleep(time.Duration(rng.Intn(100)) * time.Millisecond) + } + }(int64(i)) + + time.Sleep(time.Duration(i) * time.Millisecond / 2) + } + }() + + if testing.Short() { + time.Sleep(2 * time.Second) + } else { + time.Sleep(10 * time.Second) + } + start := time.Now() + atomic.StoreInt32(&flag, 1) + wl.Close() + wl.Drain() + end := time.Now() + if dt := end.Sub(start); dt > 300*time.Millisecond { + t.Errorf("took %v to drain; expected shorter", dt) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard.go new file mode 100644 index 0000000..a9addad --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard.go @@ -0,0 +1,98 @@ +package listener + +import "sync" + +type shard struct { + l *T + + mu sync.Mutex + idle map[*conn]struct{} + all map[*conn]struct{} + wg sync.WaitGroup + drain bool +} + +// We pretty aggressively preallocate set entries in the hopes that we never +// have to allocate memory with the lock held. This is definitely a premature +// optimization and is probably misguided, but luckily it costs us essentially +// nothing. +const prealloc = 2048 + +func (s *shard) init(l *T) { + s.l = l + s.idle = make(map[*conn]struct{}, prealloc) + s.all = make(map[*conn]struct{}, prealloc) +} + +func (s *shard) track(c *conn) (shouldClose bool) { + s.mu.Lock() + if s.drain { + s.mu.Unlock() + return true + } + s.all[c] = struct{}{} + s.idle[c] = struct{}{} + s.mu.Unlock() + return false +} + +func (s *shard) disown(c *conn) { + s.mu.Lock() + delete(s.all, c) + delete(s.idle, c) + s.mu.Unlock() +} + +func (s *shard) markIdle(c *conn) (shouldClose bool) { + s.mu.Lock() + if s.drain { + s.mu.Unlock() + return true + } + s.idle[c] = struct{}{} + s.mu.Unlock() + return false +} + +func (s *shard) markInUse(c *conn) { + s.mu.Lock() + delete(s.idle, c) + s.mu.Unlock() +} + +func (s *shard) closeConns(all, drain bool) { + s.mu.Lock() + if drain { + s.drain = true + } + set := make(map[*conn]struct{}, len(s.all)) + if all { + for c := range s.all { + set[c] = struct{}{} + } + } else { + for c := range s.idle { + set[c] = struct{}{} + } + } + // We have to drop the shard lock here to avoid deadlock: we cannot + // acquire the shard lock after the connection lock, and the closeIfIdle + // call below will grab a connection lock. + s.mu.Unlock() + + for c := range set { + // This might return an error (from Close), but I don't think we + // can do anything about it, so let's just pretend it didn't + // happen. (I also expect that most errors returned in this way + // are going to be pretty boring) + if all { + c.Close() + } else { + c.closeIfIdle() + } + } +} + +func (s *shard) wait() { + s.wg.Wait() +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard_test.go new file mode 100644 index 0000000..9b99394 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/listener/shard_test.go @@ -0,0 +1,21 @@ +// +build amd64 + +package listener + +import ( + "testing" + "unsafe" +) + +// We pack shards together in an array, but we don't want them packed too +// closely, since we want to give each shard a dedicated CPU cache line. This +// test checks this property for x64 (which has a 64-byte cache line), which +// probably covers the majority of deployments. +// +// As always, this is probably a premature optimization. +func TestShardSize(t *testing.T) { + s := unsafe.Sizeof(shard{}) + if s < 64 { + t.Errorf("sizeof(shard) = %d; expected >64", s) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware.go new file mode 100644 index 0000000..94edfe3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware.go @@ -0,0 +1,103 @@ +// +build !go1.3 + +package graceful + +import ( + "bufio" + "io" + "net" + "net/http" + "sync/atomic" + + "github.com/zenazn/goji/graceful/listener" +) + +// Middleware provides functionality similar to net/http.Server's +// SetKeepAlivesEnabled in Go 1.3, but in Go 1.2. +func middleware(h http.Handler) http.Handler { + if h == nil { + return nil + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, cn := w.(http.CloseNotifier) + _, fl := w.(http.Flusher) + _, hj := w.(http.Hijacker) + _, rf := w.(io.ReaderFrom) + + bw := basicWriter{ResponseWriter: w} + + if cn && fl && hj && rf { + h.ServeHTTP(&fancyWriter{bw}, r) + } else { + h.ServeHTTP(&bw, r) + } + if !bw.headerWritten { + bw.maybeClose() + } + }) +} + +type basicWriter struct { + http.ResponseWriter + headerWritten bool +} + +func (b *basicWriter) maybeClose() { + b.headerWritten = true + if atomic.LoadInt32(&closing) != 0 { + b.ResponseWriter.Header().Set("Connection", "close") + } +} + +func (b *basicWriter) WriteHeader(code int) { + b.maybeClose() + b.ResponseWriter.WriteHeader(code) +} + +func (b *basicWriter) Write(buf []byte) (int, error) { + if !b.headerWritten { + b.maybeClose() + } + return b.ResponseWriter.Write(buf) +} + +func (b *basicWriter) Unwrap() http.ResponseWriter { + return b.ResponseWriter +} + +// Optimize for the common case of a ResponseWriter that supports all three of +// CloseNotifier, Flusher, and Hijacker. +type fancyWriter struct { + basicWriter +} + +func (f *fancyWriter) CloseNotify() <-chan bool { + cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) + return cn.CloseNotify() +} +func (f *fancyWriter) Flush() { + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} +func (f *fancyWriter) Hijack() (c net.Conn, b *bufio.ReadWriter, e error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + c, b, e = hj.Hijack() + + if e == nil { + e = listener.Disown(c) + } + + return +} +func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { + rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) + if !f.basicWriter.headerWritten { + f.basicWriter.maybeClose() + } + return rf.ReadFrom(r) +} + +var _ http.CloseNotifier = &fancyWriter{} +var _ http.Flusher = &fancyWriter{} +var _ http.Hijacker = &fancyWriter{} +var _ io.ReaderFrom = &fancyWriter{} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware_test.go new file mode 100644 index 0000000..aa4e623 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/middleware_test.go @@ -0,0 +1,71 @@ +// +build !go1.3 + +package graceful + +import ( + "net/http" + "sync/atomic" + "testing" +) + +type fakeWriter http.Header + +func (f fakeWriter) Header() http.Header { + return http.Header(f) +} +func (f fakeWriter) Write(buf []byte) (int, error) { + return len(buf), nil +} +func (f fakeWriter) WriteHeader(status int) {} + +func testClose(t *testing.T, h http.Handler, expectClose bool) { + m := middleware(h) + r, _ := http.NewRequest("GET", "/", nil) + w := make(fakeWriter) + m.ServeHTTP(w, r) + + c, ok := w["Connection"] + if expectClose { + if !ok || len(c) != 1 || c[0] != "close" { + t.Fatal("Expected 'Connection: close'") + } + } else { + if ok { + t.Fatal("Did not expect Connection header") + } + } +} + +func TestNormal(t *testing.T) { + atomic.StoreInt32(&closing, 0) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte{}) + }) + testClose(t, h, false) +} + +func TestClose(t *testing.T) { + atomic.StoreInt32(&closing, 0) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.StoreInt32(&closing, 1) + }) + testClose(t, h, true) +} + +func TestCloseWriteHeader(t *testing.T) { + atomic.StoreInt32(&closing, 0) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.StoreInt32(&closing, 1) + w.WriteHeader(200) + }) + testClose(t, h, true) +} + +func TestCloseWrite(t *testing.T) { + atomic.StoreInt32(&closing, 0) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + atomic.StoreInt32(&closing, 1) + w.Write([]byte{}) + }) + testClose(t, h, true) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve.go new file mode 100644 index 0000000..567fb8e --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve.go @@ -0,0 +1,32 @@ +// +build !go1.3 + +package graceful + +import ( + "net" + "net/http" + "time" + + "github.com/zenazn/goji/graceful/listener" +) + +// About 200 years, also known as "forever" +const forever time.Duration = 200 * 365 * 24 * time.Hour + +func (srv *Server) Serve(l net.Listener) error { + // Spawn a shadow http.Server to do the actual servering. We do this + // because we need to sketch on some of the parameters you passed in, + // and it's nice to keep our sketching to ourselves. + shadow := *(*http.Server)(srv) + + if shadow.ReadTimeout == 0 { + shadow.ReadTimeout = forever + } + shadow.Handler = middleware(shadow.Handler) + + wrap := listener.Wrap(l, listener.Deadline) + appendListener(wrap) + + err := shadow.Serve(wrap) + return peacefulError(err) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve13.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve13.go new file mode 100644 index 0000000..b11f10a --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/serve13.go @@ -0,0 +1,75 @@ +// +build go1.3 + +package graceful + +import ( + "log" + "net" + "net/http" + + "github.com/zenazn/goji/graceful/listener" +) + +// This is a slightly hacky shim to disable keepalives when shutting a server +// down. We could have added extra functionality in listener or signal.go to +// deal with this case, but this seems simpler. +type gracefulServer struct { + net.Listener + s *http.Server +} + +func (g gracefulServer) Close() error { + g.s.SetKeepAlivesEnabled(false) + return g.Listener.Close() +} + +// A chaining http.ConnState wrapper +type connState func(net.Conn, http.ConnState) + +func (c connState) Wrap(nc net.Conn, s http.ConnState) { + // There are a few other states defined, most notably StateActive. + // Unfortunately it doesn't look like it's possible to make use of + // StateActive to implement graceful shutdown, since StateActive is set + // after a complete request has been read off the wire with an intent to + // process it. If we were to race a graceful shutdown against a + // connection that was just read off the wire (but not yet in + // StateActive), we would accidentally close the connection out from + // underneath an active request. + // + // We already needed to work around this for Go 1.2 by shimming out a + // full net.Conn object, so we can just fall back to the old behavior + // there. + // + // I started a golang-nuts thread about this here: + // https://groups.google.com/forum/#!topic/golang-nuts/Xi8yjBGWfCQ + // I'd be very eager to find a better way to do this, so reach out to me + // if you have any ideas. + switch s { + case http.StateIdle: + if err := listener.MarkIdle(nc); err != nil { + log.Printf("error marking conn as idle: %v", err) + } + case http.StateHijacked: + if err := listener.Disown(nc); err != nil { + log.Printf("error disowning hijacked conn: %v", err) + } + } + if c != nil { + c(nc, s) + } +} + +func (srv *Server) Serve(l net.Listener) error { + // Spawn a shadow http.Server to do the actual servering. We do this + // because we need to sketch on some of the parameters you passed in, + // and it's nice to keep our sketching to ourselves. + shadow := *(*http.Server)(srv) + shadow.ConnState = connState(shadow.ConnState).Wrap + + l = gracefulServer{l, &shadow} + wrap := listener.Wrap(l, listener.Automatic) + appendListener(wrap) + + err := shadow.Serve(wrap) + return peacefulError(err) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/server.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/server.go new file mode 100644 index 0000000..ab69236 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/server.go @@ -0,0 +1,82 @@ +package graceful + +import ( + "crypto/tls" + "net" + "net/http" +) + +// Most of the code here is lifted straight from net/http + +// Type Server is exactly the same as an http.Server, but provides more graceful +// implementations of its methods. +type Server http.Server + +func (srv *Server) ListenAndServe() error { + addr := srv.Addr + if addr == "" { + addr = ":http" + } + l, e := net.Listen("tcp", addr) + if e != nil { + return e + } + return srv.Serve(l) +} + +// Unlike the method of the same name on http.Server, this function defaults to +// enforcing TLS 1.0 or higher in order to address the POODLE vulnerability. +// Users who wish to enable SSLv3 must do so by supplying a TLSConfig +// explicitly. +func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { + addr := srv.Addr + if addr == "" { + addr = ":https" + } + config := &tls.Config{ + MinVersion: tls.VersionTLS10, + } + if srv.TLSConfig != nil { + *config = *srv.TLSConfig + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + + conn, err := net.Listen("tcp", addr) + if err != nil { + return err + } + + tlsListener := tls.NewListener(conn, config) + return srv.Serve(tlsListener) +} + +// ListenAndServe behaves exactly like the net/http function of the same name. +func ListenAndServe(addr string, handler http.Handler) error { + server := &Server{Addr: addr, Handler: handler} + return server.ListenAndServe() +} + +// ListenAndServeTLS behaves almost exactly like the net/http function of the +// same name. Unlike net/http, however, this function defaults to enforcing TLS +// 1.0 or higher in order to address the POODLE vulnerability. Users who wish to +// enable SSLv3 must do so by explicitly instantiating a server with an +// appropriately configured TLSConfig property. +func ListenAndServeTLS(addr, certfile, keyfile string, handler http.Handler) error { + server := &Server{Addr: addr, Handler: handler} + return server.ListenAndServeTLS(certfile, keyfile) +} + +// Serve behaves exactly like the net/http function of the same name. +func Serve(l net.Listener, handler http.Handler) error { + server := &Server{Handler: handler} + return server.Serve(l) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/graceful/signal.go b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/signal.go new file mode 100644 index 0000000..60612b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/graceful/signal.go @@ -0,0 +1,197 @@ +package graceful + +import ( + "os" + "os/signal" + "sync" + "sync/atomic" + "time" + + "github.com/zenazn/goji/graceful/listener" +) + +var mu sync.Mutex // protects everything that follows +var listeners = make([]*listener.T, 0) +var prehooks = make([]func(), 0) +var posthooks = make([]func(), 0) +var closing int32 +var doubleKick, timeout time.Duration + +var wait = make(chan struct{}) +var stdSignals = []os.Signal{os.Interrupt} +var sigchan = make(chan os.Signal, 1) + +// HandleSignals installs signal handlers for a set of standard signals. By +// default, this set only includes keyboard interrupts, however when the package +// detects that it is running under Einhorn, a SIGUSR2 handler is installed as +// well. +func HandleSignals() { + AddSignal(stdSignals...) +} + +// AddSignal adds the given signal to the set of signals that trigger a graceful +// shutdown. +func AddSignal(sig ...os.Signal) { + signal.Notify(sigchan, sig...) +} + +// ResetSignals resets the list of signals that trigger a graceful shutdown. +func ResetSignals() { + signal.Stop(sigchan) +} + +// PreHook registers a function to be called before any of this package's normal +// shutdown actions. All listeners will be called in the order they were added, +// from a single goroutine. +func PreHook(f func()) { + mu.Lock() + defer mu.Unlock() + + prehooks = append(prehooks, f) +} + +// PostHook registers a function to be called after all of this package's normal +// shutdown actions. All listeners will be called in the order they were added, +// from a single goroutine, and are guaranteed to be called after all listening +// connections have been closed, but before Wait() returns. +// +// If you've Hijacked any connections that must be gracefully shut down in some +// other way (since this library disowns all hijacked connections), it's +// reasonable to use a PostHook to signal and wait for them. +func PostHook(f func()) { + mu.Lock() + defer mu.Unlock() + + posthooks = append(posthooks, f) +} + +// Shutdown manually triggers a shutdown from your application. Like Wait, +// blocks until all connections have gracefully shut down. +func Shutdown() { + shutdown(false) +} + +// ShutdownNow triggers an immediate shutdown from your application. All +// connections (not just those that are idle) are immediately closed, even if +// they are in the middle of serving a request. +func ShutdownNow() { + shutdown(true) +} + +// DoubleKickWindow sets the length of the window during which two back-to-back +// signals are treated as an especially urgent or forceful request to exit +// (i.e., ShutdownNow instead of Shutdown). Signals delivered more than this +// duration apart are treated as separate requests to exit gracefully as usual. +// +// Setting DoubleKickWindow to 0 disables the feature. +func DoubleKickWindow(d time.Duration) { + if d < 0 { + return + } + mu.Lock() + defer mu.Unlock() + + doubleKick = d +} + +// Timeout sets the maximum amount of time package graceful will wait for +// connections to gracefully shut down after receiving a signal. After this +// timeout, connections will be forcefully shut down (similar to calling +// ShutdownNow). +// +// Setting Timeout to 0 disables the feature. +func Timeout(d time.Duration) { + if d < 0 { + return + } + mu.Lock() + defer mu.Unlock() + + timeout = d +} + +// Wait for all connections to gracefully shut down. This is commonly called at +// the bottom of the main() function to prevent the program from exiting +// prematurely. +func Wait() { + <-wait +} + +func init() { + go sigLoop() +} +func sigLoop() { + var last time.Time + for { + <-sigchan + now := time.Now() + mu.Lock() + force := doubleKick != 0 && now.Sub(last) < doubleKick + if t := timeout; t != 0 && !force { + go func() { + time.Sleep(t) + shutdown(true) + }() + } + mu.Unlock() + go shutdown(force) + last = now + } +} + +var preOnce, closeOnce, forceOnce, postOnce, notifyOnce sync.Once + +func shutdown(force bool) { + preOnce.Do(func() { + mu.Lock() + defer mu.Unlock() + for _, f := range prehooks { + f() + } + }) + + if force { + forceOnce.Do(func() { + closeListeners(force) + }) + } else { + closeOnce.Do(func() { + closeListeners(force) + }) + } + + postOnce.Do(func() { + mu.Lock() + defer mu.Unlock() + for _, f := range posthooks { + f() + } + }) + + notifyOnce.Do(func() { + close(wait) + }) +} + +func closeListeners(force bool) { + atomic.StoreInt32(&closing, 1) + + var wg sync.WaitGroup + defer wg.Wait() + + mu.Lock() + defer mu.Unlock() + wg.Add(len(listeners)) + + for _, l := range listeners { + go func(l *listener.T) { + defer wg.Done() + l.Close() + if force { + l.DrainAll() + } else { + l.Drain() + } + }(l) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic.go new file mode 100644 index 0000000..795d8e5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic.go @@ -0,0 +1,18 @@ +// +build !appengine + +package web + +import ( + "sync/atomic" + "unsafe" +) + +func (rt *router) getMachine() *routeMachine { + ptr := (*unsafe.Pointer)(unsafe.Pointer(&rt.machine)) + sm := (*routeMachine)(atomic.LoadPointer(ptr)) + return sm +} +func (rt *router) setMachine(m *routeMachine) { + ptr := (*unsafe.Pointer)(unsafe.Pointer(&rt.machine)) + atomic.StorePointer(ptr, unsafe.Pointer(m)) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic_appengine.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic_appengine.go new file mode 100644 index 0000000..027127a --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/atomic_appengine.go @@ -0,0 +1,14 @@ +// +build appengine + +package web + +func (rt *router) getMachine() *routeMachine { + rt.lock.Lock() + defer rt.lock.Unlock() + return rt.machine +} + +// We always hold the lock when calling setMachine. +func (rt *router) setMachine(m *routeMachine) { + rt.machine = m +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/bench_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/bench_test.go new file mode 100644 index 0000000..57ecf41 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/bench_test.go @@ -0,0 +1,153 @@ +package web + +import ( + "crypto/rand" + "encoding/base64" + mrand "math/rand" + "net/http" + "testing" +) + +/* +The core benchmarks here are based on cypriss's mux benchmarks, which can be +found here: +https://github.com/cypriss/golang-mux-benchmark + +They happen to play very well into Goji's router's strengths. +*/ + +type nilRouter struct{} + +var helloWorld = []byte("Hello world!\n") + +func (_ nilRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + w.Write(helloWorld) +} + +type nilResponse struct{} + +func (_ nilResponse) Write(buf []byte) (int, error) { + return len(buf), nil +} +func (_ nilResponse) Header() http.Header { + return nil +} +func (_ nilResponse) WriteHeader(code int) { +} + +func trivialMiddleware(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + +var w nilResponse + +func addRoutes(m *Mux, prefix string) { + m.Get(prefix, nilRouter{}) + m.Post(prefix, nilRouter{}) + m.Get(prefix+"/:id", nilRouter{}) + m.Put(prefix+"/:id", nilRouter{}) + m.Delete(prefix+"/:id", nilRouter{}) +} + +func randString() string { + var buf [6]byte + rand.Reader.Read(buf[:]) + return base64.URLEncoding.EncodeToString(buf[:]) +} + +func genPrefixes(n int) []string { + p := make([]string, n) + for i := range p { + p[i] = "/" + randString() + } + return p +} + +func genRequests(prefixes []string) []*http.Request { + rs := make([]*http.Request, 5*len(prefixes)) + for i, prefix := range prefixes { + rs[5*i+0], _ = http.NewRequest("GET", prefix, nil) + rs[5*i+1], _ = http.NewRequest("POST", prefix, nil) + rs[5*i+2], _ = http.NewRequest("GET", prefix+"/foo", nil) + rs[5*i+3], _ = http.NewRequest("PUT", prefix+"/foo", nil) + rs[5*i+4], _ = http.NewRequest("DELETE", prefix+"/foo", nil) + } + return rs +} + +func permuteRequests(reqs []*http.Request) []*http.Request { + out := make([]*http.Request, len(reqs)) + perm := mrand.Perm(len(reqs)) + for i, req := range reqs { + out[perm[i]] = req + } + return out +} + +func benchN(b *testing.B, n int) { + m := New() + prefixes := genPrefixes(n) + for _, prefix := range prefixes { + addRoutes(m, prefix) + } + reqs := permuteRequests(genRequests(prefixes)) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + m.ServeHTTP(w, reqs[i%len(reqs)]) + } +} + +func benchM(b *testing.B, n int) { + m := New() + m.Get("/", nilRouter{}) + for i := 0; i < n; i++ { + m.Use(trivialMiddleware) + } + r, _ := http.NewRequest("GET", "/", nil) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + m.ServeHTTP(w, r) + } +} + +func BenchmarkStatic(b *testing.B) { + m := New() + m.Get("/", nilRouter{}) + r, _ := http.NewRequest("GET", "/", nil) + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + m.ServeHTTP(w, r) + } +} + +func BenchmarkRoute5(b *testing.B) { + benchN(b, 1) +} +func BenchmarkRoute50(b *testing.B) { + benchN(b, 10) +} +func BenchmarkRoute500(b *testing.B) { + benchN(b, 100) +} +func BenchmarkRoute5000(b *testing.B) { + benchN(b, 1000) +} + +func BenchmarkMiddleware1(b *testing.B) { + benchM(b, 1) +} +func BenchmarkMiddleware10(b *testing.B) { + benchM(b, 10) +} +func BenchmarkMiddleware100(b *testing.B) { + benchM(b, 100) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_compiler.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_compiler.go new file mode 100644 index 0000000..b6f52b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_compiler.go @@ -0,0 +1,265 @@ +package web + +/* +This file implements a fast router by encoding a list of routes first into a +pseudo-trie, then encoding that pseudo-trie into a state machine realized as +a routing bytecode. + +The most interesting part of this router is not its speed (it is quite fast), +but the guarantees it provides. In a naive router, routes are examined one after +another until a match is found, and this is the programming model we want to +support. For any given request ("GET /hello/carl"), there is a list of +"plausible" routes: routes which match the method ("GET"), and which have a +prefix that is a prefix of the requested path ("/" and "/hello/", for instance, +but not "/foobar"). Patterns also have some amount of arbitrary code associated +with them, which tells us whether or not the route matched. Just like the naive +router, our goal is to call each plausible pattern, in the order they were +added, until we find one that matches. The "fast" part here is being smart about +which non-plausible routes we can skip. + +First, we sort routes using a pairwise comparison function: sorting occurs as +normal on the prefixes, with the caveat that a route may not be moved past a +route that might also match the same string. Among other things, this means +we're forced to use particularly dumb sorting algorithms, but it only has to +happen once, and there probably aren't even that many routes to begin with. This +logic appears inline in the router's handle() function. + +We then build a pseudo-trie from the sorted list of routes. It's not quite a +normal trie because there are certain routes we cannot reorder around other +routes (since we're providing identical semantics to the naive router), but it's +close enough and the basic idea is the same. + +Finally, we lower this psuedo-trie from its tree representation to a state +machine bytecode. The bytecode is pretty simple: it contains up to three bytes, +a choice of a bunch of flags, and an index. The state machine is pretty simple: +if the bytes match the next few bytes after the cursor, the instruction matches, +and the state machine advances to the next instruction. If it does not match, it +jumps to the instruction at the index. Various flags modify this basic behavior, +the documentation for which can be found below. + +The thing we're optimizing for here over pretty much everything else is memory +locality. We make an effort to lay out both the trie child selection logic and +the matching of long strings consecutively in memory, making both operations +very cheap. In fact, our matching logic isn't particularly asymptotically good, +but in practice the benefits of memory locality outweigh just about everything +else. + +Unfortunately, the code implementing all of this is pretty bad (both inefficient +and hard to read). Maybe someday I'll come and take a second pass at it. +*/ +type state struct { + mode smMode + bs [3]byte + i int32 +} +type stateMachine []state + +type smMode uint8 + +// Many combinations of smModes don't make sense, but since this is interal to +// the library I don't feel like documenting them. +const ( + // The two low bits of the mode are used as a length of how many bytes + // of bs are used. If the length is 0, the node is treated as a + // wildcard. + smLengthMask smMode = 3 +) + +const ( + // Jump to the given index on a match. Ordinarily, the state machine + // will jump to the state given by the index if the characters do not + // match. + smJumpOnMatch smMode = 4 << iota + // The index is the index of a route to try. If running the route fails, + // the state machine advances by one. + smRoute + // Reset the state machine's cursor into the input string to the state's + // index value. + smSetCursor + // If this bit is set, the machine transitions into a non-accepting + // state if it matches. + smFail +) + +type trie struct { + prefix string + children []trieSegment +} + +// A trie segment is a route matching this point (or -1), combined with a list +// of trie children that follow that route. +type trieSegment struct { + route int + children []trie +} + +func buildTrie(routes []route, dp, dr int) trie { + var t trie + ts := trieSegment{-1, nil} + for i, r := range routes { + if len(r.prefix) != dp { + continue + } + + if i == 0 { + ts.route = 0 + } else { + subroutes := routes[ts.route+1 : i] + ts.children = buildTrieSegment(subroutes, dp, dr+ts.route+1) + t.children = append(t.children, ts) + ts = trieSegment{i, nil} + } + } + + // This could be a little DRYer... + subroutes := routes[ts.route+1:] + ts.children = buildTrieSegment(subroutes, dp, dr+ts.route+1) + t.children = append(t.children, ts) + + for i := range t.children { + if t.children[i].route != -1 { + t.children[i].route += dr + } + } + + return t +} + +func commonPrefix(s1, s2 string) string { + if len(s1) > len(s2) { + return commonPrefix(s2, s1) + } + for i := 0; i < len(s1); i++ { + if s1[i] != s2[i] { + return s1[:i] + } + } + return s1 +} + +func buildTrieSegment(routes []route, dp, dr int) []trie { + if len(routes) == 0 { + return nil + } + var tries []trie + + start := 0 + p := routes[0].prefix[dp:] + for i := 1; i < len(routes); i++ { + ip := routes[i].prefix[dp:] + cp := commonPrefix(p, ip) + if len(cp) == 0 { + t := buildTrie(routes[start:i], dp+len(p), dr+start) + t.prefix = p + tries = append(tries, t) + start = i + p = ip + } else { + p = cp + } + } + + t := buildTrie(routes[start:], dp+len(p), dr+start) + t.prefix = p + return append(tries, t) +} + +// This is a bit confusing, since the encode method on a trie deals exclusively +// with trieSegments (i.e., its children), and vice versa. +// +// These methods are also hideously inefficient, both in terms of memory usage +// and algorithmic complexity. If it ever becomes a problem, maybe we can do +// something smarter than stupid O(N^2) appends, but to be honest, I bet N is +// small (it almost always is :P) and we only do it once at boot anyways. + +func (t trie) encode(dp, off int) stateMachine { + ms := make([]stateMachine, len(t.children)) + subs := make([]stateMachine, len(t.children)) + var l, msl, subl int + + for i, ts := range t.children { + ms[i], subs[i] = ts.encode(dp, 0) + msl += len(ms[i]) + l += len(ms[i]) + len(subs[i]) + } + + l++ + + m := make(stateMachine, 0, l) + for i, mm := range ms { + for j := range mm { + if mm[j].mode&(smRoute|smSetCursor) != 0 { + continue + } + + mm[j].i += int32(off + msl + subl + 1) + } + m = append(m, mm...) + subl += len(subs[i]) + } + + m = append(m, state{mode: smJumpOnMatch, i: -1}) + + msl = 0 + for i, sub := range subs { + msl += len(ms[i]) + for j := range sub { + if sub[j].mode&(smRoute|smSetCursor) != 0 { + continue + } + if sub[j].i == -1 { + sub[j].i = int32(off + msl) + } else { + sub[j].i += int32(off + len(m)) + } + } + m = append(m, sub...) + } + + return m +} + +func (ts trieSegment) encode(dp, off int) (me stateMachine, sub stateMachine) { + o := 1 + if ts.route != -1 { + o++ + } + me = make(stateMachine, len(ts.children)+o) + + me[0] = state{mode: smSetCursor, i: int32(dp)} + if ts.route != -1 { + me[1] = state{mode: smRoute, i: int32(ts.route)} + } + + for i, t := range ts.children { + p := t.prefix + + bc := copy(me[i+o].bs[:], p) + me[i+o].mode = smMode(bc) | smJumpOnMatch + me[i+o].i = int32(off + len(sub)) + + for len(p) > bc { + var bs [3]byte + p = p[bc:] + bc = copy(bs[:], p) + sub = append(sub, state{bs: bs, mode: smMode(bc), i: -1}) + } + + sub = append(sub, t.encode(dp+len(t.prefix), off+len(sub))...) + } + return +} + +func compile(routes []route) stateMachine { + if len(routes) == 0 { + return nil + } + t := buildTrie(routes, 0, 0) + m := t.encode(0, 0) + for i := range m { + if m[i].i == -1 { + m[i].mode = m[i].mode | smFail + } + } + return m +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_runner.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_runner.go new file mode 100644 index 0000000..5fcf443 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/bytecode_runner.go @@ -0,0 +1,85 @@ +package web + +import "net/http" + +type routeMachine struct { + sm stateMachine + routes []route +} + +func matchRoute(route route, m method, ms *method, r *http.Request, c *C) bool { + if !route.pattern.Match(r, c) { + return false + } + *ms |= route.method + + if route.method&m != 0 { + route.pattern.Run(r, c) + return true + } + return false +} + +func (rm routeMachine) route(c *C, w http.ResponseWriter, r *http.Request) (method, *route) { + m := httpMethod(r.Method) + var methods method + p := r.URL.Path + + if len(rm.sm) == 0 { + return methods, nil + } + + var i int + for { + sm := rm.sm[i].mode + if sm&smSetCursor != 0 { + si := rm.sm[i].i + p = r.URL.Path[si:] + i++ + continue + } + + length := int(sm & smLengthMask) + match := false + if length <= len(p) { + bs := rm.sm[i].bs + switch length { + case 3: + if p[2] != bs[2] { + break + } + fallthrough + case 2: + if p[1] != bs[1] { + break + } + fallthrough + case 1: + if p[0] != bs[0] { + break + } + fallthrough + case 0: + p = p[length:] + match = true + } + } + + if match && sm&smRoute != 0 { + si := rm.sm[i].i + if matchRoute(rm.routes[si], m, &methods, r, c) { + return 0, &rm.routes[si] + } + i++ + } else if match != (sm&smJumpOnMatch == 0) { + if sm&smFail != 0 { + return methods, nil + } + i = int(rm.sm[i].i) + } else { + i++ + } + } + + return methods, nil +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/chanpool.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/chanpool.go new file mode 100644 index 0000000..fbe2977 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/chanpool.go @@ -0,0 +1,31 @@ +// +build !go1.3 + +package web + +// This is an alternate implementation of Go 1.3's sync.Pool. + +// Maximum size of the pool of spare middleware stacks +const cPoolSize = 32 + +type cPool chan *cStack + +func makeCPool() *cPool { + var p cPool = make(chan *cStack, cPoolSize) + return &p +} + +func (c cPool) alloc() *cStack { + select { + case cs := <-c: + return cs + default: + return nil + } +} + +func (c cPool) release(cs *cStack) { + select { + case c <- cs: + default: + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/cpool.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/cpool.go new file mode 100644 index 0000000..59f8764 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/cpool.go @@ -0,0 +1,23 @@ +// +build go1.3 + +package web + +import "sync" + +type cPool sync.Pool + +func makeCPool() *cPool { + return &cPool{} +} + +func (c *cPool) alloc() *cStack { + cs := (*sync.Pool)(c).Get() + if cs == nil { + return nil + } + return cs.(*cStack) +} + +func (c *cPool) release(cs *cStack) { + (*sync.Pool)(c).Put(cs) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal.go new file mode 100644 index 0000000..3206b04 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal.go @@ -0,0 +1,38 @@ +package web + +import ( + "reflect" +) + +func isFunc(fn interface{}) bool { + return reflect.ValueOf(fn).Kind() == reflect.Func +} + +/* +This is more than a little sketchtacular. Go's rules for function pointer +equality are pretty restrictive: nil function pointers always compare equal, and +all other pointer types never do. However, this is pretty limiting: it means +that we can't let people reference the middleware they've given us since we have +no idea which function they're referring to. + +To get better data out of Go, we sketch on the representation of interfaces. We +happen to know that interfaces are pairs of pointers: one to the real data, one +to data about the type. Therefore, two interfaces, including two function +interface{}'s, point to exactly the same objects iff their interface +representations are identical. And it turns out this is sufficient for our +purposes. + +If you're curious, you can read more about the representation of functions here: +http://golang.org/s/go11func +We're in effect comparing the pointers of the indirect layer. +*/ +func funcEqual(a, b interface{}) bool { + if !isFunc(a) || !isFunc(b) { + panic("funcEqual: type error!") + } + + av := reflect.ValueOf(&a).Elem() + bv := reflect.ValueOf(&b).Elem() + + return av.InterfaceData() == bv.InterfaceData() +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal_test.go new file mode 100644 index 0000000..9080a5a --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/func_equal_test.go @@ -0,0 +1,84 @@ +package web + +import ( + "testing" +) + +// To tell you the truth, I'm not actually sure how many of these cases are +// needed. Presumably someone with more patience than I could comb through +// http://golang.org/s/go11func and figure out what all the different cases I +// ought to test are, but I think this test includes all the cases I care about +// and is at least reasonably thorough. + +func a() string { + return "A" +} +func b() string { + return "B" +} +func mkFn(s string) func() string { + return func() string { + return s + } +} + +var c = mkFn("C") +var d = mkFn("D") +var e = a +var f = c +var g = mkFn("D") + +type Type string + +func (t *Type) String() string { + return string(*t) +} + +var t1 = Type("hi") +var t2 = Type("bye") +var t1f = t1.String +var t2f = t2.String + +var funcEqualTests = []struct { + a, b func() string + result bool +}{ + {a, a, true}, + {a, b, false}, + {b, b, true}, + {a, c, false}, + {c, c, true}, + {c, d, false}, + {a, e, true}, + {a, f, false}, + {c, f, true}, + {e, f, false}, + {d, g, false}, + {t1f, t1f, true}, + {t1f, t2f, false}, +} + +func TestFuncEqual(t *testing.T) { + t.Parallel() + + for _, test := range funcEqualTests { + r := funcEqual(test.a, test.b) + if r != test.result { + t.Errorf("funcEqual(%v, %v) should have been %v", + test.a, test.b, test.result) + } + } + h := mkFn("H") + i := h + j := mkFn("H") + k := a + if !funcEqual(h, i) { + t.Errorf("h and i should have been equal") + } + if funcEqual(h, j) { + t.Errorf("h and j should not have been equal") + } + if !funcEqual(a, k) { + t.Errorf("a and k should have been equal") + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware.go new file mode 100644 index 0000000..21e000b --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware.go @@ -0,0 +1,154 @@ +package web + +import ( + "fmt" + "log" + "net/http" + "sync" +) + +// mLayer is a single middleware stack layer. It contains a canonicalized +// middleware representation, as well as the original function as passed to us. +type mLayer struct { + fn func(*C, http.Handler) http.Handler + orig interface{} +} + +// mStack is an entire middleware stack. It contains a slice of middleware +// layers (outermost first) protected by a mutex, a cache of pre-built stack +// instances, and a final routing function. +type mStack struct { + lock sync.Mutex + stack []mLayer + pool *cPool + router internalRouter +} + +type internalRouter interface { + route(*C, http.ResponseWriter, *http.Request) +} + +/* +cStack is a cached middleware stack instance. Constructing a middleware stack +involves a lot of allocations: at the very least each layer will have to close +over the layer after (inside) it and a stack N levels deep will incur at least N +separate allocations. Instead of doing this on every request, we keep a pool of +pre-built stacks around for reuse. +*/ +type cStack struct { + C + m http.Handler + pool *cPool +} + +func (s *cStack) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.C = C{} + s.m.ServeHTTP(w, r) +} +func (s *cStack) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + s.C = c + s.m.ServeHTTP(w, r) +} + +func (m *mStack) appendLayer(fn interface{}) { + ml := mLayer{orig: fn} + switch f := fn.(type) { + case func(http.Handler) http.Handler: + ml.fn = func(c *C, h http.Handler) http.Handler { + return f(h) + } + case func(*C, http.Handler) http.Handler: + ml.fn = f + default: + log.Fatalf(`Unknown middleware type %v. Expected a function `+ + `with signature "func(http.Handler) http.Handler" or `+ + `"func(*web.C, http.Handler) http.Handler".`, fn) + } + m.stack = append(m.stack, ml) +} + +func (m *mStack) findLayer(l interface{}) int { + for i, middleware := range m.stack { + if funcEqual(l, middleware.orig) { + return i + } + } + return -1 +} + +func (m *mStack) invalidate() { + m.pool = makeCPool() +} + +func (m *mStack) newStack() *cStack { + cs := cStack{} + router := m.router + + cs.m = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + router.route(&cs.C, w, r) + }) + for i := len(m.stack) - 1; i >= 0; i-- { + cs.m = m.stack[i].fn(&cs.C, cs.m) + } + + return &cs +} + +func (m *mStack) alloc() *cStack { + p := m.pool + cs := p.alloc() + if cs == nil { + cs = m.newStack() + } + + cs.pool = p + return cs +} + +func (m *mStack) release(cs *cStack) { + cs.C = C{} + if cs.pool != m.pool { + return + } + cs.pool.release(cs) + cs.pool = nil +} + +func (m *mStack) Use(middleware interface{}) { + m.lock.Lock() + defer m.lock.Unlock() + m.appendLayer(middleware) + m.invalidate() +} + +func (m *mStack) Insert(middleware, before interface{}) error { + m.lock.Lock() + defer m.lock.Unlock() + i := m.findLayer(before) + if i < 0 { + return fmt.Errorf("web: unknown middleware %v", before) + } + + m.appendLayer(middleware) + inserted := m.stack[len(m.stack)-1] + copy(m.stack[i+1:], m.stack[i:]) + m.stack[i] = inserted + + m.invalidate() + return nil +} + +func (m *mStack) Abandon(middleware interface{}) error { + m.lock.Lock() + defer m.lock.Unlock() + i := m.findLayer(middleware) + if i < 0 { + return fmt.Errorf("web: unknown middleware %v", middleware) + } + + copy(m.stack[i:], m.stack[i+1:]) + m.stack = m.stack[:len(m.stack)-1 : len(m.stack)] + + m.invalidate() + return nil +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/envinit.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/envinit.go new file mode 100644 index 0000000..aefefb7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/envinit.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "net/http" + + "github.com/zenazn/goji/web" +) + +// EnvInit is a middleware that allocates an environment map if one does not +// already exist. This is necessary because Goji does not guarantee that Env is +// present when running middleware (it avoids forcing the map allocation). Note +// that other middleware should check Env for nil in order to maximize +// compatibility (when EnvInit is not used, or when another middleware layer +// blanks out Env), but for situations in which the user controls the middleware +// stack and knows EnvInit is present, this middleware can eliminate a lot of +// boilerplate. +func EnvInit(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if c.Env == nil { + c.Env = make(map[string]interface{}) + } + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/logger.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/logger.go new file mode 100644 index 0000000..8bbcac8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/logger.go @@ -0,0 +1,92 @@ +package middleware + +import ( + "bytes" + "log" + "net/http" + "time" + + "github.com/zenazn/goji/web" + "github.com/zenazn/goji/web/mutil" +) + +// Logger is a middleware that logs the start and end of each request, along +// with some useful data about what was requested, what the response status was, +// and how long it took to return. When standard output is a TTY, Logger will +// print in color, otherwise it will print in black and white. +// +// Logger prints a request ID if one is provided. +// +// Logger has been designed explicitly to be Good Enough for use in small +// applications and for people just getting started with Goji. It is expected +// that applications will eventually outgrow this middleware and replace it with +// a custom request logger, such as one that produces machine-parseable output, +// outputs logs to a different service (e.g., syslog), or formats lines like +// those printed elsewhere in the application. +func Logger(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + reqID := GetReqID(*c) + + printStart(reqID, r) + + lw := mutil.WrapWriter(w) + + t1 := time.Now() + h.ServeHTTP(lw, r) + + if lw.Status() == 0 { + lw.WriteHeader(http.StatusOK) + } + t2 := time.Now() + + printEnd(reqID, lw, t2.Sub(t1)) + } + + return http.HandlerFunc(fn) +} + +func printStart(reqID string, r *http.Request) { + var buf bytes.Buffer + + if reqID != "" { + cW(&buf, bBlack, "[%s] ", reqID) + } + buf.WriteString("Started ") + cW(&buf, bMagenta, "%s ", r.Method) + cW(&buf, nBlue, "%q ", r.URL.String()) + buf.WriteString("from ") + buf.WriteString(r.RemoteAddr) + + log.Print(buf.String()) +} + +func printEnd(reqID string, w mutil.WriterProxy, dt time.Duration) { + var buf bytes.Buffer + + if reqID != "" { + cW(&buf, bBlack, "[%s] ", reqID) + } + buf.WriteString("Returning ") + status := w.Status() + if status < 200 { + cW(&buf, bBlue, "%03d", status) + } else if status < 300 { + cW(&buf, bGreen, "%03d", status) + } else if status < 400 { + cW(&buf, bCyan, "%03d", status) + } else if status < 500 { + cW(&buf, bYellow, "%03d", status) + } else { + cW(&buf, bRed, "%03d", status) + } + buf.WriteString(" in ") + if dt < 500*time.Millisecond { + cW(&buf, nGreen, "%s", dt) + } else if dt < 5*time.Second { + cW(&buf, nYellow, "%s", dt) + } else { + cW(&buf, nRed, "%s", dt) + } + + log.Print(buf.String()) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/middleware.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/middleware.go new file mode 100644 index 0000000..23cfde2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/middleware.go @@ -0,0 +1,4 @@ +/* +Package middleware provides several standard middleware implementations. +*/ +package middleware diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache.go new file mode 100644 index 0000000..ae3d260 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache.go @@ -0,0 +1,55 @@ +package middleware + +import ( + "net/http" + "time" +) + +// Unix epoch time +var epoch = time.Unix(0, 0).Format(time.RFC1123) + +// Taken from https://github.com/mytrile/nocache +var noCacheHeaders = map[string]string{ + "Expires": epoch, + "Cache-Control": "no-cache, private, max-age=0", + "Pragma": "no-cache", + "X-Accel-Expires": "0", +} + +var etagHeaders = []string{ + "ETag", + "If-Modified-Since", + "If-Match", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", +} + +// NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent +// a router (or subrouter) from being cached by an upstream proxy and/or client. +// +// As per http://wiki.nginx.org/HttpProxyModule - NoCache sets: +// Expires: Thu, 01 Jan 1970 00:00:00 UTC +// Cache-Control: no-cache, private, max-age=0 +// X-Accel-Expires: 0 +// Pragma: no-cache (for HTTP/1.0 proxies/clients) +func NoCache(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + + // Delete any ETag headers that may have been set + for _, v := range etagHeaders { + if r.Header.Get(v) != "" { + r.Header.Del(v) + } + } + + // Set our NoCache headers + for k, v := range noCacheHeaders { + w.Header().Set(k, v) + } + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache_test.go new file mode 100644 index 0000000..1fb71f6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/nocache_test.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/zenazn/goji/web" +) + +func TestNoCache(t *testing.T) { + + rr := httptest.NewRecorder() + s := web.New() + + s.Use(NoCache) + r, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Fatal(err) + } + + s.ServeHTTP(rr, r) + + for k, v := range noCacheHeaders { + if rr.HeaderMap[k][0] != v { + t.Errorf("%s header not set by middleware.", k) + } + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options.go new file mode 100644 index 0000000..4bdce5f --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options.go @@ -0,0 +1,97 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/zenazn/goji/web" +) + +type autoOptionsState int + +const ( + aosInit autoOptionsState = iota + aosHeaderWritten + aosProxying +) + +// I originally used an httptest.ResponseRecorder here, but package httptest +// adds a flag which I'm not particularly eager to expose. This is essentially a +// ResponseRecorder that has been specialized for the purpose at hand to avoid +// the httptest dependency. +type autoOptionsProxy struct { + w http.ResponseWriter + c *web.C + state autoOptionsState +} + +func (p *autoOptionsProxy) Header() http.Header { + return p.w.Header() +} + +func (p *autoOptionsProxy) Write(buf []byte) (int, error) { + switch p.state { + case aosInit: + p.state = aosHeaderWritten + case aosProxying: + return len(buf), nil + } + return p.w.Write(buf) +} + +func (p *autoOptionsProxy) WriteHeader(code int) { + methods := getValidMethods(*p.c) + switch p.state { + case aosInit: + if methods != nil && code == http.StatusNotFound { + p.state = aosProxying + break + } + p.state = aosHeaderWritten + fallthrough + default: + p.w.WriteHeader(code) + return + } + + methods = addMethod(methods, "OPTIONS") + p.w.Header().Set("Allow", strings.Join(methods, ", ")) + p.w.WriteHeader(http.StatusOK) +} + +// AutomaticOptions automatically return an appropriate "Allow" header when the +// request method is OPTIONS and the request would have otherwise been 404'd. +func AutomaticOptions(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + w = &autoOptionsProxy{c: c, w: w} + } + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func getValidMethods(c web.C) []string { + if c.Env == nil { + return nil + } + v, ok := c.Env[web.ValidMethodsKey] + if !ok { + return nil + } + if methods, ok := v.([]string); ok { + return methods + } + return nil +} + +func addMethod(methods []string, method string) []string { + for _, m := range methods { + if m == method { + return methods + } + } + return append(methods, method) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options_test.go new file mode 100644 index 0000000..74f5481 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/options_test.go @@ -0,0 +1,112 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/zenazn/goji/web" +) + +func testOptions(r *http.Request, f func(*web.C, http.ResponseWriter, *http.Request)) *httptest.ResponseRecorder { + var c web.C + + h := func(w http.ResponseWriter, r *http.Request) { + f(&c, w, r) + } + m := AutomaticOptions(&c, http.HandlerFunc(h)) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + + return w +} + +var optionsTestEnv = map[string]interface{}{ + web.ValidMethodsKey: []string{ + "hello", + "world", + }, +} + +func TestAutomaticOptions(t *testing.T) { + t.Parallel() + + // Shouldn't interfere with normal requests + r, _ := http.NewRequest("GET", "/", nil) + rr := testOptions(r, + func(c *web.C, w http.ResponseWriter, r *http.Request) { + w.Write([]byte{'h', 'i'}) + }, + ) + if rr.Code != http.StatusOK { + t.Errorf("status is %d, not 200", rr.Code) + } + if rr.Body.String() != "hi" { + t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") + } + allow := rr.HeaderMap.Get("Allow") + if allow != "" { + t.Errorf("Allow header was set to %q, should be empty", allow) + } + + // If we respond non-404 to an OPTIONS request, also don't interfere + r, _ = http.NewRequest("OPTIONS", "/", nil) + rr = testOptions(r, + func(c *web.C, w http.ResponseWriter, r *http.Request) { + c.Env = optionsTestEnv + w.Write([]byte{'h', 'i'}) + }, + ) + if rr.Code != http.StatusOK { + t.Errorf("status is %d, not 200", rr.Code) + } + if rr.Body.String() != "hi" { + t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") + } + allow = rr.HeaderMap.Get("Allow") + if allow != "" { + t.Errorf("Allow header was set to %q, should be empty", allow) + } + + // Provide options if we 404. Make sure we nom the output bytes + r, _ = http.NewRequest("OPTIONS", "/", nil) + rr = testOptions(r, + func(c *web.C, w http.ResponseWriter, r *http.Request) { + c.Env = optionsTestEnv + w.WriteHeader(http.StatusNotFound) + w.Write([]byte{'h', 'i'}) + }, + ) + if rr.Code != http.StatusOK { + t.Errorf("status is %d, not 200", rr.Code) + } + if rr.Body.Len() != 0 { + t.Errorf("body was %q, should be empty", rr.Body.String()) + } + allow = rr.HeaderMap.Get("Allow") + correctHeaders := "hello, world, OPTIONS" + if allow != "hello, world, OPTIONS" { + t.Errorf("Allow header should be %q, was %q", correctHeaders, + allow) + } + + // If we somehow 404 without giving a list of valid options, don't do + // anything + r, _ = http.NewRequest("OPTIONS", "/", nil) + rr = testOptions(r, + func(c *web.C, w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + w.Write([]byte{'h', 'i'}) + }, + ) + if rr.Code != http.StatusNotFound { + t.Errorf("status is %d, not 404", rr.Code) + } + if rr.Body.String() != "hi" { + t.Errorf("body was %q, should be %q", rr.Body.String(), "hi") + } + allow = rr.HeaderMap.Get("Allow") + if allow != "" { + t.Errorf("Allow header was set to %q, should be empty", allow) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/realip.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/realip.go new file mode 100644 index 0000000..99cd311 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/realip.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/zenazn/goji/web" +) + +// Key the original value of RemoteAddr is stored under. +const OriginalRemoteAddrKey = "originalRemoteAddr" + +var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") +var xRealIP = http.CanonicalHeaderKey("X-Real-IP") + +// RealIP is a middleware that sets a http.Request's RemoteAddr to the results +// of parsing either the X-Forwarded-For header or the X-Real-IP header (in that +// order). It places the original value of RemoteAddr in a context environment +// variable. +// +// This middleware should be inserted fairly early in the middleware stack to +// ensure that subsequent layers (e.g., request loggers) which examine the +// RemoteAddr will see the intended value. +// +// You should only use this middleware if you can trust the headers passed to +// you (in particular, the two headers this middleware uses), for example +// because you have placed a reverse proxy like HAProxy or nginx in front of +// Goji. If your reverse proxies are configured to pass along arbitrary header +// values from the client, or if you use this middleware without a reverse +// proxy, malicious clients will be able to make you very sad (or, depending on +// how you're using RemoteAddr, vulnerable to an attack of some sort). +func RealIP(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if rip := realIP(r); rip != "" { + if c.Env == nil { + c.Env = make(map[string]interface{}) + } + c.Env[OriginalRemoteAddrKey] = r.RemoteAddr + r.RemoteAddr = rip + } + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func realIP(r *http.Request) string { + var ip string + + if xff := r.Header.Get(xForwardedFor); xff != "" { + i := strings.Index(xff, ", ") + if i == -1 { + i = len(xff) + } + ip = xff[:i] + } else if xrip := r.Header.Get(xRealIP); xrip != "" { + ip = xrip + } + + return ip +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/recoverer.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/recoverer.go new file mode 100644 index 0000000..43ad648 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/recoverer.go @@ -0,0 +1,44 @@ +package middleware + +import ( + "bytes" + "log" + "net/http" + "runtime/debug" + + "github.com/zenazn/goji/web" +) + +// Recoverer is a middleware that recovers from panics, logs the panic (and a +// backtrace), and returns a HTTP 500 (Internal Server Error) status if +// possible. +// +// Recoverer prints a request ID if one is provided. +func Recoverer(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + reqID := GetReqID(*c) + + defer func() { + if err := recover(); err != nil { + printPanic(reqID, err) + debug.PrintStack() + http.Error(w, http.StatusText(500), 500) + } + }() + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func printPanic(reqID string, err interface{}) { + var buf bytes.Buffer + + if reqID != "" { + cW(&buf, bBlack, "[%s] ", reqID) + } + cW(&buf, bRed, "panic: %+v", err) + + log.Print(buf.String()) +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/request_id.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/request_id.go new file mode 100644 index 0000000..27e2313 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/request_id.go @@ -0,0 +1,88 @@ +package middleware + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "os" + "strings" + "sync/atomic" + + "github.com/zenazn/goji/web" +) + +// Key to use when setting the request ID. +const RequestIDKey = "reqID" + +var prefix string +var reqid uint64 + +/* +A quick note on the statistics here: we're trying to calculate the chance that +two randomly generated base62 prefixes will collide. We use the formula from +http://en.wikipedia.org/wiki/Birthday_problem + +P[m, n] \approx 1 - e^{-m^2/2n} + +We ballpark an upper bound for $m$ by imagining (for whatever reason) a server +that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$ + +For a $k$ character base-62 identifier, we have $n(k) = 62^k$ + +Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for +our purposes, and is surely more than anyone would ever need in practice -- a +process that is rebooted a handful of times a day for a hundred years has less +than a millionth of a percent chance of generating two colliding IDs. +*/ + +func init() { + hostname, err := os.Hostname() + if hostname == "" || err != nil { + hostname = "localhost" + } + var buf [12]byte + var b64 string + for len(b64) < 10 { + rand.Read(buf[:]) + b64 = base64.StdEncoding.EncodeToString(buf[:]) + b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) + } + + prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) +} + +// RequestID is a middleware that injects a request ID into the context of each +// request. A request ID is a string of the form "host.example.com/random-0001", +// where "random" is a base62 random string that uniquely identifies this go +// process, and where the last number is an atomically incremented request +// counter. +func RequestID(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if c.Env == nil { + c.Env = make(map[string]interface{}) + } + myid := atomic.AddUint64(&reqid, 1) + c.Env[RequestIDKey] = fmt.Sprintf("%s-%06d", prefix, myid) + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +// GetReqID returns a request ID from the given context if one is present. +// Returns the empty string if a request ID cannot be found. +func GetReqID(c web.C) string { + if c.Env == nil { + return "" + } + v, ok := c.Env[RequestIDKey] + if !ok { + return "" + } + if reqID, ok := v.(string); ok { + return reqID + } + return "" +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/subrouter.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/subrouter.go new file mode 100644 index 0000000..1b3f3f3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/subrouter.go @@ -0,0 +1,45 @@ +package middleware + +import ( + "net/http" + + "github.com/zenazn/goji/web" +) + +type subrouter struct { + c *web.C + h http.Handler +} + +func (s subrouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if s.c.URLParams != nil { + path, ok := s.c.URLParams["*"] + if !ok { + path, ok = s.c.URLParams["_"] + } + if ok { + oldpath := r.URL.Path + r.URL.Path = path + defer func() { + r.URL.Path = oldpath + }() + } + } + s.h.ServeHTTP(w, r) +} + +// SubRouter is a helper middleware that makes writing sub-routers easier. +// +// If you register a sub-router under a key like "/admin/*", Goji's router will +// automatically set c.URLParams["*"] to the unmatched path suffix. This +// middleware will help you set the request URL's Path to this unmatched suffix, +// allowing you to write sub-routers with no knowledge of what routes the parent +// router matches. +// +// Since Go's regular expressions do not allow you to create a capturing group +// named "*", SubRouter also accepts the string "_". For instance, to duplicate +// the semantics of the string pattern "/foo/*", you might use the regular +// expression "^/foo(?P<_>/.*)$". +func SubRouter(c *web.C, h http.Handler) http.Handler { + return subrouter{c, h} +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/terminal.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/terminal.go new file mode 100644 index 0000000..db02917 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware/terminal.go @@ -0,0 +1,60 @@ +package middleware + +import ( + "bytes" + "fmt" + "os" +) + +var ( + // Normal colors + nBlack = []byte{'\033', '[', '3', '0', 'm'} + nRed = []byte{'\033', '[', '3', '1', 'm'} + nGreen = []byte{'\033', '[', '3', '2', 'm'} + nYellow = []byte{'\033', '[', '3', '3', 'm'} + nBlue = []byte{'\033', '[', '3', '4', 'm'} + nMagenta = []byte{'\033', '[', '3', '5', 'm'} + nCyan = []byte{'\033', '[', '3', '6', 'm'} + nWhite = []byte{'\033', '[', '3', '7', 'm'} + // Bright colors + bBlack = []byte{'\033', '[', '3', '0', ';', '1', 'm'} + bRed = []byte{'\033', '[', '3', '1', ';', '1', 'm'} + bGreen = []byte{'\033', '[', '3', '2', ';', '1', 'm'} + bYellow = []byte{'\033', '[', '3', '3', ';', '1', 'm'} + bBlue = []byte{'\033', '[', '3', '4', ';', '1', 'm'} + bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} + bCyan = []byte{'\033', '[', '3', '6', ';', '1', 'm'} + bWhite = []byte{'\033', '[', '3', '7', ';', '1', 'm'} + + reset = []byte{'\033', '[', '0', 'm'} +) + +var isTTY bool + +func init() { + // This is sort of cheating: if stdout is a character device, we assume + // that means it's a TTY. Unfortunately, there are many non-TTY + // character devices, but fortunately stdout is rarely set to any of + // them. + // + // We could solve this properly by pulling in a dependency on + // code.google.com/p/go.crypto/ssh/terminal, for instance, but as a + // heuristic for whether to print in color or in black-and-white, I'd + // really rather not. + fi, err := os.Stdout.Stat() + if err == nil { + m := os.ModeDevice | os.ModeCharDevice + isTTY = fi.Mode()&m == m + } +} + +// colorWrite +func cW(buf *bytes.Buffer, color []byte, s string, args ...interface{}) { + if isTTY { + buf.Write(color) + } + fmt.Fprintf(buf, s, args...) + if isTTY { + buf.Write(reset) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware12_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware12_test.go new file mode 100644 index 0000000..fe5ca9b --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware12_test.go @@ -0,0 +1,52 @@ +// +build !go1.3 + +package web + +import "testing" + +// These tests were pretty sketchtacular to start with, but they aren't even +// guaranteed to pass with Go 1.3's sync.Pool. Let's keep them here for now; if +// they start spuriously failing later we can delete them outright. + +func TestCaching(t *testing.T) { + ch := make(chan string) + st := makeStack(ch) + cs1 := st.alloc() + cs2 := st.alloc() + if cs1 == cs2 { + t.Fatal("cs1 and cs2 are the same") + } + st.release(cs2) + cs3 := st.alloc() + if cs2 != cs3 { + t.Fatalf("Expected cs2 to equal cs3") + } + st.release(cs1) + st.release(cs3) + cs4 := st.alloc() + cs5 := st.alloc() + if cs4 != cs1 { + t.Fatal("Expected cs4 to equal cs1") + } + if cs5 != cs3 { + t.Fatal("Expected cs5 to equal cs3") + } +} + +func TestInvalidation(t *testing.T) { + ch := make(chan string) + st := makeStack(ch) + cs1 := st.alloc() + cs2 := st.alloc() + st.release(cs1) + st.invalidate() + cs3 := st.alloc() + if cs3 == cs1 { + t.Fatal("Expected cs3 to be fresh, instead got cs1") + } + st.release(cs2) + cs4 := st.alloc() + if cs4 == cs2 { + t.Fatal("Expected cs4 to be fresh, instead got cs2") + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware_test.go new file mode 100644 index 0000000..b262b07 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/middleware_test.go @@ -0,0 +1,204 @@ +package web + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +type iRouter func(*C, http.ResponseWriter, *http.Request) + +func (i iRouter) route(c *C, w http.ResponseWriter, r *http.Request) { + i(c, w, r) +} + +func makeStack(ch chan string) *mStack { + router := func(c *C, w http.ResponseWriter, r *http.Request) { + ch <- "router" + } + return &mStack{ + stack: make([]mLayer, 0), + pool: makeCPool(), + router: iRouter(router), + } +} + +func chanWare(ch chan string, s string) func(http.Handler) http.Handler { + return func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ch <- s + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} + +func simpleRequest(ch chan string, st *mStack) { + defer func() { + ch <- "end" + }() + r, _ := http.NewRequest("GET", "/", nil) + w := httptest.NewRecorder() + cs := st.alloc() + defer st.release(cs) + + cs.ServeHTTP(w, r) +} + +func assertOrder(t *testing.T, ch chan string, strings ...string) { + for i, s := range strings { + var v string + select { + case v = <-ch: + case <-time.After(5 * time.Millisecond): + t.Fatalf("Expected %q as %d'th value, but timed out", s, + i+1) + } + if s != v { + t.Errorf("%d'th value was %q, expected %q", i+1, v, s) + } + } +} + +func TestSimple(t *testing.T) { + t.Parallel() + + ch := make(chan string) + st := makeStack(ch) + st.Use(chanWare(ch, "one")) + st.Use(chanWare(ch, "two")) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "two", "router", "end") +} + +func TestTypes(t *testing.T) { + t.Parallel() + + ch := make(chan string) + st := makeStack(ch) + st.Use(func(h http.Handler) http.Handler { + return h + }) + st.Use(func(c *C, h http.Handler) http.Handler { + return h + }) +} + +func TestAddMore(t *testing.T) { + t.Parallel() + + ch := make(chan string) + st := makeStack(ch) + st.Use(chanWare(ch, "one")) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "router", "end") + + st.Use(chanWare(ch, "two")) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "two", "router", "end") + + st.Use(chanWare(ch, "three")) + st.Use(chanWare(ch, "four")) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "two", "three", "four", "router", "end") +} + +func TestInsert(t *testing.T) { + t.Parallel() + + ch := make(chan string) + st := makeStack(ch) + one := chanWare(ch, "one") + two := chanWare(ch, "two") + st.Use(one) + st.Use(two) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "two", "router", "end") + + err := st.Insert(chanWare(ch, "sloth"), chanWare(ch, "squirrel")) + if err == nil { + t.Error("Expected error when referencing unknown middleware") + } + + st.Insert(chanWare(ch, "middle"), two) + err = st.Insert(chanWare(ch, "start"), one) + if err != nil { + t.Fatal(err) + } + go simpleRequest(ch, st) + assertOrder(t, ch, "start", "one", "middle", "two", "router", "end") +} + +func TestAbandon(t *testing.T) { + t.Parallel() + + ch := make(chan string) + st := makeStack(ch) + one := chanWare(ch, "one") + two := chanWare(ch, "two") + three := chanWare(ch, "three") + st.Use(one) + st.Use(two) + st.Use(three) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "two", "three", "router", "end") + + st.Abandon(two) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "three", "router", "end") + + err := st.Abandon(chanWare(ch, "panda")) + if err == nil { + t.Error("Expected error when deleting unknown middleware") + } + + st.Abandon(one) + st.Abandon(three) + go simpleRequest(ch, st) + assertOrder(t, ch, "router", "end") + + st.Use(one) + go simpleRequest(ch, st) + assertOrder(t, ch, "one", "router", "end") +} + +func TestContext(t *testing.T) { + router := func(c *C, w http.ResponseWriter, r *http.Request) { + if c.Env["reqID"].(int) != 2 { + t.Error("Request id was not 2 :(") + } + } + st := mStack{ + stack: make([]mLayer, 0), + pool: makeCPool(), + router: iRouter(router), + } + st.Use(func(c *C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if c.Env != nil || c.URLParams != nil { + t.Error("Expected a clean context") + } + c.Env = make(map[string]interface{}) + c.Env["reqID"] = 1 + + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + }) + + st.Use(func(c *C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if c.Env == nil { + t.Error("Expected env from last middleware") + } + c.Env["reqID"] = c.Env["reqID"].(int) + 1 + + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + }) + ch := make(chan string) + go simpleRequest(ch, &st) + assertOrder(t, ch, "end") +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/mutil.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/mutil.go new file mode 100644 index 0000000..e8d5b28 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/mutil.go @@ -0,0 +1,3 @@ +// Package mutil contains various functions that are helpful when writing http +// middleware. +package mutil diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/writer_proxy.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/writer_proxy.go new file mode 100644 index 0000000..d572812 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/mutil/writer_proxy.go @@ -0,0 +1,125 @@ +package mutil + +import ( + "bufio" + "io" + "net" + "net/http" +) + +// WriterProxy is a proxy around an http.ResponseWriter that allows you to hook +// into various parts of the response process. +type WriterProxy interface { + http.ResponseWriter + // Status returns the HTTP status of the request, or 0 if one has not + // yet been sent. + Status() int + // BytesWritten returns the total number of bytes sent to the client. + BytesWritten() int + // Tee causes the response body to be written to the given io.Writer in + // addition to proxying the writes through. Only one io.Writer can be + // tee'd to at once: setting a second one will overwrite the first. + // Writes will be sent to the proxy before being written to this + // io.Writer. It is illegal for the tee'd writer to be modified + // concurrently with writes. + Tee(io.Writer) + // Unwrap returns the original proxied target. + Unwrap() http.ResponseWriter +} + +// WrapWriter wraps an http.ResponseWriter, returning a proxy that allows you to +// hook into various parts of the response process. +func WrapWriter(w http.ResponseWriter) WriterProxy { + _, cn := w.(http.CloseNotifier) + _, fl := w.(http.Flusher) + _, hj := w.(http.Hijacker) + _, rf := w.(io.ReaderFrom) + + bw := basicWriter{ResponseWriter: w} + if cn && fl && hj && rf { + return &fancyWriter{bw} + } + return &bw +} + +// basicWriter wraps a http.ResponseWriter that implements the minimal +// http.ResponseWriter interface. +type basicWriter struct { + http.ResponseWriter + wroteHeader bool + code int + bytes int + tee io.Writer +} + +func (b *basicWriter) WriteHeader(code int) { + if !b.wroteHeader { + b.code = code + b.wroteHeader = true + b.ResponseWriter.WriteHeader(code) + } +} +func (b *basicWriter) Write(buf []byte) (int, error) { + b.WriteHeader(http.StatusOK) + n, err := b.ResponseWriter.Write(buf) + if b.tee != nil { + _, err2 := b.tee.Write(buf[:n]) + // Prefer errors generated by the proxied writer. + if err == nil { + err = err2 + } + } + b.bytes += n + return n, err +} +func (b *basicWriter) maybeWriteHeader() { + if !b.wroteHeader { + b.WriteHeader(http.StatusOK) + } +} +func (b *basicWriter) Status() int { + return b.code +} +func (b *basicWriter) BytesWritten() int { + return b.bytes +} +func (b *basicWriter) Tee(w io.Writer) { + b.tee = w +} +func (b *basicWriter) Unwrap() http.ResponseWriter { + return b.ResponseWriter +} + +// fancyWriter is a writer that additionally satisfies http.CloseNotifier, +// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case +// of wrapping the http.ResponseWriter that package http gives you, in order to +// make the proxied object support the full method set of the proxied object. +type fancyWriter struct { + basicWriter +} + +func (f *fancyWriter) CloseNotify() <-chan bool { + cn := f.basicWriter.ResponseWriter.(http.CloseNotifier) + return cn.CloseNotify() +} +func (f *fancyWriter) Flush() { + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} +func (f *fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + return hj.Hijack() +} +func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) { + if f.basicWriter.tee != nil { + return io.Copy(&f.basicWriter, r) + } + rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) + f.basicWriter.maybeWriteHeader() + return rf.ReadFrom(r) +} + +var _ http.CloseNotifier = &fancyWriter{} +var _ http.Flusher = &fancyWriter{} +var _ http.Hijacker = &fancyWriter{} +var _ io.ReaderFrom = &fancyWriter{} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/mux.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/mux.go new file mode 100644 index 0000000..3908de5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/mux.go @@ -0,0 +1,212 @@ +package web + +import ( + "net/http" +) + +/* +Mux is an HTTP multiplexer, much like net/http's ServeMux. + +Routes may be added using any of the various HTTP-method-specific functions. +When processing a request, when iterating in insertion order the first route +that matches both the request's path and method is used. + +There are two other differences worth mentioning between web.Mux and +http.ServeMux. First, string patterns (i.e., Sinatra-like patterns) must match +exactly: the "rooted subtree" behavior of ServeMux is not implemented. Secondly, +unlike ServeMux, Mux does not support Host-specific patterns. + +If you require any of these features, remember that you are free to mix and +match muxes at any part of the stack. + +In order to provide a sane API, many functions on Mux take interface{}'s. This +is obviously not a very satisfying solution, but it's probably the best we can +do for now. Instead of duplicating documentation on each method, the types +accepted by those functions are documented here. + +A middleware (the untyped parameter in Use() and Insert()) must be one of the +following types: + - func(http.Handler) http.Handler + - func(c *web.C, http.Handler) http.Handler + +All of the route-adding functions on Mux take two untyped parameters: pattern +and handler. Pattern will be passed to ParsePattern, which takes a web.Pattern, +a string, or a regular expression (more information can be found in the +ParsePattern documentation). Handler must be one of the following types: + - http.Handler + - web.Handler + - func(w http.ResponseWriter, r *http.Request) + - func(c web.C, w http.ResponseWriter, r *http.Request) +*/ +type Mux struct { + ms mStack + rt router +} + +// New creates a new Mux without any routes or middleware. +func New() *Mux { + mux := Mux{ + ms: mStack{ + stack: make([]mLayer, 0), + pool: makeCPool(), + }, + rt: router{ + routes: make([]route, 0), + notFound: parseHandler(http.NotFound), + }, + } + mux.ms.router = &mux.rt + return &mux +} + +// ServeHTTP processes HTTP requests. It make Muxes satisfy net/http.Handler. +func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + stack := m.ms.alloc() + stack.ServeHTTP(w, r) + m.ms.release(stack) +} + +// ServeHTTPC creates a context dependent request with the given Mux. Satisfies +// the web.Handler interface. +func (m *Mux) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + stack := m.ms.alloc() + stack.ServeHTTPC(c, w, r) + m.ms.release(stack) +} + +// Middleware Stack functions + +// Append the given middleware to the middleware stack. See the documentation +// for type Mux for a list of valid middleware types. +// +// No attempt is made to enforce the uniqueness of middlewares. It is illegal to +// call this function concurrently with active requests. +func (m *Mux) Use(middleware interface{}) { + m.ms.Use(middleware) +} + +// Insert the given middleware immediately before a given existing middleware in +// the stack. See the documentation for type Mux for a list of valid middleware +// types. Returns an error if no middleware has the name given by "before." +// +// No attempt is made to enforce the uniqueness of middlewares. If the insertion +// point is ambiguous, the first (outermost) one is chosen. It is illegal to +// call this function concurrently with active requests. +func (m *Mux) Insert(middleware, before interface{}) error { + return m.ms.Insert(middleware, before) +} + +// Remove the given middleware from the middleware stack. Returns an error if +// no such middleware can be found. +// +// If the name of the middleware to delete is ambiguous, the first (outermost) +// one is chosen. It is illegal to call this function concurrently with active +// requests. +func (m *Mux) Abandon(middleware interface{}) error { + return m.ms.Abandon(middleware) +} + +// Router functions + +/* +Dispatch to the given handler when the pattern matches, regardless of HTTP +method. See the documentation for type Mux for a description of what types are +accepted for pattern and handler. + +This method is commonly used to implement sub-routing: an admin application, for +instance, can expose a single handler that is attached to the main Mux by +calling Handle("/admin*", adminHandler) or similar. Note that this function +doesn't strip this prefix from the path before forwarding it on (e.g., the +handler will see the full path, including the "/admin" part), but this +functionality can easily be performed by an extra middleware layer. +*/ +func (m *Mux) Handle(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mALL, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// CONNECT. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Connect(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mCONNECT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// DELETE. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Delete(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mDELETE, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// GET. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +// +// All GET handlers also transparently serve HEAD requests, since net/http will +// take care of all the fiddly bits for you. If you wish to provide an alternate +// implementation of HEAD, you should add a handler explicitly and place it +// above your GET handler. +func (m *Mux) Get(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mGET|mHEAD, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// HEAD. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Head(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mHEAD, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// OPTIONS. See the documentation for type Mux for a description of what types +// are accepted for pattern and handler. +func (m *Mux) Options(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mOPTIONS, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PATCH. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Patch(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPATCH, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// POST. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Post(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPOST, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// PUT. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Put(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mPUT, handler) +} + +// Dispatch to the given handler when the pattern matches and the HTTP method is +// TRACE. See the documentation for type Mux for a description of what types are +// accepted for pattern and handler. +func (m *Mux) Trace(pattern interface{}, handler interface{}) { + m.rt.handleUntyped(pattern, mTRACE, handler) +} + +// Set the fallback (i.e., 404) handler for this mux. See the documentation for +// type Mux for a description of what types are accepted for handler. +// +// As a convenience, the context environment variable "goji.web.validMethods" +// (also available as the constant ValidMethodsKey) will be set to the list of +// HTTP methods that could have been routed had they been provided on an +// otherwise identical request. +func (m *Mux) NotFound(handler interface{}) { + m.rt.notFound = parseHandler(handler) +} + +// Compile the list of routes into bytecode. This only needs to be done once +// after all the routes have been added, and will be called automatically for +// you (at some performance cost on the first request) if you do not call it +// explicitly. +func (m *Mux) Compile() { + m.rt.compile() +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/mux_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/mux_test.go new file mode 100644 index 0000000..1854524 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/mux_test.go @@ -0,0 +1,45 @@ +package web + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +// Sanity check types +var _ http.Handler = &Mux{} +var _ Handler = &Mux{} + +// There's... really not a lot to do here. + +func TestIfItWorks(t *testing.T) { + t.Parallel() + + m := New() + ch := make(chan string, 1) + + m.Get("/hello/:name", func(c C, w http.ResponseWriter, r *http.Request) { + greeting := "Hello " + if c.Env != nil { + if g, ok := c.Env["greeting"]; ok { + greeting = g.(string) + } + } + ch <- greeting + c.URLParams["name"] + }) + + r, _ := http.NewRequest("GET", "/hello/carl", nil) + m.ServeHTTP(httptest.NewRecorder(), r) + out := <-ch + if out != "Hello carl" { + t.Errorf(`Unexpected response %q, expected "Hello carl"`, out) + } + + r, _ = http.NewRequest("GET", "/hello/bob", nil) + env := map[string]interface{}{"greeting": "Yo "} + m.ServeHTTPC(C{Env: env}, httptest.NewRecorder(), r) + out = <-ch + if out != "Yo bob" { + t.Errorf(`Unexpected response %q, expected "Yo bob"`, out) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern.go new file mode 100644 index 0000000..99ca59e --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern.go @@ -0,0 +1,78 @@ +package web + +import ( + "log" + "net/http" + "regexp" +) + +// A Pattern determines whether or not a given request matches some criteria. +// They are often used in routes, which are essentially (pattern, methodSet, +// handler) tuples. If the method and pattern match, the given handler is used. +// +// Built-in implementations of this interface are used to implement regular +// expression and string matching. +type Pattern interface { + // In practice, most real-world routes have a string prefix that can be + // used to quickly determine if a pattern is an eligible match. The + // router uses the result of this function to optimize away calls to the + // full Match function, which is likely much more expensive to compute. + // If your Pattern does not support prefixes, this function should + // return the empty string. + Prefix() string + // Returns true if the request satisfies the pattern. This function is + // free to examine both the request and the context to make this + // decision. Match should not modify either argument, and since it will + // potentially be called several times over the course of matching a + // request, it should be reasonably efficient. + Match(r *http.Request, c *C) bool + // Run the pattern on the request and context, modifying the context as + // necessary to bind URL parameters or other parsed state. + Run(r *http.Request, c *C) +} + +/* +ParsePattern is used internally by Goji to parse route patterns. It is exposed +publicly to make it easier to write thin wrappers around the built-in Pattern +implementations. + +Although its parameter has type interface{}, ParsePattern only accepts arguments +of three types: + - web.Pattern, which is passed through + - string, which is interpreted as a Sinatra-like URL pattern. In + particular, the following syntax is recognized: + - a path segment starting with with a colon will match any + string placed at that position. e.g., "/:name" will match + "/carl", binding "name" to "carl". + - a pattern ending with "/*" will match any route with that + prefix. For instance, the pattern "/u/:name/*" will match + "/u/carl/" and "/u/carl/projects/123", but not "/u/carl" + (because there is no trailing slash). In addition to any names + bound in the pattern, the special key "*" is bound to the + unmatched tail of the match, but including the leading "/". So + for the two matching examples above, "*" would be bound to "/" + and "/projects/123" respectively. + - regexp.Regexp, which is assumed to be a Perl-style regular expression + that is anchored on the left (i.e., the beginning of the string). If + your regular expression is not anchored on the left, a + hopefully-identical left-anchored regular expression will be created + and used instead. + +ParsePattern fatally exits (using log.Fatalf) if it is passed a value of an +unexpected type. It is the caller's responsibility to ensure that ParsePattern +is called in a type-safe manner. +*/ +func ParsePattern(raw interface{}) Pattern { + switch v := raw.(type) { + case Pattern: + return v + case *regexp.Regexp: + return parseRegexpPattern(v) + case string: + return parseStringPattern(v) + default: + log.Fatalf("Unknown pattern type %T. Expected a web.Pattern, "+ + "regexp.Regexp, or a string.", v) + } + panic("log.Fatalf does not return") +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern_test.go new file mode 100644 index 0000000..8e8af29 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/pattern_test.go @@ -0,0 +1,188 @@ +package web + +import ( + "net/http" + "reflect" + "regexp" + "testing" +) + +func pt(url string, match bool, params map[string]string) patternTest { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + panic(err) + } + + return patternTest{ + r: req, + match: match, + c: &C{}, + cout: &C{URLParams: params}, + } +} + +type patternTest struct { + r *http.Request + match bool + c *C + cout *C +} + +var patternTests = []struct { + pat Pattern + prefix string + tests []patternTest +}{ + // Regexp tests + {parseRegexpPattern(regexp.MustCompile("^/hello$")), + "/hello", []patternTest{ + pt("/hello", true, nil), + pt("/hell", false, nil), + pt("/hello/", false, nil), + pt("/hello/world", false, nil), + pt("/world", false, nil), + }}, + {parseRegexpPattern(regexp.MustCompile("^/hello/(?P[a-z]+)$")), + "/hello/", []patternTest{ + pt("/hello/world", true, map[string]string{ + "name": "world", + }), + pt("/hello/", false, nil), + pt("/hello/my/love", false, nil), + }}, + {parseRegexpPattern(regexp.MustCompile(`^/a(?P
      \d+)/b(?P\d+)/?$`)), + "/a", []patternTest{ + pt("/a1/b2", true, map[string]string{ + "a": "1", + "b": "2", + }), + pt("/a9001/b007/", true, map[string]string{ + "a": "9001", + "b": "007", + }), + pt("/a/b", false, nil), + pt("/a", false, nil), + pt("/squirrel", false, nil), + }}, + {parseRegexpPattern(regexp.MustCompile(`^/hello/([a-z]+)$`)), + "/hello/", []patternTest{ + pt("/hello/world", true, map[string]string{ + "$1": "world", + }), + pt("/hello/", false, nil), + }}, + {parseRegexpPattern(regexp.MustCompile("/hello")), + "/hello", []patternTest{ + pt("/hello", true, nil), + pt("/hell", false, nil), + pt("/hello/", true, nil), + pt("/hello/world", true, nil), + pt("/world/hello", false, nil), + }}, + + // String pattern tests + {parseStringPattern("/hello"), + "/hello", []patternTest{ + pt("/hello", true, nil), + pt("/hell", false, nil), + pt("/hello/", false, nil), + pt("/hello/world", false, nil), + }}, + {parseStringPattern("/hello/:name"), + "/hello/", []patternTest{ + pt("/hello/world", true, map[string]string{ + "name": "world", + }), + pt("/hello/my.world;wow", true, map[string]string{ + "name": "my.world;wow", + }), + pt("/hell", false, nil), + pt("/hello/", false, nil), + pt("/hello/my/love", false, nil), + }}, + {parseStringPattern("/a/:a/b/:b"), + "/a/", []patternTest{ + pt("/a/1/b/2", true, map[string]string{ + "a": "1", + "b": "2", + }), + pt("/a", false, nil), + pt("/a//b/", false, nil), + pt("/a/1/b/2/3", false, nil), + }}, + {parseStringPattern("/a/:b.:c"), + "/a/", []patternTest{ + pt("/a/cat.gif", true, map[string]string{ + "b": "cat", + "c": "gif", + }), + pt("/a/cat.tar.gz", true, map[string]string{ + "b": "cat", + "c": "tar.gz", + }), + pt("/a", false, nil), + pt("/a/cat", false, nil), + pt("/a/cat/gif", false, nil), + pt("/a/cat.", false, nil), + pt("/a/cat/dog.gif", false, nil), + }}, + + // String prefix tests + {parseStringPattern("/user/:user/*"), + "/user/", []patternTest{ + pt("/user/bob/", true, map[string]string{ + "user": "bob", + "*": "/", + }), + pt("/user/bob/friends/123", true, map[string]string{ + "user": "bob", + "*": "/friends/123", + }), + pt("/user/bob", false, nil), + pt("/user/", false, nil), + pt("/user//", false, nil), + }}, + {parseStringPattern("/user/:user/friends/*"), + "/user/", []patternTest{ + pt("/user/bob/friends/", true, map[string]string{ + "user": "bob", + "*": "/", + }), + pt("/user/bob/friends/123", true, map[string]string{ + "user": "bob", + "*": "/123", + }), + pt("/user/bob/enemies", false, nil), + }}, +} + +func TestPatterns(t *testing.T) { + t.Parallel() + + for _, pt := range patternTests { + p := pt.pat.Prefix() + if p != pt.prefix { + t.Errorf("Expected prefix %q for %v, got %q", pt.prefix, + pt.pat, p) + } else { + for _, test := range pt.tests { + runTest(t, pt.pat, test) + } + } + } +} + +func runTest(t *testing.T, p Pattern, test patternTest) { + result := p.Match(test.r, test.c) + if result != test.match { + t.Errorf("Expected match(%v, %#v) to return %v", p, + test.r.URL.Path, test.match) + return + } + p.Run(test.r, test.c) + + if !reflect.DeepEqual(test.c, test.cout) { + t.Errorf("Expected a context of %v, instead got %v", test.cout, + test.c) + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/regexp_pattern.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/regexp_pattern.go new file mode 100644 index 0000000..f9f8ecc --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/regexp_pattern.go @@ -0,0 +1,145 @@ +package web + +import ( + "bytes" + "fmt" + "log" + "net/http" + "regexp" + "regexp/syntax" +) + +type regexpPattern struct { + re *regexp.Regexp + prefix string + names []string +} + +func (p regexpPattern) Prefix() string { + return p.prefix +} +func (p regexpPattern) Match(r *http.Request, c *C) bool { + return p.match(r, c, false) +} +func (p regexpPattern) Run(r *http.Request, c *C) { + p.match(r, c, false) +} + +func (p regexpPattern) match(r *http.Request, c *C, dryrun bool) bool { + matches := p.re.FindStringSubmatch(r.URL.Path) + if matches == nil || len(matches) == 0 { + return false + } + + if c == nil || dryrun || len(matches) == 1 { + return true + } + + if c.URLParams == nil { + c.URLParams = make(map[string]string, len(matches)-1) + } + for i := 1; i < len(matches); i++ { + c.URLParams[p.names[i]] = matches[i] + } + return true +} + +func (p regexpPattern) String() string { + return fmt.Sprintf("regexpPattern(%v)", p.re) +} + +/* +I'm sorry, dear reader. I really am. + +The problem here is to take an arbitrary regular expression and: +1. return a regular expression that is just like it, but left-anchored, + preferring to return the original if possible. +2. determine a string literal prefix that all matches of this regular expression + have, much like regexp.Regexp.Prefix(). Unfortunately, Prefix() does not work + in the presence of anchors, so we need to write it ourselves. + +What this actually means is that we need to sketch on the internals of the +standard regexp library to forcefully extract the information we want. + +Unfortunately, regexp.Regexp hides a lot of its state, so our abstraction is +going to be pretty leaky. The biggest leak is that we blindly assume that all +regular expressions are perl-style, not POSIX. This is probably Mostly True, and +I think most users of the library probably won't be able to notice. +*/ +func sketchOnRegex(re *regexp.Regexp) (*regexp.Regexp, string) { + rawRe := re.String() + sRe, err := syntax.Parse(rawRe, syntax.Perl) + if err != nil { + log.Printf("WARN(web): unable to parse regexp %v as perl. "+ + "This route might behave unexpectedly.", re) + return re, "" + } + sRe = sRe.Simplify() + p, err := syntax.Compile(sRe) + if err != nil { + log.Printf("WARN(web): unable to compile regexp %v. This "+ + "route might behave unexpectedly.", re) + return re, "" + } + if p.StartCond()&syntax.EmptyBeginText == 0 { + // I hope doing this is always legal... + newRe, err := regexp.Compile(`\A` + rawRe) + if err != nil { + log.Printf("WARN(web): unable to create a left-"+ + "anchored regexp from %v. This route might "+ + "behave unexpectedly", re) + return re, "" + } + re = newRe + } + + // Run the regular expression more or less by hand :( + pc := uint32(p.Start) + atStart := true + i := &p.Inst[pc] + var buf bytes.Buffer +Sadness: + for { + switch i.Op { + case syntax.InstEmptyWidth: + if !atStart { + break Sadness + } + case syntax.InstCapture, syntax.InstNop: + // nop! + case syntax.InstRune, syntax.InstRune1, syntax.InstRuneAny, + syntax.InstRuneAnyNotNL: + + atStart = false + if len(i.Rune) != 1 || + syntax.Flags(i.Arg)&syntax.FoldCase != 0 { + break Sadness + } + buf.WriteRune(i.Rune[0]) + default: + break Sadness + } + pc = i.Out + i = &p.Inst[pc] + } + return re, buf.String() +} + +func parseRegexpPattern(re *regexp.Regexp) regexpPattern { + re, prefix := sketchOnRegex(re) + rnames := re.SubexpNames() + // We have to make our own copy since package regexp forbids us + // from scribbling over the slice returned by SubexpNames(). + names := make([]string, len(rnames)) + for i, rname := range rnames { + if rname == "" { + rname = fmt.Sprintf("$%d", i) + } + names[i] = rname + } + return regexpPattern{ + re: re, + prefix: prefix, + names: names, + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/router.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/router.go new file mode 100644 index 0000000..c291294 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/router.go @@ -0,0 +1,173 @@ +package web + +import ( + "log" + "net/http" + "sort" + "strings" + "sync" +) + +type method int + +const ( + mCONNECT method = 1 << iota + mDELETE + mGET + mHEAD + mOPTIONS + mPATCH + mPOST + mPUT + mTRACE + // We only natively support the methods above, but we pass through other + // methods. This constant pretty much only exists for the sake of mALL. + mIDK + + mALL method = mCONNECT | mDELETE | mGET | mHEAD | mOPTIONS | mPATCH | + mPOST | mPUT | mTRACE | mIDK +) + +// The key used to communicate to the NotFound handler what methods would have +// been allowed if they'd been provided. +const ValidMethodsKey = "goji.web.validMethods" + +var validMethodsMap = map[string]method{ + "CONNECT": mCONNECT, + "DELETE": mDELETE, + "GET": mGET, + "HEAD": mHEAD, + "OPTIONS": mOPTIONS, + "PATCH": mPATCH, + "POST": mPOST, + "PUT": mPUT, + "TRACE": mTRACE, +} + +type route struct { + prefix string + method method + pattern Pattern + handler Handler +} + +type router struct { + lock sync.Mutex + routes []route + notFound Handler + machine *routeMachine +} + +type netHTTPWrap struct { + http.Handler +} + +func (h netHTTPWrap) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + h.Handler.ServeHTTP(w, r) +} + +func parseHandler(h interface{}) Handler { + switch f := h.(type) { + case Handler: + return f + case http.Handler: + return netHTTPWrap{f} + case func(c C, w http.ResponseWriter, r *http.Request): + return HandlerFunc(f) + case func(w http.ResponseWriter, r *http.Request): + return netHTTPWrap{http.HandlerFunc(f)} + default: + log.Fatalf("Unknown handler type %v. Expected a web.Handler, "+ + "a http.Handler, or a function with signature func(C, "+ + "http.ResponseWriter, *http.Request) or "+ + "func(http.ResponseWriter, *http.Request)", h) + } + panic("log.Fatalf does not return") +} + +func httpMethod(mname string) method { + if method, ok := validMethodsMap[mname]; ok { + return method + } + return mIDK +} + +func (rt *router) compile() *routeMachine { + rt.lock.Lock() + defer rt.lock.Unlock() + sm := routeMachine{ + sm: compile(rt.routes), + routes: rt.routes, + } + rt.setMachine(&sm) + return &sm +} + +func (rt *router) route(c *C, w http.ResponseWriter, r *http.Request) { + rm := rt.getMachine() + if rm == nil { + rm = rt.compile() + } + + methods, route := rm.route(c, w, r) + if route != nil { + route.handler.ServeHTTPC(*c, w, r) + return + } + + if methods == 0 { + rt.notFound.ServeHTTPC(*c, w, r) + return + } + + var methodsList = make([]string, 0) + for mname, meth := range validMethodsMap { + if methods&meth != 0 { + methodsList = append(methodsList, mname) + } + } + sort.Strings(methodsList) + + if c.Env == nil { + c.Env = map[string]interface{}{ + ValidMethodsKey: methodsList, + } + } else { + c.Env[ValidMethodsKey] = methodsList + } + rt.notFound.ServeHTTPC(*c, w, r) +} + +func (rt *router) handleUntyped(p interface{}, m method, h interface{}) { + rt.handle(ParsePattern(p), m, parseHandler(h)) +} + +func (rt *router) handle(p Pattern, m method, h Handler) { + rt.lock.Lock() + defer rt.lock.Unlock() + + // Calculate the sorted insertion point, because there's no reason to do + // swapping hijinks if we're already making a copy. We need to use + // bubble sort because we can only compare adjacent elements. + pp := p.Prefix() + var i int + for i = len(rt.routes); i > 0; i-- { + rip := rt.routes[i-1].prefix + if rip <= pp || strings.HasPrefix(rip, pp) { + break + } + } + + newRoutes := make([]route, len(rt.routes)+1) + copy(newRoutes, rt.routes[:i]) + newRoutes[i] = route{ + prefix: pp, + method: m, + pattern: p, + handler: h, + } + copy(newRoutes[i+1:], rt.routes[i:]) + + rt.setMachine(nil) + rt.routes = newRoutes +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/router_test.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/router_test.go new file mode 100644 index 0000000..44dabf5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/router_test.go @@ -0,0 +1,326 @@ +package web + +import ( + "net/http" + "net/http/httptest" + "reflect" + "regexp" + "testing" + "time" +) + +// These tests can probably be DRY'd up a bunch + +func chHandler(ch chan string, s string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ch <- s + }) +} + +var methods = []string{"CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", + "POST", "PUT", "TRACE", "OTHER"} + +func TestMethods(t *testing.T) { + t.Parallel() + m := New() + ch := make(chan string, 1) + + m.Connect("/", chHandler(ch, "CONNECT")) + m.Delete("/", chHandler(ch, "DELETE")) + m.Head("/", chHandler(ch, "HEAD")) + m.Get("/", chHandler(ch, "GET")) + m.Options("/", chHandler(ch, "OPTIONS")) + m.Patch("/", chHandler(ch, "PATCH")) + m.Post("/", chHandler(ch, "POST")) + m.Put("/", chHandler(ch, "PUT")) + m.Trace("/", chHandler(ch, "TRACE")) + m.Handle("/", chHandler(ch, "OTHER")) + + for _, method := range methods { + r, _ := http.NewRequest(method, "/", nil) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + select { + case val := <-ch: + if val != method { + t.Errorf("Got %q, expected %q", val, method) + } + case <-time.After(5 * time.Millisecond): + t.Errorf("Timeout waiting for method %q", method) + } + } +} + +type testPattern struct{} + +func (t testPattern) Prefix() string { + return "" +} + +func (t testPattern) Match(r *http.Request, c *C) bool { + return true +} +func (t testPattern) Run(r *http.Request, c *C) { +} + +var _ Pattern = testPattern{} + +func TestPatternTypes(t *testing.T) { + t.Parallel() + m := New() + + m.Get("/hello/carl", http.NotFound) + m.Get("/hello/:name", http.NotFound) + m.Get(regexp.MustCompile(`^/hello/(?P.+)$`), http.NotFound) + m.Get(testPattern{}, http.NotFound) +} + +type testHandler chan string + +func (t testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + t <- "http" +} +func (t testHandler) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + t <- "httpc" +} + +var testHandlerTable = map[string]string{ + "/a": "http fn", + "/b": "http handler", + "/c": "web fn", + "/d": "web handler", + "/e": "httpc", +} + +func TestHandlerTypes(t *testing.T) { + t.Parallel() + m := New() + ch := make(chan string, 1) + + m.Get("/a", func(w http.ResponseWriter, r *http.Request) { + ch <- "http fn" + }) + m.Get("/b", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ch <- "http handler" + })) + m.Get("/c", func(c C, w http.ResponseWriter, r *http.Request) { + ch <- "web fn" + }) + m.Get("/d", HandlerFunc(func(c C, w http.ResponseWriter, r *http.Request) { + ch <- "web handler" + })) + m.Get("/e", testHandler(ch)) + + for route, response := range testHandlerTable { + r, _ := http.NewRequest("GET", route, nil) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + select { + case resp := <-ch: + if resp != response { + t.Errorf("Got %q, expected %q", resp, response) + } + case <-time.After(5 * time.Millisecond): + t.Errorf("Timeout waiting for path %q", route) + } + + } +} + +// The idea behind this test is to comprehensively test if routes are being +// applied in the right order. We define a special pattern type that always +// matches so long as it's greater than or equal to the global test index. By +// incrementing this index, we can invalidate all routes up to some point, and +// therefore test the routing guarantee that Goji provides: for any path P, if +// both A and B match P, and if A was inserted before B, then Goji will route to +// A before it routes to B. +var rsRoutes = []string{ + "/", + "/a", + "/a", + "/b", + "/ab", + "/", + "/ba", + "/b", + "/a", +} + +var rsTests = []struct { + key string + results []int +}{ + {"/", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}}, + {"/a", []int{0, 1, 2, 5, 5, 5, 8, 8, 8, -1}}, + {"/b", []int{0, 3, 3, 3, 5, 5, 7, 7, -1, -1}}, + {"/ab", []int{0, 1, 2, 4, 4, 5, 8, 8, 8, -1}}, + {"/ba", []int{0, 3, 3, 3, 5, 5, 6, 7, -1, -1}}, + {"/c", []int{0, 5, 5, 5, 5, 5, -1, -1, -1, -1}}, + {"nope", []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}}, +} + +type rsPattern struct { + i int + counter *int + prefix string + ichan chan int +} + +func (rs rsPattern) Prefix() string { + return rs.prefix +} +func (rs rsPattern) Match(_ *http.Request, _ *C) bool { + return rs.i >= *rs.counter +} +func (rs rsPattern) Run(_ *http.Request, _ *C) { +} + +func (rs rsPattern) ServeHTTP(_ http.ResponseWriter, _ *http.Request) { + rs.ichan <- rs.i +} + +var _ Pattern = rsPattern{} +var _ http.Handler = rsPattern{} + +func TestRouteSelection(t *testing.T) { + t.Parallel() + m := New() + counter := 0 + ichan := make(chan int, 1) + m.NotFound(func(w http.ResponseWriter, r *http.Request) { + ichan <- -1 + }) + + for i, s := range rsRoutes { + pat := rsPattern{ + i: i, + counter: &counter, + prefix: s, + ichan: ichan, + } + m.Get(pat, pat) + } + + for _, test := range rsTests { + var n int + for counter, n = range test.results { + r, _ := http.NewRequest("GET", test.key, nil) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + actual := <-ichan + if n != actual { + t.Errorf("Expected %q @ %d to be %d, got %d", + test.key, counter, n, actual) + } + } + } +} + +func TestNotFound(t *testing.T) { + t.Parallel() + m := New() + + r, _ := http.NewRequest("post", "/", nil) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + if w.Code != 404 { + t.Errorf("Expected 404, got %d", w.Code) + } + + m.NotFound(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "I'm a teapot!", http.StatusTeapot) + }) + + r, _ = http.NewRequest("POST", "/", nil) + w = httptest.NewRecorder() + m.ServeHTTP(w, r) + if w.Code != http.StatusTeapot { + t.Errorf("Expected a teapot, got %d", w.Code) + } +} + +func TestPrefix(t *testing.T) { + t.Parallel() + m := New() + ch := make(chan string, 1) + + m.Handle("/hello/*", func(w http.ResponseWriter, r *http.Request) { + ch <- r.URL.Path + }) + + r, _ := http.NewRequest("GET", "/hello/world", nil) + w := httptest.NewRecorder() + m.ServeHTTP(w, r) + select { + case val := <-ch: + if val != "/hello/world" { + t.Errorf("Got %q, expected /hello/world", val) + } + case <-time.After(5 * time.Millisecond): + t.Errorf("Timeout waiting for hello") + } +} + +var validMethodsTable = map[string][]string{ + "/hello/carl": {"DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"}, + "/hello/bob": {"DELETE", "GET", "HEAD", "PATCH", "PUT"}, + "/hola/carl": {"DELETE", "GET", "HEAD", "PUT"}, + "/hola/bob": {"DELETE"}, + "/does/not/compute": {}, +} + +func TestValidMethods(t *testing.T) { + t.Parallel() + m := New() + ch := make(chan []string, 1) + + m.NotFound(func(c C, w http.ResponseWriter, r *http.Request) { + if c.Env == nil { + ch <- []string{} + return + } + methods, ok := c.Env[ValidMethodsKey] + if !ok { + ch <- []string{} + return + } + ch <- methods.([]string) + }) + + m.Get("/hello/carl", http.NotFound) + m.Post("/hello/carl", http.NotFound) + m.Head("/hello/bob", http.NotFound) + m.Get("/hello/:name", http.NotFound) + m.Put("/hello/:name", http.NotFound) + m.Patch("/hello/:name", http.NotFound) + m.Get("/:greet/carl", http.NotFound) + m.Put("/:greet/carl", http.NotFound) + m.Delete("/:greet/:anyone", http.NotFound) + + for path, eMethods := range validMethodsTable { + r, _ := http.NewRequest("BOGUS", path, nil) + m.ServeHTTP(httptest.NewRecorder(), r) + aMethods := <-ch + if !reflect.DeepEqual(eMethods, aMethods) { + t.Errorf("For %q, expected %v, got %v", path, eMethods, + aMethods) + } + } + + // This should also work when c.Env has already been initalized + m.Use(func(c *C, h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + c.Env = make(map[string]interface{}) + h.ServeHTTP(w, r) + }) + }) + for path, eMethods := range validMethodsTable { + r, _ := http.NewRequest("BOGUS", path, nil) + m.ServeHTTP(httptest.NewRecorder(), r) + aMethods := <-ch + if !reflect.DeepEqual(eMethods, aMethods) { + t.Errorf("For %q, expected %v, got %v", path, eMethods, + aMethods) + } + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/string_pattern.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/string_pattern.go new file mode 100644 index 0000000..9e9945a --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/string_pattern.go @@ -0,0 +1,133 @@ +package web + +import ( + "fmt" + "net/http" + "regexp" + "strings" +) + +// stringPattern is a struct describing +type stringPattern struct { + raw string + pats []string + breaks []byte + literals []string + wildcard bool +} + +func (s stringPattern) Prefix() string { + return s.literals[0] +} +func (s stringPattern) Match(r *http.Request, c *C) bool { + return s.match(r, c, true) +} +func (s stringPattern) Run(r *http.Request, c *C) { + s.match(r, c, false) +} +func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { + path := r.URL.Path + var matches map[string]string + if !dryrun { + if s.wildcard { + matches = make(map[string]string, len(s.pats)+1) + } else if len(s.pats) != 0 { + matches = make(map[string]string, len(s.pats)) + } + } + for i, pat := range s.pats { + sli := s.literals[i] + if !strings.HasPrefix(path, sli) { + return false + } + path = path[len(sli):] + + m := 0 + bc := s.breaks[i] + for ; m < len(path); m++ { + if path[m] == bc || path[m] == '/' { + break + } + } + if m == 0 { + // Empty strings are not matches, otherwise routes like + // "/:foo" would match the path "/" + return false + } + if !dryrun { + matches[pat] = path[:m] + } + path = path[m:] + } + // There's exactly one more literal than pat. + tail := s.literals[len(s.pats)] + if s.wildcard { + if !strings.HasPrefix(path, tail) { + return false + } + if !dryrun { + matches["*"] = path[len(tail)-1:] + } + } else if path != tail { + return false + } + + if c == nil || dryrun { + return true + } + + if c.URLParams == nil { + c.URLParams = matches + } else { + for k, v := range matches { + c.URLParams[k] = v + } + } + return true +} + +func (s stringPattern) String() string { + return fmt.Sprintf("stringPattern(%q)", s.raw) +} + +// "Break characters" are characters that can end patterns. They are not allowed +// to appear in pattern names. "/" was chosen because it is the standard path +// separator, and "." was chosen because it often delimits file extensions. ";" +// and "," were chosen because Section 3.3 of RFC 3986 suggests their use. +const bc = "/.;," + +var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) + +func parseStringPattern(s string) stringPattern { + raw := s + var wildcard bool + if strings.HasSuffix(s, "/*") { + s = s[:len(s)-1] + wildcard = true + } + + matches := patternRe.FindAllStringSubmatchIndex(s, -1) + pats := make([]string, len(matches)) + breaks := make([]byte, len(matches)) + literals := make([]string, len(matches)+1) + n := 0 + for i, match := range matches { + a, b := match[2], match[3] + literals[i] = s[n : a-1] // Need to leave off the colon + pats[i] = s[a:b] + if b == len(s) { + breaks[i] = '/' + } else { + breaks[i] = s[b] + } + n = b + } + literals[len(matches)] = s[n:] + return stringPattern{ + raw: raw, + pats: pats, + breaks: breaks, + literals: literals, + wildcard: wildcard, + } +} diff --git a/Godeps/_workspace/src/github.com/zenazn/goji/web/web.go b/Godeps/_workspace/src/github.com/zenazn/goji/web/web.go new file mode 100644 index 0000000..480f670 --- /dev/null +++ b/Godeps/_workspace/src/github.com/zenazn/goji/web/web.go @@ -0,0 +1,128 @@ +/* +Package web implements a fast and flexible middleware stack and mux. + +The underlying philosophy behind this package is that net/http is a very good +HTTP library which is only missing a few features. If you disagree with this +statement (e.g., you think that the interfaces it exposes are not especially +good, or if you're looking for a comprehensive "batteries included" feature +list), you're likely not going to have a good time using this library. In that +spirit, we have attempted wherever possible to be compatible with net/http. You +should be able to insert any net/http compliant handler into this library, or +use this library with any other net/http compliant mux. + +This package attempts to solve three problems that net/http does not. First, it +allows you to specify URL patterns with Sinatra-like named wildcards and +regexps. Second, it allows you to write reconfigurable middleware stacks. And +finally, it allows you to attach additional context to requests, in a manner +that can be manipulated by both compliant middleware and handlers. + +A usage example: + + m := web.New() + +Use your favorite HTTP verbs: + + var legacyFooHttpHandler http.Handler // From elsewhere + m.Get("/foo", legacyFooHttpHandler) + m.Post("/bar", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello world!")) + }) + +Bind parameters using either Sinatra-like patterns or regular expressions: + + m.Get("/hello/:name", func(c web.C, w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) + }) + pattern := regexp.MustCompile(`^/ip/(?P(?:\d{1,3}\.){3}\d{1,3})$`) + m.Get(pattern, func(c web.C, w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Info for IP address %s:", c.URLParams["ip"]) + }) + +Middleware are functions that wrap http.Handlers, just like you'd use with raw +net/http. Middleware functions can optionally take a context parameter, which +will be threaded throughout the middleware stack and to the final handler, even +if not all of these things support contexts. Middleware are encouraged to use +the Env parameter to pass data to other middleware and to the final handler: + + m.Use(func(h http.Handler) http.Handler { + handler := func(w http.ResponseWriter, r *http.Request) { + log.Println("Before request") + h.ServeHTTP(w, r) + log.Println("After request") + } + return http.HandlerFunc(handler) + }) + m.Use(func(c *web.C, h http.Handler) http.Handler { + handler := func(w http.ResponseWriter, r *http.Request) { + cookie, err := r.Cookie("user") + if err == nil { + // Consider using the middleware EnvInit instead + // of repeating the below check + if c.Env == nil { + c.Env = make(map[string]interface{}) + } + c.Env["user"] = cookie.Value + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(handler) + }) + + m.Get("/baz", func(c web.C, w http.ResponseWriter, r *http.Request) { + if user, ok := c.Env["user"].(string); ok { + w.Write([]byte("Hello " + user)) + } else { + w.Write([]byte("Hello Stranger!")) + } + }) +*/ +package web + +import ( + "net/http" +) + +/* +C is a per-request context object which is threaded through all compliant middleware +layers and to the final request handler. + +As an implementation detail, references to these structs are reused between +requests to reduce allocation churn, but the maps they contain are created fresh +on every request. If you are closing over a context (especially relevant for +middleware), you should not close over either the URLParams or Env objects, +instead accessing them through the context whenever they are required. +*/ +type C struct { + // The parameters parsed by the mux from the URL itself. In most cases, + // will contain a map from programmer-specified identifiers to the + // strings that matched those identifiers, but if a unnamed regex + // capture is used, it will be assigned to the special identifiers "$1", + // "$2", etc. + URLParams map[string]string + // A free-form environment, similar to Rack or PEP 333's environments. + // Middleware layers are encouraged to pass data to downstream layers + // and other handlers using this map, and are even more strongly + // encouraged to document and maybe namespace the keys they use. + Env map[string]interface{} +} + +// Handler is a superset of net/http's http.Handler, which also includes a +// mechanism for serving requests with a context. If your handler does not +// support the use of contexts, we encourage you to use http.Handler instead. +type Handler interface { + http.Handler + ServeHTTPC(C, http.ResponseWriter, *http.Request) +} + +// HandlerFunc is like net/http's http.HandlerFunc, but supports a context +// object. Implements both http.Handler and web.Handler free of charge. +type HandlerFunc func(C, http.ResponseWriter, *http.Request) + +func (h HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h(C{}, w, r) +} + +// ServeHTTPC wraps ServeHTTP with a context parameter. +func (h HandlerFunc) ServeHTTPC(c C, w http.ResponseWriter, r *http.Request) { + h(c, w, r) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5.go b/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5.go new file mode 100644 index 0000000..8c1b299 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5.go @@ -0,0 +1,526 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cast5 implements CAST5, as defined in RFC 2144. CAST5 is a common +// OpenPGP cipher. +package cast5 + +import "errors" + +const BlockSize = 8 +const KeySize = 16 + +type Cipher struct { + masking [16]uint32 + rotate [16]uint8 +} + +func NewCipher(key []byte) (c *Cipher, err error) { + if len(key) != KeySize { + return nil, errors.New("CAST5: keys must be 16 bytes") + } + + c = new(Cipher) + c.keySchedule(key) + return +} + +func (c *Cipher) BlockSize() int { + return BlockSize +} + +func (c *Cipher) Encrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + + l, r = r, l^f1(r, c.masking[0], c.rotate[0]) + l, r = r, l^f2(r, c.masking[1], c.rotate[1]) + l, r = r, l^f3(r, c.masking[2], c.rotate[2]) + l, r = r, l^f1(r, c.masking[3], c.rotate[3]) + + l, r = r, l^f2(r, c.masking[4], c.rotate[4]) + l, r = r, l^f3(r, c.masking[5], c.rotate[5]) + l, r = r, l^f1(r, c.masking[6], c.rotate[6]) + l, r = r, l^f2(r, c.masking[7], c.rotate[7]) + + l, r = r, l^f3(r, c.masking[8], c.rotate[8]) + l, r = r, l^f1(r, c.masking[9], c.rotate[9]) + l, r = r, l^f2(r, c.masking[10], c.rotate[10]) + l, r = r, l^f3(r, c.masking[11], c.rotate[11]) + + l, r = r, l^f1(r, c.masking[12], c.rotate[12]) + l, r = r, l^f2(r, c.masking[13], c.rotate[13]) + l, r = r, l^f3(r, c.masking[14], c.rotate[14]) + l, r = r, l^f1(r, c.masking[15], c.rotate[15]) + + dst[0] = uint8(r >> 24) + dst[1] = uint8(r >> 16) + dst[2] = uint8(r >> 8) + dst[3] = uint8(r) + dst[4] = uint8(l >> 24) + dst[5] = uint8(l >> 16) + dst[6] = uint8(l >> 8) + dst[7] = uint8(l) +} + +func (c *Cipher) Decrypt(dst, src []byte) { + l := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3]) + r := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7]) + + l, r = r, l^f1(r, c.masking[15], c.rotate[15]) + l, r = r, l^f3(r, c.masking[14], c.rotate[14]) + l, r = r, l^f2(r, c.masking[13], c.rotate[13]) + l, r = r, l^f1(r, c.masking[12], c.rotate[12]) + + l, r = r, l^f3(r, c.masking[11], c.rotate[11]) + l, r = r, l^f2(r, c.masking[10], c.rotate[10]) + l, r = r, l^f1(r, c.masking[9], c.rotate[9]) + l, r = r, l^f3(r, c.masking[8], c.rotate[8]) + + l, r = r, l^f2(r, c.masking[7], c.rotate[7]) + l, r = r, l^f1(r, c.masking[6], c.rotate[6]) + l, r = r, l^f3(r, c.masking[5], c.rotate[5]) + l, r = r, l^f2(r, c.masking[4], c.rotate[4]) + + l, r = r, l^f1(r, c.masking[3], c.rotate[3]) + l, r = r, l^f3(r, c.masking[2], c.rotate[2]) + l, r = r, l^f2(r, c.masking[1], c.rotate[1]) + l, r = r, l^f1(r, c.masking[0], c.rotate[0]) + + dst[0] = uint8(r >> 24) + dst[1] = uint8(r >> 16) + dst[2] = uint8(r >> 8) + dst[3] = uint8(r) + dst[4] = uint8(l >> 24) + dst[5] = uint8(l >> 16) + dst[6] = uint8(l >> 8) + dst[7] = uint8(l) +} + +type keyScheduleA [4][7]uint8 +type keyScheduleB [4][5]uint8 + +// keyScheduleRound contains the magic values for a round of the key schedule. +// The keyScheduleA deals with the lines like: +// z0z1z2z3 = x0x1x2x3 ^ S5[xD] ^ S6[xF] ^ S7[xC] ^ S8[xE] ^ S7[x8] +// Conceptually, both x and z are in the same array, x first. The first +// element describes which word of this array gets written to and the +// second, which word gets read. So, for the line above, it's "4, 0", because +// it's writing to the first word of z, which, being after x, is word 4, and +// reading from the first word of x: word 0. +// +// Next are the indexes into the S-boxes. Now the array is treated as bytes. So +// "xD" is 0xd. The first byte of z is written as "16 + 0", just to be clear +// that it's z that we're indexing. +// +// keyScheduleB deals with lines like: +// K1 = S5[z8] ^ S6[z9] ^ S7[z7] ^ S8[z6] ^ S5[z2] +// "K1" is ignored because key words are always written in order. So the five +// elements are the S-box indexes. They use the same form as in keyScheduleA, +// above. + +type keyScheduleRound struct{} +type keySchedule []keyScheduleRound + +var schedule = []struct { + a keyScheduleA + b keyScheduleB +}{ + { + keyScheduleA{ + {4, 0, 0xd, 0xf, 0xc, 0xe, 0x8}, + {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa}, + {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9}, + {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb}, + }, + keyScheduleB{ + {16 + 8, 16 + 9, 16 + 7, 16 + 6, 16 + 2}, + {16 + 0xa, 16 + 0xb, 16 + 5, 16 + 4, 16 + 6}, + {16 + 0xc, 16 + 0xd, 16 + 3, 16 + 2, 16 + 9}, + {16 + 0xe, 16 + 0xf, 16 + 1, 16 + 0, 16 + 0xc}, + }, + }, + { + keyScheduleA{ + {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0}, + {1, 4, 0, 2, 1, 3, 16 + 2}, + {2, 5, 7, 6, 5, 4, 16 + 1}, + {3, 7, 0xa, 9, 0xb, 8, 16 + 3}, + }, + keyScheduleB{ + {3, 2, 0xc, 0xd, 8}, + {1, 0, 0xe, 0xf, 0xd}, + {7, 6, 8, 9, 3}, + {5, 4, 0xa, 0xb, 7}, + }, + }, + { + keyScheduleA{ + {4, 0, 0xd, 0xf, 0xc, 0xe, 8}, + {5, 2, 16 + 0, 16 + 2, 16 + 1, 16 + 3, 0xa}, + {6, 3, 16 + 7, 16 + 6, 16 + 5, 16 + 4, 9}, + {7, 1, 16 + 0xa, 16 + 9, 16 + 0xb, 16 + 8, 0xb}, + }, + keyScheduleB{ + {16 + 3, 16 + 2, 16 + 0xc, 16 + 0xd, 16 + 9}, + {16 + 1, 16 + 0, 16 + 0xe, 16 + 0xf, 16 + 0xc}, + {16 + 7, 16 + 6, 16 + 8, 16 + 9, 16 + 2}, + {16 + 5, 16 + 4, 16 + 0xa, 16 + 0xb, 16 + 6}, + }, + }, + { + keyScheduleA{ + {0, 6, 16 + 5, 16 + 7, 16 + 4, 16 + 6, 16 + 0}, + {1, 4, 0, 2, 1, 3, 16 + 2}, + {2, 5, 7, 6, 5, 4, 16 + 1}, + {3, 7, 0xa, 9, 0xb, 8, 16 + 3}, + }, + keyScheduleB{ + {8, 9, 7, 6, 3}, + {0xa, 0xb, 5, 4, 7}, + {0xc, 0xd, 3, 2, 8}, + {0xe, 0xf, 1, 0, 0xd}, + }, + }, +} + +func (c *Cipher) keySchedule(in []byte) { + var t [8]uint32 + var k [32]uint32 + + for i := 0; i < 4; i++ { + j := i * 4 + t[i] = uint32(in[j])<<24 | uint32(in[j+1])<<16 | uint32(in[j+2])<<8 | uint32(in[j+3]) + } + + x := []byte{6, 7, 4, 5} + ki := 0 + + for half := 0; half < 2; half++ { + for _, round := range schedule { + for j := 0; j < 4; j++ { + var a [7]uint8 + copy(a[:], round.a[j][:]) + w := t[a[1]] + w ^= sBox[4][(t[a[2]>>2]>>(24-8*(a[2]&3)))&0xff] + w ^= sBox[5][(t[a[3]>>2]>>(24-8*(a[3]&3)))&0xff] + w ^= sBox[6][(t[a[4]>>2]>>(24-8*(a[4]&3)))&0xff] + w ^= sBox[7][(t[a[5]>>2]>>(24-8*(a[5]&3)))&0xff] + w ^= sBox[x[j]][(t[a[6]>>2]>>(24-8*(a[6]&3)))&0xff] + t[a[0]] = w + } + + for j := 0; j < 4; j++ { + var b [5]uint8 + copy(b[:], round.b[j][:]) + w := sBox[4][(t[b[0]>>2]>>(24-8*(b[0]&3)))&0xff] + w ^= sBox[5][(t[b[1]>>2]>>(24-8*(b[1]&3)))&0xff] + w ^= sBox[6][(t[b[2]>>2]>>(24-8*(b[2]&3)))&0xff] + w ^= sBox[7][(t[b[3]>>2]>>(24-8*(b[3]&3)))&0xff] + w ^= sBox[4+j][(t[b[4]>>2]>>(24-8*(b[4]&3)))&0xff] + k[ki] = w + ki++ + } + } + } + + for i := 0; i < 16; i++ { + c.masking[i] = k[i] + c.rotate[i] = uint8(k[16+i] & 0x1f) + } +} + +// These are the three 'f' functions. See RFC 2144, section 2.2. +func f1(d, m uint32, r uint8) uint32 { + t := m + d + I := (t << r) | (t >> (32 - r)) + return ((sBox[0][I>>24] ^ sBox[1][(I>>16)&0xff]) - sBox[2][(I>>8)&0xff]) + sBox[3][I&0xff] +} + +func f2(d, m uint32, r uint8) uint32 { + t := m ^ d + I := (t << r) | (t >> (32 - r)) + return ((sBox[0][I>>24] - sBox[1][(I>>16)&0xff]) + sBox[2][(I>>8)&0xff]) ^ sBox[3][I&0xff] +} + +func f3(d, m uint32, r uint8) uint32 { + t := m - d + I := (t << r) | (t >> (32 - r)) + return ((sBox[0][I>>24] + sBox[1][(I>>16)&0xff]) ^ sBox[2][(I>>8)&0xff]) - sBox[3][I&0xff] +} + +var sBox = [8][256]uint32{ + { + 0x30fb40d4, 0x9fa0ff0b, 0x6beccd2f, 0x3f258c7a, 0x1e213f2f, 0x9c004dd3, 0x6003e540, 0xcf9fc949, + 0xbfd4af27, 0x88bbbdb5, 0xe2034090, 0x98d09675, 0x6e63a0e0, 0x15c361d2, 0xc2e7661d, 0x22d4ff8e, + 0x28683b6f, 0xc07fd059, 0xff2379c8, 0x775f50e2, 0x43c340d3, 0xdf2f8656, 0x887ca41a, 0xa2d2bd2d, + 0xa1c9e0d6, 0x346c4819, 0x61b76d87, 0x22540f2f, 0x2abe32e1, 0xaa54166b, 0x22568e3a, 0xa2d341d0, + 0x66db40c8, 0xa784392f, 0x004dff2f, 0x2db9d2de, 0x97943fac, 0x4a97c1d8, 0x527644b7, 0xb5f437a7, + 0xb82cbaef, 0xd751d159, 0x6ff7f0ed, 0x5a097a1f, 0x827b68d0, 0x90ecf52e, 0x22b0c054, 0xbc8e5935, + 0x4b6d2f7f, 0x50bb64a2, 0xd2664910, 0xbee5812d, 0xb7332290, 0xe93b159f, 0xb48ee411, 0x4bff345d, + 0xfd45c240, 0xad31973f, 0xc4f6d02e, 0x55fc8165, 0xd5b1caad, 0xa1ac2dae, 0xa2d4b76d, 0xc19b0c50, + 0x882240f2, 0x0c6e4f38, 0xa4e4bfd7, 0x4f5ba272, 0x564c1d2f, 0xc59c5319, 0xb949e354, 0xb04669fe, + 0xb1b6ab8a, 0xc71358dd, 0x6385c545, 0x110f935d, 0x57538ad5, 0x6a390493, 0xe63d37e0, 0x2a54f6b3, + 0x3a787d5f, 0x6276a0b5, 0x19a6fcdf, 0x7a42206a, 0x29f9d4d5, 0xf61b1891, 0xbb72275e, 0xaa508167, + 0x38901091, 0xc6b505eb, 0x84c7cb8c, 0x2ad75a0f, 0x874a1427, 0xa2d1936b, 0x2ad286af, 0xaa56d291, + 0xd7894360, 0x425c750d, 0x93b39e26, 0x187184c9, 0x6c00b32d, 0x73e2bb14, 0xa0bebc3c, 0x54623779, + 0x64459eab, 0x3f328b82, 0x7718cf82, 0x59a2cea6, 0x04ee002e, 0x89fe78e6, 0x3fab0950, 0x325ff6c2, + 0x81383f05, 0x6963c5c8, 0x76cb5ad6, 0xd49974c9, 0xca180dcf, 0x380782d5, 0xc7fa5cf6, 0x8ac31511, + 0x35e79e13, 0x47da91d0, 0xf40f9086, 0xa7e2419e, 0x31366241, 0x051ef495, 0xaa573b04, 0x4a805d8d, + 0x548300d0, 0x00322a3c, 0xbf64cddf, 0xba57a68e, 0x75c6372b, 0x50afd341, 0xa7c13275, 0x915a0bf5, + 0x6b54bfab, 0x2b0b1426, 0xab4cc9d7, 0x449ccd82, 0xf7fbf265, 0xab85c5f3, 0x1b55db94, 0xaad4e324, + 0xcfa4bd3f, 0x2deaa3e2, 0x9e204d02, 0xc8bd25ac, 0xeadf55b3, 0xd5bd9e98, 0xe31231b2, 0x2ad5ad6c, + 0x954329de, 0xadbe4528, 0xd8710f69, 0xaa51c90f, 0xaa786bf6, 0x22513f1e, 0xaa51a79b, 0x2ad344cc, + 0x7b5a41f0, 0xd37cfbad, 0x1b069505, 0x41ece491, 0xb4c332e6, 0x032268d4, 0xc9600acc, 0xce387e6d, + 0xbf6bb16c, 0x6a70fb78, 0x0d03d9c9, 0xd4df39de, 0xe01063da, 0x4736f464, 0x5ad328d8, 0xb347cc96, + 0x75bb0fc3, 0x98511bfb, 0x4ffbcc35, 0xb58bcf6a, 0xe11f0abc, 0xbfc5fe4a, 0xa70aec10, 0xac39570a, + 0x3f04442f, 0x6188b153, 0xe0397a2e, 0x5727cb79, 0x9ceb418f, 0x1cacd68d, 0x2ad37c96, 0x0175cb9d, + 0xc69dff09, 0xc75b65f0, 0xd9db40d8, 0xec0e7779, 0x4744ead4, 0xb11c3274, 0xdd24cb9e, 0x7e1c54bd, + 0xf01144f9, 0xd2240eb1, 0x9675b3fd, 0xa3ac3755, 0xd47c27af, 0x51c85f4d, 0x56907596, 0xa5bb15e6, + 0x580304f0, 0xca042cf1, 0x011a37ea, 0x8dbfaadb, 0x35ba3e4a, 0x3526ffa0, 0xc37b4d09, 0xbc306ed9, + 0x98a52666, 0x5648f725, 0xff5e569d, 0x0ced63d0, 0x7c63b2cf, 0x700b45e1, 0xd5ea50f1, 0x85a92872, + 0xaf1fbda7, 0xd4234870, 0xa7870bf3, 0x2d3b4d79, 0x42e04198, 0x0cd0ede7, 0x26470db8, 0xf881814c, + 0x474d6ad7, 0x7c0c5e5c, 0xd1231959, 0x381b7298, 0xf5d2f4db, 0xab838653, 0x6e2f1e23, 0x83719c9e, + 0xbd91e046, 0x9a56456e, 0xdc39200c, 0x20c8c571, 0x962bda1c, 0xe1e696ff, 0xb141ab08, 0x7cca89b9, + 0x1a69e783, 0x02cc4843, 0xa2f7c579, 0x429ef47d, 0x427b169c, 0x5ac9f049, 0xdd8f0f00, 0x5c8165bf, + }, + { + 0x1f201094, 0xef0ba75b, 0x69e3cf7e, 0x393f4380, 0xfe61cf7a, 0xeec5207a, 0x55889c94, 0x72fc0651, + 0xada7ef79, 0x4e1d7235, 0xd55a63ce, 0xde0436ba, 0x99c430ef, 0x5f0c0794, 0x18dcdb7d, 0xa1d6eff3, + 0xa0b52f7b, 0x59e83605, 0xee15b094, 0xe9ffd909, 0xdc440086, 0xef944459, 0xba83ccb3, 0xe0c3cdfb, + 0xd1da4181, 0x3b092ab1, 0xf997f1c1, 0xa5e6cf7b, 0x01420ddb, 0xe4e7ef5b, 0x25a1ff41, 0xe180f806, + 0x1fc41080, 0x179bee7a, 0xd37ac6a9, 0xfe5830a4, 0x98de8b7f, 0x77e83f4e, 0x79929269, 0x24fa9f7b, + 0xe113c85b, 0xacc40083, 0xd7503525, 0xf7ea615f, 0x62143154, 0x0d554b63, 0x5d681121, 0xc866c359, + 0x3d63cf73, 0xcee234c0, 0xd4d87e87, 0x5c672b21, 0x071f6181, 0x39f7627f, 0x361e3084, 0xe4eb573b, + 0x602f64a4, 0xd63acd9c, 0x1bbc4635, 0x9e81032d, 0x2701f50c, 0x99847ab4, 0xa0e3df79, 0xba6cf38c, + 0x10843094, 0x2537a95e, 0xf46f6ffe, 0xa1ff3b1f, 0x208cfb6a, 0x8f458c74, 0xd9e0a227, 0x4ec73a34, + 0xfc884f69, 0x3e4de8df, 0xef0e0088, 0x3559648d, 0x8a45388c, 0x1d804366, 0x721d9bfd, 0xa58684bb, + 0xe8256333, 0x844e8212, 0x128d8098, 0xfed33fb4, 0xce280ae1, 0x27e19ba5, 0xd5a6c252, 0xe49754bd, + 0xc5d655dd, 0xeb667064, 0x77840b4d, 0xa1b6a801, 0x84db26a9, 0xe0b56714, 0x21f043b7, 0xe5d05860, + 0x54f03084, 0x066ff472, 0xa31aa153, 0xdadc4755, 0xb5625dbf, 0x68561be6, 0x83ca6b94, 0x2d6ed23b, + 0xeccf01db, 0xa6d3d0ba, 0xb6803d5c, 0xaf77a709, 0x33b4a34c, 0x397bc8d6, 0x5ee22b95, 0x5f0e5304, + 0x81ed6f61, 0x20e74364, 0xb45e1378, 0xde18639b, 0x881ca122, 0xb96726d1, 0x8049a7e8, 0x22b7da7b, + 0x5e552d25, 0x5272d237, 0x79d2951c, 0xc60d894c, 0x488cb402, 0x1ba4fe5b, 0xa4b09f6b, 0x1ca815cf, + 0xa20c3005, 0x8871df63, 0xb9de2fcb, 0x0cc6c9e9, 0x0beeff53, 0xe3214517, 0xb4542835, 0x9f63293c, + 0xee41e729, 0x6e1d2d7c, 0x50045286, 0x1e6685f3, 0xf33401c6, 0x30a22c95, 0x31a70850, 0x60930f13, + 0x73f98417, 0xa1269859, 0xec645c44, 0x52c877a9, 0xcdff33a6, 0xa02b1741, 0x7cbad9a2, 0x2180036f, + 0x50d99c08, 0xcb3f4861, 0xc26bd765, 0x64a3f6ab, 0x80342676, 0x25a75e7b, 0xe4e6d1fc, 0x20c710e6, + 0xcdf0b680, 0x17844d3b, 0x31eef84d, 0x7e0824e4, 0x2ccb49eb, 0x846a3bae, 0x8ff77888, 0xee5d60f6, + 0x7af75673, 0x2fdd5cdb, 0xa11631c1, 0x30f66f43, 0xb3faec54, 0x157fd7fa, 0xef8579cc, 0xd152de58, + 0xdb2ffd5e, 0x8f32ce19, 0x306af97a, 0x02f03ef8, 0x99319ad5, 0xc242fa0f, 0xa7e3ebb0, 0xc68e4906, + 0xb8da230c, 0x80823028, 0xdcdef3c8, 0xd35fb171, 0x088a1bc8, 0xbec0c560, 0x61a3c9e8, 0xbca8f54d, + 0xc72feffa, 0x22822e99, 0x82c570b4, 0xd8d94e89, 0x8b1c34bc, 0x301e16e6, 0x273be979, 0xb0ffeaa6, + 0x61d9b8c6, 0x00b24869, 0xb7ffce3f, 0x08dc283b, 0x43daf65a, 0xf7e19798, 0x7619b72f, 0x8f1c9ba4, + 0xdc8637a0, 0x16a7d3b1, 0x9fc393b7, 0xa7136eeb, 0xc6bcc63e, 0x1a513742, 0xef6828bc, 0x520365d6, + 0x2d6a77ab, 0x3527ed4b, 0x821fd216, 0x095c6e2e, 0xdb92f2fb, 0x5eea29cb, 0x145892f5, 0x91584f7f, + 0x5483697b, 0x2667a8cc, 0x85196048, 0x8c4bacea, 0x833860d4, 0x0d23e0f9, 0x6c387e8a, 0x0ae6d249, + 0xb284600c, 0xd835731d, 0xdcb1c647, 0xac4c56ea, 0x3ebd81b3, 0x230eabb0, 0x6438bc87, 0xf0b5b1fa, + 0x8f5ea2b3, 0xfc184642, 0x0a036b7a, 0x4fb089bd, 0x649da589, 0xa345415e, 0x5c038323, 0x3e5d3bb9, + 0x43d79572, 0x7e6dd07c, 0x06dfdf1e, 0x6c6cc4ef, 0x7160a539, 0x73bfbe70, 0x83877605, 0x4523ecf1, + }, + { + 0x8defc240, 0x25fa5d9f, 0xeb903dbf, 0xe810c907, 0x47607fff, 0x369fe44b, 0x8c1fc644, 0xaececa90, + 0xbeb1f9bf, 0xeefbcaea, 0xe8cf1950, 0x51df07ae, 0x920e8806, 0xf0ad0548, 0xe13c8d83, 0x927010d5, + 0x11107d9f, 0x07647db9, 0xb2e3e4d4, 0x3d4f285e, 0xb9afa820, 0xfade82e0, 0xa067268b, 0x8272792e, + 0x553fb2c0, 0x489ae22b, 0xd4ef9794, 0x125e3fbc, 0x21fffcee, 0x825b1bfd, 0x9255c5ed, 0x1257a240, + 0x4e1a8302, 0xbae07fff, 0x528246e7, 0x8e57140e, 0x3373f7bf, 0x8c9f8188, 0xa6fc4ee8, 0xc982b5a5, + 0xa8c01db7, 0x579fc264, 0x67094f31, 0xf2bd3f5f, 0x40fff7c1, 0x1fb78dfc, 0x8e6bd2c1, 0x437be59b, + 0x99b03dbf, 0xb5dbc64b, 0x638dc0e6, 0x55819d99, 0xa197c81c, 0x4a012d6e, 0xc5884a28, 0xccc36f71, + 0xb843c213, 0x6c0743f1, 0x8309893c, 0x0feddd5f, 0x2f7fe850, 0xd7c07f7e, 0x02507fbf, 0x5afb9a04, + 0xa747d2d0, 0x1651192e, 0xaf70bf3e, 0x58c31380, 0x5f98302e, 0x727cc3c4, 0x0a0fb402, 0x0f7fef82, + 0x8c96fdad, 0x5d2c2aae, 0x8ee99a49, 0x50da88b8, 0x8427f4a0, 0x1eac5790, 0x796fb449, 0x8252dc15, + 0xefbd7d9b, 0xa672597d, 0xada840d8, 0x45f54504, 0xfa5d7403, 0xe83ec305, 0x4f91751a, 0x925669c2, + 0x23efe941, 0xa903f12e, 0x60270df2, 0x0276e4b6, 0x94fd6574, 0x927985b2, 0x8276dbcb, 0x02778176, + 0xf8af918d, 0x4e48f79e, 0x8f616ddf, 0xe29d840e, 0x842f7d83, 0x340ce5c8, 0x96bbb682, 0x93b4b148, + 0xef303cab, 0x984faf28, 0x779faf9b, 0x92dc560d, 0x224d1e20, 0x8437aa88, 0x7d29dc96, 0x2756d3dc, + 0x8b907cee, 0xb51fd240, 0xe7c07ce3, 0xe566b4a1, 0xc3e9615e, 0x3cf8209d, 0x6094d1e3, 0xcd9ca341, + 0x5c76460e, 0x00ea983b, 0xd4d67881, 0xfd47572c, 0xf76cedd9, 0xbda8229c, 0x127dadaa, 0x438a074e, + 0x1f97c090, 0x081bdb8a, 0x93a07ebe, 0xb938ca15, 0x97b03cff, 0x3dc2c0f8, 0x8d1ab2ec, 0x64380e51, + 0x68cc7bfb, 0xd90f2788, 0x12490181, 0x5de5ffd4, 0xdd7ef86a, 0x76a2e214, 0xb9a40368, 0x925d958f, + 0x4b39fffa, 0xba39aee9, 0xa4ffd30b, 0xfaf7933b, 0x6d498623, 0x193cbcfa, 0x27627545, 0x825cf47a, + 0x61bd8ba0, 0xd11e42d1, 0xcead04f4, 0x127ea392, 0x10428db7, 0x8272a972, 0x9270c4a8, 0x127de50b, + 0x285ba1c8, 0x3c62f44f, 0x35c0eaa5, 0xe805d231, 0x428929fb, 0xb4fcdf82, 0x4fb66a53, 0x0e7dc15b, + 0x1f081fab, 0x108618ae, 0xfcfd086d, 0xf9ff2889, 0x694bcc11, 0x236a5cae, 0x12deca4d, 0x2c3f8cc5, + 0xd2d02dfe, 0xf8ef5896, 0xe4cf52da, 0x95155b67, 0x494a488c, 0xb9b6a80c, 0x5c8f82bc, 0x89d36b45, + 0x3a609437, 0xec00c9a9, 0x44715253, 0x0a874b49, 0xd773bc40, 0x7c34671c, 0x02717ef6, 0x4feb5536, + 0xa2d02fff, 0xd2bf60c4, 0xd43f03c0, 0x50b4ef6d, 0x07478cd1, 0x006e1888, 0xa2e53f55, 0xb9e6d4bc, + 0xa2048016, 0x97573833, 0xd7207d67, 0xde0f8f3d, 0x72f87b33, 0xabcc4f33, 0x7688c55d, 0x7b00a6b0, + 0x947b0001, 0x570075d2, 0xf9bb88f8, 0x8942019e, 0x4264a5ff, 0x856302e0, 0x72dbd92b, 0xee971b69, + 0x6ea22fde, 0x5f08ae2b, 0xaf7a616d, 0xe5c98767, 0xcf1febd2, 0x61efc8c2, 0xf1ac2571, 0xcc8239c2, + 0x67214cb8, 0xb1e583d1, 0xb7dc3e62, 0x7f10bdce, 0xf90a5c38, 0x0ff0443d, 0x606e6dc6, 0x60543a49, + 0x5727c148, 0x2be98a1d, 0x8ab41738, 0x20e1be24, 0xaf96da0f, 0x68458425, 0x99833be5, 0x600d457d, + 0x282f9350, 0x8334b362, 0xd91d1120, 0x2b6d8da0, 0x642b1e31, 0x9c305a00, 0x52bce688, 0x1b03588a, + 0xf7baefd5, 0x4142ed9c, 0xa4315c11, 0x83323ec5, 0xdfef4636, 0xa133c501, 0xe9d3531c, 0xee353783, + }, + { + 0x9db30420, 0x1fb6e9de, 0xa7be7bef, 0xd273a298, 0x4a4f7bdb, 0x64ad8c57, 0x85510443, 0xfa020ed1, + 0x7e287aff, 0xe60fb663, 0x095f35a1, 0x79ebf120, 0xfd059d43, 0x6497b7b1, 0xf3641f63, 0x241e4adf, + 0x28147f5f, 0x4fa2b8cd, 0xc9430040, 0x0cc32220, 0xfdd30b30, 0xc0a5374f, 0x1d2d00d9, 0x24147b15, + 0xee4d111a, 0x0fca5167, 0x71ff904c, 0x2d195ffe, 0x1a05645f, 0x0c13fefe, 0x081b08ca, 0x05170121, + 0x80530100, 0xe83e5efe, 0xac9af4f8, 0x7fe72701, 0xd2b8ee5f, 0x06df4261, 0xbb9e9b8a, 0x7293ea25, + 0xce84ffdf, 0xf5718801, 0x3dd64b04, 0xa26f263b, 0x7ed48400, 0x547eebe6, 0x446d4ca0, 0x6cf3d6f5, + 0x2649abdf, 0xaea0c7f5, 0x36338cc1, 0x503f7e93, 0xd3772061, 0x11b638e1, 0x72500e03, 0xf80eb2bb, + 0xabe0502e, 0xec8d77de, 0x57971e81, 0xe14f6746, 0xc9335400, 0x6920318f, 0x081dbb99, 0xffc304a5, + 0x4d351805, 0x7f3d5ce3, 0xa6c866c6, 0x5d5bcca9, 0xdaec6fea, 0x9f926f91, 0x9f46222f, 0x3991467d, + 0xa5bf6d8e, 0x1143c44f, 0x43958302, 0xd0214eeb, 0x022083b8, 0x3fb6180c, 0x18f8931e, 0x281658e6, + 0x26486e3e, 0x8bd78a70, 0x7477e4c1, 0xb506e07c, 0xf32d0a25, 0x79098b02, 0xe4eabb81, 0x28123b23, + 0x69dead38, 0x1574ca16, 0xdf871b62, 0x211c40b7, 0xa51a9ef9, 0x0014377b, 0x041e8ac8, 0x09114003, + 0xbd59e4d2, 0xe3d156d5, 0x4fe876d5, 0x2f91a340, 0x557be8de, 0x00eae4a7, 0x0ce5c2ec, 0x4db4bba6, + 0xe756bdff, 0xdd3369ac, 0xec17b035, 0x06572327, 0x99afc8b0, 0x56c8c391, 0x6b65811c, 0x5e146119, + 0x6e85cb75, 0xbe07c002, 0xc2325577, 0x893ff4ec, 0x5bbfc92d, 0xd0ec3b25, 0xb7801ab7, 0x8d6d3b24, + 0x20c763ef, 0xc366a5fc, 0x9c382880, 0x0ace3205, 0xaac9548a, 0xeca1d7c7, 0x041afa32, 0x1d16625a, + 0x6701902c, 0x9b757a54, 0x31d477f7, 0x9126b031, 0x36cc6fdb, 0xc70b8b46, 0xd9e66a48, 0x56e55a79, + 0x026a4ceb, 0x52437eff, 0x2f8f76b4, 0x0df980a5, 0x8674cde3, 0xedda04eb, 0x17a9be04, 0x2c18f4df, + 0xb7747f9d, 0xab2af7b4, 0xefc34d20, 0x2e096b7c, 0x1741a254, 0xe5b6a035, 0x213d42f6, 0x2c1c7c26, + 0x61c2f50f, 0x6552daf9, 0xd2c231f8, 0x25130f69, 0xd8167fa2, 0x0418f2c8, 0x001a96a6, 0x0d1526ab, + 0x63315c21, 0x5e0a72ec, 0x49bafefd, 0x187908d9, 0x8d0dbd86, 0x311170a7, 0x3e9b640c, 0xcc3e10d7, + 0xd5cad3b6, 0x0caec388, 0xf73001e1, 0x6c728aff, 0x71eae2a1, 0x1f9af36e, 0xcfcbd12f, 0xc1de8417, + 0xac07be6b, 0xcb44a1d8, 0x8b9b0f56, 0x013988c3, 0xb1c52fca, 0xb4be31cd, 0xd8782806, 0x12a3a4e2, + 0x6f7de532, 0x58fd7eb6, 0xd01ee900, 0x24adffc2, 0xf4990fc5, 0x9711aac5, 0x001d7b95, 0x82e5e7d2, + 0x109873f6, 0x00613096, 0xc32d9521, 0xada121ff, 0x29908415, 0x7fbb977f, 0xaf9eb3db, 0x29c9ed2a, + 0x5ce2a465, 0xa730f32c, 0xd0aa3fe8, 0x8a5cc091, 0xd49e2ce7, 0x0ce454a9, 0xd60acd86, 0x015f1919, + 0x77079103, 0xdea03af6, 0x78a8565e, 0xdee356df, 0x21f05cbe, 0x8b75e387, 0xb3c50651, 0xb8a5c3ef, + 0xd8eeb6d2, 0xe523be77, 0xc2154529, 0x2f69efdf, 0xafe67afb, 0xf470c4b2, 0xf3e0eb5b, 0xd6cc9876, + 0x39e4460c, 0x1fda8538, 0x1987832f, 0xca007367, 0xa99144f8, 0x296b299e, 0x492fc295, 0x9266beab, + 0xb5676e69, 0x9bd3ddda, 0xdf7e052f, 0xdb25701c, 0x1b5e51ee, 0xf65324e6, 0x6afce36c, 0x0316cc04, + 0x8644213e, 0xb7dc59d0, 0x7965291f, 0xccd6fd43, 0x41823979, 0x932bcdf6, 0xb657c34d, 0x4edfd282, + 0x7ae5290c, 0x3cb9536b, 0x851e20fe, 0x9833557e, 0x13ecf0b0, 0xd3ffb372, 0x3f85c5c1, 0x0aef7ed2, + }, + { + 0x7ec90c04, 0x2c6e74b9, 0x9b0e66df, 0xa6337911, 0xb86a7fff, 0x1dd358f5, 0x44dd9d44, 0x1731167f, + 0x08fbf1fa, 0xe7f511cc, 0xd2051b00, 0x735aba00, 0x2ab722d8, 0x386381cb, 0xacf6243a, 0x69befd7a, + 0xe6a2e77f, 0xf0c720cd, 0xc4494816, 0xccf5c180, 0x38851640, 0x15b0a848, 0xe68b18cb, 0x4caadeff, + 0x5f480a01, 0x0412b2aa, 0x259814fc, 0x41d0efe2, 0x4e40b48d, 0x248eb6fb, 0x8dba1cfe, 0x41a99b02, + 0x1a550a04, 0xba8f65cb, 0x7251f4e7, 0x95a51725, 0xc106ecd7, 0x97a5980a, 0xc539b9aa, 0x4d79fe6a, + 0xf2f3f763, 0x68af8040, 0xed0c9e56, 0x11b4958b, 0xe1eb5a88, 0x8709e6b0, 0xd7e07156, 0x4e29fea7, + 0x6366e52d, 0x02d1c000, 0xc4ac8e05, 0x9377f571, 0x0c05372a, 0x578535f2, 0x2261be02, 0xd642a0c9, + 0xdf13a280, 0x74b55bd2, 0x682199c0, 0xd421e5ec, 0x53fb3ce8, 0xc8adedb3, 0x28a87fc9, 0x3d959981, + 0x5c1ff900, 0xfe38d399, 0x0c4eff0b, 0x062407ea, 0xaa2f4fb1, 0x4fb96976, 0x90c79505, 0xb0a8a774, + 0xef55a1ff, 0xe59ca2c2, 0xa6b62d27, 0xe66a4263, 0xdf65001f, 0x0ec50966, 0xdfdd55bc, 0x29de0655, + 0x911e739a, 0x17af8975, 0x32c7911c, 0x89f89468, 0x0d01e980, 0x524755f4, 0x03b63cc9, 0x0cc844b2, + 0xbcf3f0aa, 0x87ac36e9, 0xe53a7426, 0x01b3d82b, 0x1a9e7449, 0x64ee2d7e, 0xcddbb1da, 0x01c94910, + 0xb868bf80, 0x0d26f3fd, 0x9342ede7, 0x04a5c284, 0x636737b6, 0x50f5b616, 0xf24766e3, 0x8eca36c1, + 0x136e05db, 0xfef18391, 0xfb887a37, 0xd6e7f7d4, 0xc7fb7dc9, 0x3063fcdf, 0xb6f589de, 0xec2941da, + 0x26e46695, 0xb7566419, 0xf654efc5, 0xd08d58b7, 0x48925401, 0xc1bacb7f, 0xe5ff550f, 0xb6083049, + 0x5bb5d0e8, 0x87d72e5a, 0xab6a6ee1, 0x223a66ce, 0xc62bf3cd, 0x9e0885f9, 0x68cb3e47, 0x086c010f, + 0xa21de820, 0xd18b69de, 0xf3f65777, 0xfa02c3f6, 0x407edac3, 0xcbb3d550, 0x1793084d, 0xb0d70eba, + 0x0ab378d5, 0xd951fb0c, 0xded7da56, 0x4124bbe4, 0x94ca0b56, 0x0f5755d1, 0xe0e1e56e, 0x6184b5be, + 0x580a249f, 0x94f74bc0, 0xe327888e, 0x9f7b5561, 0xc3dc0280, 0x05687715, 0x646c6bd7, 0x44904db3, + 0x66b4f0a3, 0xc0f1648a, 0x697ed5af, 0x49e92ff6, 0x309e374f, 0x2cb6356a, 0x85808573, 0x4991f840, + 0x76f0ae02, 0x083be84d, 0x28421c9a, 0x44489406, 0x736e4cb8, 0xc1092910, 0x8bc95fc6, 0x7d869cf4, + 0x134f616f, 0x2e77118d, 0xb31b2be1, 0xaa90b472, 0x3ca5d717, 0x7d161bba, 0x9cad9010, 0xaf462ba2, + 0x9fe459d2, 0x45d34559, 0xd9f2da13, 0xdbc65487, 0xf3e4f94e, 0x176d486f, 0x097c13ea, 0x631da5c7, + 0x445f7382, 0x175683f4, 0xcdc66a97, 0x70be0288, 0xb3cdcf72, 0x6e5dd2f3, 0x20936079, 0x459b80a5, + 0xbe60e2db, 0xa9c23101, 0xeba5315c, 0x224e42f2, 0x1c5c1572, 0xf6721b2c, 0x1ad2fff3, 0x8c25404e, + 0x324ed72f, 0x4067b7fd, 0x0523138e, 0x5ca3bc78, 0xdc0fd66e, 0x75922283, 0x784d6b17, 0x58ebb16e, + 0x44094f85, 0x3f481d87, 0xfcfeae7b, 0x77b5ff76, 0x8c2302bf, 0xaaf47556, 0x5f46b02a, 0x2b092801, + 0x3d38f5f7, 0x0ca81f36, 0x52af4a8a, 0x66d5e7c0, 0xdf3b0874, 0x95055110, 0x1b5ad7a8, 0xf61ed5ad, + 0x6cf6e479, 0x20758184, 0xd0cefa65, 0x88f7be58, 0x4a046826, 0x0ff6f8f3, 0xa09c7f70, 0x5346aba0, + 0x5ce96c28, 0xe176eda3, 0x6bac307f, 0x376829d2, 0x85360fa9, 0x17e3fe2a, 0x24b79767, 0xf5a96b20, + 0xd6cd2595, 0x68ff1ebf, 0x7555442c, 0xf19f06be, 0xf9e0659a, 0xeeb9491d, 0x34010718, 0xbb30cab8, + 0xe822fe15, 0x88570983, 0x750e6249, 0xda627e55, 0x5e76ffa8, 0xb1534546, 0x6d47de08, 0xefe9e7d4, + }, + { + 0xf6fa8f9d, 0x2cac6ce1, 0x4ca34867, 0xe2337f7c, 0x95db08e7, 0x016843b4, 0xeced5cbc, 0x325553ac, + 0xbf9f0960, 0xdfa1e2ed, 0x83f0579d, 0x63ed86b9, 0x1ab6a6b8, 0xde5ebe39, 0xf38ff732, 0x8989b138, + 0x33f14961, 0xc01937bd, 0xf506c6da, 0xe4625e7e, 0xa308ea99, 0x4e23e33c, 0x79cbd7cc, 0x48a14367, + 0xa3149619, 0xfec94bd5, 0xa114174a, 0xeaa01866, 0xa084db2d, 0x09a8486f, 0xa888614a, 0x2900af98, + 0x01665991, 0xe1992863, 0xc8f30c60, 0x2e78ef3c, 0xd0d51932, 0xcf0fec14, 0xf7ca07d2, 0xd0a82072, + 0xfd41197e, 0x9305a6b0, 0xe86be3da, 0x74bed3cd, 0x372da53c, 0x4c7f4448, 0xdab5d440, 0x6dba0ec3, + 0x083919a7, 0x9fbaeed9, 0x49dbcfb0, 0x4e670c53, 0x5c3d9c01, 0x64bdb941, 0x2c0e636a, 0xba7dd9cd, + 0xea6f7388, 0xe70bc762, 0x35f29adb, 0x5c4cdd8d, 0xf0d48d8c, 0xb88153e2, 0x08a19866, 0x1ae2eac8, + 0x284caf89, 0xaa928223, 0x9334be53, 0x3b3a21bf, 0x16434be3, 0x9aea3906, 0xefe8c36e, 0xf890cdd9, + 0x80226dae, 0xc340a4a3, 0xdf7e9c09, 0xa694a807, 0x5b7c5ecc, 0x221db3a6, 0x9a69a02f, 0x68818a54, + 0xceb2296f, 0x53c0843a, 0xfe893655, 0x25bfe68a, 0xb4628abc, 0xcf222ebf, 0x25ac6f48, 0xa9a99387, + 0x53bddb65, 0xe76ffbe7, 0xe967fd78, 0x0ba93563, 0x8e342bc1, 0xe8a11be9, 0x4980740d, 0xc8087dfc, + 0x8de4bf99, 0xa11101a0, 0x7fd37975, 0xda5a26c0, 0xe81f994f, 0x9528cd89, 0xfd339fed, 0xb87834bf, + 0x5f04456d, 0x22258698, 0xc9c4c83b, 0x2dc156be, 0x4f628daa, 0x57f55ec5, 0xe2220abe, 0xd2916ebf, + 0x4ec75b95, 0x24f2c3c0, 0x42d15d99, 0xcd0d7fa0, 0x7b6e27ff, 0xa8dc8af0, 0x7345c106, 0xf41e232f, + 0x35162386, 0xe6ea8926, 0x3333b094, 0x157ec6f2, 0x372b74af, 0x692573e4, 0xe9a9d848, 0xf3160289, + 0x3a62ef1d, 0xa787e238, 0xf3a5f676, 0x74364853, 0x20951063, 0x4576698d, 0xb6fad407, 0x592af950, + 0x36f73523, 0x4cfb6e87, 0x7da4cec0, 0x6c152daa, 0xcb0396a8, 0xc50dfe5d, 0xfcd707ab, 0x0921c42f, + 0x89dff0bb, 0x5fe2be78, 0x448f4f33, 0x754613c9, 0x2b05d08d, 0x48b9d585, 0xdc049441, 0xc8098f9b, + 0x7dede786, 0xc39a3373, 0x42410005, 0x6a091751, 0x0ef3c8a6, 0x890072d6, 0x28207682, 0xa9a9f7be, + 0xbf32679d, 0xd45b5b75, 0xb353fd00, 0xcbb0e358, 0x830f220a, 0x1f8fb214, 0xd372cf08, 0xcc3c4a13, + 0x8cf63166, 0x061c87be, 0x88c98f88, 0x6062e397, 0x47cf8e7a, 0xb6c85283, 0x3cc2acfb, 0x3fc06976, + 0x4e8f0252, 0x64d8314d, 0xda3870e3, 0x1e665459, 0xc10908f0, 0x513021a5, 0x6c5b68b7, 0x822f8aa0, + 0x3007cd3e, 0x74719eef, 0xdc872681, 0x073340d4, 0x7e432fd9, 0x0c5ec241, 0x8809286c, 0xf592d891, + 0x08a930f6, 0x957ef305, 0xb7fbffbd, 0xc266e96f, 0x6fe4ac98, 0xb173ecc0, 0xbc60b42a, 0x953498da, + 0xfba1ae12, 0x2d4bd736, 0x0f25faab, 0xa4f3fceb, 0xe2969123, 0x257f0c3d, 0x9348af49, 0x361400bc, + 0xe8816f4a, 0x3814f200, 0xa3f94043, 0x9c7a54c2, 0xbc704f57, 0xda41e7f9, 0xc25ad33a, 0x54f4a084, + 0xb17f5505, 0x59357cbe, 0xedbd15c8, 0x7f97c5ab, 0xba5ac7b5, 0xb6f6deaf, 0x3a479c3a, 0x5302da25, + 0x653d7e6a, 0x54268d49, 0x51a477ea, 0x5017d55b, 0xd7d25d88, 0x44136c76, 0x0404a8c8, 0xb8e5a121, + 0xb81a928a, 0x60ed5869, 0x97c55b96, 0xeaec991b, 0x29935913, 0x01fdb7f1, 0x088e8dfa, 0x9ab6f6f5, + 0x3b4cbf9f, 0x4a5de3ab, 0xe6051d35, 0xa0e1d855, 0xd36b4cf1, 0xf544edeb, 0xb0e93524, 0xbebb8fbd, + 0xa2d762cf, 0x49c92f54, 0x38b5f331, 0x7128a454, 0x48392905, 0xa65b1db8, 0x851c97bd, 0xd675cf2f, + }, + { + 0x85e04019, 0x332bf567, 0x662dbfff, 0xcfc65693, 0x2a8d7f6f, 0xab9bc912, 0xde6008a1, 0x2028da1f, + 0x0227bce7, 0x4d642916, 0x18fac300, 0x50f18b82, 0x2cb2cb11, 0xb232e75c, 0x4b3695f2, 0xb28707de, + 0xa05fbcf6, 0xcd4181e9, 0xe150210c, 0xe24ef1bd, 0xb168c381, 0xfde4e789, 0x5c79b0d8, 0x1e8bfd43, + 0x4d495001, 0x38be4341, 0x913cee1d, 0x92a79c3f, 0x089766be, 0xbaeeadf4, 0x1286becf, 0xb6eacb19, + 0x2660c200, 0x7565bde4, 0x64241f7a, 0x8248dca9, 0xc3b3ad66, 0x28136086, 0x0bd8dfa8, 0x356d1cf2, + 0x107789be, 0xb3b2e9ce, 0x0502aa8f, 0x0bc0351e, 0x166bf52a, 0xeb12ff82, 0xe3486911, 0xd34d7516, + 0x4e7b3aff, 0x5f43671b, 0x9cf6e037, 0x4981ac83, 0x334266ce, 0x8c9341b7, 0xd0d854c0, 0xcb3a6c88, + 0x47bc2829, 0x4725ba37, 0xa66ad22b, 0x7ad61f1e, 0x0c5cbafa, 0x4437f107, 0xb6e79962, 0x42d2d816, + 0x0a961288, 0xe1a5c06e, 0x13749e67, 0x72fc081a, 0xb1d139f7, 0xf9583745, 0xcf19df58, 0xbec3f756, + 0xc06eba30, 0x07211b24, 0x45c28829, 0xc95e317f, 0xbc8ec511, 0x38bc46e9, 0xc6e6fa14, 0xbae8584a, + 0xad4ebc46, 0x468f508b, 0x7829435f, 0xf124183b, 0x821dba9f, 0xaff60ff4, 0xea2c4e6d, 0x16e39264, + 0x92544a8b, 0x009b4fc3, 0xaba68ced, 0x9ac96f78, 0x06a5b79a, 0xb2856e6e, 0x1aec3ca9, 0xbe838688, + 0x0e0804e9, 0x55f1be56, 0xe7e5363b, 0xb3a1f25d, 0xf7debb85, 0x61fe033c, 0x16746233, 0x3c034c28, + 0xda6d0c74, 0x79aac56c, 0x3ce4e1ad, 0x51f0c802, 0x98f8f35a, 0x1626a49f, 0xeed82b29, 0x1d382fe3, + 0x0c4fb99a, 0xbb325778, 0x3ec6d97b, 0x6e77a6a9, 0xcb658b5c, 0xd45230c7, 0x2bd1408b, 0x60c03eb7, + 0xb9068d78, 0xa33754f4, 0xf430c87d, 0xc8a71302, 0xb96d8c32, 0xebd4e7be, 0xbe8b9d2d, 0x7979fb06, + 0xe7225308, 0x8b75cf77, 0x11ef8da4, 0xe083c858, 0x8d6b786f, 0x5a6317a6, 0xfa5cf7a0, 0x5dda0033, + 0xf28ebfb0, 0xf5b9c310, 0xa0eac280, 0x08b9767a, 0xa3d9d2b0, 0x79d34217, 0x021a718d, 0x9ac6336a, + 0x2711fd60, 0x438050e3, 0x069908a8, 0x3d7fedc4, 0x826d2bef, 0x4eeb8476, 0x488dcf25, 0x36c9d566, + 0x28e74e41, 0xc2610aca, 0x3d49a9cf, 0xbae3b9df, 0xb65f8de6, 0x92aeaf64, 0x3ac7d5e6, 0x9ea80509, + 0xf22b017d, 0xa4173f70, 0xdd1e16c3, 0x15e0d7f9, 0x50b1b887, 0x2b9f4fd5, 0x625aba82, 0x6a017962, + 0x2ec01b9c, 0x15488aa9, 0xd716e740, 0x40055a2c, 0x93d29a22, 0xe32dbf9a, 0x058745b9, 0x3453dc1e, + 0xd699296e, 0x496cff6f, 0x1c9f4986, 0xdfe2ed07, 0xb87242d1, 0x19de7eae, 0x053e561a, 0x15ad6f8c, + 0x66626c1c, 0x7154c24c, 0xea082b2a, 0x93eb2939, 0x17dcb0f0, 0x58d4f2ae, 0x9ea294fb, 0x52cf564c, + 0x9883fe66, 0x2ec40581, 0x763953c3, 0x01d6692e, 0xd3a0c108, 0xa1e7160e, 0xe4f2dfa6, 0x693ed285, + 0x74904698, 0x4c2b0edd, 0x4f757656, 0x5d393378, 0xa132234f, 0x3d321c5d, 0xc3f5e194, 0x4b269301, + 0xc79f022f, 0x3c997e7e, 0x5e4f9504, 0x3ffafbbd, 0x76f7ad0e, 0x296693f4, 0x3d1fce6f, 0xc61e45be, + 0xd3b5ab34, 0xf72bf9b7, 0x1b0434c0, 0x4e72b567, 0x5592a33d, 0xb5229301, 0xcfd2a87f, 0x60aeb767, + 0x1814386b, 0x30bcc33d, 0x38a0c07d, 0xfd1606f2, 0xc363519b, 0x589dd390, 0x5479f8e6, 0x1cb8d647, + 0x97fd61a9, 0xea7759f4, 0x2d57539d, 0x569a58cf, 0xe84e63ad, 0x462e1b78, 0x6580f87e, 0xf3817914, + 0x91da55f4, 0x40a230f3, 0xd1988f35, 0xb6e318d2, 0x3ffa50bc, 0x3d40f021, 0xc3c0bdae, 0x4958c24c, + 0x518f36b2, 0x84b1d370, 0x0fedce83, 0x878ddada, 0xf2a279c7, 0x94e01be8, 0x90716f4b, 0x954b8aa3, + }, + { + 0xe216300d, 0xbbddfffc, 0xa7ebdabd, 0x35648095, 0x7789f8b7, 0xe6c1121b, 0x0e241600, 0x052ce8b5, + 0x11a9cfb0, 0xe5952f11, 0xece7990a, 0x9386d174, 0x2a42931c, 0x76e38111, 0xb12def3a, 0x37ddddfc, + 0xde9adeb1, 0x0a0cc32c, 0xbe197029, 0x84a00940, 0xbb243a0f, 0xb4d137cf, 0xb44e79f0, 0x049eedfd, + 0x0b15a15d, 0x480d3168, 0x8bbbde5a, 0x669ded42, 0xc7ece831, 0x3f8f95e7, 0x72df191b, 0x7580330d, + 0x94074251, 0x5c7dcdfa, 0xabbe6d63, 0xaa402164, 0xb301d40a, 0x02e7d1ca, 0x53571dae, 0x7a3182a2, + 0x12a8ddec, 0xfdaa335d, 0x176f43e8, 0x71fb46d4, 0x38129022, 0xce949ad4, 0xb84769ad, 0x965bd862, + 0x82f3d055, 0x66fb9767, 0x15b80b4e, 0x1d5b47a0, 0x4cfde06f, 0xc28ec4b8, 0x57e8726e, 0x647a78fc, + 0x99865d44, 0x608bd593, 0x6c200e03, 0x39dc5ff6, 0x5d0b00a3, 0xae63aff2, 0x7e8bd632, 0x70108c0c, + 0xbbd35049, 0x2998df04, 0x980cf42a, 0x9b6df491, 0x9e7edd53, 0x06918548, 0x58cb7e07, 0x3b74ef2e, + 0x522fffb1, 0xd24708cc, 0x1c7e27cd, 0xa4eb215b, 0x3cf1d2e2, 0x19b47a38, 0x424f7618, 0x35856039, + 0x9d17dee7, 0x27eb35e6, 0xc9aff67b, 0x36baf5b8, 0x09c467cd, 0xc18910b1, 0xe11dbf7b, 0x06cd1af8, + 0x7170c608, 0x2d5e3354, 0xd4de495a, 0x64c6d006, 0xbcc0c62c, 0x3dd00db3, 0x708f8f34, 0x77d51b42, + 0x264f620f, 0x24b8d2bf, 0x15c1b79e, 0x46a52564, 0xf8d7e54e, 0x3e378160, 0x7895cda5, 0x859c15a5, + 0xe6459788, 0xc37bc75f, 0xdb07ba0c, 0x0676a3ab, 0x7f229b1e, 0x31842e7b, 0x24259fd7, 0xf8bef472, + 0x835ffcb8, 0x6df4c1f2, 0x96f5b195, 0xfd0af0fc, 0xb0fe134c, 0xe2506d3d, 0x4f9b12ea, 0xf215f225, + 0xa223736f, 0x9fb4c428, 0x25d04979, 0x34c713f8, 0xc4618187, 0xea7a6e98, 0x7cd16efc, 0x1436876c, + 0xf1544107, 0xbedeee14, 0x56e9af27, 0xa04aa441, 0x3cf7c899, 0x92ecbae6, 0xdd67016d, 0x151682eb, + 0xa842eedf, 0xfdba60b4, 0xf1907b75, 0x20e3030f, 0x24d8c29e, 0xe139673b, 0xefa63fb8, 0x71873054, + 0xb6f2cf3b, 0x9f326442, 0xcb15a4cc, 0xb01a4504, 0xf1e47d8d, 0x844a1be5, 0xbae7dfdc, 0x42cbda70, + 0xcd7dae0a, 0x57e85b7a, 0xd53f5af6, 0x20cf4d8c, 0xcea4d428, 0x79d130a4, 0x3486ebfb, 0x33d3cddc, + 0x77853b53, 0x37effcb5, 0xc5068778, 0xe580b3e6, 0x4e68b8f4, 0xc5c8b37e, 0x0d809ea2, 0x398feb7c, + 0x132a4f94, 0x43b7950e, 0x2fee7d1c, 0x223613bd, 0xdd06caa2, 0x37df932b, 0xc4248289, 0xacf3ebc3, + 0x5715f6b7, 0xef3478dd, 0xf267616f, 0xc148cbe4, 0x9052815e, 0x5e410fab, 0xb48a2465, 0x2eda7fa4, + 0xe87b40e4, 0xe98ea084, 0x5889e9e1, 0xefd390fc, 0xdd07d35b, 0xdb485694, 0x38d7e5b2, 0x57720101, + 0x730edebc, 0x5b643113, 0x94917e4f, 0x503c2fba, 0x646f1282, 0x7523d24a, 0xe0779695, 0xf9c17a8f, + 0x7a5b2121, 0xd187b896, 0x29263a4d, 0xba510cdf, 0x81f47c9f, 0xad1163ed, 0xea7b5965, 0x1a00726e, + 0x11403092, 0x00da6d77, 0x4a0cdd61, 0xad1f4603, 0x605bdfb0, 0x9eedc364, 0x22ebe6a8, 0xcee7d28a, + 0xa0e736a0, 0x5564a6b9, 0x10853209, 0xc7eb8f37, 0x2de705ca, 0x8951570f, 0xdf09822b, 0xbd691a6c, + 0xaa12e4f2, 0x87451c0f, 0xe0f6a27a, 0x3ada4819, 0x4cf1764f, 0x0d771c2b, 0x67cdb156, 0x350d8384, + 0x5938fa0f, 0x42399ef3, 0x36997b07, 0x0e84093d, 0x4aa93e61, 0x8360d87b, 0x1fa98b0c, 0x1149382c, + 0xe97625a5, 0x0614d1b7, 0x0e25244b, 0x0c768347, 0x589e8d82, 0x0d2059d1, 0xa466bb1e, 0xf8da0a82, + 0x04f19130, 0xba6e4ec0, 0x99265164, 0x1ee7230d, 0x50b2ad80, 0xeaee6801, 0x8db2a283, 0xea8bf59e, + }, +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5_test.go b/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5_test.go new file mode 100644 index 0000000..778b272 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/cast5/cast5_test.go @@ -0,0 +1,106 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cast5 + +import ( + "bytes" + "encoding/hex" + "testing" +) + +// This test vector is taken from RFC 2144, App B.1. +// Since the other two test vectors are for reduced-round variants, we can't +// use them. +var basicTests = []struct { + key, plainText, cipherText string +}{ + { + "0123456712345678234567893456789a", + "0123456789abcdef", + "238b4fe5847e44b2", + }, +} + +func TestBasic(t *testing.T) { + for i, test := range basicTests { + key, _ := hex.DecodeString(test.key) + plainText, _ := hex.DecodeString(test.plainText) + expected, _ := hex.DecodeString(test.cipherText) + + c, err := NewCipher(key) + if err != nil { + t.Errorf("#%d: failed to create Cipher: %s", i, err) + continue + } + var cipherText [BlockSize]byte + c.Encrypt(cipherText[:], plainText) + if !bytes.Equal(cipherText[:], expected) { + t.Errorf("#%d: got:%x want:%x", i, cipherText, expected) + } + + var plainTextAgain [BlockSize]byte + c.Decrypt(plainTextAgain[:], cipherText[:]) + if !bytes.Equal(plainTextAgain[:], plainText) { + t.Errorf("#%d: got:%x want:%x", i, plainTextAgain, plainText) + } + } +} + +// TestFull performs the test specified in RFC 2144, App B.2. +// However, due to the length of time taken, it's disabled here and a more +// limited version is included, below. +func TestFull(t *testing.T) { + if testing.Short() { + // This is too slow for normal testing + return + } + + a, b := iterate(1000000) + + const expectedA = "eea9d0a249fd3ba6b3436fb89d6dca92" + const expectedB = "b2c95eb00c31ad7180ac05b8e83d696e" + + if hex.EncodeToString(a) != expectedA { + t.Errorf("a: got:%x want:%s", a, expectedA) + } + if hex.EncodeToString(b) != expectedB { + t.Errorf("b: got:%x want:%s", b, expectedB) + } +} + +func iterate(iterations int) ([]byte, []byte) { + const initValueHex = "0123456712345678234567893456789a" + + initValue, _ := hex.DecodeString(initValueHex) + + var a, b [16]byte + copy(a[:], initValue) + copy(b[:], initValue) + + for i := 0; i < iterations; i++ { + c, _ := NewCipher(b[:]) + c.Encrypt(a[:8], a[:8]) + c.Encrypt(a[8:], a[8:]) + c, _ = NewCipher(a[:]) + c.Encrypt(b[:8], b[:8]) + c.Encrypt(b[8:], b[8:]) + } + + return a[:], b[:] +} + +func TestLimited(t *testing.T) { + a, b := iterate(1000) + + const expectedA = "23f73b14b02a2ad7dfb9f2c35644798d" + const expectedB = "e5bf37eff14c456a40b21ce369370a9f" + + if hex.EncodeToString(a) != expectedA { + t.Errorf("a: got:%x want:%s", a, expectedA) + } + if hex.EncodeToString(b) != expectedB { + t.Errorf("b: got:%x want:%s", b, expectedB) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor.go new file mode 100644 index 0000000..e8df91a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor.go @@ -0,0 +1,219 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package armor implements OpenPGP ASCII Armor, see RFC 4880. OpenPGP Armor is +// very similar to PEM except that it has an additional CRC checksum. +package armor + +import ( + "bufio" + "bytes" + "encoding/base64" + "golang.org/x/crypto/openpgp/errors" + "io" +) + +// A Block represents an OpenPGP armored structure. +// +// The encoded form is: +// -----BEGIN Type----- +// Headers +// +// base64-encoded Bytes +// '=' base64 encoded checksum +// -----END Type----- +// where Headers is a possibly empty sequence of Key: Value lines. +// +// Since the armored data can be very large, this package presents a streaming +// interface. +type Block struct { + Type string // The type, taken from the preamble (i.e. "PGP SIGNATURE"). + Header map[string]string // Optional headers. + Body io.Reader // A Reader from which the contents can be read + lReader lineReader + oReader openpgpReader +} + +var ArmorCorrupt error = errors.StructuralError("armor invalid") + +const crc24Init = 0xb704ce +const crc24Poly = 0x1864cfb +const crc24Mask = 0xffffff + +// crc24 calculates the OpenPGP checksum as specified in RFC 4880, section 6.1 +func crc24(crc uint32, d []byte) uint32 { + for _, b := range d { + crc ^= uint32(b) << 16 + for i := 0; i < 8; i++ { + crc <<= 1 + if crc&0x1000000 != 0 { + crc ^= crc24Poly + } + } + } + return crc +} + +var armorStart = []byte("-----BEGIN ") +var armorEnd = []byte("-----END ") +var armorEndOfLine = []byte("-----") + +// lineReader wraps a line based reader. It watches for the end of an armor +// block and records the expected CRC value. +type lineReader struct { + in *bufio.Reader + buf []byte + eof bool + crc uint32 +} + +func (l *lineReader) Read(p []byte) (n int, err error) { + if l.eof { + return 0, io.EOF + } + + if len(l.buf) > 0 { + n = copy(p, l.buf) + l.buf = l.buf[n:] + return + } + + line, isPrefix, err := l.in.ReadLine() + if err != nil { + return + } + if isPrefix { + return 0, ArmorCorrupt + } + + if len(line) == 5 && line[0] == '=' { + // This is the checksum line + var expectedBytes [3]byte + var m int + m, err = base64.StdEncoding.Decode(expectedBytes[0:], line[1:]) + if m != 3 || err != nil { + return + } + l.crc = uint32(expectedBytes[0])<<16 | + uint32(expectedBytes[1])<<8 | + uint32(expectedBytes[2]) + + line, _, err = l.in.ReadLine() + if err != nil && err != io.EOF { + return + } + if !bytes.HasPrefix(line, armorEnd) { + return 0, ArmorCorrupt + } + + l.eof = true + return 0, io.EOF + } + + if len(line) > 96 { + return 0, ArmorCorrupt + } + + n = copy(p, line) + bytesToSave := len(line) - n + if bytesToSave > 0 { + if cap(l.buf) < bytesToSave { + l.buf = make([]byte, 0, bytesToSave) + } + l.buf = l.buf[0:bytesToSave] + copy(l.buf, line[n:]) + } + + return +} + +// openpgpReader passes Read calls to the underlying base64 decoder, but keeps +// a running CRC of the resulting data and checks the CRC against the value +// found by the lineReader at EOF. +type openpgpReader struct { + lReader *lineReader + b64Reader io.Reader + currentCRC uint32 +} + +func (r *openpgpReader) Read(p []byte) (n int, err error) { + n, err = r.b64Reader.Read(p) + r.currentCRC = crc24(r.currentCRC, p[:n]) + + if err == io.EOF { + if r.lReader.crc != uint32(r.currentCRC&crc24Mask) { + return 0, ArmorCorrupt + } + } + + return +} + +// Decode reads a PGP armored block from the given Reader. It will ignore +// leading garbage. If it doesn't find a block, it will return nil, io.EOF. The +// given Reader is not usable after calling this function: an arbitrary amount +// of data may have been read past the end of the block. +func Decode(in io.Reader) (p *Block, err error) { + r := bufio.NewReaderSize(in, 100) + var line []byte + ignoreNext := false + +TryNextBlock: + p = nil + + // Skip leading garbage + for { + ignoreThis := ignoreNext + line, ignoreNext, err = r.ReadLine() + if err != nil { + return + } + if ignoreNext || ignoreThis { + continue + } + line = bytes.TrimSpace(line) + if len(line) > len(armorStart)+len(armorEndOfLine) && bytes.HasPrefix(line, armorStart) { + break + } + } + + p = new(Block) + p.Type = string(line[len(armorStart) : len(line)-len(armorEndOfLine)]) + p.Header = make(map[string]string) + nextIsContinuation := false + var lastKey string + + // Read headers + for { + isContinuation := nextIsContinuation + line, nextIsContinuation, err = r.ReadLine() + if err != nil { + p = nil + return + } + if isContinuation { + p.Header[lastKey] += string(line) + continue + } + line = bytes.TrimSpace(line) + if len(line) == 0 { + break + } + + i := bytes.Index(line, []byte(": ")) + if i == -1 { + goto TryNextBlock + } + lastKey = string(line[:i]) + p.Header[lastKey] = string(line[i+2:]) + } + + p.lReader.in = r + p.oReader.currentCRC = crc24Init + p.oReader.lReader = &p.lReader + p.oReader.b64Reader = base64.NewDecoder(base64.StdEncoding, &p.lReader) + p.Body = &p.oReader + + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor_test.go new file mode 100644 index 0000000..9334e94 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/armor_test.go @@ -0,0 +1,95 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package armor + +import ( + "bytes" + "hash/adler32" + "io/ioutil" + "testing" +) + +func TestDecodeEncode(t *testing.T) { + buf := bytes.NewBuffer([]byte(armorExample1)) + result, err := Decode(buf) + if err != nil { + t.Error(err) + } + expectedType := "PGP SIGNATURE" + if result.Type != expectedType { + t.Errorf("result.Type: got:%s want:%s", result.Type, expectedType) + } + if len(result.Header) != 1 { + t.Errorf("len(result.Header): got:%d want:1", len(result.Header)) + } + v, ok := result.Header["Version"] + if !ok || v != "GnuPG v1.4.10 (GNU/Linux)" { + t.Errorf("result.Header: got:%#v", result.Header) + } + + contents, err := ioutil.ReadAll(result.Body) + if err != nil { + t.Error(err) + } + + if adler32.Checksum(contents) != 0x27b144be { + t.Errorf("contents: got: %x", contents) + } + + buf = bytes.NewBuffer(nil) + w, err := Encode(buf, result.Type, result.Header) + if err != nil { + t.Error(err) + } + _, err = w.Write(contents) + if err != nil { + t.Error(err) + } + w.Close() + + if !bytes.Equal(buf.Bytes(), []byte(armorExample1)) { + t.Errorf("got: %s\nwant: %s", string(buf.Bytes()), armorExample1) + } +} + +func TestLongHeader(t *testing.T) { + buf := bytes.NewBuffer([]byte(armorLongLine)) + result, err := Decode(buf) + if err != nil { + t.Error(err) + return + } + value, ok := result.Header["Version"] + if !ok { + t.Errorf("missing Version header") + } + if value != longValueExpected { + t.Errorf("got: %s want: %s", value, longValueExpected) + } +} + +const armorExample1 = `-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iJwEAAECAAYFAk1Fv/0ACgkQo01+GMIMMbsYTwQAiAw+QAaNfY6WBdplZ/uMAccm +4g+81QPmTSGHnetSb6WBiY13kVzK4HQiZH8JSkmmroMLuGeJwsRTEL4wbjRyUKEt +p1xwUZDECs234F1xiG5enc5SGlRtP7foLBz9lOsjx+LEcA4sTl5/2eZR9zyFZqWW +TxRjs+fJCIFuo71xb1g= +=/teI +-----END PGP SIGNATURE-----` + +const armorLongLine = `-----BEGIN PGP SIGNATURE----- +Version: 0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz + +iQEcBAABAgAGBQJMtFESAAoJEKsQXJGvOPsVj40H/1WW6jaMXv4BW+1ueDSMDwM8 +kx1fLOXbVM5/Kn5LStZNt1jWWnpxdz7eq3uiqeCQjmqUoRde3YbB2EMnnwRbAhpp +cacnAvy9ZQ78OTxUdNW1mhX5bS6q1MTEJnl+DcyigD70HG/yNNQD7sOPMdYQw0TA +byQBwmLwmTsuZsrYqB68QyLHI+DUugn+kX6Hd2WDB62DKa2suoIUIHQQCd/ofwB3 +WfCYInXQKKOSxu2YOg2Eb4kLNhSMc1i9uKUWAH+sdgJh7NBgdoE4MaNtBFkHXRvv +okWuf3+xA9ksp1npSY/mDvgHijmjvtpRDe6iUeqfCn8N9u9CBg8geANgaG8+QA4= +=wfQG +-----END PGP SIGNATURE-----` + +const longValueExpected = "0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz" diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/encode.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/encode.go new file mode 100644 index 0000000..6f07582 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/armor/encode.go @@ -0,0 +1,160 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package armor + +import ( + "encoding/base64" + "io" +) + +var armorHeaderSep = []byte(": ") +var blockEnd = []byte("\n=") +var newline = []byte("\n") +var armorEndOfLineOut = []byte("-----\n") + +// writeSlices writes its arguments to the given Writer. +func writeSlices(out io.Writer, slices ...[]byte) (err error) { + for _, s := range slices { + _, err = out.Write(s) + if err != nil { + return err + } + } + return +} + +// lineBreaker breaks data across several lines, all of the same byte length +// (except possibly the last). Lines are broken with a single '\n'. +type lineBreaker struct { + lineLength int + line []byte + used int + out io.Writer + haveWritten bool +} + +func newLineBreaker(out io.Writer, lineLength int) *lineBreaker { + return &lineBreaker{ + lineLength: lineLength, + line: make([]byte, lineLength), + used: 0, + out: out, + } +} + +func (l *lineBreaker) Write(b []byte) (n int, err error) { + n = len(b) + + if n == 0 { + return + } + + if l.used == 0 && l.haveWritten { + _, err = l.out.Write([]byte{'\n'}) + if err != nil { + return + } + } + + if l.used+len(b) < l.lineLength { + l.used += copy(l.line[l.used:], b) + return + } + + l.haveWritten = true + _, err = l.out.Write(l.line[0:l.used]) + if err != nil { + return + } + excess := l.lineLength - l.used + l.used = 0 + + _, err = l.out.Write(b[0:excess]) + if err != nil { + return + } + + _, err = l.Write(b[excess:]) + return +} + +func (l *lineBreaker) Close() (err error) { + if l.used > 0 { + _, err = l.out.Write(l.line[0:l.used]) + if err != nil { + return + } + } + + return +} + +// encoding keeps track of a running CRC24 over the data which has been written +// to it and outputs a OpenPGP checksum when closed, followed by an armor +// trailer. +// +// It's built into a stack of io.Writers: +// encoding -> base64 encoder -> lineBreaker -> out +type encoding struct { + out io.Writer + breaker *lineBreaker + b64 io.WriteCloser + crc uint32 + blockType []byte +} + +func (e *encoding) Write(data []byte) (n int, err error) { + e.crc = crc24(e.crc, data) + return e.b64.Write(data) +} + +func (e *encoding) Close() (err error) { + err = e.b64.Close() + if err != nil { + return + } + e.breaker.Close() + + var checksumBytes [3]byte + checksumBytes[0] = byte(e.crc >> 16) + checksumBytes[1] = byte(e.crc >> 8) + checksumBytes[2] = byte(e.crc) + + var b64ChecksumBytes [4]byte + base64.StdEncoding.Encode(b64ChecksumBytes[:], checksumBytes[:]) + + return writeSlices(e.out, blockEnd, b64ChecksumBytes[:], newline, armorEnd, e.blockType, armorEndOfLine) +} + +// Encode returns a WriteCloser which will encode the data written to it in +// OpenPGP armor. +func Encode(out io.Writer, blockType string, headers map[string]string) (w io.WriteCloser, err error) { + bType := []byte(blockType) + err = writeSlices(out, armorStart, bType, armorEndOfLineOut) + if err != nil { + return + } + + for k, v := range headers { + err = writeSlices(out, []byte(k), armorHeaderSep, []byte(v), newline) + if err != nil { + return + } + } + + _, err = out.Write(newline) + if err != nil { + return + } + + e := &encoding{ + out: out, + breaker: newLineBreaker(out, 64), + crc: crc24Init, + blockType: bType, + } + e.b64 = base64.NewEncoder(base64.StdEncoding, e.breaker) + return e, nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text.go new file mode 100644 index 0000000..e601e38 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text.go @@ -0,0 +1,59 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import "hash" + +// NewCanonicalTextHash reformats text written to it into the canonical +// form and then applies the hash h. See RFC 4880, section 5.2.1. +func NewCanonicalTextHash(h hash.Hash) hash.Hash { + return &canonicalTextHash{h, 0} +} + +type canonicalTextHash struct { + h hash.Hash + s int +} + +var newline = []byte{'\r', '\n'} + +func (cth *canonicalTextHash) Write(buf []byte) (int, error) { + start := 0 + + for i, c := range buf { + switch cth.s { + case 0: + if c == '\r' { + cth.s = 1 + } else if c == '\n' { + cth.h.Write(buf[start:i]) + cth.h.Write(newline) + start = i + 1 + } + case 1: + cth.s = 0 + } + } + + cth.h.Write(buf[start:]) + return len(buf), nil +} + +func (cth *canonicalTextHash) Sum(in []byte) []byte { + return cth.h.Sum(in) +} + +func (cth *canonicalTextHash) Reset() { + cth.h.Reset() + cth.s = 0 +} + +func (cth *canonicalTextHash) Size() int { + return cth.h.Size() +} + +func (cth *canonicalTextHash) BlockSize() int { + return cth.h.BlockSize() +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text_test.go new file mode 100644 index 0000000..8f3ba2a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/canonical_text_test.go @@ -0,0 +1,52 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import ( + "bytes" + "testing" +) + +type recordingHash struct { + buf *bytes.Buffer +} + +func (r recordingHash) Write(b []byte) (n int, err error) { + return r.buf.Write(b) +} + +func (r recordingHash) Sum(in []byte) []byte { + return append(in, r.buf.Bytes()...) +} + +func (r recordingHash) Reset() { + panic("shouldn't be called") +} + +func (r recordingHash) Size() int { + panic("shouldn't be called") +} + +func (r recordingHash) BlockSize() int { + panic("shouldn't be called") +} + +func testCanonicalText(t *testing.T, input, expected string) { + r := recordingHash{bytes.NewBuffer(nil)} + c := NewCanonicalTextHash(r) + c.Write([]byte(input)) + result := c.Sum(nil) + if expected != string(result) { + t.Errorf("input: %x got: %x want: %x", input, result, expected) + } +} + +func TestCanonicalText(t *testing.T) { + testCanonicalText(t, "foo\n", "foo\r\n") + testCanonicalText(t, "foo", "foo") + testCanonicalText(t, "foo\r\n", "foo\r\n") + testCanonicalText(t, "foo\r\nbar", "foo\r\nbar") + testCanonicalText(t, "foo\r\nbar\n\n", "foo\r\nbar\r\n\r\n") +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go new file mode 100644 index 0000000..41a4e43 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign.go @@ -0,0 +1,366 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package clearsign generates and processes OpenPGP, clear-signed data. See +// RFC 4880, section 7. +// +// Clearsigned messages are cryptographically signed, but the contents of the +// message are kept in plaintext so that it can be read without special tools. +package clearsign + +import ( + "bufio" + "bytes" + "crypto" + "hash" + "io" + "net/textproto" + "strconv" + + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" +) + +// A Block represents a clearsigned message. A signature on a Block can +// be checked by passing Bytes into openpgp.CheckDetachedSignature. +type Block struct { + Headers textproto.MIMEHeader // Optional message headers + Plaintext []byte // The original message text + Bytes []byte // The signed message + ArmoredSignature *armor.Block // The signature block +} + +// start is the marker which denotes the beginning of a clearsigned message. +var start = []byte("\n-----BEGIN PGP SIGNED MESSAGE-----") + +// dashEscape is prefixed to any lines that begin with a hypen so that they +// can't be confused with endText. +var dashEscape = []byte("- ") + +// endText is a marker which denotes the end of the message and the start of +// an armored signature. +var endText = []byte("-----BEGIN PGP SIGNATURE-----") + +// end is a marker which denotes the end of the armored signature. +var end = []byte("\n-----END PGP SIGNATURE-----") + +var crlf = []byte("\r\n") +var lf = byte('\n') + +// getLine returns the first \r\n or \n delineated line from the given byte +// array. The line does not include the \r\n or \n. The remainder of the byte +// array (also not including the new line bytes) is also returned and this will +// always be smaller than the original argument. +func getLine(data []byte) (line, rest []byte) { + i := bytes.Index(data, []byte{'\n'}) + var j int + if i < 0 { + i = len(data) + j = i + } else { + j = i + 1 + if i > 0 && data[i-1] == '\r' { + i-- + } + } + return data[0:i], data[j:] +} + +// Decode finds the first clearsigned message in data and returns it, as well +// as the suffix of data which remains after the message. +func Decode(data []byte) (b *Block, rest []byte) { + // start begins with a newline. However, at the very beginning of + // the byte array, we'll accept the start string without it. + rest = data + if bytes.HasPrefix(data, start[1:]) { + rest = rest[len(start)-1:] + } else if i := bytes.Index(data, start); i >= 0 { + rest = rest[i+len(start):] + } else { + return nil, data + } + + // Consume the start line. + _, rest = getLine(rest) + + var line []byte + b = &Block{ + Headers: make(textproto.MIMEHeader), + } + + // Next come a series of header lines. + for { + // This loop terminates because getLine's second result is + // always smaller than its argument. + if len(rest) == 0 { + return nil, data + } + // An empty line marks the end of the headers. + if line, rest = getLine(rest); len(line) == 0 { + break + } + + i := bytes.Index(line, []byte{':'}) + if i == -1 { + return nil, data + } + + key, val := line[0:i], line[i+1:] + key = bytes.TrimSpace(key) + val = bytes.TrimSpace(val) + b.Headers.Add(string(key), string(val)) + } + + firstLine := true + for { + start := rest + + line, rest = getLine(rest) + if bytes.Equal(line, endText) { + // Back up to the start of the line because armor expects to see the + // header line. + rest = start + break + } + + // The final CRLF isn't included in the hash so we don't write it until + // we've seen the next line. + if firstLine { + firstLine = false + } else { + b.Bytes = append(b.Bytes, crlf...) + } + + if bytes.HasPrefix(line, dashEscape) { + line = line[2:] + } + line = bytes.TrimRight(line, " \t") + b.Bytes = append(b.Bytes, line...) + + b.Plaintext = append(b.Plaintext, line...) + b.Plaintext = append(b.Plaintext, lf) + } + + // We want to find the extent of the armored data (including any newlines at + // the end). + i := bytes.Index(rest, end) + if i == -1 { + return nil, data + } + i += len(end) + for i < len(rest) && (rest[i] == '\r' || rest[i] == '\n') { + i++ + } + armored := rest[:i] + rest = rest[i:] + + var err error + b.ArmoredSignature, err = armor.Decode(bytes.NewBuffer(armored)) + if err != nil { + return nil, data + } + + return b, rest +} + +// A dashEscaper is an io.WriteCloser which processes the body of a clear-signed +// message. The clear-signed message is written to buffered and a hash, suitable +// for signing, is maintained in h. +// +// When closed, an armored signature is created and written to complete the +// message. +type dashEscaper struct { + buffered *bufio.Writer + h hash.Hash + hashType crypto.Hash + + atBeginningOfLine bool + isFirstLine bool + + whitespace []byte + byteBuf []byte // a one byte buffer to save allocations + + privateKey *packet.PrivateKey + config *packet.Config +} + +func (d *dashEscaper) Write(data []byte) (n int, err error) { + for _, b := range data { + d.byteBuf[0] = b + + if d.atBeginningOfLine { + // The final CRLF isn't included in the hash so we have to wait + // until this point (the start of the next line) before writing it. + if !d.isFirstLine { + d.h.Write(crlf) + } + d.isFirstLine = false + + // At the beginning of a line, hyphens have to be escaped. + if b == '-' { + // The signature isn't calculated over the dash-escaped text so + // the escape is only written to buffered. + if _, err = d.buffered.Write(dashEscape); err != nil { + return + } + d.h.Write(d.byteBuf) + d.atBeginningOfLine = false + } else if b == '\n' { + // Nothing to do because we dely writing CRLF to the hash. + } else { + d.h.Write(d.byteBuf) + d.atBeginningOfLine = false + } + if err = d.buffered.WriteByte(b); err != nil { + return + } + } else { + // Any whitespace at the end of the line has to be removed so we + // buffer it until we find out whether there's more on this line. + if b == ' ' || b == '\t' || b == '\r' { + d.whitespace = append(d.whitespace, b) + } else if b == '\n' { + // We got a raw \n. Drop any trailing whitespace and write a + // CRLF. + d.whitespace = d.whitespace[:0] + // We dely writing CRLF to the hash until the start of the + // next line. + if err = d.buffered.WriteByte(b); err != nil { + return + } + d.atBeginningOfLine = true + } else { + // Any buffered whitespace wasn't at the end of the line so + // we need to write it out. + if len(d.whitespace) > 0 { + d.h.Write(d.whitespace) + if _, err = d.buffered.Write(d.whitespace); err != nil { + return + } + d.whitespace = d.whitespace[:0] + } + d.h.Write(d.byteBuf) + if err = d.buffered.WriteByte(b); err != nil { + return + } + } + } + } + + n = len(data) + return +} + +func (d *dashEscaper) Close() (err error) { + if !d.atBeginningOfLine { + if err = d.buffered.WriteByte(lf); err != nil { + return + } + } + sig := new(packet.Signature) + sig.SigType = packet.SigTypeText + sig.PubKeyAlgo = d.privateKey.PubKeyAlgo + sig.Hash = d.hashType + sig.CreationTime = d.config.Now() + sig.IssuerKeyId = &d.privateKey.KeyId + + if err = sig.Sign(d.h, d.privateKey, d.config); err != nil { + return + } + + out, err := armor.Encode(d.buffered, "PGP SIGNATURE", nil) + if err != nil { + return + } + + if err = sig.Serialize(out); err != nil { + return + } + if err = out.Close(); err != nil { + return + } + if err = d.buffered.Flush(); err != nil { + return + } + return +} + +// Encode returns a WriteCloser which will clear-sign a message with privateKey +// and write it to w. If config is nil, sensible defaults are used. +func Encode(w io.Writer, privateKey *packet.PrivateKey, config *packet.Config) (plaintext io.WriteCloser, err error) { + if privateKey.Encrypted { + return nil, errors.InvalidArgumentError("signing key is encrypted") + } + + hashType := config.Hash() + name := nameOfHash(hashType) + if len(name) == 0 { + return nil, errors.UnsupportedError("unknown hash type: " + strconv.Itoa(int(hashType))) + } + + if !hashType.Available() { + return nil, errors.UnsupportedError("unsupported hash type: " + strconv.Itoa(int(hashType))) + } + h := hashType.New() + + buffered := bufio.NewWriter(w) + // start has a \n at the beginning that we don't want here. + if _, err = buffered.Write(start[1:]); err != nil { + return + } + if err = buffered.WriteByte(lf); err != nil { + return + } + if _, err = buffered.WriteString("Hash: "); err != nil { + return + } + if _, err = buffered.WriteString(name); err != nil { + return + } + if err = buffered.WriteByte(lf); err != nil { + return + } + if err = buffered.WriteByte(lf); err != nil { + return + } + + plaintext = &dashEscaper{ + buffered: buffered, + h: h, + hashType: hashType, + + atBeginningOfLine: true, + isFirstLine: true, + + byteBuf: make([]byte, 1), + + privateKey: privateKey, + config: config, + } + + return +} + +// nameOfHash returns the OpenPGP name for the given hash, or the empty string +// if the name isn't known. See RFC 4880, section 9.4. +func nameOfHash(h crypto.Hash) string { + switch h { + case crypto.MD5: + return "MD5" + case crypto.SHA1: + return "SHA1" + case crypto.RIPEMD160: + return "RIPEMD160" + case crypto.SHA224: + return "SHA224" + case crypto.SHA256: + return "SHA256" + case crypto.SHA384: + return "SHA384" + case crypto.SHA512: + return "SHA512" + } + return "" +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go new file mode 100644 index 0000000..51538b9 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/clearsign/clearsign_test.go @@ -0,0 +1,187 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package clearsign + +import ( + "bytes" + "golang.org/x/crypto/openpgp" + "testing" +) + +func testParse(t *testing.T, input []byte, expected, expectedPlaintext string) { + b, rest := Decode(input) + if b == nil { + t.Fatal("failed to decode clearsign message") + } + if !bytes.Equal(rest, []byte("trailing")) { + t.Errorf("unexpected remaining bytes returned: %s", string(rest)) + } + if b.ArmoredSignature.Type != "PGP SIGNATURE" { + t.Errorf("bad armor type, got:%s, want:PGP SIGNATURE", b.ArmoredSignature.Type) + } + if !bytes.Equal(b.Bytes, []byte(expected)) { + t.Errorf("bad body, got:%x want:%x", b.Bytes, expected) + } + + if !bytes.Equal(b.Plaintext, []byte(expectedPlaintext)) { + t.Errorf("bad plaintext, got:%x want:%x", b.Plaintext, expectedPlaintext) + } + + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey)) + if err != nil { + t.Errorf("failed to parse public key: %s", err) + } + + if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body); err != nil { + t.Errorf("failed to check signature: %s", err) + } +} + +func TestParse(t *testing.T) { + testParse(t, clearsignInput, "Hello world\r\nline 2", "Hello world\nline 2\n") + testParse(t, clearsignInput2, "\r\n\r\n(This message has a couple of blank lines at the start and end.)\r\n\r\n", "\n\n(This message has a couple of blank lines at the start and end.)\n\n\n") +} + +func TestParseWithNoNewlineAtEnd(t *testing.T) { + input := clearsignInput + input = input[:len(input)-len("trailing")-1] + b, rest := Decode(input) + if b == nil { + t.Fatal("failed to decode clearsign message") + } + if len(rest) > 0 { + t.Errorf("unexpected remaining bytes returned: %s", string(rest)) + } +} + +var signingTests = []struct { + in, signed, plaintext string +}{ + {"", "", ""}, + {"a", "a", "a\n"}, + {"a\n", "a", "a\n"}, + {"-a\n", "-a", "-a\n"}, + {"--a\nb", "--a\r\nb", "--a\nb\n"}, +} + +func TestSigning(t *testing.T) { + keyring, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(signingKey)) + if err != nil { + t.Errorf("failed to parse public key: %s", err) + } + + for i, test := range signingTests { + var buf bytes.Buffer + + plaintext, err := Encode(&buf, keyring[0].PrivateKey, nil) + if err != nil { + t.Errorf("#%d: error from Encode: %s", i, err) + continue + } + if _, err := plaintext.Write([]byte(test.in)); err != nil { + t.Errorf("#%d: error from Write: %s", i, err) + continue + } + if err := plaintext.Close(); err != nil { + t.Fatalf("#%d: error from Close: %s", i, err) + continue + } + + b, _ := Decode(buf.Bytes()) + if b == nil { + t.Errorf("#%d: failed to decode clearsign message", i) + continue + } + if !bytes.Equal(b.Bytes, []byte(test.signed)) { + t.Errorf("#%d: bad result, got:%x, want:%x", i, b.Bytes, test.signed) + continue + } + if !bytes.Equal(b.Plaintext, []byte(test.plaintext)) { + t.Errorf("#%d: bad result, got:%x, want:%x", i, b.Plaintext, test.plaintext) + continue + } + + if _, err := openpgp.CheckDetachedSignature(keyring, bytes.NewBuffer(b.Bytes), b.ArmoredSignature.Body); err != nil { + t.Errorf("#%d: failed to check signature: %s", i, err) + } + } +} + +var clearsignInput = []byte(` +;lasjlkfdsa + +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA1 + +Hello world +line 2 +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iJwEAQECAAYFAk8kMuEACgkQO9o98PRieSpMsAQAhmY/vwmNpflrPgmfWsYhk5O8 +pjnBUzZwqTDoDeINjZEoPDSpQAHGhjFjgaDx/Gj4fAl0dM4D0wuUEBb6QOrwflog +2A2k9kfSOMOtk0IH/H5VuFN1Mie9L/erYXjTQIptv9t9J7NoRBMU0QOOaFU0JaO9 +MyTpno24AjIAGb+mH1U= +=hIJ6 +-----END PGP SIGNATURE----- +trailing`) + +var clearsignInput2 = []byte(` +asdlfkjasdlkfjsadf + +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + + + +(This message has a couple of blank lines at the start and end.) + + +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.11 (GNU/Linux) + +iJwEAQEIAAYFAlPpSREACgkQO9o98PRieSpZTAP+M8QUoCt/7Rf3YbXPcdzIL32v +pt1I+cMNeopzfLy0u4ioEFi8s5VkwpL1AFmirvgViCwlf82inoRxzZRiW05JQ5LI +ESEzeCoy2LIdRCQ2hcrG8pIUPzUO4TqO5D/dMbdHwNH4h5nNmGJUAEG6FpURlPm+ +qZg6BaTvOxepqOxnhVU= +=e+C6 +-----END PGP SIGNATURE----- + +trailing`) + +var signingKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp +idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn +vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB +AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X +0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL +IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk +VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn +gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9 +TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx +q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz +dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA +CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1 +ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+ +eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid +AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV +bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK +/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA +A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX +TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc +lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6 +rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN +oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8 +QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU +nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC +AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp +BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad +AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL +VrM0m72/jnpKo04= +=zNCn +-----END PGP PRIVATE KEY BLOCK----- +` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go new file mode 100644 index 0000000..a553bde --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal.go @@ -0,0 +1,122 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package elgamal implements ElGamal encryption, suitable for OpenPGP, +// as specified in "A Public-Key Cryptosystem and a Signature Scheme Based on +// Discrete Logarithms," IEEE Transactions on Information Theory, v. IT-31, +// n. 4, 1985, pp. 469-472. +// +// This form of ElGamal embeds PKCS#1 v1.5 padding, which may make it +// unsuitable for other protocols. RSA should be used in preference in any +// case. +package elgamal + +import ( + "crypto/rand" + "crypto/subtle" + "errors" + "io" + "math/big" +) + +// PublicKey represents an ElGamal public key. +type PublicKey struct { + G, P, Y *big.Int +} + +// PrivateKey represents an ElGamal private key. +type PrivateKey struct { + PublicKey + X *big.Int +} + +// Encrypt encrypts the given message to the given public key. The result is a +// pair of integers. Errors can result from reading random, or because msg is +// too large to be encrypted to the public key. +func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err error) { + pLen := (pub.P.BitLen() + 7) / 8 + if len(msg) > pLen-11 { + err = errors.New("elgamal: message too long") + return + } + + // EM = 0x02 || PS || 0x00 || M + em := make([]byte, pLen-1) + em[0] = 2 + ps, mm := em[1:len(em)-len(msg)-1], em[len(em)-len(msg):] + err = nonZeroRandomBytes(ps, random) + if err != nil { + return + } + em[len(em)-len(msg)-1] = 0 + copy(mm, msg) + + m := new(big.Int).SetBytes(em) + + k, err := rand.Int(random, pub.P) + if err != nil { + return + } + + c1 = new(big.Int).Exp(pub.G, k, pub.P) + s := new(big.Int).Exp(pub.Y, k, pub.P) + c2 = s.Mul(s, m) + c2.Mod(c2, pub.P) + + return +} + +// Decrypt takes two integers, resulting from an ElGamal encryption, and +// returns the plaintext of the message. An error can result only if the +// ciphertext is invalid. Users should keep in mind that this is a padding +// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can +// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks +// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel +// Bleichenbacher, Advances in Cryptology (Crypto '98), +func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { + s := new(big.Int).Exp(c1, priv.X, priv.P) + s.ModInverse(s, priv.P) + s.Mul(s, c2) + s.Mod(s, priv.P) + em := s.Bytes() + + firstByteIsTwo := subtle.ConstantTimeByteEq(em[0], 2) + + // The remainder of the plaintext must be a string of non-zero random + // octets, followed by a 0, followed by the message. + // lookingForIndex: 1 iff we are still looking for the zero. + // index: the offset of the first zero byte. + var lookingForIndex, index int + lookingForIndex = 1 + + for i := 1; i < len(em); i++ { + equals0 := subtle.ConstantTimeByteEq(em[i], 0) + index = subtle.ConstantTimeSelect(lookingForIndex&equals0, i, index) + lookingForIndex = subtle.ConstantTimeSelect(equals0, 0, lookingForIndex) + } + + if firstByteIsTwo != 1 || lookingForIndex != 0 || index < 9 { + return nil, errors.New("elgamal: decryption error") + } + return em[index+1:], nil +} + +// nonZeroRandomBytes fills the given slice with non-zero random octets. +func nonZeroRandomBytes(s []byte, rand io.Reader) (err error) { + _, err = io.ReadFull(rand, s) + if err != nil { + return + } + + for i := 0; i < len(s); i++ { + for s[i] == 0 { + _, err = io.ReadFull(rand, s[i:i+1]) + if err != nil { + return + } + } + } + + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go new file mode 100644 index 0000000..c4f99f5 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/elgamal/elgamal_test.go @@ -0,0 +1,49 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package elgamal + +import ( + "bytes" + "crypto/rand" + "math/big" + "testing" +) + +// This is the 1024-bit MODP group from RFC 5114, section 2.1: +const primeHex = "B10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B616073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BFACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371" + +const generatorHex = "A4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D31266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5" + +func fromHex(hex string) *big.Int { + n, ok := new(big.Int).SetString(hex, 16) + if !ok { + panic("failed to parse hex number") + } + return n +} + +func TestEncryptDecrypt(t *testing.T) { + priv := &PrivateKey{ + PublicKey: PublicKey{ + G: fromHex(generatorHex), + P: fromHex(primeHex), + }, + X: fromHex("42"), + } + priv.Y = new(big.Int).Exp(priv.G, priv.X, priv.P) + + message := []byte("hello world") + c1, c2, err := Encrypt(rand.Reader, &priv.PublicKey, message) + if err != nil { + t.Errorf("error encrypting: %s", err) + } + message2, err := Decrypt(priv, c1, c2) + if err != nil { + t.Errorf("error decrypting: %s", err) + } + if !bytes.Equal(message2, message) { + t.Errorf("decryption failed, got: %x, want: %x", message2, message) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/errors/errors.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/errors/errors.go new file mode 100644 index 0000000..6c4c2f1 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/errors/errors.go @@ -0,0 +1,72 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package errors contains common error types for the OpenPGP packages. +package errors + +import ( + "strconv" +) + +// A StructuralError is returned when OpenPGP data is found to be syntactically +// invalid. +type StructuralError string + +func (s StructuralError) Error() string { + return "openpgp: invalid data: " + string(s) +} + +// UnsupportedError indicates that, although the OpenPGP data is valid, it +// makes use of currently unimplemented features. +type UnsupportedError string + +func (s UnsupportedError) Error() string { + return "openpgp: unsupported feature: " + string(s) +} + +// InvalidArgumentError indicates that the caller is in error and passed an +// incorrect value. +type InvalidArgumentError string + +func (i InvalidArgumentError) Error() string { + return "openpgp: invalid argument: " + string(i) +} + +// SignatureError indicates that a syntactically valid signature failed to +// validate. +type SignatureError string + +func (b SignatureError) Error() string { + return "openpgp: invalid signature: " + string(b) +} + +type keyIncorrectError int + +func (ki keyIncorrectError) Error() string { + return "openpgp: incorrect key" +} + +var ErrKeyIncorrect error = keyIncorrectError(0) + +type unknownIssuerError int + +func (unknownIssuerError) Error() string { + return "openpgp: signature made by unknown entity" +} + +var ErrUnknownIssuer error = unknownIssuerError(0) + +type keyRevokedError int + +func (keyRevokedError) Error() string { + return "openpgp: signature made by revoked key" +} + +var ErrKeyRevoked error = keyRevokedError(0) + +type UnknownPacketTypeError uint8 + +func (upte UnknownPacketTypeError) Error() string { + return "openpgp: unknown packet type: " + strconv.Itoa(int(upte)) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go new file mode 100644 index 0000000..c4a6640 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go @@ -0,0 +1,624 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import ( + "crypto/rsa" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" + "io" + "time" +) + +// PublicKeyType is the armor type for a PGP public key. +var PublicKeyType = "PGP PUBLIC KEY BLOCK" + +// PrivateKeyType is the armor type for a PGP private key. +var PrivateKeyType = "PGP PRIVATE KEY BLOCK" + +// An Entity represents the components of an OpenPGP key: a primary public key +// (which must be a signing key), one or more identities claimed by that key, +// and zero or more subkeys, which may be encryption keys. +type Entity struct { + PrimaryKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Identities map[string]*Identity // indexed by Identity.Name + Revocations []*packet.Signature + Subkeys []Subkey +} + +// An Identity represents an identity claimed by an Entity and zero or more +// assertions by other entities about that claim. +type Identity struct { + Name string // by convention, has the form "Full Name (comment) " + UserId *packet.UserId + SelfSignature *packet.Signature + Signatures []*packet.Signature +} + +// A Subkey is an additional public key in an Entity. Subkeys can be used for +// encryption. +type Subkey struct { + PublicKey *packet.PublicKey + PrivateKey *packet.PrivateKey + Sig *packet.Signature +} + +// A Key identifies a specific public key in an Entity. This is either the +// Entity's primary key or a subkey. +type Key struct { + Entity *Entity + PublicKey *packet.PublicKey + PrivateKey *packet.PrivateKey + SelfSignature *packet.Signature +} + +// A KeyRing provides access to public and private keys. +type KeyRing interface { + // KeysById returns the set of keys that have the given key id. + KeysById(id uint64) []Key + // KeysByIdAndUsage returns the set of keys with the given id + // that also meet the key usage given by requiredUsage. + // The requiredUsage is expressed as the bitwise-OR of + // packet.KeyFlag* values. + KeysByIdUsage(id uint64, requiredUsage byte) []Key + // DecryptionKeys returns all private keys that are valid for + // decryption. + DecryptionKeys() []Key +} + +// primaryIdentity returns the Identity marked as primary or the first identity +// if none are so marked. +func (e *Entity) primaryIdentity() *Identity { + var firstIdentity *Identity + for _, ident := range e.Identities { + if firstIdentity == nil { + firstIdentity = ident + } + if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId { + return ident + } + } + return firstIdentity +} + +// encryptionKey returns the best candidate Key for encrypting a message to the +// given Entity. +func (e *Entity) encryptionKey(now time.Time) (Key, bool) { + candidateSubkey := -1 + + for i, subkey := range e.Subkeys { + if subkey.Sig.FlagsValid && + subkey.Sig.FlagEncryptCommunications && + subkey.PublicKey.PubKeyAlgo.CanEncrypt() && + !subkey.Sig.KeyExpired(now) { + candidateSubkey = i + break + } + } + + if candidateSubkey != -1 { + subkey := e.Subkeys[candidateSubkey] + return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true + } + + // If we don't have any candidate subkeys for encryption and + // the primary key doesn't have any usage metadata then we + // assume that the primary key is ok. Or, if the primary key is + // marked as ok to encrypt to, then we can obviously use it. + i := e.primaryIdentity() + if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications && + e.PrimaryKey.PubKeyAlgo.CanEncrypt() && + !i.SelfSignature.KeyExpired(now) { + return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true + } + + // This Entity appears to be signing only. + return Key{}, false +} + +// signingKey return the best candidate Key for signing a message with this +// Entity. +func (e *Entity) signingKey(now time.Time) (Key, bool) { + candidateSubkey := -1 + + for i, subkey := range e.Subkeys { + if subkey.Sig.FlagsValid && + subkey.Sig.FlagSign && + subkey.PublicKey.PubKeyAlgo.CanSign() && + !subkey.Sig.KeyExpired(now) { + candidateSubkey = i + break + } + } + + if candidateSubkey != -1 { + subkey := e.Subkeys[candidateSubkey] + return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig}, true + } + + // If we have no candidate subkey then we assume that it's ok to sign + // with the primary key. + i := e.primaryIdentity() + if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagSign && + !i.SelfSignature.KeyExpired(now) { + return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature}, true + } + + return Key{}, false +} + +// An EntityList contains one or more Entities. +type EntityList []*Entity + +// KeysById returns the set of keys that have the given key id. +func (el EntityList) KeysById(id uint64) (keys []Key) { + for _, e := range el { + if e.PrimaryKey.KeyId == id { + var selfSig *packet.Signature + for _, ident := range e.Identities { + if selfSig == nil { + selfSig = ident.SelfSignature + } else if ident.SelfSignature.IsPrimaryId != nil && *ident.SelfSignature.IsPrimaryId { + selfSig = ident.SelfSignature + break + } + } + keys = append(keys, Key{e, e.PrimaryKey, e.PrivateKey, selfSig}) + } + + for _, subKey := range e.Subkeys { + if subKey.PublicKey.KeyId == id { + keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig}) + } + } + } + return +} + +// KeysByIdAndUsage returns the set of keys with the given id that also meet +// the key usage given by requiredUsage. The requiredUsage is expressed as +// the bitwise-OR of packet.KeyFlag* values. +func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) { + for _, key := range el.KeysById(id) { + if len(key.Entity.Revocations) > 0 { + continue + } + + if key.SelfSignature.RevocationReason != nil { + continue + } + + if key.SelfSignature.FlagsValid && requiredUsage != 0 { + var usage byte + if key.SelfSignature.FlagCertify { + usage |= packet.KeyFlagCertify + } + if key.SelfSignature.FlagSign { + usage |= packet.KeyFlagSign + } + if key.SelfSignature.FlagEncryptCommunications { + usage |= packet.KeyFlagEncryptCommunications + } + if key.SelfSignature.FlagEncryptStorage { + usage |= packet.KeyFlagEncryptStorage + } + if usage&requiredUsage != requiredUsage { + continue + } + } + + keys = append(keys, key) + } + return +} + +// DecryptionKeys returns all private keys that are valid for decryption. +func (el EntityList) DecryptionKeys() (keys []Key) { + for _, e := range el { + for _, subKey := range e.Subkeys { + if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) { + keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig}) + } + } + } + return +} + +// ReadArmoredKeyRing reads one or more public/private keys from an armor keyring file. +func ReadArmoredKeyRing(r io.Reader) (EntityList, error) { + block, err := armor.Decode(r) + if err == io.EOF { + return nil, errors.InvalidArgumentError("no armored data found") + } + if err != nil { + return nil, err + } + if block.Type != PublicKeyType && block.Type != PrivateKeyType { + return nil, errors.InvalidArgumentError("expected public or private key block, got: " + block.Type) + } + + return ReadKeyRing(block.Body) +} + +// ReadKeyRing reads one or more public/private keys. Unsupported keys are +// ignored as long as at least a single valid key is found. +func ReadKeyRing(r io.Reader) (el EntityList, err error) { + packets := packet.NewReader(r) + var lastUnsupportedError error + + for { + var e *Entity + e, err = ReadEntity(packets) + if err != nil { + // TODO: warn about skipped unsupported/unreadable keys + if _, ok := err.(errors.UnsupportedError); ok { + lastUnsupportedError = err + err = readToNextPublicKey(packets) + } else if _, ok := err.(errors.StructuralError); ok { + // Skip unreadable, badly-formatted keys + lastUnsupportedError = err + err = readToNextPublicKey(packets) + } + if err == io.EOF { + err = nil + break + } + if err != nil { + el = nil + break + } + } else { + el = append(el, e) + } + } + + if len(el) == 0 && err == nil { + err = lastUnsupportedError + } + return +} + +// readToNextPublicKey reads packets until the start of the entity and leaves +// the first packet of the new entity in the Reader. +func readToNextPublicKey(packets *packet.Reader) (err error) { + var p packet.Packet + for { + p, err = packets.Next() + if err == io.EOF { + return + } else if err != nil { + if _, ok := err.(errors.UnsupportedError); ok { + err = nil + continue + } + return + } + + if pk, ok := p.(*packet.PublicKey); ok && !pk.IsSubkey { + packets.Unread(p) + return + } + } + + panic("unreachable") +} + +// ReadEntity reads an entity (public key, identities, subkeys etc) from the +// given Reader. +func ReadEntity(packets *packet.Reader) (*Entity, error) { + e := new(Entity) + e.Identities = make(map[string]*Identity) + + p, err := packets.Next() + if err != nil { + return nil, err + } + + var ok bool + if e.PrimaryKey, ok = p.(*packet.PublicKey); !ok { + if e.PrivateKey, ok = p.(*packet.PrivateKey); !ok { + packets.Unread(p) + return nil, errors.StructuralError("first packet was not a public/private key") + } else { + e.PrimaryKey = &e.PrivateKey.PublicKey + } + } + + if !e.PrimaryKey.PubKeyAlgo.CanSign() { + return nil, errors.StructuralError("primary key cannot be used for signatures") + } + + var current *Identity + var revocations []*packet.Signature +EachPacket: + for { + p, err := packets.Next() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + switch pkt := p.(type) { + case *packet.UserId: + current = new(Identity) + current.Name = pkt.Id + current.UserId = pkt + e.Identities[pkt.Id] = current + + for { + p, err = packets.Next() + if err == io.EOF { + return nil, io.ErrUnexpectedEOF + } else if err != nil { + return nil, err + } + + sig, ok := p.(*packet.Signature) + if !ok { + return nil, errors.StructuralError("user ID packet not followed by self-signature") + } + + if (sig.SigType == packet.SigTypePositiveCert || sig.SigType == packet.SigTypeGenericCert) && sig.IssuerKeyId != nil && *sig.IssuerKeyId == e.PrimaryKey.KeyId { + if err = e.PrimaryKey.VerifyUserIdSignature(pkt.Id, e.PrimaryKey, sig); err != nil { + return nil, errors.StructuralError("user ID self-signature invalid: " + err.Error()) + } + current.SelfSignature = sig + break + } + current.Signatures = append(current.Signatures, sig) + } + case *packet.Signature: + if pkt.SigType == packet.SigTypeKeyRevocation { + revocations = append(revocations, pkt) + } else if pkt.SigType == packet.SigTypeDirectSignature { + // TODO: RFC4880 5.2.1 permits signatures + // directly on keys (eg. to bind additional + // revocation keys). + } else if current == nil { + return nil, errors.StructuralError("signature packet found before user id packet") + } else { + current.Signatures = append(current.Signatures, pkt) + } + case *packet.PrivateKey: + if pkt.IsSubkey == false { + packets.Unread(p) + break EachPacket + } + err = addSubkey(e, packets, &pkt.PublicKey, pkt) + if err != nil { + return nil, err + } + case *packet.PublicKey: + if pkt.IsSubkey == false { + packets.Unread(p) + break EachPacket + } + err = addSubkey(e, packets, pkt, nil) + if err != nil { + return nil, err + } + default: + // we ignore unknown packets + } + } + + if len(e.Identities) == 0 { + return nil, errors.StructuralError("entity without any identities") + } + + for _, revocation := range revocations { + err = e.PrimaryKey.VerifyRevocationSignature(revocation) + if err == nil { + e.Revocations = append(e.Revocations, revocation) + } else { + // TODO: RFC 4880 5.2.3.15 defines revocation keys. + return nil, errors.StructuralError("revocation signature signed by alternate key") + } + } + + return e, nil +} + +func addSubkey(e *Entity, packets *packet.Reader, pub *packet.PublicKey, priv *packet.PrivateKey) error { + var subKey Subkey + subKey.PublicKey = pub + subKey.PrivateKey = priv + p, err := packets.Next() + if err == io.EOF { + return io.ErrUnexpectedEOF + } + if err != nil { + return errors.StructuralError("subkey signature invalid: " + err.Error()) + } + var ok bool + subKey.Sig, ok = p.(*packet.Signature) + if !ok { + return errors.StructuralError("subkey packet not followed by signature") + } + if subKey.Sig.SigType != packet.SigTypeSubkeyBinding && subKey.Sig.SigType != packet.SigTypeSubkeyRevocation { + return errors.StructuralError("subkey signature with wrong type") + } + err = e.PrimaryKey.VerifyKeySignature(subKey.PublicKey, subKey.Sig) + if err != nil { + return errors.StructuralError("subkey signature invalid: " + err.Error()) + } + e.Subkeys = append(e.Subkeys, subKey) + return nil +} + +const defaultRSAKeyBits = 2048 + +// NewEntity returns an Entity that contains a fresh RSA/RSA keypair with a +// single identity composed of the given full name, comment and email, any of +// which may be empty but must not contain any of "()<>\x00". +// If config is nil, sensible defaults will be used. +func NewEntity(name, comment, email string, config *packet.Config) (*Entity, error) { + currentTime := config.Now() + + uid := packet.NewUserId(name, comment, email) + if uid == nil { + return nil, errors.InvalidArgumentError("user id field contained invalid characters") + } + signingPriv, err := rsa.GenerateKey(config.Random(), defaultRSAKeyBits) + if err != nil { + return nil, err + } + encryptingPriv, err := rsa.GenerateKey(config.Random(), defaultRSAKeyBits) + if err != nil { + return nil, err + } + + e := &Entity{ + PrimaryKey: packet.NewRSAPublicKey(currentTime, &signingPriv.PublicKey), + PrivateKey: packet.NewRSAPrivateKey(currentTime, signingPriv), + Identities: make(map[string]*Identity), + } + isPrimaryId := true + e.Identities[uid.Id] = &Identity{ + Name: uid.Name, + UserId: uid, + SelfSignature: &packet.Signature{ + CreationTime: currentTime, + SigType: packet.SigTypePositiveCert, + PubKeyAlgo: packet.PubKeyAlgoRSA, + Hash: config.Hash(), + IsPrimaryId: &isPrimaryId, + FlagsValid: true, + FlagSign: true, + FlagCertify: true, + IssuerKeyId: &e.PrimaryKey.KeyId, + }, + } + + e.Subkeys = make([]Subkey, 1) + e.Subkeys[0] = Subkey{ + PublicKey: packet.NewRSAPublicKey(currentTime, &encryptingPriv.PublicKey), + PrivateKey: packet.NewRSAPrivateKey(currentTime, encryptingPriv), + Sig: &packet.Signature{ + CreationTime: currentTime, + SigType: packet.SigTypeSubkeyBinding, + PubKeyAlgo: packet.PubKeyAlgoRSA, + Hash: config.Hash(), + FlagsValid: true, + FlagEncryptStorage: true, + FlagEncryptCommunications: true, + IssuerKeyId: &e.PrimaryKey.KeyId, + }, + } + e.Subkeys[0].PublicKey.IsSubkey = true + e.Subkeys[0].PrivateKey.IsSubkey = true + + return e, nil +} + +// SerializePrivate serializes an Entity, including private key material, to +// the given Writer. For now, it must only be used on an Entity returned from +// NewEntity. +// If config is nil, sensible defaults will be used. +func (e *Entity) SerializePrivate(w io.Writer, config *packet.Config) (err error) { + err = e.PrivateKey.Serialize(w) + if err != nil { + return + } + for _, ident := range e.Identities { + err = ident.UserId.Serialize(w) + if err != nil { + return + } + err = ident.SelfSignature.SignUserId(ident.UserId.Id, e.PrimaryKey, e.PrivateKey, config) + if err != nil { + return + } + err = ident.SelfSignature.Serialize(w) + if err != nil { + return + } + } + for _, subkey := range e.Subkeys { + err = subkey.PrivateKey.Serialize(w) + if err != nil { + return + } + err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config) + if err != nil { + return + } + err = subkey.Sig.Serialize(w) + if err != nil { + return + } + } + return nil +} + +// Serialize writes the public part of the given Entity to w. (No private +// key material will be output). +func (e *Entity) Serialize(w io.Writer) error { + err := e.PrimaryKey.Serialize(w) + if err != nil { + return err + } + for _, ident := range e.Identities { + err = ident.UserId.Serialize(w) + if err != nil { + return err + } + err = ident.SelfSignature.Serialize(w) + if err != nil { + return err + } + for _, sig := range ident.Signatures { + err = sig.Serialize(w) + if err != nil { + return err + } + } + } + for _, subkey := range e.Subkeys { + err = subkey.PublicKey.Serialize(w) + if err != nil { + return err + } + err = subkey.Sig.Serialize(w) + if err != nil { + return err + } + } + return nil +} + +// SignIdentity adds a signature to e, from signer, attesting that identity is +// associated with e. The provided identity must already be an element of +// e.Identities and the private key of signer must have been decrypted if +// necessary. +// If config is nil, sensible defaults will be used. +func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Config) error { + if signer.PrivateKey == nil { + return errors.InvalidArgumentError("signing Entity must have a private key") + } + if signer.PrivateKey.Encrypted { + return errors.InvalidArgumentError("signing Entity's private key must be decrypted") + } + ident, ok := e.Identities[identity] + if !ok { + return errors.InvalidArgumentError("given identity string not found in Entity") + } + + sig := &packet.Signature{ + SigType: packet.SigTypeGenericCert, + PubKeyAlgo: signer.PrivateKey.PubKeyAlgo, + Hash: config.Hash(), + CreationTime: config.Now(), + IssuerKeyId: &signer.PrivateKey.KeyId, + } + if err := sig.SignUserId(identity, e.PrimaryKey, signer.PrivateKey, config); err != nil { + return err + } + ident.Signatures = append(ident.Signatures, sig) + return nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go new file mode 100644 index 0000000..e0625cb --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go @@ -0,0 +1,216 @@ +package openpgp + +import ( + "testing" + "time" + + "golang.org/x/crypto/openpgp/packet" +) + +func TestKeyExpiry(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(expiringKeyHex)) + entity := kring[0] + + const timeFormat = "2006-01-02" + time1, _ := time.Parse(timeFormat, "2013-07-01") + // The expiringKeyHex key is structured as: + // + // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC + // sub 1024R/1ABB25A0 created: 2013-07-01 expires: 2013-07-08 usage: E + // sub 1024R/96A672F5 created: 2013-07-01 expires: 2013-07-31 usage: E + // + // So this should select the first, non-expired encryption key. + key, _ := entity.encryptionKey(time1) + if id := key.PublicKey.KeyIdShortString(); id != "1ABB25A0" { + t.Errorf("Expected key 1ABB25A0 at time %s, but got key %s", time1.Format(timeFormat), id) + } + + // Once the first encryption subkey has expired, the second should be + // selected. + time2, _ := time.Parse(timeFormat, "2013-07-09") + key, _ = entity.encryptionKey(time2) + if id := key.PublicKey.KeyIdShortString(); id != "96A672F5" { + t.Errorf("Expected key 96A672F5 at time %s, but got key %s", time2.Format(timeFormat), id) + } + + // Once all the keys have expired, nothing should be returned. + time3, _ := time.Parse(timeFormat, "2013-08-01") + if key, ok := entity.encryptionKey(time3); ok { + t.Errorf("Expected no key at time %s, but got key %s", time3.Format(timeFormat), key.PublicKey.KeyIdShortString()) + } +} + +// TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission. +func TestExternallyRevocableKey(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex)) + + // The 0xA42704B92866382A key can be revoked by 0xBE3893CB843D0FE70C + // according to this signature that appears within the key: + // :signature packet: algo 1, keyid A42704B92866382A + // version 4, created 1396409682, md5len 0, sigclass 0x1f + // digest algo 2, begin of digest a9 84 + // hashed subpkt 2 len 4 (sig created 2014-04-02) + // hashed subpkt 12 len 22 (revocation key: c=80 a=1 f=CE094AA433F7040BB2DDF0BE3893CB843D0FE70C) + // hashed subpkt 7 len 1 (not revocable) + // subpkt 16 len 8 (issuer key ID A42704B92866382A) + // data: [1024 bits] + + id := uint64(0xA42704B92866382A) + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected to find key id %X, but got %d matches", id, len(keys)) + } +} + +func TestKeyRevocation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(revokedKeyHex)) + + // revokedKeyHex contains these keys: + // pub 1024R/9A34F7C0 2014-03-25 [revoked: 2014-03-25] + // sub 1024R/1BA3CD60 2014-03-25 [revoked: 2014-03-25] + ids := []uint64{0xA401D9F09A34F7C0, 0x5CD3BE0A1BA3CD60} + + for _, id := range ids { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find revoked key %X, but got %d matches", id, len(keys)) + } + keys = kring.KeysByIdUsage(id, 0) + if len(keys) != 0 { + t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", id, len(keys)) + } + } +} + +func TestSubkeyRevocation(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(revokedSubkeyHex)) + + // revokedSubkeyHex contains these keys: + // pub 1024R/4EF7E4BECCDE97F0 2014-03-25 + // sub 1024R/D63636E2B96AE423 2014-03-25 + // sub 1024D/DBCE4EE19529437F 2014-03-25 + // sub 1024R/677815E371C2FD23 2014-03-25 [revoked: 2014-03-25] + validKeys := []uint64{0x4EF7E4BECCDE97F0, 0xD63636E2B96AE423, 0xDBCE4EE19529437F} + revokedKey := uint64(0x677815E371C2FD23) + + for _, id := range validKeys { + keys := kring.KeysById(id) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", id, len(keys)) + } + keys = kring.KeysByIdUsage(id, 0) + if len(keys) != 1 { + t.Errorf("Expected KeysByIdUsage to find key %X, but got %d matches", id, len(keys)) + } + } + + keys := kring.KeysById(revokedKey) + if len(keys) != 1 { + t.Errorf("Expected KeysById to find key %X, but got %d matches", revokedKey, len(keys)) + } + + keys = kring.KeysByIdUsage(revokedKey, 0) + if len(keys) != 0 { + t.Errorf("Expected KeysByIdUsage to filter out revoked key %X, but got %d matches", revokedKey, len(keys)) + } +} + +func TestKeyUsage(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex)) + + // subkeyUsageHex contains these keys: + // pub 1024R/2866382A created: 2014-04-01 expires: never usage: SC + // sub 1024R/936C9153 created: 2014-04-01 expires: never usage: E + // sub 1024R/64D5F5BB created: 2014-04-02 expires: never usage: E + // sub 1024D/BC0BA992 created: 2014-04-02 expires: never usage: S + certifiers := []uint64{0xA42704B92866382A} + signers := []uint64{0xA42704B92866382A, 0x42CE2C64BC0BA992} + encrypters := []uint64{0x09C0C7D9936C9153, 0xC104E98664D5F5BB} + + for _, id := range certifiers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagCertify) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find certifier key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for certifier key id %X, but got %d matches", id, len(keys)) + } + } + + for _, id := range signers { + keys := kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find signing key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for signing key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) != 0 { + t.Errorf("Unexpected match for encryption key id %X", id) + } + } + + for _, id := range encrypters { + keys := kring.KeysByIdUsage(id, packet.KeyFlagEncryptStorage|packet.KeyFlagEncryptCommunications) + if len(keys) == 1 { + if keys[0].PublicKey.KeyId != id { + t.Errorf("Expected to find encryption key id %X, but got %X", id, keys[0].PublicKey.KeyId) + } + } else { + t.Errorf("Expected one match for encryption key id %X, but got %d matches", id, len(keys)) + } + + // This keyring contains no encryption keys that are also good for signing. + keys = kring.KeysByIdUsage(id, packet.KeyFlagSign) + if len(keys) != 0 { + t.Errorf("Unexpected match for signing key id %X", id) + } + } +} + +func TestIdVerification(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + if err != nil { + t.Fatal(err) + } + if err := kring[1].PrivateKey.Decrypt([]byte("passphrase")); err != nil { + t.Fatal(err) + } + + const identity = "Test Key 1 (RSA)" + if err := kring[0].SignIdentity(identity, kring[1], nil); err != nil { + t.Fatal(err) + } + + ident, ok := kring[0].Identities[identity] + if !ok { + t.Fatal("identity missing from key after signing") + } + + checked := false + for _, sig := range ident.Signatures { + if sig.IssuerKeyId == nil || *sig.IssuerKeyId != kring[1].PrimaryKey.KeyId { + continue + } + + if err := kring[1].PrimaryKey.VerifyUserIdSignature(identity, kring[0].PrimaryKey, sig); err != nil { + t.Fatalf("error verifying new identity signature: %s", err) + } + checked = true + break + } + + if !checked { + t.Fatal("didn't find identity signature in Entity") + } +} + +const expiringKeyHex = "988d0451d1ec5d010400ba3385721f2dc3f4ab096b2ee867ab77213f0a27a8538441c35d2fa225b08798a1439a66a5150e6bdc3f40f5d28d588c712394c632b6299f77db8c0d48d37903fb72ebd794d61be6aa774688839e5fdecfe06b2684cc115d240c98c66cb1ef22ae84e3aa0c2b0c28665c1e7d4d044e7f270706193f5223c8d44e0d70b7b8da830011010001b40f4578706972792074657374206b657988be041301020028050251d1ec5d021b03050900278d00060b090807030206150802090a0b0416020301021e01021780000a091072589ad75e237d8c033503fd10506d72837834eb7f994117740723adc39227104b0d326a1161871c0b415d25b4aedef946ca77ea4c05af9c22b32cf98be86ab890111fced1ee3f75e87b7cc3c00dc63bbc85dfab91c0dc2ad9de2c4d13a34659333a85c6acc1a669c5e1d6cecb0cf1e56c10e72d855ae177ddc9e766f9b2dda57ccbb75f57156438bbdb4e42b88d0451d1ec5d0104009c64906559866c5cb61578f5846a94fcee142a489c9b41e67b12bb54cfe86eb9bc8566460f9a720cb00d6526fbccfd4f552071a8e3f7744b1882d01036d811ee5a3fb91a1c568055758f43ba5d2c6a9676b012f3a1a89e47bbf624f1ad571b208f3cc6224eb378f1645dd3d47584463f9eadeacfd1ce6f813064fbfdcc4b5a53001101000188a504180102000f021b0c050251d1f06b050900093e89000a091072589ad75e237d8c20e00400ab8310a41461425b37889c4da28129b5fae6084fafbc0a47dd1adc74a264c6e9c9cc125f40462ee1433072a58384daef88c961c390ed06426a81b464a53194c4e291ddd7e2e2ba3efced01537d713bd111f48437bde2363446200995e8e0d4e528dda377fd1e8f8ede9c8e2198b393bd86852ce7457a7e3daf74d510461a5b77b88d0451d1ece8010400b3a519f83ab0010307e83bca895170acce8964a044190a2b368892f7a244758d9fc193482648acb1fb9780d28cc22d171931f38bb40279389fc9bf2110876d4f3db4fcfb13f22f7083877fe56592b3b65251312c36f83ffcb6d313c6a17f197dd471f0712aad15a8537b435a92471ba2e5b0c72a6c72536c3b567c558d7b6051001101000188a504180102000f021b0c050251d1f07b050900279091000a091072589ad75e237d8ce69e03fe286026afacf7c97ee20673864d4459a2240b5655219950643c7dba0ac384b1d4359c67805b21d98211f7b09c2a0ccf6410c8c04d4ff4a51293725d8d6570d9d8bb0e10c07d22357caeb49626df99c180be02d77d1fe8ed25e7a54481237646083a9f89a11566cd20b9e995b1487c5f9e02aeb434f3a1897cd416dd0a87861838da3e9e" +const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98" +const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f" +const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011" diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed.go new file mode 100644 index 0000000..e8f0b5c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "compress/bzip2" + "compress/flate" + "compress/zlib" + "golang.org/x/crypto/openpgp/errors" + "io" + "strconv" +) + +// Compressed represents a compressed OpenPGP packet. The decompressed contents +// will contain more OpenPGP packets. See RFC 4880, section 5.6. +type Compressed struct { + Body io.Reader +} + +const ( + NoCompression = flate.NoCompression + BestSpeed = flate.BestSpeed + BestCompression = flate.BestCompression + DefaultCompression = flate.DefaultCompression +) + +// CompressionConfig contains compressor configuration settings. +type CompressionConfig struct { + // Level is the compression level to use. It must be set to + // between -1 and 9, with -1 causing the compressor to use the + // default compression level, 0 causing the compressor to use + // no compression and 1 to 9 representing increasing (better, + // slower) compression levels. If Level is less than -1 or + // more then 9, a non-nil error will be returned during + // encryption. See the constants above for convenient common + // settings for Level. + Level int +} + +func (c *Compressed) parse(r io.Reader) error { + var buf [1]byte + _, err := readFull(r, buf[:]) + if err != nil { + return err + } + + switch buf[0] { + case 1: + c.Body = flate.NewReader(r) + case 2: + c.Body, err = zlib.NewReader(r) + case 3: + c.Body = bzip2.NewReader(r) + default: + err = errors.UnsupportedError("unknown compression algorithm: " + strconv.Itoa(int(buf[0]))) + } + + return err +} + +// compressedWriterCloser represents the serialized compression stream +// header and the compressor. Its Close() method ensures that both the +// compressor and serialized stream header are closed. Its Write() +// method writes to the compressor. +type compressedWriteCloser struct { + sh io.Closer // Stream Header + c io.WriteCloser // Compressor +} + +func (cwc compressedWriteCloser) Write(p []byte) (int, error) { + return cwc.c.Write(p) +} + +func (cwc compressedWriteCloser) Close() (err error) { + err = cwc.c.Close() + if err != nil { + return err + } + + return cwc.sh.Close() +} + +// SerializeCompressed serializes a compressed data packet to w and +// returns a WriteCloser to which the literal data packets themselves +// can be written and which MUST be closed on completion. If cc is +// nil, sensible defaults will be used to configure the compression +// algorithm. +func SerializeCompressed(w io.WriteCloser, algo CompressionAlgo, cc *CompressionConfig) (literaldata io.WriteCloser, err error) { + compressed, err := serializeStreamHeader(w, packetTypeCompressed) + if err != nil { + return + } + + _, err = compressed.Write([]byte{uint8(algo)}) + if err != nil { + return + } + + level := DefaultCompression + if cc != nil { + level = cc.Level + } + + var compressor io.WriteCloser + switch algo { + case CompressionZIP: + compressor, err = flate.NewWriter(compressed, level) + case CompressionZLIB: + compressor, err = zlib.NewWriterLevel(compressed, level) + default: + s := strconv.Itoa(int(algo)) + err = errors.UnsupportedError("Unsupported compression algorithm: " + s) + } + if err != nil { + return + } + + literaldata = compressedWriteCloser{compressed, compressor} + + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed_test.go new file mode 100644 index 0000000..cb2d70b --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/compressed_test.go @@ -0,0 +1,41 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "io" + "io/ioutil" + "testing" +) + +func TestCompressed(t *testing.T) { + packet, err := Read(readerFromHex(compressedHex)) + if err != nil { + t.Errorf("failed to read Compressed: %s", err) + return + } + + c, ok := packet.(*Compressed) + if !ok { + t.Error("didn't find Compressed packet") + return + } + + contents, err := ioutil.ReadAll(c.Body) + if err != nil && err != io.EOF { + t.Error(err) + return + } + + expected, _ := hex.DecodeString(compressedExpectedHex) + if !bytes.Equal(expected, contents) { + t.Errorf("got:%x want:%x", contents, expected) + } +} + +const compressedHex = "a3013b2d90c4e02b72e25f727e5e496a5e49b11e1700" +const compressedExpectedHex = "cb1062004d14c8fe636f6e74656e74732e0a" diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go new file mode 100644 index 0000000..8c4f213 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go @@ -0,0 +1,70 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto" + "crypto/rand" + "io" + "time" +) + +// Config collects a number of parameters along with sensible defaults. +// A nil *Config is valid and results in all default values. +type Config struct { + // Rand provides the source of entropy. + // If nil, the crypto/rand Reader is used. + Rand io.Reader + // DefaultHash is the default hash function to be used. + // If zero, SHA-256 is used. + DefaultHash crypto.Hash + // DefaultCipher is the cipher to be used. + // If zero, AES-128 is used. + DefaultCipher CipherFunction + // Time returns the current time as the number of seconds since the + // epoch. If Time is nil, time.Now is used. + Time func() time.Time + // DefaultCompressionAlgo is the compression algorithm to be + // applied to the plaintext before encryption. If zero, no + // compression is done. + DefaultCompressionAlgo CompressionAlgo + // CompressionConfig configures the compression settings. + CompressionConfig *CompressionConfig +} + +func (c *Config) Random() io.Reader { + if c == nil || c.Rand == nil { + return rand.Reader + } + return c.Rand +} + +func (c *Config) Hash() crypto.Hash { + if c == nil || uint(c.DefaultHash) == 0 { + return crypto.SHA256 + } + return c.DefaultHash +} + +func (c *Config) Cipher() CipherFunction { + if c == nil || uint8(c.DefaultCipher) == 0 { + return CipherAES128 + } + return c.DefaultCipher +} + +func (c *Config) Now() time.Time { + if c == nil || c.Time == nil { + return time.Now() + } + return c.Time() +} + +func (c *Config) Compression() CompressionAlgo { + if c == nil { + return CompressionNone + } + return c.DefaultCompressionAlgo +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go new file mode 100644 index 0000000..b43522c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go @@ -0,0 +1,168 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto/rsa" + "encoding/binary" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" + "io" + "math/big" + "strconv" +) + +const encryptedKeyVersion = 3 + +// EncryptedKey represents a public-key encrypted session key. See RFC 4880, +// section 5.1. +type EncryptedKey struct { + KeyId uint64 + Algo PublicKeyAlgorithm + CipherFunc CipherFunction // only valid after a successful Decrypt + Key []byte // only valid after a successful Decrypt + + encryptedMPI1, encryptedMPI2 []byte +} + +func (e *EncryptedKey) parse(r io.Reader) (err error) { + var buf [10]byte + _, err = readFull(r, buf[:]) + if err != nil { + return + } + if buf[0] != encryptedKeyVersion { + return errors.UnsupportedError("unknown EncryptedKey version " + strconv.Itoa(int(buf[0]))) + } + e.KeyId = binary.BigEndian.Uint64(buf[1:9]) + e.Algo = PublicKeyAlgorithm(buf[9]) + switch e.Algo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: + e.encryptedMPI1, _, err = readMPI(r) + case PubKeyAlgoElGamal: + e.encryptedMPI1, _, err = readMPI(r) + if err != nil { + return + } + e.encryptedMPI2, _, err = readMPI(r) + } + _, err = consumeAll(r) + return +} + +func checksumKeyMaterial(key []byte) uint16 { + var checksum uint16 + for _, v := range key { + checksum += uint16(v) + } + return checksum +} + +// Decrypt decrypts an encrypted session key with the given private key. The +// private key must have been decrypted first. +// If config is nil, sensible defaults will be used. +func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { + var err error + var b []byte + + // TODO(agl): use session key decryption routines here to avoid + // padding oracle attacks. + switch priv.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: + b, err = rsa.DecryptPKCS1v15(config.Random(), priv.PrivateKey.(*rsa.PrivateKey), e.encryptedMPI1) + case PubKeyAlgoElGamal: + c1 := new(big.Int).SetBytes(e.encryptedMPI1) + c2 := new(big.Int).SetBytes(e.encryptedMPI2) + b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2) + default: + err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) + } + + if err != nil { + return err + } + + e.CipherFunc = CipherFunction(b[0]) + e.Key = b[1 : len(b)-2] + expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1]) + checksum := checksumKeyMaterial(e.Key) + if checksum != expectedChecksum { + return errors.StructuralError("EncryptedKey checksum incorrect") + } + + return nil +} + +// SerializeEncryptedKey serializes an encrypted key packet to w that contains +// key, encrypted to pub. +// If config is nil, sensible defaults will be used. +func SerializeEncryptedKey(w io.Writer, pub *PublicKey, cipherFunc CipherFunction, key []byte, config *Config) error { + var buf [10]byte + buf[0] = encryptedKeyVersion + binary.BigEndian.PutUint64(buf[1:9], pub.KeyId) + buf[9] = byte(pub.PubKeyAlgo) + + keyBlock := make([]byte, 1 /* cipher type */ +len(key)+2 /* checksum */) + keyBlock[0] = byte(cipherFunc) + copy(keyBlock[1:], key) + checksum := checksumKeyMaterial(key) + keyBlock[1+len(key)] = byte(checksum >> 8) + keyBlock[1+len(key)+1] = byte(checksum) + + switch pub.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: + return serializeEncryptedKeyRSA(w, config.Random(), buf, pub.PublicKey.(*rsa.PublicKey), keyBlock) + case PubKeyAlgoElGamal: + return serializeEncryptedKeyElGamal(w, config.Random(), buf, pub.PublicKey.(*elgamal.PublicKey), keyBlock) + case PubKeyAlgoDSA, PubKeyAlgoRSASignOnly: + return errors.InvalidArgumentError("cannot encrypt to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) + } + + return errors.UnsupportedError("encrypting a key to public key of type " + strconv.Itoa(int(pub.PubKeyAlgo))) +} + +func serializeEncryptedKeyRSA(w io.Writer, rand io.Reader, header [10]byte, pub *rsa.PublicKey, keyBlock []byte) error { + cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock) + if err != nil { + return errors.InvalidArgumentError("RSA encryption failed: " + err.Error()) + } + + packetLen := 10 /* header length */ + 2 /* mpi size */ + len(cipherText) + + err = serializeHeader(w, packetTypeEncryptedKey, packetLen) + if err != nil { + return err + } + _, err = w.Write(header[:]) + if err != nil { + return err + } + return writeMPI(w, 8*uint16(len(cipherText)), cipherText) +} + +func serializeEncryptedKeyElGamal(w io.Writer, rand io.Reader, header [10]byte, pub *elgamal.PublicKey, keyBlock []byte) error { + c1, c2, err := elgamal.Encrypt(rand, pub, keyBlock) + if err != nil { + return errors.InvalidArgumentError("ElGamal encryption failed: " + err.Error()) + } + + packetLen := 10 /* header length */ + packetLen += 2 /* mpi size */ + (c1.BitLen()+7)/8 + packetLen += 2 /* mpi size */ + (c2.BitLen()+7)/8 + + err = serializeHeader(w, packetTypeEncryptedKey, packetLen) + if err != nil { + return err + } + _, err = w.Write(header[:]) + if err != nil { + return err + } + err = writeBig(w, c1) + if err != nil { + return err + } + return writeBig(w, c2) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go new file mode 100644 index 0000000..0a8dcc6 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go @@ -0,0 +1,125 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto/rsa" + "fmt" + "math/big" + "testing" +) + +func bigFromBase10(s string) *big.Int { + b, ok := new(big.Int).SetString(s, 10) + if !ok { + panic("bigFromBase10 failed") + } + return b +} + +var encryptedKeyPub = rsa.PublicKey{ + E: 65537, + N: bigFromBase10("115804063926007623305902631768113868327816898845124614648849934718568541074358183759250136204762053879858102352159854352727097033322663029387610959884180306668628526686121021235757016368038585212410610742029286439607686208110250133174279811431933746643015923132833417396844716207301518956640020862630546868823"), +} + +var encryptedKeyRSAPriv = &rsa.PrivateKey{ + PublicKey: encryptedKeyPub, + D: bigFromBase10("32355588668219869544751561565313228297765464314098552250409557267371233892496951383426602439009993875125222579159850054973310859166139474359774543943714622292329487391199285040721944491839695981199720170366763547754915493640685849961780092241140181198779299712578774460837139360803883139311171713302987058393"), +} + +var encryptedKeyPriv = &PrivateKey{ + PublicKey: PublicKey{ + PubKeyAlgo: PubKeyAlgoRSA, + }, + PrivateKey: encryptedKeyRSAPriv, +} + +func TestDecryptingEncryptedKey(t *testing.T) { + const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8" + const expectedKeyHex = "d930363f7e0308c333b9618617ea728963d8df993665ae7be1092d4926fd864b" + + p, err := Read(readerFromHex(encryptedKeyHex)) + if err != nil { + t.Errorf("error from Read: %s", err) + return + } + ek, ok := p.(*EncryptedKey) + if !ok { + t.Errorf("didn't parse an EncryptedKey, got %#v", p) + return + } + + if ek.KeyId != 0x2a67d68660df41c7 || ek.Algo != PubKeyAlgoRSA { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + err = ek.Decrypt(encryptedKeyPriv, nil) + if err != nil { + t.Errorf("error from Decrypt: %s", err) + return + } + + if ek.CipherFunc != CipherAES256 { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + keyHex := fmt.Sprintf("%x", ek.Key) + if keyHex != expectedKeyHex { + t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex) + } +} + +func TestEncryptingEncryptedKey(t *testing.T) { + key := []byte{1, 2, 3, 4} + const expectedKeyHex = "01020304" + const keyId = 42 + + pub := &PublicKey{ + PublicKey: &encryptedKeyPub, + KeyId: keyId, + PubKeyAlgo: PubKeyAlgoRSAEncryptOnly, + } + + buf := new(bytes.Buffer) + err := SerializeEncryptedKey(buf, pub, CipherAES128, key, nil) + if err != nil { + t.Errorf("error writing encrypted key packet: %s", err) + } + + p, err := Read(buf) + if err != nil { + t.Errorf("error from Read: %s", err) + return + } + ek, ok := p.(*EncryptedKey) + if !ok { + t.Errorf("didn't parse an EncryptedKey, got %#v", p) + return + } + + if ek.KeyId != keyId || ek.Algo != PubKeyAlgoRSAEncryptOnly { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + err = ek.Decrypt(encryptedKeyPriv, nil) + if err != nil { + t.Errorf("error from Decrypt: %s", err) + return + } + + if ek.CipherFunc != CipherAES128 { + t.Errorf("unexpected EncryptedKey contents: %#v", ek) + return + } + + keyHex := fmt.Sprintf("%x", ek.Key) + if keyHex != expectedKeyHex { + t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/literal.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/literal.go new file mode 100644 index 0000000..1a9ec6e --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/literal.go @@ -0,0 +1,89 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "encoding/binary" + "io" +) + +// LiteralData represents an encrypted file. See RFC 4880, section 5.9. +type LiteralData struct { + IsBinary bool + FileName string + Time uint32 // Unix epoch time. Either creation time or modification time. 0 means undefined. + Body io.Reader +} + +// ForEyesOnly returns whether the contents of the LiteralData have been marked +// as especially sensitive. +func (l *LiteralData) ForEyesOnly() bool { + return l.FileName == "_CONSOLE" +} + +func (l *LiteralData) parse(r io.Reader) (err error) { + var buf [256]byte + + _, err = readFull(r, buf[:2]) + if err != nil { + return + } + + l.IsBinary = buf[0] == 'b' + fileNameLen := int(buf[1]) + + _, err = readFull(r, buf[:fileNameLen]) + if err != nil { + return + } + + l.FileName = string(buf[:fileNameLen]) + + _, err = readFull(r, buf[:4]) + if err != nil { + return + } + + l.Time = binary.BigEndian.Uint32(buf[:4]) + l.Body = r + return +} + +// SerializeLiteral serializes a literal data packet to w and returns a +// WriteCloser to which the data itself can be written and which MUST be closed +// on completion. The fileName is truncated to 255 bytes. +func SerializeLiteral(w io.WriteCloser, isBinary bool, fileName string, time uint32) (plaintext io.WriteCloser, err error) { + var buf [4]byte + buf[0] = 't' + if isBinary { + buf[0] = 'b' + } + if len(fileName) > 255 { + fileName = fileName[:255] + } + buf[1] = byte(len(fileName)) + + inner, err := serializeStreamHeader(w, packetTypeLiteralData) + if err != nil { + return + } + + _, err = inner.Write(buf[:2]) + if err != nil { + return + } + _, err = inner.Write([]byte(fileName)) + if err != nil { + return + } + binary.BigEndian.PutUint32(buf[:], time) + _, err = inner.Write(buf[:]) + if err != nil { + return + } + + plaintext = inner + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb.go new file mode 100644 index 0000000..ce2a33a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb.go @@ -0,0 +1,143 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// OpenPGP CFB Mode. http://tools.ietf.org/html/rfc4880#section-13.9 + +package packet + +import ( + "crypto/cipher" +) + +type ocfbEncrypter struct { + b cipher.Block + fre []byte + outUsed int +} + +// An OCFBResyncOption determines if the "resynchronization step" of OCFB is +// performed. +type OCFBResyncOption bool + +const ( + OCFBResync OCFBResyncOption = true + OCFBNoResync OCFBResyncOption = false +) + +// NewOCFBEncrypter returns a cipher.Stream which encrypts data with OpenPGP's +// cipher feedback mode using the given cipher.Block, and an initial amount of +// ciphertext. randData must be random bytes and be the same length as the +// cipher.Block's block size. Resync determines if the "resynchronization step" +// from RFC 4880, 13.9 step 7 is performed. Different parts of OpenPGP vary on +// this point. +func NewOCFBEncrypter(block cipher.Block, randData []byte, resync OCFBResyncOption) (cipher.Stream, []byte) { + blockSize := block.BlockSize() + if len(randData) != blockSize { + return nil, nil + } + + x := &ocfbEncrypter{ + b: block, + fre: make([]byte, blockSize), + outUsed: 0, + } + prefix := make([]byte, blockSize+2) + + block.Encrypt(x.fre, x.fre) + for i := 0; i < blockSize; i++ { + prefix[i] = randData[i] ^ x.fre[i] + } + + block.Encrypt(x.fre, prefix[:blockSize]) + prefix[blockSize] = x.fre[0] ^ randData[blockSize-2] + prefix[blockSize+1] = x.fre[1] ^ randData[blockSize-1] + + if resync { + block.Encrypt(x.fre, prefix[2:]) + } else { + x.fre[0] = prefix[blockSize] + x.fre[1] = prefix[blockSize+1] + x.outUsed = 2 + } + return x, prefix +} + +func (x *ocfbEncrypter) XORKeyStream(dst, src []byte) { + for i := 0; i < len(src); i++ { + if x.outUsed == len(x.fre) { + x.b.Encrypt(x.fre, x.fre) + x.outUsed = 0 + } + + x.fre[x.outUsed] ^= src[i] + dst[i] = x.fre[x.outUsed] + x.outUsed++ + } +} + +type ocfbDecrypter struct { + b cipher.Block + fre []byte + outUsed int +} + +// NewOCFBDecrypter returns a cipher.Stream which decrypts data with OpenPGP's +// cipher feedback mode using the given cipher.Block. Prefix must be the first +// blockSize + 2 bytes of the ciphertext, where blockSize is the cipher.Block's +// block size. If an incorrect key is detected then nil is returned. On +// successful exit, blockSize+2 bytes of decrypted data are written into +// prefix. Resync determines if the "resynchronization step" from RFC 4880, +// 13.9 step 7 is performed. Different parts of OpenPGP vary on this point. +func NewOCFBDecrypter(block cipher.Block, prefix []byte, resync OCFBResyncOption) cipher.Stream { + blockSize := block.BlockSize() + if len(prefix) != blockSize+2 { + return nil + } + + x := &ocfbDecrypter{ + b: block, + fre: make([]byte, blockSize), + outUsed: 0, + } + prefixCopy := make([]byte, len(prefix)) + copy(prefixCopy, prefix) + + block.Encrypt(x.fre, x.fre) + for i := 0; i < blockSize; i++ { + prefixCopy[i] ^= x.fre[i] + } + + block.Encrypt(x.fre, prefix[:blockSize]) + prefixCopy[blockSize] ^= x.fre[0] + prefixCopy[blockSize+1] ^= x.fre[1] + + if prefixCopy[blockSize-2] != prefixCopy[blockSize] || + prefixCopy[blockSize-1] != prefixCopy[blockSize+1] { + return nil + } + + if resync { + block.Encrypt(x.fre, prefix[2:]) + } else { + x.fre[0] = prefix[blockSize] + x.fre[1] = prefix[blockSize+1] + x.outUsed = 2 + } + copy(prefix, prefixCopy) + return x +} + +func (x *ocfbDecrypter) XORKeyStream(dst, src []byte) { + for i := 0; i < len(src); i++ { + if x.outUsed == len(x.fre) { + x.b.Encrypt(x.fre, x.fre) + x.outUsed = 0 + } + + c := src[i] + dst[i] = x.fre[x.outUsed] ^ src[i] + x.fre[x.outUsed] = c + x.outUsed++ + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go new file mode 100644 index 0000000..91022c0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/ocfb_test.go @@ -0,0 +1,46 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "testing" +) + +var commonKey128 = []byte{0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c} + +func testOCFB(t *testing.T, resync OCFBResyncOption) { + block, err := aes.NewCipher(commonKey128) + if err != nil { + t.Error(err) + return + } + + plaintext := []byte("this is the plaintext, which is long enough to span several blocks.") + randData := make([]byte, block.BlockSize()) + rand.Reader.Read(randData) + ocfb, prefix := NewOCFBEncrypter(block, randData, resync) + ciphertext := make([]byte, len(plaintext)) + ocfb.XORKeyStream(ciphertext, plaintext) + + ocfbdec := NewOCFBDecrypter(block, prefix, resync) + if ocfbdec == nil { + t.Errorf("NewOCFBDecrypter failed (resync: %t)", resync) + return + } + plaintextCopy := make([]byte, len(plaintext)) + ocfbdec.XORKeyStream(plaintextCopy, ciphertext) + + if !bytes.Equal(plaintextCopy, plaintext) { + t.Errorf("got: %x, want: %x (resync: %t)", plaintextCopy, plaintext, resync) + } +} + +func TestOCFB(t *testing.T) { + testOCFB(t, OCFBNoResync) + testOCFB(t, OCFBResync) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go new file mode 100644 index 0000000..1713503 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/one_pass_signature.go @@ -0,0 +1,73 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto" + "encoding/binary" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" + "io" + "strconv" +) + +// OnePassSignature represents a one-pass signature packet. See RFC 4880, +// section 5.4. +type OnePassSignature struct { + SigType SignatureType + Hash crypto.Hash + PubKeyAlgo PublicKeyAlgorithm + KeyId uint64 + IsLast bool +} + +const onePassSignatureVersion = 3 + +func (ops *OnePassSignature) parse(r io.Reader) (err error) { + var buf [13]byte + + _, err = readFull(r, buf[:]) + if err != nil { + return + } + if buf[0] != onePassSignatureVersion { + err = errors.UnsupportedError("one-pass-signature packet version " + strconv.Itoa(int(buf[0]))) + } + + var ok bool + ops.Hash, ok = s2k.HashIdToHash(buf[2]) + if !ok { + return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2]))) + } + + ops.SigType = SignatureType(buf[1]) + ops.PubKeyAlgo = PublicKeyAlgorithm(buf[3]) + ops.KeyId = binary.BigEndian.Uint64(buf[4:12]) + ops.IsLast = buf[12] != 0 + return +} + +// Serialize marshals the given OnePassSignature to w. +func (ops *OnePassSignature) Serialize(w io.Writer) error { + var buf [13]byte + buf[0] = onePassSignatureVersion + buf[1] = uint8(ops.SigType) + var ok bool + buf[2], ok = s2k.HashToHashId(ops.Hash) + if !ok { + return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash))) + } + buf[3] = uint8(ops.PubKeyAlgo) + binary.BigEndian.PutUint64(buf[4:12], ops.KeyId) + if ops.IsLast { + buf[12] = 1 + } + + if err := serializeHeader(w, packetTypeOnePassSignature, len(buf)); err != nil { + return err + } + _, err := w.Write(buf[:]) + return err +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque.go new file mode 100644 index 0000000..f2e00b9 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque.go @@ -0,0 +1,161 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "golang.org/x/crypto/openpgp/errors" + "io" + "io/ioutil" +) + +// OpaquePacket represents an OpenPGP packet as raw, unparsed data. This is +// useful for splitting and storing the original packet contents separately, +// handling unsupported packet types or accessing parts of the packet not yet +// implemented by this package. +type OpaquePacket struct { + // Packet type + Tag uint8 + // Reason why the packet was parsed opaquely + Reason error + // Binary contents of the packet data + Contents []byte +} + +func (op *OpaquePacket) parse(r io.Reader) (err error) { + op.Contents, err = ioutil.ReadAll(r) + return +} + +// Serialize marshals the packet to a writer in its original form, including +// the packet header. +func (op *OpaquePacket) Serialize(w io.Writer) (err error) { + err = serializeHeader(w, packetType(op.Tag), len(op.Contents)) + if err == nil { + _, err = w.Write(op.Contents) + } + return +} + +// Parse attempts to parse the opaque contents into a structure supported by +// this package. If the packet is not known then the result will be another +// OpaquePacket. +func (op *OpaquePacket) Parse() (p Packet, err error) { + hdr := bytes.NewBuffer(nil) + err = serializeHeader(hdr, packetType(op.Tag), len(op.Contents)) + if err != nil { + op.Reason = err + return op, err + } + p, err = Read(io.MultiReader(hdr, bytes.NewBuffer(op.Contents))) + if err != nil { + op.Reason = err + p = op + } + return +} + +// OpaqueReader reads OpaquePackets from an io.Reader. +type OpaqueReader struct { + r io.Reader +} + +func NewOpaqueReader(r io.Reader) *OpaqueReader { + return &OpaqueReader{r: r} +} + +// Read the next OpaquePacket. +func (or *OpaqueReader) Next() (op *OpaquePacket, err error) { + tag, _, contents, err := readHeader(or.r) + if err != nil { + return + } + op = &OpaquePacket{Tag: uint8(tag), Reason: err} + err = op.parse(contents) + if err != nil { + consumeAll(contents) + } + return +} + +// OpaqueSubpacket represents an unparsed OpenPGP subpacket, +// as found in signature and user attribute packets. +type OpaqueSubpacket struct { + SubType uint8 + Contents []byte +} + +// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from +// their byte representation. +func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) { + var ( + subHeaderLen int + subPacket *OpaqueSubpacket + ) + for len(contents) > 0 { + subHeaderLen, subPacket, err = nextSubpacket(contents) + if err != nil { + break + } + result = append(result, subPacket) + contents = contents[subHeaderLen+len(subPacket.Contents):] + } + return +} + +func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) { + // RFC 4880, section 5.2.3.1 + var subLen uint32 + if len(contents) < 1 { + goto Truncated + } + subPacket = &OpaqueSubpacket{} + switch { + case contents[0] < 192: + subHeaderLen = 2 // 1 length byte, 1 subtype byte + if len(contents) < subHeaderLen { + goto Truncated + } + subLen = uint32(contents[0]) + contents = contents[1:] + case contents[0] < 255: + subHeaderLen = 3 // 2 length bytes, 1 subtype + if len(contents) < subHeaderLen { + goto Truncated + } + subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192 + contents = contents[2:] + default: + subHeaderLen = 6 // 5 length bytes, 1 subtype + if len(contents) < subHeaderLen { + goto Truncated + } + subLen = uint32(contents[1])<<24 | + uint32(contents[2])<<16 | + uint32(contents[3])<<8 | + uint32(contents[4]) + contents = contents[5:] + } + if subLen > uint32(len(contents)) { + goto Truncated + } + subPacket.SubType = contents[0] + subPacket.Contents = contents[1:subLen] + return +Truncated: + err = errors.StructuralError("subpacket truncated") + return +} + +func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) { + buf := make([]byte, 6) + n := serializeSubpacketLength(buf, len(osp.Contents)+1) + buf[n] = osp.SubType + if _, err = w.Write(buf[:n+1]); err != nil { + return + } + _, err = w.Write(osp.Contents) + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque_test.go new file mode 100644 index 0000000..f27bbfe --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/opaque_test.go @@ -0,0 +1,67 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "io" + "testing" +) + +// Test packet.Read error handling in OpaquePacket.Parse, +// which attempts to re-read an OpaquePacket as a supported +// Packet type. +func TestOpaqueParseReason(t *testing.T) { + buf, err := hex.DecodeString(UnsupportedKeyHex) + if err != nil { + t.Fatal(err) + } + or := NewOpaqueReader(bytes.NewBuffer(buf)) + count := 0 + badPackets := 0 + var uid *UserId + for { + op, err := or.Next() + if err == io.EOF { + break + } else if err != nil { + t.Errorf("#%d: opaque read error: %v", count, err) + break + } + // try to parse opaque packet + p, err := op.Parse() + switch pkt := p.(type) { + case *UserId: + uid = pkt + case *OpaquePacket: + // If an OpaquePacket can't re-parse, packet.Read + // certainly had its reasons. + if pkt.Reason == nil { + t.Errorf("#%d: opaque packet, no reason", count) + } else { + badPackets++ + } + } + count++ + } + + const expectedBad = 3 + // Test post-conditions, make sure we actually parsed packets as expected. + if badPackets != expectedBad { + t.Errorf("unexpected # unparseable packets: %d (want %d)", badPackets, expectedBad) + } + if uid == nil { + t.Errorf("failed to find expected UID in unsupported keyring") + } else if uid.Id != "Armin M. Warda " { + t.Errorf("unexpected UID: %v", uid.Id) + } +} + +// This key material has public key and signature packet versions modified to +// an unsupported value (1), so that trying to parse the OpaquePacket to +// a typed packet will get an error. It also contains a GnuPG trust packet. +// (Created with: od -An -t x1 pubring.gpg | xargs | sed 's/ //g') +const UnsupportedKeyHex = `988d012e7a18a20000010400d6ac00d92b89c1f4396c243abb9b76d2e9673ad63483291fed88e22b82e255e441c078c6abbbf7d2d195e50b62eeaa915b85b0ec20c225ce2c64c167cacb6e711daf2e45da4a8356a059b8160e3b3628ac0dd8437b31f06d53d6e8ea4214d4a26406a6b63e1001406ef23e0bb3069fac9a99a91f77dfafd5de0f188a5da5e3c9000511b42741726d696e204d2e205761726461203c7761726461406e657068696c696d2e727568722e64653e8900950105102e8936c705d1eb399e58489901013f0e03ff5a0c4f421e34fcfa388129166420c08cd76987bcdec6f01bd0271459a85cc22048820dd4e44ac2c7d23908d540f54facf1b36b0d9c20488781ce9dca856531e76e2e846826e9951338020a03a09b57aa5faa82e9267458bd76105399885ac35af7dc1cbb6aaed7c39e1039f3b5beda2c0e916bd38560509bab81235d1a0ead83b0020000` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go new file mode 100644 index 0000000..b1e7a7f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go @@ -0,0 +1,538 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package packet implements parsing and serialization of OpenPGP packets, as +// specified in RFC 4880. +package packet + +import ( + "bufio" + "crypto/aes" + "crypto/cipher" + "crypto/des" + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/openpgp/errors" + "io" + "math/big" +) + +// readFull is the same as io.ReadFull except that reading zero bytes returns +// ErrUnexpectedEOF rather than EOF. +func readFull(r io.Reader, buf []byte) (n int, err error) { + n, err = io.ReadFull(r, buf) + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return +} + +// readLength reads an OpenPGP length from r. See RFC 4880, section 4.2.2. +func readLength(r io.Reader) (length int64, isPartial bool, err error) { + var buf [4]byte + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + switch { + case buf[0] < 192: + length = int64(buf[0]) + case buf[0] < 224: + length = int64(buf[0]-192) << 8 + _, err = readFull(r, buf[0:1]) + if err != nil { + return + } + length += int64(buf[0]) + 192 + case buf[0] < 255: + length = int64(1) << (buf[0] & 0x1f) + isPartial = true + default: + _, err = readFull(r, buf[0:4]) + if err != nil { + return + } + length = int64(buf[0])<<24 | + int64(buf[1])<<16 | + int64(buf[2])<<8 | + int64(buf[3]) + } + return +} + +// partialLengthReader wraps an io.Reader and handles OpenPGP partial lengths. +// The continuation lengths are parsed and removed from the stream and EOF is +// returned at the end of the packet. See RFC 4880, section 4.2.2.4. +type partialLengthReader struct { + r io.Reader + remaining int64 + isPartial bool +} + +func (r *partialLengthReader) Read(p []byte) (n int, err error) { + for r.remaining == 0 { + if !r.isPartial { + return 0, io.EOF + } + r.remaining, r.isPartial, err = readLength(r.r) + if err != nil { + return 0, err + } + } + + toRead := int64(len(p)) + if toRead > r.remaining { + toRead = r.remaining + } + + n, err = r.r.Read(p[:int(toRead)]) + r.remaining -= int64(n) + if n < int(toRead) && err == io.EOF { + err = io.ErrUnexpectedEOF + } + return +} + +// partialLengthWriter writes a stream of data using OpenPGP partial lengths. +// See RFC 4880, section 4.2.2.4. +type partialLengthWriter struct { + w io.WriteCloser + lengthByte [1]byte +} + +func (w *partialLengthWriter) Write(p []byte) (n int, err error) { + for len(p) > 0 { + for power := uint(14); power < 32; power-- { + l := 1 << power + if len(p) >= l { + w.lengthByte[0] = 224 + uint8(power) + _, err = w.w.Write(w.lengthByte[:]) + if err != nil { + return + } + var m int + m, err = w.w.Write(p[:l]) + n += m + if err != nil { + return + } + p = p[l:] + break + } + } + } + return +} + +func (w *partialLengthWriter) Close() error { + w.lengthByte[0] = 0 + _, err := w.w.Write(w.lengthByte[:]) + if err != nil { + return err + } + return w.w.Close() +} + +// A spanReader is an io.LimitReader, but it returns ErrUnexpectedEOF if the +// underlying Reader returns EOF before the limit has been reached. +type spanReader struct { + r io.Reader + n int64 +} + +func (l *spanReader) Read(p []byte) (n int, err error) { + if l.n <= 0 { + return 0, io.EOF + } + if int64(len(p)) > l.n { + p = p[0:l.n] + } + n, err = l.r.Read(p) + l.n -= int64(n) + if l.n > 0 && err == io.EOF { + err = io.ErrUnexpectedEOF + } + return +} + +// readHeader parses a packet header and returns an io.Reader which will return +// the contents of the packet. See RFC 4880, section 4.2. +func readHeader(r io.Reader) (tag packetType, length int64, contents io.Reader, err error) { + var buf [4]byte + _, err = io.ReadFull(r, buf[:1]) + if err != nil { + return + } + if buf[0]&0x80 == 0 { + err = errors.StructuralError("tag byte does not have MSB set") + return + } + if buf[0]&0x40 == 0 { + // Old format packet + tag = packetType((buf[0] & 0x3f) >> 2) + lengthType := buf[0] & 3 + if lengthType == 3 { + length = -1 + contents = r + return + } + lengthBytes := 1 << lengthType + _, err = readFull(r, buf[0:lengthBytes]) + if err != nil { + return + } + for i := 0; i < lengthBytes; i++ { + length <<= 8 + length |= int64(buf[i]) + } + contents = &spanReader{r, length} + return + } + + // New format packet + tag = packetType(buf[0] & 0x3f) + length, isPartial, err := readLength(r) + if err != nil { + return + } + if isPartial { + contents = &partialLengthReader{ + remaining: length, + isPartial: true, + r: r, + } + length = -1 + } else { + contents = &spanReader{r, length} + } + return +} + +// serializeHeader writes an OpenPGP packet header to w. See RFC 4880, section +// 4.2. +func serializeHeader(w io.Writer, ptype packetType, length int) (err error) { + var buf [6]byte + var n int + + buf[0] = 0x80 | 0x40 | byte(ptype) + if length < 192 { + buf[1] = byte(length) + n = 2 + } else if length < 8384 { + length -= 192 + buf[1] = 192 + byte(length>>8) + buf[2] = byte(length) + n = 3 + } else { + buf[1] = 255 + buf[2] = byte(length >> 24) + buf[3] = byte(length >> 16) + buf[4] = byte(length >> 8) + buf[5] = byte(length) + n = 6 + } + + _, err = w.Write(buf[:n]) + return +} + +// serializeStreamHeader writes an OpenPGP packet header to w where the +// length of the packet is unknown. It returns a io.WriteCloser which can be +// used to write the contents of the packet. See RFC 4880, section 4.2. +func serializeStreamHeader(w io.WriteCloser, ptype packetType) (out io.WriteCloser, err error) { + var buf [1]byte + buf[0] = 0x80 | 0x40 | byte(ptype) + _, err = w.Write(buf[:]) + if err != nil { + return + } + out = &partialLengthWriter{w: w} + return +} + +// Packet represents an OpenPGP packet. Users are expected to try casting +// instances of this interface to specific packet types. +type Packet interface { + parse(io.Reader) error +} + +// consumeAll reads from the given Reader until error, returning the number of +// bytes read. +func consumeAll(r io.Reader) (n int64, err error) { + var m int + var buf [1024]byte + + for { + m, err = r.Read(buf[:]) + n += int64(m) + if err == io.EOF { + err = nil + return + } + if err != nil { + return + } + } + + panic("unreachable") +} + +// packetType represents the numeric ids of the different OpenPGP packet types. See +// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-2 +type packetType uint8 + +const ( + packetTypeEncryptedKey packetType = 1 + packetTypeSignature packetType = 2 + packetTypeSymmetricKeyEncrypted packetType = 3 + packetTypeOnePassSignature packetType = 4 + packetTypePrivateKey packetType = 5 + packetTypePublicKey packetType = 6 + packetTypePrivateSubkey packetType = 7 + packetTypeCompressed packetType = 8 + packetTypeSymmetricallyEncrypted packetType = 9 + packetTypeLiteralData packetType = 11 + packetTypeUserId packetType = 13 + packetTypePublicSubkey packetType = 14 + packetTypeUserAttribute packetType = 17 + packetTypeSymmetricallyEncryptedMDC packetType = 18 +) + +// peekVersion detects the version of a public key packet about to +// be read. A bufio.Reader at the original position of the io.Reader +// is returned. +func peekVersion(r io.Reader) (bufr *bufio.Reader, ver byte, err error) { + bufr = bufio.NewReader(r) + var verBuf []byte + if verBuf, err = bufr.Peek(1); err != nil { + return + } + ver = verBuf[0] + return +} + +// Read reads a single OpenPGP packet from the given io.Reader. If there is an +// error parsing a packet, the whole packet is consumed from the input. +func Read(r io.Reader) (p Packet, err error) { + tag, _, contents, err := readHeader(r) + if err != nil { + return + } + + switch tag { + case packetTypeEncryptedKey: + p = new(EncryptedKey) + case packetTypeSignature: + var version byte + // Detect signature version + if contents, version, err = peekVersion(contents); err != nil { + return + } + if version < 4 { + p = new(SignatureV3) + } else { + p = new(Signature) + } + case packetTypeSymmetricKeyEncrypted: + p = new(SymmetricKeyEncrypted) + case packetTypeOnePassSignature: + p = new(OnePassSignature) + case packetTypePrivateKey, packetTypePrivateSubkey: + pk := new(PrivateKey) + if tag == packetTypePrivateSubkey { + pk.IsSubkey = true + } + p = pk + case packetTypePublicKey, packetTypePublicSubkey: + var version byte + if contents, version, err = peekVersion(contents); err != nil { + return + } + isSubkey := tag == packetTypePublicSubkey + if version < 4 { + p = &PublicKeyV3{IsSubkey: isSubkey} + } else { + p = &PublicKey{IsSubkey: isSubkey} + } + case packetTypeCompressed: + p = new(Compressed) + case packetTypeSymmetricallyEncrypted: + p = new(SymmetricallyEncrypted) + case packetTypeLiteralData: + p = new(LiteralData) + case packetTypeUserId: + p = new(UserId) + case packetTypeUserAttribute: + p = new(UserAttribute) + case packetTypeSymmetricallyEncryptedMDC: + se := new(SymmetricallyEncrypted) + se.MDC = true + p = se + default: + err = errors.UnknownPacketTypeError(tag) + } + if p != nil { + err = p.parse(contents) + } + if err != nil { + consumeAll(contents) + } + return +} + +// SignatureType represents the different semantic meanings of an OpenPGP +// signature. See RFC 4880, section 5.2.1. +type SignatureType uint8 + +const ( + SigTypeBinary SignatureType = 0 + SigTypeText = 1 + SigTypeGenericCert = 0x10 + SigTypePersonaCert = 0x11 + SigTypeCasualCert = 0x12 + SigTypePositiveCert = 0x13 + SigTypeSubkeyBinding = 0x18 + SigTypeDirectSignature = 0x1F + SigTypeKeyRevocation = 0x20 + SigTypeSubkeyRevocation = 0x28 +) + +// PublicKeyAlgorithm represents the different public key system specified for +// OpenPGP. See +// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-12 +type PublicKeyAlgorithm uint8 + +const ( + PubKeyAlgoRSA PublicKeyAlgorithm = 1 + PubKeyAlgoRSAEncryptOnly PublicKeyAlgorithm = 2 + PubKeyAlgoRSASignOnly PublicKeyAlgorithm = 3 + PubKeyAlgoElGamal PublicKeyAlgorithm = 16 + PubKeyAlgoDSA PublicKeyAlgorithm = 17 + // RFC 6637, Section 5. + PubKeyAlgoECDH PublicKeyAlgorithm = 18 + PubKeyAlgoECDSA PublicKeyAlgorithm = 19 +) + +// CanEncrypt returns true if it's possible to encrypt a message to a public +// key of the given type. +func (pka PublicKeyAlgorithm) CanEncrypt() bool { + switch pka { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoElGamal: + return true + } + return false +} + +// CanSign returns true if it's possible for a public key of the given type to +// sign a message. +func (pka PublicKeyAlgorithm) CanSign() bool { + switch pka { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA: + return true + } + return false +} + +// CipherFunction represents the different block ciphers specified for OpenPGP. See +// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-13 +type CipherFunction uint8 + +const ( + Cipher3DES CipherFunction = 2 + CipherCAST5 CipherFunction = 3 + CipherAES128 CipherFunction = 7 + CipherAES192 CipherFunction = 8 + CipherAES256 CipherFunction = 9 +) + +// KeySize returns the key size, in bytes, of cipher. +func (cipher CipherFunction) KeySize() int { + switch cipher { + case Cipher3DES: + return 24 + case CipherCAST5: + return cast5.KeySize + case CipherAES128: + return 16 + case CipherAES192: + return 24 + case CipherAES256: + return 32 + } + return 0 +} + +// blockSize returns the block size, in bytes, of cipher. +func (cipher CipherFunction) blockSize() int { + switch cipher { + case Cipher3DES: + return des.BlockSize + case CipherCAST5: + return 8 + case CipherAES128, CipherAES192, CipherAES256: + return 16 + } + return 0 +} + +// new returns a fresh instance of the given cipher. +func (cipher CipherFunction) new(key []byte) (block cipher.Block) { + switch cipher { + case Cipher3DES: + block, _ = des.NewTripleDESCipher(key) + case CipherCAST5: + block, _ = cast5.NewCipher(key) + case CipherAES128, CipherAES192, CipherAES256: + block, _ = aes.NewCipher(key) + } + return +} + +// readMPI reads a big integer from r. The bit length returned is the bit +// length that was specified in r. This is preserved so that the integer can be +// reserialized exactly. +func readMPI(r io.Reader) (mpi []byte, bitLength uint16, err error) { + var buf [2]byte + _, err = readFull(r, buf[0:]) + if err != nil { + return + } + bitLength = uint16(buf[0])<<8 | uint16(buf[1]) + numBytes := (int(bitLength) + 7) / 8 + mpi = make([]byte, numBytes) + _, err = readFull(r, mpi) + return +} + +// mpiLength returns the length of the given *big.Int when serialized as an +// MPI. +func mpiLength(n *big.Int) (mpiLengthInBytes int) { + mpiLengthInBytes = 2 /* MPI length */ + mpiLengthInBytes += (n.BitLen() + 7) / 8 + return +} + +// writeMPI serializes a big integer to w. +func writeMPI(w io.Writer, bitLength uint16, mpiBytes []byte) (err error) { + _, err = w.Write([]byte{byte(bitLength >> 8), byte(bitLength)}) + if err == nil { + _, err = w.Write(mpiBytes) + } + return +} + +// writeBig serializes a *big.Int to w. +func writeBig(w io.Writer, i *big.Int) error { + return writeMPI(w, uint16(i.BitLen()), i.Bytes()) +} + +// CompressionAlgo Represents the different compression algorithms +// supported by OpenPGP (except for BZIP2, which is not currently +// supported). See Section 9.3 of RFC 4880. +type CompressionAlgo uint8 + +const ( + CompressionNone CompressionAlgo = 0 + CompressionZIP CompressionAlgo = 1 + CompressionZLIB CompressionAlgo = 2 +) diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet_test.go new file mode 100644 index 0000000..1dab5c3 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet_test.go @@ -0,0 +1,255 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "fmt" + "golang.org/x/crypto/openpgp/errors" + "io" + "io/ioutil" + "testing" +) + +func TestReadFull(t *testing.T) { + var out [4]byte + + b := bytes.NewBufferString("foo") + n, err := readFull(b, out[:3]) + if n != 3 || err != nil { + t.Errorf("full read failed n:%d err:%s", n, err) + } + + b = bytes.NewBufferString("foo") + n, err = readFull(b, out[:4]) + if n != 3 || err != io.ErrUnexpectedEOF { + t.Errorf("partial read failed n:%d err:%s", n, err) + } + + b = bytes.NewBuffer(nil) + n, err = readFull(b, out[:3]) + if n != 0 || err != io.ErrUnexpectedEOF { + t.Errorf("empty read failed n:%d err:%s", n, err) + } +} + +func readerFromHex(s string) io.Reader { + data, err := hex.DecodeString(s) + if err != nil { + panic("readerFromHex: bad input") + } + return bytes.NewBuffer(data) +} + +var readLengthTests = []struct { + hexInput string + length int64 + isPartial bool + err error +}{ + {"", 0, false, io.ErrUnexpectedEOF}, + {"1f", 31, false, nil}, + {"c0", 0, false, io.ErrUnexpectedEOF}, + {"c101", 256 + 1 + 192, false, nil}, + {"e0", 1, true, nil}, + {"e1", 2, true, nil}, + {"e2", 4, true, nil}, + {"ff", 0, false, io.ErrUnexpectedEOF}, + {"ff00", 0, false, io.ErrUnexpectedEOF}, + {"ff0000", 0, false, io.ErrUnexpectedEOF}, + {"ff000000", 0, false, io.ErrUnexpectedEOF}, + {"ff00000000", 0, false, nil}, + {"ff01020304", 16909060, false, nil}, +} + +func TestReadLength(t *testing.T) { + for i, test := range readLengthTests { + length, isPartial, err := readLength(readerFromHex(test.hexInput)) + if test.err != nil { + if err != test.err { + t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err) + } + continue + } + if err != nil { + t.Errorf("%d: unexpected error: %s", i, err) + continue + } + if length != test.length || isPartial != test.isPartial { + t.Errorf("%d: bad result got:(%d,%t) want:(%d,%t)", i, length, isPartial, test.length, test.isPartial) + } + } +} + +var partialLengthReaderTests = []struct { + hexInput string + err error + hexOutput string +}{ + {"e0", io.ErrUnexpectedEOF, ""}, + {"e001", io.ErrUnexpectedEOF, ""}, + {"e0010102", nil, "0102"}, + {"ff00000000", nil, ""}, + {"e10102e1030400", nil, "01020304"}, + {"e101", io.ErrUnexpectedEOF, ""}, +} + +func TestPartialLengthReader(t *testing.T) { + for i, test := range partialLengthReaderTests { + r := &partialLengthReader{readerFromHex(test.hexInput), 0, true} + out, err := ioutil.ReadAll(r) + if test.err != nil { + if err != test.err { + t.Errorf("%d: expected different error got:%s want:%s", i, err, test.err) + } + continue + } + if err != nil { + t.Errorf("%d: unexpected error: %s", i, err) + continue + } + + got := fmt.Sprintf("%x", out) + if got != test.hexOutput { + t.Errorf("%d: got:%s want:%s", i, test.hexOutput, got) + } + } +} + +var readHeaderTests = []struct { + hexInput string + structuralError bool + unexpectedEOF bool + tag int + length int64 + hexOutput string +}{ + {"", false, false, 0, 0, ""}, + {"7f", true, false, 0, 0, ""}, + + // Old format headers + {"80", false, true, 0, 0, ""}, + {"8001", false, true, 0, 1, ""}, + {"800102", false, false, 0, 1, "02"}, + {"81000102", false, false, 0, 1, "02"}, + {"820000000102", false, false, 0, 1, "02"}, + {"860000000102", false, false, 1, 1, "02"}, + {"83010203", false, false, 0, -1, "010203"}, + + // New format headers + {"c0", false, true, 0, 0, ""}, + {"c000", false, false, 0, 0, ""}, + {"c00102", false, false, 0, 1, "02"}, + {"c0020203", false, false, 0, 2, "0203"}, + {"c00202", false, true, 0, 2, ""}, + {"c3020203", false, false, 3, 2, "0203"}, +} + +func TestReadHeader(t *testing.T) { + for i, test := range readHeaderTests { + tag, length, contents, err := readHeader(readerFromHex(test.hexInput)) + if test.structuralError { + if _, ok := err.(errors.StructuralError); ok { + continue + } + t.Errorf("%d: expected StructuralError, got:%s", i, err) + continue + } + if err != nil { + if len(test.hexInput) == 0 && err == io.EOF { + continue + } + if !test.unexpectedEOF || err != io.ErrUnexpectedEOF { + t.Errorf("%d: unexpected error from readHeader: %s", i, err) + } + continue + } + if int(tag) != test.tag || length != test.length { + t.Errorf("%d: got:(%d,%d) want:(%d,%d)", i, int(tag), length, test.tag, test.length) + continue + } + + body, err := ioutil.ReadAll(contents) + if err != nil { + if !test.unexpectedEOF || err != io.ErrUnexpectedEOF { + t.Errorf("%d: unexpected error from contents: %s", i, err) + } + continue + } + if test.unexpectedEOF { + t.Errorf("%d: expected ErrUnexpectedEOF from contents but got no error", i) + continue + } + got := fmt.Sprintf("%x", body) + if got != test.hexOutput { + t.Errorf("%d: got:%s want:%s", i, got, test.hexOutput) + } + } +} + +func TestSerializeHeader(t *testing.T) { + tag := packetTypePublicKey + lengths := []int{0, 1, 2, 64, 192, 193, 8000, 8384, 8385, 10000} + + for _, length := range lengths { + buf := bytes.NewBuffer(nil) + serializeHeader(buf, tag, length) + tag2, length2, _, err := readHeader(buf) + if err != nil { + t.Errorf("length %d, err: %s", length, err) + } + if tag2 != tag { + t.Errorf("length %d, tag incorrect (got %d, want %d)", length, tag2, tag) + } + if int(length2) != length { + t.Errorf("length %d, length incorrect (got %d)", length, length2) + } + } +} + +func TestPartialLengths(t *testing.T) { + buf := bytes.NewBuffer(nil) + w := new(partialLengthWriter) + w.w = noOpCloser{buf} + + const maxChunkSize = 64 + + var b [maxChunkSize]byte + var n uint8 + for l := 1; l <= maxChunkSize; l++ { + for i := 0; i < l; i++ { + b[i] = n + n++ + } + m, err := w.Write(b[:l]) + if m != l { + t.Errorf("short write got: %d want: %d", m, l) + } + if err != nil { + t.Errorf("error from write: %s", err) + } + } + w.Close() + + want := (maxChunkSize * (maxChunkSize + 1)) / 2 + copyBuf := bytes.NewBuffer(nil) + r := &partialLengthReader{buf, 0, true} + m, err := io.Copy(copyBuf, r) + if m != int64(want) { + t.Errorf("short copy got: %d want: %d", m, want) + } + if err != nil { + t.Errorf("error from copy: %s", err) + } + + copyBytes := copyBuf.Bytes() + for i := 0; i < want; i++ { + if copyBytes[i] != uint8(i) { + t.Errorf("bad pattern in copy at %d", i) + break + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key.go new file mode 100644 index 0000000..9685a34 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key.go @@ -0,0 +1,310 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto/cipher" + "crypto/dsa" + "crypto/rsa" + "crypto/sha1" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" + "io" + "io/ioutil" + "math/big" + "strconv" + "time" +) + +// PrivateKey represents a possibly encrypted private key. See RFC 4880, +// section 5.5.3. +type PrivateKey struct { + PublicKey + Encrypted bool // if true then the private key is unavailable until Decrypt has been called. + encryptedData []byte + cipher CipherFunction + s2k func(out, in []byte) + PrivateKey interface{} // An *rsa.PrivateKey or *dsa.PrivateKey. + sha1Checksum bool + iv []byte +} + +func NewRSAPrivateKey(currentTime time.Time, priv *rsa.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewRSAPublicKey(currentTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + +func NewDSAPrivateKey(currentTime time.Time, priv *dsa.PrivateKey) *PrivateKey { + pk := new(PrivateKey) + pk.PublicKey = *NewDSAPublicKey(currentTime, &priv.PublicKey) + pk.PrivateKey = priv + return pk +} + +func (pk *PrivateKey) parse(r io.Reader) (err error) { + err = (&pk.PublicKey).parse(r) + if err != nil { + return + } + var buf [1]byte + _, err = readFull(r, buf[:]) + if err != nil { + return + } + + s2kType := buf[0] + + switch s2kType { + case 0: + pk.s2k = nil + pk.Encrypted = false + case 254, 255: + _, err = readFull(r, buf[:]) + if err != nil { + return + } + pk.cipher = CipherFunction(buf[0]) + pk.Encrypted = true + pk.s2k, err = s2k.Parse(r) + if err != nil { + return + } + if s2kType == 254 { + pk.sha1Checksum = true + } + default: + return errors.UnsupportedError("deprecated s2k function in private key") + } + + if pk.Encrypted { + blockSize := pk.cipher.blockSize() + if blockSize == 0 { + return errors.UnsupportedError("unsupported cipher in private key: " + strconv.Itoa(int(pk.cipher))) + } + pk.iv = make([]byte, blockSize) + _, err = readFull(r, pk.iv) + if err != nil { + return + } + } + + pk.encryptedData, err = ioutil.ReadAll(r) + if err != nil { + return + } + + if !pk.Encrypted { + return pk.parsePrivateKey(pk.encryptedData) + } + + return +} + +func mod64kHash(d []byte) uint16 { + var h uint16 + for _, b := range d { + h += uint16(b) + } + return h +} + +func (pk *PrivateKey) Serialize(w io.Writer) (err error) { + // TODO(agl): support encrypted private keys + buf := bytes.NewBuffer(nil) + err = pk.PublicKey.serializeWithoutHeaders(buf) + if err != nil { + return + } + buf.WriteByte(0 /* no encryption */) + + privateKeyBuf := bytes.NewBuffer(nil) + + switch priv := pk.PrivateKey.(type) { + case *rsa.PrivateKey: + err = serializeRSAPrivateKey(privateKeyBuf, priv) + case *dsa.PrivateKey: + err = serializeDSAPrivateKey(privateKeyBuf, priv) + default: + err = errors.InvalidArgumentError("unknown private key type") + } + if err != nil { + return + } + + ptype := packetTypePrivateKey + contents := buf.Bytes() + privateKeyBytes := privateKeyBuf.Bytes() + if pk.IsSubkey { + ptype = packetTypePrivateSubkey + } + err = serializeHeader(w, ptype, len(contents)+len(privateKeyBytes)+2) + if err != nil { + return + } + _, err = w.Write(contents) + if err != nil { + return + } + _, err = w.Write(privateKeyBytes) + if err != nil { + return + } + + checksum := mod64kHash(privateKeyBytes) + var checksumBytes [2]byte + checksumBytes[0] = byte(checksum >> 8) + checksumBytes[1] = byte(checksum) + _, err = w.Write(checksumBytes[:]) + + return +} + +func serializeRSAPrivateKey(w io.Writer, priv *rsa.PrivateKey) error { + err := writeBig(w, priv.D) + if err != nil { + return err + } + err = writeBig(w, priv.Primes[1]) + if err != nil { + return err + } + err = writeBig(w, priv.Primes[0]) + if err != nil { + return err + } + return writeBig(w, priv.Precomputed.Qinv) +} + +func serializeDSAPrivateKey(w io.Writer, priv *dsa.PrivateKey) error { + return writeBig(w, priv.X) +} + +// Decrypt decrypts an encrypted private key using a passphrase. +func (pk *PrivateKey) Decrypt(passphrase []byte) error { + if !pk.Encrypted { + return nil + } + + key := make([]byte, pk.cipher.KeySize()) + pk.s2k(key, passphrase) + block := pk.cipher.new(key) + cfb := cipher.NewCFBDecrypter(block, pk.iv) + + data := make([]byte, len(pk.encryptedData)) + cfb.XORKeyStream(data, pk.encryptedData) + + if pk.sha1Checksum { + if len(data) < sha1.Size { + return errors.StructuralError("truncated private key data") + } + h := sha1.New() + h.Write(data[:len(data)-sha1.Size]) + sum := h.Sum(nil) + if !bytes.Equal(sum, data[len(data)-sha1.Size:]) { + return errors.StructuralError("private key checksum failure") + } + data = data[:len(data)-sha1.Size] + } else { + if len(data) < 2 { + return errors.StructuralError("truncated private key data") + } + var sum uint16 + for i := 0; i < len(data)-2; i++ { + sum += uint16(data[i]) + } + if data[len(data)-2] != uint8(sum>>8) || + data[len(data)-1] != uint8(sum) { + return errors.StructuralError("private key checksum failure") + } + data = data[:len(data)-2] + } + + return pk.parsePrivateKey(data) +} + +func (pk *PrivateKey) parsePrivateKey(data []byte) (err error) { + switch pk.PublicKey.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoRSAEncryptOnly: + return pk.parseRSAPrivateKey(data) + case PubKeyAlgoDSA: + return pk.parseDSAPrivateKey(data) + case PubKeyAlgoElGamal: + return pk.parseElGamalPrivateKey(data) + } + panic("impossible") +} + +func (pk *PrivateKey) parseRSAPrivateKey(data []byte) (err error) { + rsaPub := pk.PublicKey.PublicKey.(*rsa.PublicKey) + rsaPriv := new(rsa.PrivateKey) + rsaPriv.PublicKey = *rsaPub + + buf := bytes.NewBuffer(data) + d, _, err := readMPI(buf) + if err != nil { + return + } + p, _, err := readMPI(buf) + if err != nil { + return + } + q, _, err := readMPI(buf) + if err != nil { + return + } + + rsaPriv.D = new(big.Int).SetBytes(d) + rsaPriv.Primes = make([]*big.Int, 2) + rsaPriv.Primes[0] = new(big.Int).SetBytes(p) + rsaPriv.Primes[1] = new(big.Int).SetBytes(q) + rsaPriv.Precompute() + pk.PrivateKey = rsaPriv + pk.Encrypted = false + pk.encryptedData = nil + + return nil +} + +func (pk *PrivateKey) parseDSAPrivateKey(data []byte) (err error) { + dsaPub := pk.PublicKey.PublicKey.(*dsa.PublicKey) + dsaPriv := new(dsa.PrivateKey) + dsaPriv.PublicKey = *dsaPub + + buf := bytes.NewBuffer(data) + x, _, err := readMPI(buf) + if err != nil { + return + } + + dsaPriv.X = new(big.Int).SetBytes(x) + pk.PrivateKey = dsaPriv + pk.Encrypted = false + pk.encryptedData = nil + + return nil +} + +func (pk *PrivateKey) parseElGamalPrivateKey(data []byte) (err error) { + pub := pk.PublicKey.PublicKey.(*elgamal.PublicKey) + priv := new(elgamal.PrivateKey) + priv.PublicKey = *pub + + buf := bytes.NewBuffer(data) + x, _, err := readMPI(buf) + if err != nil { + return + } + + priv.X = new(big.Int).SetBytes(x) + pk.PrivateKey = priv + pk.Encrypted = false + pk.encryptedData = nil + + return nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key_test.go new file mode 100644 index 0000000..6a6197a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/private_key_test.go @@ -0,0 +1,64 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "testing" + "time" +) + +var privateKeyTests = []struct { + privateKeyHex string + creationTime time.Time +}{ + { + privKeyRSAHex, + time.Unix(0x4cc349a8, 0), + }, + { + privKeyElGamalHex, + time.Unix(0x4df9ee1a, 0), + }, +} + +func TestPrivateKeyRead(t *testing.T) { + for i, test := range privateKeyTests { + packet, err := Read(readerFromHex(test.privateKeyHex)) + if err != nil { + t.Errorf("#%d: failed to parse: %s", i, err) + continue + } + + privKey := packet.(*PrivateKey) + + if !privKey.Encrypted { + t.Errorf("#%d: private key isn't encrypted", i) + continue + } + + err = privKey.Decrypt([]byte("wrong password")) + if err == nil { + t.Errorf("#%d: decrypted with incorrect key", i) + continue + } + + err = privKey.Decrypt([]byte("testing")) + if err != nil { + t.Errorf("#%d: failed to decrypt: %s", i, err) + continue + } + + if !privKey.CreationTime.Equal(test.creationTime) || privKey.Encrypted { + t.Errorf("#%d: bad result, got: %#v", i, privKey) + } + } +} + +// Generated with `gpg --export-secret-keys "Test Key 2"` +const privKeyRSAHex = "9501fe044cc349a8010400b70ca0010e98c090008d45d1ee8f9113bd5861fd57b88bacb7c68658747663f1e1a3b5a98f32fda6472373c024b97359cd2efc88ff60f77751adfbf6af5e615e6a1408cfad8bf0cea30b0d5f53aa27ad59089ba9b15b7ebc2777a25d7b436144027e3bcd203909f147d0e332b240cf63d3395f5dfe0df0a6c04e8655af7eacdf0011010001fe0303024a252e7d475fd445607de39a265472aa74a9320ba2dac395faa687e9e0336aeb7e9a7397e511b5afd9dc84557c80ac0f3d4d7bfec5ae16f20d41c8c84a04552a33870b930420e230e179564f6d19bb153145e76c33ae993886c388832b0fa042ddda7f133924f3854481533e0ede31d51278c0519b29abc3bf53da673e13e3e1214b52413d179d7f66deee35cac8eacb060f78379d70ef4af8607e68131ff529439668fc39c9ce6dfef8a5ac234d234802cbfb749a26107db26406213ae5c06d4673253a3cbee1fcbae58d6ab77e38d6e2c0e7c6317c48e054edadb5a40d0d48acb44643d998139a8a66bb820be1f3f80185bc777d14b5954b60effe2448a036d565c6bc0b915fcea518acdd20ab07bc1529f561c58cd044f723109b93f6fd99f876ff891d64306b5d08f48bab59f38695e9109c4dec34013ba3153488ce070268381ba923ee1eb77125b36afcb4347ec3478c8f2735b06ef17351d872e577fa95d0c397c88c71b59629a36aec" + +// Generated by `gpg --export-secret-keys` followed by a manual extraction of +// the ElGamal subkey from the packets. +const privKeyElGamalHex = "9d0157044df9ee1a100400eb8e136a58ec39b582629cdadf830bc64e0a94ed8103ca8bb247b27b11b46d1d25297ef4bcc3071785ba0c0bedfe89eabc5287fcc0edf81ab5896c1c8e4b20d27d79813c7aede75320b33eaeeaa586edc00fd1036c10133e6ba0ff277245d0d59d04b2b3421b7244aca5f4a8d870c6f1c1fbff9e1c26699a860b9504f35ca1d700030503fd1ededd3b840795be6d9ccbe3c51ee42e2f39233c432b831ddd9c4e72b7025a819317e47bf94f9ee316d7273b05d5fcf2999c3a681f519b1234bbfa6d359b4752bd9c3f77d6b6456cde152464763414ca130f4e91d91041432f90620fec0e6d6b5116076c2985d5aeaae13be492b9b329efcaf7ee25120159a0a30cd976b42d7afe030302dae7eb80db744d4960c4df930d57e87fe81412eaace9f900e6c839817a614ddb75ba6603b9417c33ea7b6c93967dfa2bcff3fa3c74a5ce2c962db65b03aece14c96cbd0038fc" diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go new file mode 100644 index 0000000..26bd9cc --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go @@ -0,0 +1,687 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/binary" + "fmt" + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" + "hash" + "io" + "math/big" + "strconv" + "time" +) + +var ( + // NIST curve P-256 + oidCurveP256 []byte = []byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07} + // NIST curve P-384 + oidCurveP384 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x22} + // NIST curve P-521 + oidCurveP521 []byte = []byte{0x2B, 0x81, 0x04, 0x00, 0x23} +) + +const maxOIDLength = 8 + +// ecdsaKey stores the algorithm-specific fields for ECDSA keys. +// as defined in RFC 6637, Section 9. +type ecdsaKey struct { + // oid contains the OID byte sequence identifying the elliptic curve used + oid []byte + // p contains the elliptic curve point that represents the public key + p parsedMPI +} + +// parseOID reads the OID for the curve as defined in RFC 6637, Section 9. +func parseOID(r io.Reader) (oid []byte, err error) { + buf := make([]byte, maxOIDLength) + if _, err = readFull(r, buf[:1]); err != nil { + return + } + oidLen := buf[0] + if int(oidLen) > len(buf) { + err = errors.UnsupportedError("invalid oid length: " + strconv.Itoa(int(oidLen))) + return + } + oid = buf[:oidLen] + _, err = readFull(r, oid) + return +} + +func (f *ecdsaKey) parse(r io.Reader) (err error) { + if f.oid, err = parseOID(r); err != nil { + return err + } + f.p.bytes, f.p.bitLength, err = readMPI(r) + return +} + +func (f *ecdsaKey) serialize(w io.Writer) (err error) { + buf := make([]byte, maxOIDLength+1) + buf[0] = byte(len(f.oid)) + copy(buf[1:], f.oid) + if _, err = w.Write(buf[:len(f.oid)+1]); err != nil { + return + } + return writeMPIs(w, f.p) +} + +func (f *ecdsaKey) newECDSA() (*ecdsa.PublicKey, error) { + var c elliptic.Curve + if bytes.Equal(f.oid, oidCurveP256) { + c = elliptic.P256() + } else if bytes.Equal(f.oid, oidCurveP384) { + c = elliptic.P384() + } else if bytes.Equal(f.oid, oidCurveP521) { + c = elliptic.P521() + } else { + return nil, errors.UnsupportedError(fmt.Sprintf("unsupported oid: %x", f.oid)) + } + x, y := elliptic.Unmarshal(c, f.p.bytes) + if x == nil { + return nil, errors.UnsupportedError("failed to parse EC point") + } + return &ecdsa.PublicKey{Curve: c, X: x, Y: y}, nil +} + +func (f *ecdsaKey) byteLen() int { + return 1 + len(f.oid) + 2 + len(f.p.bytes) +} + +type kdfHashFunction byte +type kdfAlgorithm byte + +// ecdhKdf stores key derivation function parameters +// used for ECDH encryption. See RFC 6637, Section 9. +type ecdhKdf struct { + KdfHash kdfHashFunction + KdfAlgo kdfAlgorithm +} + +func (f *ecdhKdf) parse(r io.Reader) (err error) { + buf := make([]byte, 1) + if _, err = readFull(r, buf); err != nil { + return + } + kdfLen := int(buf[0]) + if kdfLen < 3 { + return errors.UnsupportedError("Unsupported ECDH KDF length: " + strconv.Itoa(kdfLen)) + } + buf = make([]byte, kdfLen) + if _, err = readFull(r, buf); err != nil { + return + } + reserved := int(buf[0]) + f.KdfHash = kdfHashFunction(buf[1]) + f.KdfAlgo = kdfAlgorithm(buf[2]) + if reserved != 0x01 { + return errors.UnsupportedError("Unsupported KDF reserved field: " + strconv.Itoa(reserved)) + } + return +} + +func (f *ecdhKdf) serialize(w io.Writer) (err error) { + buf := make([]byte, 4) + // See RFC 6637, Section 9, Algorithm-Specific Fields for ECDH keys. + buf[0] = byte(0x03) // Length of the following fields + buf[1] = byte(0x01) // Reserved for future extensions, must be 1 for now + buf[2] = byte(f.KdfHash) + buf[3] = byte(f.KdfAlgo) + _, err = w.Write(buf[:]) + return +} + +func (f *ecdhKdf) byteLen() int { + return 4 +} + +// PublicKey represents an OpenPGP public key. See RFC 4880, section 5.5.2. +type PublicKey struct { + CreationTime time.Time + PubKeyAlgo PublicKeyAlgorithm + PublicKey interface{} // *rsa.PublicKey, *dsa.PublicKey or *ecdsa.PublicKey + Fingerprint [20]byte + KeyId uint64 + IsSubkey bool + + n, e, p, q, g, y parsedMPI + + // RFC 6637 fields + ec *ecdsaKey + ecdh *ecdhKdf +} + +// signingKey provides a convenient abstraction over signature verification +// for v3 and v4 public keys. +type signingKey interface { + SerializeSignaturePrefix(io.Writer) + serializeWithoutHeaders(io.Writer) error +} + +func fromBig(n *big.Int) parsedMPI { + return parsedMPI{ + bytes: n.Bytes(), + bitLength: uint16(n.BitLen()), + } +} + +// NewRSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey. +func NewRSAPublicKey(creationTime time.Time, pub *rsa.PublicKey) *PublicKey { + pk := &PublicKey{ + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoRSA, + PublicKey: pub, + n: fromBig(pub.N), + e: fromBig(big.NewInt(int64(pub.E))), + } + + pk.setFingerPrintAndKeyId() + return pk +} + +// NewDSAPublicKey returns a PublicKey that wraps the given rsa.PublicKey. +func NewDSAPublicKey(creationTime time.Time, pub *dsa.PublicKey) *PublicKey { + pk := &PublicKey{ + CreationTime: creationTime, + PubKeyAlgo: PubKeyAlgoDSA, + PublicKey: pub, + p: fromBig(pub.P), + q: fromBig(pub.Q), + g: fromBig(pub.G), + y: fromBig(pub.Y), + } + + pk.setFingerPrintAndKeyId() + return pk +} + +func (pk *PublicKey) parse(r io.Reader) (err error) { + // RFC 4880, section 5.5.2 + var buf [6]byte + _, err = readFull(r, buf[:]) + if err != nil { + return + } + if buf[0] != 4 { + return errors.UnsupportedError("public key version") + } + pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) + pk.PubKeyAlgo = PublicKeyAlgorithm(buf[5]) + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + err = pk.parseRSA(r) + case PubKeyAlgoDSA: + err = pk.parseDSA(r) + case PubKeyAlgoElGamal: + err = pk.parseElGamal(r) + case PubKeyAlgoECDSA: + pk.ec = new(ecdsaKey) + if err = pk.ec.parse(r); err != nil { + return err + } + pk.PublicKey, err = pk.ec.newECDSA() + case PubKeyAlgoECDH: + pk.ec = new(ecdsaKey) + if err = pk.ec.parse(r); err != nil { + return + } + pk.ecdh = new(ecdhKdf) + if err = pk.ecdh.parse(r); err != nil { + return + } + // The ECDH key is stored in an ecdsa.PublicKey for convenience. + pk.PublicKey, err = pk.ec.newECDSA() + default: + err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) + } + if err != nil { + return + } + + pk.setFingerPrintAndKeyId() + return +} + +func (pk *PublicKey) setFingerPrintAndKeyId() { + // RFC 4880, section 12.2 + fingerPrint := sha1.New() + pk.SerializeSignaturePrefix(fingerPrint) + pk.serializeWithoutHeaders(fingerPrint) + copy(pk.Fingerprint[:], fingerPrint.Sum(nil)) + pk.KeyId = binary.BigEndian.Uint64(pk.Fingerprint[12:20]) +} + +// parseRSA parses RSA public key material from the given Reader. See RFC 4880, +// section 5.5.2. +func (pk *PublicKey) parseRSA(r io.Reader) (err error) { + pk.n.bytes, pk.n.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.e.bytes, pk.e.bitLength, err = readMPI(r) + if err != nil { + return + } + + if len(pk.e.bytes) > 3 { + err = errors.UnsupportedError("large public exponent") + return + } + rsa := &rsa.PublicKey{ + N: new(big.Int).SetBytes(pk.n.bytes), + E: 0, + } + for i := 0; i < len(pk.e.bytes); i++ { + rsa.E <<= 8 + rsa.E |= int(pk.e.bytes[i]) + } + pk.PublicKey = rsa + return +} + +// parseDSA parses DSA public key material from the given Reader. See RFC 4880, +// section 5.5.2. +func (pk *PublicKey) parseDSA(r io.Reader) (err error) { + pk.p.bytes, pk.p.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.q.bytes, pk.q.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.g.bytes, pk.g.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.y.bytes, pk.y.bitLength, err = readMPI(r) + if err != nil { + return + } + + dsa := new(dsa.PublicKey) + dsa.P = new(big.Int).SetBytes(pk.p.bytes) + dsa.Q = new(big.Int).SetBytes(pk.q.bytes) + dsa.G = new(big.Int).SetBytes(pk.g.bytes) + dsa.Y = new(big.Int).SetBytes(pk.y.bytes) + pk.PublicKey = dsa + return +} + +// parseElGamal parses ElGamal public key material from the given Reader. See +// RFC 4880, section 5.5.2. +func (pk *PublicKey) parseElGamal(r io.Reader) (err error) { + pk.p.bytes, pk.p.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.g.bytes, pk.g.bitLength, err = readMPI(r) + if err != nil { + return + } + pk.y.bytes, pk.y.bitLength, err = readMPI(r) + if err != nil { + return + } + + elgamal := new(elgamal.PublicKey) + elgamal.P = new(big.Int).SetBytes(pk.p.bytes) + elgamal.G = new(big.Int).SetBytes(pk.g.bytes) + elgamal.Y = new(big.Int).SetBytes(pk.y.bytes) + pk.PublicKey = elgamal + return +} + +// SerializeSignaturePrefix writes the prefix for this public key to the given Writer. +// The prefix is used when calculating a signature over this public key. See +// RFC 4880, section 5.2.4. +func (pk *PublicKey) SerializeSignaturePrefix(h io.Writer) { + var pLength uint16 + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + pLength += 2 + uint16(len(pk.n.bytes)) + pLength += 2 + uint16(len(pk.e.bytes)) + case PubKeyAlgoDSA: + pLength += 2 + uint16(len(pk.p.bytes)) + pLength += 2 + uint16(len(pk.q.bytes)) + pLength += 2 + uint16(len(pk.g.bytes)) + pLength += 2 + uint16(len(pk.y.bytes)) + case PubKeyAlgoElGamal: + pLength += 2 + uint16(len(pk.p.bytes)) + pLength += 2 + uint16(len(pk.g.bytes)) + pLength += 2 + uint16(len(pk.y.bytes)) + case PubKeyAlgoECDSA: + pLength += uint16(pk.ec.byteLen()) + case PubKeyAlgoECDH: + pLength += uint16(pk.ec.byteLen()) + pLength += uint16(pk.ecdh.byteLen()) + default: + panic("unknown public key algorithm") + } + pLength += 6 + h.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}) + return +} + +func (pk *PublicKey) Serialize(w io.Writer) (err error) { + length := 6 // 6 byte header + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + length += 2 + len(pk.n.bytes) + length += 2 + len(pk.e.bytes) + case PubKeyAlgoDSA: + length += 2 + len(pk.p.bytes) + length += 2 + len(pk.q.bytes) + length += 2 + len(pk.g.bytes) + length += 2 + len(pk.y.bytes) + case PubKeyAlgoElGamal: + length += 2 + len(pk.p.bytes) + length += 2 + len(pk.g.bytes) + length += 2 + len(pk.y.bytes) + case PubKeyAlgoECDSA: + length += pk.ec.byteLen() + case PubKeyAlgoECDH: + length += pk.ec.byteLen() + length += pk.ecdh.byteLen() + default: + panic("unknown public key algorithm") + } + + packetType := packetTypePublicKey + if pk.IsSubkey { + packetType = packetTypePublicSubkey + } + err = serializeHeader(w, packetType, length) + if err != nil { + return + } + return pk.serializeWithoutHeaders(w) +} + +// serializeWithoutHeaders marshals the PublicKey to w in the form of an +// OpenPGP public key packet, not including the packet header. +func (pk *PublicKey) serializeWithoutHeaders(w io.Writer) (err error) { + var buf [6]byte + buf[0] = 4 + t := uint32(pk.CreationTime.Unix()) + buf[1] = byte(t >> 24) + buf[2] = byte(t >> 16) + buf[3] = byte(t >> 8) + buf[4] = byte(t) + buf[5] = byte(pk.PubKeyAlgo) + + _, err = w.Write(buf[:]) + if err != nil { + return + } + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + return writeMPIs(w, pk.n, pk.e) + case PubKeyAlgoDSA: + return writeMPIs(w, pk.p, pk.q, pk.g, pk.y) + case PubKeyAlgoElGamal: + return writeMPIs(w, pk.p, pk.g, pk.y) + case PubKeyAlgoECDSA: + return pk.ec.serialize(w) + case PubKeyAlgoECDH: + if err = pk.ec.serialize(w); err != nil { + return + } + return pk.ecdh.serialize(w) + } + return errors.InvalidArgumentError("bad public-key algorithm") +} + +// CanSign returns true iff this public key can generate signatures +func (pk *PublicKey) CanSign() bool { + return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly && pk.PubKeyAlgo != PubKeyAlgoElGamal +} + +// VerifySignature returns nil iff sig is a valid signature, made by this +// public key, of the data hashed into signed. signed is mutated by this call. +func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err error) { + if !pk.CanSign() { + return errors.InvalidArgumentError("public key cannot generate signatures") + } + + signed.Write(sig.HashSuffix) + hashBytes := signed.Sum(nil) + + if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { + return errors.SignatureError("hash tag doesn't match") + } + + if pk.PubKeyAlgo != sig.PubKeyAlgo { + return errors.InvalidArgumentError("public key and signature use different algorithms") + } + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + rsaPublicKey, _ := pk.PublicKey.(*rsa.PublicKey) + err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes) + if err != nil { + return errors.SignatureError("RSA verification failure") + } + return nil + case PubKeyAlgoDSA: + dsaPublicKey, _ := pk.PublicKey.(*dsa.PublicKey) + // Need to truncate hashBytes to match FIPS 186-3 section 4.6. + subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8 + if len(hashBytes) > subgroupSize { + hashBytes = hashBytes[:subgroupSize] + } + if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) { + return errors.SignatureError("DSA verification failure") + } + return nil + case PubKeyAlgoECDSA: + ecdsaPublicKey := pk.PublicKey.(*ecdsa.PublicKey) + if !ecdsa.Verify(ecdsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.ECDSASigR.bytes), new(big.Int).SetBytes(sig.ECDSASigS.bytes)) { + return errors.SignatureError("ECDSA verification failure") + } + return nil + default: + return errors.SignatureError("Unsupported public key algorithm used in signature") + } + panic("unreachable") +} + +// VerifySignatureV3 returns nil iff sig is a valid signature, made by this +// public key, of the data hashed into signed. signed is mutated by this call. +func (pk *PublicKey) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) { + if !pk.CanSign() { + return errors.InvalidArgumentError("public key cannot generate signatures") + } + + suffix := make([]byte, 5) + suffix[0] = byte(sig.SigType) + binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix())) + signed.Write(suffix) + hashBytes := signed.Sum(nil) + + if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { + return errors.SignatureError("hash tag doesn't match") + } + + if pk.PubKeyAlgo != sig.PubKeyAlgo { + return errors.InvalidArgumentError("public key and signature use different algorithms") + } + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + rsaPublicKey := pk.PublicKey.(*rsa.PublicKey) + if err = rsa.VerifyPKCS1v15(rsaPublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil { + return errors.SignatureError("RSA verification failure") + } + return + case PubKeyAlgoDSA: + dsaPublicKey := pk.PublicKey.(*dsa.PublicKey) + // Need to truncate hashBytes to match FIPS 186-3 section 4.6. + subgroupSize := (dsaPublicKey.Q.BitLen() + 7) / 8 + if len(hashBytes) > subgroupSize { + hashBytes = hashBytes[:subgroupSize] + } + if !dsa.Verify(dsaPublicKey, hashBytes, new(big.Int).SetBytes(sig.DSASigR.bytes), new(big.Int).SetBytes(sig.DSASigS.bytes)) { + return errors.SignatureError("DSA verification failure") + } + return nil + default: + panic("shouldn't happen") + } + panic("unreachable") +} + +// keySignatureHash returns a Hash of the message that needs to be signed for +// pk to assert a subkey relationship to signed. +func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { + if !hashFunc.Available() { + return nil, errors.UnsupportedError("hash function") + } + h = hashFunc.New() + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + signed.SerializeSignaturePrefix(h) + signed.serializeWithoutHeaders(h) + return +} + +// VerifyKeySignature returns nil iff sig is a valid signature, made by this +// public key, of signed. +func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err error) { + h, err := keySignatureHash(pk, signed, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignature(h, sig) +} + +func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { + if !hashFunc.Available() { + return nil, errors.UnsupportedError("hash function") + } + h = hashFunc.New() + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + + return +} + +// VerifyRevocationSignature returns nil iff sig is a valid signature, made by this +// public key. +func (pk *PublicKey) VerifyRevocationSignature(sig *Signature) (err error) { + h, err := keyRevocationHash(pk, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignature(h, sig) +} + +// userIdSignatureHash returns a Hash of the message that needs to be signed +// to assert that pk is a valid key for id. +func userIdSignatureHash(id string, pk *PublicKey, hashFunc crypto.Hash) (h hash.Hash, err error) { + if !hashFunc.Available() { + return nil, errors.UnsupportedError("hash function") + } + h = hashFunc.New() + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + + var buf [5]byte + buf[0] = 0xb4 + buf[1] = byte(len(id) >> 24) + buf[2] = byte(len(id) >> 16) + buf[3] = byte(len(id) >> 8) + buf[4] = byte(len(id)) + h.Write(buf[:]) + h.Write([]byte(id)) + + return +} + +// VerifyUserIdSignature returns nil iff sig is a valid signature, made by this +// public key, that id is the identity of pub. +func (pk *PublicKey) VerifyUserIdSignature(id string, pub *PublicKey, sig *Signature) (err error) { + h, err := userIdSignatureHash(id, pub, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignature(h, sig) +} + +// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this +// public key, that id is the identity of pub. +func (pk *PublicKey) VerifyUserIdSignatureV3(id string, pub *PublicKey, sig *SignatureV3) (err error) { + h, err := userIdSignatureV3Hash(id, pub, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignatureV3(h, sig) +} + +// KeyIdString returns the public key's fingerprint in capital hex +// (e.g. "6C7EE1B8621CC013"). +func (pk *PublicKey) KeyIdString() string { + return fmt.Sprintf("%X", pk.Fingerprint[12:20]) +} + +// KeyIdShortString returns the short form of public key's fingerprint +// in capital hex, as shown by gpg --list-keys (e.g. "621CC013"). +func (pk *PublicKey) KeyIdShortString() string { + return fmt.Sprintf("%X", pk.Fingerprint[16:20]) +} + +// A parsedMPI is used to store the contents of a big integer, along with the +// bit length that was specified in the original input. This allows the MPI to +// be reserialized exactly. +type parsedMPI struct { + bytes []byte + bitLength uint16 +} + +// writeMPIs is a utility function for serializing several big integers to the +// given Writer. +func writeMPIs(w io.Writer, mpis ...parsedMPI) (err error) { + for _, mpi := range mpis { + err = writeMPI(w, mpi.bitLength, mpi.bytes) + if err != nil { + return + } + } + return +} + +// BitLength returns the bit length for the given public key. +func (pk *PublicKey) BitLength() (bitLength uint16, err error) { + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + bitLength = pk.n.bitLength + case PubKeyAlgoDSA: + bitLength = pk.p.bitLength + case PubKeyAlgoElGamal: + bitLength = pk.p.bitLength + default: + err = errors.InvalidArgumentError("bad public-key algorithm") + } + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_test.go new file mode 100644 index 0000000..7ad7d91 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_test.go @@ -0,0 +1,202 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "testing" + "time" +) + +var pubKeyTests = []struct { + hexData string + hexFingerprint string + creationTime time.Time + pubKeyAlgo PublicKeyAlgorithm + keyId uint64 + keyIdString string + keyIdShort string +}{ + {rsaPkDataHex, rsaFingerprintHex, time.Unix(0x4d3c5c10, 0), PubKeyAlgoRSA, 0xa34d7e18c20c31bb, "A34D7E18C20C31BB", "C20C31BB"}, + {dsaPkDataHex, dsaFingerprintHex, time.Unix(0x4d432f89, 0), PubKeyAlgoDSA, 0x8e8fbe54062f19ed, "8E8FBE54062F19ED", "062F19ED"}, + {ecdsaPkDataHex, ecdsaFingerprintHex, time.Unix(0x5071c294, 0), PubKeyAlgoECDSA, 0x43fe956c542ca00b, "43FE956C542CA00B", "542CA00B"}, +} + +func TestPublicKeyRead(t *testing.T) { + for i, test := range pubKeyTests { + packet, err := Read(readerFromHex(test.hexData)) + if err != nil { + t.Errorf("#%d: Read error: %s", i, err) + continue + } + pk, ok := packet.(*PublicKey) + if !ok { + t.Errorf("#%d: failed to parse, got: %#v", i, packet) + continue + } + if pk.PubKeyAlgo != test.pubKeyAlgo { + t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo) + } + if !pk.CreationTime.Equal(test.creationTime) { + t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime) + } + expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint) + if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) { + t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint) + } + if pk.KeyId != test.keyId { + t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId) + } + if g, e := pk.KeyIdString(), test.keyIdString; g != e { + t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e) + } + if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e { + t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e) + } + } +} + +func TestPublicKeySerialize(t *testing.T) { + for i, test := range pubKeyTests { + packet, err := Read(readerFromHex(test.hexData)) + if err != nil { + t.Errorf("#%d: Read error: %s", i, err) + continue + } + pk, ok := packet.(*PublicKey) + if !ok { + t.Errorf("#%d: failed to parse, got: %#v", i, packet) + continue + } + serializeBuf := bytes.NewBuffer(nil) + err = pk.Serialize(serializeBuf) + if err != nil { + t.Errorf("#%d: failed to serialize: %s", i, err) + continue + } + + packet, err = Read(serializeBuf) + if err != nil { + t.Errorf("#%d: Read error (from serialized data): %s", i, err) + continue + } + pk, ok = packet.(*PublicKey) + if !ok { + t.Errorf("#%d: failed to parse serialized data, got: %#v", i, packet) + continue + } + } +} + +func TestEcc384Serialize(t *testing.T) { + r := readerFromHex(ecc384PubHex) + var w bytes.Buffer + for i := 0; i < 2; i++ { + // Public key + p, err := Read(r) + if err != nil { + t.Error(err) + } + pubkey := p.(*PublicKey) + if !bytes.Equal(pubkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) { + t.Errorf("Unexpected pubkey OID: %x", pubkey.ec.oid) + } + if !bytes.Equal(pubkey.ec.p.bytes[:5], []byte{0x04, 0xf6, 0xb8, 0xc5, 0xac}) { + t.Errorf("Unexpected pubkey P[:5]: %x", pubkey.ec.p.bytes) + } + if pubkey.KeyId != 0x098033880F54719F { + t.Errorf("Unexpected pubkey ID: %x", pubkey.KeyId) + } + err = pubkey.Serialize(&w) + if err != nil { + t.Error(err) + } + // User ID + p, err = Read(r) + if err != nil { + t.Error(err) + } + uid := p.(*UserId) + if uid.Id != "ec_dsa_dh_384 " { + t.Error("Unexpected UID:", uid.Id) + } + err = uid.Serialize(&w) + if err != nil { + t.Error(err) + } + // User ID Sig + p, err = Read(r) + if err != nil { + t.Error(err) + } + uidSig := p.(*Signature) + err = pubkey.VerifyUserIdSignature(uid.Id, pubkey, uidSig) + if err != nil { + t.Error(err, ": UID") + } + err = uidSig.Serialize(&w) + if err != nil { + t.Error(err) + } + // Subkey + p, err = Read(r) + if err != nil { + t.Error(err) + } + subkey := p.(*PublicKey) + if !bytes.Equal(subkey.ec.oid, []byte{0x2b, 0x81, 0x04, 0x00, 0x22}) { + t.Errorf("Unexpected subkey OID: %x", subkey.ec.oid) + } + if !bytes.Equal(subkey.ec.p.bytes[:5], []byte{0x04, 0x2f, 0xaa, 0x84, 0x02}) { + t.Errorf("Unexpected subkey P[:5]: %x", subkey.ec.p.bytes) + } + if subkey.ecdh.KdfHash != 0x09 { + t.Error("Expected KDF hash function SHA384 (0x09), got", subkey.ecdh.KdfHash) + } + if subkey.ecdh.KdfAlgo != 0x09 { + t.Error("Expected KDF symmetric alg AES256 (0x09), got", subkey.ecdh.KdfAlgo) + } + if subkey.KeyId != 0xAA8B938F9A201946 { + t.Errorf("Unexpected subkey ID: %x", subkey.KeyId) + } + err = subkey.Serialize(&w) + if err != nil { + t.Error(err) + } + // Subkey Sig + p, err = Read(r) + if err != nil { + t.Error(err) + } + subkeySig := p.(*Signature) + err = pubkey.VerifyKeySignature(subkey, subkeySig) + if err != nil { + t.Error(err) + } + err = subkeySig.Serialize(&w) + if err != nil { + t.Error(err) + } + // Now read back what we've written again + r = bytes.NewBuffer(w.Bytes()) + w.Reset() + } +} + +const rsaFingerprintHex = "5fb74b1d03b1e3cb31bc2f8aa34d7e18c20c31bb" + +const rsaPkDataHex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001" + +const dsaFingerprintHex = "eece4c094db002103714c63c8e8fbe54062f19ed" + +const dsaPkDataHex = "9901a2044d432f89110400cd581334f0d7a1e1bdc8b9d6d8c0baf68793632735d2bb0903224cbaa1dfbf35a60ee7a13b92643421e1eb41aa8d79bea19a115a677f6b8ba3c7818ce53a6c2a24a1608bd8b8d6e55c5090cbde09dd26e356267465ae25e69ec8bdd57c7bbb2623e4d73336f73a0a9098f7f16da2e25252130fd694c0e8070c55a812a423ae7f00a0ebf50e70c2f19c3520a551bd4b08d30f23530d3d03ff7d0bf4a53a64a09dc5e6e6e35854b7d70c882b0c60293401958b1bd9e40abec3ea05ba87cf64899299d4bd6aa7f459c201d3fbbd6c82004bdc5e8a9eb8082d12054cc90fa9d4ec251a843236a588bf49552441817436c4f43326966fe85447d4e6d0acf8fa1ef0f014730770603ad7634c3088dc52501c237328417c31c89ed70400b2f1a98b0bf42f11fefc430704bebbaa41d9f355600c3facee1e490f64208e0e094ea55e3a598a219a58500bf78ac677b670a14f4e47e9cf8eab4f368cc1ddcaa18cc59309d4cc62dd4f680e73e6cc3e1ce87a84d0925efbcb26c575c093fc42eecf45135fabf6403a25c2016e1774c0484e440a18319072c617cc97ac0a3bb0" + +const ecdsaFingerprintHex = "9892270b38b8980b05c8d56d43fe956c542ca00b" + +const ecdsaPkDataHex = "9893045071c29413052b8104002304230401f4867769cedfa52c325018896245443968e52e51d0c2df8d939949cb5b330f2921711fbee1c9b9dddb95d15cb0255e99badeddda7cc23d9ddcaacbc290969b9f24019375d61c2e4e3b36953a28d8b2bc95f78c3f1d592fb24499be348656a7b17e3963187b4361afe497bc5f9f81213f04069f8e1fb9e6a6290ae295ca1a92b894396cb4" + +// Source: https://sites.google.com/site/brainhub/pgpecckeys#TOC-ECC-NIST-P-384-key +const ecc384PubHex = `99006f044d53059213052b81040022030304f6b8c5aced5b84ef9f4a209db2e4a9dfb70d28cb8c10ecd57674a9fa5a67389942b62d5e51367df4c7bfd3f8e500feecf07ed265a621a8ebbbe53e947ec78c677eba143bd1533c2b350e1c29f82313e1e1108eba063be1e64b10e6950e799c2db42465635f6473615f64685f333834203c6f70656e70677040627261696e6875622e6f72673e8900cb04101309005305024d530592301480000000002000077072656665727265642d656d61696c2d656e636f64696e67407067702e636f6d7067706d696d65040b090807021901051b03000000021602051e010000000415090a08000a0910098033880f54719fca2b0180aa37350968bd5f115afd8ce7bc7b103822152dbff06d0afcda835329510905b98cb469ba208faab87c7412b799e7b633017f58364ea480e8a1a3f253a0c5f22c446e8be9a9fce6210136ee30811abbd49139de28b5bdf8dc36d06ae748579e9ff503b90073044d53059212052b810400220303042faa84024a20b6735c4897efa5bfb41bf85b7eefeab5ca0cb9ffc8ea04a46acb25534a577694f9e25340a4ab5223a9dd1eda530c8aa2e6718db10d7e672558c7736fe09369ea5739a2a3554bf16d41faa50562f11c6d39bbd5dffb6b9a9ec9180301090989008404181309000c05024d530592051b0c000000000a0910098033880f54719f80970180eee7a6d8fcee41ee4f9289df17f9bcf9d955dca25c583b94336f3a2b2d4986dc5cf417b8d2dc86f741a9e1a6d236c0e3017d1c76575458a0cfb93ae8a2b274fcc65ceecd7a91eec83656ba13219969f06945b48c56bd04152c3a0553c5f2f4bd1267` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go new file mode 100644 index 0000000..1641248 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3.go @@ -0,0 +1,275 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto" + "crypto/md5" + "crypto/rsa" + "encoding/binary" + "fmt" + "hash" + "io" + "math/big" + "strconv" + "time" + + "golang.org/x/crypto/openpgp/errors" +) + +// PublicKeyV3 represents older, version 3 public keys. These keys are less secure and +// should not be used for signing or encrypting. They are supported here only for +// parsing version 3 key material and validating signatures. +// See RFC 4880, section 5.5.2. +type PublicKeyV3 struct { + CreationTime time.Time + DaysToExpire uint16 + PubKeyAlgo PublicKeyAlgorithm + PublicKey *rsa.PublicKey + Fingerprint [16]byte + KeyId uint64 + IsSubkey bool + + n, e parsedMPI +} + +// newRSAPublicKeyV3 returns a PublicKey that wraps the given rsa.PublicKey. +// Included here for testing purposes only. RFC 4880, section 5.5.2: +// "an implementation MUST NOT generate a V3 key, but MAY accept it." +func newRSAPublicKeyV3(creationTime time.Time, pub *rsa.PublicKey) *PublicKeyV3 { + pk := &PublicKeyV3{ + CreationTime: creationTime, + PublicKey: pub, + n: fromBig(pub.N), + e: fromBig(big.NewInt(int64(pub.E))), + } + + pk.setFingerPrintAndKeyId() + return pk +} + +func (pk *PublicKeyV3) parse(r io.Reader) (err error) { + // RFC 4880, section 5.5.2 + var buf [8]byte + if _, err = readFull(r, buf[:]); err != nil { + return + } + if buf[0] < 2 || buf[0] > 3 { + return errors.UnsupportedError("public key version") + } + pk.CreationTime = time.Unix(int64(uint32(buf[1])<<24|uint32(buf[2])<<16|uint32(buf[3])<<8|uint32(buf[4])), 0) + pk.DaysToExpire = binary.BigEndian.Uint16(buf[5:7]) + pk.PubKeyAlgo = PublicKeyAlgorithm(buf[7]) + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + err = pk.parseRSA(r) + default: + err = errors.UnsupportedError("public key type: " + strconv.Itoa(int(pk.PubKeyAlgo))) + } + if err != nil { + return + } + + pk.setFingerPrintAndKeyId() + return +} + +func (pk *PublicKeyV3) setFingerPrintAndKeyId() { + // RFC 4880, section 12.2 + fingerPrint := md5.New() + fingerPrint.Write(pk.n.bytes) + fingerPrint.Write(pk.e.bytes) + fingerPrint.Sum(pk.Fingerprint[:0]) + pk.KeyId = binary.BigEndian.Uint64(pk.n.bytes[len(pk.n.bytes)-8:]) +} + +// parseRSA parses RSA public key material from the given Reader. See RFC 4880, +// section 5.5.2. +func (pk *PublicKeyV3) parseRSA(r io.Reader) (err error) { + if pk.n.bytes, pk.n.bitLength, err = readMPI(r); err != nil { + return + } + if pk.e.bytes, pk.e.bitLength, err = readMPI(r); err != nil { + return + } + + if len(pk.e.bytes) > 3 { + err = errors.UnsupportedError("large public exponent") + return + } + rsa := &rsa.PublicKey{N: new(big.Int).SetBytes(pk.n.bytes)} + for i := 0; i < len(pk.e.bytes); i++ { + rsa.E <<= 8 + rsa.E |= int(pk.e.bytes[i]) + } + pk.PublicKey = rsa + return +} + +// SerializeSignaturePrefix writes the prefix for this public key to the given Writer. +// The prefix is used when calculating a signature over this public key. See +// RFC 4880, section 5.2.4. +func (pk *PublicKeyV3) SerializeSignaturePrefix(w io.Writer) { + var pLength uint16 + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + pLength += 2 + uint16(len(pk.n.bytes)) + pLength += 2 + uint16(len(pk.e.bytes)) + default: + panic("unknown public key algorithm") + } + pLength += 6 + w.Write([]byte{0x99, byte(pLength >> 8), byte(pLength)}) + return +} + +func (pk *PublicKeyV3) Serialize(w io.Writer) (err error) { + length := 8 // 8 byte header + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + length += 2 + len(pk.n.bytes) + length += 2 + len(pk.e.bytes) + default: + panic("unknown public key algorithm") + } + + packetType := packetTypePublicKey + if pk.IsSubkey { + packetType = packetTypePublicSubkey + } + if err = serializeHeader(w, packetType, length); err != nil { + return + } + return pk.serializeWithoutHeaders(w) +} + +// serializeWithoutHeaders marshals the PublicKey to w in the form of an +// OpenPGP public key packet, not including the packet header. +func (pk *PublicKeyV3) serializeWithoutHeaders(w io.Writer) (err error) { + var buf [8]byte + // Version 3 + buf[0] = 3 + // Creation time + t := uint32(pk.CreationTime.Unix()) + buf[1] = byte(t >> 24) + buf[2] = byte(t >> 16) + buf[3] = byte(t >> 8) + buf[4] = byte(t) + // Days to expire + buf[5] = byte(pk.DaysToExpire >> 8) + buf[6] = byte(pk.DaysToExpire) + // Public key algorithm + buf[7] = byte(pk.PubKeyAlgo) + + if _, err = w.Write(buf[:]); err != nil { + return + } + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + return writeMPIs(w, pk.n, pk.e) + } + return errors.InvalidArgumentError("bad public-key algorithm") +} + +// CanSign returns true iff this public key can generate signatures +func (pk *PublicKeyV3) CanSign() bool { + return pk.PubKeyAlgo != PubKeyAlgoRSAEncryptOnly +} + +// VerifySignatureV3 returns nil iff sig is a valid signature, made by this +// public key, of the data hashed into signed. signed is mutated by this call. +func (pk *PublicKeyV3) VerifySignatureV3(signed hash.Hash, sig *SignatureV3) (err error) { + if !pk.CanSign() { + return errors.InvalidArgumentError("public key cannot generate signatures") + } + + suffix := make([]byte, 5) + suffix[0] = byte(sig.SigType) + binary.BigEndian.PutUint32(suffix[1:], uint32(sig.CreationTime.Unix())) + signed.Write(suffix) + hashBytes := signed.Sum(nil) + + if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { + return errors.SignatureError("hash tag doesn't match") + } + + if pk.PubKeyAlgo != sig.PubKeyAlgo { + return errors.InvalidArgumentError("public key and signature use different algorithms") + } + + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + if err = rsa.VerifyPKCS1v15(pk.PublicKey, sig.Hash, hashBytes, sig.RSASignature.bytes); err != nil { + return errors.SignatureError("RSA verification failure") + } + return + default: + // V3 public keys only support RSA. + panic("shouldn't happen") + } + panic("unreachable") +} + +// VerifyUserIdSignatureV3 returns nil iff sig is a valid signature, made by this +// public key, that id is the identity of pub. +func (pk *PublicKeyV3) VerifyUserIdSignatureV3(id string, pub *PublicKeyV3, sig *SignatureV3) (err error) { + h, err := userIdSignatureV3Hash(id, pk, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignatureV3(h, sig) +} + +// VerifyKeySignatureV3 returns nil iff sig is a valid signature, made by this +// public key, of signed. +func (pk *PublicKeyV3) VerifyKeySignatureV3(signed *PublicKeyV3, sig *SignatureV3) (err error) { + h, err := keySignatureHash(pk, signed, sig.Hash) + if err != nil { + return err + } + return pk.VerifySignatureV3(h, sig) +} + +// userIdSignatureV3Hash returns a Hash of the message that needs to be signed +// to assert that pk is a valid key for id. +func userIdSignatureV3Hash(id string, pk signingKey, hfn crypto.Hash) (h hash.Hash, err error) { + if !hfn.Available() { + return nil, errors.UnsupportedError("hash function") + } + h = hfn.New() + + // RFC 4880, section 5.2.4 + pk.SerializeSignaturePrefix(h) + pk.serializeWithoutHeaders(h) + + h.Write([]byte(id)) + + return +} + +// KeyIdString returns the public key's fingerprint in capital hex +// (e.g. "6C7EE1B8621CC013"). +func (pk *PublicKeyV3) KeyIdString() string { + return fmt.Sprintf("%X", pk.KeyId) +} + +// KeyIdShortString returns the short form of public key's fingerprint +// in capital hex, as shown by gpg --list-keys (e.g. "621CC013"). +func (pk *PublicKeyV3) KeyIdShortString() string { + return fmt.Sprintf("%X", pk.KeyId&0xFFFFFFFF) +} + +// BitLength returns the bit length for the given public key. +func (pk *PublicKeyV3) BitLength() (bitLength uint16, err error) { + switch pk.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly, PubKeyAlgoRSASignOnly: + bitLength = pk.n.bitLength + default: + err = errors.InvalidArgumentError("bad public-key algorithm") + } + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go new file mode 100644 index 0000000..e064059 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key_v3_test.go @@ -0,0 +1,82 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "testing" + "time" +) + +var pubKeyV3Test = struct { + hexFingerprint string + creationTime time.Time + pubKeyAlgo PublicKeyAlgorithm + keyId uint64 + keyIdString string + keyIdShort string +}{ + "103BECF5BD1E837C89D19E98487767F7", + time.Unix(779753634, 0), + PubKeyAlgoRSA, + 0xDE0F188A5DA5E3C9, + "DE0F188A5DA5E3C9", + "5DA5E3C9"} + +func TestPublicKeyV3Read(t *testing.T) { + i, test := 0, pubKeyV3Test + packet, err := Read(v3KeyReader(t)) + if err != nil { + t.Fatalf("#%d: Read error: %s", i, err) + } + pk, ok := packet.(*PublicKeyV3) + if !ok { + t.Fatalf("#%d: failed to parse, got: %#v", i, packet) + } + if pk.PubKeyAlgo != test.pubKeyAlgo { + t.Errorf("#%d: bad public key algorithm got:%x want:%x", i, pk.PubKeyAlgo, test.pubKeyAlgo) + } + if !pk.CreationTime.Equal(test.creationTime) { + t.Errorf("#%d: bad creation time got:%v want:%v", i, pk.CreationTime, test.creationTime) + } + expectedFingerprint, _ := hex.DecodeString(test.hexFingerprint) + if !bytes.Equal(expectedFingerprint, pk.Fingerprint[:]) { + t.Errorf("#%d: bad fingerprint got:%x want:%x", i, pk.Fingerprint[:], expectedFingerprint) + } + if pk.KeyId != test.keyId { + t.Errorf("#%d: bad keyid got:%x want:%x", i, pk.KeyId, test.keyId) + } + if g, e := pk.KeyIdString(), test.keyIdString; g != e { + t.Errorf("#%d: bad KeyIdString got:%q want:%q", i, g, e) + } + if g, e := pk.KeyIdShortString(), test.keyIdShort; g != e { + t.Errorf("#%d: bad KeyIdShortString got:%q want:%q", i, g, e) + } +} + +func TestPublicKeyV3Serialize(t *testing.T) { + //for i, test := range pubKeyV3Tests { + i := 0 + packet, err := Read(v3KeyReader(t)) + if err != nil { + t.Fatalf("#%d: Read error: %s", i, err) + } + pk, ok := packet.(*PublicKeyV3) + if !ok { + t.Fatalf("#%d: failed to parse, got: %#v", i, packet) + } + var serializeBuf bytes.Buffer + if err = pk.Serialize(&serializeBuf); err != nil { + t.Fatalf("#%d: failed to serialize: %s", i, err) + } + + if packet, err = Read(bytes.NewBuffer(serializeBuf.Bytes())); err != nil { + t.Fatalf("#%d: Read error (from serialized data): %s", i, err) + } + if pk, ok = packet.(*PublicKeyV3); !ok { + t.Fatalf("#%d: failed to parse serialized data, got: %#v", i, packet) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go new file mode 100644 index 0000000..753bf8f --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go @@ -0,0 +1,62 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "golang.org/x/crypto/openpgp/errors" + "io" +) + +// Reader reads packets from an io.Reader and allows packets to be 'unread' so +// that they result from the next call to Next. +type Reader struct { + q []Packet + readers []io.Reader +} + +// Next returns the most recently unread Packet, or reads another packet from +// the top-most io.Reader. Unknown packet types are skipped. +func (r *Reader) Next() (p Packet, err error) { + if len(r.q) > 0 { + p = r.q[len(r.q)-1] + r.q = r.q[:len(r.q)-1] + return + } + + for len(r.readers) > 0 { + p, err = Read(r.readers[len(r.readers)-1]) + if err == nil { + return + } + if err == io.EOF { + r.readers = r.readers[:len(r.readers)-1] + continue + } + if _, ok := err.(errors.UnknownPacketTypeError); !ok { + return nil, err + } + } + + return nil, io.EOF +} + +// Push causes the Reader to start reading from a new io.Reader. When an EOF +// error is seen from the new io.Reader, it is popped and the Reader continues +// to read from the next most recent io.Reader. +func (r *Reader) Push(reader io.Reader) { + r.readers = append(r.readers, reader) +} + +// Unread causes the given Packet to be returned from the next call to Next. +func (r *Reader) Unread(p Packet) { + r.q = append(r.q, p) +} + +func NewReader(r io.Reader) *Reader { + return &Reader{ + q: nil, + readers: []io.Reader{r}, + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go new file mode 100644 index 0000000..79cf993 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go @@ -0,0 +1,663 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto" + "crypto/dsa" + "crypto/rsa" + "encoding/binary" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" + "hash" + "io" + "strconv" + "time" +) + +const ( + // See RFC 4880, section 5.2.3.21 for details. + KeyFlagCertify = 1 << iota + KeyFlagSign + KeyFlagEncryptCommunications + KeyFlagEncryptStorage +) + +// Signature represents a signature. See RFC 4880, section 5.2. +type Signature struct { + SigType SignatureType + PubKeyAlgo PublicKeyAlgorithm + Hash crypto.Hash + + // HashSuffix is extra data that is hashed in after the signed data. + HashSuffix []byte + // HashTag contains the first two bytes of the hash for fast rejection + // of bad signed data. + HashTag [2]byte + CreationTime time.Time + + RSASignature parsedMPI + DSASigR, DSASigS parsedMPI + ECDSASigR, ECDSASigS parsedMPI + + // rawSubpackets contains the unparsed subpackets, in order. + rawSubpackets []outputSubpacket + + // The following are optional so are nil when not included in the + // signature. + + SigLifetimeSecs, KeyLifetimeSecs *uint32 + PreferredSymmetric, PreferredHash, PreferredCompression []uint8 + IssuerKeyId *uint64 + IsPrimaryId *bool + + // FlagsValid is set if any flags were given. See RFC 4880, section + // 5.2.3.21 for details. + FlagsValid bool + FlagCertify, FlagSign, FlagEncryptCommunications, FlagEncryptStorage bool + + // RevocationReason is set if this signature has been revoked. + // See RFC 4880, section 5.2.3.23 for details. + RevocationReason *uint8 + RevocationReasonText string + + outSubpackets []outputSubpacket +} + +func (sig *Signature) parse(r io.Reader) (err error) { + // RFC 4880, section 5.2.3 + var buf [5]byte + _, err = readFull(r, buf[:1]) + if err != nil { + return + } + if buf[0] != 4 { + err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0]))) + return + } + + _, err = readFull(r, buf[:5]) + if err != nil { + return + } + sig.SigType = SignatureType(buf[0]) + sig.PubKeyAlgo = PublicKeyAlgorithm(buf[1]) + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA: + default: + err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo))) + return + } + + var ok bool + sig.Hash, ok = s2k.HashIdToHash(buf[2]) + if !ok { + return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2]))) + } + + hashedSubpacketsLength := int(buf[3])<<8 | int(buf[4]) + l := 6 + hashedSubpacketsLength + sig.HashSuffix = make([]byte, l+6) + sig.HashSuffix[0] = 4 + copy(sig.HashSuffix[1:], buf[:5]) + hashedSubpackets := sig.HashSuffix[6:l] + _, err = readFull(r, hashedSubpackets) + if err != nil { + return + } + // See RFC 4880, section 5.2.4 + trailer := sig.HashSuffix[l:] + trailer[0] = 4 + trailer[1] = 0xff + trailer[2] = uint8(l >> 24) + trailer[3] = uint8(l >> 16) + trailer[4] = uint8(l >> 8) + trailer[5] = uint8(l) + + err = parseSignatureSubpackets(sig, hashedSubpackets, true) + if err != nil { + return + } + + _, err = readFull(r, buf[:2]) + if err != nil { + return + } + unhashedSubpacketsLength := int(buf[0])<<8 | int(buf[1]) + unhashedSubpackets := make([]byte, unhashedSubpacketsLength) + _, err = readFull(r, unhashedSubpackets) + if err != nil { + return + } + err = parseSignatureSubpackets(sig, unhashedSubpackets, false) + if err != nil { + return + } + + _, err = readFull(r, sig.HashTag[:2]) + if err != nil { + return + } + + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r) + case PubKeyAlgoDSA: + sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r) + if err == nil { + sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r) + } + case PubKeyAlgoECDSA: + sig.ECDSASigR.bytes, sig.ECDSASigR.bitLength, err = readMPI(r) + if err == nil { + sig.ECDSASigS.bytes, sig.ECDSASigS.bitLength, err = readMPI(r) + } + default: + panic("unreachable") + } + return +} + +// parseSignatureSubpackets parses subpackets of the main signature packet. See +// RFC 4880, section 5.2.3.1. +func parseSignatureSubpackets(sig *Signature, subpackets []byte, isHashed bool) (err error) { + for len(subpackets) > 0 { + subpackets, err = parseSignatureSubpacket(sig, subpackets, isHashed) + if err != nil { + return + } + } + + if sig.CreationTime.IsZero() { + err = errors.StructuralError("no creation time in signature") + } + + return +} + +type signatureSubpacketType uint8 + +const ( + creationTimeSubpacket signatureSubpacketType = 2 + signatureExpirationSubpacket signatureSubpacketType = 3 + keyExpirationSubpacket signatureSubpacketType = 9 + prefSymmetricAlgosSubpacket signatureSubpacketType = 11 + issuerSubpacket signatureSubpacketType = 16 + prefHashAlgosSubpacket signatureSubpacketType = 21 + prefCompressionSubpacket signatureSubpacketType = 22 + primaryUserIdSubpacket signatureSubpacketType = 25 + keyFlagsSubpacket signatureSubpacketType = 27 + reasonForRevocationSubpacket signatureSubpacketType = 29 +) + +// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. +func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (rest []byte, err error) { + // RFC 4880, section 5.2.3.1 + var ( + length uint32 + packetType signatureSubpacketType + isCritical bool + ) + switch { + case subpacket[0] < 192: + length = uint32(subpacket[0]) + subpacket = subpacket[1:] + case subpacket[0] < 255: + if len(subpacket) < 2 { + goto Truncated + } + length = uint32(subpacket[0]-192)<<8 + uint32(subpacket[1]) + 192 + subpacket = subpacket[2:] + default: + if len(subpacket) < 5 { + goto Truncated + } + length = uint32(subpacket[1])<<24 | + uint32(subpacket[2])<<16 | + uint32(subpacket[3])<<8 | + uint32(subpacket[4]) + subpacket = subpacket[5:] + } + if length > uint32(len(subpacket)) { + goto Truncated + } + rest = subpacket[length:] + subpacket = subpacket[:length] + if len(subpacket) == 0 { + err = errors.StructuralError("zero length signature subpacket") + return + } + packetType = signatureSubpacketType(subpacket[0] & 0x7f) + isCritical = subpacket[0]&0x80 == 0x80 + subpacket = subpacket[1:] + sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket}) + switch packetType { + case creationTimeSubpacket: + if !isHashed { + err = errors.StructuralError("signature creation time in non-hashed area") + return + } + if len(subpacket) != 4 { + err = errors.StructuralError("signature creation time not four bytes") + return + } + t := binary.BigEndian.Uint32(subpacket) + sig.CreationTime = time.Unix(int64(t), 0) + case signatureExpirationSubpacket: + // Signature expiration time, section 5.2.3.10 + if !isHashed { + return + } + if len(subpacket) != 4 { + err = errors.StructuralError("expiration subpacket with bad length") + return + } + sig.SigLifetimeSecs = new(uint32) + *sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket) + case keyExpirationSubpacket: + // Key expiration time, section 5.2.3.6 + if !isHashed { + return + } + if len(subpacket) != 4 { + err = errors.StructuralError("key expiration subpacket with bad length") + return + } + sig.KeyLifetimeSecs = new(uint32) + *sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket) + case prefSymmetricAlgosSubpacket: + // Preferred symmetric algorithms, section 5.2.3.7 + if !isHashed { + return + } + sig.PreferredSymmetric = make([]byte, len(subpacket)) + copy(sig.PreferredSymmetric, subpacket) + case issuerSubpacket: + // Issuer, section 5.2.3.5 + if len(subpacket) != 8 { + err = errors.StructuralError("issuer subpacket with bad length") + return + } + sig.IssuerKeyId = new(uint64) + *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) + case prefHashAlgosSubpacket: + // Preferred hash algorithms, section 5.2.3.8 + if !isHashed { + return + } + sig.PreferredHash = make([]byte, len(subpacket)) + copy(sig.PreferredHash, subpacket) + case prefCompressionSubpacket: + // Preferred compression algorithms, section 5.2.3.9 + if !isHashed { + return + } + sig.PreferredCompression = make([]byte, len(subpacket)) + copy(sig.PreferredCompression, subpacket) + case primaryUserIdSubpacket: + // Primary User ID, section 5.2.3.19 + if !isHashed { + return + } + if len(subpacket) != 1 { + err = errors.StructuralError("primary user id subpacket with bad length") + return + } + sig.IsPrimaryId = new(bool) + if subpacket[0] > 0 { + *sig.IsPrimaryId = true + } + case keyFlagsSubpacket: + // Key flags, section 5.2.3.21 + if !isHashed { + return + } + if len(subpacket) == 0 { + err = errors.StructuralError("empty key flags subpacket") + return + } + sig.FlagsValid = true + if subpacket[0]&KeyFlagCertify != 0 { + sig.FlagCertify = true + } + if subpacket[0]&KeyFlagSign != 0 { + sig.FlagSign = true + } + if subpacket[0]&KeyFlagEncryptCommunications != 0 { + sig.FlagEncryptCommunications = true + } + if subpacket[0]&KeyFlagEncryptStorage != 0 { + sig.FlagEncryptStorage = true + } + case reasonForRevocationSubpacket: + // Reason For Revocation, section 5.2.3.23 + if !isHashed { + return + } + if len(subpacket) == 0 { + err = errors.StructuralError("empty revocation reason subpacket") + return + } + sig.RevocationReason = new(uint8) + *sig.RevocationReason = subpacket[0] + sig.RevocationReasonText = string(subpacket[1:]) + + default: + if isCritical { + err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType))) + return + } + } + return + +Truncated: + err = errors.StructuralError("signature subpacket truncated") + return +} + +// subpacketLengthLength returns the length, in bytes, of an encoded length value. +func subpacketLengthLength(length int) int { + if length < 192 { + return 1 + } + if length < 16320 { + return 2 + } + return 5 +} + +// serializeSubpacketLength marshals the given length into to. +func serializeSubpacketLength(to []byte, length int) int { + // RFC 4880, Section 4.2.2. + if length < 192 { + to[0] = byte(length) + return 1 + } + if length < 16320 { + length -= 192 + to[0] = byte((length >> 8) + 192) + to[1] = byte(length) + return 2 + } + to[0] = 255 + to[1] = byte(length >> 24) + to[2] = byte(length >> 16) + to[3] = byte(length >> 8) + to[4] = byte(length) + return 5 +} + +// subpacketsLength returns the serialized length, in bytes, of the given +// subpackets. +func subpacketsLength(subpackets []outputSubpacket, hashed bool) (length int) { + for _, subpacket := range subpackets { + if subpacket.hashed == hashed { + length += subpacketLengthLength(len(subpacket.contents) + 1) + length += 1 // type byte + length += len(subpacket.contents) + } + } + return +} + +// serializeSubpackets marshals the given subpackets into to. +func serializeSubpackets(to []byte, subpackets []outputSubpacket, hashed bool) { + for _, subpacket := range subpackets { + if subpacket.hashed == hashed { + n := serializeSubpacketLength(to, len(subpacket.contents)+1) + to[n] = byte(subpacket.subpacketType) + to = to[1+n:] + n = copy(to, subpacket.contents) + to = to[n:] + } + } + return +} + +// KeyExpired returns whether sig is a self-signature of a key that has +// expired. +func (sig *Signature) KeyExpired(currentTime time.Time) bool { + if sig.KeyLifetimeSecs == nil { + return false + } + expiry := sig.CreationTime.Add(time.Duration(*sig.KeyLifetimeSecs) * time.Second) + return currentTime.After(expiry) +} + +// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing. +func (sig *Signature) buildHashSuffix() (err error) { + hashedSubpacketsLen := subpacketsLength(sig.outSubpackets, true) + + var ok bool + l := 6 + hashedSubpacketsLen + sig.HashSuffix = make([]byte, l+6) + sig.HashSuffix[0] = 4 + sig.HashSuffix[1] = uint8(sig.SigType) + sig.HashSuffix[2] = uint8(sig.PubKeyAlgo) + sig.HashSuffix[3], ok = s2k.HashToHashId(sig.Hash) + if !ok { + sig.HashSuffix = nil + return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash))) + } + sig.HashSuffix[4] = byte(hashedSubpacketsLen >> 8) + sig.HashSuffix[5] = byte(hashedSubpacketsLen) + serializeSubpackets(sig.HashSuffix[6:l], sig.outSubpackets, true) + trailer := sig.HashSuffix[l:] + trailer[0] = 4 + trailer[1] = 0xff + trailer[2] = byte(l >> 24) + trailer[3] = byte(l >> 16) + trailer[4] = byte(l >> 8) + trailer[5] = byte(l) + return +} + +func (sig *Signature) signPrepareHash(h hash.Hash) (digest []byte, err error) { + err = sig.buildHashSuffix() + if err != nil { + return + } + + h.Write(sig.HashSuffix) + digest = h.Sum(nil) + copy(sig.HashTag[:], digest) + return +} + +// Sign signs a message with a private key. The hash, h, must contain +// the hash of the message to be signed and will be mutated by this function. +// On success, the signature is stored in sig. Call Serialize to write it out. +// If config is nil, sensible defaults will be used. +func (sig *Signature) Sign(h hash.Hash, priv *PrivateKey, config *Config) (err error) { + sig.outSubpackets = sig.buildSubpackets() + digest, err := sig.signPrepareHash(h) + if err != nil { + return + } + + switch priv.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + sig.RSASignature.bytes, err = rsa.SignPKCS1v15(config.Random(), priv.PrivateKey.(*rsa.PrivateKey), sig.Hash, digest) + sig.RSASignature.bitLength = uint16(8 * len(sig.RSASignature.bytes)) + case PubKeyAlgoDSA: + dsaPriv := priv.PrivateKey.(*dsa.PrivateKey) + + // Need to truncate hashBytes to match FIPS 186-3 section 4.6. + subgroupSize := (dsaPriv.Q.BitLen() + 7) / 8 + if len(digest) > subgroupSize { + digest = digest[:subgroupSize] + } + r, s, err := dsa.Sign(config.Random(), dsaPriv, digest) + if err == nil { + sig.DSASigR.bytes = r.Bytes() + sig.DSASigR.bitLength = uint16(8 * len(sig.DSASigR.bytes)) + sig.DSASigS.bytes = s.Bytes() + sig.DSASigS.bitLength = uint16(8 * len(sig.DSASigS.bytes)) + } + default: + err = errors.UnsupportedError("public key algorithm: " + strconv.Itoa(int(sig.PubKeyAlgo))) + } + + return +} + +// SignUserId computes a signature from priv, asserting that pub is a valid +// key for the identity id. On success, the signature is stored in sig. Call +// Serialize to write it out. +// If config is nil, sensible defaults will be used. +func (sig *Signature) SignUserId(id string, pub *PublicKey, priv *PrivateKey, config *Config) error { + h, err := userIdSignatureHash(id, pub, sig.Hash) + if err != nil { + return nil + } + return sig.Sign(h, priv, config) +} + +// SignKey computes a signature from priv, asserting that pub is a subkey. On +// success, the signature is stored in sig. Call Serialize to write it out. +// If config is nil, sensible defaults will be used. +func (sig *Signature) SignKey(pub *PublicKey, priv *PrivateKey, config *Config) error { + h, err := keySignatureHash(&priv.PublicKey, pub, sig.Hash) + if err != nil { + return err + } + return sig.Sign(h, priv, config) +} + +// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been +// called first. +func (sig *Signature) Serialize(w io.Writer) (err error) { + if len(sig.outSubpackets) == 0 { + sig.outSubpackets = sig.rawSubpackets + } + if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil && sig.ECDSASigR.bytes == nil { + return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize") + } + + sigLength := 0 + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + sigLength = 2 + len(sig.RSASignature.bytes) + case PubKeyAlgoDSA: + sigLength = 2 + len(sig.DSASigR.bytes) + sigLength += 2 + len(sig.DSASigS.bytes) + case PubKeyAlgoECDSA: + sigLength = 2 + len(sig.ECDSASigR.bytes) + sigLength += 2 + len(sig.ECDSASigS.bytes) + default: + panic("impossible") + } + + unhashedSubpacketsLen := subpacketsLength(sig.outSubpackets, false) + length := len(sig.HashSuffix) - 6 /* trailer not included */ + + 2 /* length of unhashed subpackets */ + unhashedSubpacketsLen + + 2 /* hash tag */ + sigLength + err = serializeHeader(w, packetTypeSignature, length) + if err != nil { + return + } + + _, err = w.Write(sig.HashSuffix[:len(sig.HashSuffix)-6]) + if err != nil { + return + } + + unhashedSubpackets := make([]byte, 2+unhashedSubpacketsLen) + unhashedSubpackets[0] = byte(unhashedSubpacketsLen >> 8) + unhashedSubpackets[1] = byte(unhashedSubpacketsLen) + serializeSubpackets(unhashedSubpackets[2:], sig.outSubpackets, false) + + _, err = w.Write(unhashedSubpackets) + if err != nil { + return + } + _, err = w.Write(sig.HashTag[:]) + if err != nil { + return + } + + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + err = writeMPIs(w, sig.RSASignature) + case PubKeyAlgoDSA: + err = writeMPIs(w, sig.DSASigR, sig.DSASigS) + case PubKeyAlgoECDSA: + err = writeMPIs(w, sig.ECDSASigR, sig.ECDSASigS) + default: + panic("impossible") + } + return +} + +// outputSubpacket represents a subpacket to be marshaled. +type outputSubpacket struct { + hashed bool // true if this subpacket is in the hashed area. + subpacketType signatureSubpacketType + isCritical bool + contents []byte +} + +func (sig *Signature) buildSubpackets() (subpackets []outputSubpacket) { + creationTime := make([]byte, 4) + binary.BigEndian.PutUint32(creationTime, uint32(sig.CreationTime.Unix())) + subpackets = append(subpackets, outputSubpacket{true, creationTimeSubpacket, false, creationTime}) + + if sig.IssuerKeyId != nil { + keyId := make([]byte, 8) + binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId) + subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId}) + } + + if sig.SigLifetimeSecs != nil && *sig.SigLifetimeSecs != 0 { + sigLifetime := make([]byte, 4) + binary.BigEndian.PutUint32(sigLifetime, *sig.SigLifetimeSecs) + subpackets = append(subpackets, outputSubpacket{true, signatureExpirationSubpacket, true, sigLifetime}) + } + + // Key flags may only appear in self-signatures or certification signatures. + + if sig.FlagsValid { + var flags byte + if sig.FlagCertify { + flags |= KeyFlagCertify + } + if sig.FlagSign { + flags |= KeyFlagSign + } + if sig.FlagEncryptCommunications { + flags |= KeyFlagEncryptCommunications + } + if sig.FlagEncryptStorage { + flags |= KeyFlagEncryptStorage + } + subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}}) + } + + // The following subpackets may only appear in self-signatures + + if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 { + keyLifetime := make([]byte, 4) + binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs) + subpackets = append(subpackets, outputSubpacket{true, keyExpirationSubpacket, true, keyLifetime}) + } + + if sig.IsPrimaryId != nil && *sig.IsPrimaryId { + subpackets = append(subpackets, outputSubpacket{true, primaryUserIdSubpacket, false, []byte{1}}) + } + + if len(sig.PreferredSymmetric) > 0 { + subpackets = append(subpackets, outputSubpacket{true, prefSymmetricAlgosSubpacket, false, sig.PreferredSymmetric}) + } + + if len(sig.PreferredHash) > 0 { + subpackets = append(subpackets, outputSubpacket{true, prefHashAlgosSubpacket, false, sig.PreferredHash}) + } + + if len(sig.PreferredCompression) > 0 { + subpackets = append(subpackets, outputSubpacket{true, prefCompressionSubpacket, false, sig.PreferredCompression}) + } + + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_test.go new file mode 100644 index 0000000..c1bbde8 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_test.go @@ -0,0 +1,42 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto" + "encoding/hex" + "testing" +) + +func TestSignatureRead(t *testing.T) { + packet, err := Read(readerFromHex(signatureDataHex)) + if err != nil { + t.Error(err) + return + } + sig, ok := packet.(*Signature) + if !ok || sig.SigType != SigTypeBinary || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.SHA1 { + t.Errorf("failed to parse, got: %#v", packet) + } +} + +func TestSignatureReserialize(t *testing.T) { + packet, _ := Read(readerFromHex(signatureDataHex)) + sig := packet.(*Signature) + out := new(bytes.Buffer) + err := sig.Serialize(out) + if err != nil { + t.Errorf("error reserializing: %s", err) + return + } + + expected, _ := hex.DecodeString(signatureDataHex) + if !bytes.Equal(expected, out.Bytes()) { + t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected)) + } +} + +const signatureDataHex = "c2c05c04000102000605024cb45112000a0910ab105c91af38fb158f8d07ff5596ea368c5efe015bed6e78348c0f033c931d5f2ce5db54ce7f2a7e4b4ad64db758d65a7a71773edeab7ba2a9e0908e6a94a1175edd86c1d843279f045b021a6971a72702fcbd650efc393c5474d5b59a15f96d2eaad4c4c426797e0dcca2803ef41c6ff234d403eec38f31d610c344c06f2401c262f0993b2e66cad8a81ebc4322c723e0d4ba09fe917e8777658307ad8329adacba821420741009dfe87f007759f0982275d028a392c6ed983a0d846f890b36148c7358bdb8a516007fac760261ecd06076813831a36d0459075d1befa245ae7f7fb103d92ca759e9498fe60ef8078a39a3beda510deea251ea9f0a7f0df6ef42060f20780360686f3e400e" diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3.go new file mode 100644 index 0000000..6edff88 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3.go @@ -0,0 +1,146 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto" + "encoding/binary" + "fmt" + "io" + "strconv" + "time" + + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" +) + +// SignatureV3 represents older version 3 signatures. These signatures are less secure +// than version 4 and should not be used to create new signatures. They are included +// here for backwards compatibility to read and validate with older key material. +// See RFC 4880, section 5.2.2. +type SignatureV3 struct { + SigType SignatureType + CreationTime time.Time + IssuerKeyId uint64 + PubKeyAlgo PublicKeyAlgorithm + Hash crypto.Hash + HashTag [2]byte + + RSASignature parsedMPI + DSASigR, DSASigS parsedMPI +} + +func (sig *SignatureV3) parse(r io.Reader) (err error) { + // RFC 4880, section 5.2.2 + var buf [8]byte + if _, err = readFull(r, buf[:1]); err != nil { + return + } + if buf[0] < 2 || buf[0] > 3 { + err = errors.UnsupportedError("signature packet version " + strconv.Itoa(int(buf[0]))) + return + } + if _, err = readFull(r, buf[:1]); err != nil { + return + } + if buf[0] != 5 { + err = errors.UnsupportedError( + "invalid hashed material length " + strconv.Itoa(int(buf[0]))) + return + } + + // Read hashed material: signature type + creation time + if _, err = readFull(r, buf[:5]); err != nil { + return + } + sig.SigType = SignatureType(buf[0]) + t := binary.BigEndian.Uint32(buf[1:5]) + sig.CreationTime = time.Unix(int64(t), 0) + + // Eight-octet Key ID of signer. + if _, err = readFull(r, buf[:8]); err != nil { + return + } + sig.IssuerKeyId = binary.BigEndian.Uint64(buf[:]) + + // Public-key and hash algorithm + if _, err = readFull(r, buf[:2]); err != nil { + return + } + sig.PubKeyAlgo = PublicKeyAlgorithm(buf[0]) + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA: + default: + err = errors.UnsupportedError("public key algorithm " + strconv.Itoa(int(sig.PubKeyAlgo))) + return + } + var ok bool + if sig.Hash, ok = s2k.HashIdToHash(buf[1]); !ok { + return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2]))) + } + + // Two-octet field holding left 16 bits of signed hash value. + if _, err = readFull(r, sig.HashTag[:2]); err != nil { + return + } + + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + sig.RSASignature.bytes, sig.RSASignature.bitLength, err = readMPI(r) + case PubKeyAlgoDSA: + if sig.DSASigR.bytes, sig.DSASigR.bitLength, err = readMPI(r); err != nil { + return + } + sig.DSASigS.bytes, sig.DSASigS.bitLength, err = readMPI(r) + default: + panic("unreachable") + } + return +} + +// Serialize marshals sig to w. Sign, SignUserId or SignKey must have been +// called first. +func (sig *SignatureV3) Serialize(w io.Writer) (err error) { + buf := make([]byte, 8) + + // Write the sig type and creation time + buf[0] = byte(sig.SigType) + binary.BigEndian.PutUint32(buf[1:5], uint32(sig.CreationTime.Unix())) + if _, err = w.Write(buf[:5]); err != nil { + return + } + + // Write the issuer long key ID + binary.BigEndian.PutUint64(buf[:8], sig.IssuerKeyId) + if _, err = w.Write(buf[:8]); err != nil { + return + } + + // Write public key algorithm, hash ID, and hash value + buf[0] = byte(sig.PubKeyAlgo) + hashId, ok := s2k.HashToHashId(sig.Hash) + if !ok { + return errors.UnsupportedError(fmt.Sprintf("hash function %v", sig.Hash)) + } + buf[1] = hashId + copy(buf[2:4], sig.HashTag[:]) + if _, err = w.Write(buf[:4]); err != nil { + return + } + + if sig.RSASignature.bytes == nil && sig.DSASigR.bytes == nil { + return errors.InvalidArgumentError("Signature: need to call Sign, SignUserId or SignKey before Serialize") + } + + switch sig.PubKeyAlgo { + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly: + err = writeMPIs(w, sig.RSASignature) + case PubKeyAlgoDSA: + err = writeMPIs(w, sig.DSASigR, sig.DSASigS) + default: + panic("impossible") + } + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go new file mode 100644 index 0000000..ad7b62a --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature_v3_test.go @@ -0,0 +1,92 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto" + "encoding/hex" + "io" + "io/ioutil" + "testing" + + "golang.org/x/crypto/openpgp/armor" +) + +func TestSignatureV3Read(t *testing.T) { + r := v3KeyReader(t) + Read(r) // Skip public key + Read(r) // Skip uid + packet, err := Read(r) // Signature + if err != nil { + t.Error(err) + return + } + sig, ok := packet.(*SignatureV3) + if !ok || sig.SigType != SigTypeGenericCert || sig.PubKeyAlgo != PubKeyAlgoRSA || sig.Hash != crypto.MD5 { + t.Errorf("failed to parse, got: %#v", packet) + } +} + +func TestSignatureV3Reserialize(t *testing.T) { + r := v3KeyReader(t) + Read(r) // Skip public key + Read(r) // Skip uid + packet, err := Read(r) + if err != nil { + t.Error(err) + return + } + sig := packet.(*SignatureV3) + out := new(bytes.Buffer) + if err = sig.Serialize(out); err != nil { + t.Errorf("error reserializing: %s", err) + return + } + expected, err := ioutil.ReadAll(v3KeyReader(t)) + if err != nil { + t.Error(err) + return + } + expected = expected[4+141+4+39:] // See pgpdump offsets below, this is where the sig starts + if !bytes.Equal(expected, out.Bytes()) { + t.Errorf("output doesn't match input (got vs expected):\n%s\n%s", hex.Dump(out.Bytes()), hex.Dump(expected)) + } +} + +func v3KeyReader(t *testing.T) io.Reader { + armorBlock, err := armor.Decode(bytes.NewBufferString(keySigV3Armor)) + if err != nil { + t.Fatalf("armor Decode failed: %v", err) + } + return armorBlock.Body +} + +// keySigV3Armor is some V3 public key I found in an SKS dump. +// Old: Public Key Packet(tag 6)(141 bytes) +// Ver 4 - new +// Public key creation time - Fri Sep 16 17:13:54 CDT 1994 +// Pub alg - unknown(pub 0) +// Unknown public key(pub 0) +// Old: User ID Packet(tag 13)(39 bytes) +// User ID - Armin M. Warda +// Old: Signature Packet(tag 2)(149 bytes) +// Ver 4 - new +// Sig type - unknown(05) +// Pub alg - ElGamal Encrypt-Only(pub 16) +// Hash alg - unknown(hash 46) +// Hashed Sub: unknown(sub 81, critical)(1988 bytes) +const keySigV3Armor = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: SKS 1.0.10 + +mI0CLnoYogAAAQQA1qwA2SuJwfQ5bCQ6u5t20ulnOtY0gykf7YjiK4LiVeRBwHjGq7v30tGV +5Qti7qqRW4Ww7CDCJc4sZMFnystucR2vLkXaSoNWoFm4Fg47NiisDdhDezHwbVPW6OpCFNSi +ZAamtj4QAUBu8j4LswafrJqZqR9336/V3g8Yil2l48kABRG0J0FybWluIE0uIFdhcmRhIDx3 +YXJkYUBuZXBoaWxpbS5ydWhyLmRlPoiVAgUQLok2xwXR6zmeWEiZAQE/DgP/WgxPQh40/Po4 +gSkWZCDAjNdph7zexvAb0CcUWahcwiBIgg3U5ErCx9I5CNVA9U+s8bNrDZwgSIeBzp3KhWUx +524uhGgm6ZUTOAIKA6CbV6pfqoLpJnRYvXYQU5mIWsNa99wcu2qu18OeEDnztb7aLA6Ra9OF +YFCbq4EjXRoOrYM= +=LPjs +-----END PGP PUBLIC KEY BLOCK-----` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go new file mode 100644 index 0000000..23721bc --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go @@ -0,0 +1,164 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto/cipher" + "io" + "strconv" + + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" +) + +// This is the largest session key that we'll support. Since no 512-bit cipher +// has even been seriously used, this is comfortably large. +const maxSessionKeySizeInBytes = 64 + +// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC +// 4880, section 5.3. +type SymmetricKeyEncrypted struct { + CipherFunc CipherFunction + Encrypted bool + Key []byte // Empty unless Encrypted is false. + s2k func(out, in []byte) + encryptedKey []byte +} + +const symmetricKeyEncryptedVersion = 4 + +func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) { + // RFC 4880, section 5.3. + var buf [2]byte + _, err = readFull(r, buf[:]) + if err != nil { + return + } + if buf[0] != symmetricKeyEncryptedVersion { + return errors.UnsupportedError("SymmetricKeyEncrypted version") + } + ske.CipherFunc = CipherFunction(buf[1]) + + if ske.CipherFunc.KeySize() == 0 { + return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1]))) + } + + ske.s2k, err = s2k.Parse(r) + if err != nil { + return + } + + encryptedKey := make([]byte, maxSessionKeySizeInBytes) + // The session key may follow. We just have to try and read to find + // out. If it exists then we limit it to maxSessionKeySizeInBytes. + n, err := readFull(r, encryptedKey) + if err != nil && err != io.ErrUnexpectedEOF { + return + } + err = nil + if n != 0 { + if n == maxSessionKeySizeInBytes { + return errors.UnsupportedError("oversized encrypted session key") + } + ske.encryptedKey = encryptedKey[:n] + } + + ske.Encrypted = true + + return +} + +// Decrypt attempts to decrypt an encrypted session key. If it returns nil, +// ske.Key will contain the session key. +func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error { + if !ske.Encrypted { + return nil + } + + key := make([]byte, ske.CipherFunc.KeySize()) + ske.s2k(key, passphrase) + + if len(ske.encryptedKey) == 0 { + ske.Key = key + } else { + // the IV is all zeros + iv := make([]byte, ske.CipherFunc.blockSize()) + c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv) + c.XORKeyStream(ske.encryptedKey, ske.encryptedKey) + ske.CipherFunc = CipherFunction(ske.encryptedKey[0]) + if ske.CipherFunc.blockSize() == 0 { + return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc))) + } + ske.CipherFunc = CipherFunction(ske.encryptedKey[0]) + ske.Key = ske.encryptedKey[1:] + if len(ske.Key)%ske.CipherFunc.blockSize() != 0 { + ske.Key = nil + return errors.StructuralError("length of decrypted key not a multiple of block size") + } + } + + ske.Encrypted = false + return nil +} + +// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. The +// packet contains a random session key, encrypted by a key derived from the +// given passphrase. The session key is returned and must be passed to +// SerializeSymmetricallyEncrypted. +// If config is nil, sensible defaults will be used. +func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) { + cipherFunc := config.Cipher() + keySize := cipherFunc.KeySize() + if keySize == 0 { + return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc))) + } + + s2kBuf := new(bytes.Buffer) + keyEncryptingKey := make([]byte, keySize) + // s2k.Serialize salts and stretches the passphrase, and writes the + // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. + err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash()}) + if err != nil { + return + } + s2kBytes := s2kBuf.Bytes() + + packetLength := 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize + err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength) + if err != nil { + return + } + + var buf [2]byte + buf[0] = symmetricKeyEncryptedVersion + buf[1] = byte(cipherFunc) + _, err = w.Write(buf[:]) + if err != nil { + return + } + _, err = w.Write(s2kBytes) + if err != nil { + return + } + + sessionKey := make([]byte, keySize) + _, err = io.ReadFull(config.Random(), sessionKey) + if err != nil { + return + } + iv := make([]byte, cipherFunc.blockSize()) + c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv) + encryptedCipherAndKey := make([]byte, keySize+1) + c.XORKeyStream(encryptedCipherAndKey, buf[1:]) + c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey) + _, err = w.Write(encryptedCipherAndKey) + if err != nil { + return + } + + key = sessionKey + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go new file mode 100644 index 0000000..dd983cb --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted_test.go @@ -0,0 +1,102 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/hex" + "io" + "io/ioutil" + "testing" +) + +func TestSymmetricKeyEncrypted(t *testing.T) { + buf := readerFromHex(symmetricallyEncryptedHex) + packet, err := Read(buf) + if err != nil { + t.Errorf("failed to read SymmetricKeyEncrypted: %s", err) + return + } + ske, ok := packet.(*SymmetricKeyEncrypted) + if !ok { + t.Error("didn't find SymmetricKeyEncrypted packet") + return + } + err = ske.Decrypt([]byte("password")) + if err != nil { + t.Error(err) + return + } + + packet, err = Read(buf) + if err != nil { + t.Errorf("failed to read SymmetricallyEncrypted: %s", err) + return + } + se, ok := packet.(*SymmetricallyEncrypted) + if !ok { + t.Error("didn't find SymmetricallyEncrypted packet") + return + } + r, err := se.Decrypt(ske.CipherFunc, ske.Key) + if err != nil { + t.Error(err) + return + } + + contents, err := ioutil.ReadAll(r) + if err != nil && err != io.EOF { + t.Error(err) + return + } + + expectedContents, _ := hex.DecodeString(symmetricallyEncryptedContentsHex) + if !bytes.Equal(expectedContents, contents) { + t.Errorf("bad contents got:%x want:%x", contents, expectedContents) + } +} + +const symmetricallyEncryptedHex = "8c0d04030302371a0b38d884f02060c91cf97c9973b8e58e028e9501708ccfe618fb92afef7fa2d80ddadd93cf" +const symmetricallyEncryptedContentsHex = "cb1062004d14c4df636f6e74656e74732e0a" + +func TestSerializeSymmetricKeyEncrypted(t *testing.T) { + buf := bytes.NewBuffer(nil) + passphrase := []byte("testing") + config := &Config{ + DefaultCipher: CipherAES128, + } + + key, err := SerializeSymmetricKeyEncrypted(buf, passphrase, config) + if err != nil { + t.Errorf("failed to serialize: %s", err) + return + } + + p, err := Read(buf) + if err != nil { + t.Errorf("failed to reparse: %s", err) + return + } + ske, ok := p.(*SymmetricKeyEncrypted) + if !ok { + t.Errorf("parsed a different packet type: %#v", p) + return + } + + if !ske.Encrypted { + t.Errorf("SKE not encrypted but should be") + } + if ske.CipherFunc != config.DefaultCipher { + t.Errorf("SKE cipher function is %d (expected %d)", ske.CipherFunc, config.DefaultCipher) + } + err = ske.Decrypt(passphrase) + if err != nil { + t.Errorf("failed to decrypt reparsed SKE: %s", err) + return + } + if !bytes.Equal(key, ske.Key) { + t.Errorf("keys don't match after Decrpyt: %x (original) vs %x (parsed)", key, ske.Key) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go new file mode 100644 index 0000000..6126030 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted.go @@ -0,0 +1,290 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "crypto/cipher" + "crypto/sha1" + "crypto/subtle" + "golang.org/x/crypto/openpgp/errors" + "hash" + "io" + "strconv" +) + +// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The +// encrypted contents will consist of more OpenPGP packets. See RFC 4880, +// sections 5.7 and 5.13. +type SymmetricallyEncrypted struct { + MDC bool // true iff this is a type 18 packet and thus has an embedded MAC. + contents io.Reader + prefix []byte +} + +const symmetricallyEncryptedVersion = 1 + +func (se *SymmetricallyEncrypted) parse(r io.Reader) error { + if se.MDC { + // See RFC 4880, section 5.13. + var buf [1]byte + _, err := readFull(r, buf[:]) + if err != nil { + return err + } + if buf[0] != symmetricallyEncryptedVersion { + return errors.UnsupportedError("unknown SymmetricallyEncrypted version") + } + } + se.contents = r + return nil +} + +// Decrypt returns a ReadCloser, from which the decrypted contents of the +// packet can be read. An incorrect key can, with high probability, be detected +// immediately and this will result in a KeyIncorrect error being returned. +func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { + keySize := c.KeySize() + if keySize == 0 { + return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c))) + } + if len(key) != keySize { + return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length") + } + + if se.prefix == nil { + se.prefix = make([]byte, c.blockSize()+2) + _, err := readFull(se.contents, se.prefix) + if err != nil { + return nil, err + } + } else if len(se.prefix) != c.blockSize()+2 { + return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths") + } + + ocfbResync := OCFBResync + if se.MDC { + // MDC packets use a different form of OCFB mode. + ocfbResync = OCFBNoResync + } + + s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync) + if s == nil { + return nil, errors.ErrKeyIncorrect + } + + plaintext := cipher.StreamReader{S: s, R: se.contents} + + if se.MDC { + // MDC packets have an embedded hash that we need to check. + h := sha1.New() + h.Write(se.prefix) + return &seMDCReader{in: plaintext, h: h}, nil + } + + // Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser. + return seReader{plaintext}, nil +} + +// seReader wraps an io.Reader with a no-op Close method. +type seReader struct { + in io.Reader +} + +func (ser seReader) Read(buf []byte) (int, error) { + return ser.in.Read(buf) +} + +func (ser seReader) Close() error { + return nil +} + +const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size + +// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold +// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an +// MDC packet containing a hash of the previous contents which is checked +// against the running hash. See RFC 4880, section 5.13. +type seMDCReader struct { + in io.Reader + h hash.Hash + trailer [mdcTrailerSize]byte + scratch [mdcTrailerSize]byte + trailerUsed int + error bool + eof bool +} + +func (ser *seMDCReader) Read(buf []byte) (n int, err error) { + if ser.error { + err = io.ErrUnexpectedEOF + return + } + if ser.eof { + err = io.EOF + return + } + + // If we haven't yet filled the trailer buffer then we must do that + // first. + for ser.trailerUsed < mdcTrailerSize { + n, err = ser.in.Read(ser.trailer[ser.trailerUsed:]) + ser.trailerUsed += n + if err == io.EOF { + if ser.trailerUsed != mdcTrailerSize { + n = 0 + err = io.ErrUnexpectedEOF + ser.error = true + return + } + ser.eof = true + n = 0 + return + } + + if err != nil { + n = 0 + return + } + } + + // If it's a short read then we read into a temporary buffer and shift + // the data into the caller's buffer. + if len(buf) <= mdcTrailerSize { + n, err = readFull(ser.in, ser.scratch[:len(buf)]) + copy(buf, ser.trailer[:n]) + ser.h.Write(buf[:n]) + copy(ser.trailer[:], ser.trailer[n:]) + copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:]) + if n < len(buf) { + ser.eof = true + err = io.EOF + } + return + } + + n, err = ser.in.Read(buf[mdcTrailerSize:]) + copy(buf, ser.trailer[:]) + ser.h.Write(buf[:n]) + copy(ser.trailer[:], buf[n:]) + + if err == io.EOF { + ser.eof = true + } + return +} + +// This is a new-format packet tag byte for a type 19 (MDC) packet. +const mdcPacketTagByte = byte(0x80) | 0x40 | 19 + +func (ser *seMDCReader) Close() error { + if ser.error { + return errors.SignatureError("error during reading") + } + + for !ser.eof { + // We haven't seen EOF so we need to read to the end + var buf [1024]byte + _, err := ser.Read(buf[:]) + if err == io.EOF { + break + } + if err != nil { + return errors.SignatureError("error during reading") + } + } + + if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size { + return errors.SignatureError("MDC packet not found") + } + ser.h.Write(ser.trailer[:2]) + + final := ser.h.Sum(nil) + if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 { + return errors.SignatureError("hash mismatch") + } + return nil +} + +// An seMDCWriter writes through to an io.WriteCloser while maintains a running +// hash of the data written. On close, it emits an MDC packet containing the +// running hash. +type seMDCWriter struct { + w io.WriteCloser + h hash.Hash +} + +func (w *seMDCWriter) Write(buf []byte) (n int, err error) { + w.h.Write(buf) + return w.w.Write(buf) +} + +func (w *seMDCWriter) Close() (err error) { + var buf [mdcTrailerSize]byte + + buf[0] = mdcPacketTagByte + buf[1] = sha1.Size + w.h.Write(buf[:2]) + digest := w.h.Sum(nil) + copy(buf[2:], digest) + + _, err = w.w.Write(buf[:]) + if err != nil { + return + } + return w.w.Close() +} + +// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. +type noOpCloser struct { + w io.Writer +} + +func (c noOpCloser) Write(data []byte) (n int, err error) { + return c.w.Write(data) +} + +func (c noOpCloser) Close() error { + return nil +} + +// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet +// to w and returns a WriteCloser to which the to-be-encrypted packets can be +// written. +// If config is nil, sensible defaults will be used. +func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (contents io.WriteCloser, err error) { + if c.KeySize() != len(key) { + return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length") + } + writeCloser := noOpCloser{w} + ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC) + if err != nil { + return + } + + _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion}) + if err != nil { + return + } + + block := c.new(key) + blockSize := block.BlockSize() + iv := make([]byte, blockSize) + _, err = config.Random().Read(iv) + if err != nil { + return + } + s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync) + _, err = ciphertext.Write(prefix) + if err != nil { + return + } + plaintext := cipher.StreamWriter{S: s, W: ciphertext} + + h := sha1.New() + h.Write(iv) + h.Write(iv[blockSize-2:]) + contents = &seMDCWriter{w: plaintext, h: h} + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go new file mode 100644 index 0000000..c5c00f7 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetrically_encrypted_test.go @@ -0,0 +1,123 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "crypto/sha1" + "encoding/hex" + "golang.org/x/crypto/openpgp/errors" + "io" + "io/ioutil" + "testing" +) + +// TestReader wraps a []byte and returns reads of a specific length. +type testReader struct { + data []byte + stride int +} + +func (t *testReader) Read(buf []byte) (n int, err error) { + n = t.stride + if n > len(t.data) { + n = len(t.data) + } + if n > len(buf) { + n = len(buf) + } + copy(buf, t.data) + t.data = t.data[n:] + if len(t.data) == 0 { + err = io.EOF + } + return +} + +func testMDCReader(t *testing.T) { + mdcPlaintext, _ := hex.DecodeString(mdcPlaintextHex) + + for stride := 1; stride < len(mdcPlaintext)/2; stride++ { + r := &testReader{data: mdcPlaintext, stride: stride} + mdcReader := &seMDCReader{in: r, h: sha1.New()} + body, err := ioutil.ReadAll(mdcReader) + if err != nil { + t.Errorf("stride: %d, error: %s", stride, err) + continue + } + if !bytes.Equal(body, mdcPlaintext[:len(mdcPlaintext)-22]) { + t.Errorf("stride: %d: bad contents %x", stride, body) + continue + } + + err = mdcReader.Close() + if err != nil { + t.Errorf("stride: %d, error on Close: %s", stride, err) + } + } + + mdcPlaintext[15] ^= 80 + + r := &testReader{data: mdcPlaintext, stride: 2} + mdcReader := &seMDCReader{in: r, h: sha1.New()} + _, err := ioutil.ReadAll(mdcReader) + if err != nil { + t.Errorf("corruption test, error: %s", err) + return + } + err = mdcReader.Close() + if err == nil { + t.Error("corruption: no error") + } else if _, ok := err.(*errors.SignatureError); !ok { + t.Errorf("corruption: expected SignatureError, got: %s", err) + } +} + +const mdcPlaintextHex = "a302789c3b2d93c4e0eb9aba22283539b3203335af44a134afb800c849cb4c4de10200aff40b45d31432c80cb384299a0655966d6939dfdeed1dddf980" + +func TestSerialize(t *testing.T) { + buf := bytes.NewBuffer(nil) + c := CipherAES128 + key := make([]byte, c.KeySize()) + + w, err := SerializeSymmetricallyEncrypted(buf, c, key, nil) + if err != nil { + t.Errorf("error from SerializeSymmetricallyEncrypted: %s", err) + return + } + + contents := []byte("hello world\n") + + w.Write(contents) + w.Close() + + p, err := Read(buf) + if err != nil { + t.Errorf("error from Read: %s", err) + return + } + + se, ok := p.(*SymmetricallyEncrypted) + if !ok { + t.Errorf("didn't read a *SymmetricallyEncrypted") + return + } + + r, err := se.Decrypt(c, key) + if err != nil { + t.Errorf("error from Decrypt: %s", err) + return + } + + contentsCopy := bytes.NewBuffer(nil) + _, err = io.Copy(contentsCopy, r) + if err != nil { + t.Errorf("error from io.Copy: %s", err) + return + } + if !bytes.Equal(contentsCopy.Bytes(), contents) { + t.Errorf("contents not equal got: %x want: %x", contentsCopy.Bytes(), contents) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute.go new file mode 100644 index 0000000..96a2b38 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute.go @@ -0,0 +1,91 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "image" + "image/jpeg" + "io" + "io/ioutil" +) + +const UserAttrImageSubpacket = 1 + +// UserAttribute is capable of storing other types of data about a user +// beyond name, email and a text comment. In practice, user attributes are typically used +// to store a signed thumbnail photo JPEG image of the user. +// See RFC 4880, section 5.12. +type UserAttribute struct { + Contents []*OpaqueSubpacket +} + +// NewUserAttributePhoto creates a user attribute packet +// containing the given images. +func NewUserAttributePhoto(photos ...image.Image) (uat *UserAttribute, err error) { + uat = new(UserAttribute) + for _, photo := range photos { + var buf bytes.Buffer + // RFC 4880, Section 5.12.1. + data := []byte{ + 0x10, 0x00, // Little-endian image header length (16 bytes) + 0x01, // Image header version 1 + 0x01, // JPEG + 0, 0, 0, 0, // 12 reserved octets, must be all zero. + 0, 0, 0, 0, + 0, 0, 0, 0} + if _, err = buf.Write(data); err != nil { + return + } + if err = jpeg.Encode(&buf, photo, nil); err != nil { + return + } + uat.Contents = append(uat.Contents, &OpaqueSubpacket{ + SubType: UserAttrImageSubpacket, + Contents: buf.Bytes()}) + } + return +} + +// NewUserAttribute creates a new user attribute packet containing the given subpackets. +func NewUserAttribute(contents ...*OpaqueSubpacket) *UserAttribute { + return &UserAttribute{Contents: contents} +} + +func (uat *UserAttribute) parse(r io.Reader) (err error) { + // RFC 4880, section 5.13 + b, err := ioutil.ReadAll(r) + if err != nil { + return + } + uat.Contents, err = OpaqueSubpackets(b) + return +} + +// Serialize marshals the user attribute to w in the form of an OpenPGP packet, including +// header. +func (uat *UserAttribute) Serialize(w io.Writer) (err error) { + var buf bytes.Buffer + for _, sp := range uat.Contents { + sp.Serialize(&buf) + } + if err = serializeHeader(w, packetTypeUserAttribute, buf.Len()); err != nil { + return err + } + _, err = w.Write(buf.Bytes()) + return +} + +// ImageData returns zero or more byte slices, each containing +// JPEG File Interchange Format (JFIF), for each photo in the +// the user attribute packet. +func (uat *UserAttribute) ImageData() (imageData [][]byte) { + for _, sp := range uat.Contents { + if sp.SubType == UserAttrImageSubpacket && len(sp.Contents) > 16 { + imageData = append(imageData, sp.Contents[16:]) + } + } + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go new file mode 100644 index 0000000..13ca514 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userattribute_test.go @@ -0,0 +1,109 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "bytes" + "encoding/base64" + "image/color" + "image/jpeg" + "testing" +) + +func TestParseUserAttribute(t *testing.T) { + r := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(userAttributePacket)) + for i := 0; i < 2; i++ { + p, err := Read(r) + if err != nil { + t.Fatal(err) + } + uat := p.(*UserAttribute) + imgs := uat.ImageData() + if len(imgs) != 1 { + t.Errorf("Unexpected number of images in user attribute packet: %d", len(imgs)) + } + if len(imgs[0]) != 3395 { + t.Errorf("Unexpected JPEG image size: %d", len(imgs[0])) + } + img, err := jpeg.Decode(bytes.NewBuffer(imgs[0])) + if err != nil { + t.Errorf("Error decoding JPEG image: %v", err) + } + // A pixel in my right eye. + pixel := color.NRGBAModel.Convert(img.At(56, 36)) + ref := color.NRGBA{R: 157, G: 128, B: 124, A: 255} + if pixel != ref { + t.Errorf("Unexpected pixel color: %v", pixel) + } + w := bytes.NewBuffer(nil) + err = uat.Serialize(w) + if err != nil { + t.Errorf("Error writing user attribute: %v", err) + } + r = bytes.NewBuffer(w.Bytes()) + } +} + +const userAttributePacket = ` +0cyWzJQBEAABAQAAAAAAAAAAAAAAAP/Y/+AAEEpGSUYAAQIAAAEAAQAA/9sAQwAFAwQEBAMFBAQE +BQUFBgcMCAcHBwcPCgsJDBEPEhIRDxEQExYcFxMUGhUQERghGBocHR8fHxMXIiQiHiQcHh8e/9sA +QwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e +Hh4eHh4eHh4e/8AAEQgAZABkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYH +CAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHw +JDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6 +g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk +5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIB +AgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEX +GBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKT +lJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX2 +9/j5+v/aAAwDAQACEQMRAD8A5uGP06VehQ4pIox04q5EnHSvAep+hIIl4zVuMHGPWmRrUWtalaaN +pU2oXsgSGJSxPr6ClvoitErs0Itqjc7BQOpPAFYmrfEnwjojtHNqaXEynBjtx5hH4jj9a8B8d+Od +W8UXZjWR4LJT+7t0Jwfc+prnIdO1CWZEW2mZ3HyDactXXDB3V5s8evm1namj6r0H4weCLtxG+ova +ueP30RA/MV6not1bX0Ed1ZzxzwyDKvGwZSPqK+Ff+ES8R8t/ZV2oHUmM10Hgbxp4m8BatEfNnWBH +/eWshOxx9Kmpg4te49RUM1kn+8Wh9zQ4P1FaMC7l465rjPh14y0fxnoseoaXOpfaPOgJ+eI98j09 +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` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid.go new file mode 100644 index 0000000..d6bea7d --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid.go @@ -0,0 +1,160 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "io" + "io/ioutil" + "strings" +) + +// UserId contains text that is intended to represent the name and email +// address of the key holder. See RFC 4880, section 5.11. By convention, this +// takes the form "Full Name (Comment) " +type UserId struct { + Id string // By convention, this takes the form "Full Name (Comment) " which is split out in the fields below. + + Name, Comment, Email string +} + +func hasInvalidCharacters(s string) bool { + for _, c := range s { + switch c { + case '(', ')', '<', '>', 0: + return true + } + } + return false +} + +// NewUserId returns a UserId or nil if any of the arguments contain invalid +// characters. The invalid characters are '\x00', '(', ')', '<' and '>' +func NewUserId(name, comment, email string) *UserId { + // RFC 4880 doesn't deal with the structure of userid strings; the + // name, comment and email form is just a convention. However, there's + // no convention about escaping the metacharacters and GPG just refuses + // to create user ids where, say, the name contains a '('. We mirror + // this behaviour. + + if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) { + return nil + } + + uid := new(UserId) + uid.Name, uid.Comment, uid.Email = name, comment, email + uid.Id = name + if len(comment) > 0 { + if len(uid.Id) > 0 { + uid.Id += " " + } + uid.Id += "(" + uid.Id += comment + uid.Id += ")" + } + if len(email) > 0 { + if len(uid.Id) > 0 { + uid.Id += " " + } + uid.Id += "<" + uid.Id += email + uid.Id += ">" + } + return uid +} + +func (uid *UserId) parse(r io.Reader) (err error) { + // RFC 4880, section 5.11 + b, err := ioutil.ReadAll(r) + if err != nil { + return + } + uid.Id = string(b) + uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id) + return +} + +// Serialize marshals uid to w in the form of an OpenPGP packet, including +// header. +func (uid *UserId) Serialize(w io.Writer) error { + err := serializeHeader(w, packetTypeUserId, len(uid.Id)) + if err != nil { + return err + } + _, err = w.Write([]byte(uid.Id)) + return err +} + +// parseUserId extracts the name, comment and email from a user id string that +// is formatted as "Full Name (Comment) ". +func parseUserId(id string) (name, comment, email string) { + var n, c, e struct { + start, end int + } + var state int + + for offset, rune := range id { + switch state { + case 0: + // Entering name + n.start = offset + state = 1 + fallthrough + case 1: + // In name + if rune == '(' { + state = 2 + n.end = offset + } else if rune == '<' { + state = 5 + n.end = offset + } + case 2: + // Entering comment + c.start = offset + state = 3 + fallthrough + case 3: + // In comment + if rune == ')' { + state = 4 + c.end = offset + } + case 4: + // Between comment and email + if rune == '<' { + state = 5 + } + case 5: + // Entering email + e.start = offset + state = 6 + fallthrough + case 6: + // In email + if rune == '>' { + state = 7 + e.end = offset + } + default: + // After email + } + } + switch state { + case 1: + // ended in the name + n.end = len(id) + case 3: + // ended in comment + c.end = len(id) + case 6: + // ended in email + e.end = len(id) + } + + name = strings.TrimSpace(id[n.start:n.end]) + comment = strings.TrimSpace(id[c.start:c.end]) + email = strings.TrimSpace(id[e.start:e.end]) + return +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid_test.go new file mode 100644 index 0000000..2968193 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/userid_test.go @@ -0,0 +1,87 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packet + +import ( + "testing" +) + +var userIdTests = []struct { + id string + name, comment, email string +}{ + {"", "", "", ""}, + {"John Smith", "John Smith", "", ""}, + {"John Smith ()", "John Smith", "", ""}, + {"John Smith () <>", "John Smith", "", ""}, + {"(comment", "", "comment", ""}, + {"(comment)", "", "comment", ""}, + {" sdfk", "", "", "email"}, + {" John Smith ( Comment ) asdkflj < email > lksdfj", "John Smith", "Comment", "email"}, + {" John Smith < email > lksdfj", "John Smith", "", "email"}, + {"("}, + {"foo", "bar", "", "foo (bar)"}, + {"foo", "", "baz", "foo "}, + {"", "bar", "baz", "(bar) "}, + {"foo", "bar", "baz", "foo (bar) "}, +} + +func TestNewUserId(t *testing.T) { + for i, test := range newUserIdTests { + uid := NewUserId(test.name, test.comment, test.email) + if uid == nil { + t.Errorf("#%d: returned nil", i) + continue + } + if uid.Id != test.id { + t.Errorf("#%d: got '%s', want '%s'", i, uid.Id, test.id) + } + } +} + +var invalidNewUserIdTests = []struct { + name, comment, email string +}{ + {"foo(", "", ""}, + {"foo<", "", ""}, + {"", "bar)", ""}, + {"", "bar<", ""}, + {"", "", "baz>"}, + {"", "", "baz)"}, + {"", "", "baz\x00"}, +} + +func TestNewUserIdWithInvalidInput(t *testing.T) { + for i, test := range invalidNewUserIdTests { + if uid := NewUserId(test.name, test.comment, test.email); uid != nil { + t.Errorf("#%d: returned non-nil value: %#v", i, uid) + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go new file mode 100644 index 0000000..08129d7 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go @@ -0,0 +1,423 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package openpgp implements high level operations on OpenPGP messages. +package openpgp + +import ( + "crypto" + _ "crypto/sha256" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" + "hash" + "io" + "strconv" +) + +// SignatureType is the armor type for a PGP signature. +var SignatureType = "PGP SIGNATURE" + +// readArmored reads an armored block with the given type. +func readArmored(r io.Reader, expectedType string) (body io.Reader, err error) { + block, err := armor.Decode(r) + if err != nil { + return + } + + if block.Type != expectedType { + return nil, errors.InvalidArgumentError("expected '" + expectedType + "', got: " + block.Type) + } + + return block.Body, nil +} + +// MessageDetails contains the result of parsing an OpenPGP encrypted and/or +// signed message. +type MessageDetails struct { + IsEncrypted bool // true if the message was encrypted. + EncryptedToKeyIds []uint64 // the list of recipient key ids. + IsSymmetricallyEncrypted bool // true if a passphrase could have decrypted the message. + DecryptedWith Key // the private key used to decrypt the message, if any. + IsSigned bool // true if the message is signed. + SignedByKeyId uint64 // the key id of the signer, if any. + SignedBy *Key // the key of the signer, if available. + LiteralData *packet.LiteralData // the metadata of the contents + UnverifiedBody io.Reader // the contents of the message. + + // If IsSigned is true and SignedBy is non-zero then the signature will + // be verified as UnverifiedBody is read. The signature cannot be + // checked until the whole of UnverifiedBody is read so UnverifiedBody + // must be consumed until EOF before the data can trusted. Even if a + // message isn't signed (or the signer is unknown) the data may contain + // an authentication code that is only checked once UnverifiedBody has + // been consumed. Once EOF has been seen, the following fields are + // valid. (An authentication code failure is reported as a + // SignatureError error when reading from UnverifiedBody.) + SignatureError error // nil if the signature is good. + Signature *packet.Signature // the signature packet itself. + + decrypted io.ReadCloser +} + +// A PromptFunction is used as a callback by functions that may need to decrypt +// a private key, or prompt for a passphrase. It is called with a list of +// acceptable, encrypted private keys and a boolean that indicates whether a +// passphrase is usable. It should either decrypt a private key or return a +// passphrase to try. If the decrypted private key or given passphrase isn't +// correct, the function will be called again, forever. Any error returned will +// be passed up. +type PromptFunction func(keys []Key, symmetric bool) ([]byte, error) + +// A keyEnvelopePair is used to store a private key with the envelope that +// contains a symmetric key, encrypted with that key. +type keyEnvelopePair struct { + key Key + encryptedKey *packet.EncryptedKey +} + +// ReadMessage parses an OpenPGP message that may be signed and/or encrypted. +// The given KeyRing should contain both public keys (for signature +// verification) and, possibly encrypted, private keys for decrypting. +// If config is nil, sensible defaults will be used. +func ReadMessage(r io.Reader, keyring KeyRing, prompt PromptFunction, config *packet.Config) (md *MessageDetails, err error) { + var p packet.Packet + + var symKeys []*packet.SymmetricKeyEncrypted + var pubKeys []keyEnvelopePair + var se *packet.SymmetricallyEncrypted + + packets := packet.NewReader(r) + md = new(MessageDetails) + md.IsEncrypted = true + + // The message, if encrypted, starts with a number of packets + // containing an encrypted decryption key. The decryption key is either + // encrypted to a public key, or with a passphrase. This loop + // collects these packets. +ParsePackets: + for { + p, err = packets.Next() + if err != nil { + return nil, err + } + switch p := p.(type) { + case *packet.SymmetricKeyEncrypted: + // This packet contains the decryption key encrypted with a passphrase. + md.IsSymmetricallyEncrypted = true + symKeys = append(symKeys, p) + case *packet.EncryptedKey: + // This packet contains the decryption key encrypted to a public key. + md.EncryptedToKeyIds = append(md.EncryptedToKeyIds, p.KeyId) + switch p.Algo { + case packet.PubKeyAlgoRSA, packet.PubKeyAlgoRSAEncryptOnly, packet.PubKeyAlgoElGamal: + break + default: + continue + } + var keys []Key + if p.KeyId == 0 { + keys = keyring.DecryptionKeys() + } else { + keys = keyring.KeysById(p.KeyId) + } + for _, k := range keys { + pubKeys = append(pubKeys, keyEnvelopePair{k, p}) + } + case *packet.SymmetricallyEncrypted: + se = p + break ParsePackets + case *packet.Compressed, *packet.LiteralData, *packet.OnePassSignature: + // This message isn't encrypted. + if len(symKeys) != 0 || len(pubKeys) != 0 { + return nil, errors.StructuralError("key material not followed by encrypted message") + } + packets.Unread(p) + return readSignedMessage(packets, nil, keyring) + } + } + + var candidates []Key + var decrypted io.ReadCloser + + // Now that we have the list of encrypted keys we need to decrypt at + // least one of them or, if we cannot, we need to call the prompt + // function so that it can decrypt a key or give us a passphrase. +FindKey: + for { + // See if any of the keys already have a private key available + candidates = candidates[:0] + candidateFingerprints := make(map[string]bool) + + for _, pk := range pubKeys { + if pk.key.PrivateKey == nil { + continue + } + if !pk.key.PrivateKey.Encrypted { + if len(pk.encryptedKey.Key) == 0 { + pk.encryptedKey.Decrypt(pk.key.PrivateKey, config) + } + if len(pk.encryptedKey.Key) == 0 { + continue + } + decrypted, err = se.Decrypt(pk.encryptedKey.CipherFunc, pk.encryptedKey.Key) + if err != nil && err != errors.ErrKeyIncorrect { + return nil, err + } + if decrypted != nil { + md.DecryptedWith = pk.key + break FindKey + } + } else { + fpr := string(pk.key.PublicKey.Fingerprint[:]) + if v := candidateFingerprints[fpr]; v { + continue + } + candidates = append(candidates, pk.key) + candidateFingerprints[fpr] = true + } + } + + if len(candidates) == 0 && len(symKeys) == 0 { + return nil, errors.ErrKeyIncorrect + } + + if prompt == nil { + return nil, errors.ErrKeyIncorrect + } + + passphrase, err := prompt(candidates, len(symKeys) != 0) + if err != nil { + return nil, err + } + + // Try the symmetric passphrase first + if len(symKeys) != 0 && passphrase != nil { + for _, s := range symKeys { + err = s.Decrypt(passphrase) + if err == nil && !s.Encrypted { + decrypted, err = se.Decrypt(s.CipherFunc, s.Key) + if err != nil && err != errors.ErrKeyIncorrect { + return nil, err + } + if decrypted != nil { + break FindKey + } + } + + } + } + } + + md.decrypted = decrypted + packets.Push(decrypted) + return readSignedMessage(packets, md, keyring) +} + +// readSignedMessage reads a possibly signed message if mdin is non-zero then +// that structure is updated and returned. Otherwise a fresh MessageDetails is +// used. +func readSignedMessage(packets *packet.Reader, mdin *MessageDetails, keyring KeyRing) (md *MessageDetails, err error) { + if mdin == nil { + mdin = new(MessageDetails) + } + md = mdin + + var p packet.Packet + var h hash.Hash + var wrappedHash hash.Hash +FindLiteralData: + for { + p, err = packets.Next() + if err != nil { + return nil, err + } + switch p := p.(type) { + case *packet.Compressed: + packets.Push(p.Body) + case *packet.OnePassSignature: + if !p.IsLast { + return nil, errors.UnsupportedError("nested signatures") + } + + h, wrappedHash, err = hashForSignature(p.Hash, p.SigType) + if err != nil { + md = nil + return + } + + md.IsSigned = true + md.SignedByKeyId = p.KeyId + keys := keyring.KeysByIdUsage(p.KeyId, packet.KeyFlagSign) + if len(keys) > 0 { + md.SignedBy = &keys[0] + } + case *packet.LiteralData: + md.LiteralData = p + break FindLiteralData + } + } + + if md.SignedBy != nil { + md.UnverifiedBody = &signatureCheckReader{packets, h, wrappedHash, md} + } else if md.decrypted != nil { + md.UnverifiedBody = checkReader{md} + } else { + md.UnverifiedBody = md.LiteralData.Body + } + + return md, nil +} + +// hashForSignature returns a pair of hashes that can be used to verify a +// signature. The signature may specify that the contents of the signed message +// should be preprocessed (i.e. to normalize line endings). Thus this function +// returns two hashes. The second should be used to hash the message itself and +// performs any needed preprocessing. +func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) { + if !hashId.Available() { + return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId))) + } + h := hashId.New() + + switch sigType { + case packet.SigTypeBinary: + return h, h, nil + case packet.SigTypeText: + return h, NewCanonicalTextHash(h), nil + } + + return nil, nil, errors.UnsupportedError("unsupported signature type: " + strconv.Itoa(int(sigType))) +} + +// checkReader wraps an io.Reader from a LiteralData packet. When it sees EOF +// it closes the ReadCloser from any SymmetricallyEncrypted packet to trigger +// MDC checks. +type checkReader struct { + md *MessageDetails +} + +func (cr checkReader) Read(buf []byte) (n int, err error) { + n, err = cr.md.LiteralData.Body.Read(buf) + if err == io.EOF { + mdcErr := cr.md.decrypted.Close() + if mdcErr != nil { + err = mdcErr + } + } + return +} + +// signatureCheckReader wraps an io.Reader from a LiteralData packet and hashes +// the data as it is read. When it sees an EOF from the underlying io.Reader +// it parses and checks a trailing Signature packet and triggers any MDC checks. +type signatureCheckReader struct { + packets *packet.Reader + h, wrappedHash hash.Hash + md *MessageDetails +} + +func (scr *signatureCheckReader) Read(buf []byte) (n int, err error) { + n, err = scr.md.LiteralData.Body.Read(buf) + scr.wrappedHash.Write(buf[:n]) + if err == io.EOF { + var p packet.Packet + p, scr.md.SignatureError = scr.packets.Next() + if scr.md.SignatureError != nil { + return + } + + var ok bool + if scr.md.Signature, ok = p.(*packet.Signature); !ok { + scr.md.SignatureError = errors.StructuralError("LiteralData not followed by Signature") + return + } + + scr.md.SignatureError = scr.md.SignedBy.PublicKey.VerifySignature(scr.h, scr.md.Signature) + + // The SymmetricallyEncrypted packet, if any, might have an + // unsigned hash of its own. In order to check this we need to + // close that Reader. + if scr.md.decrypted != nil { + mdcErr := scr.md.decrypted.Close() + if mdcErr != nil { + err = mdcErr + } + } + } + return +} + +// CheckDetachedSignature takes a signed file and a detached signature and +// returns the signer if the signature is valid. If the signer isn't known, +// ErrUnknownIssuer is returned. +func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) { + p, err := packet.Read(signature) + if err != nil { + return + } + + var issuerKeyId uint64 + var hashFunc crypto.Hash + var sigType packet.SignatureType + + switch sig := p.(type) { + case *packet.Signature: + if sig.IssuerKeyId == nil { + return nil, errors.StructuralError("signature doesn't have an issuer") + } + issuerKeyId = *sig.IssuerKeyId + hashFunc = sig.Hash + sigType = sig.SigType + case *packet.SignatureV3: + issuerKeyId = sig.IssuerKeyId + hashFunc = sig.Hash + sigType = sig.SigType + default: + return nil, errors.StructuralError("non signature packet found") + } + + h, wrappedHash, err := hashForSignature(hashFunc, sigType) + if err != nil { + return + } + + _, err = io.Copy(wrappedHash, signed) + if err != nil && err != io.EOF { + return + } + + keys := keyring.KeysByIdUsage(issuerKeyId, packet.KeyFlagSign) + if len(keys) == 0 { + return nil, errors.ErrUnknownIssuer + } + + for _, key := range keys { + switch sig := p.(type) { + case *packet.Signature: + err = key.PublicKey.VerifySignature(h, sig) + case *packet.SignatureV3: + err = key.PublicKey.VerifySignatureV3(h, sig) + } + if err == nil { + return key.Entity, nil + } + } + + if err == nil { + err = errors.ErrUnknownIssuer + } + return nil, err +} + +// CheckArmoredDetachedSignature performs the same actions as +// CheckDetachedSignature but expects the signature to be armored. +func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader) (signer *Entity, err error) { + body, err := readArmored(signature, SignatureType) + if err != nil { + return + } + + return CheckDetachedSignature(keyring, signed, body) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go new file mode 100644 index 0000000..c2720ae --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go @@ -0,0 +1,416 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import ( + "bytes" + _ "crypto/sha512" + "encoding/hex" + "golang.org/x/crypto/openpgp/errors" + "io" + "io/ioutil" + "strings" + "testing" +) + +func readerFromHex(s string) io.Reader { + data, err := hex.DecodeString(s) + if err != nil { + panic("readerFromHex: bad input") + } + return bytes.NewBuffer(data) +} + +func TestReadKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestRereadKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + if err != nil { + t.Errorf("error in initial parse: %s", err) + return + } + out := new(bytes.Buffer) + err = kring[0].Serialize(out) + if err != nil { + t.Errorf("error in serialization: %s", err) + return + } + kring, err = ReadKeyRing(out) + if err != nil { + t.Errorf("error in second parse: %s", err) + return + } + + if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestReadPrivateKeyRing(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 2 || uint32(kring[0].PrimaryKey.KeyId) != 0xC20C31BB || uint32(kring[1].PrimaryKey.KeyId) != 0x1E35246B || kring[0].PrimaryKey == nil { + t.Errorf("bad keyring: %#v", kring) + } +} + +func TestReadDSAKey(t *testing.T) { + kring, err := ReadKeyRing(readerFromHex(dsaTestKeyHex)) + if err != nil { + t.Error(err) + return + } + if len(kring) != 1 || uint32(kring[0].PrimaryKey.KeyId) != 0x0CCC0360 { + t.Errorf("bad parse: %#v", kring) + } +} + +func TestDSAHashTruncatation(t *testing.T) { + // dsaKeyWithSHA512 was generated with GnuPG and --cert-digest-algo + // SHA512 in order to require DSA hash truncation to verify correctly. + _, err := ReadKeyRing(readerFromHex(dsaKeyWithSHA512)) + if err != nil { + t.Error(err) + } +} + +func TestGetKeyById(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + + keys := kring.KeysById(0xa34d7e18c20c31bb) + if len(keys) != 1 || keys[0].Entity != kring[0] { + t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys) + } + + keys = kring.KeysById(0xfd94408d4543314f) + if len(keys) != 1 || keys[0].Entity != kring[0] { + t.Errorf("bad result for 0xa34d7e18c20c31bb: %#v", keys) + } +} + +func checkSignedMessage(t *testing.T, signedHex, expected string) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + + md, err := ReadMessage(readerFromHex(signedHex), kring, nil, nil) + if err != nil { + t.Error(err) + return + } + + if !md.IsSigned || md.SignedByKeyId != 0xa34d7e18c20c31bb || md.SignedBy == nil || md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) != 0 || md.IsSymmetricallyEncrypted { + t.Errorf("bad MessageDetails: %#v", md) + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + if string(contents) != expected { + t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected) + } + if md.SignatureError != nil || md.Signature == nil { + t.Errorf("failed to validate: %s", md.SignatureError) + } +} + +func TestSignedMessage(t *testing.T) { + checkSignedMessage(t, signedMessageHex, signedInput) +} + +func TestTextSignedMessage(t *testing.T) { + checkSignedMessage(t, signedTextMessageHex, signedTextInput) +} + +var signedEncryptedMessageTests = []struct { + keyRingHex string + messageHex string + signedByKeyId uint64 + encryptedToKeyId uint64 +}{ + { + testKeys1And2PrivateHex, + signedEncryptedMessageHex, + 0xa34d7e18c20c31bb, + 0x2a67d68660df41c7, + }, + { + dsaElGamalTestKeysHex, + signedEncryptedMessage2Hex, + 0x33af447ccd759b09, + 0xcf6a7abcd43e3673, + }, +} + +func TestSignedEncryptedMessage(t *testing.T) { + for i, test := range signedEncryptedMessageTests { + expected := "Signed and encrypted message\n" + kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex)) + prompt := func(keys []Key, symmetric bool) ([]byte, error) { + if symmetric { + t.Errorf("prompt: message was marked as symmetrically encrypted") + return nil, errors.ErrKeyIncorrect + } + + if len(keys) == 0 { + t.Error("prompt: no keys requested") + return nil, errors.ErrKeyIncorrect + } + + err := keys[0].PrivateKey.Decrypt([]byte("passphrase")) + if err != nil { + t.Errorf("prompt: error decrypting key: %s", err) + return nil, errors.ErrKeyIncorrect + } + + return nil, nil + } + + md, err := ReadMessage(readerFromHex(test.messageHex), kring, prompt, nil) + if err != nil { + t.Errorf("#%d: error reading message: %s", i, err) + return + } + + if !md.IsSigned || md.SignedByKeyId != test.signedByKeyId || md.SignedBy == nil || !md.IsEncrypted || md.IsSymmetricallyEncrypted || len(md.EncryptedToKeyIds) == 0 || md.EncryptedToKeyIds[0] != test.encryptedToKeyId { + t.Errorf("#%d: bad MessageDetails: %#v", i, md) + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("#%d: error reading UnverifiedBody: %s", i, err) + } + if string(contents) != expected { + t.Errorf("#%d: bad UnverifiedBody got:%s want:%s", i, string(contents), expected) + } + + if md.SignatureError != nil || md.Signature == nil { + t.Errorf("#%d: failed to validate: %s", i, md.SignatureError) + } + } +} + +func TestUnspecifiedRecipient(t *testing.T) { + expected := "Recipient unspecified\n" + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + + md, err := ReadMessage(readerFromHex(recipientUnspecifiedHex), kring, nil, nil) + if err != nil { + t.Errorf("error reading message: %s", err) + return + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("error reading UnverifiedBody: %s", err) + } + if string(contents) != expected { + t.Errorf("bad UnverifiedBody got:%s want:%s", string(contents), expected) + } +} + +func TestSymmetricallyEncrypted(t *testing.T) { + expected := "Symmetrically encrypted.\n" + + prompt := func(keys []Key, symmetric bool) ([]byte, error) { + if len(keys) != 0 { + t.Errorf("prompt: len(keys) = %d (want 0)", len(keys)) + } + + if !symmetric { + t.Errorf("symmetric is not set") + } + + return []byte("password"), nil + } + + md, err := ReadMessage(readerFromHex(symmetricallyEncryptedCompressedHex), nil, prompt, nil) + if err != nil { + t.Errorf("ReadMessage: %s", err) + return + } + + contents, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("ReadAll: %s", err) + } + + expectedCreationTime := uint32(1295992998) + if md.LiteralData.Time != expectedCreationTime { + t.Errorf("LiteralData.Time is %d, want %d", md.LiteralData.Time, expectedCreationTime) + } + + if string(contents) != expected { + t.Errorf("contents got: %s want: %s", string(contents), expected) + } +} + +func testDetachedSignature(t *testing.T, kring KeyRing, signature io.Reader, sigInput, tag string, expectedSignerKeyId uint64) { + signed := bytes.NewBufferString(sigInput) + signer, err := CheckDetachedSignature(kring, signed, signature) + if err != nil { + t.Errorf("%s: signature error: %s", tag, err) + return + } + if signer == nil { + t.Errorf("%s: signer is nil", tag) + return + } + if signer.PrimaryKey.KeyId != expectedSignerKeyId { + t.Errorf("%s: wrong signer got:%x want:%x", tag, signer.PrimaryKey.KeyId, expectedSignerKeyId) + } +} + +func TestDetachedSignature(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureHex), signedInput, "binary", testKey1KeyId) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureTextHex), signedInput, "text", testKey1KeyId) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureV3TextHex), signedInput, "v3", testKey1KeyId) + + incorrectSignedInput := signedInput + "X" + _, err := CheckDetachedSignature(kring, bytes.NewBufferString(incorrectSignedInput), readerFromHex(detachedSignatureHex)) + if err == nil { + t.Fatal("CheckDetachedSignature returned without error for bad signature") + } + if err == errors.ErrUnknownIssuer { + t.Fatal("CheckDetachedSignature returned ErrUnknownIssuer when the signer was known, but the signature invalid") + } +} + +func TestDetachedSignatureDSA(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyHex)) + testDetachedSignature(t, kring, readerFromHex(detachedSignatureDSAHex), signedInput, "binary", testKey3KeyId) +} + +func testHashFunctionError(t *testing.T, signatureHex string) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2Hex)) + _, err := CheckDetachedSignature(kring, nil, readerFromHex(signatureHex)) + if err == nil { + t.Fatal("Packet with bad hash type was correctly parsed") + } + unsupported, ok := err.(errors.UnsupportedError) + if !ok { + t.Fatalf("Unexpected class of error: %s", err) + } + if !strings.Contains(string(unsupported), "hash ") { + t.Fatalf("Unexpected error: %s", err) + } +} + +func TestUnknownHashFunction(t *testing.T) { + // unknownHashFunctionHex contains a signature packet with hash + // function type 153 (which isn't a real hash function id). + testHashFunctionError(t, unknownHashFunctionHex) +} + +func TestMissingHashFunction(t *testing.T) { + // missingHashFunctionHex contains a signature packet that uses + // RIPEMD160, which isn't compiled in. + testHashFunctionError(t, missingHashFunctionHex) +} + +func TestReadingArmoredPrivateKey(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKeyBlock)) + if err != nil { + t.Error(err) + } + if len(el) != 1 { + t.Errorf("got %d entities, wanted 1\n", len(el)) + } +} + +func TestNoArmoredData(t *testing.T) { + _, err := ReadArmoredKeyRing(bytes.NewBufferString("foo")) + if _, ok := err.(errors.InvalidArgumentError); !ok { + t.Errorf("error was not an InvalidArgumentError: %s", err) + } +} + +const testKey1KeyId = 0xA34D7E18C20C31BB +const testKey3KeyId = 0x338934250CCC0360 + +const signedInput = "Signed message\nline 2\nline 3\n" +const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n" + +const recipientUnspecifiedHex = "848c0300000000000000000103ff62d4d578d03cf40c3da998dfe216c074fa6ddec5e31c197c9666ba292830d91d18716a80f699f9d897389a90e6d62d0238f5f07a5248073c0f24920e4bc4a30c2d17ee4e0cae7c3d4aaa4e8dced50e3010a80ee692175fa0385f62ecca4b56ee6e9980aa3ec51b61b077096ac9e800edaf161268593eedb6cc7027ff5cb32745d250010d407a6221ae22ef18469b444f2822478c4d190b24d36371a95cb40087cdd42d9399c3d06a53c0673349bfb607927f20d1e122bde1e2bf3aa6cae6edf489629bcaa0689539ae3b718914d88ededc3b" + +const detachedSignatureHex = "889c04000102000605024d449cd1000a0910a34d7e18c20c31bb167603ff57718d09f28a519fdc7b5a68b6a3336da04df85e38c5cd5d5bd2092fa4629848a33d85b1729402a2aab39c3ac19f9d573f773cc62c264dc924c067a79dfd8a863ae06c7c8686120760749f5fd9b1e03a64d20a7df3446ddc8f0aeadeaeba7cbaee5c1e366d65b6a0c6cc749bcb912d2f15013f812795c2e29eb7f7b77f39ce77" + +const detachedSignatureTextHex = "889c04010102000605024d449d21000a0910a34d7e18c20c31bbc8c60400a24fbef7342603a41cb1165767bd18985d015fb72fe05db42db36cfb2f1d455967f1e491194fbf6cf88146222b23bf6ffbd50d17598d976a0417d3192ff9cc0034fd00f287b02e90418bbefe609484b09231e4e7a5f3562e199bf39909ab5276c4d37382fe088f6b5c3426fc1052865da8b3ab158672d58b6264b10823dc4b39" + +const detachedSignatureV3TextHex = "8900950305005255c25ca34d7e18c20c31bb0102bb3f04009f6589ef8a028d6e54f6eaf25432e590d31c3a41f4710897585e10c31e5e332c7f9f409af8512adceaff24d0da1474ab07aa7bce4f674610b010fccc5b579ae5eb00a127f272fb799f988ab8e4574c141da6dbfecfef7e6b2c478d9a3d2551ba741f260ee22bec762812f0053e05380bfdd55ad0f22d8cdf71b233fe51ae8a24" + +const detachedSignatureDSAHex = "884604001102000605024d6c4eac000a0910338934250ccc0360f18d00a087d743d6405ed7b87755476629600b8b694a39e900a0abff8126f46faf1547c1743c37b21b4ea15b8f83" + +const testKeys1And2Hex = "988d044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd0011010001b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b0020003b88d044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f0011010001889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab0020003988d044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b0020003b88d044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020003" + +const testKeys1And2PrivateHex = "9501d8044d3c5c10010400b1d13382944bd5aba23a4312968b5095d14f947f600eb478e14a6fcb16b0e0cac764884909c020bc495cfcc39a935387c661507bdb236a0612fb582cac3af9b29cc2c8c70090616c41b662f4da4c1201e195472eb7f4ae1ccbcbf9940fe21d985e379a5563dde5b9a23d35f1cfaa5790da3b79db26f23695107bfaca8e7b5bcd00110100010003ff4d91393b9a8e3430b14d6209df42f98dc927425b881f1209f319220841273a802a97c7bdb8b3a7740b3ab5866c4d1d308ad0d3a79bd1e883aacf1ac92dfe720285d10d08752a7efe3c609b1d00f17f2805b217be53999a7da7e493bfc3e9618fd17018991b8128aea70a05dbce30e4fbe626aa45775fa255dd9177aabf4df7cf0200c1ded12566e4bc2bb590455e5becfb2e2c9796482270a943343a7835de41080582c2be3caf5981aa838140e97afa40ad652a0b544f83eb1833b0957dce26e47b0200eacd6046741e9ce2ec5beb6fb5e6335457844fb09477f83b050a96be7da043e17f3a9523567ed40e7a521f818813a8b8a72209f1442844843ccc7eb9805442570200bdafe0438d97ac36e773c7162028d65844c4d463e2420aa2228c6e50dc2743c3d6c72d0d782a5173fe7be2169c8a9f4ef8a7cf3e37165e8c61b89c346cdc6c1799d2b41054657374204b6579203120285253412988b804130102002205024d3c5c10021b03060b090807030206150802090a0b0416020301021e01021780000a0910a34d7e18c20c31bbb5b304009cc45fe610b641a2c146331be94dade0a396e73ca725e1b25c21708d9cab46ecca5ccebc23055879df8f99eea39b377962a400f2ebdc36a7c99c333d74aeba346315137c3ff9d0a09b0273299090343048afb8107cf94cbd1400e3026f0ccac7ecebbc4d78588eb3e478fe2754d3ca664bcf3eac96ca4a6b0c8d7df5102f60f6b00200009d01d8044d3c5c10010400b201df61d67487301f11879d514f4248ade90c8f68c7af1284c161098de4c28c2850f1ec7b8e30f959793e571542ffc6532189409cb51c3d30dad78c4ad5165eda18b20d9826d8707d0f742e2ab492103a85bbd9ddf4f5720f6de7064feb0d39ee002219765bb07bcfb8b877f47abe270ddeda4f676108cecb6b9bb2ad484a4f00110100010003fd17a7490c22a79c59281fb7b20f5e6553ec0c1637ae382e8adaea295f50241037f8997cf42c1ce26417e015091451b15424b2c59eb8d4161b0975630408e394d3b00f88d4b4e18e2cc85e8251d4753a27c639c83f5ad4a571c4f19d7cd460b9b73c25ade730c99df09637bd173d8e3e981ac64432078263bb6dc30d3e974150dd0200d0ee05be3d4604d2146fb0457f31ba17c057560785aa804e8ca5530a7cd81d3440d0f4ba6851efcfd3954b7e68908fc0ba47f7ac37bf559c6c168b70d3a7c8cd0200da1c677c4bce06a068070f2b3733b0a714e88d62aa3f9a26c6f5216d48d5c2b5624144f3807c0df30be66b3268eeeca4df1fbded58faf49fc95dc3c35f134f8b01fd1396b6c0fc1b6c4f0eb8f5e44b8eace1e6073e20d0b8bc5385f86f1cf3f050f66af789f3ef1fc107b7f4421e19e0349c730c68f0a226981f4e889054fdb4dc149e8e889f04180102000905024d3c5c10021b0c000a0910a34d7e18c20c31bb1a03040085c8d62e16d05dc4e9dad64953c8a2eed8b6c12f92b1575eeaa6dcf7be9473dd5b24b37b6dffbb4e7c99ed1bd3cb11634be19b3e6e207bed7505c7ca111ccf47cb323bf1f8851eb6360e8034cbff8dd149993c959de89f8f77f38e7e98b8e3076323aa719328e2b408db5ec0d03936efd57422ba04f925cdc7b4c1af7590e40ab00200009501fe044d3c5c33010400b488c3e5f83f4d561f317817538d9d0397981e9aef1321ca68ebfae1cf8b7d388e19f4b5a24a82e2fbbf1c6c26557a6c5845307a03d815756f564ac7325b02bc83e87d5480a8fae848f07cb891f2d51ce7df83dcafdc12324517c86d472cc0ee10d47a68fd1d9ae49a6c19bbd36d82af597a0d88cc9c49de9df4e696fc1f0b5d0011010001fe030302e9030f3c783e14856063f16938530e148bc57a7aa3f3e4f90df9dceccdc779bc0835e1ad3d006e4a8d7b36d08b8e0de5a0d947254ecfbd22037e6572b426bcfdc517796b224b0036ff90bc574b5509bede85512f2eefb520fb4b02aa523ba739bff424a6fe81c5041f253f8d757e69a503d3563a104d0d49e9e890b9d0c26f96b55b743883b472caa7050c4acfd4a21f875bdf1258d88bd61224d303dc9df77f743137d51e6d5246b88c406780528fd9a3e15bab5452e5b93970d9dcc79f48b38651b9f15bfbcf6da452837e9cc70683d1bdca94507870f743e4ad902005812488dd342f836e72869afd00ce1850eea4cfa53ce10e3608e13d3c149394ee3cbd0e23d018fcbcb6e2ec5a1a22972d1d462ca05355d0d290dd2751e550d5efb38c6c89686344df64852bf4ff86638708f644e8ec6bd4af9b50d8541cb91891a431326ab2e332faa7ae86cfb6e0540aa63160c1e5cdd5a4add518b303fff0a20117c6bc77f7cfbaf36b04c865c6c2b42754657374204b6579203220285253412c20656e637279707465642070726976617465206b65792988b804130102002205024d3c5c33021b03060b090807030206150802090a0b0416020301021e01021780000a0910d4984f961e35246b98940400908a73b6a6169f700434f076c6c79015a49bee37130eaf23aaa3cfa9ce60bfe4acaa7bc95f1146ada5867e0079babb38804891f4f0b8ebca57a86b249dee786161a755b7a342e68ccf3f78ed6440a93a6626beb9a37aa66afcd4f888790cb4bb46d94a4ae3eb3d7d3e6b00f6bfec940303e89ec5b32a1eaaacce66497d539328b00200009d01fe044d3c5c33010400a4e913f9442abcc7f1804ccab27d2f787ffa592077ca935a8bb23165bd8d57576acac647cc596b2c3f814518cc8c82953c7a4478f32e0cf645630a5ba38d9618ef2bc3add69d459ae3dece5cab778938d988239f8c5ae437807075e06c828019959c644ff05ef6a5a1dab72227c98e3a040b0cf219026640698d7a13d8538a570011010001fe030302e9030f3c783e148560f936097339ae381d63116efcf802ff8b1c9360767db5219cc987375702a4123fd8657d3e22700f23f95020d1b261eda5257e9a72f9a918e8ef22dd5b3323ae03bbc1923dd224db988cadc16acc04b120a9f8b7e84da9716c53e0334d7b66586ddb9014df604b41be1e960dcfcbc96f4ed150a1a0dd070b9eb14276b9b6be413a769a75b519a53d3ecc0c220e85cd91ca354d57e7344517e64b43b6e29823cbd87eae26e2b2e78e6dedfbb76e3e9f77bcb844f9a8932eb3db2c3f9e44316e6f5d60e9e2a56e46b72abe6b06dc9a31cc63f10023d1f5e12d2a3ee93b675c96f504af0001220991c88db759e231b3320dcedf814dcf723fd9857e3d72d66a0f2af26950b915abdf56c1596f46a325bf17ad4810d3535fb02a259b247ac3dbd4cc3ecf9c51b6c07cebb009c1506fba0a89321ec8683e3fd009a6e551d50243e2d5092fefb3321083a4bad91320dc624bd6b5dddf93553e3d53924c05bfebec1fb4bd47e89a1a889f04180102000905024d3c5c33021b0c000a0910d4984f961e35246b26c703ff7ee29ef53bc1ae1ead533c408fa136db508434e233d6e62be621e031e5940bbd4c08142aed0f82217e7c3e1ec8de574bc06ccf3c36633be41ad78a9eacd209f861cae7b064100758545cc9dd83db71806dc1cfd5fb9ae5c7474bba0c19c44034ae61bae5eca379383339dece94ff56ff7aa44a582f3e5c38f45763af577c0934b0020000" + +const dsaElGamalTestKeysHex = "9501e1044dfcb16a110400aa3e5c1a1f43dd28c2ffae8abf5cfce555ee874134d8ba0a0f7b868ce2214beddc74e5e1e21ded354a95d18acdaf69e5e342371a71fbb9093162e0c5f3427de413a7f2c157d83f5cd2f9d791256dc4f6f0e13f13c3302af27f2384075ab3021dff7a050e14854bbde0a1094174855fc02f0bae8e00a340d94a1f22b32e48485700a0cec672ac21258fb95f61de2ce1af74b2c4fa3e6703ff698edc9be22c02ae4d916e4fa223f819d46582c0516235848a77b577ea49018dcd5e9e15cff9dbb4663a1ae6dd7580fa40946d40c05f72814b0f88481207e6c0832c3bded4853ebba0a7e3bd8e8c66df33d5a537cd4acf946d1080e7a3dcea679cb2b11a72a33a2b6a9dc85f466ad2ddf4c3db6283fa645343286971e3dd700703fc0c4e290d45767f370831a90187e74e9972aae5bff488eeff7d620af0362bfb95c1a6c3413ab5d15a2e4139e5d07a54d72583914661ed6a87cce810be28a0aa8879a2dd39e52fb6fe800f4f181ac7e328f740cde3d09a05cecf9483e4cca4253e60d4429ffd679d9996a520012aad119878c941e3cf151459873bdfc2a9563472fe0303027a728f9feb3b864260a1babe83925ce794710cfd642ee4ae0e5b9d74cee49e9c67b6cd0ea5dfbb582132195a121356a1513e1bca73e5b80c58c7ccb4164453412f456c47616d616c2054657374204b65792031886204131102002205024dfcb16a021b03060b090807030206150802090a0b0416020301021e01021780000a091033af447ccd759b09fadd00a0b8fd6f5a790bad7e9f2dbb7632046dc4493588db009c087c6a9ba9f7f49fab221587a74788c00db4889ab00200009d0157044dfcb16a1004008dec3f9291205255ccff8c532318133a6840739dd68b03ba942676f9038612071447bf07d00d559c5c0875724ea16a4c774f80d8338b55fca691a0522e530e604215b467bbc9ccfd483a1da99d7bc2648b4318fdbd27766fc8bfad3fddb37c62b8ae7ccfe9577e9b8d1e77c1d417ed2c2ef02d52f4da11600d85d3229607943700030503ff506c94c87c8cab778e963b76cf63770f0a79bf48fb49d3b4e52234620fc9f7657f9f8d56c96a2b7c7826ae6b57ebb2221a3fe154b03b6637cea7e6d98e3e45d87cf8dc432f723d3d71f89c5192ac8d7290684d2c25ce55846a80c9a7823f6acd9bb29fa6cd71f20bc90eccfca20451d0c976e460e672b000df49466408d527affe0303027a728f9feb3b864260abd761730327bca2aaa4ea0525c175e92bf240682a0e83b226f97ecb2e935b62c9a133858ce31b271fa8eb41f6a1b3cd72a63025ce1a75ee4180dcc284884904181102000905024dfcb16a021b0c000a091033af447ccd759b09dd0b009e3c3e7296092c81bee5a19929462caaf2fff3ae26009e218c437a2340e7ea628149af1ec98ec091a43992b00200009501e1044dfcb1be1104009f61faa61aa43df75d128cbe53de528c4aec49ce9360c992e70c77072ad5623de0a3a6212771b66b39a30dad6781799e92608316900518ec01184a85d872365b7d2ba4bacfb5882ea3c2473d3750dc6178cc1cf82147fb58caa28b28e9f12f6d1efcb0534abed644156c91cca4ab78834268495160b2400bc422beb37d237c2300a0cac94911b6d493bda1e1fbc6feeca7cb7421d34b03fe22cec6ccb39675bb7b94a335c2b7be888fd3906a1125f33301d8aa6ec6ee6878f46f73961c8d57a3e9544d8ef2a2cbfd4d52da665b1266928cfe4cb347a58c412815f3b2d2369dec04b41ac9a71cc9547426d5ab941cccf3b18575637ccfb42df1a802df3cfe0a999f9e7109331170e3a221991bf868543960f8c816c28097e503fe319db10fb98049f3a57d7c80c420da66d56f3644371631fad3f0ff4040a19a4fedc2d07727a1b27576f75a4d28c47d8246f27071e12d7a8de62aad216ddbae6aa02efd6b8a3e2818cda48526549791ab277e447b3a36c57cefe9b592f5eab73959743fcc8e83cbefec03a329b55018b53eec196765ae40ef9e20521a603c551efe0303020950d53a146bf9c66034d00c23130cce95576a2ff78016ca471276e8227fb30b1ffbd92e61804fb0c3eff9e30b1a826ee8f3e4730b4d86273ca977b4164453412f456c47616d616c2054657374204b65792032886204131102002205024dfcb1be021b03060b090807030206150802090a0b0416020301021e01021780000a0910a86bf526325b21b22bd9009e34511620415c974750a20df5cb56b182f3b48e6600a0a9466cb1a1305a84953445f77d461593f1d42bc1b00200009d0157044dfcb1be1004009565a951da1ee87119d600c077198f1c1bceb0f7aa54552489298e41ff788fa8f0d43a69871f0f6f77ebdfb14a4260cf9fbeb65d5844b4272a1904dd95136d06c3da745dc46327dd44a0f16f60135914368c8039a34033862261806bb2c5ce1152e2840254697872c85441ccb7321431d75a747a4bfb1d2c66362b51ce76311700030503fc0ea76601c196768070b7365a200e6ddb09307f262d5f39eec467b5f5784e22abdf1aa49226f59ab37cb49969d8f5230ea65caf56015abda62604544ed526c5c522bf92bed178a078789f6c807b6d34885688024a5bed9e9f8c58d11d4b82487b44c5f470c5606806a0443b79cadb45e0f897a561a53f724e5349b9267c75ca17fe0303020950d53a146bf9c660bc5f4ce8f072465e2d2466434320c1e712272fafc20e342fe7608101580fa1a1a367e60486a7cd1246b7ef5586cf5e10b32762b710a30144f12dd17dd4884904181102000905024dfcb1be021b0c000a0910a86bf526325b21b2904c00a0b2b66b4b39ccffda1d10f3ea8d58f827e30a8b8e009f4255b2d8112a184e40cde43a34e8655ca7809370b0020000" + +const signedMessageHex = "a3019bc0cbccc0c4b8d8b74ee2108fe16ec6d3ca490cbe362d3f8333d3f352531472538b8b13d353b97232f352158c20943157c71c16064626063656269052062e4e01987e9b6fccff4b7df3a34c534b23e679cbec3bc0f8f6e64dfb4b55fe3f8efa9ce110ddb5cd79faf1d753c51aecfa669f7e7aa043436596cccc3359cb7dd6bbe9ecaa69e5989d9e57209571edc0b2fa7f57b9b79a64ee6e99ce1371395fee92fec2796f7b15a77c386ff668ee27f6d38f0baa6c438b561657377bf6acff3c5947befd7bf4c196252f1d6e5c524d0300" + +const signedTextMessageHex = "a3019bc0cbccc8c4b8d8b74ee2108fe16ec6d36a250cbece0c178233d3f352531472538b8b13d35379b97232f352158ca0b4312f57c71c1646462606365626906a062e4e019811591798ff99bf8afee860b0d8a8c2a85c3387e3bcf0bb3b17987f2bbcfab2aa526d930cbfd3d98757184df3995c9f3e7790e36e3e9779f06089d4c64e9e47dd6202cb6e9bc73c5d11bb59fbaf89d22d8dc7cf199ddf17af96e77c5f65f9bbed56f427bd8db7af37f6c9984bf9385efaf5f184f986fb3e6adb0ecfe35bbf92d16a7aa2a344fb0bc52fb7624f0200" + +const signedEncryptedMessageHex = "848c032a67d68660df41c70103ff5789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8d2c03b018bd210b1d3791e1aba74b0f1034e122ab72e760492c192383cf5e20b5628bd043272d63df9b923f147eb6091cd897553204832aba48fec54aa447547bb16305a1024713b90e77fd0065f1918271947549205af3c74891af22ee0b56cd29bfec6d6e351901cd4ab3ece7c486f1e32a792d4e474aed98ee84b3f591c7dff37b64e0ecd68fd036d517e412dcadf85840ce184ad7921ad446c4ee28db80447aea1ca8d4f574db4d4e37688158ddd19e14ee2eab4873d46947d65d14a23e788d912cf9a19624ca7352469b72a83866b7c23cb5ace3deab3c7018061b0ba0f39ed2befe27163e5083cf9b8271e3e3d52cc7ad6e2a3bd81d4c3d7022f8d" + +const signedEncryptedMessage2Hex = "85010e03cf6a7abcd43e36731003fb057f5495b79db367e277cdbe4ab90d924ddee0c0381494112ff8c1238fb0184af35d1731573b01bc4c55ecacd2aafbe2003d36310487d1ecc9ac994f3fada7f9f7f5c3a64248ab7782906c82c6ff1303b69a84d9a9529c31ecafbcdb9ba87e05439897d87e8a2a3dec55e14df19bba7f7bd316291c002ae2efd24f83f9e3441203fc081c0c23dc3092a454ca8a082b27f631abf73aca341686982e8fbda7e0e7d863941d68f3de4a755c2964407f4b5e0477b3196b8c93d551dd23c8beef7d0f03fbb1b6066f78907faf4bf1677d8fcec72651124080e0b7feae6b476e72ab207d38d90b958759fdedfc3c6c35717c9dbfc979b3cfbbff0a76d24a5e57056bb88acbd2a901ef64bc6e4db02adc05b6250ff378de81dca18c1910ab257dff1b9771b85bb9bbe0a69f5989e6d1710a35e6dfcceb7d8fb5ccea8db3932b3d9ff3fe0d327597c68b3622aec8e3716c83a6c93f497543b459b58ba504ed6bcaa747d37d2ca746fe49ae0a6ce4a8b694234e941b5159ff8bd34b9023da2814076163b86f40eed7c9472f81b551452d5ab87004a373c0172ec87ea6ce42ccfa7dbdad66b745496c4873d8019e8c28d6b3" + +const symmetricallyEncryptedCompressedHex = "8c0d04030302eb4a03808145d0d260c92f714339e13de5a79881216431925bf67ee2898ea61815f07894cd0703c50d0a76ef64d482196f47a8bc729af9b80bb6" + +const dsaTestKeyHex = "9901a2044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" + +const dsaTestKeyPrivateHex = "9501bb044d6c49de110400cb5ce438cf9250907ac2ba5bf6547931270b89f7c4b53d9d09f4d0213a5ef2ec1f26806d3d259960f872a4a102ef1581ea3f6d6882d15134f21ef6a84de933cc34c47cc9106efe3bd84c6aec12e78523661e29bc1a61f0aab17fa58a627fd5fd33f5149153fbe8cd70edf3d963bc287ef875270ff14b5bfdd1bca4483793923b00a0fe46d76cb6e4cbdc568435cd5480af3266d610d303fe33ae8273f30a96d4d34f42fa28ce1112d425b2e3bf7ea553d526e2db6b9255e9dc7419045ce817214d1a0056dbc8d5289956a4b1b69f20f1105124096e6a438f41f2e2495923b0f34b70642607d45559595c7fe94d7fa85fc41bf7d68c1fd509ebeaa5f315f6059a446b9369c277597e4f474a9591535354c7e7f4fd98a08aa60400b130c24ff20bdfbf683313f5daebf1c9b34b3bdadfc77f2ddd72ee1fb17e56c473664bc21d66467655dd74b9005e3a2bacce446f1920cd7017231ae447b67036c9b431b8179deacd5120262d894c26bc015bffe3d827ba7087ad9b700d2ca1f6d16cc1786581e5dd065f293c31209300f9b0afcc3f7c08dd26d0a22d87580b4d00009f592e0619d823953577d4503061706843317e4fee083db41054657374204b65792033202844534129886204131102002205024d6c49de021b03060b090807030206150802090a0b0416020301021e01021780000a0910338934250ccc03607e0400a0bdb9193e8a6b96fc2dfc108ae848914b504481f100a09c4dc148cb693293a67af24dd40d2b13a9e36794" + +const armoredPrivateKeyBlock = `-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1.4.10 (GNU/Linux) + +lQHYBE2rFNoBBADFwqWQIW/DSqcB4yCQqnAFTJ27qS5AnB46ccAdw3u4Greeu3Bp +idpoHdjULy7zSKlwR1EA873dO/k/e11Ml3dlAFUinWeejWaK2ugFP6JjiieSsrKn +vWNicdCS4HTWn0X4sjl0ZiAygw6GNhqEQ3cpLeL0g8E9hnYzJKQ0LWJa0QARAQAB +AAP/TB81EIo2VYNmTq0pK1ZXwUpxCrvAAIG3hwKjEzHcbQznsjNvPUihZ+NZQ6+X +0HCfPAdPkGDCLCb6NavcSW+iNnLTrdDnSI6+3BbIONqWWdRDYJhqZCkqmG6zqSfL +IdkJgCw94taUg5BWP/AAeQrhzjChvpMQTVKQL5mnuZbUCeMCAN5qrYMP2S9iKdnk +VANIFj7656ARKt/nf4CBzxcpHTyB8+d2CtPDKCmlJP6vL8t58Jmih+kHJMvC0dzn +gr5f5+sCAOOe5gt9e0am7AvQWhdbHVfJU0TQJx+m2OiCJAqGTB1nvtBLHdJnfdC9 +TnXXQ6ZXibqLyBies/xeY2sCKL5qtTMCAKnX9+9d/5yQxRyrQUHt1NYhaXZnJbHx +q4ytu0eWz+5i68IYUSK69jJ1NWPM0T6SkqpB3KCAIv68VFm9PxqG1KmhSrQIVGVz +dCBLZXmIuAQTAQIAIgUCTasU2gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AA +CgkQO9o98PRieSoLhgQAkLEZex02Qt7vGhZzMwuN0R22w3VwyYyjBx+fM3JFETy1 +ut4xcLJoJfIaF5ZS38UplgakHG0FQ+b49i8dMij0aZmDqGxrew1m4kBfjXw9B/v+ +eIqpODryb6cOSwyQFH0lQkXC040pjq9YqDsO5w0WYNXYKDnzRV0p4H1pweo2VDid +AdgETasU2gEEAN46UPeWRqKHvA99arOxee38fBt2CI08iiWyI8T3J6ivtFGixSqV +bRcPxYO/qLpVe5l84Nb3X71GfVXlc9hyv7CD6tcowL59hg1E/DC5ydI8K8iEpUmK +/UnHdIY5h8/kqgGxkY/T/hgp5fRQgW1ZoZxLajVlMRZ8W4tFtT0DeA+JABEBAAEA +A/0bE1jaaZKj6ndqcw86jd+QtD1SF+Cf21CWRNeLKnUds4FRRvclzTyUMuWPkUeX +TaNNsUOFqBsf6QQ2oHUBBK4VCHffHCW4ZEX2cd6umz7mpHW6XzN4DECEzOVksXtc +lUC1j4UB91DC/RNQqwX1IV2QLSwssVotPMPqhOi0ZLNY7wIA3n7DWKInxYZZ4K+6 +rQ+POsz6brEoRHwr8x6XlHenq1Oki855pSa1yXIARoTrSJkBtn5oI+f8AzrnN0BN +oyeQAwIA/7E++3HDi5aweWrViiul9cd3rcsS0dEnksPhvS0ozCJiHsq/6GFmy7J8 +QSHZPteedBnZyNp5jR+H7cIfVN3KgwH/Skq4PsuPhDq5TKK6i8Pc1WW8MA6DXTdU +nLkX7RGmMwjC0DBf7KWAlPjFaONAX3a8ndnz//fy1q7u2l9AZwrj1qa1iJ8EGAEC +AAkFAk2rFNoCGwwACgkQO9o98PRieSo2/QP/WTzr4ioINVsvN1akKuekmEMI3LAp +BfHwatufxxP1U+3Si/6YIk7kuPB9Hs+pRqCXzbvPRrI8NHZBmc8qIGthishdCYad +AHcVnXjtxrULkQFGbGvhKURLvS9WnzD/m1K2zzwxzkPTzT9/Yf06O6Mal5AdugPL +VrM0m72/jnpKo04= +=zNCn +-----END PGP PRIVATE KEY BLOCK-----` + +const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003` + +const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` + +const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go new file mode 100644 index 0000000..9e993ae --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go @@ -0,0 +1,218 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package s2k implements the various OpenPGP string-to-key transforms as +// specified in RFC 4800 section 3.7.1. +package s2k + +import ( + "crypto" + "hash" + "io" + "strconv" + + "golang.org/x/crypto/openpgp/errors" +) + +// Config collects configuration parameters for s2k key-stretching +// transformatioms. A nil *Config is valid and results in all default +// values. Currently, Config is used only by the Serialize function in +// this package. +type Config struct { + // Hash is the default hash function to be used. If + // nil, SHA1 is used. + Hash crypto.Hash +} + +func (c *Config) hash() crypto.Hash { + if c == nil || uint(c.Hash) == 0 { + // SHA1 is the historical default in this package. + return crypto.SHA1 + } + + return c.Hash +} + +// Simple writes to out the result of computing the Simple S2K function (RFC +// 4880, section 3.7.1.1) using the given hash and input passphrase. +func Simple(out []byte, h hash.Hash, in []byte) { + Salted(out, h, in, nil) +} + +var zero [1]byte + +// Salted writes to out the result of computing the Salted S2K function (RFC +// 4880, section 3.7.1.2) using the given hash, input passphrase and salt. +func Salted(out []byte, h hash.Hash, in []byte, salt []byte) { + done := 0 + var digest []byte + + for i := 0; done < len(out); i++ { + h.Reset() + for j := 0; j < i; j++ { + h.Write(zero[:]) + } + h.Write(salt) + h.Write(in) + digest = h.Sum(digest[:0]) + n := copy(out[done:], digest) + done += n + } +} + +// Iterated writes to out the result of computing the Iterated and Salted S2K +// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase, +// salt and iteration count. +func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) { + combined := make([]byte, len(in)+len(salt)) + copy(combined, salt) + copy(combined[len(salt):], in) + + if count < len(combined) { + count = len(combined) + } + + done := 0 + var digest []byte + for i := 0; done < len(out); i++ { + h.Reset() + for j := 0; j < i; j++ { + h.Write(zero[:]) + } + written := 0 + for written < count { + if written+len(combined) > count { + todo := count - written + h.Write(combined[:todo]) + written = count + } else { + h.Write(combined) + written += len(combined) + } + } + digest = h.Sum(digest[:0]) + n := copy(out[done:], digest) + done += n + } +} + +// Parse reads a binary specification for a string-to-key transformation from r +// and returns a function which performs that transform. +func Parse(r io.Reader) (f func(out, in []byte), err error) { + var buf [9]byte + + _, err = io.ReadFull(r, buf[:2]) + if err != nil { + return + } + + hash, ok := HashIdToHash(buf[1]) + if !ok { + return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1]))) + } + if !hash.Available() { + return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash))) + } + h := hash.New() + + switch buf[0] { + case 0: + f := func(out, in []byte) { + Simple(out, h, in) + } + return f, nil + case 1: + _, err = io.ReadFull(r, buf[:8]) + if err != nil { + return + } + f := func(out, in []byte) { + Salted(out, h, in, buf[:8]) + } + return f, nil + case 3: + _, err = io.ReadFull(r, buf[:9]) + if err != nil { + return + } + count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6) + f := func(out, in []byte) { + Iterated(out, h, in, buf[:8], count) + } + return f, nil + } + + return nil, errors.UnsupportedError("S2K function") +} + +// Serialize salts and stretches the given passphrase and writes the +// resulting key into key. It also serializes an S2K descriptor to +// w. The key stretching can be configured with c, which may be +// nil. In that case, sensible defaults will be used. +func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error { + var buf [11]byte + buf[0] = 3 /* iterated and salted */ + buf[1], _ = HashToHashId(c.hash()) + salt := buf[2:10] + if _, err := io.ReadFull(rand, salt); err != nil { + return err + } + const count = 65536 // this is the default in gpg + buf[10] = 96 // 65536 iterations + if _, err := w.Write(buf[:]); err != nil { + return err + } + + Iterated(key, c.hash().New(), passphrase, salt, count) + return nil +} + +// hashToHashIdMapping contains pairs relating OpenPGP's hash identifier with +// Go's crypto.Hash type. See RFC 4880, section 9.4. +var hashToHashIdMapping = []struct { + id byte + hash crypto.Hash + name string +}{ + {1, crypto.MD5, "MD5"}, + {2, crypto.SHA1, "SHA1"}, + {3, crypto.RIPEMD160, "RIPEMD160"}, + {8, crypto.SHA256, "SHA256"}, + {9, crypto.SHA384, "SHA384"}, + {10, crypto.SHA512, "SHA512"}, + {11, crypto.SHA224, "SHA224"}, +} + +// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP +// hash id. +func HashIdToHash(id byte) (h crypto.Hash, ok bool) { + for _, m := range hashToHashIdMapping { + if m.id == id { + return m.hash, true + } + } + return 0, false +} + +// HashIdToString returns the name of the hash function corresponding to the +// given OpenPGP hash id, or panics if id is unknown. +func HashIdToString(id byte) (name string, ok bool) { + for _, m := range hashToHashIdMapping { + if m.id == id { + return m.name, true + } + } + + return "", false +} + +// HashIdToHash returns an OpenPGP hash id which corresponds the given Hash. +func HashToHashId(h crypto.Hash) (id byte, ok bool) { + for _, m := range hashToHashIdMapping { + if m.hash == h { + return m.id, true + } + } + return 0, false +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go new file mode 100644 index 0000000..2e3d7b6 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go @@ -0,0 +1,134 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package s2k + +import ( + "bytes" + "crypto" + _ "crypto/md5" + "crypto/rand" + "crypto/sha1" + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/hex" + "testing" + + _ "golang.org/x/crypto/ripemd160" +) + +var saltedTests = []struct { + in, out string +}{ + {"hello", "10295ac1"}, + {"world", "ac587a5e"}, + {"foo", "4dda8077"}, + {"bar", "bd8aac6b9ea9cae04eae6a91c6133b58b5d9a61c14f355516ed9370456"}, + {"x", "f1d3f289"}, + {"xxxxxxxxxxxxxxxxxxxxxxx", "e00d7b45"}, +} + +func TestSalted(t *testing.T) { + h := sha1.New() + salt := [4]byte{1, 2, 3, 4} + + for i, test := range saltedTests { + expected, _ := hex.DecodeString(test.out) + out := make([]byte, len(expected)) + Salted(out, h, []byte(test.in), salt[:]) + if !bytes.Equal(expected, out) { + t.Errorf("#%d, got: %x want: %x", i, out, expected) + } + } +} + +var iteratedTests = []struct { + in, out string +}{ + {"hello", "83126105"}, + {"world", "6fa317f9"}, + {"foo", "8fbc35b9"}, + {"bar", "2af5a99b54f093789fd657f19bd245af7604d0f6ae06f66602a46a08ae"}, + {"x", "5a684dfe"}, + {"xxxxxxxxxxxxxxxxxxxxxxx", "18955174"}, +} + +func TestIterated(t *testing.T) { + h := sha1.New() + salt := [4]byte{4, 3, 2, 1} + + for i, test := range iteratedTests { + expected, _ := hex.DecodeString(test.out) + out := make([]byte, len(expected)) + Iterated(out, h, []byte(test.in), salt[:], 31) + if !bytes.Equal(expected, out) { + t.Errorf("#%d, got: %x want: %x", i, out, expected) + } + } +} + +var parseTests = []struct { + spec, in, out string +}{ + /* Simple with SHA1 */ + {"0002", "hello", "aaf4c61d"}, + /* Salted with SHA1 */ + {"01020102030405060708", "hello", "f4f7d67e"}, + /* Iterated with SHA1 */ + {"03020102030405060708f1", "hello", "f2a57b7c"}, +} + +func TestParse(t *testing.T) { + for i, test := range parseTests { + spec, _ := hex.DecodeString(test.spec) + buf := bytes.NewBuffer(spec) + f, err := Parse(buf) + if err != nil { + t.Errorf("%d: Parse returned error: %s", i, err) + continue + } + + expected, _ := hex.DecodeString(test.out) + out := make([]byte, len(expected)) + f(out, []byte(test.in)) + if !bytes.Equal(out, expected) { + t.Errorf("%d: output got: %x want: %x", i, out, expected) + } + if testing.Short() { + break + } + } +} + +func TestSerialize(t *testing.T) { + hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160, + crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224} + for _, h := range hashes { + testSerializeConfig(t, &Config{Hash: h}) + } +} + +func testSerializeConfig(t *testing.T, c *Config) { + t.Logf("Running testSerializeConfig() with config: %+v", c) + + buf := bytes.NewBuffer(nil) + key := make([]byte, 16) + passphrase := []byte("testing") + err := Serialize(buf, key, rand.Reader, passphrase, c) + if err != nil { + t.Errorf("failed to serialize: %s", err) + return + } + + f, err := Parse(buf) + if err != nil { + t.Errorf("failed to reparse: %s", err) + return + } + key2 := make([]byte, len(key)) + f(key2, passphrase) + if !bytes.Equal(key2, key) { + t.Errorf("keys don't match: %x (serialied) vs %x (parsed)", key, key2) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write.go new file mode 100644 index 0000000..e471174 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write.go @@ -0,0 +1,374 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import ( + "crypto" + "golang.org/x/crypto/openpgp/armor" + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/packet" + "golang.org/x/crypto/openpgp/s2k" + "hash" + "io" + "strconv" + "time" +) + +// DetachSign signs message with the private key from signer (which must +// already have been decrypted) and writes the signature to w. +// If config is nil, sensible defaults will be used. +func DetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { + return detachSign(w, signer, message, packet.SigTypeBinary, config) +} + +// ArmoredDetachSign signs message with the private key from signer (which +// must already have been decrypted) and writes an armored signature to w. +// If config is nil, sensible defaults will be used. +func ArmoredDetachSign(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) (err error) { + return armoredDetachSign(w, signer, message, packet.SigTypeBinary, config) +} + +// DetachSignText signs message (after canonicalising the line endings) with +// the private key from signer (which must already have been decrypted) and +// writes the signature to w. +// If config is nil, sensible defaults will be used. +func DetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { + return detachSign(w, signer, message, packet.SigTypeText, config) +} + +// ArmoredDetachSignText signs message (after canonicalising the line endings) +// with the private key from signer (which must already have been decrypted) +// and writes an armored signature to w. +// If config is nil, sensible defaults will be used. +func ArmoredDetachSignText(w io.Writer, signer *Entity, message io.Reader, config *packet.Config) error { + return armoredDetachSign(w, signer, message, packet.SigTypeText, config) +} + +func armoredDetachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { + out, err := armor.Encode(w, SignatureType, nil) + if err != nil { + return + } + err = detachSign(out, signer, message, sigType, config) + if err != nil { + return + } + return out.Close() +} + +func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.SignatureType, config *packet.Config) (err error) { + if signer.PrivateKey == nil { + return errors.InvalidArgumentError("signing key doesn't have a private key") + } + if signer.PrivateKey.Encrypted { + return errors.InvalidArgumentError("signing key is encrypted") + } + + sig := new(packet.Signature) + sig.SigType = sigType + sig.PubKeyAlgo = signer.PrivateKey.PubKeyAlgo + sig.Hash = config.Hash() + sig.CreationTime = config.Now() + sig.IssuerKeyId = &signer.PrivateKey.KeyId + + h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) + if err != nil { + return + } + io.Copy(wrappedHash, message) + + err = sig.Sign(h, signer.PrivateKey, config) + if err != nil { + return + } + + return sig.Serialize(w) +} + +// FileHints contains metadata about encrypted files. This metadata is, itself, +// encrypted. +type FileHints struct { + // IsBinary can be set to hint that the contents are binary data. + IsBinary bool + // FileName hints at the name of the file that should be written. It's + // truncated to 255 bytes if longer. It may be empty to suggest that the + // file should not be written to disk. It may be equal to "_CONSOLE" to + // suggest the data should not be written to disk. + FileName string + // ModTime contains the modification time of the file, or the zero time if not applicable. + ModTime time.Time +} + +// SymmetricallyEncrypt acts like gpg -c: it encrypts a file with a passphrase. +// The resulting WriteCloser must be closed after the contents of the file have +// been written. +// If config is nil, sensible defaults will be used. +func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { + if hints == nil { + hints = &FileHints{} + } + + key, err := packet.SerializeSymmetricKeyEncrypted(ciphertext, passphrase, config) + if err != nil { + return + } + w, err := packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config) + if err != nil { + return + } + + literaldata := w + if algo := config.Compression(); algo != packet.CompressionNone { + var compConfig *packet.CompressionConfig + if config != nil { + compConfig = config.CompressionConfig + } + literaldata, err = packet.SerializeCompressed(w, algo, compConfig) + if err != nil { + return + } + } + + var epochSeconds uint32 + if !hints.ModTime.IsZero() { + epochSeconds = uint32(hints.ModTime.Unix()) + } + return packet.SerializeLiteral(literaldata, hints.IsBinary, hints.FileName, epochSeconds) +} + +// intersectPreferences mutates and returns a prefix of a that contains only +// the values in the intersection of a and b. The order of a is preserved. +func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) { + var j int + for _, v := range a { + for _, v2 := range b { + if v == v2 { + a[j] = v + j++ + break + } + } + } + + return a[:j] +} + +func hashToHashId(h crypto.Hash) uint8 { + v, ok := s2k.HashToHashId(h) + if !ok { + panic("tried to convert unknown hash") + } + return v +} + +// Encrypt encrypts a message to a number of recipients and, optionally, signs +// it. hints contains optional information, that is also encrypted, that aids +// the recipients in processing the message. The resulting WriteCloser must +// be closed after the contents of the file have been written. +// If config is nil, sensible defaults will be used. +func Encrypt(ciphertext io.Writer, to []*Entity, signed *Entity, hints *FileHints, config *packet.Config) (plaintext io.WriteCloser, err error) { + var signer *packet.PrivateKey + if signed != nil { + signKey, ok := signed.signingKey(config.Now()) + if !ok { + return nil, errors.InvalidArgumentError("no valid signing keys") + } + signer = signKey.PrivateKey + if signer.Encrypted { + return nil, errors.InvalidArgumentError("signing key must be decrypted") + } + } + + // These are the possible ciphers that we'll use for the message. + candidateCiphers := []uint8{ + uint8(packet.CipherAES128), + uint8(packet.CipherAES256), + uint8(packet.CipherCAST5), + } + // These are the possible hash functions that we'll use for the signature. + candidateHashes := []uint8{ + hashToHashId(crypto.SHA256), + hashToHashId(crypto.SHA512), + hashToHashId(crypto.SHA1), + hashToHashId(crypto.RIPEMD160), + } + // In the event that a recipient doesn't specify any supported ciphers + // or hash functions, these are the ones that we assume that every + // implementation supports. + defaultCiphers := candidateCiphers[len(candidateCiphers)-1:] + defaultHashes := candidateHashes[len(candidateHashes)-1:] + + encryptKeys := make([]Key, len(to)) + for i := range to { + var ok bool + encryptKeys[i], ok = to[i].encryptionKey(config.Now()) + if !ok { + return nil, errors.InvalidArgumentError("cannot encrypt a message to key id " + strconv.FormatUint(to[i].PrimaryKey.KeyId, 16) + " because it has no encryption keys") + } + + sig := to[i].primaryIdentity().SelfSignature + + preferredSymmetric := sig.PreferredSymmetric + if len(preferredSymmetric) == 0 { + preferredSymmetric = defaultCiphers + } + preferredHashes := sig.PreferredHash + if len(preferredHashes) == 0 { + preferredHashes = defaultHashes + } + candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric) + candidateHashes = intersectPreferences(candidateHashes, preferredHashes) + } + + if len(candidateCiphers) == 0 || len(candidateHashes) == 0 { + return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms") + } + + cipher := packet.CipherFunction(candidateCiphers[0]) + // If the cipher specifed by config is a candidate, we'll use that. + configuredCipher := config.Cipher() + for _, c := range candidateCiphers { + cipherFunc := packet.CipherFunction(c) + if cipherFunc == configuredCipher { + cipher = cipherFunc + break + } + } + + var hash crypto.Hash + for _, hashId := range candidateHashes { + if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { + hash = h + break + } + } + + // If the hash specified by config is a candidate, we'll use that. + if configuredHash := config.Hash(); configuredHash.Available() { + for _, hashId := range candidateHashes { + if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { + hash = h + break + } + } + } + + if hash == 0 { + hashId := candidateHashes[0] + name, ok := s2k.HashIdToString(hashId) + if !ok { + name = "#" + strconv.Itoa(int(hashId)) + } + return nil, errors.InvalidArgumentError("cannot encrypt because no candidate hash functions are compiled in. (Wanted " + name + " in this case.)") + } + + symKey := make([]byte, cipher.KeySize()) + if _, err := io.ReadFull(config.Random(), symKey); err != nil { + return nil, err + } + + for _, key := range encryptKeys { + if err := packet.SerializeEncryptedKey(ciphertext, key.PublicKey, cipher, symKey, config); err != nil { + return nil, err + } + } + + encryptedData, err := packet.SerializeSymmetricallyEncrypted(ciphertext, cipher, symKey, config) + if err != nil { + return + } + + if signer != nil { + ops := &packet.OnePassSignature{ + SigType: packet.SigTypeBinary, + Hash: hash, + PubKeyAlgo: signer.PubKeyAlgo, + KeyId: signer.KeyId, + IsLast: true, + } + if err := ops.Serialize(encryptedData); err != nil { + return nil, err + } + } + + if hints == nil { + hints = &FileHints{} + } + + w := encryptedData + if signer != nil { + // If we need to write a signature packet after the literal + // data then we need to stop literalData from closing + // encryptedData. + w = noOpCloser{encryptedData} + + } + var epochSeconds uint32 + if !hints.ModTime.IsZero() { + epochSeconds = uint32(hints.ModTime.Unix()) + } + literalData, err := packet.SerializeLiteral(w, hints.IsBinary, hints.FileName, epochSeconds) + if err != nil { + return nil, err + } + + if signer != nil { + return signatureWriter{encryptedData, literalData, hash, hash.New(), signer, config}, nil + } + return literalData, nil +} + +// signatureWriter hashes the contents of a message while passing it along to +// literalData. When closed, it closes literalData, writes a signature packet +// to encryptedData and then also closes encryptedData. +type signatureWriter struct { + encryptedData io.WriteCloser + literalData io.WriteCloser + hashType crypto.Hash + h hash.Hash + signer *packet.PrivateKey + config *packet.Config +} + +func (s signatureWriter) Write(data []byte) (int, error) { + s.h.Write(data) + return s.literalData.Write(data) +} + +func (s signatureWriter) Close() error { + sig := &packet.Signature{ + SigType: packet.SigTypeBinary, + PubKeyAlgo: s.signer.PubKeyAlgo, + Hash: s.hashType, + CreationTime: s.config.Now(), + IssuerKeyId: &s.signer.KeyId, + } + + if err := sig.Sign(s.h, s.signer, s.config); err != nil { + return err + } + if err := s.literalData.Close(); err != nil { + return err + } + if err := sig.Serialize(s.encryptedData); err != nil { + return err + } + return s.encryptedData.Close() +} + +// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. +// TODO: we have two of these in OpenPGP packages alone. This probably needs +// to be promoted somewhere more common. +type noOpCloser struct { + w io.Writer +} + +func (c noOpCloser) Write(data []byte) (n int, err error) { + return c.w.Write(data) +} + +func (c noOpCloser) Close() error { + return nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write_test.go new file mode 100644 index 0000000..9f8c358 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/write_test.go @@ -0,0 +1,234 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package openpgp + +import ( + "bytes" + "io" + "io/ioutil" + "testing" + "time" +) + +func TestSignDetached(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSign(out, kring[0], message, nil) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId) +} + +func TestSignTextDetached(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(testKeys1And2PrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSignText(out, kring[0], message, nil) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey1KeyId) +} + +func TestSignDetachedDSA(t *testing.T) { + kring, _ := ReadKeyRing(readerFromHex(dsaTestKeyPrivateHex)) + out := bytes.NewBuffer(nil) + message := bytes.NewBufferString(signedInput) + err := DetachSign(out, kring[0], message, nil) + if err != nil { + t.Error(err) + } + + testDetachedSignature(t, kring, out, signedInput, "check", testKey3KeyId) +} + +func TestNewEntity(t *testing.T) { + if testing.Short() { + return + } + + e, err := NewEntity("Test User", "test", "test@example.com", nil) + if err != nil { + t.Errorf("failed to create entity: %s", err) + return + } + + w := bytes.NewBuffer(nil) + if err := e.SerializePrivate(w, nil); err != nil { + t.Errorf("failed to serialize entity: %s", err) + return + } + serialized := w.Bytes() + + el, err := ReadKeyRing(w) + if err != nil { + t.Errorf("failed to reparse entity: %s", err) + return + } + + if len(el) != 1 { + t.Errorf("wrong number of entities found, got %d, want 1", len(el)) + } + + w = bytes.NewBuffer(nil) + if err := e.SerializePrivate(w, nil); err != nil { + t.Errorf("failed to serialize entity second time: %s", err) + return + } + + if !bytes.Equal(w.Bytes(), serialized) { + t.Errorf("results differed") + } +} + +func TestSymmetricEncryption(t *testing.T) { + buf := new(bytes.Buffer) + plaintext, err := SymmetricallyEncrypt(buf, []byte("testing"), nil, nil) + if err != nil { + t.Errorf("error writing headers: %s", err) + return + } + message := []byte("hello world\n") + _, err = plaintext.Write(message) + if err != nil { + t.Errorf("error writing to plaintext writer: %s", err) + } + err = plaintext.Close() + if err != nil { + t.Errorf("error closing plaintext writer: %s", err) + } + + md, err := ReadMessage(buf, nil, func(keys []Key, symmetric bool) ([]byte, error) { + return []byte("testing"), nil + }, nil) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + messageBuf := bytes.NewBuffer(nil) + _, err = io.Copy(messageBuf, md.UnverifiedBody) + if err != nil { + t.Errorf("error rereading message: %s", err) + } + if !bytes.Equal(message, messageBuf.Bytes()) { + t.Errorf("recovered message incorrect got '%s', want '%s'", messageBuf.Bytes(), message) + } +} + +var testEncryptionTests = []struct { + keyRingHex string + isSigned bool +}{ + { + testKeys1And2PrivateHex, + false, + }, + { + testKeys1And2PrivateHex, + true, + }, + { + dsaElGamalTestKeysHex, + false, + }, + { + dsaElGamalTestKeysHex, + true, + }, +} + +func TestEncryption(t *testing.T) { + for i, test := range testEncryptionTests { + kring, _ := ReadKeyRing(readerFromHex(test.keyRingHex)) + + passphrase := []byte("passphrase") + for _, entity := range kring { + if entity.PrivateKey != nil && entity.PrivateKey.Encrypted { + err := entity.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt key", i) + } + } + for _, subkey := range entity.Subkeys { + if subkey.PrivateKey != nil && subkey.PrivateKey.Encrypted { + err := subkey.PrivateKey.Decrypt(passphrase) + if err != nil { + t.Errorf("#%d: failed to decrypt subkey", i) + } + } + } + } + + var signed *Entity + if test.isSigned { + signed = kring[0] + } + + buf := new(bytes.Buffer) + w, err := Encrypt(buf, kring[:1], signed, nil /* no hints */, nil) + if err != nil { + t.Errorf("#%d: error in Encrypt: %s", i, err) + continue + } + + const message = "testing" + _, err = w.Write([]byte(message)) + if err != nil { + t.Errorf("#%d: error writing plaintext: %s", i, err) + continue + } + err = w.Close() + if err != nil { + t.Errorf("#%d: error closing WriteCloser: %s", i, err) + continue + } + + md, err := ReadMessage(buf, kring, nil /* no prompt */, nil) + if err != nil { + t.Errorf("#%d: error reading message: %s", i, err) + continue + } + + testTime, _ := time.Parse("2006-01-02", "2013-07-01") + if test.isSigned { + signKey, _ := kring[0].signingKey(testTime) + expectedKeyId := signKey.PublicKey.KeyId + if md.SignedByKeyId != expectedKeyId { + t.Errorf("#%d: message signed by wrong key id, got: %d, want: %d", i, *md.SignedBy, expectedKeyId) + } + if md.SignedBy == nil { + t.Errorf("#%d: failed to find the signing Entity", i) + } + } + + plaintext, err := ioutil.ReadAll(md.UnverifiedBody) + if err != nil { + t.Errorf("#%d: error reading encrypted contents: %s", i, err) + continue + } + + encryptKey, _ := kring[0].encryptionKey(testTime) + expectedKeyId := encryptKey.PublicKey.KeyId + if len(md.EncryptedToKeyIds) != 1 || md.EncryptedToKeyIds[0] != expectedKeyId { + t.Errorf("#%d: expected message to be encrypted to %v, but got %#v", i, expectedKeyId, md.EncryptedToKeyIds) + } + + if string(plaintext) != message { + t.Errorf("#%d: got: %s, want: %s", i, string(plaintext), message) + } + + if test.isSigned { + if md.SignatureError != nil { + t.Errorf("#%d: signature error: %s", i, md.SignatureError) + } + if md.Signature == nil { + t.Error("signature missing") + } + } + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160.go b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160.go new file mode 100644 index 0000000..da690f0 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160.go @@ -0,0 +1,120 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ripemd160 implements the RIPEMD-160 hash algorithm. +package ripemd160 + +// RIPEMD-160 is designed by by Hans Dobbertin, Antoon Bosselaers, and Bart +// Preneel with specifications available at: +// http://homes.esat.kuleuven.be/~cosicart/pdf/AB-9601/AB-9601.pdf. + +import ( + "crypto" + "hash" +) + +func init() { + crypto.RegisterHash(crypto.RIPEMD160, New) +} + +// The size of the checksum in bytes. +const Size = 20 + +// The block size of the hash algorithm in bytes. +const BlockSize = 64 + +const ( + _s0 = 0x67452301 + _s1 = 0xefcdab89 + _s2 = 0x98badcfe + _s3 = 0x10325476 + _s4 = 0xc3d2e1f0 +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [5]uint32 // running context + x [BlockSize]byte // temporary buffer + nx int // index into x + tc uint64 // total count of bytes processed +} + +func (d *digest) Reset() { + d.s[0], d.s[1], d.s[2], d.s[3], d.s[4] = _s0, _s1, _s2, _s3, _s4 + d.nx = 0 + d.tc = 0 +} + +// New returns a new hash.Hash computing the checksum. +func New() hash.Hash { + result := new(digest) + result.Reset() + return result +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.tc += uint64(nn) + if d.nx > 0 { + n := len(p) + if n > BlockSize-d.nx { + n = BlockSize - d.nx + } + for i := 0; i < n; i++ { + d.x[d.nx+i] = p[i] + } + d.nx += n + if d.nx == BlockSize { + _Block(d, d.x[0:]) + d.nx = 0 + } + p = p[n:] + } + n := _Block(d, p) + p = p[n:] + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d0 *digest) Sum(in []byte) []byte { + // Make a copy of d0 so that caller can keep writing and summing. + d := *d0 + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + tc := d.tc + var tmp [64]byte + tmp[0] = 0x80 + if tc%64 < 56 { + d.Write(tmp[0 : 56-tc%64]) + } else { + d.Write(tmp[0 : 64+56-tc%64]) + } + + // Length in bits. + tc <<= 3 + for i := uint(0); i < 8; i++ { + tmp[i] = byte(tc >> (8 * i)) + } + d.Write(tmp[0:8]) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [Size]byte + for i, s := range d.s { + digest[i*4] = byte(s) + digest[i*4+1] = byte(s >> 8) + digest[i*4+2] = byte(s >> 16) + digest[i*4+3] = byte(s >> 24) + } + + return append(in, digest[:]...) +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160_test.go b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160_test.go new file mode 100644 index 0000000..5df1b25 --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160_test.go @@ -0,0 +1,64 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ripemd160 + +// Test vectors are from: +// http://homes.esat.kuleuven.be/~bosselae/ripemd160.html + +import ( + "fmt" + "io" + "testing" +) + +type mdTest struct { + out string + in string +} + +var vectors = [...]mdTest{ + {"9c1185a5c5e9fc54612808977ee8f548b2258d31", ""}, + {"0bdc9d2d256b3ee9daae347be6f4dc835a467ffe", "a"}, + {"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc", "abc"}, + {"5d0689ef49d2fae572b881b123a85ffa21595f36", "message digest"}, + {"f71c27109c692c1b56bbdceb5b9d2865b3708dbc", "abcdefghijklmnopqrstuvwxyz"}, + {"12a053384a9c0c88e405a06c27dcf49ada62eb2b", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"}, + {"b0e20b6e3116640286ed3a87a5713079b21f5189", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"}, + {"9b752e45573d4b39f4dbd3323cab82bf63326bfb", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"}, +} + +func TestVectors(t *testing.T) { + for i := 0; i < len(vectors); i++ { + tv := vectors[i] + md := New() + for j := 0; j < 3; j++ { + if j < 2 { + io.WriteString(md, tv.in) + } else { + io.WriteString(md, tv.in[0:len(tv.in)/2]) + md.Sum(nil) + io.WriteString(md, tv.in[len(tv.in)/2:]) + } + s := fmt.Sprintf("%x", md.Sum(nil)) + if s != tv.out { + t.Fatalf("RIPEMD-160[%d](%s) = %s, expected %s", j, tv.in, s, tv.out) + } + md.Reset() + } + } +} + +func TestMillionA(t *testing.T) { + md := New() + for i := 0; i < 100000; i++ { + io.WriteString(md, "aaaaaaaaaa") + } + out := "52783243c1697bdbe16d37f97f68f08325dc1528" + s := fmt.Sprintf("%x", md.Sum(nil)) + if s != out { + t.Fatalf("RIPEMD-160 (1 million 'a') = %s, expected %s", s, out) + } + md.Reset() +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160block.go b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160block.go new file mode 100644 index 0000000..7bc8e6c --- /dev/null +++ b/Godeps/_workspace/src/golang.org/x/crypto/ripemd160/ripemd160block.go @@ -0,0 +1,161 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// RIPEMD-160 block step. +// In its own file so that a faster assembly or C version +// can be substituted easily. + +package ripemd160 + +// work buffer indices and roll amounts for one line +var _n = [80]uint{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13, +} + +var _r = [80]uint{ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6, +} + +// same for the other parallel one +var n_ = [80]uint{ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11, +} + +var r_ = [80]uint{ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11, +} + +func _Block(md *digest, p []byte) int { + n := 0 + var x [16]uint32 + var alpha, beta uint32 + for len(p) >= BlockSize { + a, b, c, d, e := md.s[0], md.s[1], md.s[2], md.s[3], md.s[4] + aa, bb, cc, dd, ee := a, b, c, d, e + j := 0 + for i := 0; i < 16; i++ { + x[i] = uint32(p[j]) | uint32(p[j+1])<<8 | uint32(p[j+2])<<16 | uint32(p[j+3])<<24 + j += 4 + } + + // round 1 + i := 0 + for i < 16 { + alpha = a + (b ^ c ^ d) + x[_n[i]] + s := _r[i] + alpha = (alpha<>(32-s)) + e + beta = c<<10 | c>>22 + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ (cc | ^dd)) + x[n_[i]] + 0x50a28be6 + s = r_[i] + alpha = (alpha<>(32-s)) + ee + beta = cc<<10 | cc>>22 + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 2 + for i < 32 { + alpha = a + (b&c | ^b&d) + x[_n[i]] + 0x5a827999 + s := _r[i] + alpha = (alpha<>(32-s)) + e + beta = c<<10 | c>>22 + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&dd | cc&^dd) + x[n_[i]] + 0x5c4dd124 + s = r_[i] + alpha = (alpha<>(32-s)) + ee + beta = cc<<10 | cc>>22 + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 3 + for i < 48 { + alpha = a + (b | ^c ^ d) + x[_n[i]] + 0x6ed9eba1 + s := _r[i] + alpha = (alpha<>(32-s)) + e + beta = c<<10 | c>>22 + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb | ^cc ^ dd) + x[n_[i]] + 0x6d703ef3 + s = r_[i] + alpha = (alpha<>(32-s)) + ee + beta = cc<<10 | cc>>22 + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 4 + for i < 64 { + alpha = a + (b&d | c&^d) + x[_n[i]] + 0x8f1bbcdc + s := _r[i] + alpha = (alpha<>(32-s)) + e + beta = c<<10 | c>>22 + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb&cc | ^bb&dd) + x[n_[i]] + 0x7a6d76e9 + s = r_[i] + alpha = (alpha<>(32-s)) + ee + beta = cc<<10 | cc>>22 + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // round 5 + for i < 80 { + alpha = a + (b ^ (c | ^d)) + x[_n[i]] + 0xa953fd4e + s := _r[i] + alpha = (alpha<>(32-s)) + e + beta = c<<10 | c>>22 + a, b, c, d, e = e, alpha, b, beta, d + + // parallel line + alpha = aa + (bb ^ cc ^ dd) + x[n_[i]] + s = r_[i] + alpha = (alpha<>(32-s)) + ee + beta = cc<<10 | cc>>22 + aa, bb, cc, dd, ee = ee, alpha, bb, beta, dd + + i++ + } + + // combine results + dd += c + md.s[1] + md.s[1] = md.s[2] + d + ee + md.s[2] = md.s[3] + e + aa + md.s[3] = md.s[4] + a + bb + md.s[4] = md.s[0] + b + cc + md.s[0] = dd + + p = p[BlockSize:] + n += BlockSize + } + return n +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/.gitignore b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/.gitignore new file mode 100644 index 0000000..861a82b --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/.gitignore @@ -0,0 +1,3 @@ +*.swp +profile.out +*.coverprofile diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/benchmarks_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/benchmarks_test.go new file mode 100644 index 0000000..3d8a7a4 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/benchmarks_test.go @@ -0,0 +1,82 @@ +package sockjs + +import ( + "bufio" + "fmt" + "log" + "net/http" + "net/http/httptest" + "strings" + "sync" + "testing" +) + +func BenchmarkSimple(b *testing.B) { + var messages = make(chan string, 10) + h := NewHandler("/echo", DefaultOptions, func(session Session) { + for m := range messages { + session.Send(m) + } + session.Close(1024, "Close") + }) + server := httptest.NewServer(h) + defer server.Close() + + req, _ := http.NewRequest("POST", server.URL+fmt.Sprintf("/echo/server/%d/xhr_streaming", 1000), nil) + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + for n := 0; n < b.N; n++ { + messages <- "some message" + } + fmt.Println(b.N) + close(messages) + resp.Body.Close() +} + +func BenchmarkMessages(b *testing.B) { + msg := strings.Repeat("m", 10) + h := NewHandler("/echo", DefaultOptions, func(session Session) { + for n := 0; n < b.N; n++ { + session.Send(msg) + } + session.Close(1024, "Close") + }) + server := httptest.NewServer(h) + + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func(session int) { + reqc := 0 + // req, _ := http.NewRequest("POST", server.URL+fmt.Sprintf("/echo/server/%d/xhr_streaming", session), nil) + req, _ := http.NewRequest("GET", server.URL+fmt.Sprintf("/echo/server/%d/eventsource", session), nil) + for { + reqc++ + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatal(err) + } + reader := bufio.NewReader(resp.Body) + for { + line, err := reader.ReadString('\n') + if err != nil { + goto AGAIN + } + if strings.HasPrefix(line, "data: c[1024") { + resp.Body.Close() + goto DONE + } + } + AGAIN: + resp.Body.Close() + } + DONE: + wg.Done() + }(i) + } + wg.Wait() + server.Close() +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/doc.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/doc.go new file mode 100644 index 0000000..a1a46e7 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/doc.go @@ -0,0 +1,5 @@ +/* +Package sockjs is a server side implementation of sockjs protocol. +*/ + +package sockjs diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource.go new file mode 100644 index 0000000..41c460f --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource.go @@ -0,0 +1,32 @@ +package sockjs + +import ( + "fmt" + "io" + "net/http" +) + +func (h *handler) eventSource(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "text/event-stream; charset=UTF-8") + fmt.Fprintf(rw, "\r\n") + rw.(http.Flusher).Flush() + + recv := newHTTPReceiver(rw, h.options.ResponseLimit, new(eventSourceFrameWriter)) + sess, _ := h.sessionByRequest(req) + if err := sess.attachReceiver(recv); err != nil { + recv.sendFrame(cFrame) + recv.close() + return + } + + select { + case <-recv.doneNotify(): + case <-recv.interruptedNotify(): + } +} + +type eventSourceFrameWriter struct{} + +func (*eventSourceFrameWriter) write(w io.Writer, frame string) (int, error) { + return fmt.Fprintf(w, "data: %s\r\n\r\n", frame) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource_test.go new file mode 100644 index 0000000..4bd0017 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/eventsource_test.go @@ -0,0 +1,70 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHandler_EventSource(t *testing.T) { + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/eventsource", nil) + h := newTestHandler() + h.options.ResponseLimit = 1024 + go func() { + h.sessionsMux.Lock() + defer h.sessionsMux.Unlock() + sess := h.sessions["session"] + sess.Lock() + defer sess.Unlock() + recv := sess.recv + recv.close() + }() + h.eventSource(rw, req) + contentType := rw.Header().Get("content-type") + expected := "text/event-stream; charset=UTF-8" + if contentType != expected { + t.Errorf("Unexpected content type, got '%s', extected '%s'", contentType, expected) + } + if rw.Code != http.StatusOK { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusOK) + } + + if rw.Body.String() != "\r\ndata: o\r\n\r\n" { + t.Errorf("Event stream prelude, got '%s'", rw.Body) + } +} + +func TestHandler_EventSourceMultipleConnections(t *testing.T) { + h := newTestHandler() + h.options.ResponseLimit = 1024 + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/sess/eventsource", nil) + go func() { + rw := &ClosableRecorder{httptest.NewRecorder(), nil} + h.eventSource(rw, req) + if rw.Body.String() != "\r\ndata: c[2010,\"Another connection still open\"]\r\n\r\n" { + t.Errorf("wrong, got '%v'", rw.Body) + } + h.sessionsMux.Lock() + sess := h.sessions["sess"] + sess.close() + h.sessionsMux.Unlock() + }() + h.eventSource(rw, req) +} + +func TestHandler_EventSourceConnectionInterrupted(t *testing.T) { + h := newTestHandler() + sess := newTestSession() + sess.state = sessionActive + h.sessions["session"] = sess + req, _ := http.NewRequest("POST", "/server/session/eventsource", nil) + rw := newClosableRecorder() + close(rw.closeNotifCh) + h.eventSource(rw, req) + sess.Lock() + if sess.state != sessionClosed { + t.Errorf("Session should be closed") + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/example_handler_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/example_handler_test.go new file mode 100644 index 0000000..0a90480 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/example_handler_test.go @@ -0,0 +1,39 @@ +package sockjs_test + +import ( + "net/http" + + "github.com/igm/sockjs-go/sockjs" +) + +func ExampleNewHandler_simple() { + handler := sockjs.NewHandler("/echo", sockjs.DefaultOptions, func(session sockjs.Session) { + for { + if msg, err := session.Recv(); err == nil { + if session.Send(msg) != nil { + break + } + } else { + break + } + } + }) + http.ListenAndServe(":8080", handler) +} + +func ExampleNewHandler_defaultMux() { + handler := sockjs.NewHandler("/echo", sockjs.DefaultOptions, func(session sockjs.Session) { + for { + if msg, err := session.Recv(); err == nil { + if session.Send(msg) != nil { + break + } + } else { + break + } + } + }) + // need to provide path prefix for http.Mux + http.Handle("/echo/", handler) + http.ListenAndServe(":8080", nil) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame.go new file mode 100644 index 0000000..fb32e32 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame.go @@ -0,0 +1,11 @@ +package sockjs + +import ( + "encoding/json" + "fmt" +) + +func closeFrame(status uint32, reason string) string { + bytes, _ := json.Marshal([]interface{}{status, reason}) + return fmt.Sprintf("c%s", string(bytes)) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame_test.go new file mode 100644 index 0000000..58c0514 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/frame_test.go @@ -0,0 +1,10 @@ +package sockjs + +import "testing" + +func TestCloseFrame(t *testing.T) { + cf := closeFrame(1024, "some close text") + if cf != "c[1024,\"some close text\"]" { + t.Errorf("Wrong close frame generated '%s'", cf) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler.go new file mode 100644 index 0000000..980a897 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler.go @@ -0,0 +1,120 @@ +package sockjs + +import ( + "errors" + "net/http" + "net/url" + "regexp" + "strings" + "sync" +) + +type handler struct { + prefix string + options Options + handlerFunc func(Session) + mappings []*mapping + + sessionsMux sync.Mutex + sessions map[string]*session +} + +// NewHandler creates new HTTP handler that conforms to the basic net/http.Handler interface. +// It takes path prefix, options and sockjs handler function as parameters +func NewHandler(prefix string, opts Options, handleFunc func(Session)) http.Handler { + return newHandler(prefix, opts, handleFunc) +} + +func newHandler(prefix string, opts Options, handlerFunc func(Session)) *handler { + h := &handler{ + prefix: prefix, + options: opts, + handlerFunc: handlerFunc, + sessions: make(map[string]*session), + } + + sessionPrefix := prefix + "/[^/.]+/[^/.]+" + h.mappings = []*mapping{ + newMapping("GET", prefix+"[/]?$", welcomeHandler), + newMapping("OPTIONS", prefix+"/info$", opts.cookie, xhrCors, cacheFor, opts.info), + newMapping("GET", prefix+"/info$", xhrCors, noCache, opts.info), + // XHR + newMapping("POST", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, noCache, h.xhrSend), + newMapping("OPTIONS", sessionPrefix+"/xhr_send$", opts.cookie, xhrCors, cacheFor, xhrOptions), + newMapping("POST", sessionPrefix+"/xhr$", opts.cookie, xhrCors, noCache, h.xhrPoll), + newMapping("OPTIONS", sessionPrefix+"/xhr$", opts.cookie, xhrCors, cacheFor, xhrOptions), + newMapping("POST", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, noCache, h.xhrStreaming), + newMapping("OPTIONS", sessionPrefix+"/xhr_streaming$", opts.cookie, xhrCors, cacheFor, xhrOptions), + // EventStream + newMapping("GET", sessionPrefix+"/eventsource$", opts.cookie, xhrCors, noCache, h.eventSource), + // Htmlfile + newMapping("GET", sessionPrefix+"/htmlfile$", opts.cookie, xhrCors, noCache, h.htmlFile), + // JsonP + newMapping("GET", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, noCache, h.jsonp), + newMapping("OPTIONS", sessionPrefix+"/jsonp$", opts.cookie, xhrCors, cacheFor, xhrOptions), + newMapping("POST", sessionPrefix+"/jsonp_send$", opts.cookie, xhrCors, noCache, h.jsonpSend), + // IFrame + newMapping("GET", prefix+"/iframe[0-9-.a-z_]*.html$", cacheFor, h.iframe), + } + if opts.Websocket { + h.mappings = append(h.mappings, newMapping("GET", sessionPrefix+"/websocket$", h.sockjsWebsocket)) + } + return h +} + +func (h *handler) Prefix() string { return h.prefix } + +func (h *handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + // iterate over mappings + allowedMethods := []string{} + for _, mapping := range h.mappings { + if match, method := mapping.matches(req); match == fullMatch { + for _, hf := range mapping.chain { + hf(rw, req) + } + return + } else if match == pathMatch { + allowedMethods = append(allowedMethods, method) + } + } + if len(allowedMethods) > 0 { + rw.Header().Set("allow", strings.Join(allowedMethods, ", ")) + rw.Header().Set("Content-Type", "") + rw.WriteHeader(http.StatusMethodNotAllowed) + return + } + http.NotFound(rw, req) +} + +func (h *handler) parseSessionID(url *url.URL) (string, error) { + session := regexp.MustCompile(h.prefix + "/(?P[^/.]+)/(?P[^/.]+)/.*") + matches := session.FindStringSubmatch(url.Path) + if len(matches) == 3 { + return matches[2], nil + } + return "", errors.New("unable to parse URL for session") +} + +func (h *handler) sessionByRequest(req *http.Request) (*session, error) { + h.sessionsMux.Lock() + defer h.sessionsMux.Unlock() + sessionID, err := h.parseSessionID(req.URL) + if err != nil { + return nil, err + } + sess, exists := h.sessions[sessionID] + if !exists { + sess = newSession(sessionID, h.options.DisconnectDelay, h.options.HeartbeatDelay) + h.sessions[sessionID] = sess + if h.handlerFunc != nil { + go h.handlerFunc(sess) + } + go func() { + <-sess.closedNotify() + h.sessionsMux.Lock() + delete(h.sessions, sessionID) + h.sessionsMux.Unlock() + }() + } + return sess, nil +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler_test.go new file mode 100644 index 0000000..9943dff --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/handler_test.go @@ -0,0 +1,77 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "net/url" + "testing" + "time" +) + +func TestHandler_Create(t *testing.T) { + handler := newHandler("/echo", DefaultOptions, nil) + if handler.Prefix() != "/echo" { + t.Errorf("Prefix not properly set, got '%s' expected '%s'", handler.Prefix(), "/echo") + } + if handler.sessions == nil { + t.Errorf("Handler session map not made") + } + server := httptest.NewServer(handler) + defer server.Close() + + resp, err := http.Get(server.URL + "/echo") + if err != nil { + t.Errorf("There should not be any error, got '%s'", err) + } + if resp.StatusCode != http.StatusOK { + t.Errorf("Unexpected status code receiver, got '%d' expected '%d'", resp.StatusCode, http.StatusOK) + } +} + +func TestHandler_ParseSessionId(t *testing.T) { + h := handler{prefix: "/prefix"} + url, _ := url.Parse("http://server:port/prefix/server/session/whatever") + if session, err := h.parseSessionID(url); session != "session" || err != nil { + t.Errorf("Wrong session parsed, got '%s' expected '%s' with error = '%v'", session, "session", err) + } + url, _ = url.Parse("http://server:port/asdasd/server/session/whatever") + if _, err := h.parseSessionID(url); err == nil { + t.Errorf("Should return error") + } +} + +func TestHandler_SessionByRequest(t *testing.T) { + h := newHandler("", DefaultOptions, nil) + h.options.DisconnectDelay = 10 * time.Millisecond + var handlerFuncCalled = make(chan Session) + h.handlerFunc = func(conn Session) { handlerFuncCalled <- conn } + req, _ := http.NewRequest("POST", "/server/sessionid/whatever/follows", nil) + sess, err := h.sessionByRequest(req) + if sess == nil || err != nil { + t.Errorf("Session should be returned") + // test handlerFunc was called + select { + case conn := <-handlerFuncCalled: // ok + if conn != sess { + t.Errorf("Handler was not passed correct session") + } + case <-time.After(100 * time.Millisecond): + t.Errorf("HandlerFunc was not called") + } + } + // test session is reused for multiple requests with same sessionID + req2, _ := http.NewRequest("POST", "/server/sessionid/whatever", nil) + if sess2, err := h.sessionByRequest(req2); sess2 != sess || err != nil { + t.Errorf("Expected error, got session: '%v'", sess) + } + // test session expires after timeout + time.Sleep(15 * time.Millisecond) + if _, exists := h.sessions["sessionid"]; exists { + t.Errorf("Session should not exist in handler after timeout") + } + // test proper behaviour in case URL is not correct + req, _ = http.NewRequest("POST", "", nil) + if _, err := h.sessionByRequest(req); err == nil { + t.Errorf("Expected parser sessionID from URL error, got 'nil'") + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile.go new file mode 100644 index 0000000..c7510ee --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile.go @@ -0,0 +1,58 @@ +package sockjs + +import ( + "fmt" + "io" + "net/http" + "strings" +) + +var iframeTemplate = ` + + + +

      Don't panic!

      + +` + +func init() { + iframeTemplate += strings.Repeat(" ", 1024-len(iframeTemplate)+14) + iframeTemplate += "\r\n\r\n" +} + +func (h *handler) htmlFile(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "text/html; charset=UTF-8") + + req.ParseForm() + callback := req.Form.Get("c") + if callback == "" { + http.Error(rw, `"callback" parameter required`, http.StatusInternalServerError) + return + } + rw.WriteHeader(http.StatusOK) + fmt.Fprintf(rw, iframeTemplate, callback) + rw.(http.Flusher).Flush() + sess, _ := h.sessionByRequest(req) + recv := newHTTPReceiver(rw, h.options.ResponseLimit, new(htmlfileFrameWriter)) + if err := sess.attachReceiver(recv); err != nil { + recv.sendFrame(cFrame) + recv.close() + return + } + select { + case <-recv.doneNotify(): + case <-recv.interruptedNotify(): + } +} + +type htmlfileFrameWriter struct{} + +func (*htmlfileFrameWriter) write(w io.Writer, frame string) (int, error) { + return fmt.Fprintf(w, "\r\n", quote(frame)) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile_test.go new file mode 100644 index 0000000..1d7f7bd --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/htmlfile_test.go @@ -0,0 +1,60 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestHandler_htmlFileNoCallback(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/server/session/htmlfile", nil) + h.htmlFile(rw, req) + if rw.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusInternalServerError) + } + expectedContentType := "text/plain; charset=utf-8" + if rw.Header().Get("content-type") != expectedContentType { + t.Errorf("Unexpected content type, got '%s', expected '%s'", rw.Header().Get("content-type"), expectedContentType) + } +} + +func TestHandler_htmlFile(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/server/session/htmlfile?c=testCallback", nil) + h.htmlFile(rw, req) + if rw.Code != http.StatusOK { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusOK) + } + expectedContentType := "text/html; charset=UTF-8" + if rw.Header().Get("content-type") != expectedContentType { + t.Errorf("Unexpected content-type, got '%s', expected '%s'", rw.Header().Get("content-type"), expectedContentType) + } + if rw.Body.String() != expectedIFrame { + t.Errorf("Unexpected response body, got '%s', expected '%s'", rw.Body, expectedIFrame) + } + +} + +func init() { + expectedIFrame += strings.Repeat(" ", 1024-len(expectedIFrame)+len("testCallack")+13) + expectedIFrame += "\r\n\r\n" + expectedIFrame += "\r\n" +} + +var expectedIFrame = ` + + + +

      Don't panic!

      + +` diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver.go new file mode 100644 index 0000000..b552b8b --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver.go @@ -0,0 +1,99 @@ +package sockjs + +import ( + "fmt" + "io" + "net/http" + "strings" + "sync" +) + +type frameWriter interface { + write(writer io.Writer, frame string) (int, error) +} + +type httpReceiverState int + +const ( + stateHTTPReceiverActive httpReceiverState = iota + stateHTTPReceiverClosed +) + +type httpReceiver struct { + sync.Mutex + state httpReceiverState + + frameWriter frameWriter + rw http.ResponseWriter + maxResponseSize uint32 + currentResponseSize uint32 + doneCh chan struct{} + interruptCh chan struct{} +} + +func newHTTPReceiver(rw http.ResponseWriter, maxResponse uint32, frameWriter frameWriter) *httpReceiver { + recv := &httpReceiver{ + rw: rw, + frameWriter: frameWriter, + maxResponseSize: maxResponse, + doneCh: make(chan struct{}), + interruptCh: make(chan struct{}), + } + if closeNotifier, ok := rw.(http.CloseNotifier); ok { + // if supported check for close notifications from http.RW + go func() { + select { + case <-closeNotifier.CloseNotify(): + recv.Lock() + defer recv.Unlock() + if recv.state < stateHTTPReceiverClosed { + recv.state = stateHTTPReceiverClosed + close(recv.interruptCh) + } + case <-recv.doneCh: + // ok, no action needed here, receiver closed in correct way + // just finish the routine + } + }() + } + return recv +} + +func (recv *httpReceiver) sendBulk(messages ...string) { + if len(messages) > 0 { + recv.sendFrame(fmt.Sprintf("a[%s]", + strings.Join( + transform(messages, quote), + ",", + ), + )) + } +} + +func (recv *httpReceiver) sendFrame(value string) { + recv.Lock() + defer recv.Unlock() + + if recv.state == stateHTTPReceiverActive { + // TODO(igm) check err, possibly act as if interrupted + n, _ := recv.frameWriter.write(recv.rw, value) + recv.currentResponseSize += uint32(n) + if recv.currentResponseSize >= recv.maxResponseSize { + recv.state = stateHTTPReceiverClosed + close(recv.doneCh) + } else { + recv.rw.(http.Flusher).Flush() + } + } +} + +func (recv *httpReceiver) doneNotify() <-chan struct{} { return recv.doneCh } +func (recv *httpReceiver) interruptedNotify() <-chan struct{} { return recv.interruptCh } +func (recv *httpReceiver) close() { + recv.Lock() + defer recv.Unlock() + if recv.state < stateHTTPReceiverClosed { + recv.state = stateHTTPReceiverClosed + close(recv.doneCh) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver_test.go new file mode 100644 index 0000000..a2a1c99 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/httpreceiver_test.go @@ -0,0 +1,101 @@ +package sockjs + +import ( + "io" + "net/http/httptest" + "testing" +) + +type testFrameWriter struct { + frames []string +} + +func (t *testFrameWriter) write(w io.Writer, frame string) (int, error) { + t.frames = append(t.frames, frame) + return len(frame), nil +} + +func TestHttpReceiver_Create(t *testing.T) { + rec := httptest.NewRecorder() + recv := newHTTPReceiver(rec, 1024, new(testFrameWriter)) + if recv.doneCh != recv.doneNotify() { + t.Errorf("Calling done() must return close channel, but it does not") + } + if recv.rw != rec { + t.Errorf("Http.ResponseWriter not properly initialized") + } + if recv.maxResponseSize != 1024 { + t.Errorf("MaxResponseSize not properly initialized") + } +} + +func TestHttpReceiver_SendEmptyFrames(t *testing.T) { + rec := httptest.NewRecorder() + recv := newHTTPReceiver(rec, 1024, new(testFrameWriter)) + recv.sendBulk() + if rec.Body.String() != "" { + t.Errorf("Incorrect body content received from receiver '%s'", rec.Body.String()) + } +} + +func TestHttpReceiver_SendFrame(t *testing.T) { + rec := httptest.NewRecorder() + fw := new(testFrameWriter) + recv := newHTTPReceiver(rec, 1024, fw) + var frame = "some frame content" + recv.sendFrame(frame) + if len(fw.frames) != 1 || fw.frames[0] != frame { + t.Errorf("Incorrect body content received, got '%s', expected '%s'", fw.frames, frame) + } + +} + +func TestHttpReceiver_SendBulk(t *testing.T) { + rec := httptest.NewRecorder() + fw := new(testFrameWriter) + recv := newHTTPReceiver(rec, 1024, fw) + recv.sendBulk("message 1", "message 2", "message 3") + expected := "a[\"message 1\",\"message 2\",\"message 3\"]" + if len(fw.frames) != 1 || fw.frames[0] != expected { + t.Errorf("Incorrect body content received from receiver, got '%s' expected '%s'", fw.frames, expected) + } +} + +func TestHttpReceiver_MaximumResponseSize(t *testing.T) { + rec := httptest.NewRecorder() + recv := newHTTPReceiver(rec, 52, new(testFrameWriter)) + recv.sendBulk("message 1", "message 2") // produces 26 bytes of response in 1 frame + if recv.currentResponseSize != 26 { + t.Errorf("Incorrect response size calcualated, got '%d' expected '%d'", recv.currentResponseSize, 26) + } + select { + case <-recv.doneNotify(): + t.Errorf("Receiver should not be done yet") + default: // ok + } + recv.sendBulk("message 1", "message 2") // produces another 26 bytes of response in 1 frame to go over max resposne size + select { + case <-recv.doneNotify(): // ok + default: + t.Errorf("Receiver closed channel did not close") + } +} + +func TestHttpReceiver_Close(t *testing.T) { + rec := httptest.NewRecorder() + recv := newHTTPReceiver(rec, 1024, nil) + recv.close() + if recv.state != stateHTTPReceiverClosed { + t.Errorf("Unexpected state, got '%d', expected '%d'", recv.state, stateHTTPReceiverClosed) + } +} + +func TestHttpReceiver_ConnectionInterrupt(t *testing.T) { + rw := newClosableRecorder() + recv := newHTTPReceiver(rw, 1024, nil) + rw.closeNotifCh <- true + recv.Lock() + if recv.state != stateHTTPReceiverClosed { + t.Errorf("Unexpected state, got '%d', expected '%d'", recv.state, stateHTTPReceiverClosed) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe.go new file mode 100644 index 0000000..0975334 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe.go @@ -0,0 +1,42 @@ +package sockjs + +import ( + "crypto/md5" + "fmt" + "net/http" + "text/template" +) + +var tmpl = template.Must(template.New("iframe").Parse(iframeBody)) + +func (h *handler) iframe(rw http.ResponseWriter, req *http.Request) { + etagReq := req.Header.Get("If-None-Match") + hash := md5.New() + hash.Write([]byte(iframeBody)) + etag := fmt.Sprintf("%x", hash.Sum(nil)) + if etag == etagReq { + rw.WriteHeader(http.StatusNotModified) + return + } + + rw.Header().Set("Content-Type", "text/html; charset=UTF-8") + rw.Header().Add("ETag", etag) + tmpl.Execute(rw, h.options.SockJSURL) +} + +var iframeBody = ` + + + + + + + + +

      Don't panic!

      +

      This is a SockJS hidden iframe. It's used for cross domain magic.

      + +` diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe_test.go new file mode 100644 index 0000000..9915750 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/iframe_test.go @@ -0,0 +1,42 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHandler_iframe(t *testing.T) { + h := newTestHandler() + h.options.SockJSURL = "http://sockjs.com/sockjs.js" + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/server/sess/iframe", nil) + h.iframe(rw, req) + if rw.Body.String() != expected { + t.Errorf("Unexpected html content,\ngot:\n'%s'\n\nexpected\n'%s'", rw.Body, expected) + } + eTag := rw.Header().Get("etag") + req.Header.Set("if-none-match", eTag) + rw = httptest.NewRecorder() + h.iframe(rw, req) + if rw.Code != http.StatusNotModified { + t.Errorf("Unexpected response, got '%d', expected '%d'", rw.Code, http.StatusNotModified) + } +} + +var expected = ` + + + + + + + + +

      Don't panic!

      +

      This is a SockJS hidden iframe. It's used for cross domain magic.

      + +` diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go new file mode 100644 index 0000000..5c98efb --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp.go @@ -0,0 +1,75 @@ +package sockjs + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +func (h *handler) jsonp(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "application/javascript; charset=UTF-8") + + req.ParseForm() + callback := req.Form.Get("c") + if callback == "" { + http.Error(rw, `"callback" parameter required`, http.StatusInternalServerError) + return + } + rw.WriteHeader(http.StatusOK) + rw.(http.Flusher).Flush() + + sess, _ := h.sessionByRequest(req) + recv := newHTTPReceiver(rw, 1, &jsonpFrameWriter{callback}) + if err := sess.attachReceiver(recv); err != nil { + recv.sendFrame(cFrame) + recv.close() + return + } + select { + case <-recv.doneNotify(): + case <-recv.interruptedNotify(): + } +} + +func (h *handler) jsonpSend(rw http.ResponseWriter, req *http.Request) { + req.ParseForm() + var data io.Reader + data = req.Body + + formReader := strings.NewReader(req.PostFormValue("d")) + if formReader.Len() != 0 { + data = formReader + } + if data == nil { + http.Error(rw, "Payload expected.", http.StatusInternalServerError) + return + } + var messages []string + err := json.NewDecoder(data).Decode(&messages) + if err == io.EOF { + http.Error(rw, "Payload expected.", http.StatusInternalServerError) + return + } + if err != nil { + http.Error(rw, "Broken JSON encoding.", http.StatusInternalServerError) + return + } + sessionID, _ := h.parseSessionID(req.URL) + if sess, ok := h.sessions[sessionID]; !ok { + http.NotFound(rw, req) + } else { + _ = sess.accept(messages...) // TODO(igm) reponse with http.StatusInternalServerError in case of err? + rw.Header().Set("content-type", "text/plain; charset=UTF-8") + rw.Write([]byte("ok")) + } +} + +type jsonpFrameWriter struct { + callback string +} + +func (j *jsonpFrameWriter) write(w io.Writer, frame string) (int, error) { + return fmt.Fprintf(w, "%s(%s);\r\n", j.callback, quote(frame)) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp_test.go new file mode 100644 index 0000000..3a58d78 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/jsonp_test.go @@ -0,0 +1,96 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +func TestHandler_jsonpNoCallback(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/server/session/jsonp", nil) + h.jsonp(rw, req) + if rw.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusInternalServerError) + } + expectedContentType := "text/plain; charset=utf-8" + if rw.Header().Get("content-type") != expectedContentType { + t.Errorf("Unexpected content type, got '%s', expected '%s'", rw.Header().Get("content-type"), expectedContentType) + } +} + +func TestHandler_jsonp(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/server/session/jsonp?c=testCallback", nil) + h.jsonp(rw, req) + expectedContentType := "application/javascript; charset=UTF-8" + if rw.Header().Get("content-type") != expectedContentType { + t.Errorf("Unexpected content type, got '%s', expected '%s'", rw.Header().Get("content-type"), expectedContentType) + } + expectedBody := "testCallback(\"o\");\r\n" + if rw.Body.String() != expectedBody { + t.Errorf("Unexpected body, got '%s', expected '%s'", rw.Body, expectedBody) + } +} + +func TestHandler_jsonpSendNoPayload(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/jsonp_send", nil) + h.jsonpSend(rw, req) + if rw.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusInternalServerError) + } +} + +func TestHandler_jsonpSendWrongPayload(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/jsonp_send", strings.NewReader("wrong payload")) + h.jsonpSend(rw, req) + if rw.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusInternalServerError) + } +} + +func TestHandler_jsonpSendNoSession(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/jsonp_send", strings.NewReader("[\"message\"]")) + h.jsonpSend(rw, req) + if rw.Code != http.StatusNotFound { + t.Errorf("Unexpected response code, got '%d', expected '%d'", rw.Code, http.StatusNotFound) + } +} + +func TestHandler_jsonpSend(t *testing.T) { + h := newTestHandler() + sess := newSession("session", time.Second, time.Second) + h.sessions["session"] = sess + + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/jsonp_send", strings.NewReader("[\"message\"]")) + var done = make(chan struct{}) + go func() { + h.jsonpSend(rw, req) + close(done) + }() + msg, _ := sess.Recv() + if msg != "message" { + t.Errorf("Incorrect message in the channel, should be '%s', was '%s'", "some message", msg) + } + <-done + if rw.Code != http.StatusOK { + t.Errorf("Wrong response status received %d, should be %d", rw.Code, http.StatusOK) + } + if rw.Header().Get("content-type") != "text/plain; charset=UTF-8" { + t.Errorf("Wrong content type received '%s'", rw.Header().Get("content-type")) + } + if rw.Body.String() != "ok" { + t.Errorf("Unexpected body, got '%s', expected 'ok'", rw.Body) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping.go new file mode 100644 index 0000000..9b1cbdf --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping.go @@ -0,0 +1,36 @@ +package sockjs + +import ( + "net/http" + "regexp" +) + +type mapping struct { + method string + path *regexp.Regexp + chain []http.HandlerFunc +} + +func newMapping(method string, re string, handlers ...http.HandlerFunc) *mapping { + return &mapping{method, regexp.MustCompile(re), handlers} +} + +type matchType uint32 + +const ( + fullMatch matchType = iota + pathMatch + noMatch +) + +// matches checks if given req.URL is a match with a mapping. Match can be either full, partial (http method mismatch) or no match. +func (m *mapping) matches(req *http.Request) (match matchType, method string) { + if !m.path.MatchString(req.URL.Path) { + match, method = noMatch, "" + } else if m.method != req.Method { + match, method = pathMatch, m.method + } else { + match, method = fullMatch, m.method + } + return +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping_test.go new file mode 100644 index 0000000..08ad7c1 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/mapping_test.go @@ -0,0 +1,38 @@ +package sockjs + +import ( + "net/http" + "regexp" + "testing" +) + +func TestMappingMatcher(t *testing.T) { + mappingPrefix := mapping{"GET", regexp.MustCompile("prefix/$"), nil} + mappingPrefixRegExp := mapping{"GET", regexp.MustCompile(".*x/$"), nil} + + var testRequests = []struct { + mapping mapping + method string + url string + expectedMatch matchType + }{ + {mappingPrefix, "GET", "http://foo/prefix/", fullMatch}, + {mappingPrefix, "POST", "http://foo/prefix/", pathMatch}, + {mappingPrefix, "GET", "http://foo/prefix_not_mapped", noMatch}, + {mappingPrefixRegExp, "GET", "http://foo/prefix/", fullMatch}, + } + + for _, request := range testRequests { + req, _ := http.NewRequest(request.method, request.url, nil) + m := request.mapping + match, method := m.matches(req) + if match != request.expectedMatch { + t.Errorf("mapping %s should match url=%s", m.path, request.url) + } + if request.expectedMatch == pathMatch { + if method != m.method { + t.Errorf("Matcher method should be %s, but got %s", m.method, method) + } + } + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options.go new file mode 100644 index 0000000..cffc4c8 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options.go @@ -0,0 +1,103 @@ +package sockjs + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "time" +) + +var entropy *rand.Rand + +func init() { + entropy = rand.New(rand.NewSource(time.Now().UnixNano())) +} + +// Options type is used for defining various sockjs options +type Options struct { + // Transports which don't support cross-domain communication natively ('eventsource' to name one) use an iframe trick. + // A simple page is served from the SockJS server (using its foreign domain) and is placed in an invisible iframe. + // Code run from this iframe doesn't need to worry about cross-domain issues, as it's being run from domain local to the SockJS server. + // This iframe also does need to load SockJS javascript client library, and this option lets you specify its url (if you're unsure, + // point it to the latest minified SockJS client release, this is the default). You must explicitly specify this url on the server + // side for security reasons - we don't want the possibility of running any foreign javascript within the SockJS domain (aka cross site scripting attack). + // Also, sockjs javascript library is probably already cached by the browser - it makes sense to reuse the sockjs url you're using in normally. + SockJSURL string + // Most streaming transports save responses on the client side and don't free memory used by delivered messages. + // Such transports need to be garbage-collected once in a while. `response_limit` sets a minimum number of bytes that can be send + // over a single http streaming request before it will be closed. After that client needs to open new request. + // Setting this value to one effectively disables streaming and will make streaming transports to behave like polling transports. + // The default value is 128K. + ResponseLimit uint32 + // Some load balancers don't support websockets. This option can be used to disable websockets support by the server. By default websockets are enabled. + Websocket bool + // In order to keep proxies and load balancers from closing long running http requests we need to pretend that the connection is active + // and send a heartbeat packet once in a while. This setting controls how often this is done. + // By default a heartbeat packet is sent every 25 seconds. + HeartbeatDelay time.Duration + // The server closes a session when a client receiving connection have not been seen for a while. + // This delay is configured by this setting. + // By default the session is closed when a receiving connection wasn't seen for 5 seconds. + DisconnectDelay time.Duration + // Some hosting providers enable sticky sessions only to requests that have JSessionID cookie set. + // This setting controls if the server should set this cookie to a dummy value. + // By default setting JSessionID cookie is disabled. More sophisticated behaviour can be achieved by supplying a function. + JSessionID func(http.ResponseWriter, *http.Request) +} + +// DefaultOptions is a convenient set of options to be used for sockjs +var DefaultOptions = Options{ + Websocket: true, + JSessionID: nil, + SockJSURL: "http://cdn.sockjs.org/sockjs-0.3.min.js", + HeartbeatDelay: 25 * time.Second, + DisconnectDelay: 5 * time.Second, + ResponseLimit: 128 * 1024, +} + +type info struct { + Websocket bool `json:"websocket"` + CookieNeeded bool `json:"cookie_needed"` + Origins []string `json:"origins"` + Entropy int32 `json:"entropy"` +} + +func (options *Options) info(rw http.ResponseWriter, req *http.Request) { + switch req.Method { + case "GET": + rw.Header().Set("Content-Type", "application/json; charset=UTF-8") + json.NewEncoder(rw).Encode(info{ + Websocket: options.Websocket, + CookieNeeded: options.JSessionID != nil, + Origins: []string{"*:*"}, + Entropy: entropy.Int31(), + }) + case "OPTIONS": + rw.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET") + rw.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", 365*24*60*60)) + rw.WriteHeader(http.StatusNoContent) // 204 + default: + http.NotFound(rw, req) + } +} + +// DefaultJSessionID is a default behaviour function to be used in options for JSessionID if JSESSIONID is needed +func DefaultJSessionID(rw http.ResponseWriter, req *http.Request) { + cookie, err := req.Cookie("JSESSIONID") + if err == http.ErrNoCookie { + cookie = &http.Cookie{ + Name: "JSESSIONID", + Value: "dummy", + } + } + cookie.Path = "/" + header := rw.Header() + header.Add("Set-Cookie", cookie.String()) +} + +func (options *Options) cookie(rw http.ResponseWriter, req *http.Request) { + if options.JSessionID != nil { // cookie is needed + options.JSessionID(rw, req) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options_test.go new file mode 100644 index 0000000..1d8da08 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/options_test.go @@ -0,0 +1,64 @@ +package sockjs + +import ( + "encoding/json" + "net/http" + "net/http/httptest" +) +import "testing" + +func TestInfoGet(t *testing.T) { + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("GET", "", nil) + DefaultOptions.info(recorder, request) + + if recorder.Code != http.StatusOK { + t.Errorf("Wrong status code, got '%d' expected '%d'", recorder.Code, http.StatusOK) + } + + decoder := json.NewDecoder(recorder.Body) + var a info + decoder.Decode(&a) + if !a.Websocket { + t.Errorf("Websocket field should be set true") + } + if a.CookieNeeded { + t.Errorf("CookieNeede should be set to false") + } +} + +func TestInfoOptions(t *testing.T) { + recorder := httptest.NewRecorder() + request, _ := http.NewRequest("OPTIONS", "", nil) + DefaultOptions.info(recorder, request) + if recorder.Code != http.StatusNoContent { + t.Errorf("Incorrect status code received, got '%d' expected '%d'", recorder.Code, http.StatusNoContent) + } +} + +func TestInfoUnknown(t *testing.T) { + req, _ := http.NewRequest("PUT", "", nil) + rec := httptest.NewRecorder() + DefaultOptions.info(rec, req) + if rec.Code != http.StatusNotFound { + t.Errorf("Incorrec response status, got '%d' expected '%d'", rec.Code, http.StatusNotFound) + } +} + +func TestCookies(t *testing.T) { + rec := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "", nil) + optionsWithCookies := DefaultOptions + optionsWithCookies.JSessionID = DefaultJSessionID + optionsWithCookies.cookie(rec, req) + if rec.Header().Get("set-cookie") != "JSESSIONID=dummy; Path=/" { + t.Errorf("Cookie not properly set in response") + } + // cookie value set in request + req.AddCookie(&http.Cookie{Name: "JSESSIONID", Value: "some_jsession_id", Path: "/"}) + rec = httptest.NewRecorder() + optionsWithCookies.cookie(rec, req) + if rec.Header().Get("set-cookie") != "JSESSIONID=some_jsession_id; Path=/" { + t.Errorf("Cookie not properly set in response") + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session.go new file mode 100644 index 0000000..031cf07 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session.go @@ -0,0 +1,216 @@ +package sockjs + +import ( + "encoding/gob" + "errors" + "io" + "sync" + "time" +) + +type sessionState uint32 + +const ( + // brand new session, need to send "h" to receiver + sessionOpening sessionState = iota + // active session + sessionActive + // session being closed, sending "closeFrame" to receivers + sessionClosing + // closed session, no activity at all, should be removed from handler completely and not reused + sessionClosed +) + +var ( + // ErrSessionNotOpen error is used to denote session not in open state. + // Recv() and Send() operations are not suppored if session is closed. + ErrSessionNotOpen = errors.New("sockjs: session not in open state") + errSessionReceiverAttached = errors.New("sockjs: another receiver already attached") +) + +type session struct { + sync.Mutex + id string + state sessionState + // protocol dependent receiver (xhr, eventsource, ...) + recv receiver + // messages to be sent to client + sendBuffer []string + // messages received from client to be consumed by application + // receivedBuffer chan string + msgReader *io.PipeReader + msgWriter *io.PipeWriter + msgEncoder *gob.Encoder + msgDecoder *gob.Decoder + + // closeFrame to send after session is closed + closeFrame string + + // internal timer used to handle session expiration if no receiver is attached, or heartbeats if recevier is attached + sessionTimeoutInterval time.Duration + heartbeatInterval time.Duration + timer *time.Timer + // once the session timeouts this channel also closes + closeCh chan struct{} +} + +type receiver interface { + // sendBulk send multiple data messages in frame frame in format: a["msg 1", "msg 2", ....] + sendBulk(...string) + // sendFrame sends given frame over the wire (with possible chunking depending on receiver) + sendFrame(string) + // close closes the receiver in a "done" way (idempotent) + close() + // done notification channel gets closed whenever receiver ends + doneNotify() <-chan struct{} + // interrupted channel gets closed whenever receiver is interrupted (i.e. http connection drops,...) + interruptedNotify() <-chan struct{} +} + +// Session is a central component that handles receiving and sending frames. It maintains internal state +func newSession(sessionID string, sessionTimeoutInterval, heartbeatInterval time.Duration) *session { + r, w := io.Pipe() + s := &session{ + id: sessionID, + msgReader: r, + msgWriter: w, + msgEncoder: gob.NewEncoder(w), + msgDecoder: gob.NewDecoder(r), + sessionTimeoutInterval: sessionTimeoutInterval, + heartbeatInterval: heartbeatInterval, + closeCh: make(chan struct{})} + s.Lock() // "go test -race" complains if ommited, not sure why as no race can happen here + s.timer = time.AfterFunc(sessionTimeoutInterval, s.close) + s.Unlock() + return s +} + +func (s *session) sendMessage(msg string) error { + s.Lock() + defer s.Unlock() + if s.state > sessionActive { + return ErrSessionNotOpen + } + s.sendBuffer = append(s.sendBuffer, msg) + if s.recv != nil { + s.recv.sendBulk(s.sendBuffer...) + s.sendBuffer = nil + } + return nil +} + +func (s *session) attachReceiver(recv receiver) error { + s.Lock() + defer s.Unlock() + if s.recv != nil { + return errSessionReceiverAttached + } + s.recv = recv + go func(r receiver) { + select { + case <-r.doneNotify(): + s.detachReceiver() + case <-r.interruptedNotify(): + s.detachReceiver() + s.close() + } + }(recv) + + if s.state == sessionClosing { + s.recv.sendFrame(s.closeFrame) + s.recv.close() + return nil + } + if s.state == sessionOpening { + s.recv.sendFrame("o") + s.state = sessionActive + } + s.recv.sendBulk(s.sendBuffer...) + s.sendBuffer = nil + s.timer.Stop() + s.timer = time.AfterFunc(s.heartbeatInterval, s.heartbeat) + return nil +} + +func (s *session) detachReceiver() { + s.Lock() + defer s.Unlock() + s.timer.Stop() + s.timer = time.AfterFunc(s.sessionTimeoutInterval, s.close) + s.recv = nil +} + +func (s *session) heartbeat() { + s.Lock() + defer s.Unlock() + if s.recv != nil { // timer could have fired between Lock and timer.Stop in detachReceiver + s.recv.sendFrame("h") + s.timer = time.AfterFunc(s.heartbeatInterval, s.heartbeat) + } +} + +func (s *session) accept(messages ...string) error { + for _, msg := range messages { + if err := s.msgEncoder.Encode(msg); err != nil { + return err + } + } + return nil +} + +// idempotent operation +func (s *session) closing() { + s.Lock() + defer s.Unlock() + if s.state < sessionClosing { + s.msgReader.Close() + s.msgWriter.Close() + s.state = sessionClosing + if s.recv != nil { + s.recv.sendFrame(s.closeFrame) + s.recv.close() + } + } +} + +// idempotent operation +func (s *session) close() { + s.closing() + s.Lock() + defer s.Unlock() + if s.state < sessionClosed { + s.state = sessionClosed + s.timer.Stop() + close(s.closeCh) + } +} + +func (s *session) closedNotify() <-chan struct{} { return s.closeCh } + +// Conn interface implementation +func (s *session) Close(status uint32, reason string) error { + s.Lock() + if s.state < sessionClosing { + s.closeFrame = closeFrame(status, reason) + s.Unlock() + s.closing() + return nil + } + s.Unlock() + return ErrSessionNotOpen +} + +func (s *session) Recv() (string, error) { + var msg string + err := s.msgDecoder.Decode(&msg) + if err == io.ErrClosedPipe { + err = ErrSessionNotOpen + } + return msg, err +} + +func (s *session) Send(msg string) error { + return s.sendMessage(msg) +} + +func (s *session) ID() string { return s.id } diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session_test.go new file mode 100644 index 0000000..df0d4cc --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/session_test.go @@ -0,0 +1,319 @@ +package sockjs + +import ( + "io" + "runtime" + "sync" + "testing" + "time" +) + +func newTestSession() *session { + // session with long expiration and heartbeats with ID + return newSession("sessionId", 1000*time.Second, 1000*time.Second) +} + +func TestSession_Create(t *testing.T) { + session := newTestSession() + session.sendMessage("this is a message") + if len(session.sendBuffer) != 1 { + t.Errorf("Session send buffer should contain 1 message") + } + session.sendMessage("another message") + if len(session.sendBuffer) != 2 { + t.Errorf("Session send buffer should contain 2 messages") + } + if session.state != sessionOpening { + t.Errorf("Session in wrong state %v, should be %v", session.state, sessionOpening) + } +} + +func TestSession_ConcurrentSend(t *testing.T) { + session := newTestSession() + done := make(chan bool) + for i := 0; i < 100; i++ { + go func() { + session.sendMessage("message D") + done <- true + }() + } + for i := 0; i < 100; i++ { + <-done + } + if len(session.sendBuffer) != 100 { + t.Errorf("Session send buffer should contain 102 messages") + } +} + +func TestSession_AttachReceiver(t *testing.T) { + session := newTestSession() + recv := &testReceiver{} + // recv := &mockRecv{ + // _sendFrame: func(frame string) { + // if frame != "o" { + // t.Errorf("Incorrect open header received") + // } + // }, + // _sendBulk: func(...string) {}, + // } + if err := session.attachReceiver(recv); err != nil { + t.Errorf("Should not return error") + } + if session.state != sessionActive { + t.Errorf("Session in wrong state after receiver attached %d, should be %d", session.state, sessionActive) + } + session.detachReceiver() + // recv = &mockRecv{ + // _sendFrame: func(frame string) { + // t.Errorf("No frame shold be send, got '%s'", frame) + // }, + // _sendBulk: func(...string) {}, + // } + if err := session.attachReceiver(recv); err != nil { + t.Errorf("Should not return error") + } +} + +func TestSession_Timeout(t *testing.T) { + sess := newSession("id", 10*time.Millisecond, 10*time.Second) + select { + case <-sess.closeCh: + case <-time.After(20 * time.Millisecond): + t.Errorf("sess close notification channel should close") + } + sess.Lock() + if sess.state != sessionClosed { + t.Errorf("Session did not timeout") + } + sess.Unlock() +} + +func TestSession_TimeoutOfClosedSession(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Unexcpected error '%v'", r) + } + }() + sess := newSession("id", 1*time.Millisecond, time.Second) + sess.closing() + time.Sleep(1 * time.Millisecond) + sess.closing() +} + +func TestSession_AttachReceiverAndCheckHeartbeats(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Unexcpected error '%v'", r) + } + }() + session := newSession("id", time.Second, 10*time.Millisecond) // 10ms heartbeats + recv := newTestReceiver() + defer close(recv.doneCh) + session.attachReceiver(recv) + time.Sleep(120 * time.Millisecond) + recv.Lock() + if len(recv.frames) < 10 || len(recv.frames) > 13 { // should get around 10 heartbeats (120ms/10ms) + t.Fatalf("Wrong number of frames received, got '%d'", len(recv.frames)) + } + for i := 1; i < len(recv.frames); i++ { + if recv.frames[i] != "h" { + t.Errorf("Heartbeat no received") + } + } +} + +func TestSession_AttachReceiverAndRefuse(t *testing.T) { + session := newTestSession() + if err := session.attachReceiver(newTestReceiver()); err != nil { + t.Errorf("Should not return error") + } + var a sync.WaitGroup + a.Add(100) + for i := 0; i < 100; i++ { + go func() { + defer a.Done() + if err := session.attachReceiver(newTestReceiver()); err != errSessionReceiverAttached { + t.Errorf("Should return error as another receiver is already attached") + } + }() + } + a.Wait() +} + +func TestSession_DetachRecevier(t *testing.T) { + session := newTestSession() + session.detachReceiver() + session.detachReceiver() // idempotent operation + session.attachReceiver(newTestReceiver()) + session.detachReceiver() + +} + +func TestSession_SendWithRecv(t *testing.T) { + session := newTestSession() + session.sendMessage("message A") + session.sendMessage("message B") + if len(session.sendBuffer) != 2 { + t.Errorf("There should be 2 messages in buffer, but there are %d", len(session.sendBuffer)) + } + recv := newTestReceiver() + defer close(recv.doneCh) + + session.attachReceiver(recv) + if len(recv.frames[1:]) != 2 { + t.Errorf("Reciver should get 2 message frames from session, got %d", len(recv.frames)) + } + session.sendMessage("message C") + if len(recv.frames[1:]) != 3 { + t.Errorf("Reciver should get 3 message frames from session, got %d", len(recv.frames)) + } + session.sendMessage("message D") + if len(recv.frames[1:]) != 4 { + t.Errorf("Reciver should get 4 frames from session, got %d", len(recv.frames)) + } + if len(session.sendBuffer) != 0 { + t.Errorf("Send buffer should be empty now, but there are %d messaged", len(session.sendBuffer)) + } +} + +func TestSession_Recv(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Panic should not happen") + } + }() + session := newTestSession() + go func() { + session.accept("message A") + session.accept("message B") + if err := session.accept("message C"); err != io.ErrClosedPipe { + t.Errorf("Session should not accept new messages if closed, got '%v' expected '%v'", err, io.ErrClosedPipe) + } + }() + if msg, _ := session.Recv(); msg != "message A" { + t.Errorf("Got %s, should be %s", msg, "message A") + } + if msg, _ := session.Recv(); msg != "message B" { + t.Errorf("Got %s, should be %s", msg, "message B") + } + session.close() +} + +func TestSession_Closing(t *testing.T) { + session := newTestSession() + session.closing() + if _, err := session.Recv(); err == nil { + t.Errorf("Session's receive buffer channel should close") + } + if err := session.sendMessage("some message"); err != ErrSessionNotOpen { + t.Errorf("Session should not accept new message after close") + } +} + +// Session as Session Tests +func TestSession_AsSession(t *testing.T) { var _ Session = newSession("id", 0, 0) } + +func TestSession_SessionRecv(t *testing.T) { + s := newTestSession() + go func() { + s.accept("message 1") + }() + msg, err := s.Recv() + if msg != "message 1" || err != nil { + t.Errorf("Should receive a message without error, got '%s' err '%v'", msg, err) + } + go func() { + s.closing() + _, err := s.Recv() + if err != ErrSessionNotOpen { + t.Errorf("Session not in correct state, got '%v', expected '%v'", err, ErrSessionNotOpen) + } + }() + _, err = s.Recv() + if err != ErrSessionNotOpen { + t.Errorf("Session not in correct state, got '%v', expected '%v'", err, ErrSessionNotOpen) + } +} + +func TestSession_SessionSend(t *testing.T) { + s := newTestSession() + err := s.Send("message A") + if err != nil { + t.Errorf("Session should take messages by default") + } + if len(s.sendBuffer) != 1 || s.sendBuffer[0] != "message A" { + t.Errorf("Message not properly queued in session, got '%v'", s.sendBuffer) + } +} + +func TestSession_SessionClose(t *testing.T) { + s := newTestSession() + s.state = sessionActive + recv := newTestReceiver() + s.attachReceiver(recv) + err := s.Close(1, "some reason") + if len(recv.frames) != 1 || recv.frames[0] != "c[1,\"some reason\"]" { + t.Errorf("Expected close frame, got '%v'", recv.frames) + } + if err != nil { + t.Errorf("Should not get any error, got '%s'", err) + } + if s.closeFrame != "c[1,\"some reason\"]" { + t.Errorf("Incorrect closeFrame, got '%s'", s.closeFrame) + } + if s.state != sessionClosing { + t.Errorf("Incorrect session state, expected 'sessionClosing', got '%v'", s.state) + } + // all the consequent receivers trying to attach shoult get the same close frame + var i = 100 + for i > 0 { + recv := newTestReceiver() + err := s.attachReceiver(recv) + if err != nil { + // give a chance to a receiver to detach + runtime.Gosched() + continue + } + i-- + if len(recv.frames) != 1 || recv.frames[0] != "c[1,\"some reason\"]" { + t.Errorf("Close frame not received by recv, frames '%v'", recv.frames) + } + } + if err := s.Close(1, "some other reson"); err != ErrSessionNotOpen { + t.Errorf("Expected error, got '%v'", err) + } +} + +func TestSession_SessionSessionId(t *testing.T) { + s := newTestSession() + if s.ID() != "sessionId" { + t.Errorf("Unexpected session ID, got '%s', expected '%s'", s.ID(), "sessionId") + } +} + +func newTestReceiver() *testReceiver { + return &testReceiver{ + doneCh: make(chan struct{}), + interruptCh: make(chan struct{}), + } +} + +type testReceiver struct { + sync.Mutex + doneCh, interruptCh chan struct{} + frames []string +} + +func (t *testReceiver) doneNotify() <-chan struct{} { return t.doneCh } +func (t *testReceiver) interruptedNotify() <-chan struct{} { return t.interruptCh } +func (t *testReceiver) close() { close(t.doneCh) } +func (t *testReceiver) sendBulk(messages ...string) { + for _, m := range messages { + t.sendFrame(m) + } +} +func (t *testReceiver) sendFrame(frame string) { + t.Lock() + defer t.Unlock() + t.frames = append(t.frames, frame) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs.go new file mode 100644 index 0000000..b5523b0 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs.go @@ -0,0 +1,13 @@ +package sockjs + +// Session represents a connection between server and client. +type Session interface { + // Id returns a session id + ID() string + // Recv reads one text frame from session + Recv() (string, error) + // Send sends one text frame to session + Send(string) error + // Close closes the session with provided code and reason. + Close(status uint32, reason string) error +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs_test.go new file mode 100644 index 0000000..7661ecf --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/sockjs_test.go @@ -0,0 +1,27 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "regexp" + "testing" +) + +func TestSockJS_ServeHTTP(t *testing.T) { + m := handler{mappings: make([]*mapping, 0)} + m.mappings = []*mapping{ + &mapping{"POST", regexp.MustCompile("/foo/.*"), []http.HandlerFunc{func(http.ResponseWriter, *http.Request) {}}}, + } + req, _ := http.NewRequest("GET", "/foo/bar", nil) + rec := httptest.NewRecorder() + m.ServeHTTP(rec, req) + if rec.Code != http.StatusMethodNotAllowed { + t.Errorf("Unexpected response status, got '%d' expected '%d'", rec.Code, http.StatusMethodNotAllowed) + } + req, _ = http.NewRequest("GET", "/bar", nil) + rec = httptest.NewRecorder() + m.ServeHTTP(rec, req) + if rec.Code != http.StatusNotFound { + t.Errorf("Unexpected response status, got '%d' expected '%d'", rec.Code, http.StatusNotFound) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils.go new file mode 100644 index 0000000..4f61139 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils.go @@ -0,0 +1,16 @@ +package sockjs + +import "encoding/json" + +func quote(in string) string { + quoted, _ := json.Marshal(in) + return string(quoted) +} + +func transform(values []string, transformFn func(string) string) []string { + ret := make([]string, len(values)) + for i, msg := range values { + ret[i] = transformFn(msg) + } + return ret +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils_test.go new file mode 100644 index 0000000..546d1f8 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/utils_test.go @@ -0,0 +1,19 @@ +package sockjs + +import "testing" + +func TestQuote(t *testing.T) { + var quotationTests = []struct { + input string + output string + }{ + {"simple", "\"simple\""}, + {"more complex \"", "\"more complex \\\"\""}, + } + + for _, testCase := range quotationTests { + if quote(testCase.input) != testCase.output { + t.Errorf("Expected '%s', got '%s'", testCase.output, quote(testCase.input)) + } + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web.go new file mode 100644 index 0000000..2fc20ed --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web.go @@ -0,0 +1,47 @@ +package sockjs + +import ( + "fmt" + "net/http" + "time" +) + +func xhrCors(rw http.ResponseWriter, req *http.Request) { + header := rw.Header() + origin := req.Header.Get("origin") + if origin == "" || origin == "null" { + origin = "*" + } + header.Set("Access-Control-Allow-Origin", origin) + + if allowHeaders := req.Header.Get("Access-Control-Request-Headers"); allowHeaders != "" && allowHeaders != "null" { + header.Add("Access-Control-Allow-Headers", allowHeaders) + } + header.Add("Access-Control-Allow-Credentials", "true") +} + +func xhrOptions(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST") + rw.WriteHeader(http.StatusNoContent) // 204 +} + +func cacheFor(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", 365*24*60*60)) + rw.Header().Set("Expires", time.Now().AddDate(1, 0, 0).Format(time.RFC1123)) + rw.Header().Set("Access-Control-Max-Age", fmt.Sprintf("%d", 365*24*60*60)) +} + +func noCache(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0") +} + +func welcomeHandler(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "text/plain;charset=UTF-8") + fmt.Fprintf(rw, "Welcome to SockJS!\n") +} + +func httpError(w http.ResponseWriter, error string, code int) { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(code) + fmt.Fprintf(w, error) +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web_test.go new file mode 100644 index 0000000..ca54494 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/web_test.go @@ -0,0 +1,69 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestXhrCors(t *testing.T) { + recorder := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + xhrCors(recorder, req) + acao := recorder.Header().Get("access-control-allow-origin") + if acao != "*" { + t.Errorf("Incorrect value for access-control-allow-origin header, got %s, expected %s", acao, "*") + } + req.Header.Set("origin", "localhost") + xhrCors(recorder, req) + acao = recorder.Header().Get("access-control-allow-origin") + if acao != "localhost" { + t.Errorf("Incorrect value for access-control-allow-origin header, got %s, expected %s", acao, "localhost") + } + + req.Header.Set("access-control-request-headers", "some value") + rec := httptest.NewRecorder() + xhrCors(rec, req) + if rec.Header().Get("access-control-allow-headers") != "some value" { + t.Errorf("Incorent value for ACAH, got %s", rec.Header().Get("access-control-allow-headers")) + } +} + +func TestXhrOptions(t *testing.T) { + rec := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/", nil) + xhrOptions(rec, req) + if rec.Code != http.StatusNoContent { + t.Errorf("Wrong response status code, expected %d, got %d", http.StatusNoContent, rec.Code) + } +} + +func TestCacheFor(t *testing.T) { + rec := httptest.NewRecorder() + cacheFor(rec, nil) + cacheControl := rec.Header().Get("cache-control") + if cacheControl != "public, max-age=31536000" { + t.Errorf("Incorrect cache-control header value, got '%s'", cacheControl) + } + expires := rec.Header().Get("expires") + if expires == "" { + t.Errorf("Expires header should not be empty") // TODO(igm) check proper formating of string + } + maxAge := rec.Header().Get("access-control-max-age") + if maxAge != "31536000" { + t.Errorf("Incorrect value for access-control-max-age, got '%s'", maxAge) + } +} + +func TestNoCache(t *testing.T) { + rec := httptest.NewRecorder() + noCache(rec, nil) +} + +func TestWelcomeHandler(t *testing.T) { + rec := httptest.NewRecorder() + welcomeHandler(rec, nil) + if rec.Body.String() != "Welcome to SockJS!\n" { + t.Errorf("Incorrect welcome message received, got '%s'", rec.Body.String()) + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket.go new file mode 100644 index 0000000..e89a292 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket.go @@ -0,0 +1,89 @@ +package sockjs + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/websocket" +) + +// WebSocketReadBufSize is a parameter that is used for WebSocket Upgrader. +// https://github.com/gorilla/websocket/blob/master/server.go#L230 +var WebSocketReadBufSize = 4096 + +// WebSocketWriteBufSize is a parameter that is used for WebSocket Upgrader +// https://github.com/gorilla/websocket/blob/master/server.go#L230 +var WebSocketWriteBufSize = 4096 + +func (h *handler) sockjsWebsocket(rw http.ResponseWriter, req *http.Request) { + conn, err := websocket.Upgrade(rw, req, nil, WebSocketReadBufSize, WebSocketWriteBufSize) + if _, ok := err.(websocket.HandshakeError); ok { + http.Error(rw, `Can "Upgrade" only to "WebSocket".`, http.StatusBadRequest) + return + } else if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + return + } + sessID, _ := h.parseSessionID(req.URL) + sess := newSession(sessID, h.options.DisconnectDelay, h.options.HeartbeatDelay) + if h.handlerFunc != nil { + go h.handlerFunc(sess) + } + + receiver := newWsReceiver(conn) + sess.attachReceiver(receiver) + readCloseCh := make(chan struct{}) + go func() { + var d []string + for { + err := conn.ReadJSON(&d) + if err != nil { + close(readCloseCh) + return + } + sess.accept(d...) + } + }() + + select { + case <-readCloseCh: + case <-receiver.doneNotify(): + } + sess.close() + conn.Close() +} + +type wsReceiver struct { + conn *websocket.Conn + closeCh chan struct{} +} + +func newWsReceiver(conn *websocket.Conn) *wsReceiver { + return &wsReceiver{ + conn: conn, + closeCh: make(chan struct{}), + } +} + +func (w *wsReceiver) sendBulk(messages ...string) { + if len(messages) > 0 { + w.sendFrame(fmt.Sprintf("a[%s]", strings.Join(transform(messages, quote), ","))) + } +} + +func (w *wsReceiver) sendFrame(frame string) { + if err := w.conn.WriteMessage(websocket.TextMessage, []byte(frame)); err != nil { + w.close() + } +} + +func (w *wsReceiver) close() { + select { + case <-w.closeCh: // already closed + default: + close(w.closeCh) + } +} +func (w *wsReceiver) doneNotify() <-chan struct{} { return w.closeCh } +func (w *wsReceiver) interruptedNotify() <-chan struct{} { return nil } diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket_test.go new file mode 100644 index 0000000..858f2e7 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/websocket_test.go @@ -0,0 +1,116 @@ +package sockjs + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gorilla/websocket" +) + +func TestHandler_WebSocketHandshakeError(t *testing.T) { + h := newTestHandler() + server := httptest.NewServer(http.HandlerFunc(h.sockjsWebsocket)) + defer server.Close() + req, _ := http.NewRequest("GET", server.URL, nil) + req.Header.Set("origin", "https"+server.URL[4:]) + resp, _ := http.DefaultClient.Do(req) + if resp.StatusCode != http.StatusBadRequest { + t.Errorf("Unexpected response code, got '%d', expected '%d'", resp.StatusCode, http.StatusBadRequest) + } +} + +func TestHandler_WebSocket(t *testing.T) { + h := newTestHandler() + server := httptest.NewServer(http.HandlerFunc(h.sockjsWebsocket)) + defer server.CloseClientConnections() + url := "ws" + server.URL[4:] + var connCh = make(chan Session) + h.handlerFunc = func(conn Session) { connCh <- conn } + conn, resp, err := websocket.DefaultDialer.Dial(url, nil) + if conn == nil { + t.Errorf("Connection should not be nil") + } + if err != nil { + t.Errorf("Unexpected error '%v'", err) + } + if resp.StatusCode != http.StatusSwitchingProtocols { + t.Errorf("Wrong response code returned, got '%d', expected '%d'", resp.StatusCode, http.StatusSwitchingProtocols) + } + select { + case <-connCh: //ok + case <-time.After(10 * time.Millisecond): + t.Errorf("Sockjs Handler not invoked") + } +} + +func TestHandler_WebSocketTerminationByServer(t *testing.T) { + h := newTestHandler() + server := httptest.NewServer(http.HandlerFunc(h.sockjsWebsocket)) + defer server.Close() + url := "ws" + server.URL[4:] + h.handlerFunc = func(conn Session) { + conn.Close(1024, "some close message") + conn.Close(0, "this should be ignored") + } + conn, _, err := websocket.DefaultDialer.Dial(url, map[string][]string{"Origin": []string{server.URL}}) + _, msg, err := conn.ReadMessage() + if string(msg) != "o" || err != nil { + t.Errorf("Open frame expected, got '%s' and error '%v', expected '%s' without error", msg, err, "o") + } + _, msg, err = conn.ReadMessage() + if string(msg) != `c[1024,"some close message"]` || err != nil { + t.Errorf("Open frame expected, got '%s' and error '%v', expected '%s' without error", msg, err, `c[1024,"some close message"]`) + } + _, msg, err = conn.ReadMessage() + if err != io.ErrUnexpectedEOF { + t.Errorf("Expected '%v', got '%v'", io.EOF, err) + } +} + +func TestHandler_WebSocketTerminationByClient(t *testing.T) { + h := newTestHandler() + server := httptest.NewServer(http.HandlerFunc(h.sockjsWebsocket)) + defer server.Close() + url := "ws" + server.URL[4:] + var done = make(chan struct{}) + h.handlerFunc = func(conn Session) { + if _, err := conn.Recv(); err != ErrSessionNotOpen { + t.Errorf("Recv should fail") + } + close(done) + } + conn, _, _ := websocket.DefaultDialer.Dial(url, map[string][]string{"Origin": []string{server.URL}}) + conn.Close() + <-done +} + +func TestHandler_WebSocketCommunication(t *testing.T) { + h := newTestHandler() + server := httptest.NewServer(http.HandlerFunc(h.sockjsWebsocket)) + // defer server.CloseClientConnections() + url := "ws" + server.URL[4:] + var done = make(chan struct{}) + h.handlerFunc = func(conn Session) { + conn.Send("message 1") + conn.Send("message 2") + msg, err := conn.Recv() + if msg != "message 3" || err != nil { + t.Errorf("Got '%s', expecte '%s'", msg, "message 3") + } + conn.Close(123, "close") + close(done) + } + conn, _, _ := websocket.DefaultDialer.Dial(url, map[string][]string{"Origin": []string{server.URL}}) + conn.WriteJSON([]string{"message 3"}) + var expected = []string{"o", `a["message 1"]`, `a["message 2"]`, `c[123,"close"]`} + for _, exp := range expected { + _, msg, err := conn.ReadMessage() + if string(msg) != exp || err != nil { + t.Errorf("Wrong frame, got '%s' and error '%v', expected '%s' without error", msg, err, exp) + } + } + <-done +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go new file mode 100644 index 0000000..986fe20 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr.go @@ -0,0 +1,88 @@ +package sockjs + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" +) + +var ( + cFrame = closeFrame(2010, "Another connection still open") + xhrStreamingPrelude = strings.Repeat("h", 2048) +) + +func (h *handler) xhrSend(rw http.ResponseWriter, req *http.Request) { + if req.Body == nil { + httpError(rw, "Payload expected.", http.StatusInternalServerError) + return + } + var messages []string + err := json.NewDecoder(req.Body).Decode(&messages) + if err == io.EOF { + httpError(rw, "Payload expected.", http.StatusInternalServerError) + return + } + if _, ok := err.(*json.SyntaxError); ok || err == io.ErrUnexpectedEOF { + httpError(rw, "Broken JSON encoding.", http.StatusInternalServerError) + return + } + sessionID, err := h.parseSessionID(req.URL) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + h.sessionsMux.Lock() + defer h.sessionsMux.Unlock() + if sess, ok := h.sessions[sessionID]; !ok { + http.NotFound(rw, req) + } else { + _ = sess.accept(messages...) // TODO(igm) reponse with SISE in case of err? + rw.Header().Set("content-type", "text/plain; charset=UTF-8") // Ignored by net/http (but protocol test complains), see https://code.google.com/p/go/source/detail?r=902dc062bff8 + rw.WriteHeader(http.StatusNoContent) + } +} + +type xhrFrameWriter struct{} + +func (*xhrFrameWriter) write(w io.Writer, frame string) (int, error) { + return fmt.Fprintf(w, "%s\n", frame) +} + +func (h *handler) xhrPoll(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "application/javascript; charset=UTF-8") + sess, _ := h.sessionByRequest(req) // TODO(igm) add err handling, although err should not happen as handler should not pass req in that case + receiver := newHTTPReceiver(rw, 1, new(xhrFrameWriter)) + if err := sess.attachReceiver(receiver); err != nil { + receiver.sendFrame(cFrame) + receiver.close() + return + } + + select { + case <-receiver.doneNotify(): + case <-receiver.interruptedNotify(): + } +} + +func (h *handler) xhrStreaming(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("content-type", "application/javascript; charset=UTF-8") + fmt.Fprintf(rw, "%s\n", xhrStreamingPrelude) + rw.(http.Flusher).Flush() + + sess, _ := h.sessionByRequest(req) + receiver := newHTTPReceiver(rw, h.options.ResponseLimit, new(xhrFrameWriter)) + + if err := sess.attachReceiver(receiver); err != nil { + receiver.sendFrame(cFrame) + receiver.close() + return + } + + select { + case <-receiver.doneNotify(): + case <-receiver.interruptedNotify(): + } +} diff --git a/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr_test.go b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr_test.go new file mode 100644 index 0000000..4e849cb --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/igm/sockjs-go.v2/sockjs/xhr_test.go @@ -0,0 +1,184 @@ +package sockjs + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +func TestHandler_XhrSendNilBody(t *testing.T) { + h := newTestHandler() + rec := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/non_existing_session/xhr_send", nil) + h.xhrSend(rec, req) + if rec.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response status, got '%d' expected '%d'", rec.Code, http.StatusInternalServerError) + } + if rec.Body.String() != "Payload expected." { + t.Errorf("Unexcpected body received: '%s'", rec.Body.String()) + } +} + +func TestHandler_XhrSendEmptyBody(t *testing.T) { + h := newTestHandler() + rec := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/non_existing_session/xhr_send", strings.NewReader("")) + h.xhrSend(rec, req) + if rec.Code != http.StatusInternalServerError { + t.Errorf("Unexpected response status, got '%d' expected '%d'", rec.Code, http.StatusInternalServerError) + } + if rec.Body.String() != "Payload expected." { + t.Errorf("Unexcpected body received: '%s'", rec.Body.String()) + } +} + +func TestHandler_XhrSendWrongUrlPath(t *testing.T) { + h := newTestHandler() + rec := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "incorrect", strings.NewReader("[\"a\"]")) + h.xhrSend(rec, req) + if rec.Code != http.StatusInternalServerError { + t.Errorf("Unexcpected response status, got '%d', expected '%d'", rec.Code, http.StatusInternalServerError) + } +} + +func TestHandler_XhrSendToExistingSession(t *testing.T) { + h := newTestHandler() + sess := newSession("session", time.Second, time.Second) + h.sessions["session"] = sess + + rec := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/xhr_send", strings.NewReader("[\"some message\"]")) + var done = make(chan bool) + go func() { + h.xhrSend(rec, req) + done <- true + }() + msg, _ := sess.Recv() + if msg != "some message" { + t.Errorf("Incorrect message in the channel, should be '%s', was '%s'", "some message", msg) + } + <-done + if rec.Code != http.StatusNoContent { + t.Errorf("Wrong response status received %d, should be %d", rec.Code, http.StatusNoContent) + } + if rec.Header().Get("content-type") != "text/plain; charset=UTF-8" { + t.Errorf("Wrong content type received '%s'", rec.Header().Get("content-type")) + } +} + +func TestHandler_XhrSendInvalidInput(t *testing.T) { + h := newTestHandler() + req, _ := http.NewRequest("POST", "/server/session/xhr_send", strings.NewReader("some invalid message frame")) + rec := httptest.NewRecorder() + h.xhrSend(rec, req) + if rec.Code != http.StatusInternalServerError || rec.Body.String() != "Broken JSON encoding." { + t.Errorf("Unexpected response, got '%d,%s' expected '%d,Broken JSON encoding.'", rec.Code, rec.Body.String(), http.StatusInternalServerError) + } + + // unexpected EOF + req, _ = http.NewRequest("POST", "/server/session/xhr_send", strings.NewReader("[\"x")) + rec = httptest.NewRecorder() + h.xhrSend(rec, req) + if rec.Code != http.StatusInternalServerError || rec.Body.String() != "Broken JSON encoding." { + t.Errorf("Unexpected response, got '%d,%s' expected '%d,Broken JSON encoding.'", rec.Code, rec.Body.String(), http.StatusInternalServerError) + } +} + +func TestHandler_XhrSendSessionNotFound(t *testing.T) { + h := handler{} + req, _ := http.NewRequest("POST", "/server/session/xhr_send", strings.NewReader("[\"some message\"]")) + rec := httptest.NewRecorder() + h.xhrSend(rec, req) + if rec.Code != http.StatusNotFound { + t.Errorf("Unexpected response status, got '%d' expected '%d'", rec.Code, http.StatusNotFound) + } +} + +func TestHandler_XhrPoll(t *testing.T) { + h := newTestHandler() + rw := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/server/session/xhr", nil) + h.xhrPoll(rw, req) + if rw.Header().Get("content-type") != "application/javascript; charset=UTF-8" { + t.Errorf("Wrong content type received, got '%s'", rw.Header().Get("content-type")) + } +} + +func TestHandler_XhrPollConnectionInterrupted(t *testing.T) { + h := newTestHandler() + sess := newTestSession() + sess.state = sessionActive + h.sessions["session"] = sess + req, _ := http.NewRequest("POST", "/server/session/xhr", nil) + rw := newClosableRecorder() + close(rw.closeNotifCh) + h.xhrPoll(rw, req) + sess.Lock() + if sess.state != sessionClosed { + t.Errorf("Session should be closed") + } +} + +func TestHandler_XhrPollAnotherConnectionExists(t *testing.T) { + h := newTestHandler() + // turn of timeoutes and heartbeats + sess := newSession("session", time.Hour, time.Hour) + h.sessions["session"] = sess + sess.attachReceiver(newTestReceiver()) + req, _ := http.NewRequest("POST", "/server/session/xhr", nil) + rw2 := httptest.NewRecorder() + h.xhrPoll(rw2, req) + if rw2.Body.String() != "c[2010,\"Another connection still open\"]\n" { + t.Errorf("Unexpected body, got '%s'", rw2.Body) + } +} + +func TestHandler_XhrStreaming(t *testing.T) { + h := newTestHandler() + rw := newClosableRecorder() + req, _ := http.NewRequest("POST", "/server/session/xhr_streaming", nil) + h.xhrStreaming(rw, req) + expectedBody := strings.Repeat("h", 2048) + "\no\n" + if rw.Body.String() != expectedBody { + t.Errorf("Unexpected body, got '%s' expected '%s'", rw.Body, expectedBody) + } +} + +func TestHandler_XhrStreamingAnotherReceiver(t *testing.T) { + h := newTestHandler() + h.options.ResponseLimit = 4096 + rw1 := newClosableRecorder() + req, _ := http.NewRequest("POST", "/server/session/xhr_streaming", nil) + go func() { + rec := httptest.NewRecorder() + h.xhrStreaming(rec, req) + expectedBody := strings.Repeat("h", 2048) + "\n" + "c[2010,\"Another connection still open\"]\n" + if rec.Body.String() != expectedBody { + t.Errorf("Unexpected body got '%s', expected '%s', ", rec.Body, expectedBody) + } + close(rw1.closeNotifCh) + }() + h.xhrStreaming(rw1, req) +} + +// various test only structs +func newTestHandler() *handler { + h := &handler{sessions: make(map[string]*session)} + h.options.HeartbeatDelay = time.Hour + h.options.DisconnectDelay = time.Hour + return h +} + +type ClosableRecorder struct { + *httptest.ResponseRecorder + closeNotifCh chan bool +} + +func newClosableRecorder() *ClosableRecorder { + return &ClosableRecorder{httptest.NewRecorder(), make(chan bool)} +} + +func (cr *ClosableRecorder) CloseNotify() <-chan bool { return cr.closeNotifCh } From 5921e2fc6f31e8a62e7b0b5a41d7ad11510f352c Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 27 Mar 2015 22:01:40 +0100 Subject: [PATCH 03/67] Added a Procfile --- Procfile | 1 + 1 file changed, 1 insertion(+) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..00afadb --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: api \ No newline at end of file From 7e6fdddcbb50322abffc9696f4b4928eaef93ff0 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 31 Mar 2015 19:44:58 +0200 Subject: [PATCH 04/67] Updated RethinkDB --- Godeps/Godeps.json | 10 + .../etcd/etcdserver/etcdhttp/httptypes/doc.go | 19 ++ .../etcdserver/etcdhttp/httptypes/errors.go | 49 +++++ .../etcdhttp/httptypes/errors_test.go | 47 +++++ .../etcdserver/etcdhttp/httptypes/member.go | 67 +++++++ .../etcdhttp/httptypes/member_test.go | 135 +++++++++++++ .../github.com/coreos/etcd/pkg/types/id.go | 41 ++++ .../coreos/etcd/pkg/types/id_test.go | 95 +++++++++ .../github.com/coreos/etcd/pkg/types/set.go | 178 +++++++++++++++++ .../coreos/etcd/pkg/types/set_test.go | 186 ++++++++++++++++++ .../github.com/coreos/etcd/pkg/types/slice.go | 22 +++ .../coreos/etcd/pkg/types/slice_test.go | 30 +++ .../github.com/coreos/etcd/pkg/types/urls.go | 74 +++++++ .../coreos/etcd/pkg/types/urls_test.go | 169 ++++++++++++++++ 14 files changed, 1122 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go create mode 100644 Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index dce4843..7b952c1 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -56,6 +56,16 @@ "Comment": "v0.4.3-28-g3378bdc", "Rev": "3378bdcb5cebedcbf8b5750edee28010f128fe24" }, + { + "ImportPath": "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes", + "Comment": "v2.0.4-312-g9a33678", + "Rev": "9a33678878e57410a67d07d39fa8502e85797aeb" + }, + { + "ImportPath": "github.com/coreos/etcd/pkg/types", + "Comment": "v2.0.4-312-g9a33678", + "Rev": "9a33678878e57410a67d07d39fa8502e85797aeb" + }, { "ImportPath": "github.com/coreos/go-etcd/etcd", "Comment": "v0.2.0-rc1-130-g6aa2da5", diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go new file mode 100644 index 0000000..fa01580 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/doc.go @@ -0,0 +1,19 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +/* +Package httptypes defines how etcd's HTTP API entities are serialized to and deserialized from JSON. +*/ + +package httptypes diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go new file mode 100644 index 0000000..7e0d275 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go @@ -0,0 +1,49 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package httptypes + +import ( + "encoding/json" + "log" + "net/http" +) + +type HTTPError struct { + Message string `json:"message"` + // HTTP return code + Code int `json:"-"` +} + +func (e HTTPError) Error() string { + return e.Message +} + +// TODO(xiangli): handle http write errors +func (e HTTPError) WriteTo(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(e.Code) + b, err := json.Marshal(e) + if err != nil { + log.Panicf("marshal HTTPError should never fail: %v", err) + } + w.Write(b) +} + +func NewHTTPError(code int, m string) *HTTPError { + return &HTTPError{ + Message: m, + Code: code, + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go new file mode 100644 index 0000000..f5cec6d --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors_test.go @@ -0,0 +1,47 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package httptypes + +import ( + "net/http" + "net/http/httptest" + "reflect" + "testing" +) + +func TestHTTPErrorWriteTo(t *testing.T) { + err := NewHTTPError(http.StatusBadRequest, "what a bad request you made!") + rr := httptest.NewRecorder() + err.WriteTo(rr) + + wcode := http.StatusBadRequest + wheader := http.Header(map[string][]string{ + "Content-Type": []string{"application/json"}, + }) + wbody := `{"message":"what a bad request you made!"}` + + if wcode != rr.Code { + t.Errorf("HTTP status code %d, want %d", rr.Code, wcode) + } + + if !reflect.DeepEqual(wheader, rr.HeaderMap) { + t.Errorf("HTTP headers %v, want %v", rr.HeaderMap, wheader) + } + + gbody := rr.Body.String() + if wbody != gbody { + t.Errorf("HTTP body %q, want %q", gbody, wbody) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go new file mode 100644 index 0000000..30ecbb5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go @@ -0,0 +1,67 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package httptypes + +import ( + "encoding/json" + + "github.com/coreos/etcd/pkg/types" +) + +type Member struct { + ID string `json:"id"` + Name string `json:"name"` + PeerURLs []string `json:"peerURLs"` + ClientURLs []string `json:"clientURLs"` +} + +type MemberCreateRequest struct { + PeerURLs types.URLs +} + +type MemberUpdateRequest struct { + MemberCreateRequest +} + +func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error { + s := struct { + PeerURLs []string `json:"peerURLs"` + }{} + + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + urls, err := types.NewURLs(s.PeerURLs) + if err != nil { + return err + } + + m.PeerURLs = urls + return nil +} + +type MemberCollection []Member + +func (c *MemberCollection) MarshalJSON() ([]byte, error) { + d := struct { + Members []Member `json:"members"` + }{ + Members: []Member(*c), + } + + return json.Marshal(d) +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go new file mode 100644 index 0000000..e0b29d8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member_test.go @@ -0,0 +1,135 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package httptypes + +import ( + "encoding/json" + "net/url" + "reflect" + "testing" + + "github.com/coreos/etcd/pkg/types" +) + +func TestMemberUnmarshal(t *testing.T) { + tests := []struct { + body []byte + wantMember Member + wantError bool + }{ + // no URLs, just check ID & Name + { + body: []byte(`{"id": "c", "name": "dungarees"}`), + wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil}, + }, + + // both client and peer URLs + { + body: []byte(`{"peerURLs": ["http://127.0.0.1:4001"], "clientURLs": ["http://127.0.0.1:4001"]}`), + wantMember: Member{ + PeerURLs: []string{ + "http://127.0.0.1:4001", + }, + ClientURLs: []string{ + "http://127.0.0.1:4001", + }, + }, + }, + + // multiple peer URLs + { + body: []byte(`{"peerURLs": ["http://127.0.0.1:4001", "https://example.com"]}`), + wantMember: Member{ + PeerURLs: []string{ + "http://127.0.0.1:4001", + "https://example.com", + }, + ClientURLs: nil, + }, + }, + + // multiple client URLs + { + body: []byte(`{"clientURLs": ["http://127.0.0.1:4001", "https://example.com"]}`), + wantMember: Member{ + PeerURLs: nil, + ClientURLs: []string{ + "http://127.0.0.1:4001", + "https://example.com", + }, + }, + }, + + // invalid JSON + { + body: []byte(`{"peerU`), + wantError: true, + }, + } + + for i, tt := range tests { + got := Member{} + err := json.Unmarshal(tt.body, &got) + if tt.wantError != (err != nil) { + t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err) + continue + } + + if !reflect.DeepEqual(tt.wantMember, got) { + t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got) + } + } +} + +func TestMemberCreateRequestUnmarshal(t *testing.T) { + body := []byte(`{"peerURLs": ["http://127.0.0.1:8081", "https://127.0.0.1:8080"]}`) + want := MemberCreateRequest{ + PeerURLs: types.URLs([]url.URL{ + url.URL{Scheme: "http", Host: "127.0.0.1:8081"}, + url.URL{Scheme: "https", Host: "127.0.0.1:8080"}, + }), + } + + var req MemberCreateRequest + if err := json.Unmarshal(body, &req); err != nil { + t.Fatalf("Unmarshal returned unexpected err=%v", err) + } + + if !reflect.DeepEqual(want, req) { + t.Fatalf("Failed to unmarshal MemberCreateRequest: want=%#v, got=%#v", want, req) + } +} + +func TestMemberCreateRequestUnmarshalFail(t *testing.T) { + tests := [][]byte{ + // invalid JSON + []byte(``), + []byte(`{`), + + // spot-check validation done in types.NewURLs + []byte(`{"peerURLs": "foo"}`), + []byte(`{"peerURLs": ["."]}`), + []byte(`{"peerURLs": []}`), + []byte(`{"peerURLs": ["http://127.0.0.1:4001/foo"]}`), + []byte(`{"peerURLs": ["http://127.0.0.1"]}`), + } + + for i, tt := range tests { + var req MemberCreateRequest + if err := json.Unmarshal(tt, &req); err == nil { + t.Errorf("#%d: expected err, got nil", i) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go new file mode 100644 index 0000000..88cb9e6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id.go @@ -0,0 +1,41 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "strconv" +) + +// ID represents a generic identifier which is canonically +// stored as a uint64 but is typically represented as a +// base-16 string for input/output +type ID uint64 + +func (i ID) String() string { + return strconv.FormatUint(uint64(i), 16) +} + +// IDFromString attempts to create an ID from a base-16 string. +func IDFromString(s string) (ID, error) { + i, err := strconv.ParseUint(s, 16, 64) + return ID(i), err +} + +// IDSlice implements the sort interface +type IDSlice []ID + +func (p IDSlice) Len() int { return len(p) } +func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) } +func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go new file mode 100644 index 0000000..97d168f --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/id_test.go @@ -0,0 +1,95 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "reflect" + "sort" + "testing" +) + +func TestIDString(t *testing.T) { + tests := []struct { + input ID + want string + }{ + { + input: 12, + want: "c", + }, + { + input: 4918257920282737594, + want: "444129853c343bba", + }, + } + + for i, tt := range tests { + got := tt.input.String() + if tt.want != got { + t.Errorf("#%d: ID.String failure: want=%v, got=%v", i, tt.want, got) + } + } +} + +func TestIDFromString(t *testing.T) { + tests := []struct { + input string + want ID + }{ + { + input: "17", + want: 23, + }, + { + input: "612840dae127353", + want: 437557308098245459, + }, + } + + for i, tt := range tests { + got, err := IDFromString(tt.input) + if err != nil { + t.Errorf("#%d: IDFromString failure: err=%v", i, err) + continue + } + if tt.want != got { + t.Errorf("#%d: IDFromString failure: want=%v, got=%v", i, tt.want, got) + } + } +} + +func TestIDFromStringFail(t *testing.T) { + tests := []string{ + "", + "XXX", + "612840dae127353612840dae127353", + } + + for i, tt := range tests { + _, err := IDFromString(tt) + if err == nil { + t.Fatalf("#%d: IDFromString expected error, but err=nil", i) + } + } +} + +func TestIDSlice(t *testing.T) { + g := []ID{10, 500, 5, 1, 100, 25} + w := []ID{1, 5, 10, 25, 100, 500} + sort.Sort(IDSlice(g)) + if !reflect.DeepEqual(g, w) { + t.Errorf("slice after sort = %#v, want %#v", g, w) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go new file mode 100644 index 0000000..3228752 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set.go @@ -0,0 +1,178 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "reflect" + "sort" + "sync" +) + +type Set interface { + Add(string) + Remove(string) + Contains(string) bool + Equals(Set) bool + Length() int + Values() []string + Copy() Set + Sub(Set) Set +} + +func NewUnsafeSet(values ...string) *unsafeSet { + set := &unsafeSet{make(map[string]struct{})} + for _, v := range values { + set.Add(v) + } + return set +} + +func NewThreadsafeSet(values ...string) *tsafeSet { + us := NewUnsafeSet(values...) + return &tsafeSet{us, sync.RWMutex{}} +} + +type unsafeSet struct { + d map[string]struct{} +} + +// Add adds a new value to the set (no-op if the value is already present) +func (us *unsafeSet) Add(value string) { + us.d[value] = struct{}{} +} + +// Remove removes the given value from the set +func (us *unsafeSet) Remove(value string) { + delete(us.d, value) +} + +// Contains returns whether the set contains the given value +func (us *unsafeSet) Contains(value string) (exists bool) { + _, exists = us.d[value] + return +} + +// ContainsAll returns whether the set contains all given values +func (us *unsafeSet) ContainsAll(values []string) bool { + for _, s := range values { + if !us.Contains(s) { + return false + } + } + return true +} + +// Equals returns whether the contents of two sets are identical +func (us *unsafeSet) Equals(other Set) bool { + v1 := sort.StringSlice(us.Values()) + v2 := sort.StringSlice(other.Values()) + v1.Sort() + v2.Sort() + return reflect.DeepEqual(v1, v2) +} + +// Length returns the number of elements in the set +func (us *unsafeSet) Length() int { + return len(us.d) +} + +// Values returns the values of the Set in an unspecified order. +func (us *unsafeSet) Values() (values []string) { + values = make([]string, 0) + for val, _ := range us.d { + values = append(values, val) + } + return +} + +// Copy creates a new Set containing the values of the first +func (us *unsafeSet) Copy() Set { + cp := NewUnsafeSet() + for val, _ := range us.d { + cp.Add(val) + } + + return cp +} + +// Sub removes all elements in other from the set +func (us *unsafeSet) Sub(other Set) Set { + oValues := other.Values() + result := us.Copy().(*unsafeSet) + + for _, val := range oValues { + if _, ok := result.d[val]; !ok { + continue + } + delete(result.d, val) + } + + return result +} + +type tsafeSet struct { + us *unsafeSet + m sync.RWMutex +} + +func (ts *tsafeSet) Add(value string) { + ts.m.Lock() + defer ts.m.Unlock() + ts.us.Add(value) +} + +func (ts *tsafeSet) Remove(value string) { + ts.m.Lock() + defer ts.m.Unlock() + ts.us.Remove(value) +} + +func (ts *tsafeSet) Contains(value string) (exists bool) { + ts.m.RLock() + defer ts.m.RUnlock() + return ts.us.Contains(value) +} + +func (ts *tsafeSet) Equals(other Set) bool { + ts.m.RLock() + defer ts.m.RUnlock() + return ts.us.Equals(other) +} + +func (ts *tsafeSet) Length() int { + ts.m.RLock() + defer ts.m.RUnlock() + return ts.us.Length() +} + +func (ts *tsafeSet) Values() (values []string) { + ts.m.RLock() + defer ts.m.RUnlock() + return ts.us.Values() +} + +func (ts *tsafeSet) Copy() Set { + ts.m.RLock() + defer ts.m.RUnlock() + usResult := ts.us.Copy().(*unsafeSet) + return &tsafeSet{usResult, sync.RWMutex{}} +} + +func (ts *tsafeSet) Sub(other Set) Set { + ts.m.RLock() + defer ts.m.RUnlock() + usResult := ts.us.Sub(other).(*unsafeSet) + return &tsafeSet{usResult, sync.RWMutex{}} +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go new file mode 100644 index 0000000..ff1ecc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/set_test.go @@ -0,0 +1,186 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "reflect" + "sort" + "testing" +) + +func TestUnsafeSet(t *testing.T) { + driveSetTests(t, NewUnsafeSet()) +} + +func TestThreadsafeSet(t *testing.T) { + driveSetTests(t, NewThreadsafeSet()) +} + +// Check that two slices contents are equal; order is irrelevant +func equal(a, b []string) bool { + as := sort.StringSlice(a) + bs := sort.StringSlice(b) + as.Sort() + bs.Sort() + return reflect.DeepEqual(as, bs) +} + +func driveSetTests(t *testing.T, s Set) { + // Verify operations on an empty set + eValues := []string{} + values := s.Values() + if !reflect.DeepEqual(values, eValues) { + t.Fatalf("Expect values=%v got %v", eValues, values) + } + if l := s.Length(); l != 0 { + t.Fatalf("Expected length=0, got %d", l) + } + for _, v := range []string{"foo", "bar", "baz"} { + if s.Contains(v) { + t.Fatalf("Expect s.Contains(%q) to be fale, got true", v) + } + } + + // Add three items, ensure they show up + s.Add("foo") + s.Add("bar") + s.Add("baz") + + eValues = []string{"foo", "bar", "baz"} + values = s.Values() + if !equal(values, eValues) { + t.Fatalf("Expect values=%v got %v", eValues, values) + } + + for _, v := range eValues { + if !s.Contains(v) { + t.Fatalf("Expect s.Contains(%q) to be true, got false", v) + } + } + + if l := s.Length(); l != 3 { + t.Fatalf("Expected length=3, got %d", l) + } + + // Add the same item a second time, ensuring it is not duplicated + s.Add("foo") + + values = s.Values() + if !equal(values, eValues) { + t.Fatalf("Expect values=%v got %v", eValues, values) + } + if l := s.Length(); l != 3 { + t.Fatalf("Expected length=3, got %d", l) + } + + // Remove all items, ensure they are gone + s.Remove("foo") + s.Remove("bar") + s.Remove("baz") + + eValues = []string{} + values = s.Values() + if !equal(values, eValues) { + t.Fatalf("Expect values=%v got %v", eValues, values) + } + + if l := s.Length(); l != 0 { + t.Fatalf("Expected length=0, got %d", l) + } + + // Create new copies of the set, and ensure they are unlinked to the + // original Set by making modifications + s.Add("foo") + s.Add("bar") + cp1 := s.Copy() + cp2 := s.Copy() + s.Remove("foo") + cp3 := s.Copy() + cp1.Add("baz") + + for i, tt := range []struct { + want []string + got []string + }{ + {[]string{"bar"}, s.Values()}, + {[]string{"foo", "bar", "baz"}, cp1.Values()}, + {[]string{"foo", "bar"}, cp2.Values()}, + {[]string{"bar"}, cp3.Values()}, + } { + if !equal(tt.want, tt.got) { + t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got) + } + } + + for i, tt := range []struct { + want bool + got bool + }{ + {true, s.Equals(cp3)}, + {true, cp3.Equals(s)}, + {false, s.Equals(cp2)}, + {false, s.Equals(cp1)}, + {false, cp1.Equals(s)}, + {false, cp2.Equals(s)}, + {false, cp2.Equals(cp1)}, + } { + if tt.got != tt.want { + t.Fatalf("case %d: want %t, got %t", i, tt.want, tt.got) + + } + } + + // Subtract values from a Set, ensuring a new Set is created and + // the original Sets are unmodified + sub1 := cp1.Sub(s) + sub2 := cp2.Sub(cp1) + + for i, tt := range []struct { + want []string + got []string + }{ + {[]string{"foo", "bar", "baz"}, cp1.Values()}, + {[]string{"foo", "bar"}, cp2.Values()}, + {[]string{"bar"}, s.Values()}, + {[]string{"foo", "baz"}, sub1.Values()}, + {[]string{}, sub2.Values()}, + } { + if !equal(tt.want, tt.got) { + t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got) + } + } +} + +func TestUnsafeSetContainsAll(t *testing.T) { + vals := []string{"foo", "bar", "baz"} + s := NewUnsafeSet(vals...) + + tests := []struct { + strs []string + wcontain bool + }{ + {[]string{}, true}, + {vals[:1], true}, + {vals[:2], true}, + {vals, true}, + {[]string{"cuz"}, false}, + {[]string{vals[0], "cuz"}, false}, + } + for i, tt := range tests { + if g := s.ContainsAll(tt.strs); g != tt.wcontain { + t.Errorf("#%d: ok = %v, want %v", i, g, tt.wcontain) + } + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go new file mode 100644 index 0000000..0327950 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice.go @@ -0,0 +1,22 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +// Uint64Slice implements sort interface +type Uint64Slice []uint64 + +func (p Uint64Slice) Len() int { return len(p) } +func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] } +func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go new file mode 100644 index 0000000..95e37e0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/slice_test.go @@ -0,0 +1,30 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "reflect" + "sort" + "testing" +) + +func TestUint64Slice(t *testing.T) { + g := Uint64Slice{10, 500, 5, 1, 100, 25} + w := Uint64Slice{1, 5, 10, 25, 100, 500} + sort.Sort(g) + if !reflect.DeepEqual(g, w) { + t.Errorf("slice after sort = %#v, want %#v", g, w) + } +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go new file mode 100644 index 0000000..ce2483f --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls.go @@ -0,0 +1,74 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "errors" + "fmt" + "net" + "net/url" + "sort" + "strings" +) + +type URLs []url.URL + +func NewURLs(strs []string) (URLs, error) { + all := make([]url.URL, len(strs)) + if len(all) == 0 { + return nil, errors.New("no valid URLs given") + } + for i, in := range strs { + in = strings.TrimSpace(in) + u, err := url.Parse(in) + if err != nil { + return nil, err + } + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf("URL scheme must be http or https: %s", in) + } + if _, _, err := net.SplitHostPort(u.Host); err != nil { + return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in) + } + if u.Path != "" { + return nil, fmt.Errorf("URL must not contain a path: %s", in) + } + all[i] = *u + } + us := URLs(all) + us.Sort() + + return us, nil +} + +func (us URLs) String() string { + return strings.Join(us.StringSlice(), ",") +} + +func (us *URLs) Sort() { + sort.Sort(us) +} +func (us URLs) Len() int { return len(us) } +func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() } +func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] } + +func (us URLs) StringSlice() []string { + out := make([]string, len(us)) + for i := range us { + out[i] = us[i].String() + } + + return out +} diff --git a/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go new file mode 100644 index 0000000..41caa5d --- /dev/null +++ b/Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urls_test.go @@ -0,0 +1,169 @@ +// Copyright 2015 CoreOS, Inc. +// +// 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. + +package types + +import ( + "reflect" + "testing" + + "github.com/coreos/etcd/pkg/testutil" +) + +func TestNewURLs(t *testing.T) { + tests := []struct { + strs []string + wurls URLs + }{ + { + []string{"http://127.0.0.1:4001"}, + testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}), + }, + // it can trim space + { + []string{" http://127.0.0.1:4001 "}, + testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}), + }, + // it does sort + { + []string{ + "http://127.0.0.2:4001", + "http://127.0.0.1:4001", + }, + testutil.MustNewURLs(t, []string{ + "http://127.0.0.1:4001", + "http://127.0.0.2:4001", + }), + }, + } + for i, tt := range tests { + urls, _ := NewURLs(tt.strs) + if !reflect.DeepEqual(urls, tt.wurls) { + t.Errorf("#%d: urls = %+v, want %+v", i, urls, tt.wurls) + } + } +} + +func TestURLsString(t *testing.T) { + tests := []struct { + us URLs + wstr string + }{ + { + URLs{}, + "", + }, + { + testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}), + "http://127.0.0.1:4001", + }, + { + testutil.MustNewURLs(t, []string{ + "http://127.0.0.1:4001", + "http://127.0.0.2:4001", + }), + "http://127.0.0.1:4001,http://127.0.0.2:4001", + }, + { + testutil.MustNewURLs(t, []string{ + "http://127.0.0.2:4001", + "http://127.0.0.1:4001", + }), + "http://127.0.0.2:4001,http://127.0.0.1:4001", + }, + } + for i, tt := range tests { + g := tt.us.String() + if g != tt.wstr { + t.Errorf("#%d: string = %s, want %s", i, g, tt.wstr) + } + } +} + +func TestURLsSort(t *testing.T) { + g := testutil.MustNewURLs(t, []string{ + "http://127.0.0.4:4001", + "http://127.0.0.2:4001", + "http://127.0.0.1:4001", + "http://127.0.0.3:4001", + }) + w := testutil.MustNewURLs(t, []string{ + "http://127.0.0.1:4001", + "http://127.0.0.2:4001", + "http://127.0.0.3:4001", + "http://127.0.0.4:4001", + }) + gurls := URLs(g) + gurls.Sort() + if !reflect.DeepEqual(g, w) { + t.Errorf("URLs after sort = %#v, want %#v", g, w) + } +} + +func TestURLsStringSlice(t *testing.T) { + tests := []struct { + us URLs + wstr []string + }{ + { + URLs{}, + []string{}, + }, + { + testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}), + []string{"http://127.0.0.1:4001"}, + }, + { + testutil.MustNewURLs(t, []string{ + "http://127.0.0.1:4001", + "http://127.0.0.2:4001", + }), + []string{"http://127.0.0.1:4001", "http://127.0.0.2:4001"}, + }, + { + testutil.MustNewURLs(t, []string{ + "http://127.0.0.2:4001", + "http://127.0.0.1:4001", + }), + []string{"http://127.0.0.2:4001", "http://127.0.0.1:4001"}, + }, + } + for i, tt := range tests { + g := tt.us.StringSlice() + if !reflect.DeepEqual(g, tt.wstr) { + t.Errorf("#%d: string slice = %+v, want %+v", i, g, tt.wstr) + } + } +} + +func TestNewURLsFail(t *testing.T) { + tests := [][]string{ + // no urls given + {}, + // missing protocol scheme + {"://127.0.0.1:4001"}, + // unsupported scheme + {"mailto://127.0.0.1:4001"}, + // not conform to host:port + {"http://127.0.0.1"}, + // contain a path + {"http://127.0.0.1:4001/path"}, + } + for i, tt := range tests { + _, err := NewURLs(tt) + if err == nil { + t.Errorf("#%d: err = nil, but error", i) + } + } +} From 562c59749e13f05ec4649b5177ca6220d1685dc3 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 31 Mar 2015 19:49:52 +0200 Subject: [PATCH 05/67] Properly updated gorethink --- Godeps/Godeps.json | 4 +- .../github.com/dancannon/gorethink/.gitignore | 6 +- .../dancannon/gorethink/CHANGELOG.md | 48 ++- .../github.com/dancannon/gorethink/README.md | 36 +- .../dancannon/gorethink/benchmarks_test.go | 288 ++++++++++++++ .../dancannon/gorethink/checkers_test.go | 168 ++++++++ .../dancannon/gorethink/connection.go | 2 + .../dancannon/gorethink/connection_helper.go | 2 +- .../github.com/dancannon/gorethink/cursor.go | 4 - .../src/github.com/dancannon/gorethink/doc.go | 2 +- .../dancannon/gorethink/encoding/decoder.go | 5 + .../gorethink/encoding/decoder_test.go | 75 +++- .../gorethink/encoding/decoder_types.go | 4 + .../gorethink/encoding/encoder_test.go | 32 ++ .../dancannon/gorethink/gorethink_test.go | 81 ++-- .../dancannon/gorethink/pseudotypes.go | 2 +- .../gorethink/query_geospatial_test.go | 375 +++++++++--------- .../dancannon/gorethink/query_select.go | 1 + .../dancannon/gorethink/types/geometry.go | 13 +- .../dancannon/gorethink/wercker.yml | 12 +- 20 files changed, 894 insertions(+), 266 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go create mode 100644 Godeps/_workspace/src/github.com/dancannon/gorethink/checkers_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 7b952c1..c5ce0d4 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -77,8 +77,8 @@ }, { "ImportPath": "github.com/dancannon/gorethink", - "Comment": "v0.6.1", - "Rev": "441666ad982764e434a151f17dfe399a846cee54" + "Comment": "v0.6.3-13-g67d2428", + "Rev": "67d2428ba6800ad5a3441f8740dbc77070e253b1" }, { "ImportPath": "github.com/dchest/uniuri", diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore b/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore index 385ee47..125f317 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/.gitignore @@ -3,6 +3,10 @@ *.a *.so +#vim +*.swp +*.swo + # Folders _obj _test @@ -20,4 +24,4 @@ _cgo_export.* _testmain.go *.exe -.wercker \ No newline at end of file +.wercker diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md index 67d5b76..2f1e2b2 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/CHANGELOG.md @@ -1,12 +1,29 @@ -# Changelog +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). -## v0.6.1 - 13 Feb 2015 + +## v0.6.3 - 2015-03-04 +### Added +- Add `IdentifierFormat` optarg to `TableOpts` (#158) + +### Fixed +- Fix struct alignment for ARM and x86-32 builds (#153) +- Fix sprintf format for geometry error message (#157) +- Fix duplicate if block (#159) +- Fix incorrect assertion in decoder tests + +## v0.6.2 - 2015-02-15 + +- Fixed `writeQuery` being too small when sending large queries + +## v0.6.1 - 2015-02-13 - Reduce GC by using buffers when reading and writing - Fixed encoding `time.Time` ignoring millseconds - Fixed pointers in structs that implement the `Marshaler`/`Unmarshaler` interfaces being ignored -## v0.6.0 - 1 Feb 2015 +## v0.6.0 - 2015-01-01 There are some major changes to the driver with this release that are not related to the RethinkDB v1.16 release. Please have a read through them: - Improvements to result decoding by caching reflection calls. @@ -31,11 +48,11 @@ With that out the way here are the v1.16 changes: Since this release has a lot of changes and although I have tested these changes sometimes things fall through the gaps. If you discover any bugs please let me know and I will try to fix them as soon as possible. -## Hotfix - 14 Dec 2014 +## v.0.5.1 - 2014-12-14 - Fixed empty slices being returned as `[]T(nil)` not `[]T{}` #138 -## v0.5.0 - 6 Oct 2014 +## v0.5.0 - 2014-10-06 - Added geospatial terms (`Circle`, `Distance`, `Fill`, `Geojson`, `ToGeojson`, `GetIntersecting`, `GetNearest`, `Includes`, `Intersects`, `Line`, `Point`, `Polygon`, `PolygonSub`) - Added `UUID` term for generating unique IDs @@ -44,21 +61,22 @@ Since this release has a lot of changes and although I have tested these changes - Updated the `BatchConf` field in `RunOpts`, now uses the `BatchOpts` type - Removed support for the `FieldMapper` interface -### Internal Changes +Internal Changes + - Fixed encoding performance issues, greatly improves writes/second - Updated `Next` to zero the destination value every time it is called. -## v0.4.2 - 6 Sept 2014 +## v0.4.2 - 2014-09-06 - Fixed issue causing `Close` to start an infinite loop - Tidied up connection closing logic -## v0.4.1 - 5 Sept 2014 +## v0.4.1 - 2014-09-05 - Fixed bug causing Pseudotypes to not be decoded properly (#117) - Updated github.com/fatih/pool to v2 (#118) -## v0.4.0 - 13 Aug 2014 +## v0.4.0 - 2014-08-13 - Updated the driver to support RethinkDB v1.14 (#116) - Added the Binary data type @@ -71,23 +89,23 @@ Since this release has a lot of changes and although I have tested these changes - Added the `IndexRename` command - Updated `Distinct` to now take the `Index` optional argument (using `DistinctOpts`) -### Internal Changes +Internal Changes - Updated to use the new JSON protocol - Switched the connection pool code to use github.com/fatih/pool - Added some benchmarks -## v0.3.2 - 17 Aug 2014 +## v0.3.2 - 2014-08-17 - Fixed issue causing connections not to be closed correctly (#109) - Fixed issue causing terms in optional arguments to be encoded incorrectly (#114) -## v0.3.1 - 14 June 2014 +## v0.3.1 - 2014-06-14 - Fixed "Token ## not in stream cache" error (#103) - Changed Exec to no longer use NoReply. It now waits for the server to respond. -## v0.3 (RethinkDB v1.13) - 26 June 2014 +## v0.3.0 - 2014-06-26 - Replaced `ResultRows`/`ResultRow` with `Cursor`, `Cursor` has the `Next`, `All` and `One` methods which stores the relevant value in the value pointed at by result. For more information check the examples. - Changed the time constants (Days and Months) to package globals instead of functions @@ -99,7 +117,7 @@ Since this release has a lot of changes and although I have tested these changes - `EqJoin` now accepts a function as its first argument - `Nth` now returns a selection -## v0.2 (RethinkDB v1.12) - 13 April 2014 +## v0.2.0 - 2014-04-13 * Changed `Connect` to use `ConnectOpts` instead of `map[string]interface{}` * Migrated to new `Group`/`Ungroup` functions, these replace `GroupedMapReduce` and `GroupBy` @@ -110,7 +128,7 @@ Since this release has a lot of changes and although I have tested these changes * Added `GROUPED_DATA` pseudotype * Fixed query printing -## v0.1 (RethinkDB v1.11) - 27 November 2013 +## v0.1.0 - 2013-11-27 * Added noreply writes * Added the new terms `index_status`, `index_wait` and `sync` diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md index 0ccf115..10a1e42 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/README.md @@ -7,7 +7,7 @@ [Go](http://golang.org/) driver for [RethinkDB](http://www.rethinkdb.com/) -Current version: v0.6.1 (RethinkDB v1.16) +Current version: v0.6.3 (RethinkDB v1.16) **Version 0.6 introduced some small API changes and some significant internal changes, for more information check the [change log](CHANGELOG.md) and please be aware the driver is not yet stable** @@ -135,7 +135,7 @@ Cursors have a number of methods available for accessing the query results - `Next` retrieves the next document from the result set, blocking if necessary. - `All` retrieves all documents from the result set into the provided slice. -- `One` retrieves the first document from the result se. +- `One` retrieves the first document from the result set. Examples: @@ -207,6 +207,38 @@ func (a A) FieldMap() map[string]string { } ``` +## Benchmarks + +Everyone wants their project's benchmarks to be speedy. And while we know that rethinkDb and the gorethink driver are quite fast, our primary goal is for our benchmarks to be correct. They are designed to give you, the user, an accurate picture of writes per second (w/s). If you come up with a accurate test that meets this aim, submit a pull request please. + +Thanks to @jaredfolkins for the contribution. + +| Type | Value | +| --- | --- | +| **Model Name** | MacBook Pro | +| **Model Identifier** | MacBookPro11,3 | +| **Processor Name** | Intel Core i7 | +| **Processor Speed** | 2.3 GHz | +| **Number of Processors** | 1 | +| **Total Number of Cores** | 4 | +| **L2 Cache (per Core)** | 256 KB | +| **L3 Cache** | 6 MB | +| **Memory** | 16 GB | + +```bash +BenchmarkBatch200RandomWrites 20 557227775 ns/op +BenchmarkBatch200RandomWritesParallel10 30 354465417 ns/op +BenchmarkBatch200SoftRandomWritesParallel10 100 761639276 ns/op +BenchmarkRandomWrites 100 10456580 ns/op +BenchmarkRandomWritesParallel10 1000 1614175 ns/op +BenchmarkRandomSoftWrites 3000 589660 ns/op +BenchmarkRandomSoftWritesParallel10 10000 247588 ns/op +BenchmarkSequentialWrites 50 24408285 ns/op +BenchmarkSequentialWritesParallel10 1000 1755373 ns/op +BenchmarkSequentialSoftWrites 3000 631211 ns/op +BenchmarkSequentialSoftWritesParallel10 10000 263481 ns/op +``` + ## Examples View other examples on the [wiki](https://github.com/dancannon/gorethink/wiki/Examples). diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go new file mode 100644 index 0000000..8d6646c --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/benchmarks_test.go @@ -0,0 +1,288 @@ +package gorethink + +import ( + "math/rand" + "strconv" + "sync" + "testing" + "time" +) + +var bSess *Session +var bDbName string +var bTableName string + +func BenchmarkBatch200RandomWrites(b *testing.B) { + + var term Term + var data []map[string]interface{} + + for i := 0; i < b.N; i++ { + + for is := 0; is < 200; is++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + cid := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + data = append(data, cid) + } + + // Insert the new item into the database + term = Table(bTableName).Insert(data) + + // Insert the new item into the database + _, err := term.RunWrite(bSess, RunOpts{ + MinBatchRows: 200, + MaxBatchRows: 200, + }) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + +} + +func BenchmarkBatch200RandomWritesParallel10(b *testing.B) { + + var term Term + var data []map[string]interface{} + + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + + for pb.Next() { + for is := 0; is < 200; is++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + cid := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + data = append(data, cid) + } + + // Insert the new item into the database + term = Table(bTableName).Insert(data) + + // Insert the new item into the database + _, err := term.RunWrite(bSess, RunOpts{ + MinBatchRows: 200, + MaxBatchRows: 200, + }) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + }) + +} + +func BenchmarkBatch200SoftRandomWritesParallel10(b *testing.B) { + + var term Term + var data []map[string]interface{} + + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + + for pb.Next() { + + opts := InsertOpts{Durability: "soft"} + + for is := 0; is < 200; is++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + cid := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + data = append(data, cid) + } + + // Insert the new item into the database + term = Table(bTableName).Insert(data, opts) + + // Insert the new item into the database + _, err := term.RunWrite(bSess, RunOpts{ + MinBatchRows: 200, + MaxBatchRows: 200, + }) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + }) + +} + +func BenchmarkRandomWrites(b *testing.B) { + + for i := 0; i < b.N; i++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + data := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + // Insert the new item into the database + _, err := Table(bTableName).Insert(data).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + +} + +func BenchmarkRandomWritesParallel10(b *testing.B) { + + // p*GOMAXPROCS + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + data := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + // Insert the new item into the database + _, err := Table(bTableName).Insert(data).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + }) + +} + +func BenchmarkRandomSoftWrites(b *testing.B) { + + for i := 0; i < b.N; i++ { + data := map[string]interface{}{ + "customer_id": strconv.FormatInt(rand.Int63(), 10), + } + // Insert the new item into the database + opts := InsertOpts{Durability: "soft"} + _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + +} + +func BenchmarkRandomSoftWritesParallel10(b *testing.B) { + + // p*GOMAXPROCS + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + data := map[string]interface{}{ + "customer_id": strconv.FormatInt(r.Int63(), 10), + } + + // Insert the new item into the database + opts := InsertOpts{Durability: "soft"} + _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + } + } + }) + +} + +func BenchmarkSequentialWrites(b *testing.B) { + + si := 0 + for i := 0; i < b.N; i++ { + si++ + data := map[string]interface{}{ + "customer_id": si, + } + + // Insert the new item into the database + _, err := Table(bTableName).Insert(data).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + return + } + } +} + +func BenchmarkSequentialWritesParallel10(b *testing.B) { + + var mu sync.Mutex + si := 0 + + // p*GOMAXPROCS + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + mu.Lock() + si++ + mu.Unlock() + + data := map[string]interface{}{ + "customer_id": si, + } + + // Insert the new item into the database + _, err := Table(bTableName).Insert(data).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + return + } + } + }) + +} + +func BenchmarkSequentialSoftWrites(b *testing.B) { + + opts := InsertOpts{Durability: "soft"} + si := 0 + + for i := 0; i < b.N; i++ { + si++ + data := map[string]interface{}{ + "customer_id": si, + } + + // Insert the new item into the database + _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + return + } + } +} + +func BenchmarkSequentialSoftWritesParallel10(b *testing.B) { + + var mu sync.Mutex + si := 0 + + // p*GOMAXPROCS + b.SetParallelism(10) + + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + mu.Lock() + si++ + mu.Unlock() + + data := map[string]interface{}{ + "customer_id": si, + } + + opts := InsertOpts{Durability: "soft"} + + // Insert the new item into the database + _, err := Table(bTableName).Insert(data, opts).RunWrite(bSess) + if err != nil { + b.Errorf("insert failed [%s] ", err) + return + } + } + }) + +} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/checkers_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/checkers_test.go new file mode 100644 index 0000000..9659db7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/checkers_test.go @@ -0,0 +1,168 @@ +package gorethink + +import ( + "encoding/json" + "fmt" + + test "gopkg.in/check.v1" + + "github.com/dancannon/gorethink/types" +) + +type jsonChecker struct { + *test.CheckerInfo +} + +func (j jsonChecker) Check(params []interface{}, names []string) (result bool, error string) { + var jsonParams []interface{} + for _, param := range params { + jsonParam, err := json.Marshal(param) + if err != nil { + return false, err.Error() + } + jsonParams = append(jsonParams, jsonParam) + } + return test.DeepEquals.Check(jsonParams, names) +} + +// jsonEquals compares two interface{} objects by converting them to JSON and +// seeing if the strings match +var jsonEquals = &jsonChecker{ + &test.CheckerInfo{Name: "jsonEquals", Params: []string{"obtained", "expected"}}, +} + +type geometryChecker struct { + *test.CheckerInfo +} + +func (j geometryChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained, ok := params[0].(types.Geometry) + if !ok { + return false, "obtained must be a Geometry" + } + expectedType, ok := params[1].(string) + if !ok { + return false, "expectedType must be a string" + } + + switch expectedType { + case "Polygon": + expectedCoords, ok := params[2].([][][]float64) + if !ok { + return false, "expectedCoords must be a [][][]float64" + } + + return comparePolygon(expectedCoords, obtained) + case "LineString": + expectedCoords, ok := params[2].([][]float64) + if !ok { + return false, "expectedCoords must be a [][]float64" + } + + return compareLineString(expectedCoords, obtained) + case "Point": + expectedCoords, ok := params[2].([]float64) + if !ok { + return false, "expectedCoords must be a []float64" + } + + return comparePoint(expectedCoords, obtained) + default: + return false, "unknown expectedType" + } +} + +// geometryEquals compares two geometry values, all coordinates are compared with a small amount of tolerance +var geometryEquals = &geometryChecker{ + &test.CheckerInfo{Name: "geometryEquals", Params: []string{"obtained", "expectedType", "expectedCoords"}}, +} + +/* BEGIN FLOAT HELPERS */ + +// totally ripped off from math/all_test.go +// https://github.com/golang/go/blob/master/src/math/all_test.go#L1723-L1749 +func tolerance(a, b, e float64) bool { + d := a - b + if d < 0 { + d = -d + + } + + if a != 0 { + e = e * a + if e < 0 { + e = -e + + } + + } + return d < e +} + +func mehclose(a, b float64) bool { return tolerance(a, b, 1e-2) } +func kindaclose(a, b float64) bool { return tolerance(a, b, 1e-8) } +func prettyclose(a, b float64) bool { return tolerance(a, b, 1e-14) } +func veryclose(a, b float64) bool { return tolerance(a, b, 4e-16) } +func soclose(a, b, e float64) bool { return tolerance(a, b, e) } + +func comparePolygon(expected [][][]float64, obtained types.Geometry) (result bool, error string) { + if obtained.Type != "Polygon" { + return false, fmt.Sprintf("obtained geometry has incorrect type, has %s but expected Polygon", obtained.Type) + } + + for i, line := range obtained.Lines { + for j, point := range line { + ok, err := assertPointsEqual( + expected[i][j][0], point.Lon, // Lon + expected[i][j][1], point.Lat, // Lat + ) + if !ok { + return false, err + } + } + } + + return true, "" +} + +func compareLineString(expected [][]float64, obtained types.Geometry) (result bool, error string) { + if obtained.Type != "LineString" { + return false, fmt.Sprintf("obtained geometry has incorrect type, has %s but expected LineString", obtained.Type) + } + + for j, point := range obtained.Line { + ok, err := assertPointsEqual( + expected[j][0], point.Lon, // Lon + expected[j][1], point.Lat, // Lat + ) + if !ok { + return false, err + } + } + + return true, "" +} + +func comparePoint(expected []float64, obtained types.Geometry) (result bool, error string) { + if obtained.Type != "Point" { + return false, fmt.Sprintf("obtained geometry has incorrect type, has %s but expected Point", obtained.Type) + } + + return assertPointsEqual( + expected[0], obtained.Point.Lon, // Lon + expected[1], obtained.Point.Lat, // Lat + ) +} + +func assertPointsEqual(expectedLon, obtainedLon, expectedLat, obtainedLat float64) (result bool, error string) { + if !kindaclose(expectedLon, obtainedLon) { + return false, fmt.Sprintf("the deviation between the compared floats is too great [%v:%v]", expectedLon, obtainedLon) + } + if !kindaclose(expectedLat, obtainedLat) { + return false, fmt.Sprintf("the deviation between the compared floats is too great [%v:%v]", expectedLat, obtainedLat) + } + + return true, "" +} + +/* END FLOAT HELPERS */ diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go index 076abd3..50ddf06 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection.go @@ -28,6 +28,7 @@ type Response struct { type Connection struct { conn net.Conn opts *ConnectOpts + _ [4]byte token int64 cursors map[int64]*Cursor bad bool @@ -164,6 +165,7 @@ func (c *Connection) sendQuery(q Query) error { // getToken generates the next query token, used to number requests and match // responses with requests. func (c *Connection) nextToken() int64 { + // requires c.token to be 64-bit aligned on ARM return atomic.AddInt64(&c.token, 1) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go index ec59c38..8fe909d 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/connection_helper.go @@ -71,7 +71,7 @@ func (c *Connection) writeQuery(token int64, q []byte) error { pos := 0 dataLen := 8 + 4 + len(q) - data := c.buf.takeSmallBuffer(dataLen) + data := c.buf.takeBuffer(dataLen) if data == nil { return RqlDriverError{ErrBusyBuffer.Error()} } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go index f71018a..ec4d505 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/cursor.go @@ -279,10 +279,6 @@ func (c *Cursor) IsNil() bool { return true } - if bufferedItem == nil { - return true - } - return false } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go index c32c42a..66cb0a8 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/doc.go @@ -1,6 +1,6 @@ // Go driver for RethinkDB // -// Current version: v0.6.1 (RethinkDB v1.16) +// Current version: v0.6.3 (RethinkDB v1.16) // For more in depth information on how to use RethinkDB check out the API docs // at http://rethinkdb.com/api package gorethink diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go index 76edc63..f50478a 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder.go @@ -69,6 +69,11 @@ func valueDecoder(dv, sv reflect.Value) decoderFunc { return invalidValueDecoder } + if dv.IsValid() { + dv = indirect(dv, false) + dv.Set(reflect.Zero(dv.Type())) + } + return typeDecoder(dv.Type(), sv.Type()) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go index 90f065f..909b2cc 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_test.go @@ -3,7 +3,6 @@ package encoding import ( "bytes" "encoding/json" - "fmt" "image" "reflect" "testing" @@ -125,6 +124,11 @@ type S13 struct { S8 } +type Pointer struct { + PPoint *Point + Point Point +} + type decodeTest struct { in interface{} ptr interface{} @@ -149,7 +153,7 @@ var decodeTests = []decodeTest{ {in: string("2"), ptr: new(interface{}), out: string("2")}, {in: "a\u1234", ptr: new(string), out: "a\u1234"}, {in: []interface{}{}, ptr: new([]string), out: []string{}}, - {in: map[string]interface{}{"X": []interface{}{1, 2, 3}, "Y": 4}, ptr: new(T), out: T{}, err: &DecodeTypeError{reflect.TypeOf([0]interface{}{}), reflect.TypeOf(""), ""}}, + {in: map[string]interface{}{"X": []interface{}{1, 2, 3}, "Y": 4}, ptr: new(T), out: T{}, err: &DecodeTypeError{reflect.TypeOf(""), reflect.TypeOf([]interface{}{}), ""}}, {in: map[string]interface{}{"x": 1}, ptr: new(tx), out: tx{}}, {in: map[string]interface{}{"F1": float64(1), "F2": 2, "F3": 3}, ptr: new(V), out: V{F1: float64(1), F2: int32(2), F3: string("3")}}, {in: map[string]interface{}{"F1": string("1"), "F2": 2, "F3": 3}, ptr: new(V), out: V{F1: string("1"), F2: int32(2), F3: string("3")}}, @@ -239,6 +243,16 @@ var decodeTests = []decodeTest{ ptr: new(S10), out: S10{S13: S13{S8: S8{S9: S9{Y: 2}}}}, }, + { + in: map[string]interface{}{"PPoint": map[string]interface{}{"Z": 1}, "Point": map[string]interface{}{"Z": 2}}, + ptr: new(Pointer), + out: Pointer{PPoint: &Point{Z: 1}, Point: Point{Z: 2}}, + }, + { + in: map[string]interface{}{"Point": map[string]interface{}{"Z": 2}}, + ptr: new(Pointer), + out: Pointer{PPoint: nil, Point: Point{Z: 2}}, + }, } func TestDecode(t *testing.T) { @@ -256,9 +270,7 @@ func TestDecode(t *testing.T) { continue } - if !jsonEqual(v.Elem().Interface(), tt.out) { - fmt.Printf("%#v\n", v.Elem().Interface()) - fmt.Printf("%#v\n", tt.out) + if tt.err == nil && !jsonEqual(v.Elem().Interface(), tt.out) { t.Errorf("#%d: mismatch\nhave: %+v\nwant: %+v", i, v.Elem().Interface(), tt.out) continue } @@ -347,6 +359,59 @@ type Bar struct { Baz int `gorethink:"baz"` } +type UnmarshalerPointer struct { + Value *UnmarshalerValue +} + +type UnmarshalerValue struct { + ValueInt int64 + ValueString string +} + +func (v *UnmarshalerValue) MarshalRQL() (interface{}, error) { + if v.ValueInt != int64(0) { + return Encode(v.ValueInt) + } + if v.ValueString != "" { + return Encode(v.ValueString) + } + + return Encode(nil) +} + +func (v *UnmarshalerValue) UnmarshalRQL(b interface{}) (err error) { + n, s := int64(0), "" + + if err = Decode(&s, b); err == nil { + v.ValueString = s + return + } + if err = Decode(&n, b); err == nil { + v.ValueInt = n + + } + + return +} + +func TestDecodeUnmarshalerPointer(t *testing.T) { + input := map[string]interface{}{ + "Value": "abc", + } + want := &UnmarshalerPointer{ + Value: &UnmarshalerValue{ValueString: "abc"}, + } + + out := &UnmarshalerPointer{} + err := Decode(out, input) + if err != nil { + t.Errorf("got error %v, expected nil", err) + } + if !jsonEqual(out, want) { + t.Errorf("got %q, want %q", out, want) + } +} + func jsonEqual(a, b interface{}) bool { ba, err := json.Marshal(a) if err != nil { diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go index d49939e..61d268f 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/decoder_types.go @@ -210,6 +210,10 @@ func unmarshalerDecoder(dv, sv reflect.Value) { dv = dv.Addr() } + if dv.IsNil() { + dv.Set(reflect.New(dv.Type().Elem())) + } + u := dv.Interface().(Unmarshaler) err := u.UnmarshalRQL(sv.Interface()) if err != nil { diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go index dd1a626..bacae94 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/encoding/encoder_test.go @@ -123,6 +123,38 @@ func TestAnonymousNonstruct(t *testing.T) { } } +func TestEncodePointer(t *testing.T) { + v := Pointer{PPoint: &Point{Z: 1}, Point: Point{Z: 2}} + var want = map[string]interface{}{ + "PPoint": map[string]interface{}{"Z": int64(1)}, + "Point": map[string]interface{}{"Z": int64(2)}, + } + + got, err := Encode(v) + if err != nil { + t.Fatalf("Encode: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } +} + +func TestEncodeNilPointer(t *testing.T) { + v := Pointer{PPoint: nil, Point: Point{Z: 2}} + var want = map[string]interface{}{ + "PPoint": nil, + "Point": map[string]interface{}{"Z": int64(2)}, + } + + got, err := Encode(v) + if err != nil { + t.Fatalf("Encode: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("got %v, want %v", got, want) + } +} + type BugA struct { S string } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go index 2afac67..27d6608 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/gorethink_test.go @@ -1,8 +1,8 @@ package gorethink import ( - "encoding/json" "flag" + "log" "math/rand" "os" "runtime" @@ -35,6 +35,59 @@ func init() { authKey = os.Getenv("RETHINKDB_AUTHKEY") } +// +// Begin TestMain(), Setup, Teardown +// +func testBenchmarkSetup() { + + var err error + + bDbName = "benchmark" + bTableName = "benchmarks" + + bSess, err = Connect(ConnectOpts{ + Address: url, + Database: bDbName, + MaxIdle: 50, + MaxOpen: 50, + }) + + if err != nil { + log.Fatalln(err.Error()) + } + + DbDrop(bDbName).Exec(bSess) + DbCreate(bDbName).Exec(bSess) + + Db(bDbName).TableDrop(bTableName).Run(bSess) + Db(bDbName).TableCreate(bTableName).Run(bSess) + +} + +func testBenchmarkTeardown() { + Db(bDbName).TableDrop(bTableName).Run(bSess) + bSess.Close() +} + +// stubs +func testSetup() {} +func testTeardown() {} + +func TestMain(m *testing.M) { + // seed randomness for use with tests + rand.Seed(time.Now().UTC().UnixNano()) + testSetup() + testBenchmarkSetup() + res := m.Run() + testTeardown() + testBenchmarkTeardown() + os.Exit(res) +} + +// +// End TestMain(), Setup, Teardown +// + // Hook up gocheck into the gotest runner. func Test(t *testing.T) { test.TestingT(t) } @@ -55,32 +108,6 @@ func (s *RethinkSuite) TearDownSuite(c *test.C) { sess.Close() } -type jsonChecker struct { - info *test.CheckerInfo -} - -func (j jsonChecker) Info() *test.CheckerInfo { - return j.info -} - -func (j jsonChecker) Check(params []interface{}, names []string) (result bool, error string) { - var jsonParams []interface{} - for _, param := range params { - jsonParam, err := json.Marshal(param) - if err != nil { - return false, err.Error() - } - jsonParams = append(jsonParams, jsonParam) - } - return test.DeepEquals.Check(jsonParams, names) -} - -// jsonEquals compares two interface{} objects by converting them to JSON and -// seeing if the strings match -var jsonEquals = &jsonChecker{ - &test.CheckerInfo{Name: "jsonEquals", Params: []string{"obtained", "expected"}}, -} - // Expressions used in tests var now = time.Now() var arr = []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9} diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go index 343d689..724f32b 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/pseudotypes.go @@ -214,6 +214,6 @@ func reqlGeometryToNativeGeometry(obj map[string]interface{}) (interface{}, erro }, nil } } else { - return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field has unknown type %s", typ) + return nil, fmt.Errorf("pseudo-type GEOMETRY object %v field has unknown type %s", obj, typ) } } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go index a576bd6..0b9518f 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_geospatial_test.go @@ -1,42 +1,43 @@ package gorethink import ( - "github.com/dancannon/gorethink/types" - test "gopkg.in/check.v1" + + "github.com/dancannon/gorethink/types" ) func (s *RethinkSuite) TestGeospatialDecodeGeometryPseudoType(c *test.C) { var response types.Geometry + + // setup coordinates + coords := [][][]float64{ + { + {-122.423246, 37.779388}, + {-122.423246, 37.329898}, + {-121.88642, 37.329898}, + {-121.88642, 37.329898}, + {-122.423246, 37.779388}, + }, + } + + gt := "Polygon" res, err := Expr(map[string]interface{}{ "$reql_type$": "GEOMETRY", "type": "Polygon", - "coordinates": []interface{}{ - []interface{}{ - []interface{}{-122.423246, 37.779388}, - []interface{}{-122.423246, 37.329898}, - []interface{}{-121.88642, 37.329898}, - []interface{}{-121.88642, 37.779388}, - []interface{}{-122.423246, 37.779388}, - }, - }, + "coordinates": coords, }).Run(sess) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.779388}, - types.Point{Lon: -122.423246, Lat: 37.329898}, - types.Point{Lon: -121.88642, Lat: 37.329898}, - types.Point{Lon: -121.88642, Lat: 37.779388}, - types.Point{Lon: -122.423246, Lat: 37.779388}, - }, - }, - }) + + // test shape + if response.Type != gt { + c.Errorf("expected [%v], instead [%v]", gt, response.Type) + } + + // assert points are within threshold + c.Assert(response, geometryEquals, "Polygon", coords) } func (s *RethinkSuite) TestGeospatialEncodeGeometryPseudoType(c *test.C) { @@ -53,7 +54,7 @@ func (s *RethinkSuite) TestGeospatialEncodeGeometryPseudoType(c *test.C) { }, }) c.Assert(err, test.IsNil) - c.Assert(encoded, test.DeepEquals, map[string]interface{}{ + c.Assert(encoded, jsonEquals, map[string]interface{}{ "$reql_type$": "GEOMETRY", "type": "Polygon", "coordinates": []interface{}{ @@ -75,44 +76,41 @@ func (s *RethinkSuite) TestGeospatialCircle(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, - types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, - types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, - types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, - types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, - types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, - types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, - types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, - types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, - types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, - types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, - types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, - types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, - types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, - types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, - types.Point{Lon: -122.423246, Lat: 37.779478096334365}, - types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, - types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, - types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, - types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, - types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, - types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, - types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, - types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, - types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, - types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, - types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, - types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, - types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, - types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, - types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.423246, 37.77929790366427}, + {-122.42326814543915, 37.77929963483801}, + {-122.4232894398445, 37.779304761831504}, + {-122.42330906488651, 37.77931308761787}, + {-122.42332626638755, 37.77932429224285}, + {-122.42334038330416, 37.77933794512014}, + {-122.42335087313059, 37.77935352157849}, + {-122.42335733274696, 37.77937042302436}, + {-122.4233595139113, 37.77938799994533}, + {-122.42335733279968, 37.7794055768704}, + {-122.42335087322802, 37.779422478327966}, + {-122.42334038343147, 37.77943805480385}, + {-122.42332626652532, 37.779451707701796}, + {-122.42330906501378, 37.77946291234741}, + {-122.42328943994191, 37.77947123815131}, + {-122.42326814549187, 37.77947636515649}, + {-122.423246, 37.779478096334365}, + {-122.42322385450814, 37.77947636515649}, + {-122.4232025600581, 37.77947123815131}, + {-122.42318293498623, 37.77946291234741}, + {-122.42316573347469, 37.779451707701796}, + {-122.42315161656855, 37.77943805480385}, + {-122.423141126772, 37.779422478327966}, + {-122.42313466720033, 37.7794055768704}, + {-122.42313248608872, 37.77938799994533}, + {-122.42313466725305, 37.77937042302436}, + {-122.42314112686942, 37.77935352157849}, + {-122.42315161669585, 37.77933794512014}, + {-122.42316573361246, 37.77932429224285}, + {-122.4231829351135, 37.77931308761787}, + {-122.42320256015552, 37.779304761831504}, + {-122.42322385456086, 37.77929963483801}, + {-122.423246, 37.77929790366427}, }, }) } @@ -124,44 +122,41 @@ func (s *RethinkSuite) TestGeospatialCirclePoint(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, - types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, - types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, - types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, - types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, - types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, - types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, - types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, - types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, - types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, - types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, - types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, - types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, - types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, - types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, - types.Point{Lon: -122.423246, Lat: 37.779478096334365}, - types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, - types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, - types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, - types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, - types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, - types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, - types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, - types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, - types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, - types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, - types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, - types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, - types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, - types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, - types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.423246, 37.77929790366427}, + {-122.42326814543915, 37.77929963483801}, + {-122.4232894398445, 37.779304761831504}, + {-122.42330906488651, 37.77931308761787}, + {-122.42332626638755, 37.77932429224285}, + {-122.42334038330416, 37.77933794512014}, + {-122.42335087313059, 37.77935352157849}, + {-122.42335733274696, 37.77937042302436}, + {-122.4233595139113, 37.77938799994533}, + {-122.42335733279968, 37.7794055768704}, + {-122.42335087322802, 37.779422478327966}, + {-122.42334038343147, 37.77943805480385}, + {-122.42332626652532, 37.779451707701796}, + {-122.42330906501378, 37.77946291234741}, + {-122.42328943994191, 37.77947123815131}, + {-122.42326814549187, 37.77947636515649}, + {-122.423246, 37.779478096334365}, + {-122.42322385450814, 37.77947636515649}, + {-122.4232025600581, 37.77947123815131}, + {-122.42318293498623, 37.77946291234741}, + {-122.42316573347469, 37.779451707701796}, + {-122.42315161656855, 37.77943805480385}, + {-122.423141126772, 37.779422478327966}, + {-122.42313466720033, 37.7794055768704}, + {-122.42313248608872, 37.77938799994533}, + {-122.42313466725305, 37.77937042302436}, + {-122.42314112686942, 37.77935352157849}, + {-122.42315161669585, 37.77933794512014}, + {-122.42316573361246, 37.77932429224285}, + {-122.4231829351135, 37.77931308761787}, + {-122.42320256015552, 37.779304761831504}, + {-122.42322385456086, 37.77929963483801}, + {-122.423246, 37.77929790366427}, }, }) } @@ -173,76 +168,83 @@ func (s *RethinkSuite) TestGeospatialCirclePointFill(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - types.Point{Lon: -122.42326814543915, Lat: 37.77929963483801}, - types.Point{Lon: -122.4232894398445, Lat: 37.779304761831504}, - types.Point{Lon: -122.42330906488651, Lat: 37.77931308761787}, - types.Point{Lon: -122.42332626638755, Lat: 37.77932429224285}, - types.Point{Lon: -122.42334038330416, Lat: 37.77933794512014}, - types.Point{Lon: -122.42335087313059, Lat: 37.77935352157849}, - types.Point{Lon: -122.42335733274696, Lat: 37.77937042302436}, - types.Point{Lon: -122.4233595139113, Lat: 37.77938799994533}, - types.Point{Lon: -122.42335733279968, Lat: 37.7794055768704}, - types.Point{Lon: -122.42335087322802, Lat: 37.779422478327966}, - types.Point{Lon: -122.42334038343147, Lat: 37.77943805480385}, - types.Point{Lon: -122.42332626652532, Lat: 37.779451707701796}, - types.Point{Lon: -122.42330906501378, Lat: 37.77946291234741}, - types.Point{Lon: -122.42328943994191, Lat: 37.77947123815131}, - types.Point{Lon: -122.42326814549187, Lat: 37.77947636515649}, - types.Point{Lon: -122.423246, Lat: 37.779478096334365}, - types.Point{Lon: -122.42322385450814, Lat: 37.77947636515649}, - types.Point{Lon: -122.4232025600581, Lat: 37.77947123815131}, - types.Point{Lon: -122.42318293498623, Lat: 37.77946291234741}, - types.Point{Lon: -122.42316573347469, Lat: 37.779451707701796}, - types.Point{Lon: -122.42315161656855, Lat: 37.77943805480385}, - types.Point{Lon: -122.423141126772, Lat: 37.779422478327966}, - types.Point{Lon: -122.42313466720033, Lat: 37.7794055768704}, - types.Point{Lon: -122.42313248608872, Lat: 37.77938799994533}, - types.Point{Lon: -122.42313466725305, Lat: 37.77937042302436}, - types.Point{Lon: -122.42314112686942, Lat: 37.77935352157849}, - types.Point{Lon: -122.42315161669585, Lat: 37.77933794512014}, - types.Point{Lon: -122.42316573361246, Lat: 37.77932429224285}, - types.Point{Lon: -122.4231829351135, Lat: 37.77931308761787}, - types.Point{Lon: -122.42320256015552, Lat: 37.779304761831504}, - types.Point{Lon: -122.42322385456086, Lat: 37.77929963483801}, - types.Point{Lon: -122.423246, Lat: 37.77929790366427}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.423246, 37.77929790366427}, + {-122.42326814543915, 37.77929963483801}, + {-122.4232894398445, 37.779304761831504}, + {-122.42330906488651, 37.77931308761787}, + {-122.42332626638755, 37.77932429224285}, + {-122.42334038330416, 37.77933794512014}, + {-122.42335087313059, 37.77935352157849}, + {-122.42335733274696, 37.77937042302436}, + {-122.4233595139113, 37.77938799994533}, + {-122.42335733279968, 37.7794055768704}, + {-122.42335087322802, 37.779422478327966}, + {-122.42334038343147, 37.77943805480385}, + {-122.42332626652532, 37.779451707701796}, + {-122.42330906501378, 37.77946291234741}, + {-122.42328943994191, 37.77947123815131}, + {-122.42326814549187, 37.77947636515649}, + {-122.423246, 37.779478096334365}, + {-122.42322385450814, 37.77947636515649}, + {-122.4232025600581, 37.77947123815131}, + {-122.42318293498623, 37.77946291234741}, + {-122.42316573347469, 37.779451707701796}, + {-122.42315161656855, 37.77943805480385}, + {-122.423141126772, 37.779422478327966}, + {-122.42313466720033, 37.7794055768704}, + {-122.42313248608872, 37.77938799994533}, + {-122.42313466725305, 37.77937042302436}, + {-122.42314112686942, 37.77935352157849}, + {-122.42315161669585, 37.77933794512014}, + {-122.42316573361246, 37.77932429224285}, + {-122.4231829351135, 37.77931308761787}, + {-122.42320256015552, 37.779304761831504}, + {-122.42322385456086, 37.77929963483801}, + {-122.423246, 37.77929790366427}, }, }) + } func (s *RethinkSuite) TestGeospatialPointDistanceMethod(c *test.C) { var response float64 + f := 734125.249602186 res, err := Point(-122.423246, 37.779388).Distance(Point(-117.220406, 32.719464)).Run(sess) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.Equals, 734125.249602186) + if !kindaclose(response, f) { + c.Errorf("the deviation between the compared floats is too great [%v:%v]", response, f) + } } func (s *RethinkSuite) TestGeospatialPointDistanceRoot(c *test.C) { var response float64 + f := 734125.249602186 res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464)).Run(sess) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.Equals, 734125.249602186) + if !kindaclose(response, f) { + c.Errorf("the deviation between the compared floats is too great [%v:%v]", response, f) + } } func (s *RethinkSuite) TestGeospatialPointDistanceRootKm(c *test.C) { var response float64 + f := 734.125249602186 res, err := Distance(Point(-122.423246, 37.779388), Point(-117.220406, 32.719464), DistanceOpts{Unit: "km"}).Run(sess) c.Assert(err, test.IsNil) err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.Equals, 734.125249602186) + if !kindaclose(response, f) { + c.Errorf("the deviation between the compared floats is too great [%v:%v]", response, f) + } } func (s *RethinkSuite) TestGeospatialFill(c *test.C) { @@ -257,16 +259,13 @@ func (s *RethinkSuite) TestGeospatialFill(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.779388}, - types.Point{Lon: -122.423246, Lat: 37.329898}, - types.Point{Lon: -121.88642, Lat: 37.329898}, - types.Point{Lon: -121.88642, Lat: 37.779388}, - types.Point{Lon: -122.423246, Lat: 37.779388}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.423246, 37.779388}, + {-122.423246, 37.329898}, + {-121.88642, 37.329898}, + {-121.88642, 37.779388}, + {-122.423246, 37.779388}, }, }) } @@ -281,10 +280,7 @@ func (s *RethinkSuite) TestGeospatialGeojson(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Point", - Point: types.Point{Lon: -122.423246, Lat: 37.779388}, - }) + c.Assert(response, geometryEquals, "Point", []float64{-122.423246, 37.779388}) } func (s *RethinkSuite) TestGeospatialToGeojson(c *test.C) { @@ -294,7 +290,7 @@ func (s *RethinkSuite) TestGeospatialToGeojson(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, map[string]interface{}{ + c.Assert(response, jsonEquals, map[string]interface{}{ "type": "Point", "coordinates": []interface{}{-122.423246, 37.779388}, }) @@ -433,12 +429,9 @@ func (s *RethinkSuite) TestGeospatialLineLatLon(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "LineString", - Line: types.Line{ - types.Point{Lon: -122.423246, Lat: 37.779388}, - types.Point{Lon: -121.886420, Lat: 37.329898}, - }, + c.Assert(response, geometryEquals, "LineString", [][]float64{ + {-122.423246, 37.779388}, + {-121.886420, 37.329898}, }) } @@ -449,12 +442,9 @@ func (s *RethinkSuite) TestGeospatialLinePoint(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "LineString", - Line: types.Line{ - types.Point{Lon: -122.423246, Lat: 37.779388}, - types.Point{Lon: -121.886420, Lat: 37.329898}, - }, + c.Assert(response, geometryEquals, "LineString", [][]float64{ + {-122.423246, 37.779388}, + {-121.886420, 37.329898}, }) } @@ -465,10 +455,11 @@ func (s *RethinkSuite) TestGeospatialPoint(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ + c.Assert(response, jsonEquals, types.Geometry{ Type: "Point", Point: types.Point{Lon: -122.423246, Lat: 37.779388}, }) + c.Assert(response, geometryEquals, "Point", []float64{-122.423246, 37.779388}) } func (s *RethinkSuite) TestGeospatialPolygon(c *test.C) { @@ -478,15 +469,12 @@ func (s *RethinkSuite) TestGeospatialPolygon(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.423246, Lat: 37.779388}, - types.Point{Lon: -122.423246, Lat: 37.329898}, - types.Point{Lon: -121.88642, Lat: 37.329898}, - types.Point{Lon: -122.423246, Lat: 37.779388}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.423246, 37.779388}, + {-122.423246, 37.329898}, + {-121.88642, 37.329898}, + {-122.423246, 37.779388}, }, }) } @@ -508,23 +496,20 @@ func (s *RethinkSuite) TestGeospatialPolygonSub(c *test.C) { err = res.One(&response) c.Assert(err, test.IsNil) - c.Assert(response, test.DeepEquals, types.Geometry{ - Type: "Polygon", - Lines: types.Lines{ - types.Line{ - types.Point{Lon: -122.4, Lat: 37.7}, - types.Point{Lon: -122.4, Lat: 37.3}, - types.Point{Lon: -121.8, Lat: 37.3}, - types.Point{Lon: -121.8, Lat: 37.7}, - types.Point{Lon: -122.4, Lat: 37.7}, - }, - types.Line{ - types.Point{Lon: -122.3, Lat: 37.4}, - types.Point{Lon: -122.3, Lat: 37.6}, - types.Point{Lon: -122, Lat: 37.6}, - types.Point{Lon: -122, Lat: 37.4}, - types.Point{Lon: -122.3, Lat: 37.4}, - }, + c.Assert(response, geometryEquals, "Polygon", [][][]float64{ + { + {-122.4, 37.7}, + {-122.4, 37.3}, + {-121.8, 37.3}, + {-121.8, 37.7}, + {-122.4, 37.7}, + }, + { + {-122.3, 37.4}, + {-122.3, 37.6}, + {-122, 37.6}, + {-122, 37.4}, + {-122.3, 37.4}, }, }) } diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go index d4cfb8f..5f15593 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/query_select.go @@ -11,6 +11,7 @@ func Db(args ...interface{}) Term { type TableOpts struct { UseOutdated interface{} `gorethink:"use_outdated,omitempty"` + IdentifierFormat interface{} `gorethink:"identifier_format,omitempty"` } func (o *TableOpts) toMap() map[string]interface{} { diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go b/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go index 93c220d..00ff80f 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/types/geometry.go @@ -1,6 +1,8 @@ package types -import "fmt" +import ( + "fmt" +) type Geometry struct { Type string @@ -23,6 +25,15 @@ func (g Geometry) MarshalRQL() (interface{}, error) { } func (g *Geometry) UnmarshalRQL(data interface{}) error { + if data, ok := data.(Geometry); ok { + g.Type = data.Type + g.Point = data.Point + g.Line = data.Line + g.Lines = data.Lines + + return nil + } + m, ok := data.(map[string]interface{}) if !ok { return fmt.Errorf("pseudo-type GEOMETRY object is not valid") diff --git a/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml b/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml index f03209c..b2b33e4 100644 --- a/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml +++ b/Godeps/_workspace/src/github.com/dancannon/gorethink/wercker.yml @@ -6,14 +6,7 @@ services: build: # The steps that will be executed on build steps: - # Sets the go workspace and places you package - # at the right place in the workspace tree - pjvds/setup-go-workspace - - - script: - name: Populate cache - code: |- - if test -d "$WERCKER_CACHE_DIR/go-pkg-cache"; then rsync -avzv --exclude "$WERCKER_SOURCE_DIR" "$WERCKER_CACHE_DIR/go-pkg-cache/" "$GOPATH/" ; fi # Gets the dependencies - script: name: get dependencies @@ -31,7 +24,4 @@ build: name: Test code: |- go get -u gopkg.in/check.v1 - - script: - name: Store cache - code: |- - rsync -avzv --exclude "$WERCKER_SOURCE_DIR" "$GOPATH/" "$WERCKER_CACHE_DIR/go-pkg-cache/" + go test ./... -short From fce903f2e6d4f75717e834634f25ff989e30cd1d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 01:34:18 +0200 Subject: [PATCH 06/67] New thread fetching query --- db/table_threads.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/db/table_threads.go b/db/table_threads.go index 23a0995..ec591e1 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -1,6 +1,8 @@ package db import ( + "time" + "github.com/dancannon/gorethink" "github.com/lavab/api/models" @@ -102,13 +104,18 @@ func (t *ThreadsTable) List( } // Add manifests - term = term.InnerJoin(gorethink.Db(t.GetDBName()).Table("emails").Pluck("thread", "manifest"), func(thread gorethink.Term, email gorethink.Term) gorethink.Term { - return thread.Field("id").Eq(email.Field("thread")) - }).Without(map[string]interface{}{ - "right": map[string]interface{}{ - "thread": true, - }, - }).Zip() + term = term.Map(func(thread gorethink.Term) gorethink.Term { + return gorethink.Db(t.GetDBName()).Table("emails").Between([]interface{}{ + thread.Field("id"), + time.Date(1990, time.January, 1, 23, 0, 0, 0, time.UTC), + }, []interface{}{ + thread.Field("id"), + time.Date(2090, time.January, 1, 23, 0, 0, 0, time.UTC), + }, gorethink.BetweenOpts{ + Index: "threadAndDate", + }).OrderBy(gorethink.OrderByOpts{Index: "threadAndDate"}). + Nth(0).Pluck("manifest").Merge(thread) + }) // Run the query cursor, err := term.Run(t.GetSession()) From 275e53802d5205a256a148d84676f569e92cd214 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 11:59:40 +0200 Subject: [PATCH 07/67] Update table_threads.go --- db/table_threads.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/table_threads.go b/db/table_threads.go index ec591e1..0b2d358 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -105,7 +105,7 @@ func (t *ThreadsTable) List( // Add manifests term = term.Map(func(thread gorethink.Term) gorethink.Term { - return gorethink.Db(t.GetDBName()).Table("emails").Between([]interface{}{ + return thread.Merge(gorethink.Db(t.GetDBName()).Table("emails").Between([]interface{}{ thread.Field("id"), time.Date(1990, time.January, 1, 23, 0, 0, 0, time.UTC), }, []interface{}{ @@ -114,7 +114,7 @@ func (t *ThreadsTable) List( }, gorethink.BetweenOpts{ Index: "threadAndDate", }).OrderBy(gorethink.OrderByOpts{Index: "threadAndDate"}). - Nth(0).Pluck("manifest").Merge(thread) + Nth(0).Pluck("manifest")) }) // Run the query From e0d879825664a9472225190921d57d5831539918 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 22:01:34 +0200 Subject: [PATCH 08/67] Multiple label parsing --- db/table_threads.go | 78 ++++++++++++++++++++++++++++++--------------- routes/threads.go | 9 ++++-- 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/db/table_threads.go b/db/table_threads.go index ec591e1..a63094b 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -50,28 +50,13 @@ func (t *ThreadsTable) List( sort []string, offset int, limit int, - label string, + labels string, ) ([]*models.Thread, error) { - var term gorethink.Term - - if owner != "" && label != "" { - term = t.GetTable().Filter(func(row gorethink.Term) gorethink.Term { - return gorethink.And( - row.Field("owner").Eq(owner), - row.Field("labels").Contains(label), - ) - }) - } else if owner != "" && label == "" { - term = t.GetTable().Filter(map[string]interface{}{ - "owner": owner, - }) - } else if owner == "" && label != "" { - term = t.GetTable().Filter(func(row gorethink.Term) gorethink.Term { - return row.Field("labels").Contains(label) - }) - } else { - term = t.GetTable() + term := t.GetTable() + + if owner != "" { + term = t.GetTable().GetAllByIndex("owner", owner) } // If sort array has contents, parse them and add to the term @@ -90,16 +75,57 @@ func (t *ThreadsTable) List( term = term.OrderBy(conds...) } - // Slice the result in 3 cases - if offset != 0 && limit == 0 { - term = term.Skip(offset) + // Parse labels + hasLabels := []string{} + excLabels := []string{} + for _, label := range labels { + if label[0] == '-' { + excLabels = append(excLabels, label) + } else { + hasLabels = append(hasLabels, label) + } } - if offset == 0 && limit != 0 { - term = term.Limit(limit) + // Transform that into a term + if len(hasLabels) > 0 || len(excLabels) > 0 { + var hasTerm gorethink.Term + if len(hasLabels) == 1 { + hasTerm = gorethink.Row.Field("labels").Contains(hasLabels[0]) + } else if len(hasLabels) > 0 { + for i, label := range hasLabels { + if i == 0 { + hasTerm = gorethink.Row.Field("labels").Contains(label) + } else { + hasTerm = hasTerm.And(gorethink.Row.Field("labels").Contains(label)) + } + } + } + + var excTerm gorethink.Term + if len(excLabels) == 1 { + excTerm = gorethink.Not(gorethink.Row.Field("labels").Contains(excLabels[0])) + } else { + for i, label := range excLabels { + if i == 0 { + excTerm = gorethink.Not(gorethink.Row.Field("labels").Contains(label)) + } else { + excTerm = excTerm.And(gorethink.Not(gorethink.Row.Field("labels").Contains(label))) + } + } + } + + // Append them into the term + if len(hasLabels) > 0 && len(excLabels) > 0 { + term = term.Filter(hasTerm.And(excTerm)) + } else if len(hasLabels) > 0 && len(excLabels) == 0 { + term = term.Filter(hasTerm) + } else if len(hasLabels) == 0 && len(excLabels) > 0 { + term = term.Filter(excTerm) + } } - if offset != 0 && limit != 0 { + // Slice the result + if offset != 0 || limit != 0 { term = term.Slice(offset, offset+limit) } diff --git a/routes/threads.go b/routes/threads.go index 95b4484..1e04d7f 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -32,7 +32,8 @@ func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { offsetRaw = query.Get("offset") limitRaw = query.Get("limit") countFiles = query.Get("count_files") - label = query.Get("label") + labelsRaw = query.Get("label") + labels []string sort []string offset int limit int @@ -76,7 +77,11 @@ func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { sort = strings.Split(sortRaw, ",") } - threads, err := env.Threads.List(session.Owner, sort, offset, limit, label) + if labelsRaw != "" { + labels = strings.Split(labelsRaw, "") + } + + threads, err := env.Threads.List(session.Owner, sort, offset, limit, labels) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), From 8c9be3c4fdb854ba134274bd5edbf674be95dfbd Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 22:16:04 +0200 Subject: [PATCH 09/67] Fixed a typo --- db/table_threads.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/table_threads.go b/db/table_threads.go index 5aa97a5..5298d13 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -50,7 +50,7 @@ func (t *ThreadsTable) List( sort []string, offset int, limit int, - labels string, + labels []string, ) ([]*models.Thread, error) { term := t.GetTable() From 69afafd5629663ddad9017a75399ca176a6ee1bf Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 22:20:46 +0200 Subject: [PATCH 10/67] thank you based andreis --- db/table_threads.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/table_threads.go b/db/table_threads.go index 5298d13..46fc0fa 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -80,7 +80,7 @@ func (t *ThreadsTable) List( excLabels := []string{} for _, label := range labels { if label[0] == '-' { - excLabels = append(excLabels, label) + excLabels = append(excLabels, label[1:]) } else { hasLabels = append(hasLabels, label) } From f665a90e638d41fb0521bec002c42e766ede0956 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 22:56:32 +0200 Subject: [PATCH 11/67] Removed count_files. Fixes #116 --- db/table_files.go | 17 -------------- models/thread.go | 3 +-- routes/threads.go | 57 ++++++++--------------------------------------- 3 files changed, 10 insertions(+), 67 deletions(-) diff --git a/db/table_files.go b/db/table_files.go index dbe9da1..25c2a97 100644 --- a/db/table_files.go +++ b/db/table_files.go @@ -77,23 +77,6 @@ func (f *FilesTable) CountByEmail(id string) (int, error) { return result, nil } -func (f *FilesTable) CountByThread(id ...interface{}) (int, error) { - query, err := f.GetTable().Filter(func(row gorethink.Term) gorethink.Term { - return gorethink.Table("emails").GetAllByIndex("thread", id...).Field("files").Contains(row.Field("id")) - }).Count().Run(f.GetSession()) - if err != nil { - return 0, err - } - - var result int - err = query.One(&result) - if err != nil { - return 0, err - } - - return result, nil -} - func (f *FilesTable) GetInEmail(owner string, email string, name string) ([]*models.File, error) { e, err := f.Emails.GetEmail(email) if err != nil { diff --git a/models/thread.go b/models/thread.go index aceb473..6ab92d8 100644 --- a/models/thread.go +++ b/models/thread.go @@ -17,8 +17,7 @@ type Thread struct { IsRead bool `json:"is_read" gorethink:"is_read"` LastRead string `json:"last_read" gorethink:"last_read"` - FilesCount *int `json:"files_count,omitempty" gorethink:"-"` - Manifest string `json:"manifest,omitempty" gorethink:"manifest"` + Manifest string `json:"manifest,omitempty" gorethink:"manifest"` // SHA256 hash of the raw subject without prefixes SubjectHash string `json:"subject_hash" gorethink:"subject_hash"` diff --git a/routes/threads.go b/routes/threads.go index 1e04d7f..e78583b 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -27,16 +27,15 @@ func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { session := c.Env["token"].(*models.Token) var ( - query = r.URL.Query() - sortRaw = query.Get("sort") - offsetRaw = query.Get("offset") - limitRaw = query.Get("limit") - countFiles = query.Get("count_files") - labelsRaw = query.Get("label") - labels []string - sort []string - offset int - limit int + query = r.URL.Query() + sortRaw = query.Get("sort") + offsetRaw = query.Get("offset") + limitRaw = query.Get("limit") + labelsRaw = query.Get("label") + labels []string + sort []string + offset int + limit int ) if offsetRaw != "" { @@ -110,26 +109,6 @@ func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Total-Count", strconv.Itoa(count)) } - if countFiles == "true" || countFiles == "1" { - for _, thread := range threads { - count, err := env.Files.CountByThread(thread.ID) - if err != nil { - env.Log.WithFields(logrus.Fields{ - "error": err.Error(), - "thread": thread.ID, - }).Error("Unable to count files per thread") - - utils.JSONResponse(w, 500, &ThreadsListResponse{ - Success: false, - Message: "Internal error (code TH/LI/03)", - }) - return - } - - thread.FilesCount = &count - } - } - utils.JSONResponse(w, 200, &ThreadsListResponse{ Success: true, Threads: &threads, @@ -192,24 +171,6 @@ func ThreadsGet(c web.C, w http.ResponseWriter, r *http.Request) { } } - if ok := r.URL.Query().Get("count_files"); ok == "true" || ok == "1" { - count, err := env.Files.CountByThread(thread.ID) - if err != nil { - env.Log.WithFields(logrus.Fields{ - "error": err.Error(), - "id": thread.ID, - }).Error("Unable to count files linked to a thread") - - utils.JSONResponse(w, 500, &ThreadsGetResponse{ - Success: false, - Message: "Unable to count files", - }) - return - } - - thread.FilesCount = &count - } - utils.JSONResponse(w, 200, &ThreadsGetResponse{ Success: true, Thread: thread, From c02273110eb1dd88595279cbd15ccdcd0cbf4263 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 23:56:22 +0200 Subject: [PATCH 12/67] Removed labels count for spam and trash --- routes/labels.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routes/labels.go b/routes/labels.go index 579cb77..9da8573 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -35,6 +35,10 @@ func LabelsList(c web.C, w http.ResponseWriter, r *http.Request) { } for _, label := range labels { + if label.Builtin && (label.Name == "Spam" || label.Name == "Trash") { + continue + } + totalCount, err := env.Threads.CountByLabel(label.ID) if err != nil { env.Log.WithFields(logrus.Fields{ From 79792d4b5b1e9b7bdf6010b0d5996feccae0f469 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 23:57:05 +0200 Subject: [PATCH 13/67] Changed label ignoring logic. Fixes #114 --- routes/labels.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/labels.go b/routes/labels.go index 9da8573..e31588d 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -35,7 +35,7 @@ func LabelsList(c web.C, w http.ResponseWriter, r *http.Request) { } for _, label := range labels { - if label.Builtin && (label.Name == "Spam" || label.Name == "Trash") { + if label.Builtin && label.Name != "Inbox" { continue } From e707bc98cf8aadca573cf3db4bc11ff6f82e58e9 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 1 Apr 2015 23:59:22 +0200 Subject: [PATCH 14/67] Added account status checks to account creation. Fixes #111 --- routes/accounts.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/routes/accounts.go b/routes/accounts.go index ea7453e..d8bdd1f 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -215,6 +215,15 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Ensure that the account is "registered" + if account.Status != "registered" { + utils.JSONResponse(w, 403, &AccountsCreateResponse{ + Success: true, + Message: "This account was already configured", + }) + return + } + // Everything is fine, return it. utils.JSONResponse(w, 200, &AccountsCreateResponse{ Success: true, @@ -289,6 +298,15 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Ensure that the account is "registered" + if account.Status != "registered" { + utils.JSONResponse(w, 403, &AccountsCreateResponse{ + Success: true, + Message: "This account was already configured", + }) + return + } + // Our token is fine, next part: password. // Ensure that user has chosen a secure password (check against 10k most used) From 0afc261a2f59d42250451cfeaff85fa2d69e5d3c Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 2 Apr 2015 09:12:14 +0200 Subject: [PATCH 15/67] Update threads.go --- routes/threads.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/threads.go b/routes/threads.go index e78583b..de0bff9 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -77,7 +77,7 @@ func ThreadsList(c web.C, w http.ResponseWriter, r *http.Request) { } if labelsRaw != "" { - labels = strings.Split(labelsRaw, "") + labels = strings.Split(labelsRaw, ",") } threads, err := env.Threads.List(session.Owner, sort, offset, limit, labels) From 81cd8ee1e21aa5ebf4d3989fecc581df9a936009 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 2 Apr 2015 13:46:10 +0200 Subject: [PATCH 16/67] New index setup. Fixes #119 --- db/setup.go | 97 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 32 deletions(-) diff --git a/db/setup.go b/db/setup.go index c503457..e4c3871 100644 --- a/db/setup.go +++ b/db/setup.go @@ -1,28 +1,9 @@ package db import ( - "github.com/dancannon/gorethink" + r "github.com/dancannon/gorethink" ) -// Publicly exported table models -var ( - Accounts *AccountsTable - Sessions *TokensTable -) - -// Indexes of tables in the database -var tableIndexes = map[string][]string{ - "accounts": []string{"name", "alt_email"}, - "contacts": []string{"owner"}, - "emails": []string{"owner", "label_ids", "date_created", "thread"}, - "files": []string{"owner"}, - "keys": []string{"owner", "key_id"}, - "labels": []string{"owner"}, - "reservations": []string{"email", "name"}, - "threads": []string{"owner", "subject_hash"}, - "tokens": []string{"owner"}, -} - // List of names of databases var databaseNames = []string{ "prod", @@ -32,27 +13,79 @@ var databaseNames = []string{ } // Setup configures the RethinkDB server -func Setup(opts gorethink.ConnectOpts) error { +func Setup(opts r.ConnectOpts) error { // Initialize a new setup connection - setupSession, err := gorethink.Connect(opts) + ss, err := r.Connect(opts) if err != nil { return err } // Create databases for _, d := range databaseNames { - gorethink.DbCreate(d).Run(setupSession) + r.DbCreate(d).Exec(ss) + + r.Db(d).TableCreate("accounts").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("name").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("date_modified").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("alt_email").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("type").Exec(ss) + r.Db(d).Table("accounts").IndexCreate("status").Exec(ss) + + r.Db(d).TableCreate("contacts").Exec(ss) + r.Db(d).Table("contacts").IndexCreate("owner").Exec(ss) + r.Db(d).Table("contacts").IndexCreate("name").Exec(ss) + r.Db(d).Table("contacts").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("contacts").IndexCreate("date_modified").Exec(ss) + + r.Db(d).TableCreate("emails").Exec(ss) + r.Db(d).Table("emails").IndexCreate("owner").Exec(ss) + r.Db(d).Table("emails").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("emails").IndexCreate("date_modified").Exec(ss) + r.Db(d).Table("emails").IndexCreate("thread").Exec(ss) + r.Db(d).Table("emails").IndexCreate("kind").Exec(ss) + r.Db(d).Table("emails").IndexCreate("from").Exec(ss) + r.Db(d).Table("emails").IndexCreate("to", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("emails").IndexCreate("cc", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("emails").IndexCreate("bcc", r.IndexCreateOpts{Multi: true}).Exec(ss) + + r.Db(d).TableCreate("files").Exec(ss) + r.Db(d).Table("files").IndexCreate("owner").Exec(ss) + r.Db(d).Table("files").IndexCreate("name").Exec(ss) + r.Db(d).Table("files").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("files").IndexCreate("date_modified").Exec(ss) + + r.Db(d).TableCreate("keys").Exec(ss) + r.Db(d).Table("keys").IndexCreate("owner").Exec(ss) + r.Db(d).Table("keys").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("keys").IndexCreate("date_modified").Exec(ss) + r.Db(d).Table("keys").IndexCreate("key_id").Exec(ss) + + r.Db(d).TableCreate("labels").Exec(ss) + r.Db(d).Table("labels").IndexCreate("name").Exec(ss) + r.Db(d).Table("labels").IndexCreate("builtin").Exec(ss) + r.Db(d).Table("labels").IndexCreate("owner").Exec(ss) + r.Db(d).Table("labels").IndexCreate([]interface{}{"name", "owner", "builtin"}).Exec(ss) - // Create tables - for t, indexes := range tableIndexes { - gorethink.Db(d).TableCreate(t).RunWrite(setupSession) + r.Db(d).TableCreate("threads").Exec(ss) + r.Db(d).Table("threads").IndexCreate("name").Exec(ss) + r.Db(d).Table("threads").IndexCreate("owner").Exec(ss) + r.Db(d).Table("threads").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("threads").IndexCreate("date_modified").Exec(ss) + r.Db(d).Table("threads").IndexCreate("emails", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("threads").IndexCreate("labels", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("threads").IndexCreate("members", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("threads").IndexCreate("subject_hash").Exec(ss) + r.Db(d).Table("threads").IndexCreate("secure").Exec(ss) - // Create indexes - for _, index := range indexes { - gorethink.Db(d).Table(t).IndexCreate(index).Exec(setupSession) - } - } + r.Db(d).TableCreate("tokens").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("name").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("owner").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("date_modified").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("type").Exec(ss) + r.Db(d).Table("tokens").IndexCreate("expiry_date").Exec(ss) } - return setupSession.Close() + return ss.Close() } From ff4445a284b69f497bf91b50390501348cb382aa Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 2 Apr 2015 15:16:58 +0200 Subject: [PATCH 17/67] Ownership check for files during email creation. Fixes #101 --- db/table_files.go | 19 +++++++++++++++++++ routes/emails.go | 19 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/db/table_files.go b/db/table_files.go index 25c2a97..efd7560 100644 --- a/db/table_files.go +++ b/db/table_files.go @@ -21,6 +21,25 @@ func (f *FilesTable) GetFile(id string) (*models.File, error) { return &result, nil } +func (f *FilesTable) GetFiles(ids ...string) ([]*models.File, error) { + iids := make([]interface{}, len(ids)) + for i, v := range ids { + iids[i] = v + } + + query, err := f.GetTable().GetAll(iids...).Run(f.GetSession()) + if err != nil { + return nil, err + } + + var result []*models.File + if err := query.All(&result); err != nil { + return nil, err + } + + return result, nil +} + func (f *FilesTable) GetOwnedBy(id string) ([]*models.File, error) { var result []*models.File diff --git a/routes/emails.go b/routes/emails.go index 7393730..993f06b 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -195,6 +195,25 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } + // Check rights to files + files, err := env.Files.GetFiles(input.Files...) + if err != nil { + utils.JSONResponse(w, 500, &EmailsCreateResponse{ + Success: false, + Message: "Unable to fetch emails", + }) + return + } + for _, file := range files { + if file.Owner != session.Owner { + utils.JSONResponse(w, 403, &EmailsCreateResponse{ + Success: false, + Message: "You are not the owner of file " + file.ID, + }) + return + } + } + // Create an email resource resource := models.MakeResource(session.Owner, input.Subject) From 129ed5e24dc5a215b6d5d28562bd90ee1c03336b Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 10:19:04 +0200 Subject: [PATCH 18/67] File ownership check was a little bit overeager. Fixes #120 --- routes/emails.go | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/routes/emails.go b/routes/emails.go index 993f06b..c7819ee 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -195,23 +195,25 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } - // Check rights to files - files, err := env.Files.GetFiles(input.Files...) - if err != nil { - utils.JSONResponse(w, 500, &EmailsCreateResponse{ - Success: false, - Message: "Unable to fetch emails", - }) - return - } - for _, file := range files { - if file.Owner != session.Owner { - utils.JSONResponse(w, 403, &EmailsCreateResponse{ + if input.Files != nil && len(input.Files) > 0 { + // Check rights to files + files, err := env.Files.GetFiles(input.Files...) + if err != nil { + utils.JSONResponse(w, 500, &EmailsCreateResponse{ Success: false, - Message: "You are not the owner of file " + file.ID, + Message: "Unable to fetch emails", }) return } + for _, file := range files { + if file.Owner != session.Owner { + utils.JSONResponse(w, 403, &EmailsCreateResponse{ + Success: false, + Message: "You are not the owner of file " + file.ID, + }) + return + } + } } // Create an email resource From 5515288cb3276729e4e5105d2d1fdca63c5e53d7 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 14:09:17 +0200 Subject: [PATCH 19/67] Database preparation for the new threading algorithm --- db/setup.go | 21 ++++++++++++++++++++- models/email.go | 2 ++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/db/setup.go b/db/setup.go index e4c3871..eb22730 100644 --- a/db/setup.go +++ b/db/setup.go @@ -45,9 +45,16 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("emails").IndexCreate("thread").Exec(ss) r.Db(d).Table("emails").IndexCreate("kind").Exec(ss) r.Db(d).Table("emails").IndexCreate("from").Exec(ss) + r.Db(d).Table("emails").IndexCreate("message_id").Exec(ss) r.Db(d).Table("emails").IndexCreate("to", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("emails").IndexCreate("cc", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("emails").IndexCreate("bcc", r.IndexCreateOpts{Multi: true}).Exec(ss) + r.Db(d).Table("emails").IndexCreateFunc("messageIDOwner", func(row r.Term) r.Term { + return r.Expr([]interface{}{ + row.Field("message_id"), + row.Field("owner"), + }) + }).Exec(ss) r.Db(d).TableCreate("files").Exec(ss) r.Db(d).Table("files").IndexCreate("owner").Exec(ss) @@ -65,7 +72,13 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("labels").IndexCreate("name").Exec(ss) r.Db(d).Table("labels").IndexCreate("builtin").Exec(ss) r.Db(d).Table("labels").IndexCreate("owner").Exec(ss) - r.Db(d).Table("labels").IndexCreate([]interface{}{"name", "owner", "builtin"}).Exec(ss) + r.Db(d).Table("labels").IndexCreateFunc("nameOwnerBuiltin", func(row r.Term) r.Term { + return r.Expr([]interface{}{ + row.Field("name"), + row.Field("owner"), + row.Field("builtin"), + }) + }).Exec(ss) r.Db(d).TableCreate("threads").Exec(ss) r.Db(d).Table("threads").IndexCreate("name").Exec(ss) @@ -77,6 +90,12 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("threads").IndexCreate("members", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("threads").IndexCreate("subject_hash").Exec(ss) r.Db(d).Table("threads").IndexCreate("secure").Exec(ss) + r.Db(d).Table("threads").IndexCreateFunc("subjectOwner", func(row r.Term) r.Term { + return r.Expr([]interface{}{ + row.Field("subject_hash"), + row.Field("owner"), + }) + }) r.Db(d).TableCreate("tokens").Exec(ss) r.Db(d).Table("tokens").IndexCreate("name").Exec(ss) diff --git a/models/email.go b/models/email.go index 298ca10..eccc7dd 100644 --- a/models/email.go +++ b/models/email.go @@ -4,6 +4,8 @@ package models type Email struct { Resource + MessageID string `json:"message_id" gorethink:"message_id"` + // Kind is the type of encryption used in the email: // - raw - when sending raw emails before they get sent // - manifest - Manifest field is not empty, From 2a4093a7fb24a4645bbf5aba09fbd238a2f8ebbe Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 14:22:56 +0200 Subject: [PATCH 20/67] MessageID for outbound emails --- routes/emails.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/emails.go b/routes/emails.go index c7819ee..0e74e59 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -330,7 +330,8 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { // Create a new email struct email := &models.Email{ - Resource: resource, + Resource: resource, + MessageID: resource.ID + "@lavaboom.com", Kind: input.Kind, Thread: input.Thread, From 293deb2cd00d91aa9ae1553f6161fbc64e92d3cc Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 15:29:11 +0200 Subject: [PATCH 21/67] Additional compound index --- db/setup.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/db/setup.go b/db/setup.go index eb22730..878d367 100644 --- a/db/setup.go +++ b/db/setup.go @@ -55,6 +55,12 @@ func Setup(opts r.ConnectOpts) error { row.Field("owner"), }) }).Exec(ss) + r.Db(d).Table("emails").IndexCreateFunc("threadStatus", func(row r.Term) r.Term { + return r.Expr([]interface{}{ + row.Field("thread"), + row.Field("status"), + }) + }) r.Db(d).TableCreate("files").Exec(ss) r.Db(d).Table("files").IndexCreate("owner").Exec(ss) From 007c39b817904f6fbce39b2b3df7181786c1761f Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 15:50:50 +0200 Subject: [PATCH 22/67] Proper compound index creation --- db/setup.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/db/setup.go b/db/setup.go index 878d367..eb7fb27 100644 --- a/db/setup.go +++ b/db/setup.go @@ -49,17 +49,17 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("emails").IndexCreate("to", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("emails").IndexCreate("cc", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("emails").IndexCreate("bcc", r.IndexCreateOpts{Multi: true}).Exec(ss) - r.Db(d).Table("emails").IndexCreateFunc("messageIDOwner", func(row r.Term) r.Term { - return r.Expr([]interface{}{ + r.Db(d).Table("emails").IndexCreateFunc("messageIDOwner", func(row r.Term) interface{} { + return []interface{}{ row.Field("message_id"), row.Field("owner"), - }) + } }).Exec(ss) - r.Db(d).Table("emails").IndexCreateFunc("threadStatus", func(row r.Term) r.Term { - return r.Expr([]interface{}{ + r.Db(d).Table("emails").IndexCreateFunc("threadStatus", func(row r.Term) interface{} { + return []interface{}{ row.Field("thread"), row.Field("status"), - }) + } }) r.Db(d).TableCreate("files").Exec(ss) @@ -78,12 +78,12 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("labels").IndexCreate("name").Exec(ss) r.Db(d).Table("labels").IndexCreate("builtin").Exec(ss) r.Db(d).Table("labels").IndexCreate("owner").Exec(ss) - r.Db(d).Table("labels").IndexCreateFunc("nameOwnerBuiltin", func(row r.Term) r.Term { - return r.Expr([]interface{}{ + r.Db(d).Table("labels").IndexCreateFunc("nameOwnerBuiltin", func(row r.Term) interface{} { + return []interface{}{ row.Field("name"), row.Field("owner"), row.Field("builtin"), - }) + } }).Exec(ss) r.Db(d).TableCreate("threads").Exec(ss) @@ -96,11 +96,11 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("threads").IndexCreate("members", r.IndexCreateOpts{Multi: true}).Exec(ss) r.Db(d).Table("threads").IndexCreate("subject_hash").Exec(ss) r.Db(d).Table("threads").IndexCreate("secure").Exec(ss) - r.Db(d).Table("threads").IndexCreateFunc("subjectOwner", func(row r.Term) r.Term { - return r.Expr([]interface{}{ + r.Db(d).Table("threads").IndexCreateFunc("subjectOwner", func(row r.Term) interface{} { + return []interface{}{ row.Field("subject_hash"), row.Field("owner"), - }) + } }) r.Db(d).TableCreate("tokens").Exec(ss) From 1a9e1cb38b18e306922d4ded566d61256511037b Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 21:02:17 +0200 Subject: [PATCH 23/67] Addresses mapping table and endpoint --- db/setup.go | 9 +++++++-- db/table_addresses.go | 33 +++++++++++++++++++++++++++++++++ env/env.go | 2 ++ models/address.go | 7 +++++++ routes/accounts.go | 21 +++++++++++++++++++++ routes/addresses.go | 39 +++++++++++++++++++++++++++++++++++++++ setup/setup.go | 10 ++++++++++ 7 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 db/table_addresses.go create mode 100644 models/address.go create mode 100644 routes/addresses.go diff --git a/db/setup.go b/db/setup.go index eb7fb27..7c2a5e8 100644 --- a/db/setup.go +++ b/db/setup.go @@ -32,6 +32,11 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("accounts").IndexCreate("type").Exec(ss) r.Db(d).Table("accounts").IndexCreate("status").Exec(ss) + r.Db(d).TableCreate("addresses").Exec(ss) + r.Db(d).Table("addresses").IndexCreate("owner").Exec(ss) + r.Db(d).Table("addresses").IndexCreate("date_created").Exec(ss) + r.Db(d).Table("addresses").IndexCreate("date_modified").Exec(ss) + r.Db(d).TableCreate("contacts").Exec(ss) r.Db(d).Table("contacts").IndexCreate("owner").Exec(ss) r.Db(d).Table("contacts").IndexCreate("name").Exec(ss) @@ -60,7 +65,7 @@ func Setup(opts r.ConnectOpts) error { row.Field("thread"), row.Field("status"), } - }) + }).Exec(ss) r.Db(d).TableCreate("files").Exec(ss) r.Db(d).Table("files").IndexCreate("owner").Exec(ss) @@ -101,7 +106,7 @@ func Setup(opts r.ConnectOpts) error { row.Field("subject_hash"), row.Field("owner"), } - }) + }).Exec(ss) r.Db(d).TableCreate("tokens").Exec(ss) r.Db(d).Table("tokens").IndexCreate("name").Exec(ss) diff --git a/db/table_addresses.go b/db/table_addresses.go new file mode 100644 index 0000000..ac0924b --- /dev/null +++ b/db/table_addresses.go @@ -0,0 +1,33 @@ +package db + +import ( + "github.com/lavab/api/models" +) + +type AddressesTable struct { + RethinkCRUD +} + +func (a *AddressesTable) GetAddress(id string) (*models.Address, error) { + var result models.Address + if err := a.FindFetchOne(id, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *AddressesTable) GetOwnedBy(id string) ([]*models.Address, error) { + cursor, err := a.GetTable().GetAllByIndex("owner", id).Run(a.GetSession()) + if err != nil { + return nil, err + } + var result []*models.Address + if err := cursor.All(&result); err != nil { + return nil, err + } + return result, nil +} + +func (a *AddressesTable) DeleteOwnedBy(id string) error { + return a.GetTable().GetAllByIndex("owner", id).Delete().Exec(a.GetSession()) +} diff --git a/env/env.go b/env/env.go index 00ab65c..20f4fae 100644 --- a/env/env.go +++ b/env/env.go @@ -21,6 +21,8 @@ var ( Cache cache.Cache // Accounts is the global instance of AccountsTable Accounts *db.AccountsTable + // Addresses is the global instance of Addresses table + Addresses *db.AddressesTable // Tokens is the global instance of TokensTable Tokens *db.TokensTable // Keys is the global instance of KeysTable diff --git a/models/address.go b/models/address.go new file mode 100644 index 0000000..d6693de --- /dev/null +++ b/models/address.go @@ -0,0 +1,7 @@ +package models + +type Address struct { + // ID is an unique string @lavaboom.com + // Owner is the user whose address it is + Resource +} diff --git a/routes/accounts.go b/routes/accounts.go index d8bdd1f..55bbd5c 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -378,6 +378,27 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Add a new mapping + err = env.Addresses.Insert(&models.Address{ + Resource: models.Resource{ + ID: account.Name, + DateCreated: time.Now(), + DateModified: time.Now(), + Owner: account.ID, + }, + }) + if err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Unable to create a new address mapping", + }) + + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Could not insert an address mapping into db") + return + } + // Update the account err = env.Accounts.UpdateID(account.ID, account) if err != nil { diff --git a/routes/addresses.go b/routes/addresses.go new file mode 100644 index 0000000..ca91043 --- /dev/null +++ b/routes/addresses.go @@ -0,0 +1,39 @@ +package routes + +import ( + "net/http" + + "github.com/Sirupsen/logrus" + "github.com/zenazn/goji/web" + + "github.com/lavab/api/env" + "github.com/lavab/api/models" + "github.com/lavab/api/utils" +) + +type AddressesListResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Addresses []*models.Address `json:"addresses,omitempty"` +} + +func AddressesList(c web.C, w http.ResponseWriter, r *http.Request) { + session := c.Env["token"].(*models.Token) + addresses, err := env.Addresses.GetOwnedBy(session.Owner) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Unable to fetch addresses") + + utils.JSONResponse(w, 500, &AddressesListResponse{ + Success: false, + Message: "Internal error (code AD/LI/01)", + }) + return + } + + utils.JSONResponse(w, 200, &AddressesListResponse{ + Success: true, + Addresses: addresses, + }) +} diff --git a/setup/setup.go b/setup/setup.go index 10301da..733762c 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -163,6 +163,13 @@ func PrepareMux(flags *env.Flags) *web.Mux { ), Tokens: env.Tokens, } + env.Addresses = &db.AddressesTable{ + RethinkCRUD: db.NewCRUDTable( + rethinkSession, + rethinkOpts.Database, + "addresses", + ), + } env.Keys = &db.KeysTable{ RethinkCRUD: db.NewCRUDTable( rethinkSession, @@ -472,6 +479,9 @@ func PrepareMux(flags *env.Flags) *web.Mux { auth.Delete("/accounts/:id", routes.AccountsDelete) auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeData) + // Addresses + auth.Get("/addresses", routes.AddressesList) + // Avatars mux.Get(regexp.MustCompile(`/avatars/(?P[\S\s]*?)\.(?Psvg|png)(?:[\S\s]*?)$`), routes.Avatars) //mux.Get("/avatars/:hash.:ext", routes.Avatars) From 391b054190d9fb2e3cef222c48acf1d9caae0ded Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 4 Apr 2015 21:06:16 +0200 Subject: [PATCH 24/67] Address mapping support for keys endpoint --- routes/keys.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/routes/keys.go b/routes/keys.go index 3c551c3..cfb6048 100644 --- a/routes/keys.go +++ b/routes/keys.go @@ -37,21 +37,21 @@ func KeysList(w http.ResponseWriter, r *http.Request) { user = utils.RemoveDots(utils.NormalizeUsername(user)) - account, err := env.Accounts.FindAccountByName(user) + address, err := env.Addresses.GetAddress(user) if err != nil { utils.JSONResponse(w, 409, &KeysListResponse{ Success: false, - Message: "Invalid username", + Message: "Invalid address", }) return } // Find all keys owner by user - keys, err := env.Keys.FindByOwner(account.ID) + keys, err := env.Keys.FindByOwner(address.Owner) if err != nil { utils.JSONResponse(w, 500, &KeysListResponse{ Success: false, - Message: "Internal server error (KE/LI/01)", + Message: "Internal server error (KE//LI/01)", }) return } @@ -218,8 +218,23 @@ func KeysGet(c web.C, w http.ResponseWriter, r *http.Request) { username = utils.RemoveDots(utils.NormalizeUsername(username)) - // Resolve account - account, err := env.Accounts.FindAccountByName(username) + // Resolve address + address, err := env.Addresses.GetAddress(username) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + "name": username, + }).Warn("Unable to fetch the requested address from the database") + + utils.JSONResponse(w, 404, &KeysGetResponse{ + Success: false, + Message: "No such address", + }) + return + } + + // Get its owner + account, err := env.Accounts.GetAccount(address.Owner) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), @@ -228,7 +243,7 @@ func KeysGet(c web.C, w http.ResponseWriter, r *http.Request) { utils.JSONResponse(w, 404, &KeysGetResponse{ Success: false, - Message: "No such user", + Message: "No such account", }) return } From 93d3734b068977b029d003839d7d06802cd03818 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 15 Apr 2015 22:18:11 +0200 Subject: [PATCH 25/67] Address table used for usernames being taken. Fixes #126 --- routes/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/accounts.go b/routes/accounts.go index 55bbd5c..5ab561c 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -88,7 +88,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { input.Username = utils.NormalizeUsername(input.Username) // Ensure that the username is not used - if used, err := env.Accounts.IsUsernameUsed(input.Username); err != nil || used { + if used, err := env.Addresses.GetAddress(input.Username); err != nil || used != nil { if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), From bd0c46aa9c6ccfd5c0a717d6bb2c9df9fe768005 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 15 Apr 2015 22:20:49 +0200 Subject: [PATCH 26/67] Username length check. Fixes #125 --- routes/accounts.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index 5ab561c..3e90275 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -87,15 +87,24 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Normalize the username input.Username = utils.NormalizeUsername(input.Username) + // Validate the username + if len(input.Username) < 3 || len(input.Username) > 32 { + utils.JSONResponse(w, 400, &AccountsCreateResponse{ + Success: false, + Message: "Invalid username - it has to be at least 3 and at max 32 characters long", + }) + return + } + // Ensure that the username is not used - if used, err := env.Addresses.GetAddress(input.Username); err != nil || used != nil { + if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err != nil || used != nil { if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to lookup registered accounts for usernames") } - utils.JSONResponse(w, 400, &AccountsCreateResponse{ + utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already used", }) @@ -110,7 +119,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { }).Error("Unable to lookup registered accounts for emails") } - utils.JSONResponse(w, 400, &AccountsCreateResponse{ + utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Email already used", }) From 5994e2d6217feb79ed74be95f8b4224f8c496c1d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 16 Apr 2015 09:11:52 +0200 Subject: [PATCH 27/67] Backport of Dockerfile fix --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8d0e486..9adf1e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,9 @@ MAINTAINER "Andrei Simionescu " RUN mkdir -p /gopath/src/github.com/lavab/api ADD . /gopath/src/github.com/lavab/api -RUN go get github.com/lavab/api +#RUN go get github.com/lavab/api +RUN cd /gopath/src/github.com/lavab/api && godep go install + CMD [] ENTRYPOINT ["/gopath/bin/api"] From f8b6825bdae0d98d42bde3f6133435d177f4f5ca Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 16 Apr 2015 09:13:23 +0200 Subject: [PATCH 28/67] Dockerfile fix --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 9adf1e6..9fd60c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,8 @@ FROM google/golang MAINTAINER "Andrei Simionescu " +RUN go get github.com/tools/godep + RUN mkdir -p /gopath/src/github.com/lavab/api ADD . /gopath/src/github.com/lavab/api #RUN go get github.com/lavab/api From 8d0fe118844c19ccbec7d5bed598ac2653f27825 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 18 Apr 2015 10:18:20 +0200 Subject: [PATCH 29/67] Registration patch --- routes/accounts.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index 3e90275..9cb5458 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -98,12 +98,6 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Ensure that the username is not used if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err != nil || used != nil { - if err != nil { - env.Log.WithFields(logrus.Fields{ - "error": err.Error(), - }).Error("Unable to lookup registered accounts for usernames") - } - utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already used", From 2665d462a6d44d4f781f2ba878e1aec5b1f7bcb1 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 26 Apr 2015 18:13:00 +0200 Subject: [PATCH 30/67] Basic API client --- client/client.go | 108 +++++++++++++++++++++++++++++++++++++++++++++ client/messages.go | 42 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 client/client.go create mode 100644 client/messages.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..3b1de2d --- /dev/null +++ b/client/client.go @@ -0,0 +1,108 @@ +package client + +import ( + "encoding/json" + "log" + + "github.com/dchest/uniuri" + "github.com/lavab/api/models" + "github.com/lavab/api/routes" + "github.com/lavab/sockjs-go-client" +) + +type Client struct { + Address string + SockJS *sockjs.Client + Headers map[string]string +} + +func New(address string) (*Client, error) { + sjs, err := sockjs.NewClient(address + "/ws") + if err != nil { + return nil, err + } + + return &Client{ + Address: address, + SockJS: sjs, + }, nil +} + +func (c *Client) Request(method, path string, headers map[string]string, body interface{}) ([]string, error) { + if c.Headers != nil { + for k, v := range c.Headers { + headers[k] = v + } + } + + req := &Request{ + ID: uniuri.New(), + Type: "request", + Path: path, + Method: method, + Headers: headers, + } + + if body != nil { + data, err := json.Marshal(body) + if err != nil { + return nil, err + } + + req.Body = string(data) + } + + return Encode(req) +} + +func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, error) { + data, err := c.Request("POST", "/tokens", map[string]string{ + "Content-Type": "application/json;charset=utf-8", + }, req) + if err != nil { + return nil, err + } + + if err := c.SockJS.WriteMessage(data); err != nil { + return nil, err + } + + x := []string{} + if err := c.SockJS.ReadMessage(&x); err != nil { + return nil, err + } + + var resp *routes.TokensCreateResponse + if err := Decode(x, &resp); err != nil { + return nil, err + } + + return resp.Token, nil +} + +func (c *Client) CreateEmail(req *routes.EmailsCreateRequest) ([]string, error) { + data, err := c.Request("POST", "/emails", map[string]string{ + "Content-Type": "application/json;charset=utf-8", + }, req) + if err != nil { + return nil, err + } + + if err := c.SockJS.WriteMessage(data); err != nil { + return nil, err + } + + x := []string{} + if err := c.SockJS.ReadMessage(&x); err != nil { + return nil, err + } + + var resp *routes.EmailsCreateResponse + if err := Decode(x, &resp); err != nil { + return nil, err + } + + log.Printf("%+v", resp) + + return resp.Created, nil +} diff --git a/client/messages.go b/client/messages.go new file mode 100644 index 0000000..3441d0d --- /dev/null +++ b/client/messages.go @@ -0,0 +1,42 @@ +package client + +import ( + "encoding/json" +) + +type Request struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` + Method string `json:"method,omitempty"` + Path string `json:"path,omitempty"` + Body string `json:"body,omitempty"` + Headers map[string]string `json:"headers,omitempty"` +} + +type Response struct { + Type string `json:"type,omitempty"` + ID string `json:"id,omitempty"` + Body string `json:"body,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` +} + +func Encode(r interface{}) ([]string, error) { + data, err := json.Marshal(r) + if err != nil { + return nil, err + } + + return []string{string(data)}, nil +} + +func Decode(x []string, r interface{}) error { + var v *Response + if err := json.Unmarshal([]byte(x[0]), &v); err != nil { + return err + } + if err := json.Unmarshal([]byte(v.Body), &r); err != nil { + return err + } + + return nil +} From fafc1a98533c9cf416e02baa8b2f4ddc4a29c459 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 29 Apr 2015 20:37:17 +0200 Subject: [PATCH 31/67] Different execution model of the client --- client/client.go | 107 ++++++++++++++++++++++++++++++++++++--------- client/messages.go | 5 +++ 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/client/client.go b/client/client.go index 3b1de2d..bade297 100644 --- a/client/client.go +++ b/client/client.go @@ -2,7 +2,10 @@ package client import ( "encoding/json" + "errors" "log" + "sync" + "time" "github.com/dchest/uniuri" "github.com/lavab/api/models" @@ -11,24 +14,81 @@ import ( ) type Client struct { - Address string - SockJS *sockjs.Client - Headers map[string]string + sync.RWMutex + + Address string + SockJS *sockjs.Client + Headers map[string]string + Incoming map[string]chan string + Timeout time.Duration } -func New(address string) (*Client, error) { +func New(address string, timeout time.Duration) (*Client, error) { sjs, err := sockjs.NewClient(address + "/ws") if err != nil { return nil, err } - return &Client{ - Address: address, - SockJS: sjs, - }, nil + if timeout == 0 { + timeout = time.Second * 30 + } + + client := &Client{ + Address: address, + SockJS: sjs, + Headers: map[string]string{}, + Incoming: map[string]chan string{}, + Timeout: timeout, + } + + go client.Loop() + + return client, nil +} + +func (c *Client) Loop() { + for { + x := []string{} + if err := c.SockJS.ReadMessage(&x); err != nil { + log.Print(err) + break + } + + var resp *Message + if err := Decode(x, &resp); err != nil { + log.Print(err) + continue + } + + if resp.Type == "response" { + c.RLock() + d, ok := c.Incoming[resp.ID] + c.RUnlock() + if ok { + d <- x[0] + } + } else { + // Run event handlers + } + } +} + +func (c *Client) Receive(id string) (string, error) { + c.Lock() + c.Incoming[id] = make(chan string) + c.Unlock() + + select { + case <-time.After(c.Timeout): + return "", errors.New("Request timeout") + case data := <-c.Incoming[id]: + return data, nil + } + + return "", errors.New("This shouldn't happen!") } -func (c *Client) Request(method, path string, headers map[string]string, body interface{}) ([]string, error) { +func (c *Client) Request(method, path string, headers map[string]string, body interface{}) ([]string, string, error) { if c.Headers != nil { for k, v := range c.Headers { headers[k] = v @@ -46,34 +106,39 @@ func (c *Client) Request(method, path string, headers map[string]string, body in if body != nil { data, err := json.Marshal(body) if err != nil { - return nil, err + return nil, "", err } req.Body = string(data) } - return Encode(req) + d, err := Encode(req) + if err != nil { + return nil, "", err + } + + return d, req.ID, nil } func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, error) { - data, err := c.Request("POST", "/tokens", map[string]string{ + data, id, err := c.Request("POST", "/tokens", map[string]string{ "Content-Type": "application/json;charset=utf-8", }, req) if err != nil { return nil, err } - if err := c.SockJS.WriteMessage(data); err != nil { + rcv, err := c.Receive(id) + if err != nil { return nil, err } - x := []string{} - if err := c.SockJS.ReadMessage(&x); err != nil { + if err := c.SockJS.WriteMessage(data); err != nil { return nil, err } var resp *routes.TokensCreateResponse - if err := Decode(x, &resp); err != nil { + if err := json.Unmarshal([]byte(rcv), &resp); err != nil { return nil, err } @@ -81,24 +146,24 @@ func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, er } func (c *Client) CreateEmail(req *routes.EmailsCreateRequest) ([]string, error) { - data, err := c.Request("POST", "/emails", map[string]string{ + data, id, err := c.Request("POST", "/emails", map[string]string{ "Content-Type": "application/json;charset=utf-8", }, req) if err != nil { return nil, err } - if err := c.SockJS.WriteMessage(data); err != nil { + rcv, err := c.Receive(id) + if err != nil { return nil, err } - x := []string{} - if err := c.SockJS.ReadMessage(&x); err != nil { + if err := c.SockJS.WriteMessage(data); err != nil { return nil, err } var resp *routes.EmailsCreateResponse - if err := Decode(x, &resp); err != nil { + if err := json.Unmarshal([]byte(rcv), &resp); err != nil { return nil, err } diff --git a/client/messages.go b/client/messages.go index 3441d0d..463b8ae 100644 --- a/client/messages.go +++ b/client/messages.go @@ -4,6 +4,11 @@ import ( "encoding/json" ) +type Message struct { + Type string `json:"type"` + ID string `json:"id"` +} + type Request struct { Type string `json:"type,omitempty"` ID string `json:"id,omitempty"` From cff1042ce315abced04b54d628643c9e1882ba74 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 29 Apr 2015 20:51:59 +0200 Subject: [PATCH 32/67] Moar flow fixes --- client/client.go | 38 +++++++++++++++++++------------------- client/messages.go | 5 ----- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/client/client.go b/client/client.go index bade297..f00f30b 100644 --- a/client/client.go +++ b/client/client.go @@ -19,7 +19,7 @@ type Client struct { Address string SockJS *sockjs.Client Headers map[string]string - Incoming map[string]chan string + Incoming map[string]chan *Response Timeout time.Duration } @@ -37,7 +37,7 @@ func New(address string, timeout time.Duration) (*Client, error) { Address: address, SockJS: sjs, Headers: map[string]string{}, - Incoming: map[string]chan string{}, + Incoming: map[string]chan *Response{}, Timeout: timeout, } @@ -48,14 +48,15 @@ func New(address string, timeout time.Duration) (*Client, error) { func (c *Client) Loop() { for { + x := []string{} if err := c.SockJS.ReadMessage(&x); err != nil { log.Print(err) break } - var resp *Message - if err := Decode(x, &resp); err != nil { + var resp *Response + if err := json.Unmarshal([]byte(x[0]), &resp); err != nil { log.Print(err) continue } @@ -64,8 +65,9 @@ func (c *Client) Loop() { c.RLock() d, ok := c.Incoming[resp.ID] c.RUnlock() + if ok { - d <- x[0] + d <- resp } } else { // Run event handlers @@ -73,19 +75,19 @@ func (c *Client) Loop() { } } -func (c *Client) Receive(id string) (string, error) { +func (c *Client) Receive(id string) (*Response, error) { c.Lock() - c.Incoming[id] = make(chan string) + c.Incoming[id] = make(chan *Response) c.Unlock() select { case <-time.After(c.Timeout): - return "", errors.New("Request timeout") + return nil, errors.New("Request timeout") case data := <-c.Incoming[id]: return data, nil } - return "", errors.New("This shouldn't happen!") + return nil, errors.New("This shouldn't happen!") } func (c *Client) Request(method, path string, headers map[string]string, body interface{}) ([]string, string, error) { @@ -128,17 +130,17 @@ func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, er return nil, err } - rcv, err := c.Receive(id) - if err != nil { + if err := c.SockJS.WriteMessage(data); err != nil { return nil, err } - if err := c.SockJS.WriteMessage(data); err != nil { + rcv, err := c.Receive(id) + if err != nil { return nil, err } var resp *routes.TokensCreateResponse - if err := json.Unmarshal([]byte(rcv), &resp); err != nil { + if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { return nil, err } @@ -153,21 +155,19 @@ func (c *Client) CreateEmail(req *routes.EmailsCreateRequest) ([]string, error) return nil, err } - rcv, err := c.Receive(id) - if err != nil { + if err := c.SockJS.WriteMessage(data); err != nil { return nil, err } - if err := c.SockJS.WriteMessage(data); err != nil { + rcv, err := c.Receive(id) + if err != nil { return nil, err } var resp *routes.EmailsCreateResponse - if err := json.Unmarshal([]byte(rcv), &resp); err != nil { + if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { return nil, err } - log.Printf("%+v", resp) - return resp.Created, nil } diff --git a/client/messages.go b/client/messages.go index 463b8ae..3441d0d 100644 --- a/client/messages.go +++ b/client/messages.go @@ -4,11 +4,6 @@ import ( "encoding/json" ) -type Message struct { - Type string `json:"type"` - ID string `json:"id"` -} - type Request struct { Type string `json:"type,omitempty"` ID string `json:"id,omitempty"` From b5982945aa21b12ae84f8dd689e77720bab40e96 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 1 May 2015 19:50:12 +0200 Subject: [PATCH 33/67] Accounts registration patch --- routes/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/accounts.go b/routes/accounts.go index 9cb5458..28c9e61 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -97,7 +97,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { } // Ensure that the username is not used - if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err != nil || used != nil { + if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err == nil || used != nil { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, Message: "Username already used", From 2abdd94f683d019306b9dfe381e2719735dcd985 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 1 May 2015 20:06:58 +0200 Subject: [PATCH 34/67] Onboarding emails support --- routes/accounts.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/routes/accounts.go b/routes/accounts.go index 28c9e61..1ee162f 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -1,6 +1,7 @@ package routes import ( + "encoding/json" "net/http" "time" @@ -417,6 +418,29 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Add to onboarding emails + data, err := json.Marshal(map[string]interface{}{ + "type": "onboarding", + "email": account.Name + "@lavaboom.com", + // polish roulette + "first_name": account.Settings.(map[string]interface{})["firstName"].(string), + }) + if err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Unable to initialize onboarding emails", + }) + return + } + + if err := env.Producer.Publish("hub", data); err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Unable to initialize onboarding emails #2", + }) + return + } + // Remove the token and return a response err = env.Tokens.DeleteID(input.InviteCode) if err != nil { From 266ffb7936b7c931ce84c9665d4801548bcf5afe Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 1 May 2015 20:12:48 +0200 Subject: [PATCH 35/67] I shouldn't have tried that --- routes/accounts.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/routes/accounts.go b/routes/accounts.go index 1ee162f..28c9e61 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -1,7 +1,6 @@ package routes import ( - "encoding/json" "net/http" "time" @@ -418,29 +417,6 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } - // Add to onboarding emails - data, err := json.Marshal(map[string]interface{}{ - "type": "onboarding", - "email": account.Name + "@lavaboom.com", - // polish roulette - "first_name": account.Settings.(map[string]interface{})["firstName"].(string), - }) - if err != nil { - utils.JSONResponse(w, 500, &AccountsCreateResponse{ - Success: false, - Message: "Unable to initialize onboarding emails", - }) - return - } - - if err := env.Producer.Publish("hub", data); err != nil { - utils.JSONResponse(w, 500, &AccountsCreateResponse{ - Success: false, - Message: "Unable to initialize onboarding emails #2", - }) - return - } - // Remove the token and return a response err = env.Tokens.DeleteID(input.InviteCode) if err != nil { From 84af03bdf12c624d1c30578f7f2f42b39a186389 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 1 May 2015 23:15:49 +0200 Subject: [PATCH 36/67] onboarding emails support --- routes/accounts.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++ setup/setup.go | 1 + 2 files changed, 94 insertions(+) diff --git a/routes/accounts.go b/routes/accounts.go index 28c9e61..75146e1 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -1,6 +1,7 @@ package routes import ( + "encoding/json" "net/http" "time" @@ -836,3 +837,95 @@ func AccountsWipeData(c web.C, w http.ResponseWriter, r *http.Request) { Message: "Your account has been successfully wiped", }) } + +type AccountsStartOnboardingResponse struct { + Success bool `json:"success"` + Message string `json:"message"` +} + +func AccountsStartOnboarding(c web.C, w http.ResponseWriter, r *http.Request) { + // Get the account ID from the request + id := c.URLParams["id"] + + // Right now we only support "me" as the ID + if id != "me" { + utils.JSONResponse(w, 501, &AccountsStartOnboardingResponse{ + Success: false, + Message: `Only the "me" user is implemented`, + }) + return + } + + // Fetch the current session from the database + session := c.Env["token"].(*models.Token) + + // Fetch the user object from the database + account, err := env.Accounts.GetTokenOwner(session) + if err != nil { + // The session refers to a non-existing user + env.Log.WithFields(logrus.Fields{ + "id": session.ID, + "error": err.Error(), + }).Warn("Valid session referred to a removed account") + + utils.JSONResponse(w, 410, &AccountsStartOnboardingResponse{ + Success: false, + Message: "Account disabled", + }) + return + } + + x1, ok := account.Settings.(map[string]interface{}) + if !ok { + utils.JSONResponse(w, 403, &AccountsStartOnboardingResponse{ + Success: false, + Message: "Account misconfigured #1", + }) + return + } + + x2, ok := x1["firstName"] + if !ok { + utils.JSONResponse(w, 403, &AccountsStartOnboardingResponse{ + Success: false, + Message: "Account misconfigured #2", + }) + return + } + + x3, ok := x2.(string) + if !ok { + utils.JSONResponse(w, 403, &AccountsStartOnboardingResponse{ + Success: false, + Message: "Account misconfigured #3", + }) + return + } + + data, err := json.Marshal(map[string]interface{}{ + "type": "onboarding", + "email": account.Name + "@lavaboom.com", + // polish roulette + "first_name": x3, + }) + if !ok { + utils.JSONResponse(w, 500, &AccountsStartOnboardingResponse{ + Success: false, + Message: "Unable to encode a message", + }) + return + } + + if err := env.Producer.Publish("hub", data); err != nil { + utils.JSONResponse(w, 500, &AccountsCreateResponse{ + Success: false, + Message: "Unable to initialize onboarding emails", + }) + return + } + + utils.JSONResponse(w, 200, &AccountsStartOnboardingResponse{ + Success: true, + Message: "Onboarding emails for your account have been initialized", + }) +} diff --git a/setup/setup.go b/setup/setup.go index 733762c..9ecf2ac 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -478,6 +478,7 @@ func PrepareMux(flags *env.Flags) *web.Mux { auth.Put("/accounts/:id", routes.AccountsUpdate) auth.Delete("/accounts/:id", routes.AccountsDelete) auth.Post("/accounts/:id/wipe-data", routes.AccountsWipeData) + auth.Post("/accounts/:id/start-onboarding", routes.AccountsStartOnboarding) // Addresses auth.Get("/addresses", routes.AddressesList) From 9e2c7b5e548becff7fee4c1b343af058063a9701 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 2 May 2015 13:21:43 +0200 Subject: [PATCH 37/67] .GetKey to the client --- client/client.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/client/client.go b/client/client.go index f00f30b..d4a2485 100644 --- a/client/client.go +++ b/client/client.go @@ -143,6 +143,9 @@ func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, er if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { return nil, err } + if !resp.Success { + return nil, errors.New(resp.Message) + } return resp.Token, nil } @@ -168,6 +171,35 @@ func (c *Client) CreateEmail(req *routes.EmailsCreateRequest) ([]string, error) if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { return nil, err } + if !resp.Success { + return nil, errors.New(resp.Message) + } return resp.Created, nil } + +func (c *Client) GetKey(id string) (*models.Key, error) { + data, id, err := c.Request("GET", "/keys/"+id, nil, nil) + if err != nil { + return nil, err + } + + if err := c.SockJS.WriteMessage(data); err != nil { + return nil, err + } + + rcv, err := c.Receive(id) + if err != nil { + return nil, err + } + + var resp *routes.KeysGetResponse + if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { + return nil, err + } + if !resp.Success { + return nil, errors.New(resp.Message) + } + + return resp.Key, nil +} From 26b692c2edee7d266854d87c24b18a9a347ddbc8 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 2 May 2015 13:55:40 +0200 Subject: [PATCH 38/67] Crash prevention if request query generator gets a nil instead of a map --- client/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/client.go b/client/client.go index d4a2485..58e0444 100644 --- a/client/client.go +++ b/client/client.go @@ -91,6 +91,10 @@ func (c *Client) Receive(id string) (*Response, error) { } func (c *Client) Request(method, path string, headers map[string]string, body interface{}) ([]string, string, error) { + if headers == nil { + headers = map[string]string{} + } + if c.Headers != nil { for k, v := range c.Headers { headers[k] = v From b423c8cf6a2e5129de6c068a4019d6b1d3eb981d Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 2 May 2015 19:02:52 +0200 Subject: [PATCH 39/67] Basic identity support --- routes/emails.go | 69 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/routes/emails.go b/routes/emails.go index 0e74e59..3ac67a5 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -6,6 +6,7 @@ import ( //"crypto/sha256" //"encoding/hex" "net/http" + "net/mail" "regexp" "strconv" "strings" @@ -132,9 +133,10 @@ type EmailsCreateRequest struct { Thread string `json:"thread"` // Metadata that has to be leaked - To []string `json:"to"` - CC []string `json:"cc"` - BCC []string `json:"bcc"` + From string `json:"from"` + To []string `json:"to"` + CC []string `json:"cc"` + BCC []string `json:"bcc"` // Encrypted parts PGPFingerprints []string `json:"pgp_fingerprints"` @@ -260,6 +262,65 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { return } + if input.From != "" { + // Parse the from field + from, err := mail.ParseAddress(input.From) + if err != nil { + utils.JSONResponse(w, 400, &EmailsCreateResponse{ + Success: false, + Message: "Invalid email.From", + }) + return + } + + // We have a specified address + if from.Address != "" { + parts := strings.SplitN(from.Address, "@", 2) + + if parts[1] != env.Config.EmailDomain { + utils.JSONResponse(w, 400, &EmailsCreateResponse{ + Success: false, + Message: "Invalid email.From (invalid domain)", + }) + return + } + + address, err := env.Addresses.GetAddress(parts[0]) + if err != nil { + utils.JSONResponse(w, 400, &EmailsCreateResponse{ + Success: false, + Message: "Invalid email.From (invalid username)", + }) + return + } + + if address.Owner != account.ID { + utils.JSONResponse(w, 400, &EmailsCreateResponse{ + Success: false, + Message: "Invalid email.From (address not owned)", + }) + return + } + } + } else { + displayName := "" + + if x, ok := account.Settings.(map[string]interface{}); ok { + if y, ok := x["displayName"]; ok { + if z, ok := y.(string); ok { + displayName = z + } + } + } + + addr := &mail.Address{ + Name: displayName, + Address: account.StyledName + "@" + env.Config.EmailDomain, + } + + input.From = addr.String() + } + // Check if Thread is set if input.Thread != "" { // todo: make it an actual exists check to reduce lan bandwidth @@ -336,7 +397,7 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { Kind: input.Kind, Thread: input.Thread, - From: account.StyledName + "@" + env.Config.EmailDomain, + From: input.From, To: input.To, CC: input.CC, BCC: input.BCC, From db8887a5a7eb3aaaa465535c2457f051a969c4da Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 3 May 2015 19:55:16 +0200 Subject: [PATCH 40/67] More username checks in registration --- routes/accounts.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/routes/accounts.go b/routes/accounts.go index 75146e1..482ad8e 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -97,7 +97,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } - // Ensure that the username is not used + // Ensure that the username is not used in address table if used, err := env.Addresses.GetAddress(utils.RemoveDots(input.Username)); err == nil || used != nil { utils.JSONResponse(w, 409, &AccountsCreateResponse{ Success: false, @@ -106,6 +106,15 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { return } + // Then check it in the accounts table + if ok, err := env.Accounts.IsUsernameUsed(utils.RemoveDots(input.Username)); ok || err != nil { + utils.JSONResponse(w, 409, &AccountsCreateResponse{ + Success: false, + Message: "Username already used", + }) + return + } + // Also check that the email is unique if used, err := env.Accounts.IsEmailUsed(input.AltEmail); err != nil || used { if err != nil { From 030ba31c4b33d40135b2cc5b3d2a5e88a71e3b23 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 7 May 2015 20:27:42 +0200 Subject: [PATCH 41/67] Cursor closing in RethinkDB code --- .../users/account_tokens_delete.go | 0 .../users/account_tokens_list.go | 0 .../microservices/users/accounts_create.go | 0 .../microservices/users/accounts_delete.go | 0 _research/microservices/users/accounts_get.go | 0 .../microservices/users/accounts_list.go | 1 + .../microservices/users/accounts_update.go | 0 _research/microservices/users/main.go | 53 +++++++++++++++++++ .../microservices/users/tokens_create.go | 35 ++++++++++++ .../microservices/users/tokens_delete.go | 23 ++++++++ _research/microservices/users/tokens_list.go | 0 .../microservices/users/tokens_update.go | 35 ++++++++++++ db/default_crud.go | 18 +++++-- db/table_addresses.go | 1 + db/table_emails.go | 1 + db/table_threads.go | 1 + setup/setup_test.go_ | 33 ------------ 17 files changed, 163 insertions(+), 38 deletions(-) create mode 100644 _research/microservices/users/account_tokens_delete.go create mode 100644 _research/microservices/users/account_tokens_list.go create mode 100644 _research/microservices/users/accounts_create.go create mode 100644 _research/microservices/users/accounts_delete.go create mode 100644 _research/microservices/users/accounts_get.go create mode 100644 _research/microservices/users/accounts_list.go create mode 100644 _research/microservices/users/accounts_update.go create mode 100644 _research/microservices/users/main.go create mode 100644 _research/microservices/users/tokens_create.go create mode 100644 _research/microservices/users/tokens_delete.go create mode 100644 _research/microservices/users/tokens_list.go create mode 100644 _research/microservices/users/tokens_update.go delete mode 100644 setup/setup_test.go_ diff --git a/_research/microservices/users/account_tokens_delete.go b/_research/microservices/users/account_tokens_delete.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/account_tokens_list.go b/_research/microservices/users/account_tokens_list.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/accounts_create.go b/_research/microservices/users/accounts_create.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/accounts_delete.go b/_research/microservices/users/accounts_delete.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/accounts_get.go b/_research/microservices/users/accounts_get.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/accounts_list.go b/_research/microservices/users/accounts_list.go new file mode 100644 index 0000000..f189a27 --- /dev/null +++ b/_research/microservices/users/accounts_list.go @@ -0,0 +1 @@ +package main diff --git a/_research/microservices/users/accounts_update.go b/_research/microservices/users/accounts_update.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/main.go b/_research/microservices/users/main.go new file mode 100644 index 0000000..79a4c95 --- /dev/null +++ b/_research/microservices/users/main.go @@ -0,0 +1,53 @@ +package main + +import ( + "net" + "net/http" + "os" + "strings" + + "github.com/Sirupsen/logrus" + r "github.com/dancannon/gorethink" + "github.com/lavab/goji" + "github.com/namsral/flag" +) + +var ( + configFlag = flag.String("config", "", "config file to load") + rethinkdbHosts = flag.String("rethinkdb_hosts", "127.0.0.1:28015", "Addresses of the RethinkDB servers") + rethinkdbDatabase = flag.String("rethinkdb_database", "prod", "Name of the RethinkDB database to use") +) + +var ( + session *r.Session +) + +func main() { + flag.Parse() + + var err error + session, err := r.Connect(r.ConnectOpts{ + Hosts: strings.Split(*rethinkdbName, ","), + Database: *rethinkdbDatabase, + }) + if err != nil { + log.Fatal(err) + } + + goji.Get("/accounts", listAccounts) + goji.Post("/accounts", createAccount) + goji.Get("/accounts/:id", getAccount) + goji.Put("/accounts/:id", updateAccount) + goji.Delete("/accounts/:id", deleteAccount) + + goji.Get("/accounts/:id/tokens", listAccountTokens) + goji.Delete("/accounts/:id/tokens", deleteAccountToken) + + goji.Get("/tokens", listTokens) + goji.Post("/tokens", createToken) + goji.Get("/tokens/:id", getToken) + goji.Put("/tokens/:id", updateToken) + goji.Delete("/tokens/:id", deleteToken) + + goji.Serve() +} diff --git a/_research/microservices/users/tokens_create.go b/_research/microservices/users/tokens_create.go new file mode 100644 index 0000000..1a3402d --- /dev/null +++ b/_research/microservices/users/tokens_create.go @@ -0,0 +1,35 @@ +package main + +import ( + "encoding/json" + + r "github.com/dancannon/gorethink" + "github.com/dchest/uniuri" + "github.com/lavab/api/models" + "github.com/lavab/api/utils" + "github.com/lavab/goji/web" +) + +func deleteToken(w http.ResponseWriter, req *http.Request) { + var user *models.Token + if err := json.NewDecoder(req).Decode(&user); err != nil { + utils.JSONResponse(w, 500, map[string]interface{}{ + "error": err.Error(), + }) + return + } + + user.ID = uniuri.NewLen(uniuri.UUIDLen) + + wr, err := r.Table("tokens").Insert(user).RunWrite(session) + if err != nil { + utils.JSONResponse(w, 500, map[string]interface{}{ + "error": err.Error(), + }) + return + } + + utils.JSONResponse(w, 200, map[string]interface{}{ + "created": wr.Created, + }) +} diff --git a/_research/microservices/users/tokens_delete.go b/_research/microservices/users/tokens_delete.go new file mode 100644 index 0000000..3a17b23 --- /dev/null +++ b/_research/microservices/users/tokens_delete.go @@ -0,0 +1,23 @@ +package main + +import ( + r "github.com/dancannon/gorethink" + "github.com/lavab/api/utils" + "github.com/lavab/goji/web" +) + +func deleteToken(c web.C, w http.ResponseWriter, req *http.Request) { + id := c.URLParams["id"] + + wr, err := r.Table("tokens").Get(id).Delete().RunWrite(session) + if err != nil { + utils.JSONResponse(w, 500, map[string]interface{}{ + "error": err.Error(), + }) + return + } + + utils.JSONResponse(w, 200, map[string]interface{}{ + "dropped": wr.Dropped, + }) +} diff --git a/_research/microservices/users/tokens_list.go b/_research/microservices/users/tokens_list.go new file mode 100644 index 0000000..e69de29 diff --git a/_research/microservices/users/tokens_update.go b/_research/microservices/users/tokens_update.go new file mode 100644 index 0000000..8568862 --- /dev/null +++ b/_research/microservices/users/tokens_update.go @@ -0,0 +1,35 @@ +package main + +import ( + "encoding/json" + + r "github.com/dancannon/gorethink" + "github.com/dchest/uniuri" + "github.com/lavab/api/models" + "github.com/lavab/api/utils" + "github.com/lavab/goji/web" +) + +func updateToken(c web.C, w http.ResponseWriter, req *http.Request) { + var token *models.Token + if err := json.NewDecoder(req).Decode(&token); err != nil { + utils.JSONResponse(w, 400, map[string]interface{}{ + "error": err.Error(), + }) + return + } + + token.ID = uniuri.NewLen(uniuri.UUIDLen) + + wr, err := r.Table("tokens").Insert(token).RunWrite(session) + if err != nil { + utils.JSONResponse(w, 500, map[string]interface{}{ + "error": err.Error(), + }) + return + } + + utils.JSONResponse(w, 200, map[string]interface{}{ + "created": wr.Created, + }) +} diff --git a/db/default_crud.go b/db/default_crud.go index 8cdfef5..255a1a6 100644 --- a/db/default_crud.go +++ b/db/default_crud.go @@ -42,7 +42,7 @@ func (d *Default) GetSession() *gorethink.Session { // Insert inserts a document into the database func (d *Default) Insert(data interface{}) error { - _, err := d.GetTable().Insert(data).RunWrite(d.session) + err := d.GetTable().Insert(data).Exec(d.session) if err != nil { return NewDatabaseError(d, err, "") } @@ -52,7 +52,7 @@ func (d *Default) Insert(data interface{}) error { // Update performs an update on an existing resource according to passed data func (d *Default) Update(data interface{}) error { - _, err := d.GetTable().Update(data).RunWrite(d.session) + err := d.GetTable().Update(data).Exec(d.session) if err != nil { return NewDatabaseError(d, err, "") } @@ -62,7 +62,7 @@ func (d *Default) Update(data interface{}) error { // UpdateID performs an update on an existing resource with ID that equals the id argument func (d *Default) UpdateID(id string, data interface{}) error { - _, err := d.GetTable().Get(id).Update(data).RunWrite(d.session) + err := d.GetTable().Get(id).Update(data).Exec(d.session) if err != nil { return NewDatabaseError(d, err, "") } @@ -72,7 +72,7 @@ func (d *Default) UpdateID(id string, data interface{}) error { // Delete deletes resources that match the passed filter func (d *Default) Delete(pred interface{}) error { - _, err := d.GetTable().Filter(pred).Delete().RunWrite(d.session) + err := d.GetTable().Filter(pred).Delete().Exec(d.session) if err != nil { return NewDatabaseError(d, err, "") } @@ -82,7 +82,7 @@ func (d *Default) Delete(pred interface{}) error { // DeleteID deletes a resource with specified ID func (d *Default) DeleteID(id string) error { - _, err := d.GetTable().Get(id).Delete().RunWrite(d.session) + err := d.GetTable().Get(id).Delete().Exec(d.session) if err != nil { return NewDatabaseError(d, err, "") } @@ -106,6 +106,7 @@ func (d *Default) FindFetchOne(id string, value interface{}) error { if err != nil { return err } + defer cursor.Close() if err := cursor.One(value); err != nil { return NewDatabaseError(d, err, "") @@ -135,6 +136,7 @@ func (d *Default) FindByAndCount(key string, value interface{}) (int, error) { if err != nil { return 0, err } + defer cursor.Close() var count int if err := cursor.One(&count); err != nil { @@ -150,6 +152,7 @@ func (d *Default) FindByAndFetch(key string, value interface{}, results interfac if err != nil { return err } + defer cursor.Close() if err := cursor.All(results); err != nil { return NewDatabaseError(d, err, "") @@ -164,6 +167,7 @@ func (d *Default) FindByAndFetchOne(key string, value interface{}, result interf if err != nil { return err } + defer cursor.Close() if err := cursor.One(result); err != nil { return NewDatabaseError(d, err, "") @@ -188,6 +192,7 @@ func (d *Default) WhereAndFetch(filter map[string]interface{}, results interface if err != nil { return err } + defer cursor.Close() if err := cursor.All(results); err != nil { return NewDatabaseError(d, err, "") @@ -202,6 +207,7 @@ func (d *Default) WhereAndFetchOne(filter map[string]interface{}, result interfa if err != nil { return err } + defer cursor.Close() if err := cursor.One(result); err != nil { return NewDatabaseError(d, err, "") @@ -226,6 +232,7 @@ func (d *Default) FindByIndexFetch(results interface{}, index string, values ... if err != nil { return err } + defer cursor.Close() //now fetch the item from database if err := cursor.All(results); err != nil { @@ -241,6 +248,7 @@ func (d *Default) FindByIndexFetchOne(result interface{}, index string, values . if err != nil { return err } + defer cursor.Close() if err := cursor.One(result); err != nil { return NewDatabaseError(d, err, "") diff --git a/db/table_addresses.go b/db/table_addresses.go index ac0924b..fdeedc4 100644 --- a/db/table_addresses.go +++ b/db/table_addresses.go @@ -21,6 +21,7 @@ func (a *AddressesTable) GetOwnedBy(id string) ([]*models.Address, error) { if err != nil { return nil, err } + defer cursor.Close() var result []*models.Address if err := cursor.All(&result); err != nil { return nil, err diff --git a/db/table_emails.go b/db/table_emails.go index 6d388d4..5eabe13 100644 --- a/db/table_emails.go +++ b/db/table_emails.go @@ -101,6 +101,7 @@ func (e *EmailsTable) List( if err != nil { return nil, err } + defer cursor.Close() // Fetch the cursor var resp []*models.Email diff --git a/db/table_threads.go b/db/table_threads.go index 46fc0fa..ace58c8 100644 --- a/db/table_threads.go +++ b/db/table_threads.go @@ -148,6 +148,7 @@ func (t *ThreadsTable) List( if err != nil { return nil, err } + defer cursor.Close() // Fetch the cursor var resp []*models.Thread diff --git a/setup/setup_test.go_ b/setup/setup_test.go_ deleted file mode 100644 index e44739d..0000000 --- a/setup/setup_test.go_ +++ /dev/null @@ -1,33 +0,0 @@ -package setup - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/lavab/api/env" -) - -func TestSetup(t *testing.T) { - // Mock data - env.Config = &env.Flags{ - APIVersion: "v0", - LogFormatterType: "text", - ForceColors: true, - - SessionDuration: 72, - - RedisAddress: "127.0.0.1:6379", - - NSQAddress: "127.0.0.1:4150", - LookupdAddress: "127.0.0.1:4160", - - RethinkDBAddress: "127.0.0.1:28015", - RethinkDBKey: "", - RethinkDBDatabase: "test", - } - - // Prepare a new mux (initialize the API) - mux := PrepareMux(env.Config) - require.NotNil(t, mux, "mux should not be nil") -} From 0a91042efabe471d412a19bc188573cff5b32a6b Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 10 May 2015 00:55:07 +0200 Subject: [PATCH 42/67] Updated the client --- client/client.go | 62 +++++++++++++++++++++++++++++++++++++++++++++- client/messages.go | 2 ++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/client/client.go b/client/client.go index 58e0444..95ce6d8 100644 --- a/client/client.go +++ b/client/client.go @@ -20,6 +20,7 @@ type Client struct { SockJS *sockjs.Client Headers map[string]string Incoming map[string]chan *Response + Handlers []func(ev *Event) Timeout time.Duration } @@ -37,6 +38,7 @@ func New(address string, timeout time.Duration) (*Client, error) { Address: address, SockJS: sjs, Headers: map[string]string{}, + Handlers: []func(ev *Event){}, Incoming: map[string]chan *Response{}, Timeout: timeout, } @@ -48,7 +50,6 @@ func New(address string, timeout time.Duration) (*Client, error) { func (c *Client) Loop() { for { - x := []string{} if err := c.SockJS.ReadMessage(&x); err != nil { log.Print(err) @@ -71,6 +72,13 @@ func (c *Client) Loop() { } } else { // Run event handlers + for _, handler := range c.Handlers { + handler(&Event{ + Type: resp.Type, + ID: resp.ID, + Name: resp.Name, + }) + } } } } @@ -126,6 +134,32 @@ func (c *Client) Request(method, path string, headers map[string]string, body in return d, req.ID, nil } +type Event struct { + Type string `json:"event"` + ID string `json:"id"` + Name string `json:"name"` +} + +func (c *Client) Subscribe(token string, callback func(ev *Event)) error { + c.Handlers = append(c.Handlers, callback) + + if token != "" { + data, err := Encode(&Request{ + Type: "subscribe", + Token: token, + }) + if err != nil { + return err + } + + if err := c.SockJS.WriteMessage(data); err != nil { + return err + } + } + + return nil +} + func (c *Client) CreateToken(req *routes.TokensCreateRequest) (*models.Token, error) { data, id, err := c.Request("POST", "/tokens", map[string]string{ "Content-Type": "application/json;charset=utf-8", @@ -207,3 +241,29 @@ func (c *Client) GetKey(id string) (*models.Key, error) { return resp.Key, nil } + +func (c *Client) GetEmail(id string) (*models.Email, error) { + data, id, err := c.Request("GET", "/email/"+id, nil, nil) + if err != nil { + return nil, err + } + + if err := c.SockJS.WriteMessage(data); err != nil { + return nil, err + } + + rcv, err := c.Receive(id) + if err != nil { + return nil, err + } + + var resp *routes.EmailsGetResponse + if err := json.Unmarshal([]byte(rcv.Body), &resp); err != nil { + return nil, err + } + if !resp.Success { + return nil, errors.New(resp.Message) + } + + return resp.Email, nil +} diff --git a/client/messages.go b/client/messages.go index 3441d0d..8652e46 100644 --- a/client/messages.go +++ b/client/messages.go @@ -10,6 +10,7 @@ type Request struct { Method string `json:"method,omitempty"` Path string `json:"path,omitempty"` Body string `json:"body,omitempty"` + Token string `json:"token,omitempty"` Headers map[string]string `json:"headers,omitempty"` } @@ -17,6 +18,7 @@ type Response struct { Type string `json:"type,omitempty"` ID string `json:"id,omitempty"` Body string `json:"body,omitempty"` + Name string `json:"name,omitempty"` Headers map[string][]string `json:"headers,omitempty"` } From c2034b2da0d4fc8e4c98686dd835407635f26594 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Mon, 11 May 2015 22:44:46 +0200 Subject: [PATCH 43/67] Fixed a typo in lavab/api/client --- client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/client.go b/client/client.go index 95ce6d8..4431ace 100644 --- a/client/client.go +++ b/client/client.go @@ -243,7 +243,7 @@ func (c *Client) GetKey(id string) (*models.Key, error) { } func (c *Client) GetEmail(id string) (*models.Email, error) { - data, id, err := c.Request("GET", "/email/"+id, nil, nil) + data, id, err := c.Request("GET", "/emails/"+id, nil, nil) if err != nil { return nil, err } From eab051440caeb948d3b5ed25b37b19b28ff61c5e Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 12 May 2015 22:15:07 +0200 Subject: [PATCH 44/67] Disabled glogrus, fixed client locking --- client/client.go | 26 ++++++++++++++++++-------- setup/setup.go | 4 ++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/client/client.go b/client/client.go index 4431ace..fcfe3c6 100644 --- a/client/client.go +++ b/client/client.go @@ -50,34 +50,44 @@ func New(address string, timeout time.Duration) (*Client, error) { func (c *Client) Loop() { for { + log.Print("STARTING THE LOOP") + x := []string{} if err := c.SockJS.ReadMessage(&x); err != nil { log.Print(err) break } + log.Print("GOT THE MESSAGE") + var resp *Response if err := json.Unmarshal([]byte(x[0]), &resp); err != nil { log.Print(err) continue } + log.Printf("%+v", resp) + if resp.Type == "response" { c.RLock() d, ok := c.Incoming[resp.ID] c.RUnlock() - if ok { - d <- resp - } + go func(resp *Response) { + if ok { + d <- resp + } + }(resp) } else { // Run event handlers for _, handler := range c.Handlers { - handler(&Event{ - Type: resp.Type, - ID: resp.ID, - Name: resp.Name, - }) + go func(resp *Response) { + handler(&Event{ + Type: resp.Type, + ID: resp.ID, + Name: resp.Name, + }) + }(resp) } } } diff --git a/setup/setup.go b/setup/setup.go index 9ecf2ac..cbe193d 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -15,7 +15,7 @@ import ( "github.com/bitly/go-nsq" "github.com/dancannon/gorethink" "github.com/johntdyer/slackrus" - "github.com/pzduniak/glogrus" + //"github.com/pzduniak/glogrus" "github.com/segmentio/go-loggly" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" @@ -460,7 +460,7 @@ func PrepareMux(flags *env.Flags) *web.Mux { }) }) mux.Use(middleware.RequestID) - mux.Use(glogrus.NewGlogrus(log, "api")) + //mux.Use(glogrus.NewGlogrus(log, "api")) mux.Use(middleware.Recoverer) mux.Use(middleware.AutomaticOptions) From b0bcf390f57401d17f0d21613802f7ad50ee019f Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 16:25:51 +0200 Subject: [PATCH 45/67] Removed date_modified updating upon thread.update --- routes/threads.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/threads.go b/routes/threads.go index de0bff9..dcd8e2f 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -5,7 +5,6 @@ import ( "reflect" "strconv" "strings" - "time" "github.com/Sirupsen/logrus" "github.com/zenazn/goji/web" @@ -240,7 +239,8 @@ func ThreadsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { thread.IsRead = *input.IsRead } - thread.DateModified = time.Now() + // Disabled for now, as we're using DateModified for sorting by the date of the last email + // thread.DateModified = time.Now() err = env.Threads.UpdateID(c.URLParams["id"], thread) if err != nil { From ce4f3bb59472939c5c9485cb889baf18612e82f1 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 17:21:05 +0200 Subject: [PATCH 46/67] New labels counting mechanism --- CHANGELOG.md | 0 models/label.go | 4 +- routes/labels.go | 123 ++++++++++++++++++++++++++++++----------------- 3 files changed, 80 insertions(+), 47 deletions(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/models/label.go b/models/label.go index 588044c..cf19fc6 100644 --- a/models/label.go +++ b/models/label.go @@ -16,6 +16,6 @@ type Label struct { // Examples: inbox, trash, spam, drafts, starred, etc. Builtin bool `json:"builtin" gorethink:"builtin"` - UnreadThreadsCount int `json:"unread_threads_count" gorethink:"-"` - TotalThreadsCount int `json:"total_threads_count" gorethink:"-"` + UnreadThreadsCount int `json:"unread_threads_count" gorethink:"unread_threads_count"` + TotalThreadsCount int `json:"total_threads_count" gorethink:"total_threads_count"` } diff --git a/routes/labels.go b/routes/labels.go index e31588d..55d3283 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/Sirupsen/logrus" + r "github.com/dancannon/gorethink" "github.com/lavab/api/env" "github.com/lavab/api/models" "github.com/lavab/api/utils" @@ -18,57 +19,89 @@ type LabelsListResponse struct { } // LabelsList fetches all labels -func LabelsList(c web.C, w http.ResponseWriter, r *http.Request) { +func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { session := c.Env["token"].(*models.Token) - labels, err := env.Labels.GetOwnedBy(session.Owner) + cursor, err := env.Labels.GetTable().GetAllByIndex("nameOwnerBuiltin", []interface{}{ + "Spam", + session.Owner, + true, + }, []interface{}{ + "Trash", + session.Owner, + true, + }).Run(env.Rethink) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), - }).Error("Unable to fetch labels") + }).Error("Unable to get account's specified labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, - Message: "Internal error (code LA/LI/01)", + Message: err.Error(), + }) + return + } + defer cursor.Close() + var spamTrash []*models.Label + if err := cursor.All(&spamTrash); err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Unable to unmarshal account's specified labels") + + utils.JSONResponse(w, 500, &LabelsListResponse{ + Success: false, + Message: err.Error(), + }) + return + } + + if len(spamTrash) != 2 { + env.Log.WithFields(logrus.Fields{ + "count": len(spamTrash), + "account": session.Owner, + }).Error("Invalid count of Trash and Spam labels") + + utils.JSONResponse(w, 500, &LabelsListResponse{ + Success: false, + Message: "Misconfigured account", }) return } - for _, label := range labels { - if label.Builtin && label.Name != "Inbox" { - continue - } - - totalCount, err := env.Threads.CountByLabel(label.ID) - if err != nil { - env.Log.WithFields(logrus.Fields{ - "error": err.Error(), - "label": label.ID, - }).Error("Unable to fetch total threads count") - - utils.JSONResponse(w, 500, &LabelsListResponse{ - Success: false, - Message: "Internal error (code LA/LI/02)", - }) - return - } - - unreadCount, err := env.Threads.CountByLabelUnread(label.ID) - if err != nil { - env.Log.WithFields(logrus.Fields{ - "error": err.Error(), - "label": label.ID, - }).Error("Unable to fetch unread threads count") - - utils.JSONResponse(w, 500, &LabelsListResponse{ - Success: false, - Message: "Internal error (code LA/LI/03)", - }) - return - } - - label.TotalThreadsCount = totalCount - label.UnreadThreadsCount = unreadCount + cursor, err = env.Labels.GetTable().GetAllByIndex("owner", session.Owner).Map(func(label r.Term) r.Term { + return label.Merge(map[string]interface{}{ + "total_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Count(), + "unread_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Filter(func(thread r.Term) r.Term { + return r.Not(thread.Field("is_read")).And( + r.Not(thread.Field("labels").Contains(spamTrash[0].ID).Or(thread.Field("labels").Contains(spamTrash[1].ID))), + ) + }), + }).Count() + }).Run(env.Rethink) + if err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Unable to get account's all labels") + + utils.JSONResponse(w, 500, &LabelsListResponse{ + Success: false, + Message: err.Error(), + }) + return + } + defer cursor.Close() + var labels []*models.Label + if err := cursor.All(&labels); err != nil { + env.Log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Unable to unmarshal account's labels") + + utils.JSONResponse(w, 500, &LabelsListResponse{ + Success: false, + Message: err.Error(), + }) + return } utils.JSONResponse(w, 200, &LabelsListResponse{ @@ -89,10 +122,10 @@ type LabelsCreateResponse struct { } // LabelsCreate does *something* - TODO -func LabelsCreate(c web.C, w http.ResponseWriter, r *http.Request) { +func LabelsCreate(c web.C, w http.ResponseWriter, req *http.Request) { // Decode the request var input LabelsCreateRequest - err := utils.ParseRequest(r, &input) + err := utils.ParseRequest(req, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), @@ -158,7 +191,7 @@ type LabelsGetResponse struct { } // LabelsGet does *something* - TODO -func LabelsGet(c web.C, w http.ResponseWriter, r *http.Request) { +func LabelsGet(c web.C, w http.ResponseWriter, req *http.Request) { // Get the label from the database label, err := env.Labels.GetLabel(c.URLParams["id"]) if err != nil { @@ -231,10 +264,10 @@ type LabelsUpdateResponse struct { } // LabelsUpdate does *something* - TODO -func LabelsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { +func LabelsUpdate(c web.C, w http.ResponseWriter, req *http.Request) { // Decode the request var input LabelsUpdateRequest - err := utils.ParseRequest(r, &input) + err := utils.ParseRequest(req, &input) if err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), @@ -302,7 +335,7 @@ type LabelsDeleteResponse struct { } // LabelsDelete does *something* - TODO -func LabelsDelete(c web.C, w http.ResponseWriter, r *http.Request) { +func LabelsDelete(c web.C, w http.ResponseWriter, req *http.Request) { // Get the label from the database label, err := env.Labels.GetLabel(c.URLParams["id"]) if err != nil { From a2e7614d90a83fcd6838696f54c09be2a5b5929a Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 17:38:52 +0200 Subject: [PATCH 47/67] A typo in the new label listing query --- routes/labels.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/labels.go b/routes/labels.go index 55d3283..26b09b5 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -76,8 +76,8 @@ func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { return r.Not(thread.Field("is_read")).And( r.Not(thread.Field("labels").Contains(spamTrash[0].ID).Or(thread.Field("labels").Contains(spamTrash[1].ID))), ) - }), - }).Count() + }).Count(), + }) }).Run(env.Rethink) if err != nil { env.Log.WithFields(logrus.Fields{ From 144a1cfc971d959a2dd93fa10d22a576199cde6a Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 17:43:27 +0200 Subject: [PATCH 48/67] Add a label check for Sent --- routes/labels.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/routes/labels.go b/routes/labels.go index 26b09b5..2de8862 100644 --- a/routes/labels.go +++ b/routes/labels.go @@ -30,6 +30,10 @@ func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { "Trash", session.Owner, true, + }, []interface{}{ + "Sent", + session.Owner, + true, }).Run(env.Rethink) if err != nil { env.Log.WithFields(logrus.Fields{ @@ -43,8 +47,8 @@ func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { return } defer cursor.Close() - var spamTrash []*models.Label - if err := cursor.All(&spamTrash); err != nil { + var spamTrashSent []*models.Label + if err := cursor.All(&spamTrashSent); err != nil { env.Log.WithFields(logrus.Fields{ "error": err.Error(), }).Error("Unable to unmarshal account's specified labels") @@ -56,11 +60,11 @@ func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { return } - if len(spamTrash) != 2 { + if len(spamTrashSent) != 3 { env.Log.WithFields(logrus.Fields{ - "count": len(spamTrash), + "count": len(spamTrashSent), "account": session.Owner, - }).Error("Invalid count of Trash and Spam labels") + }).Error("Invalid count of Trash, Sent and Spam labels") utils.JSONResponse(w, 500, &LabelsListResponse{ Success: false, @@ -74,7 +78,13 @@ func LabelsList(c web.C, w http.ResponseWriter, req *http.Request) { "total_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Count(), "unread_threads_count": env.Threads.GetTable().GetAllByIndex("labels", label.Field("id")).Filter(func(thread r.Term) r.Term { return r.Not(thread.Field("is_read")).And( - r.Not(thread.Field("labels").Contains(spamTrash[0].ID).Or(thread.Field("labels").Contains(spamTrash[1].ID))), + r.Not( + thread.Field("labels").Contains(spamTrashSent[0].ID).Or( + thread.Field("labels").Contains(spamTrashSent[1].ID).Or( + thread.Field("labels").Contains(spamTrashSent[2].ID), + ), + ), + ), ) }).Count(), }) From 7c8a3505613733742efc971424e938eec6094ca7 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 18:31:48 +0200 Subject: [PATCH 49/67] Added a changelog --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..38d5f53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased][unreleased] +### Added + - Added a check whether an address mapping is used in the username + reservation. + - SockJS API client for headless serverside client development. + - Onboarding emails that introduce users to the service. + - Multiple identity support (also known as email aliases). + +### Changed + - Moved from traditional `go get`-based flow to dependency vendoring + using [godep](https://github.com/tools/godep). + - Disabled most of the log output to make it easier to analyze. + +### Fixed + - Cursor leakage all over the `db` package. + - thread.update changing date_modified field of the model, which + resulted in invalid ordering of the emails in the web client. + Emails in "spam" being shown as unread on the sidebar (new label + fetching query). + +## [2.0.1] - 2015-04-15 +### Added + - Address mapping table for account's name-to-id lookups. + - Username length check during registration. + +### Changed + - New index creation code (multiple compound and multi indexes). + +### Fixed + - Lack of Message-ID header causing Lavaboom emails to be flagged as + spam. + +## 2.0.0 - 2015-04-02 +### Added + - Initial release of Lavaboom API 2.0 + +[unreleased]: https://github.com/lavab/api/compare/2.0.1...HEAD +[2.0.1]: https://github.com/lavab/api/compare/2.0.1...0.2.0 \ No newline at end of file From 84f12527494bc24fe2162d286e21fd7a30c51a3a Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 18:42:56 +0200 Subject: [PATCH 50/67] Thread update corrections, fixes #122 and #117 --- routes/threads.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/routes/threads.go b/routes/threads.go index dcd8e2f..a533e1b 100644 --- a/routes/threads.go +++ b/routes/threads.go @@ -180,7 +180,7 @@ func ThreadsGet(c web.C, w http.ResponseWriter, r *http.Request) { type ThreadsUpdateRequest struct { Labels []string `json:"labels"` IsRead *bool `json:"is_read"` - LastRead string `json:"last_read"` + LastRead *string `json:"last_read"` } type ThreadsUpdateResponse struct { @@ -227,12 +227,12 @@ func ThreadsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { return } - if thread.Labels != nil && !reflect.DeepEqual(thread.Labels, input.Labels) { + if input.Labels != nil && !reflect.DeepEqual(thread.Labels, input.Labels) { thread.Labels = input.Labels } - if thread.LastRead != input.LastRead { - thread.LastRead = input.LastRead + if input.LastRead != nil && *input.LastRead != thread.LastRead { + thread.LastRead = *input.LastRead } if input.IsRead != nil && *input.IsRead != thread.IsRead { From 39180a32736efe602d281913dff89442cc638400 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sun, 17 May 2015 18:44:18 +0200 Subject: [PATCH 51/67] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38d5f53..be02835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). resulted in invalid ordering of the emails in the web client. Emails in "spam" being shown as unread on the sidebar (new label fetching query). + - Incorrect difference checker in thread.update. ## [2.0.1] - 2015-04-15 ### Added From 58c0c03c98450f701f32d52484ff1efb2c44a021 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 19 May 2015 00:18:43 +0200 Subject: [PATCH 52/67] Bloom filter for leaked password matching --- .gitignore | 1 + env/config.go | 3 + env/env.go | 3 + main.go | 6 + routes/accounts.go | 4 +- setup/setup.go | 17 + utils/10k.go | 10009 ------------------------------------------- 7 files changed, 32 insertions(+), 10011 deletions(-) delete mode 100644 utils/10k.go diff --git a/.gitignore b/.gitignore index 4140b23..eec3c92 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.exe _vagrant/.vagrant .config.conf +bloom.db diff --git a/env/config.go b/env/config.go index e1f2a67..48c743a 100644 --- a/env/config.go +++ b/env/config.go @@ -31,4 +31,7 @@ type Flags struct { SlackChannel string SlackIcon string SlackUsername string + + BloomFilter string + BloomCount uint } diff --git a/env/env.go b/env/env.go index 20f4fae..9b85ce3 100644 --- a/env/env.go +++ b/env/env.go @@ -4,6 +4,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/bitly/go-nsq" "github.com/dancannon/gorethink" + "github.com/willf/bloom" "github.com/lavab/api/cache" "github.com/lavab/api/db" @@ -43,4 +44,6 @@ var ( Factors map[string]factor.Factor // Producer is the nsq producer used to send messages to other components of the system Producer *nsq.Producer + // PasswordBF is the bloom filter used for leaked password matching + PasswordBF *bloom.BloomFilter ) diff --git a/main.go b/main.go index cfec8b8..5038374 100644 --- a/main.go +++ b/main.go @@ -86,6 +86,9 @@ var ( slackChannel = flag.String("slack_channel", "#notif-api-logs", "channel to which Slack bot will send messages") slackIcon = flag.String("slack_icon", ":ghost:", "emoji icon of the Slack bot") slackUsername = flag.String("slack_username", "API", "username of the Slack bot") + // Password bloom filter path + bloomFilter = flag.String("bloom_filter", "bloom.db", "Bloom filter containing passwords") + bloomCount = flag.Uint("bloom_count", 14522336, "Estimated count of passwords in the bloom filter") ) func main() { @@ -123,6 +126,9 @@ func main() { SlackChannel: *slackChannel, SlackIcon: *slackIcon, SlackUsername: *slackUsername, + + BloomFilter: *bloomFilter, + BloomCount: *bloomCount, } // Generate a mux diff --git a/routes/accounts.go b/routes/accounts.go index 482ad8e..1caaa98 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -323,7 +323,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { // Our token is fine, next part: password. // Ensure that user has chosen a secure password (check against 10k most used) - if !utils.IsPasswordSecure(input.Password) { + if env.PasswordBF.TestString(input.Password) { utils.JSONResponse(w, 403, &AccountsCreateResponse{ Success: false, Message: "Weak password", @@ -601,7 +601,7 @@ func AccountsUpdate(c web.C, w http.ResponseWriter, r *http.Request) { } } - if input.NewPassword != "" && !utils.IsPasswordSecure(input.NewPassword) { + if input.NewPassword != "" && env.PasswordBF.TestString(input.NewPassword) { utils.JSONResponse(w, 400, &AccountsUpdateResponse{ Success: false, Message: "Weak new password", diff --git a/setup/setup.go b/setup/setup.go index cbe193d..cba33c5 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -17,6 +17,7 @@ import ( "github.com/johntdyer/slackrus" //"github.com/pzduniak/glogrus" "github.com/segmentio/go-loggly" + "github.com/willf/bloom" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" "gopkg.in/igm/sockjs-go.v2/sockjs" @@ -91,6 +92,22 @@ func PrepareMux(flags *env.Flags) *web.Mux { // Pass it to the environment package env.Log = log + // Load the bloom filter + bf := bloom.NewWithEstimates(flags.BloomCount, 0.001) + bff, err := os.Open(flags.BloomFilter) + if err != nil { + log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Fatal("Unable to open the bloom filter file") + } + defer bff.Close() + if _, err := bf.ReadFrom(bff); err != nil { + log.WithFields(logrus.Fields{ + "error": err.Error(), + }).Fatal("Unable to read from the bloom filter file") + } + env.PasswordBF = bf + // Initialize the cache redis, err := cache.NewRedisCache(&cache.RedisCacheOpts{ Address: flags.RedisAddress, diff --git a/utils/10k.go b/utils/10k.go deleted file mode 100644 index b0597a5..0000000 --- a/utils/10k.go +++ /dev/null @@ -1,10009 +0,0 @@ -package utils - -var passwords10000 = map[string]struct{}{ - "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8": struct{}{}, - "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92": struct{}{}, - "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f": struct{}{}, - "03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4": struct{}{}, - "65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5": struct{}{}, - "5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5": struct{}{}, - "a9c43be948c5cabd56ef2bacffb77cdaa5eec49dd5eb0cc4129cf3eda5f0e74c": struct{}{}, - "0f28c4960d96647e77e7ab6d13b85bd16c7ca56f45df802cdc763a5e5c0c7863": struct{}{}, - "a01edad91c00abe7be5b72b5e36bf4ce3c6f26e8bce3340eba365642813ab8b6": struct{}{}, - "6382deaf1f5dc6e792b76db4a4a7bf2ba468884e000b25e7928e621e27fb23cb": struct{}{}, - "1c8bfe8f801d79745c4631d09fff36c82aa37fc4cce4fc946683d7b336b63032": struct{}{}, - "000c285457fc971f862a79b786476c78812c8897063c6fa9c045f579a3b2d63f": struct{}{}, - "c2eb7898bb6771503ffee5d0c722e5b561fe480edbc30141880a1cdf1e5b1cf6": struct{}{}, - "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090": struct{}{}, - "a92f6bdb75789bccc118adfcf704029aa58063c604bab4fcdd9cd126ef9b69af": struct{}{}, - "34550715062af006ac4fab288de67ecb44793c3a05c475227241535f6ef7a81b": struct{}{}, - "0bb09d80600eec3eb9d7793a6f859bedde2a2d83899b70bd78e961ed674b32f4": struct{}{}, - "fc613b4dfd6736a7bd268c8a0e74ed0d1c04a959f59dd74ef2874983fd443fc9": struct{}{}, - "9ce8db922a8f4a7abd859adee70bd8b7a63321265487da54cf4bed6a69eb3e1b": struct{}{}, - "bcb15f821479b4d5772bd0ca866c00ad5f926e3580720659cc80d39c9d09802a": struct{}{}, - "81a83544cf93c245178cbc1620030f1123f435af867c79d87135983c52ab39d9": struct{}{}, - "136c67657614311f32238751044a0a3c0294f2a521e573afa8e496992d3786ba": struct{}{}, - "73cd1b16c4fb83061ad18a0b29b9643a68d4640075a466dc9e51682f84a847f5": struct{}{}, - "37bfdcb4c50793a6286fa0efe07b9e6bba8605b2c32e329fb9f71f225545f027": struct{}{}, - "8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414": struct{}{}, - "a30c89b446e0e8ab6b8c00c986f586c1ce378aaf0c5348c660e3ad1779be9886": struct{}{}, - "e9a63a4eb15738ae85cd416221c8fcc4ccc0018fac91335b42eaa016c76e87f9": struct{}{}, - "6161b0a284159565a0f7d5df2dd2698b5f87906cd91ff5322caf179b451f5a41": struct{}{}, - "203b70b5ae883932161bbd0bded9357e763e63afce98b16230be33f0b94c2cc5": struct{}{}, - "dbc4a04327176e6577b4da46df04564150053960eba5d89587dad1f76a818d80": struct{}{}, - "cbeaff314ef5ad032caa60ee2e8d8144ae52a8572c7d6f75631f3bd4080a7b16": struct{}{}, - "d38681074467c0bc147b17a9a12b9efa8cc10bcf545f5b0bccccf5a93c4a2b79": struct{}{}, - "abc529a4b673cbbbc532e584706cb8137be876ad53269df3b97fbd40fc76fe57": struct{}{}, - "4007d46292298e83da10d0763d95d5139fe0c157148d0587aa912170414ccba6": struct{}{}, - "8f27f432fcbaa4b5180a1cc7a8fa166a93cda3c1bce6f19922dd519d02f4bb39": struct{}{}, - "6ac3c336e4094835293a3fed8a4b5fedde1b5e2626d9838fed50693bba00af0e": struct{}{}, - "1532e76dbe9d43d0dea98c331ca5ae8a65c5e8e8b99d3e2a42ae989356f6242a": struct{}{}, - "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08": struct{}{}, - "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1": struct{}{}, - "ed45d626b07112a8a501d9672f3b92796a6754b8d8d9cb4c617fec9774889220": struct{}{}, - "308738b8195da46d65c96f4ee3909032e27c818d8a079bccb5a1ef62e8daaa45": struct{}{}, - "0522a55e2d5f0993a3d66d28864b2862a7218a75ea7968b075333434404485c3": struct{}{}, - "b9dd960c1753459a78115d3cb845a57d924b6877e805b08bd01086ccdf34433c": struct{}{}, - "d979885447a413abb6d606a5d0f45c3b7809e6fde2c83f0df3426f1fc9bfed97": struct{}{}, - "01621148306fc8fb7c2b95eeb5c37e375f90db53cf8313ea87c9c34c05b7e0e5": struct{}{}, - "686f746a95b6f836d7d70567c302c3f9ebb5ee0def3d1220ee9d4e9f34f5e131": struct{}{}, - "a941a4c4fd0c01cddef61b8be963bf4c1e2b0811c037ce3f1835fddf6ef6c223": struct{}{}, - "e1fc45f7880e0505ff0b6a079b9af149f225e260f59b1d20225357a8cce8ffd8": struct{}{}, - "f50c51ed2315dcf3fa88181cf033f8029cac64f7dea4048327ca032ec102ea74": struct{}{}, - "6161b2838ffa6ce17b84db3b45b4f8437855ecf43e75de2d1ad0008eaae91aa0": struct{}{}, - "8cbbcf29d9cef89675c5f5c1dcfe827d0570416a5aaba30dd0de159661ad905b": struct{}{}, - "bd3dae5fb91f88a4f0978222dfd58f59a124257cb081486387cbae9df11fb879": struct{}{}, - "a0561fd649cdb6baa784055f051bad796ea0afef17fca38219549deeba4e8c1a": struct{}{}, - "15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225": struct{}{}, - "481f6cc0511143ccdd7e2d1b1b94faf0a700a8b49cd13922a70b5ae28acaa8c5": struct{}{}, - "fc52fabe94c0e037d2df4498e87481a6438960c9f73d517584a7a5c564535ac4": struct{}{}, - "aae5be5f6474904b686f639e0fcfd2be440121cd889fa381a94b71750758345e": struct{}{}, - "74fca0325b5fdb3a34badb40a2581cfbd5344187e8d3432952a5abc0929c1246": struct{}{}, - "78cde64c3e47f2cbfd9da721f54aacde33779916683c79de86962898feefac21": struct{}{}, - "d0784c6b1785dcd474688d46b1fe99792ff66f6b56bebf26dda0c08516bac22e": struct{}{}, - "fa2115f8d576a6ab722956697fc759c31d1cd6b93c8336bfebf73ed5cba2ff49": struct{}{}, - "8360632a2b41498c6f979a15aced6655a2857f259533e77106228c683c4ab5af": struct{}{}, - "96cae35ce8a9b0244178bf28e4966c2ce1b8385723a96a6b838858cdd6ca0a1e": struct{}{}, - "c64975ba3cf3f9cd58459710b0a42369f34b0759c9967fb5a47eea488e8bea79": struct{}{}, - "94edf28c6d6da38fd35d7ad53e485307f89fbeaf120485c8d17a43f323deee71": struct{}{}, - "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824": struct{}{}, - "52e8e47b38e854580afce4aade15dbd5ce0c0464da711afe71da123687d5a4cd": struct{}{}, - "1b4c9133da73a711322404314402765ab0d23fd362a167d6f0c65bb215113d94": struct{}{}, - "da5fe20988c8e92bdbb374788b85e9da76a6677fa1cde68c62842c0ab083fbaf": struct{}{}, - "13b1f7ec5beaefc781e43a3b344371cd49923a8a05edd71844b92f56f6a08d38": struct{}{}, - "aa97302150fce811425cd84537028a5afbe37e3f1362ad45a51d467e17afdc9c": struct{}{}, - "7f5a9ba14cb89dc20e9001e41ba6f98fc1d4d71752a7407bcd082d52bb63465c": struct{}{}, - "49eff747f7b66f70133bfe00aa8ac2d6b0fbee5be80e52537b0163f147d20418": struct{}{}, - "b54a1af8b666f61c2dd5ae8f8a543133409fd28c3b78064c5db993bf2c8e77bc": struct{}{}, - "08ddff4ebe39249a9208cd305b7d14091b1ebabef6adfa897cc34675fa0e0848": struct{}{}, - "a0b7c245fab334467e9508386bdccbf953957d01043ec043da915ad993766a48": struct{}{}, - "3ef81cb18bdaac2f67a114146b7f9c8da4bf8ceef8021dfc2da4daa8c1416e52": struct{}{}, - "e83664255c6963e962bb20f9fcfaad1b570ddf5da69f5444ed37e5260f3ef689": struct{}{}, - "09c587fb282c3423f0867eb10c37a6fd4e3a157865efbf7bccfd51621e9940c1": struct{}{}, - "8e924025a26c584ad4ac6365116e09b852ae6b7016da4c0851e269348d93c228": struct{}{}, - "bb61ef40814ce34c1edf0edb609854be9793198a8f60b67d9fb26643c32281d3": struct{}{}, - "c7c1319276e936c8d64f1d5ed80cd8a0cf54e6dea7b0125533eb4163e03a2c11": struct{}{}, - "0ffe1abd1a08215353c233d6e009613e95eec4253832a761af28ff37ac5a150c": struct{}{}, - "f6274d9892026fe47dd5f96f708ef8983dccc7bacf5ee4a90b2400805adaea0a": struct{}{}, - "68be7550846ecd878947b4eb0ac13d3cca3cf6c4940c94d90163e0a15e947203": struct{}{}, - "3ea87a56da3844b420ec2925ae922bc731ec16a4fc44dcbeafdad49b0e61d39c": struct{}{}, - "1ef7bb1ba959d2c3be7c7a72c8256de96ab78e901f717f1899fd6ac041e21225": struct{}{}, - "873ac9ffea4dd04fa719e8920cd6938f0c23cd678af330939cff53c3d2855f34": struct{}{}, - "04e77bf8f95cb3e1a36a59d1e93857c411930db646b46c218a0352e432023cf2": struct{}{}, - "b6f8d434a847fb0f0c1a8d9b936b8ca952e224f205a55f4ba9b2c20f88fdc9e7": struct{}{}, - "1ecd41c03ef78bd6daeaa6bb008896607a8413bf8ba6266be80327554b370a9e": struct{}{}, - "23ddda4810068cc44360dffd31b6c5a9ad13fb9e6a69c9354a5d1b07f1b9843f": struct{}{}, - "61bffea9215f65164ad18b45aff1436c0c165d0d5dd2087ef61b4232ba6d2c1a": struct{}{}, - "81c9d6128b5fcd7bbe4ba65c177388ff767a2017f971c053dcaa6f32b4d6a758": struct{}{}, - "c685a2c9bab235ccdd2ab0ea92281a521c8aaf37895493d080070ea00fc7f5d7": struct{}{}, - "fa340114498cfb0f5dfedaf79ddcf3af3e3211a852438d087a77c1a4af98c42e": struct{}{}, - "2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b": struct{}{}, - "8588310a98676af6e22563c1559e1ae20f85950792bdcd0c8f334867c54581cd": struct{}{}, - "1855f8fe0e94e2f8a0b09707b3159acc0be5a9f6bb1b8fc0c2e75b662398e050": struct{}{}, - "6d1173c424c75d203bbcac1f480ecee0f4db7d4a457c008961fde94f61117d63": struct{}{}, - "69d4211f8999a5de44911f2b7fccb31dcc4b225d6f04eaaa2381a182b1a32b45": struct{}{}, - "502913bfdd49eab564282dff101e6d167321237eeec66eedb2a438ed80fdeaa0": struct{}{}, - "6e00cd562cc2d88e238dfb81d9439de7ec843ee9d0c9879d549cb1436786f975": struct{}{}, - "a84571394b5e99fe70aae39ece25f844acbaf83479e27f39a30732e092b19677": struct{}{}, - "e4ad93ca07acb8d908a3aa41e920ea4f4ef4f26e7f86cf8291c5db289780a5ae": struct{}{}, - "299d6631d639256a762b81ee007deb44cdd1cbc983e025038e113a0e709d3f7b": struct{}{}, - "d081f5e402980b267f1f87cb6b74fc3eb249de26e670a9db55dec67da7864de4": struct{}{}, - "dc355ec75a2dc4a1d29582933b52f9f2ed71061432d72e1991d8b15445b2ff03": struct{}{}, - "8e0a1b0ada42172886fd1297e25abf99f14396a9400acbd5f20da20289cff02f": struct{}{}, - "b5ad121307b9c486471d1d2e45bbcfa70cf31125e2049c66f658bcf40016c84e": struct{}{}, - "03a8f0dd8edb33781a836ac497800b5f9c5c47c2ddbfd0f89581140589725a85": struct{}{}, - "ed02457b5c41d964dbd2f2a609d63fe1bb7528dbe55e1abf5b52c249cd735797": struct{}{}, - "d2a1ba788399c8d1c1273791c5d9937a0b844247425556a9464677ac6dbbbd1f": struct{}{}, - "248b57c5cabbc9944d169d10bc4959a042d0bb81ab6cfc9166f40a9d0f0fd614": struct{}{}, - "9c15e816069946fbd20bed0935dd9d8e34d64034d657a2b852f8b66ad91af5b6": struct{}{}, - "33c614ca3cf78827a85dc0d8d06bfcf8c4d923fd23c813acd50b80ed2d4d4fb3": struct{}{}, - "d2f129dd306d1d7a43fcfe047ef0dbb28275f5ed65f4636a28e2cae37b81d095": struct{}{}, - "d7e83e28a04b537e64424546b14caf9b67bad2f28dabce68116e0d372319fa00": struct{}{}, - "91246e60ed7dc0330ab443434feba9e5aec9a0f2de5dec0f7575fd2a3caaa421": struct{}{}, - "5509840d0873adb0405588821197a8634501293486c601ca51e14063abe25d06": struct{}{}, - "102cf10b5286bad9fcfe5e275ace3ddd7dcc23931fb0ca93dc223daf9877cabd": struct{}{}, - "3fe1f7584833183e2da842b2f18123186919d4aa9828dbebdb3956429d9607bb": struct{}{}, - "8d2ac8b58ead9744d77286de9b0bcb7a894f238c3149fc9f3b1e3caff36330fe": struct{}{}, - "4140197ec7959b1c0674de1ee3c6d3f542c371276e2518427e0ad89e5c30f804": struct{}{}, - "c5422c052bfbd7bbd9764e0467688b62193fec4fa32a1b13af28d1708d5870ec": struct{}{}, - "a259276acf1cd8259e01258016c4716e109defd8e02fe88431322919fbb6907f": struct{}{}, - "aae0a09ff5c9b924621f84fecff5989846af92b22624bbfca50eadb94d7e5aeb": struct{}{}, - "7ee8118150e0ce023742beba6f10bf23aabbf0bc2c182f36fd1a6753cd21b4c6": struct{}{}, - "6d5ef1fdb68c5ab10b7c90f1796f711153c1134f75c778280bc67b1bf1d3e21c": struct{}{}, - "cec8478d2feedfa7bda5501face1aab64368b9876ea5149ec11b2b9df4a2e568": struct{}{}, - "85738f8f9a7f1b04b5329c590ebcb9e425925c6d0984089c43a022de4f19c281": struct{}{}, - "047d0d21dcef00fbfb1702ecf5132666d0a65a644cc1a7b1984d545235e70ee9": struct{}{}, - "d443268885f6a8215c70672c4b523cfed398c895e7004741e6389d56957d4ca8": struct{}{}, - "4ea5ee68fea05586106890ded5733820bb77d919cda27bc4b8139b7cd33b8889": struct{}{}, - "b2c03f7e6a98dcfeb568cd82d3de69250681e3071c00accbcdd31709930401d3": struct{}{}, - "f86aa868c3b4d5a01c4f14f5ef0656241d43d863239e15ef64cc9198f0a8c6d0": struct{}{}, - "c94bb5f268d814b7ebe7ea96321013e23ce47e48cb418302a60f2d231519437d": struct{}{}, - "811eb81b9d11d65a36c53c3ebdb738ee303403cb79d781ccf4b40764e0a9d12a": struct{}{}, - "ad505b0be8a49b89273e307106fa42133cbd804456724c5e7635bd953215d92a": struct{}{}, - "c006c7e3ab14d686f63524136f1ec7c5e553d839bc01c851e4dc9de2bdbfc589": struct{}{}, - "e8f56862d74ef5599af4eeca73924bfa44a6773a497af0c29c48e18729ba6ff0": struct{}{}, - "56ccc4dcfc96534b06fc0c08a301be24f13b491484d5d984953cc0dba9bbb89a": struct{}{}, - "5f3d6952c5c5e22077fabf461de80f1ce475752fe75afcf5ca46bac438405619": struct{}{}, - "b8510932dad3ddf0fc34661a0caf6674e5c0d672e3930c6a736424d4df0e8016": struct{}{}, - "ce1d801d152db837c0fc85ec31d69aba1a610d18a7759851d8cd07adaee9d8fd": struct{}{}, - "08775183b0ecf9d8b7e7911521679e9afb0b287508a2d116f2cc7c77fbc45088": struct{}{}, - "8def3bf5d78abb247b4829e87b52b10d79b1cd0e2aec529930ed692ee8d1cd2c": struct{}{}, - "f916a6b54fccf4fbdce5e09cbd30bf7070c3c249f808395009cd0870dfbe80c5": struct{}{}, - "37290d74ac4d186e3a8e5785d259d2ec04fac91ae28092e7620ec8bc99e830aa": struct{}{}, - "b6bbbc6f04a28bb31e595bf47bfec6e168c0f1fb98ad3131e927b34c4ca38c05": struct{}{}, - "d75a838dc758ba17f28bd8dbac605cb70c35465263d5733164521de2f7ef7926": struct{}{}, - "d808cfd66215b9ca25d0d02778e1931c7055e2a21bde4a695b9df4ab522ff3cf": struct{}{}, - "5aa7ae42bb3078ad03a392a9bfb9cfecec614c64c2ca83da298ede69430e574d": struct{}{}, - "b7fb217694ae2d305e766608d250f797daa984e4ac4b5fa638a729be352f2fcd": struct{}{}, - "280d44ab1e9f79b5cce2dd4f58f5fe91f0fbacdac9f7447dffc318ceb79f2d02": struct{}{}, - "119c9ae6f9ca741bd0a76f87fba0b22cab5413187afb2906aa2875c38e213603": struct{}{}, - "cdb59355f3ba293977fc0945fb85f11822d412c45c7520c7121bd2234f6c1f48": struct{}{}, - "4d1d13fdfd973f8594e0e03d363f2239484ac1173e3e7223c1fb77d7dc2745eb": struct{}{}, - "67a7d5777fedda6f582329a7f1199a6fcafd40da7d6b29e29271d8d97b4233ee": struct{}{}, - "ed49887b003c3fc77e98e329dd814453eeecee252775e8f088d27008d81c71e8": struct{}{}, - "10fe82adb964fa73c3c60be251181c421193f56ae01ba671974d82365a08a410": struct{}{}, - "8fdd880f097cddfef86895d2c48f649e943bed14639f0ad29671508b536c9fc1": struct{}{}, - "3b0fe0d342e9fa16a5c68dbba33f2e63c024f72a9d4c1ce1028570101d5229ff": struct{}{}, - "1931307202ca8e6b48a45f8187db0c9156538399ba8fd04be747c0b951f20f87": struct{}{}, - "64b4d0f47c93ce23d157e68a58767356283dc9b63c459d45d0e0e39b3a64b9b9": struct{}{}, - "270d76c78b081db72f35458cc7b0019ced8e29da7ec15a77d99533a0dcb06c1d": struct{}{}, - "f1996a7cda48bca0dc87f700b183266eed3a770330d924fb1507b3036e37b0ff": struct{}{}, - "665cb762e3bc03d078dea6bd624f49ac355ed3389d977f6e320792111961421d": struct{}{}, - "b493d48364afe44d11c0165cf470a4164d1e2609911ef998be868d46ade3de4e": struct{}{}, - "beaafa510bcc774b1be0c7f3fbb033017cd97f2c73455ec2d7747c2e993759b5": struct{}{}, - "9bfa0b50a90e669907e78780bcc1e5e972742e0d124b30a67fbeb6371c604891": struct{}{}, - "be392b792809651cdec3485da4357568972ceb4cfd939c4bf5e5c3d011b04837": struct{}{}, - "d2641888ed6426afd3d3649066cf3614ec2eb63d3ec90ba2e3a54ba2dffa61ca": struct{}{}, - "d37d96b42ad43384915e4513505c30c0b1c4e7c765b5577eda25b5dbd7f26d89": struct{}{}, - "e08d706b3e4ce964b632746cf568913cb93f1ed36476fbb0494b80ed17c5975c": struct{}{}, - "917ebb3396b2ff2e27b75e3fe421b1edc07b998f74350472f3abc5c6620a68db": struct{}{}, - "fdb7d5c701a3b4a9981e98fd486d22b51b51f2e91605540e57081d440573c009": struct{}{}, - "c7c084318b6f1bece6f74ffce1ea53596070345272dee8040037497c7d4cbffe": struct{}{}, - "26ae61de69441914fdb5f3a540758fb2d95eaee4f73c2493f01f5a6debb894c8": struct{}{}, - "96d9632f363564cc3032521409cf22a852f2032eec099ed5967c0d000cec607a": struct{}{}, - "80537d65d479a2f986a78e859cc2bcaaf6a62f6df7a6ba2c5dd192cfcb2ff790": struct{}{}, - "8c649a8f85f3d89d4eba2cf885e7f9273c9672b73eaf3ce43c5ea038ca648f0f": struct{}{}, - "5d7f15f2fce8ddb2dbef5c38be896c238ba7e0a432e396759030a853fa6b1151": struct{}{}, - "3c66157844fa8ce7e9b67b0022383d7709ba2b30f8306d3c9b2eceb2cd91e4dc": struct{}{}, - "f15c16b99f82d8201767d3a841ff40849c8a1b812ffbfd2e393d2b6aa6682a6e": struct{}{}, - "8fe26efbd6c39984f6538ba14968a881925d02fc3ca58fd5b3caa63a4c310f17": struct{}{}, - "a2470c9d137c1c5d3567d1180a64cb43a9269c4d6f1ff13ac8cdbaf6fc5df3b7": struct{}{}, - "2bc0659381c5a5ea1421ba9bc04f34f13522e4ccc919f8ce51d4f1ff949d67bc": struct{}{}, - "739e72f2db16302b3d58893083364d942bd22727938b316be2ae6c5a12b342fc": struct{}{}, - "30c5461fc27b84f1f1ad0a83162a26882b22d11cdfa45978dd21c810056e8d0e": struct{}{}, - "30c1d524d7221c3c698e18697b61aafa595575a2118a0b74815c5471db50b1fc": struct{}{}, - "26a05370613ccf56c10ddc2439e319dc808aa9cd14ea8e1b64bde0fc602d3c9d": struct{}{}, - "2632ec06e2c2e54a52245ffd9e5af1cb65b21a4cdb031d4e11383719cf8c0ca4": struct{}{}, - "0b04bad57d3b2c6f3736b7cb584f403ecf625cc31c0ad21e19e05a537304e969": struct{}{}, - "8c5c04391361cbf4afd74c5ed8101ea4af881c4ee3b3df1d5b3716a19b1a834d": struct{}{}, - "7349a876ddf6f1d6fb44146e0d1a826c9fabfcf8e96b48d0619d1c83cc6ad6bf": struct{}{}, - "8e6740a5526b00be54e28489b7afe9d9aabf53ea1a1a4612faf2516fd27aa3b8": struct{}{}, - "7a850e85feb9d33611aec64e220b6ddf3ca574d06f7f91ebc415d23e0672b3f2": struct{}{}, - "f0b139242b497c8a5ac33bf176ad89d57176a044708ede42613983ccb97798c3": struct{}{}, - "2be4e33aa3e21f6ff7ad144f8fd58228a1cce3b50340b4805ff0c63480eec488": struct{}{}, - "a384e2f868dbe2347844d303784dfc82b39dd32d40e322f8941e20c1775846da": struct{}{}, - "6089854c94ca5454b76be6752c562901a985f64c9a946f62976aeab593b83161": struct{}{}, - "35af25f5a5fdac6401dd4baf949794b59db80168d69c80e7f2cfd83999e2feff": struct{}{}, - "16477688c0e00699c6cfa4497a3612d7e83c532062b64b250fed8908128ed548": struct{}{}, - "b0120ab459558f30fb7d0d98d4bd29a8131505f13eaa4b584ec91244d09cbde2": struct{}{}, - "91b4d142823f7d20c5f08df69122de43f35f057a988d9619f6d3138485c9a203": struct{}{}, - "fc881aa34d44660e1012dec26ccda0b469d6c8359e91dc674dab4c095b9fe832": struct{}{}, - "6061913f881d4dca9a230e3e86a75a412cf26487501a9e6a25873dcfde6d5dcc": struct{}{}, - "ee79976c9380d5e337fc1c095ece8c8f22f91f306ceeb161fa51fecede2c4ba1": struct{}{}, - "972964b66bdfe6b5b181c5112a6a0470204f64661ee7e3efb9aab0ce3cc403ff": struct{}{}, - "ee2ee2af90c0fa7372eee418556cd0d6a777dc17355932a8cf6ee24dc42d5d20": struct{}{}, - "aa866f38a639692f90aa5396faf5e50012af2edd49b01b513d6ac1de3e697022": struct{}{}, - "ea386175a5e7b01bf4a498e1b922dd4513731d8986940c037cb1377597bbc953": struct{}{}, - "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b": struct{}{}, - "b3d4cd641cbb8123b7e269854e9fccde3a3722e891a1d1e90a51768b3000eafe": struct{}{}, - "346793476af7c0a55cfa62abc42b77c3fd87086b08fe6fdd628bfdda4e1d7cab": struct{}{}, - "1df1854015e31ca286d015345eaff29a6c6073f70984a3a746823d4cac16b075": struct{}{}, - "15c6d611193988e468c7431229c59ce13b0407fba24f11d36c42680d7fa11e98": struct{}{}, - "7dfef7aed2105b7eceb4d34e1ad84fdad4693bd5de041e1b47079efeb6001a83": struct{}{}, - "88b1cca59060320e5e5662a7da636884eb7580f4dc7e22cfb6f88b8f99045a71": struct{}{}, - "cf0622ef2a661cd2f11b0b644e40e4e00eb962918424a8c8ba53842fdf290235": struct{}{}, - "ab1cb712f2dca756105160805501f4d6d8657d93d40b16eee4ecb5fd048d26eb": struct{}{}, - "8c1cdb9cb4dbac6dbb6ebd118ec8f9523d22e4e4cb8cc9df5f7e1e499bba3c10": struct{}{}, - "3721e1a2ad55f5ac10498a98a9069121be19ea3363cf44f18f10008728c360ad": struct{}{}, - "519ba91a5a5b4afb9dc66f8805ce8c442b6576316c19c6896af2fa9bda6aff71": struct{}{}, - "07d046d5fac12b3f82daf5035b9aae86db5adc8275ebfbf05ec83005a4a8ba3e": struct{}{}, - "4221a0fc3dfbfd830dc3a13f6c72d233781179bea27df532ff903f3abdba5586": struct{}{}, - "60a0f78b507f6249a6e29eee34f3f2940188b0b995715a82a03469f5d1586275": struct{}{}, - "dd56de4137951d9c92681b03416ec15f886b4482a27e3a517d32f085244cbe5d": struct{}{}, - "b7fc6eaae1bc7d072af23fd0ddb91bcdb21c9252231a7d0a6a93804883d7198b": struct{}{}, - "f748d0c7d58eaf6f47571c846fddce9528f64da78a49fbc1223af3891fbcb78d": struct{}{}, - "31611159e7e6ff7843ea4627745e89225fc866621cfcfdbd40871af4413747cc": struct{}{}, - "eab18a7ebe4a09e361ff2de6c14e0418fe9446403de8abb1da6b0baa9669a522": struct{}{}, - "461f09556c26f2100aaac02b36fb3b73303bd9f884a937f7c983e1e5c1148a48": struct{}{}, - "ca700c8aa0b8566b842c86c4c1ce298b8d8e62f1a91d4698dc0f6d468ac399e1": struct{}{}, - "c812b846bd8b54cfdf715ff2627b263a70e25cc7039e6385c0c93d2b77bdb6d4": struct{}{}, - "9bd2ef7a9ddbb22386f27f5f6885f8eeef6cdf57e96bbdef0f9e0421f1ec4654": struct{}{}, - "e66e9a769d159582cda9c0da24247e36340045a7c340ec59dcd63c0b61cc95cc": struct{}{}, - "ab0f4de8e463ff02f4ae998522219ee95ca1f6b41140c4ab06c273a689eeeab7": struct{}{}, - "6dbc81f0fa76157e049f9e8263ab582793ef5fa6baa39271c3cbb29a42c551fb": struct{}{}, - "919e680ee460849a74a82614de062bfbbac76bc98a2f692952b5fcb6364e598b": struct{}{}, - "7b85175b455060e3237e925f023053ca9515e8682a83c8b09911c724a1f8b75f": struct{}{}, - "c6e7ed2709a89658fed4c20bd8bff13cc5ba65d61f0b94ef48391ff1e7ba448e": struct{}{}, - "ee4e30e780bcc5fffa26e4aeb171857015b059da591faa3f8e271dcd65400f04": struct{}{}, - "2d4795b5a4b00eb38d1a1db9a90ffd8c8ba2e407b4e51f4271c1e4821064b04b": struct{}{}, - "2780b8eef998d6eaca5dbfe00e4043e626c79bc214195e1848af17a85d51519c": struct{}{}, - "ece0b66924e17189006554186450e954b984e07c61c356809fe0a692d8a5ec18": struct{}{}, - "6d1c5859550824d403d8c31af9ac673b7ba206e6f87e61e096a017299f74d63f": struct{}{}, - "c9b82b90ae572c840a7698cf0fbbb1d7d554fddcb52266dfc5e84f343e9cddda": struct{}{}, - "4b05ab7893e860443cbc0816f9f5db76de6e653519f3e91968ed56c813c6f439": struct{}{}, - "79a6a933dfc9b1975e444d4e8481c64c771d8ab40b7ac72f8bc1a1bca1718bef": struct{}{}, - "625fe74cad4600b5e8b76a9283333eb79052ae50d6af7f660feb4831d87af5d2": struct{}{}, - "b918c3f9116ce3a61efd71a1bada0d46c8f575283ce5d5798454a4abfb56ae79": struct{}{}, - "c58dd0a3760008c901839e21a0a5ae6cc92e3765513823ca9553e6ef5f733b36": struct{}{}, - "834d79caa6e0efb1f91df5d913653bc82decc238c6781b61213c848d320afab6": struct{}{}, - "992345f21b57d68f497b9c5dbf837e060eaf2d8a4894f3f98c0b64de2b13006d": struct{}{}, - "bb647e29b34334079a9d553184f6cb58abd93182f8a4f7e95caf4bea0c2b2171": struct{}{}, - "a29445fd9c3c4830dc0c905230e80d97603bcf0c120ceae8bf9dd06a71729d85": struct{}{}, - "c06b0cfe0cc5e900c57784484094331f095bf441995c3c31ea6c75691c786c35": struct{}{}, - "2070f725ff1c765b73c498de52bc419377979691f6100de3ed99794aeb40d988": struct{}{}, - "ba4788b226aa8dc2e6dc74248bb9f618cfa8c959e0c26c147be48f6839a0b088": struct{}{}, - "ae50ad81a2d4006e372c0bd3220f24c377345455d0e980c3f0d94f0e5faf2561": struct{}{}, - "baff4fb62c8b1c5a7934aeb176930b58670bc06d1db866b398b7a511a6b90f25": struct{}{}, - "74dd730b5c36c1cb4fdbb4bce1764c57f260190f94ba52d877308332f1dfe363": struct{}{}, - "5e0176c9d2070a5a2a22bf74b4abed303654690d58d64221ccbd022af827abc4": struct{}{}, - "e5d503d4582229c4c9abe250e49654765c885eb49bb49df7f1861bb79fc7346e": struct{}{}, - "701033ae26411908f31114d29190f60cdd9bf3e720e303f793882af5763b688f": struct{}{}, - "337b8d2c1e132acd75171f1acf0e73b20bc9541720d5003813f59ef0ad51f86f": struct{}{}, - "06b9a6eacd7a77b9361123fd19776455eb16b9c83426a1abbf514a414792b73f": struct{}{}, - "dca6ec9510fc0176c600bb5d75a919fba07877c74eb1a41b0530b330c5767648": struct{}{}, - "ea66f06bd8d37bbf7f74fcf064cd23af4aa104eadb0aa13684484b88a7e1c202": struct{}{}, - "548a39619a812e451be4ec19229bafffbab2cccfd539c8b1e430de93edd25716": struct{}{}, - "c7904c9551072c0cc8cad7ead0ece9521d2642b2f62281db77c1156fb224c537": struct{}{}, - "81ac4a9305a1e8b96418b7b444aa171586bbf8697b84768a0f11dddfccd48533": struct{}{}, - "350c754ba4d38897693aa077ef43072a859d23f613443133fecbbd90a3512ca5": struct{}{}, - "73ecb4b9d82c620a68d517119881d21eaaf808307da44ea47836893e551f9d51": struct{}{}, - "81fdff283ec2829b4002384ad18370f64e7a48618c45058e3d112d965e27f72e": struct{}{}, - "ee588149abfb6f15ee073cede8007aa0eb8f989519364043902d325364b3aac3": struct{}{}, - "e0bc60c82713f64ef8a57c0c40d02ce24fd0141d5cc3086259c19b1e62a62bea": struct{}{}, - "83ed5207a41179600b50c409a460cfc229c60cb91bf06480d705c923c864e501": struct{}{}, - "a32d9d7c6187886c3b750722bdae1514fc990af3bbfebdce881fe9c2ce700097": struct{}{}, - "3b80d38f7686a8b5f8e61ad562ec069ac172732fb4dab946401f21a438669a4b": struct{}{}, - "ca52e7d1c18961a8c5b3947a54e48fbf4ee70b66ea413cf7814f1c962631fd58": struct{}{}, - "7781113a99f177280ad3e89bcf631f03acb8fa8e626082dd9158eeee0bdd5674": struct{}{}, - "b4def8217cadae26d4da633fd2a4e58e326cbb5d570afdc3989484da07af3579": struct{}{}, - "66683ceffae995e70a4970e5123b319edb189689203d96b8181b4b035ee4f6bc": struct{}{}, - "1abe8f5aca6045c7844a07b0e09fb57039cb2c5923de729dfce9d07f28624971": struct{}{}, - "31506a8448a761a448a08aa69d9116ea8a6cb1c6b3f4244b3043051f69c9cc3c": struct{}{}, - "cfe730e46ecc2adc2adc7c1b22733310bbcfe972ce80e41ffab6c8f2629059de": struct{}{}, - "db9fbddbd7caeff7a326645c1bb47116eb3fd4ae3834bf5039fc5d47386786f5": struct{}{}, - "cf407fe3eab1561eb2b4a360d21248f4b24bd33770eceb59b11d9e5469bfbfbb": struct{}{}, - "37ba3881108bf3e48180350246c5959b9481633d0cb1d8694fb141dc74e5fe79": struct{}{}, - "bc98bb50e8094b2ac3ceb90ba2512587c0513cd294a07efcfdcf467198da6266": struct{}{}, - "7686d20a74e92093e15047d9102275dcba138744ee98fbf5415562b59131cded": struct{}{}, - "b7249cd073d74a83f38be932ffc7bcecddf624d0e82de38b7be66aa296b1523f": struct{}{}, - "9ff8476644903ba6cb3f90e79bf40b67133a95e44b39123a1d4caf800f216b6b": struct{}{}, - "4cc8f4d609b717356701c57a03e737e5ac8fe885da8c7163d3de47e01849c635": struct{}{}, - "bcd1642bca163da3c9d2903abf3663a79fe3295dd31a8e5b12705c6bd62611db": struct{}{}, - "b371dbc4b0268fd4fb467c755690433d1c50348b944b696087a8700a4235217e": struct{}{}, - "fa5bf879acb91bd614cfdbcaff2dc80acf09fcaa3a1cbc137deb406ce5978df9": struct{}{}, - "ca0b642ed0c1c9902c3e9c8ac00865e0a9cf6449f66012e1c6948b66818f26ff": struct{}{}, - "d0cfc2e5319b82cdc71a33873e826c93d7ee11363f8ac91c4fa3a2cfcd2286e5": struct{}{}, - "489f719cadf919094ddb38e7654de153ac33c02febb5de91e5345cbe372cf4a0": struct{}{}, - "35602208e86ac7d6b3a63780a9538a9d1763a646d5b9f3930a0548e0983e0ca6": struct{}{}, - "13a465fc6616da8d2afacaf00a8276aa841662820f9337de127f815ee71cc55d": struct{}{}, - "5d72436256ada53828b51895a94bb8489e9f1ac4fe937a8024ef1594e7045ff6": struct{}{}, - "16a2dd3a872593902a1e39276f1800bdb42614ff16caa364938e35ed8d8326a9": struct{}{}, - "4cf0c1012276f46af31e44d2fbb03ae7af56f03c9996eb9452b99b3e6273698e": struct{}{}, - "ec4c88ca7f69534f10c0611c1ecd13e7c2cdf73e1b915e9fd0cf27ac10da43fa": struct{}{}, - "408c7c5887a0f3905767754f424989b0089c14ac502d7f851d11b31ea2d1baa6": struct{}{}, - "d2653ff7cbb2d8ff129ac27ef5781ce68b2558c41a74af1f2ddca635cbeef07d": struct{}{}, - "99bde068af2d49ed7fc8b8fa79abe13a6059e0db320bb73459fd96624bb4b33f": struct{}{}, - "e067e8beb3a29632e1a9c1a074dac8b34b7fd3a6eb8851cf0111a3f409345f93": struct{}{}, - "615ed7fb1504b0c724a296d7a69e6c7b2f9ea2c57c1d8206c5afdf392ebdfd25": struct{}{}, - "a6b70a8a5074fc12ed70d61d05fffa30a5bec120ed140cf009b8a6302328360b": struct{}{}, - "556f07d09850995fb0e725730dfc84c6e5a6d2a819669197955c4493a9f1eb03": struct{}{}, - "a70a59b3981af385be12b293222f27848fbbb0387d05504b02c7f2012d02e67f": struct{}{}, - "72f4be89d6ebab1496e21e38bcd7c8ca0a68928af3081ad7dff87e772eb350c2": struct{}{}, - "7130df43355ec25b38bac55351d4bdf2c965da2847b8824b5e7760adb7ef7cd1": struct{}{}, - "aee408847d35e44e99430f0979c3357b85fe8dbb4535a494301198adbee85f27": struct{}{}, - "c2d214a484e9d44d34c0c26cca27b34d0eca755d7d7341549885efc7ef592425": struct{}{}, - "51b912f34ae18b4e5ad349f50bc6fdd8d9a605d09bab4f302a09c7f790854296": struct{}{}, - "ffb29103283b6b9997b82997ed5a6a687f34bcfff7c07f33c715555942a30aa6": struct{}{}, - "826d351f4fc913a4377c90a658119bc57a5bb231f21ce6f71fef7c262d9b2f97": struct{}{}, - "f5903f51e341a783e69ffc2d9b335048716f5f040a782a2764cd4e728b0f74d9": struct{}{}, - "b474a99a2705e23cf905a484ec6d14ef58b56bbe62e9292783466ec363b5072d": struct{}{}, - "a17444550e2c127b02ea1c197bcffa422c21713040f53d5c2ca7925419bccf7f": struct{}{}, - "3be7a505483c0050243c5cbad4700da13925aa4137a55e9e33efd8bc4d05850f": struct{}{}, - "e2284dc3b5535645288cde2bad818404be728fb8c9f70b055c0b52023b0ff0a0": struct{}{}, - "ce1484f50441f993b211de372a6968e3c424604c985829f96f3050323437df99": struct{}{}, - "8fced00b6ce281456d69daef5f2b33eaf1a4a29b5923ebe5f1f2c54f5886c7a3": struct{}{}, - "77c669164c377e39f7854d786c26b0b785d7b8ba2e1a43db978baeed24536a24": struct{}{}, - "2a8610aefdd0028c6bf074dd18721c0ef8bc43241cc7a653d7aedf2036bdf6b3": struct{}{}, - "51eeed7251f3f56288ee554afaa1028b7fbc3daacc4a952be4ae8d18ddaf3320": struct{}{}, - "8c4fd8b2c24ffcc223dbf09088bd79734e8404cd4d9e90fc418ecb490622d1ca": struct{}{}, - "06006bac27e86fca786c421407893ecb761925634c6318b1f0f9d0f4b6f7328e": struct{}{}, - "796e43a5a8cdb73b92b5f59eb50610cea3efa8ce229cd7f0557983091b2b4552": struct{}{}, - "44c59909f17c296d6f2ec4a53efac3a951add75aa67616d9c5d9d2f5fbb44f04": struct{}{}, - "cbfad02f9ed2a8d1e08d8f74f5303e9eb93637d47f82ab6f1c15871cf8dd0481": struct{}{}, - "c46d87f1fb4a5df6df841030ffe300e8271af74b62f2545c6c1759d18932675d": struct{}{}, - "a95dc4e899ac156e6e0b6666dd970c9959e0e4fd948177c263543b23492768ec": struct{}{}, - "532f011ec89ff0e2e1be76953593b588d47e8a454d18f554e4f2ea6d89615a10": struct{}{}, - "cf80cd8aed482d5d1527d7dc72fceff84e6326592848447d2dc0b0e87dfc9a90": struct{}{}, - "06a47350eaa1801d58e2946b8b8f4b0bd03007b1be7bfab26207bd2d8a7ee83d": struct{}{}, - "e31ab643c44f7a0ec824b59d1194d60dac334200d845e61d2d289daa0f087ea4": struct{}{}, - "a7fe9dcbcafa8559ea3617a3a21af7b8aa06c2badf7322c67c5ee6b6f880cdb1": struct{}{}, - "714b74b9fd79bf335839a273ce69ae2e28f7ab92dda4dd803f713f80d712f0ab": struct{}{}, - "41c991eb6a66242c0454191244278183ce58cf4a6bcd372f799e4b9cc01886af": struct{}{}, - "28a516f0a6737f6be0ed66833c14ef508571849545d6be489542f764474ce4c8": struct{}{}, - "4135aa9dc1b842a653dea846903ddb95bfb8c5a10c504a7fa16e10bc31d1fdf0": struct{}{}, - "8d3aa1a6f227d714692a9d5a7fbbda496fb09f17f7207a11ffd0a4cca6cf35b7": struct{}{}, - "dc9f28b12dd1818ee42ffc92ecb940386214598837348d30d3c6c0b7b57e34c9": struct{}{}, - "9bba5c53a0545e0c80184b946153c9f58387e3bd1d4ee35740f29ac2e718b019": struct{}{}, - "4748d4c802a775e8db9a23ec58f0986cacdc5d2d3356d22c490a7d22331ff133": struct{}{}, - "708e4b2a324e91922e63e65f519c6d206b0e9053654c853c65b45d09faa88368": struct{}{}, - "e6e62ee55819cdc13126a8633ee8c43eaf48abf491cbca92129ddf5505b70bc1": struct{}{}, - "026ad9b14a7453b7488daa0c6acbc258b1506f52c441c7c465474c1a564394ff": struct{}{}, - "0033728f0fbc83a0f0226d91bb063540d6d0158c0bbfa1620ae5b95b10f82932": struct{}{}, - "510133fe029f3f0771ce22793daed8de767e92fbabc5ebdd004a48105fff3fe2": struct{}{}, - "2144a56f77286d0cb5648501a27b5aba1bf662b955435cbd756779b5c8b83d8f": struct{}{}, - "1d8b4cf854cd42f4868849c4ce329da72c406cc11983b4bf45acdae0805f7a72": struct{}{}, - "3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b": struct{}{}, - "a57a07cb258093b2a6b4ff7819bfe31cb4666d030cd9f86aba875e18caf328d3": struct{}{}, - "628612b803c647a7de70f0306c6bbc489d8b75e6ffef150b70a1553a5a7abbeb": struct{}{}, - "466d1a9cc50f04d4f75efd2876bb4ea27dcd701ade74095ee3c361f5da5b42f0": struct{}{}, - "e8032604447171cc6e65cfb98ff38ccbf9f5f9113e0cb63060533ed86ad0032e": struct{}{}, - "12a303c224c250d07c81691de6e0fd74699ce6bd78c234057de70413a58457cf": struct{}{}, - "42cdd33820bcf8ff4beee575017de1a1f06d4d39c1bb0ca778985891f8e906d6": struct{}{}, - "07d715edb696fb5f628f7298e5d7217ed4d2bdfc5347a1c55fee30832267f21d": struct{}{}, - "9fe93417853739c1c18c2e8b051860d1a317824f1aa91304d16f3fe832486f7a": struct{}{}, - "9611d66d2d183aaa44936048ca4a32a5f9b043a615e663a39248653e3ff6ec2b": struct{}{}, - "71da248c6049134707691aa231922056ffaf061cdd2513420d945d478f5033db": struct{}{}, - "525eca1d5089dbdcbb6700d910c5e0bc23fbaa23ee026c0e224c2b45490e5f29": struct{}{}, - "819837a1df89760e1d106d2e7d83338772ace66824c6803bc262420c2814cdc1": struct{}{}, - "38c0b73caba85f5f55e8ec66fcb38ce79377ceacd97abfce7461f381fe2c6da5": struct{}{}, - "7e85c676fd97d370e2309003a5681bc4388306979b7db04c37c6fb0672891b6a": struct{}{}, - "c81ce2684a7b8d8738cd9a978e5e1acc846eca4b92686420bc1e641d287c4e80": struct{}{}, - "95fbeb8f769d2c0079d1d11348877da944aaefaba6ecf9f7f7dab6344ece8605": struct{}{}, - "f148389d080cfe85952998a8a367e2f7eaf35f2d72d2599a5b0412fe4094d65c": struct{}{}, - "6bdf0b2dfc2e0221960849943e884b3d4c6baf7cd25286caee29b9737ab3a343": struct{}{}, - "631ffa50b59a0051744dee00a0b9194d44b357cc91fc7595628a867c133f2102": struct{}{}, - "de856656f5769546822682ff8e7d991f888bcb126989badb74fad321e057addd": struct{}{}, - "ba29297b6c4f8486cb3f92f5ff09f93ca5e16ceb198d9b5e159aa0ca47f94033": struct{}{}, - "f1e5a6b9186111bdd7e173b8aafbe57ca3884eecb08f7fe561aca12ed3fd875a": struct{}{}, - "d3e9c03d2bd118d0ddcb91594734a0b46d556d9e562c6fee2d9d68e29cd31153": struct{}{}, - "1a8d9de9490302d2d234f9c92bc2c854910ebf70c5eae016b86f03427fef03b7": struct{}{}, - "623210167553939c87ed8c5f2bfe0b3e0684e12c3a3dd2513613c4e67263b5a1": struct{}{}, - "527aa9f431539da8e151d5434d1d5e611d973f601d8e970790882624554146b0": struct{}{}, - "fb26e8598ca3bfa4114ebd0b4f689425cebcac6588b458d97103be8c734ec9a0": struct{}{}, - "5a9d9dcefb56e593cbb6c58eb83a5a112d277053dacf9ffef31449c48bc578bd": struct{}{}, - "af41e68e1309fa29a5044cbdc36b90a3821d8807e68c7675a6c495112bc8a55f": struct{}{}, - "20e09e434912381a1c6b13d77536d6786742fc5a11639eba8faae8fada86bfd7": struct{}{}, - "d334cb97f2f6f07cabbfad0b2d87d6195110f1cd811cc1d618167c7b0fd87594": struct{}{}, - "2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892": struct{}{}, - "818efe46a6c2ddb95df1bdecd7869fbd6ac790f24182f681cc6b7edf43ec7b69": struct{}{}, - "895d1ba9ca06739e53361d66a06042cdc037ef6d8db8056d38e3aca23c1c9644": struct{}{}, - "ab91a82d14cfc6125c1a40e6920a4cd3fcc3e2f9940347175b06367845a9bcbc": struct{}{}, - "edee29f882543b956620b26d0ee0e7e950399b1c4222f5de05e06425b4c995e9": struct{}{}, - "e8760e8aa7203bd218dfdb2b43d94e023f22dada8dec6273e9ca672f42c1f70e": struct{}{}, - "79f06f8fde333461739f220090a23cb2a79f6d714bee100d0e4b4af249294619": struct{}{}, - "befa156f0283eb0062beb9b86e16a413e1cf8c5135e5518d5c4fa321ce0c7b6b": struct{}{}, - "a3ca38ef0e8554b39ce6fd34b011f9aa197cda1f17e2b08b1816142c4bc67199": struct{}{}, - "715dc8493c36579a5b116995100f635e3572fdf8703e708ef1a08d943b36774e": struct{}{}, - "e9605a74009672a33c5e33a5a26808cb7d20f9519efca25551db7fabe574002e": struct{}{}, - "4138cfbc5d36f31e8ae09ef4044bb88c0c9c6f289a6a1c27b335a99d1d8dc86f": struct{}{}, - "d17108747e922613fe66bf5b0ec450801f513b92e91d7838f0b739df9f68ba98": struct{}{}, - "47e1ba669a30915cdab7927fb539d21b752e9131cea7428242b572e2018c3d46": struct{}{}, - "3bb68de60f56156655a2a70e606892edda3e2f2fc57b482bfe3ef1c5263db5b6": struct{}{}, - "61ea0803f8853523b777d414ace3130cd4d3f92de2cd7ff8695c337d79c2eeee": struct{}{}, - "350a770c0ec9f353e1a5629895f374fdaa299876c3870c03feb60eb4a3769d94": struct{}{}, - "c2afdd38565b01f278bc8a5513cd8354840fec53de25dd45af4d23d6939abe8a": struct{}{}, - "aaf5ad63ac417e5002bdac202e07287cf90f35b1d419464d2c4fc79e508a1e4c": struct{}{}, - "c34045c1a1db8d1b3fca8a692198466952daae07eaf6104b4c87ed3b55b6af1b": struct{}{}, - "f3fe5a51a2be8c6dc715028858fcba82ee021be7687e4f95b45086b8ffb1a23f": struct{}{}, - "43ff792d79f75890d3c5181739783889bf9ef4a9397cc8d2fc0ea543ce5a30f7": struct{}{}, - "cdf30c6b345276278bedc7bcedd9d5582f5b8e0c1dd858f46ef4ea231f92731d": struct{}{}, - "cbc17ed4ed1a11325ad72a09d8176094bf9e6c4577819490f3b62f81084e83c9": struct{}{}, - "0357513deb903a056e74a7e475247fc1ffe31d8be4c1d4a31f58dd47ae484100": struct{}{}, - "6201eb4dccc956cc4fa3a78dca0c2888177ec52efd48f125df214f046eb43138": struct{}{}, - "77646f5a4f3166637627abe998e7a1470fe72d8b430f067dafa86263f1f23f94": struct{}{}, - "32e050dfc844554cadd05e1537d56954d650df2dabb6b61ed20af1150f3e9d66": struct{}{}, - "d16f06fe255d801e0639b8d749b815ea9e788af7b9ceba2b773c1dbc8f080442": struct{}{}, - "47798d12ae31ce5a6d9c9dddec7bc9f2a27fffa424b7f2a154fa0e26690972de": struct{}{}, - "4dc9418555a26aa694ef4c4b4d02137070a537d7786d4b337245a54304e7824e": struct{}{}, - "5094dcad61aca84adae760a3a4ef66bd00284a17a39286132c3b641ddbad87df": struct{}{}, - "da454b02b4700b1f5dc5f9de972a531934ccb666cd00ed832fe6a36ece8b9ec3": struct{}{}, - "566f532d486c947709d3d0e6b7575af8380248db66dada211d58eb00ad585297": struct{}{}, - "180ca01b95f0dfdd36fbb600e51cf6e46c8ef468de56b017847886fefaf7b6f9": struct{}{}, - "a6e13bc9170fd9daf042c9358435394f00d78518c2c39488969d64494fd1bbf6": struct{}{}, - "c2c3b68b48832afd9a4dbdd474c1b6c81c8baecdb71446f9947dac72dd0fe93d": struct{}{}, - "72d0166b5707d129dc321e56692fe454c034552ee9e2b38f5a7f1c1306a632ea": struct{}{}, - "2692d41ba9f29ac9dc12495c3caffa47b71d5d2f8c5fd899c884a6de67922e2c": struct{}{}, - "58972659401cbee9ac0c6f92382c5cabc26bc5ea44ab8902b68c4363672fafb9": struct{}{}, - "73d1b1b1bc1dabfb97f216d897b7968e44b06457920f00f2dc6c1ed3be25ad4c": struct{}{}, - "3a120dc1589bb2f0cb023b28ec75328be3fc5333ef0707285b31f47ad268dfd3": struct{}{}, - "0a2946b1359c3fc3c48f0f21f1c006863d6debc9ca385a64a4121b97974c5764": struct{}{}, - "379224b71a3cf4a8298f97090802a32132b92440b1f229d34d82ac2f66a6e80b": struct{}{}, - "b41e6560a4467aa44f85d4dc8eebe1db4a65fa8eac469ae9198d022c960ea295": struct{}{}, - "d17f25ecfbcc7857f7bebea469308be0b2580943e96d13a3ad98a13675c4bfc2": struct{}{}, - "01d027f99c658a86308f5ce4a721933a123462294983951e262d74e5b2dab989": struct{}{}, - "d6e21286621a8586f7e54720ab5a39c93acc2f4b8fba7a16ec1c24d69a08c613": struct{}{}, - "ad95d5fa651ba86d8923fe1238d24a4f1988a752acfe426ac72ac7c04471bc17": struct{}{}, - "a320480f534776bddb5cdb54b1e93d210a3c7d199e80a23c1b2178497b184c76": struct{}{}, - "b942714f0ecb8e42f27b6baefd673c5dcb1676928ed0482a6d84b7a6b44e3dc4": struct{}{}, - "30a989afc82c0a21139573591de4e5ff37994f7d1506a9acf2b5997005c2649f": struct{}{}, - "018fa96a44715c90bf93be148069cb28dd45d398f2cc75aa1565311f6e55d174": struct{}{}, - "48fe20b1438396c895f55de0692dfd3dc7dba7da2eeae343e4b3f5dfc6050f78": struct{}{}, - "1c24333ca1c6792a65d6e5b1e0b7fbee330dbfd935cfebdcb7fdd332182169e5": struct{}{}, - "4837dd751d693d10e4b30e7975e70caf181680fde04e42c49087a7744005cea4": struct{}{}, - "44e8dd36c54ce8a436e35ca82398e2f5d93f951c2e4eee39348c1fc8a56b7267": struct{}{}, - "937377f056160fc4b15e0b770c67136a5f03c15205b4d3bf918268fefa2c6d0a": struct{}{}, - "b988b5837c24ad1987f31266e0246b0fdaaf7948714cfa2a9f7757c52977ff14": struct{}{}, - "018cc3e8b4c3be5cc91f05ebb741aecc787e03aeb45a41fc9f7c8114dff5f542": struct{}{}, - "cd08c4c4316df20d9c30450fe776dcde4810029e641cde526c5bbffec1f770a3": struct{}{}, - "d3f40079ab5385e2d3dfbcf07d4b088853feda1e6496c17c287a88ef02fecd68": struct{}{}, - "85fc17f7069acd39a5c636cd0a6530651096128da447959f5e250824857dc559": struct{}{}, - "bd938c688f49b77c7fc537c6b9222e2c97ebddd63076b87f2feaec66fb9c05d0": struct{}{}, - "08bb42a30ca57f3455e11ac74fd30eae1196b25a9e7d634bfc5704d542ab7d20": struct{}{}, - "9e69e7e29351ad837503c44a5971edebc9b7e6d8601c89c284b1b59bf37afa80": struct{}{}, - "85f5e10431f69bc2a14046a13aabaefc660103b6de7a84f75c4b96181d03f0b5": struct{}{}, - "1cfafff6d51a03662b85b93dc3417f51687034ba9a46682f5328257eff7133ed": struct{}{}, - "1c467339c960a107ab9ee5ff63b38009797416c964f988b224710773cc38796a": struct{}{}, - "d8b01b2435a39d18015cabdf20afba9ca275412f2eb117f80fb7989c4101c029": struct{}{}, - "1366fac71146793a85e98a88ad9bb15adb9aac31da8ac6d73e67059bd5a467a9": struct{}{}, - "a0811a47b1e299c8152b2455cbb53b6976148853ee35ef7f6f427277925b96bc": struct{}{}, - "8745f86640ad2ed6b8a2fd428845df3daa1cfcdba9ef74c0cfaa57cacec34f5b": struct{}{}, - "8ee9938e4b960a50540f1ca9299facc5a5f342d0848b402c322fd14592e4bc32": struct{}{}, - "f76b61b962db075bb76ad6f3fab10f7bd546f92f1b89f18c513d4122575c18ac": struct{}{}, - "b9043c16af362f30807deaf692025d61c3c803bba1efc43fe2f63dc8b507418d": struct{}{}, - "87a70ca00cb98b73aca8c97442d81f1faf7cb5299ea20a2fb0465fd9fa7e8772": struct{}{}, - "e81dfe69841ad2f7b5790b63e998f0febaf3b29acd732881975130761b98e2c7": struct{}{}, - "f6fc84c9f21c24907d6bee6eec38cabab5fa9a7be8c4a7827fe9e56f245bd2d5": struct{}{}, - "06a1d8bf7fc210de23d9540ab35744ae9db310afa0afbabe9529333e175a850f": struct{}{}, - "ac13a8b534df4a28d0fbbbb64cff909b5b1e8faddb4e5ea57bb8a642ce998f66": struct{}{}, - "9e88b067408182d0bd525120e9ba2607e19b0dfa17b48582d2d405fc52dc4a1a": struct{}{}, - "1134e4f4a13b06aeaa8471f934589a3e180bf3fe432e32e1f9454e76a90077e7": struct{}{}, - "56e1239485139d3d0564c10a8cb8e7dbb6c75b2d744d49073d07c4208f7b7941": struct{}{}, - "8c3bc97b36d17f130bd5dfef659955c2ac02148e5ac4bbea87cddaa8f76405ee": struct{}{}, - "0323eeb8b2e7c8900df1fadcf87b06f40d9c6d1f57aeb95d30f7d6e3abc7f2ec": struct{}{}, - "f6952d6eef555ddd87aca66e56b91530222d6e318414816f3ba7cf5bf694bf0f": struct{}{}, - "0d05845a31cfa01390dee7a91fb7c8ee2590fcee7875fa9a80583e307f10e4f7": struct{}{}, - "9af15b336e6a9619928537df30b2e6a2376569fcf9d7e773eccede65606529a0": struct{}{}, - "5a020aa82f17b6d1d929cead88e4676a97be509ec03774eea6c16da33eacba62": struct{}{}, - "5030c5bd002de8713fef5daebd597620f5e8bcea31c603dccdfcdf502a57cc60": struct{}{}, - "1f6f699c758927baadc825ad46bf7476d650b346a294b2bf9d457887b57f5153": struct{}{}, - "648b4a1fdb300546e35945195b516665bf9e45b69e334712e35f3ea0cf8cd79d": struct{}{}, - "6ccffa4977d4246ed9de8ad27693e9802b50d105aae43730d06f1dc840ca6df5": struct{}{}, - "a58e03220bf0a56686e193d23b60914a4b904184624a00a5608836d0400cd753": struct{}{}, - "e24df920078c3dd4e7e8d2442f00e5c9ab2a231bb3918d65cc50906e49ecaef4": struct{}{}, - "1785cfc3bc6ac7738e8b38cdccd1af12563c2b9070e07af336a1bf8c0f772b6a": struct{}{}, - "d22f115abd6b44f9985037cfb39d6e1ab2db87a470d3e1081196562d420b8e41": struct{}{}, - "934a11e602682e862b4b4d1f881ae8da1e097e3f6964bd780e5bb1fb798a6fdd": struct{}{}, - "cda3c373f19f1c5114f0f6ece4762cab9c78dd494191d19252e1ef5f4342a146": struct{}{}, - "68487dc295052aa79c530e283ce698b8c6bb1b42ff0944252e1910dbecdc5425": struct{}{}, - "f9860d4c1fe72330e750bb2a1310337fc35022f31a0c625fd792930f4dc3e00c": struct{}{}, - "234d6d31ecb9d31204f97fa13cf7c5af2dd45a1bdb862311e3ac259e98e8f796": struct{}{}, - "8879addeb18c6b29e8b89960f3310224792fbe08e955aabc3956bd4f29f1bc72": struct{}{}, - "dd130a849d7b29e5541b05d2f7f86a4acd4f1ec598c1c9438783f56bc4f0ff80": struct{}{}, - "a657fa855740a63d55448b06afbc256bcecb8ba1bcb219fea8def369b4d0ef29": struct{}{}, - "198becaf9c45016fec5d9bcd2e8d748de6b44a26cd4cc35ea72b670e665dff79": struct{}{}, - "67731ff58137eb39713ae30eba33c54c8c1d5418e081428ca815e4e733d64f6d": struct{}{}, - "118ee6549ea26347dafb34e76207e108ce29bca7c9ecf4b0b32973002e627990": struct{}{}, - "e73b48f750be953532c1d1757b5ba081e8a6b0408ea8b4e65ef1e65631a7da06": struct{}{}, - "79ea9358181884637163fbd61c06b22150f429636bdded87bdc70067db2396a4": struct{}{}, - "4ca6f6d5a544bf57c323657ad33aae1a019c775518cf4414beedb86962aea7c1": struct{}{}, - "45d7c42cb65265ae97c298d439c18e5293d6615a66627b382d04491113afd794": struct{}{}, - "c12ea8107a1210574a31394fe05bebdb2cd1fb7f491939fd743d3f6f09aee721": struct{}{}, - "94c41436f038313595af6897442afcc442ebec2c31803cb8fb88765258a52f85": struct{}{}, - "2481a63c85a62cf889d2b149f1a52e985a9341750173fe01eff50cc27b5941b5": struct{}{}, - "a78ae4b4d2e8c1e7b6ef20f163cc194f5979d7db1a9b4b9806bffc5ee959b130": struct{}{}, - "eee9e0172bbf5a80265c631143abb58c37f3cb91f91a099ce70246045e818328": struct{}{}, - "ff48e511e1638fc379cb75de1c28fe2016051b167f9aa8cac3dd86c6f4787539": struct{}{}, - "730551f5bad4af0604f661e7f8b82e6a6c364ca19ce140166333d86cdc814ca6": struct{}{}, - "ecd71870d1963316a97e3ac3408c9835ad8cf0f3c1bc703527c30265534f75ae": struct{}{}, - "431ba1c6936744f659289a8ff5292f004a51ba6a96dcecc65ef8f0783018dd5f": struct{}{}, - "d28eb5f7b4ab81491e6617a7fe0d9bac14ab6d12ed69f479ac6a93c29bd3df0d": struct{}{}, - "8be2978e343799a3747a31957706009d207d6a3de7978e63f00b2a41cc99355f": struct{}{}, - "a6c70cf4d6fbc0678b5e040b85e389880eeaa8d6dd855cd8a6b3721cdc3d69f0": struct{}{}, - "5292f27501324087b9dcd65408163e58c921009fc8a85c1b7782823a934cf6c7": struct{}{}, - "b2808c947ea04c193e096f4923d13599ab96317dee1e33c8705bfc863f354903": struct{}{}, - "2c8c4f00dee0bb25cb5cda2ef243456e7fa9a11dbb37a24b159d3c35177a0393": struct{}{}, - "635450503029fc2484f1d7eb80da8e25bdc1770e1dd14710c592c8929ba37ee9": struct{}{}, - "d7ae9de750a5640adf6e724d72643767faa73bca2941781dae9d276ff2d4b4ca": struct{}{}, - "2daf0e6c79009f9234ed9baa5bb930898e2847810617e118518d88e4d3140a2e": struct{}{}, - "a3441069e422a14127a80e5f3323cffb5972097b39353462337c951006879bb9": struct{}{}, - "92925488b28ab12584ac8fcaa8a27a0f497b2c62940c8f4fbc8ef19ebc87c43e": struct{}{}, - "d334cbac4bb3f242dfe5e11a656333345ce05c8409bc347be201f2447e14b480": struct{}{}, - "d1a4be19b3cfc283dc5e90b64b4cc6349da616d456cd0a993e3d378845bdbb53": struct{}{}, - "6cb09fe72a3471a776f9dbb8509fa5befe73e878f23dd71be79d24ec90c1b9db": struct{}{}, - "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e": struct{}{}, - "57c7bb1bb755cdf7b93f1c63a35c8e873bec990aaa82117ed5e520ca62dc6340": struct{}{}, - "e501c35bdff69476d8bf5b4234b27d1fc70d566353a054d2bafb3f9e0bfab66b": struct{}{}, - "e0a32e5cf709a367548ca63aec27ab874a3a6b8ca15da31ec4957a12bb066655": struct{}{}, - "776af9401679287d8d332e26e99062919d58a135f4ef5ba85d4ec981b50f58ae": struct{}{}, - "78a7edfb3adb263c381f942170ee5813160d4017f13c615a6c067473ecca439a": struct{}{}, - "8f0e2f76e22b43e2855189877e7dc1e1e7d98c226c95db247cd1d547928334a9": struct{}{}, - "d2cb49216759f2d2666a702518a34da8d2a53a6fa042992d517bec6808c833dd": struct{}{}, - "6627835f988e2c5e50533d491163072d3f4f41f5c8b04630150debb3722ca2dd": struct{}{}, - "fb12bb6e5802257873a301ccdfed71411543a6288cd453d5a5524821e970272f": struct{}{}, - "f3c2ce176290b0c384cb4881eb714f2db58f630c33863d91c9bedf58d36007db": struct{}{}, - "07a0a3bd1783de63016f550da1c0e47c8225fea4b0fa040fe058af60c2d858c7": struct{}{}, - "318aee3fed8c9d040d35a7fc1fa776fb31303833aa2de885354ddf3d44d8fb69": struct{}{}, - "52c8e02987ccb1b0eef9c7c966aebf03c92a159aafda37fbcf03f4838dbcd48d": struct{}{}, - "fd2ef6af41aa559dbb48b68b8b14708850efaa1a1a2f061ad6da677601628897": struct{}{}, - "c0c4a69b17a7955ac230bfc8db4a123eaa956ccf3c0022e68b8d4e2f5b699d1f": struct{}{}, - "80b1509ea37643250da601f2d24fe9b236b76fd3a3d7d18498d760cdd38813b2": struct{}{}, - "72ab994fa2eb426c051ef59cad617750bfe06d7cf6311285ff79c19c32afd236": struct{}{}, - "c604dabc9591840978d2585238a908898b22e3337b207dc722ae208eb204c291": struct{}{}, - "90cc33a41b541af2c1964e3e10a46088cbdedf63031efaa35d588a698c91193f": struct{}{}, - "3e6078c8113a01138e0d1128f5763fede882e25ffae19e8012240e1084c174a2": struct{}{}, - "2aaab795b3836904f82efc6ca2285d927aed75206214e1da383418eb90c9052f": struct{}{}, - "c68943be5eeeb3092a614aa6119bcb560217ed1b4ec44b9c99d6e2766e524049": struct{}{}, - "80f189984e5ca70287d13342f6daa0db45cba3c131c4e46dc81360f3a4c4f690": struct{}{}, - "61be55a8e2f6b4e172338bddf184d6dbee29c98853e0a0485ecee7f27b9af0b4": struct{}{}, - "01df2cc6fa6f8098a687eb5c5ad5f4186bffcd18f75f87fba9735daede10108c": struct{}{}, - "930a68a51a2db950f58fd3b0b5f1d76f56afaa16e12a418d71ca6c25f2390424": struct{}{}, - "de0de82ac6a33803004cdc6d74a8a99cd379a5e05cd9e8aa164c456d1b1b6158": struct{}{}, - "b82e723b461323374c1eec211e066dc7a1c2130fe36c906e4bec5cc45109c248": struct{}{}, - "828febb7609afd5e728ec843df2d11f0b8cd2167d4af2af02eb354f8503850c2": struct{}{}, - "91e9240f415223982edc345532630710e94a7f52cd5f48f5ee1afc555078f0ab": struct{}{}, - "6510126aae54bb1a39c360955a0fc67b049b60100763aba7827a389a90d10da6": struct{}{}, - "a59597c3cb0c1b5c9ae2e720d40ad44eab8a6656652de3cafab6a009fdb9a4a5": struct{}{}, - "1a79668eac4051a9128b81c116007d1b41ce17828d7722afc9746699f4e817b8": struct{}{}, - "80d71140f751394c4712bf46d6b65abfa0184a35553dc2cb1b41546fbe2afa1a": struct{}{}, - "53ddafee31895d3f881928b0f32b7ce540bae7932dcbf0eb610ec068892856f2": struct{}{}, - "bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721": struct{}{}, - "2ea0d25e51920dc0ba247fcda7f514f3e8ad6234e704277036df405af860ed6c": struct{}{}, - "195f33b50fa62ef2bd3d89227ecc1219419e3b907579718541f22cd1b1f1f5e4": struct{}{}, - "95d9daee3b0998effd79f11fe4509bc010931002ee4e2ee0b736de1f8a6ae7f7": struct{}{}, - "3fd30542fe3f61b14bd4a4b2dc0b6fb30fa6f63ebce52dd1778aaa8c4dc02cff": struct{}{}, - "267eed33dc459584a996e2cb0613d1d5cfa3c64ad6d97558f477ff74a2e5f71e": struct{}{}, - "a2a52f3eba927ee4e451df164d0c2e0e42bef8c04713d493140f50620696a789": struct{}{}, - "efaaeaef9944477b228400456ec2952311ee23d548bc649fbbfac6c97ce9d956": struct{}{}, - "c1f330d0aff31c1c87403f1e4347bcc21aff7c179908723535f2b31723702525": struct{}{}, - "41b4d5e85655f1b35e001a70684482f9cb760385974cbb7bc401531079c9f4a1": struct{}{}, - "14272bd361c9990628cab4ecf17f28120b2a40655560992b18c7a4a5c3372417": struct{}{}, - "5be0888bbe2087f962fee5748d9cf52e37e4c6a24af79675ff7e1ca0a1b12739": struct{}{}, - "cbbcacaf0d582e760874a68b44c572218102a1d24fa262b00dc7f090c7257302": struct{}{}, - "b9a365c4d2675d5fd8ec77672a2bc29474184c3805b550b987b630c83c200c9c": struct{}{}, - "d9035738cefcf6ffe9a48bd5d94d73822d9b2a2879596681e6e625a77f6dca83": struct{}{}, - "a051e62c16c385ab646c4161a67a338bc8e7efdff2c797e53fcdb72b9fc2b4d0": struct{}{}, - "31651957b57dbcaac543a97f7bb0a8adfb5d21b676a63a43ddb1c96cc30446cc": struct{}{}, - "118011bef6c697f7107cc0d788664a0f8c7d0316ce8d17673634155f5ecdba39": struct{}{}, - "a0ec927b1044a5e945fbd9cf4370b4be1bc1ebc93bca51bc37f02a8814196bde": struct{}{}, - "8417659f46b5b43b2bb9396f794ab19728546f699babdb914d97bc5adcb9f528": struct{}{}, - "4f31fa50e5bd5ff45684e560fc24aeee527a43739ab611c49c51098a33e2b469": struct{}{}, - "f2d81a260dea8a100dd517984e53c56a7523d96942a834b9cdc249bd4e8c7aa9": struct{}{}, - "d7697570462f7562b83e81258de0f1e41832e98072e44c36ec8efec46786e24e": struct{}{}, - "07672b212369f416dc3505dcba04f379ff39de0d0db31536e57dae76486b5000": struct{}{}, - "88b5141a508d2efb1a74df9b8a9d62745e779e72467932395f965f14b3aa1c6b": struct{}{}, - "ab36e84344729d2bc762ee67e55bb3ceb69fdcfd7585a792b220c2a7cd1e6c0d": struct{}{}, - "78fe3f05768ff3a95c74ffafe366cc3474022d925ad5593af733bf8ac1ab0de6": struct{}{}, - "fe2592b42a727e977f055947385b709cc82b16b9a87f88c6abf3900d65d0cdc3": struct{}{}, - "a7a39b72f29718e653e73503210fbb597057b7a1c77d1fe321a1afcff041d4e1": struct{}{}, - "58f87e265d97de141076d2e770459f51f20b32112139a0f7dc27b7d7803465db": struct{}{}, - "69f7f7a7f8bca9970fa6f9c0b8dad06901d3ef23fd599d3213aa5eee5621c3e3": struct{}{}, - "4404f5b7a8836863b4bf63b9958024b6fc9074f0f0480ed8115e4168760e505e": struct{}{}, - "b01774366b2bea90002f784f533085eb3dd6eeb2fc85cbc778c76ef96769715e": struct{}{}, - "d233633d9524e84c71d6fe45eb3836f8919148e4a5fc2234cc9e6494ec0f11c2": struct{}{}, - "bc12b8f31ccdb4f7e89f96e080a823c23bccc8f2969cfeaa5667b64cc1dd8a76": struct{}{}, - "0309714f4cc5bdb8d02f2247faf674e3d4456cd2c60a86d7c50d1a7d94bbab60": struct{}{}, - "b95cbcfe0cd25ed09eef361239fee7d50a3f31545465ca2421f4e791be2624b6": struct{}{}, - "dfef5e53f9848472560a3e680a310d097ecc75919740646df38d31cab7aa07ac": struct{}{}, - "c39bc737faf3a8006e018686cbbaa090076928030278dac2be1865a4f281322e": struct{}{}, - "c49b876c05fd376922883ceaf5b7e7871ae4ae9f24e79958f3fc70efe114627d": struct{}{}, - "c0a4942143e872cd1ae29fc759e04526de2e909ac1732734d38550a29c2e2516": struct{}{}, - "0f4168490e38b8447e11ba4bd656aa11b925bd22af30bac464bc153fdb608501": struct{}{}, - "770e607624d689265ca6c44884d0807d9b054d23c473c106c72be9de08b7376c": struct{}{}, - "1934d8bfaed57f11f8af9175540fce4b2ff0492e6b258e73ae2f16bc8c339d4c": struct{}{}, - "336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e": struct{}{}, - "fbfb386efea67e816f2dda0a8c94a98eb203757aebb3f55f183755a192d44467": struct{}{}, - "b68823eded0bc9c7f3317d601ac24f6ac563895cee8e5a2bd2ca475906fe2615": struct{}{}, - "eaf16bc07968e013f3f94ab1342472434a39fc3475f11cf341a6c3965974f8e9": struct{}{}, - "7aee5b5dbb9e781589946f9087eb09e4a880d57e5d52441daaf7d49f9c2e629f": struct{}{}, - "5022472b2831c66a63bf99a7261874b55b088edf89913009cb64e889cc9c7d21": struct{}{}, - "e512a05583448f44790783f986b1f36925c8cfc42338ca0e1caa637755bd15ae": struct{}{}, - "dbba921ce20510210ee7db98e000ee32d0e17f5e93105707acc498d6f8ef5a7e": struct{}{}, - "2a057642222a878bc360f52f8e1f0dfd2af93196f123269397423155a4ec4884": struct{}{}, - "3393b41eeeea1c11dc97c34242fca5f4c9fbf6239cf2b6c4f1469e1571d7cc28": struct{}{}, - "ed5465b9220df9ce176d0bf30d6a317729bd9d37e4ae1cc015cb24c99af1df49": struct{}{}, - "0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e": struct{}{}, - "65c3f75641b22925c737ca657b126cd68c39e423349d43031cf9a3b9a18cee1f": struct{}{}, - "cc5e7b4077ac5ed584ee17ba03e5cbb9acce55dba9f531789b5786ea9b75e948": struct{}{}, - "2e62c8d602ce51c37dd70f3dd64a5cefe9758a888a7dc1ceeb27f8291db67e74": struct{}{}, - "56584949957de203b0b6cb347b57e0553cb31115cc3049e9839b7fb944465a9b": struct{}{}, - "73740548d223792c35f0e1df5a5c543221786947281313a06fcf872af283816e": struct{}{}, - "8dd9ed10798ded82a2554cee40bac79449a6aaa87eb0cc02af94cf45b8d02fcc": struct{}{}, - "26c1d062cc7a60c3d55fe2df1797d9e361054a08ddb979a09093549ca157ca86": struct{}{}, - "ff1b4a27562d8ffc821b4d7368818ad7c759cfc2068b7adf0d2712315d67359a": struct{}{}, - "8b591cc313c07621780dba89d4e698566cff82ccac2667483ac2fccd7344e330": struct{}{}, - "f08f448a5e7a9dc3619bb7c129f6a7d5fc6af002cea17ad71dfdc1c68f4d4e0e": struct{}{}, - "12e90b8e74f20fc0a7274cff9fcbae14592db12292757f1ea0d7503d30799fd2": struct{}{}, - "389ec5f1112d5e956ad212f67fdd03654d1a39e914bbb31ea59550e040be8f6a": struct{}{}, - "06fca49e873e311ce7dac2de09b1f01193b94248daff30b9ca7a1ef7c6dfc471": struct{}{}, - "0cab1c9617404faf2b24e221e189ca5945813e14d3f766345b09ca13bbe28ffc": struct{}{}, - "ec87d0e0735ce8d20ddf792630a066ab99a7e3370281adc2eba9c3192033ff7f": struct{}{}, - "0508a634445d401652d06013cdcc95183ab78c58e406cfec6dc58a395958ef2b": struct{}{}, - "b7395530e4ed55cb6a5376ee8481f603d18679ce4c53b87b1146a9edc11fce1e": struct{}{}, - "c1d35cc3471fe509203d65b3e7a53fc82337ac9ae2c797b836621f706fe37df8": struct{}{}, - "8548d75fcb3a2a275a1ba868a36f6a26e528f16b07aa7d8006616ad090a9b349": struct{}{}, - "6f9edcd3408cbda14a837e6a44fc5b7f64ccc9a2477c1498fcb13c777ffb9605": struct{}{}, - "9409d68826fdb81945b7ffc4c8821e20da61464be2b738b910c834bd06eba39b": struct{}{}, - "096e8bbb8f1e3eeeebda20037edf9eef0b2631ba37d46394e99d968784bdc1fc": struct{}{}, - "4da860a59a46e88c2b71a71baf7a2e2d0b2b42c583ffb04e8f0f671dfd0b3493": struct{}{}, - "fc66f021c67d064c1490a12b5a4d4d2f5167ca692a16ca12f1f3a4cda29a6fa9": struct{}{}, - "bfbe56885d33f05b961223170f34a0af243bd62f2d0d9d3dfdf4e3fa4058c968": struct{}{}, - "d34a569ab7aaa54dacd715ae64953455d86b768846cd0085ef4e9e7471489b7b": struct{}{}, - "fd62862b6dc213bee77c2badd6311528253c6cb3107e03c16051aa15584eca1c": struct{}{}, - "67a4f45f0d1d9bc606486fc42dc4941668e71d34ee500735fe9b7ea4625c687c": struct{}{}, - "6ae95ca5f8098007fc09f75761e85d4d1f6c9dc16e3f1679de4196127b0387b7": struct{}{}, - "09053dbc39a10cab79b0c0d5b1bb2793cad99291245ea4c0fcb953cffb7bb644": struct{}{}, - "5c8f52ca28966103ff0aad98160bc8e978c9ca0285a2043a521481d11ed17506": struct{}{}, - "7aa4f5c396342cbbeadca3991899e7a939432684b094d9c559512119c1db1f03": struct{}{}, - "9706987d633e05a0d2931c21108b37e8c790570048ac47c48900314bb325d606": struct{}{}, - "c507a68f3093e885765257ed3f176c757aaf62bb4cbc2ef94b2e7da3406d9676": struct{}{}, - "60e77101e76950a9b1830fa107fd2f8fc545255b3e0f14b6a7797cf9ee005f07": struct{}{}, - "4dcab0d82ccb503fea0f6f7a4d63440981cf2755d9fba55733489e8c8091fdf5": struct{}{}, - "2e0b8d61fa2a6959d254b6ff5d0fb512249329097336a35568089933b49abdde": struct{}{}, - "d64debd942d7dc26a851231583b1721f43ea936fa41932b6dad7556e5f8cd24a": struct{}{}, - "98d9d59a907519c80fc08e9ba0e4505f152c1bb8dbe94538e11ad19f5ccdbfe0": struct{}{}, - "f85b43067a280d4cc40f89cb78d8efff6e908727bc3cb43a9ae72b400d2eecc5": struct{}{}, - "059a00192592d5444bc0caad7203f98b506332e2cf7abb35d684ea9bf7c18f08": struct{}{}, - "a459891617d735655dcfed3e37db66fa07f0175866ebf35f9de8ccc59c0840bb": struct{}{}, - "05c5f6ef21bb02be3db6d066c4e822e07ef75b2f85241333a7cba5dec74e2d33": struct{}{}, - "7904791f9bb496a3354bf75641e45feb31e881a00cebcb2508e45bd942a6e430": struct{}{}, - "c6c48631228774bb4a65f0b553aa404a75c56616ab27c7fe37896ab698a38ccf": struct{}{}, - "f6f7e983afc59354c91673d637c22072ec68f710794f899ae7472dff7b7508e4": struct{}{}, - "62708057fa8cb45202db7af23362156934cbc08237057fcfac8d4cad610f5c8e": struct{}{}, - "104f24e15f4dba76498bb256593dc6d95876d881ee2201ff369225b6d51e8b45": struct{}{}, - "4ae19158348463785bb8bbe51890203f49c92b83fa89b83ce09fd0b99d9cee03": struct{}{}, - "d92e06b325d24423dd187a0101924107ace8b367379b12c2b8608ddf843cf435": struct{}{}, - "f83cff8066af378d59395fa91278096b180f836bcc17ea2a2744a847debdfae2": struct{}{}, - "f6056ef7890a99494c34951817c2ed4fd3608a8488ef0ae6f2afac93ed76854e": struct{}{}, - "9f0c989dd3e4c7a6ef5512d6347a9af24b95ee886e9874edbebf2055166c5ed6": struct{}{}, - "9da70176374d9904885d9ac0f269770a68748ecfdff56dbbae7af768d614dbb4": struct{}{}, - "d222a18d6474d0819d8ee5da0c2c524f31ff0903a4e6abc3f7957f84f1ee3f0f": struct{}{}, - "569d7dc1611b50e40d5b898c212f4742e3b7d76996bac5d63739fef589f3ccc0": struct{}{}, - "76b1ee582dbd709cf8e9e34f2f00c6552564e3b7845931a1ac951e961dd3c07f": struct{}{}, - "ae80f3fbe24d41338766aaf64cdf0145f92c0afdc95eb87aa3777d076584e345": struct{}{}, - "3a031b8cd4b8ceba2a889deb50036f17978fca25985cfc03790705f27d615c84": struct{}{}, - "b5676481a6a1818bcb60de8c6859919f5855d44e60ea1c1587e06459b23e36f1": struct{}{}, - "cb29da26a1d1fbf68d1d3bc51e240d249a2ea2077ff9466349001b32d6e72e16": struct{}{}, - "73704b531ac77d446ab4f8aee0a09acef8c67171ba24a1693e2af36ca7cacabf": struct{}{}, - "b1cc53f5f6e8209902436bff13802205f4f0fa16363201e8fb81d7131ead9140": struct{}{}, - "8ed3f6ad685b959ead7022518e1af76cd816f8e8ec7ccdda1ed4018e8f2223f8": struct{}{}, - "fd245a718d0d43fe4b5b017e0ef57dd55e65b8c982766530f4821738044af274": struct{}{}, - "467b1b85a0ee941df09ab955dcb561d51bcae760fbec8d0ec154c05528c9cb51": struct{}{}, - "d86dc6e60b0632e30f2a78abe922c238e913dd5c74f76df6dc935f1ec73a5df6": struct{}{}, - "59d2c86f6f27c1744195cb2ef0bb809fe9b51c7fad22aa3ae9287f1e508705e9": struct{}{}, - "03bdd14ef87c5b28bc15ed0bebd48fa76451f7cce7e50a0131d2d0f151af55f3": struct{}{}, - "044f4b3501cd8e8131d40c057893f4fdff66bf4032ecae159e0c892a28cf6c8e": struct{}{}, - "e5263ae7e98c0b6e2a089017ef0d314d4c613f9377a6025bd73c852c8c25bfe0": struct{}{}, - "1e9cc37678c0112d7a394909256c2a5998a6e57e2b0d82209900e1456e51b7eb": struct{}{}, - "003de168f3110b0c0e619a90777d5b3c768302721e602e9b204f60891cda2bb7": struct{}{}, - "cfdafb0452d56b03eb6146eb9152895431709c96363baddd4e28a47342750c0a": struct{}{}, - "bdb1cc258d6976aafd3a0c3399dfff42a486e63a6948d8bc2e10e7c36fab7611": struct{}{}, - "c8c97cdd252556bc893ccba057aa851597ee6a6786cf71e599c2b5d4e8558fd0": struct{}{}, - "e4873839d496eba54b2b88e8f145551e638b1300056ca3cc09edf22747f5c3ab": struct{}{}, - "1670f2e42fefa5044d59a65349e47c566009488fc57d7b4376dd5787b59e3c57": struct{}{}, - "cd879f74c6ffe6e4ae973145798ca26489fa2a61f7b29ac5fd816c0ad959511f": struct{}{}, - "f95fc9d255314deebeb3e3be8c7a926caa4e2415c6372befaa8fd6f069b7c4a3": struct{}{}, - "bd6e50d182ed790cee1118af6f7aacd6738b6a03ab6a78509cd2f47545466c4b": struct{}{}, - "340d600392818df2413382dc7d8325c360d83ea49a262d31760348484bbc10b5": struct{}{}, - "a68999add325ba9952ddf20a853ab998393e4488b8229c0fb7fc9dbc5be26ae8": struct{}{}, - "8cc131a56ea5986fdf7dae19cba8e0c57991f5ae6d8c226bea1c9b5f32f77cf3": struct{}{}, - "d618fb157a4b382e92fcd4a830c5fa8adef45a809543c524f7904e38f867dac8": struct{}{}, - "42ef841dc27fe0b5aa2d06bd31308bb63a59cdcddcbcddd917248349d22020a3": struct{}{}, - "2f45e2d1724a03cc0f7e6719bcf92d4a1ba2f7f3bea653a089a670c3e1a1a91e": struct{}{}, - "56fe43f748e258de06b4955e2b8978bbc1c28ff9ba53917387d6dca8fb920018": struct{}{}, - "c0f1779c16d26bac92c4e32fe385d4214da39e7bd6f87aa7a4ce2bef8fb669ed": struct{}{}, - "0a4346f806b28b3ce94905c3ac56fcd5ee2337d8613161696aba52eb0c3551cc": struct{}{}, - "c9564c5bb75297ec0dcfcae08a68c2e9e7bb9aae9e8639b0b6d93e02666903f0": struct{}{}, - "619a2ccf126ce837dc7cd3bdb4d7f44280a0ea4f721e5a738f859aa19d52a78f": struct{}{}, - "f3a22c1ce8e0a5a96393703806e4ad0b63031f0782c1b2d87dd175fc46a9d63d": struct{}{}, - "55fbbb9de6c380e13013f7f7621cfafdc939fe87c1b0af1cc425aa18f3744dec": struct{}{}, - "851e43bd44a2c3d30e5f3acadc9240c12d9f1c610dba34761e8c47ba82d1daea": struct{}{}, - "97599235748ff4a0fb249e9a4c8df4188d1bd60cc5874f32e8ce28af8739b865": struct{}{}, - "d84f691fc4981d9db5165238aef2b639a2969f75740236bfeebaf873ce162c67": struct{}{}, - "69ff8042237aeef5ddf1ced10e851bc6104e1ebb15a304061dba43ea10106fa9": struct{}{}, - "0109df3bfbae1a4b6b6de4d96a49b95be66cc1915a621bb6fd576d6b6cca7741": struct{}{}, - "5944ae849448011ca08c3785f1de1a54c8a96d6c23f787f9d962b624edd4151d": struct{}{}, - "eaa2bded32cc585d3f37c5319abe8890ad28a697ed66d5823f10536cc9c0fdb9": struct{}{}, - "d14a7310bdf55b8ffeb7b8462d1f1e3bc6c79a7d80a90e70eae4ef36f223847f": struct{}{}, - "dc845de6967eb622c20902c423f63a869a8770209623f262db949a80f149c1c2": struct{}{}, - "fb9ebf18103e119e819b5ebbd637958db338fe2821156a51e1b691015615e984": struct{}{}, - "ef2266aa5730c71ac49636cfabc87cb705a2371dd9d3aaa8295235244d5574df": struct{}{}, - "b4e6bf0156a0ef1c6427f9b2f82654757c7bce8327a9bf2f6afe83b8a5254067": struct{}{}, - "bbc5e661e106c6dcd8dc6dd186454c2fcba3c710fb4d8e71a60c93eaf077f073": struct{}{}, - "5072f198bd5e3aabc87e7affa1895be59117fb214381e431ba85ae0e26131678": struct{}{}, - "da8575ae360c77ac19d033499678439453e2a06fa2f12e98218cdd0e73908a5f": struct{}{}, - "75fba329cb6ee8907a1f009b69353d8c0e9a8228b25a1095c7976efc1eaa259d": struct{}{}, - "46d86146774497af0aa4959c0777932a52de8b79f6babec58d58a4a0d8d5d68e": struct{}{}, - "c4cf94e75b6067e81d73250448a38c1030abfdd4b801a5b2e02559b2adddcbe9": struct{}{}, - "35f85825b9016fcc7818aca22298dc88a92ba6e4f029366ff8acbb3f823152d4": struct{}{}, - "bb3a12a527b1614ae117b8bcc0ae5ee6076837386e7018173895a2b53e83759d": struct{}{}, - "193d3043e029a9f9fbac4f4aaad437edc2c5837719c5cc5f49939573baf78760": struct{}{}, - "1f296a30cd39c840ae77512edbcf1ba21cddccc2ec4598b1a1273de52aadda37": struct{}{}, - "5d2d3ceb7abe552344276d47d36a8175b7aeb250a9bf0bf00e850cd23ecf2e43": struct{}{}, - "d0c92c764c5647d43d7166efc3e7d350751de063c1388bd2e113fab4865529d3": struct{}{}, - "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918": struct{}{}, - "c73c3a07b644b026251aa655331eee0394bea4e7bb1d5f019bfbcbeeb13e0f83": struct{}{}, - "130229cd36251accf673a6bbd04ae063a6f1f8aa92bda3bec573b679d79f75e5": struct{}{}, - "bc332d001e2c163ab0eafbcf32a3ae0a286151c6da26d8a9d60eca49c9632d8a": struct{}{}, - "1871ee4724dbe67de59cd64935035d6f992de01d6f29417f847e011cba5bd66c": struct{}{}, - "82d3042b941378bc994b8c2596fa69e4370bd2cb5400c4c043c996dc197fda7c": struct{}{}, - "b690cdbe59f14b3be083db03237e04051abd85c4ffe35180723e9cb45cd3a519": struct{}{}, - "e9cee71ab932fde863338d08be4de9dfe39ea049bdafb342ce659ec5450b69ae": struct{}{}, - "7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a": struct{}{}, - "4f278cdddf52263fe21c64c94932f2b2ec316acecd39a7adcc01eb2e6592a678": struct{}{}, - "b186ba1f53366ec267460b1543996b1b05b912d951c36bf6fb6ca0f145923555": struct{}{}, - "35379d9c009e290d702b72abf3f5f9bdbaa7e00a63fc37b5174fc3825c8095e3": struct{}{}, - "87765da6ccf0668238c1d27c35692e11b4cee9c0e5cd5713a3f6e462d48e9730": struct{}{}, - "8bd10698229d26627eb039ac20f4537b62c6687ccb2892590bc7a3691659e892": struct{}{}, - "993b97603778a944ff00c86775aeb4c852a6deae031dbee58206dc3ae1e3242c": struct{}{}, - "332a552909d0574979e23e94f762952d75054893d63e02bcdadf1ed733eb5fe5": struct{}{}, - "468c7b6449dd928f2ddad069fbfdb31f59ce214d67a1422c7610efb886ce6aa7": struct{}{}, - "fe384adb7e67d54e973c65b0174e3de5d8288e483958da561d39c98ddb1cc5d4": struct{}{}, - "3bdcdc469bf1e4961d4b5053b537bbf5209d22e10e6f6a9ad3c5934c91e8d9e9": struct{}{}, - "a3d92ef00ea150ebe63e6010f733567b904c0e5a34e660096913a1f341f793c1": struct{}{}, - "8549b94cd6a9d2e5bc79a8394775334d0cf0030f1a99f610dc0b6fc745da0931": struct{}{}, - "b769a6983b42d565e79bb4f3f534623453f301d39784e57804a649a67ea05327": struct{}{}, - "47944637b32facc94c1c49b32202b63e23b6ea7f225baa1d89868b3ff32d4a1e": struct{}{}, - "888df25ae35772424a560c7152a1de794440e0ea5cfee62828333a456a506e05": struct{}{}, - "4c6eb87b502e3e019acbd4b1e579bd1566104abc0914f5186df63e4833c993c2": struct{}{}, - "6fef2d9315ca9bc36eda4039f9f2f2d02653816c55cd59de5b18bdf00282b209": struct{}{}, - "39fbe87afb3461d7a2272a229ce4338dc1300cecea2ab08ac10a3e59c947989a": struct{}{}, - "1938caca1799f070c8e34224c3b0425271db986df387612175d5234d88dc5729": struct{}{}, - "2b4a59fec3e7bcc78e408b90f8f66c6ed43373b574e4213cdb9998817abcab68": struct{}{}, - "0a5d17d3b19f82f8340d3977609aa9e86b4ad8b9bd71bd9eced9271f1d5b2e4a": struct{}{}, - "e9e326d5f3b4741fe5967b5f9f3997e6275331ba18567ef9ef9e0e3a00e78371": struct{}{}, - "4625fd63b0e96fc0d656ae7381605e48d4a0f63a319fc743adf22688613883c7": struct{}{}, - "cde48537ca2c28084ff560826d0e6388b7c57a51497a6cb56f397289e52ff41b": struct{}{}, - "a8618937133ce1a3c3dc28f9cd7e1e7fb3dee4d57ccc708c721a1952465f5d7f": struct{}{}, - "12a667c9be6ff8f665d2e9163f3b96422e8025ef238817ec8101c17a23f1a4c3": struct{}{}, - "dd5a13788c80cf1003e233f8ffdd541705121742e922aee2d2214ba877bc3551": struct{}{}, - "44b3c5003d93c5f1623bf53e26dabe4253e8ab4a42311e37c337e89f8f891274": struct{}{}, - "11cf8376a1575fdcbe1c9f16d01460a2d98623c91c44d82dc72da03379849f0e": struct{}{}, - "d4df2ec253dc95b3455b77b158630202b15e5e509c6bd341592b3019c62fbf90": struct{}{}, - "5e10aaf3bc689abcf8e5f5cf133cf077032a90bf3d99c92e6538ead62c73dcae": struct{}{}, - "a2d3fcd68f5fdb226432f256a4cde76a123d24a1b81aea49339d2d6147375cff": struct{}{}, - "196c562cb5e78e8cb10c66466868ca8b40ad7b029d8ecef850b717cebe055eb5": struct{}{}, - "b47057f8109a13528099ae0669aa49c0354f11e0fdd9eafb19ecf6fb38e5ebcd": struct{}{}, - "49460b7bbbd3aad3f2cba09864f5e8b01a220ea8c077e9fa996de367e7984af0": struct{}{}, - "7aab06cd90b6efabfbc581922a4ad2ddfba2a20a0be4120eb5d0e5e72a59df66": struct{}{}, - "f6804460f509a765b8ad07dca8c787ec984109088953153b8540cc1bb8a1c432": struct{}{}, - "b6ad34b0b6b7e38f878a513b3f7927ebeb4cffb01aeb6d9fd9f9ad67fbc76517": struct{}{}, - "44fef9ae98127511825d5cb74157007b7f53d2b6f706411eab1932ef5833d6dd": struct{}{}, - "cea6e08605496fcf871946f7ee8d5f26bfe32eeec8fdfd614d152d1bd3e6391c": struct{}{}, - "34a533dfd7273a9bb03656b32956a2b1d08dc62533b11c813b28d0ef23ec8e67": struct{}{}, - "ab80177c2fa8dfa42997fecef5373fc82ca987f9640e7dcbb572ffe11737c796": struct{}{}, - "94dc354ccaa14e47b774467966de2b443e428ae8ed4849d4e423fb387463d9a0": struct{}{}, - "ab73d590e57e04c18fbe4c85a0983028927e8ba7b4cf74b0d076c52e979d4ef2": struct{}{}, - "e49034cf4bc9e985c70adf434617679084e33541f726c1561825edba7755fb87": struct{}{}, - "4d1608c849f1a4968565d5019e500b9c3541f654c33ac9d096b610d27229499c": struct{}{}, - "a48fdf8507eb98e7b05aa143a93cbad2c6a5db3e18cfd3a51b9dccb7bf364108": struct{}{}, - "b2471d941bf888366cf43813752ea2ebfef08254773cf116187cdd6d0463a50a": struct{}{}, - "d8e66c7c5692c1e03b2cea2791af8dd611d9a938b29fca1ee6326caea0a84352": struct{}{}, - "37797910f477feb99433121aabaac06bf771eaf9eba5ec5379fa8e693b3d3117": struct{}{}, - "4d1e58c90b3b94bcad9848eccacd6d2a8c9fbc5ca913304bba5cdeab36feefa3": struct{}{}, - "2926a2731f4b312c08982cacf8061eb14bf65c1a87cc5d70e864e079c6220731": struct{}{}, - "968e2d5b08687bf42997461cbdef6c844eabbf04f440cee888c95b864c2a4bcc": struct{}{}, - "c0a63c0607837069c0ad00c8782ce279540c6b06858495480ebfc73b43d8a4eb": struct{}{}, - "9ae315a94e428a7ee3b5e48adae6541965d93b86acf10ffa1c45b93b6fe577b4": struct{}{}, - "0a4992ea442b53e3dca861deac09a8d4987004a8483079b12861080ea4aa1b52": struct{}{}, - "0feae16d55365acf07fe9f909834361ba6ee606854746539230bdc84a6a24cee": struct{}{}, - "cbc471ca60092bbe6c631f02bc57399b2da3d1ae491a55a1aa960a4b2e0ac7e8": struct{}{}, - "1f3ce40415a2081fa3eee75fc39fff8e56c22270d1a978a7249b592dcebd20b4": struct{}{}, - "a3686065149555674b1b7a7d39e00f15d2f831a62b6a33d12f3a14b87c8d0c88": struct{}{}, - "f41f2c785e15ca0c3fa8776852d7dec9166e163011bf41d9f40ad67b8e93cc5f": struct{}{}, - "054da1e8bc1cb20b4504d603ca6154d353cedb698909503733343bb3f22161c1": struct{}{}, - "a54e71f0e17f5aaf7946e66ab42cf3b1fd4e61d60581736c9f0eb1c3f794eb7c": struct{}{}, - "88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589": struct{}{}, - "b9bfd32099465cb6f8a446e4ec1205fdc9143bb6f7d91d89fc903d9e2364cf28": struct{}{}, - "cf724afc5051ed01b0bfe0b6b8c9f518d7ff2006419227aeb822f561d14d7c24": struct{}{}, - "f7237929cde295ea08104ba164db86c314c26eaefbb3f5018b98325dfd14e73a": struct{}{}, - "348d77e943a990e64b08bd3bafc7c1b3fde497e92670f78cd8e9eb27529706f2": struct{}{}, - "d426ad2be55b9ef24dd8e4b446c28a2f969c761d50d8bd08836bdb9036c2dcee": struct{}{}, - "54e71340c86515e247936b1ef45f6abb1cb7f1fc1bec859c4ef789cd00aae56e": struct{}{}, - "6eccae6877cf656aa30e3dca5cd966b41ea72f7108d2b76e39753222c431961f": struct{}{}, - "1349d5e18426092e5708ce2109ab6981dae37f0f795c79206796a5e41552eedc": struct{}{}, - "b82c52985932456714a4cda67432a05795cbcf94b304c541773ec6ae9bf126f0": struct{}{}, - "d7b8988f8fd2edbd7c5a63ff60009a39f9cc9546f2ca1a5e8eafea8e2f2fa97b": struct{}{}, - "33f7aa34db4177774e059cfceaf9d7f3021a427ac6ecc1eb7196bb93d0026ff3": struct{}{}, - "b002a634647c3350c37b15a376bae6867d9034e9aa36a06002e3e335229c91db": struct{}{}, - "2ebda397567291e64bea0575d54715ea4ba8cec2606bf8b2da95df58df67ca8e": struct{}{}, - "05ac8744ffc3055bb3ec9c4abdf45bd9f1aa54fefae01b0e2d1a3ffc6beed516": struct{}{}, - "b4ef49fb50c0c07d21028202d3e6cd74ee18ea84dbee07dddd29e8cec99f1d61": struct{}{}, - "0a4ef253e3fef22cb9d3bc5280386f9f2d1cf351da6582d8254659d1b27ea0b3": struct{}{}, - "8d7d5397f8842b4181d38bc57b85b9ff1860456f92872c43f991a904c45062d5": struct{}{}, - "239127e09157cbafb6212123b102aa1103241946b3684c232c44b8367c3a4d47": struct{}{}, - "b1601f694b9d336c35fc456de5697dfde5e1b1ce4e8c40766fb6cb763aba91c7": struct{}{}, - "e9c43c5dcf143fdc4173b5a9d62dfabcd704d436aad8b2034df993f44ee49e2b": struct{}{}, - "47c5c28cae2574cdf5a194fe7717de68f8276f4bf83e653830925056aeb32a48": struct{}{}, - "dc6f7e7a3940cf045fc5d85257b4eb290118e410c3f6119ba2bf9a70a4da1c7d": struct{}{}, - "8c2a25260209b2db50e9d7c369876ddeeaebde2472a38426ca4907fbe4135921": struct{}{}, - "9e861941ad8bf5bcb649e5fde92d712528200a216018c2437371498e6ab7683d": struct{}{}, - "04e9418eeb500e3b1f4e12e5fc930d7f2833c2b0cfc5e1cc9d900729effa0744": struct{}{}, - "7c8159dd2e49a576176f5461ea9d8919abdfd0ed4e29e2b4c1c7b98a8060bf40": struct{}{}, - "2e4b9cc2428beb7bff6a56c14d7304e472a23e890e91ef0a6b0b0380e89f9734": struct{}{}, - "bf15d6ce1d5e7f9ccedfc7ab2468df4e4c18db573446493b1b24f3d32534bb34": struct{}{}, - "f7c008438c5b2cf95bd37cbc716adef7ff0b01d4434ad3049831ad3823b0334a": struct{}{}, - "ff06535ac1029cca2fc2b86ac7355a7b4e0b8d839fc76b51d30833f4e1347ddc": struct{}{}, - "ab0b179946024f2dedc5bbc0673e689acc4dbf45848c0f12f0a29bfb9bce8b22": struct{}{}, - "0661652820890613176da180316c4cdbe82aecc68cb8426f983d2c846d3f498e": struct{}{}, - "806956355a14acbf0e5b8a937c4969de8497a467a4444f6824d52354f9ab6de8": struct{}{}, - "1c39a01150f2883cc771db74832fa5e41bb3249094c5d30caa59ad588024ebb5": struct{}{}, - "cc0b8a95a883bc0b5f64a536de42349e0ce0673918a3c446255ddc8354887140": struct{}{}, - "e442422bf978c0059f7e772d0d35dcc5359ac1d5f265ed3d5ecfb325066896e5": struct{}{}, - "2a732df8e85f4472fadd2d26c1f9e6ac635144217a706a775c1b2e244643e84f": struct{}{}, - "9f45aa99ad9ab5c822155c4a44f9d64d010faeaea76468a939d2d4f046e340bd": struct{}{}, - "85266837dfc78c80e4c34070decff906a65838cd62e452787c051711b0c52a71": struct{}{}, - "8529ab6899015263573df683e57a9de269d60d38f9a5493152e1f53e8c5c2730": struct{}{}, - "32f987ecd36719a12041719b7a729ecbe96f3d76e110121e60df0e599fe9e768": struct{}{}, - "1a49bfba8f876f00a50aebea5a851f346c7c5ecb409f592f2b0f9ac08bba5f85": struct{}{}, - "1e84855630913c012d541a4720d243e6d07e8131ae47ebc69c5b0e11c36e6eee": struct{}{}, - "b0f60a0469e2ea24fdfc4bdbca468e54818bc5387647f97b2acf0a6e4b2ccacf": struct{}{}, - "462aec2ca8883242483416b27326b2258b9ae4d116aef0c55c38b1820d07355e": struct{}{}, - "d36e9c47a21aed0b08455e0d192be59b4b0b25077d509f328a15b54494602831": struct{}{}, - "44d3f3e9c1e19a2ff31593315ff941859567263dd5f320a2f3ddde538439d028": struct{}{}, - "f5a38e4245e1c0094edfe353a8d77577f61031999ec900c57de05640cb5459d9": struct{}{}, - "d602bf8f470bc67ca7faa0386276bbdd4330efaf76d1a219cb4d6991ca9872b2": struct{}{}, - "f969fdbe811d8a66010d6f8973246763147a2a0914afc8087839e29b563a5af0": struct{}{}, - "2a09783b1c474294d122b82b93b01c3701bc9e4d65b3ae60da211a4121152a43": struct{}{}, - "5eb67f9f8409b9c3f739735633cbdf92121393d0e13bd0f464b1b2a6a15ad2dc": struct{}{}, - "bc71de234142b3704f3188654a642a416a04a7fe9ce4db9fc4d338023cf8a733": struct{}{}, - "afffe7625f4a57162381b7ee4488cfbc48e8c26126d46cdbd18a20ea36398994": struct{}{}, - "f6f2ea8f45d8a057c9566a33f99474da2e5c6a6604d736121650e2730c6fb0a3": struct{}{}, - "e697c5113ce58b708b7c1f31f0423cc021d0bf3aacd3d63865c53a5f817ce278": struct{}{}, - "6c60158cf3b504d644792e806dc645fb3d3a10204027f0ae3d6021ab87d22a3e": struct{}{}, - "c9022680f888674e2b2274758755bfa07dea729b68d71cde5c521ed70ef261bf": struct{}{}, - "00451d9a00bf9b7b39579402195ea63bad0a1d73c8259608057ea7bcd9d37f07": struct{}{}, - "99a7026172d42714d0293e5598ec1c8e8260d7d0c4b8582ad002c1883494e216": struct{}{}, - "20f3765880a5c269b747e1e906054a4b4a3a991259f1e16b5dde4742cec2319a": struct{}{}, - "3254c79b3804f5d6982635c46b28d455357e24378e1e78576b9f55c6bd8a7f76": struct{}{}, - "5f0365c978d880bc024461c6e16c1a012153a8eb86e9cd2adb3762f777d308a3": struct{}{}, - "2f34b7a451c629020abca0f194f78f61a08d2b96c4dae92a3b9bbde9794f7ed8": struct{}{}, - "a55e2e3846a51f6ad0abfdfbdea2ba0e5e0c76b5ccfa8a920895fedeae89a8b6": struct{}{}, - "4efdac93aa5c18773028b58c29901de6bc13cf3491b42b3f8a2787b85bfbad2f": struct{}{}, - "f6a9dcb187642124f2081705482bead1919ff763b0badd2818ffc7ebabc3140b": struct{}{}, - "d84fe7e07bedb227cffff10009151d96fc944f6a1bd37cff60e8e4626a1eb1c3": struct{}{}, - "050a5a129d528beb55fba7680d16f8930de1259faac42145897fd984edc20fcf": struct{}{}, - "f7f376a1fcd0d0e11a10ed1b6577c99784d3a6bbe669b1d13fae43eb64634f6e": struct{}{}, - "538d7d9fe78e7baac47a9fbd6f2c68845ecca72dbdc2b47b4c5a0f5620ab8e93": struct{}{}, - "345e24682c0e3ce8770a6c394c5476aa143bf3349ca5981785a144ada1b34095": struct{}{}, - "28f6155150517676afdef5e125740b054ca6e8e3655fcfc4d294ecd240654392": struct{}{}, - "b6197fe0d62a4e463edd2925382d4d268c4fce0859378682608efa4fda326f26": struct{}{}, - "0f48868342614a8b80d760eb91ee51ae8c777f8140baf40a8c4060c2ad2cefd6": struct{}{}, - "1f006e3414a8153fead977395cfab2e825d4f9541fb669c74e7088aa5dcad66e": struct{}{}, - "657cd4d68c6a6e51740f32894b11446e720096f954c4faa6b7bdb708e4b8e215": struct{}{}, - "aec6e9200847dcb045214f9a0e17921cf539b3e0718f2a2f76b6b6f8117e793b": struct{}{}, - "cc767fb1c6efc232caf9dd1f6e853d853b58ddd2c058c8820320e69d755c18fb": struct{}{}, - "881ee8156b76283075eed6a33c37017fb0108187ca3154a89d3e78c9816397e6": struct{}{}, - "fc4fe1af2b46813efc38e648f02a24edc639c0ae14d0214fec1335445728c383": struct{}{}, - "248b3b8bfa4dd9d85123b89dcdaf29540718991d7f8f9148e4c4308ae6dae1e1": struct{}{}, - "092cda390b47416151199357c71b71d07dbc1fc70a57399d2c91fe246691112c": struct{}{}, - "fda6cf2f920f4e1e9256f7019aad6debe21ef1337314fa8f0d6a3eb8f4aef494": struct{}{}, - "e75dfc45e696576babdebced53338142cb413994f18024752ab1dfe24676751b": struct{}{}, - "6460662e217c7a9f899208dd70a2c28abdea42f128666a9b78e6c0c064846493": struct{}{}, - "3e04da76d7473719ff2fe2e24c539e6643751a0fd2b85966b3ad5e96f866eb6d": struct{}{}, - "2751b66fd5dea24da4ef3f03a6d9d3db1bb54c05f91650dda050c6a51cee9019": struct{}{}, - "1d975ddabeb63098bcbfd0c5bde1de51ae5c1f02fb760d17e22361635a582f71": struct{}{}, - "962314b7ef2f70007380c5b6700daedc6343ac6938e8241a44713e721993c0ac": struct{}{}, - "0c7fb88c430897c6307037f3251e855a91b708fe02e83da6c58cc646bffecdfd": struct{}{}, - "1069f35d549fe898d18de8b21985fa1d2dae45460ae813de2a80957dfba6d800": struct{}{}, - "e6deb10a6558650aebf4c4d95fc47c716dfb2da7350183c114041c19ae9bfcd3": struct{}{}, - "fe0266155513993bff683fc20f734de0a36b8ae2a5e7d9b2dc0d061a06366237": struct{}{}, - "b834866543b2bf0d52711989f23e37b41a66664a3eb6991c4813bca774d55557": struct{}{}, - "8a6ba32c9bed6ce703f999f9af6ec23686d44e144e4da572d94c8daca4a9cbab": struct{}{}, - "d9ddcb1a43b9fa02abf7a0361928c5a026db56c246fb235cc0c8e73f86044c6c": struct{}{}, - "f601c4054d7945352a07232bce0a4735923d7f04a8fa137e16319cc4da9281f7": struct{}{}, - "4ac134964872c4b0e2027c2be20aa440fa4e699ba9c5b9f1929bdab52824e424": struct{}{}, - "ea0df5beff12699fbf204c7fa7f7951f12f2dd57d7d0456dfc9fa4d3c0a0e1b1": struct{}{}, - "2d120fbc1b9f1ce8578a9db83fc2fdd1d2ae49311de479c9d10f579c813e294e": struct{}{}, - "9a13a9d1ca9254fbb69dc93472295b15d741742de15dfde64d4a2320e1e29200": struct{}{}, - "fd21cc3cb5062bc4ac714c489e1ca0e37a577c19ba23b0d00e9767f598d37636": struct{}{}, - "f405f95c2f4102f99c2b4188638943c98baa358cbdfddf8db8eea1e3cd9daa3f": struct{}{}, - "c3e8e999bf52bb9aa3309c8e81233f1815029e6067c3c534d6eccebe3650e737": struct{}{}, - "2c598436e5575a5769b69986014588d52c2698414b623e81b2e776766c30eaba": struct{}{}, - "b2547aa06de503c7bd16e63ef3483e826622672c0d05ca0a75dd997e14df1303": struct{}{}, - "8c74299e1cce9e204e62f50196cda0d761e29e0b19307cc4afa31a7e61e92fdc": struct{}{}, - "9131259d0e544cf42637f55feb191cdd68b318bc75dd4df4a459b5cae3e71eec": struct{}{}, - "1cb542228c76558789d114d3cb273a75850cca54ec3ee9a41100f2dc56ee561e": struct{}{}, - "3a5a2512949399115565867a73a413ec6ba215c8f2df385f78b33238a6639b7c": struct{}{}, - "e54fc6b51915e222ba6196747a19ebb8dfa651fd2b46a385a0ded647fbfefda0": struct{}{}, - "e9b71991b7f947a3467fff8aeb5f6944a34cb9c5f9ab9e605411dd3655190c6c": struct{}{}, - "e111a1ef7daa8597240eda0ad482ba99ad09b4f84c093bde80063a23fff2cfde": struct{}{}, - "6fe6c35728c65f0351ca3e4f151b8010215afec63174941e71755ea1d8440475": struct{}{}, - "e0e3a2b6471d044a53a7757994b51dd33c6b3ec90e1aca21cebc8e2ae79d6d9b": struct{}{}, - "14f22c3ccfb754c8a1e6fd94f9f6f36e0415243d0ea0d1aef49822e6ca325425": struct{}{}, - "238a314cb3737af198ebfb964f2b8da69b143414f00f3d780a939ab5d67534dd": struct{}{}, - "ecd18c6a66631988cfa1ef186b61d729c1430c3e1a2503c3d83167a6d95f7b95": struct{}{}, - "be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912": struct{}{}, - "8d1630d4ad519dd5fe3193567c013f464af77050111e73a67c2373dee5479b1e": struct{}{}, - "67e0f6c50d58f0420f424d623d706590581c6b2c7e70a68129ee51693e86d53f": struct{}{}, - "fafe97f7def328bbd4f10779b9625a8aa0bfaa143d7ae64e6f5770e47b51cd1d": struct{}{}, - "9df6b026a8c6c26e3c3acd2370a16e93fffdc0015ff5bd879218788025db0280": struct{}{}, - "622a494d3ea8c7ba2fed4f37909f14d9b50ab412322de39be62c8d6c2418bfca": struct{}{}, - "954c7a1f84096998e6df172544976b0f8357d9d545c0f5ffa0d7ca839ee8a383": struct{}{}, - "798552d3924a30ba1defcdd9c1619ec2faaabe3b3e345806ca9458033b535b7b": struct{}{}, - "3460eb8087523e19ac486f37fc68192c2dcd087814a2a9c9ad6b668fee3e0134": struct{}{}, - "d931a74fe3bb28deee7f370e404c97740113e325b75c41335fe7798fbbbb67cc": struct{}{}, - "f5a1971c2ef02a5ab2263f20895b14e7ac1607d21d28805ca8a7ed31ef802364": struct{}{}, - "0b4bd77cec705cf5df016344c0fc673fb3c2240078e6af572b2c5c6fa76114af": struct{}{}, - "fbcd8f9bfb45339dc8a1863128ab4613f61e58c52660c3552f70724e1074d1fe": struct{}{}, - "954bafccc8afa13db7e106a919051fc24727aeb4dfb11f5dbceda1d64bcc117c": struct{}{}, - "947f187506f7629c81c81879a2cb2256455038e4ac770091d897fa0a8b945e3b": struct{}{}, - "19e0f50bcf2f64571e4cbac622e7b6f4ef0decf6145c95c2c0d176adf4cbd549": struct{}{}, - "1c18bc2214f67ae19bbaa640fc776f5a4a38c4021d421cdd8cd3d2bcad23da93": struct{}{}, - "de7cef61aebedb3efb298dbf09d6ada05a04998b08e6fc9b4862bc0c6f28407a": struct{}{}, - "a4c71d043bc7eff3c8e2efc77667b791963c075b3e387ef208713ccd425b899a": struct{}{}, - "5873102fa0951713a81154785217660a3a116274481a19fb2eea5c56182f4860": struct{}{}, - "1d0fea39ec33ff7543f345be85d1ccd34d6d864297d4151b737802cb294a338c": struct{}{}, - "ed12eb20000f3e53945efeb9eca0c664f0ac3382cdf93a8d46b808a990f69a79": struct{}{}, - "c34ce311989d8bf0a65b9272905811c857e8c975313327c3deca09d3f95409e1": struct{}{}, - "80974e50413f641d256a53afb12ab5790505723fd03d35e5202a34406f71efd4": struct{}{}, - "8f36b08546c5a3c7f7a8f2d03aa9839abae87d58084a5bf308d346c301acc3d9": struct{}{}, - "5146ade2f3cf7a7517c161dfe47aae05bef44ca3cb63ba31a701268f49289018": struct{}{}, - "59ae856cd788d0f57e39fdd66d421ba930cd89be4682de3aa36c22a2021a710d": struct{}{}, - "7cd58cdd64a3dcfcd4a55b5972523d0b472bd7487ef2f785aa64c75fa39f830e": struct{}{}, - "846d124b6785689c70bc65cf99e0ea2e896349ffeb38cf1b1224b88b3982fe94": struct{}{}, - "02d32f28a881b6966e7fcfd58b6b261a0cc5f50fda0e43f637cb6f087bdf3ed8": struct{}{}, - "a702d8656aadebab0ff8560906a16110873c1c57b569686ac2f57fd071ea186a": struct{}{}, - "0e1d59321a7eb0e3bb9584245747cc73a9d03cd9ea2c24c39a071912dfadb33a": struct{}{}, - "fb38a93fd89e1a5fc7852a5d7109e321d23a721a1899292273ac5a21dc4db378": struct{}{}, - "964a9431ff491fd7e0d7436ea1ed3e76e15d1d2e34fae585cdfca3c57805cf75": struct{}{}, - "8d3e0bf685d077784de23e1c217de5c5d8da4c0200d7c86df6ff607d9cbc6959": struct{}{}, - "30d0e035e4aca1148085ed163769d38969a281f4d0b94da7c31f2f56416b13bc": struct{}{}, - "827961b48d1f730d74322a1d42a04e86b75dd19aa2e7c76061d7d1ea03397964": struct{}{}, - "90a6d53df47e4097ad65833c01eba6203665b93a60b00a848edbcc3ac53d605d": struct{}{}, - "b51976231e40813782e020fb8da52603782bf4be1ef95acfb29bd1a6216a806b": struct{}{}, - "86c12ba6737d0873c383445f01db4c6c691579efc0110dd4537bc34b7f5e3e6d": struct{}{}, - "37268335dd6931045bdcdf92623ff819a64244b53d0e746d438797349d4da578": struct{}{}, - "6d30726493527b61af2ebe7daef19b60ae66073f11080749ac370fd6478f5dd4": struct{}{}, - "441349e1ec2f7fd0aa17267bdc4339ea1379cae3a7d24cf7f3f295a6c9f9d469": struct{}{}, - "e85728824f20115b998278e65abc3cc1546c28b88f666e71e58e256fc24fe8f9": struct{}{}, - "a5ad895656074bb12930374348bf903460016bcf430bf7039d7e34f0c505a7b1": struct{}{}, - "d24259be13407e0d132337bd8398ee9aaff43a249c38d0bf222429f311c9c939": struct{}{}, - "120f6e5b4ea32f65bda68452fcfaaef06b0136e1d0e4a6f60bc3771fa0936dd6": struct{}{}, - "3d14c2d4e4ced81e459e4ace7c01466a700000fb94a3bbe944a55fb92693e879": struct{}{}, - "0ef49a3c92f37310f483c66652fd0687d3e723bda4e4ee91248e4d386a5a8f9c": struct{}{}, - "b24997a19f7ea8c293124b711e7900d89a3c97b3d8e3f4c42e7795cbbd00b963": struct{}{}, - "812b700dccfc140e227c3d58ee93dfdb9b8c7801961d011e663821da7d4388c8": struct{}{}, - "2126ee32a42735bdba2abdfe540afab577243c3a69c88d5a82c5a84f30db61db": struct{}{}, - "9f2e6d33a3717ee826353a404ba4618d1aeeb6879ad7936bce8ed5f46814924d": struct{}{}, - "2cd65aff56c2891c86ca105a5de55f32142afa8652e50b8a707e6d75d9deb168": struct{}{}, - "68adede792564ff36f58723719d594c92270debc9021dd3ce7fdd35d8c9106f5": struct{}{}, - "ad1d0a33d1475e29c577be23e29fdf19e1df3663b7c7ed77272bcf3289f1595e": struct{}{}, - "bc020a35b7f9cb1382e7b534c68e3c531d849b119bf14f75ddead6cc45c3ccc1": struct{}{}, - "22ba2d6f2848f4c040f5721ad15349dc5851fdc7d8b2973d45706afffbedc25e": struct{}{}, - "523c26eee58e33174f55667f3fdefc6cbba5185c708340984fc759f15c33272a": struct{}{}, - "e181b3dfcbf4b938b8254b8383c8fc0e4f1e812f6053e09abedf8034732e113c": struct{}{}, - "182072537ada59e4d6b18034a80302ebae935f66adbdf0f271d3d36309c2d481": struct{}{}, - "fcf36428e8298e58d52a366b280d79df384942883dde225856a8741c194496fc": struct{}{}, - "4e629e9dbcc153d73fcbb2befc93d5a50182d6d956be8d9ae579fe5df39c5f14": struct{}{}, - "d79987051a2552ac1895e06b97d12d8cfe5924872f61dba59c15552bcc832f9e": struct{}{}, - "106b086224a4d945eae25f7be3805a931a873270326dd868b0e41f71ee9fff72": struct{}{}, - "5330e8f20aac22d5c299072da2a36dd4945ff1eaa409442215c1c15492ffb2bc": struct{}{}, - "2e844ad651c6b9a69cbe8f887b6a74dd3b9fc489aae777701e6e2d0d523a0cfb": struct{}{}, - "1ae3d35454b6ddf204329a7ba8bcbbaf1b1efd8ab1b3df346fc83b0146caaf17": struct{}{}, - "0fcd568a5cb9bdb4677b69354b11ee415af8f784519cff3da49a26f84eaee7f2": struct{}{}, - "d50ced435359c1f2baadf3c9149ca29fcdd610e639cd6082c52c2d33bb4ab748": struct{}{}, - "21c584f66fecc9174a94f4b7f8c0978a43f8e77998ba9901ff8e6d55a91ef80a": struct{}{}, - "0d06d7954e0b8880a94c01769ff4fb08df92cccaf0ed7ede7ffa2384df2524aa": struct{}{}, - "20f759a1625250532102c726b8477079a46255459abf22831d96337df61f8baa": struct{}{}, - "31c74d4639ab2f1477eeab8c319fe796acf0036ab7fc6495e62fb71460e23c35": struct{}{}, - "3830580a8eeea6f3b1d777ee46f2ebc33ee6b9dc231798e7556e3b46e13fbb16": struct{}{}, - "0ff9f948f80c3127c3477cec4286cdb98e9fff6a29c9c3d1293459876ab45767": struct{}{}, - "6d08a4e630e4aa0d5cd873e65aea0a23df42de61073ecb49ef17158fe6a9dcea": struct{}{}, - "1b4f0e9851971998e732078544c96b36c3d01cedf7caa332359d6f1d83567014": struct{}{}, - "208ad55e4c4200ca42a73ab260efd6df57efccc95aad25fe4d1f378e23a0acb6": struct{}{}, - "4ee3679892e6ac5a5b513eba7fd529d363d7a96508421c5dbd44b01b349cf514": struct{}{}, - "e1ce2544228e9c0677ec2885ffaea9b2a07e694cbf2c1bf693e402c439172da4": struct{}{}, - "c8c781c96ca628d1579171c94dfdf3a4e157591c87bd23dac86330b206d82304": struct{}{}, - "057ba03d6c44104863dc7361fe4578965d1887360f90a0895882e58a6248fc86": struct{}{}, - "203b8ade4aaee21728c5ecc2b53aadbe0cd25c7337c205da91f0686542ad3478": struct{}{}, - "2340486ee63d60f5587b8d7adf27a1c2aa0914e8df3588c0b1f372fcc3ba8543": struct{}{}, - "df4b1086ed85d4c9d62ad32a6855a5373c2a862ce4124b808b6598f4fb726013": struct{}{}, - "c23ad6f18412014673b2d04794ca038ef6767fe94afe408dffb775362fe07e68": struct{}{}, - "92117ce11e98294a222704bdbaab68054e47de56740af058bdabd7d2135ac78f": struct{}{}, - "472de632654658760cad8db98e2aa2a51001d43334dee4ce2721a879b9f4350f": struct{}{}, - "ec5f70142aaeb262eaf5063d536df28620cfc0bdbd721c6b7e0155645148f7c4": struct{}{}, - "0829103205fbe7963996bfacbaeaab326bb8eded5bd97e9b677f7c05fbe44b5b": struct{}{}, - "fce3559e5a7e33d289bab3244a5480eeb9008296849084008d329f2ab59749bb": struct{}{}, - "0a14a1797925f8b61d010574c110617ee9bce8918da92c2187b9d02b68a792ba": struct{}{}, - "123fd666aa39d376690cfa6570426d3585c188b291bc87acf47b84e3fe822102": struct{}{}, - "7f5bcffa0de82e95ca6db79fb869cc667e3df2699d6d4eff804017b7f54c376c": struct{}{}, - "ed08c290d7e22f7bb324b15cbadce35b0b348564fd2d5f95752388d86d71bcca": struct{}{}, - "91c0b7c9c0fa9864fb4e5216123a286d625394476cfc0a4f89f4fbd04ab6270e": struct{}{}, - "891e12e156d8c6609c6d5f3e04b2fc8da6d9ff3d7e9f906314c0909da69637eb": struct{}{}, - "7f0b629cbb9d794b3daf19fcd686a30a039b47395545394dadc0574744996a87": struct{}{}, - "85d6385b945c0d602103db39b0b654b2af93b5127938e26a959c123f0789b948": struct{}{}, - "758d61f26a44448384e5c4468a0dcb7a2abe456067b0f7b505bc28b9411fe931": struct{}{}, - "5e90f7d53f9d8cc6625ad8f16abc031cff09d776c74ff68dc6907ad680fafd49": struct{}{}, - "3d553499ef2d0924b7f878f1d3dbd5ca08662d428f28b0090113fe5ade93e556": struct{}{}, - "18138372fad4b94533cd4881f03dc6c69296dd897234e0cee83f727e2e6b1f63": struct{}{}, - "5019030d9eb3ce3b7733365e3b786808e733630653db1a0186edfd970264ea05": struct{}{}, - "405e6e0273215dab5aa088d765d9883727f078fbc89dd6fef0723476eb26d3d9": struct{}{}, - "9c0e126a9f0375702fc470f76553b6d09047271297fc6ca114e56b0120906f5c": struct{}{}, - "5ec772f3a2b5a18a3cb110dcdddfcd03e2039986a6ea35f40e18860d96bfc050": struct{}{}, - "4378c5895e7ade691792a8bbed89dbfcbea257a8949a3b1132a875e67aa45583": struct{}{}, - "660f5656e3e8f13ca2bfec97802c34e9b665e2360e27f572edf04431aca1be5e": struct{}{}, - "b6086e6137a8d3b5df439a842a2840850713632fef199d0aedbcc3a36e9e0ccc": struct{}{}, - "d7e04cba6a8ba148c966860632ef3636c77935984791f36ce5457016d7c313da": struct{}{}, - "8c68cf65b6c48e9d23d12e9a0cbfa7cba9e9ffb492e8f0022dec98d85d3d56f9": struct{}{}, - "9c6d405bba2db24bfbd22fc7ff74b39bd9c5e9c6ce66299c6519be517e6ed7c6": struct{}{}, - "057f7cb1be04e614a14dfd58ce0001f46d2a3a1d3ad6827991e806b59c643f4c": struct{}{}, - "19a0098e641d4bee278bb5d470d06679ffc5fdc818c3a1c52bfb7f8cde3752d3": struct{}{}, - "140bedbf9c3f6d56a9846d2ba7088798683f4da0c248231336e6a05679e4fdfe": struct{}{}, - "11d40417959631d3d2420e8cd8709893c11cd7a4db737af63e8d56cfa7866f85": struct{}{}, - "401cb0a3c9577a6aede136e6d5d4c9b87b955ef3c98010ff91390acd06d4b2aa": struct{}{}, - "eeac7816005ff0bde67004b95a5563844d60901f3db3baaafc62ac4e850e08de": struct{}{}, - "50db3805b4f95280749012c82afdb09f883c9420c502f1d7cc71c8a1679cc1fd": struct{}{}, - "22a9cd4d8ea1f55ccb030b317e5d2f34c1ea7a830e68b3ad2f45063cc4b8628a": struct{}{}, - "c9fd53f3265ef99703dc177a8a07849da6c3797fd3cb7992d3b800fc47c65b05": struct{}{}, - "d56d0ff69b62792a00a361fbf6e02e2a634a7a8da1c3e49d59e71e0f19c27875": struct{}{}, - "61503690505f84b144e6ac89124540a3eb8d22e77db76500984cfc50a1d8776e": struct{}{}, - "3151a8f227c0e11fd9a7fd1aa24ebfee734503dea095c22c3b2fae09d62eeb25": struct{}{}, - "0a1b086f072513ebb1d3d715166583135b706781ce4948cb1eb90b9837eb5707": struct{}{}, - "618d663af0f1ec88a5a19defa65a2f80d06582a832510b12f475d80870bdb3ab": struct{}{}, - "df46219531cb5d522d0845901978dccfa286a5b0437f4f9cd4e485064f6b632c": struct{}{}, - "bee3b55d3acd5f39ab4ca24605255b69540ffdf4b7a478cb9858f371eebfc948": struct{}{}, - "5a7b2e919d9eb13cbcfcdaa0bda8bf6aec156a00e29448e96f1702676f70b119": struct{}{}, - "d6a0639bc5f81e3b27093ecb39eb106161e55bd523cbb15e26fde6426839e166": struct{}{}, - "1ca7e37f735416af538ebfb896caaa8f53b5156dcef44c457c9c5af5adea261f": struct{}{}, - "8b2fa4880d6a46649514ae8f8d606207e39f2d971f560a75289a5627ebc9ed65": struct{}{}, - "aecdef99872725172ea6994ea08d2d4375b90bc8f8239d76e443544ee0900d85": struct{}{}, - "9a105749cfcbac2e07d7640697861f62dfcc7c02bc24519e9c287431bc493f9d": struct{}{}, - "5f16c3e2f49a617db5d9b49a3b433af11b054f8ee5700e3cb5b004c4cce20dca": struct{}{}, - "d132e4bc948e1cc12383afe9439fdee37eaf63dba3041639c64739d93a601bfd": struct{}{}, - "c97ace4c8fef2cee8fa0f3c9f52aab18dbd4f42438afe362ffb8f75ce4c04b84": struct{}{}, - "91037a18ec3f84c7c071e338f4ad509e1dd2e3c2ba9aba1b392e818e91ab132d": struct{}{}, - "1433d68859090304120ab33c5523485492a24de68464810b770a5957b6d64ca1": struct{}{}, - "e22e5f218141cf74f5551f2186956c713f4b2c0efb5386bf8fc03276f600ae57": struct{}{}, - "a9d1e780687ac78d0eff2fc993037b1dd95440913ae402eb2acb488ee9eb6c03": struct{}{}, - "5fc6b6a518032ad46371a1a6a1e9f59931460a69cde2e26addc28d9a6f280237": struct{}{}, - "9c1880f9229fe5e242a6c3e3794387c41c8bad966a4d0a03176860287a1c4237": struct{}{}, - "1c22f26ac4d48e559798c29665ef2eeea067b849eabc4e6c609de04294c5e348": struct{}{}, - "a2114e464a6b56701ee1ac601abd50b761c30c1a17a5313395089feaf9304812": struct{}{}, - "3bf39bf3fe465c0600105b3451f274fa9f05b3c706f022608c0fb28285fe5cbf": struct{}{}, - "a292f1b96aceb731f173265d7852be0710e0bd0a666a7b0b0ad5fc92fcf81a0f": struct{}{}, - "762070c61b44e276d5072daf0a5ef2b9420d3b2379c83fcb5a48f70977fa1234": struct{}{}, - "997d365120369bd47e6b01b4f595a2c023502eec97c3247ff72712a0ee944c6e": struct{}{}, - "4674309091fa4ce073a21bee0c4d994505e0a17e13600ba0435a540208a37a6c": struct{}{}, - "f64f62a5954569dd1fa24d365ba40c06631186f39a4bbac41c840a3ba876da9c": struct{}{}, - "f9846d74a1137294b0ddf8730300a44192b0745e6233821733ef34f7cb9526a7": struct{}{}, - "04d588cb41b8022d1233924f0fe8280d9e2bb92cdc81658eb102ef9c42fc9452": struct{}{}, - "fceac367216ad8d1154e929a93a5706dd81f4355fa2d22107aa61e32b3ce5016": struct{}{}, - "3ba8d02b16fd2a01c1a8ba1a1f036d7ce386ed953696fa57331c2ac48a80b255": struct{}{}, - "86d625ecfd684082637fae24012f5cf86b8af2d6d724280822a30ff91d37f017": struct{}{}, - "102626eace2d12939928583069428230227ca3b4ac67e622d0cec1ee9bd6e5b9": struct{}{}, - "a1dbe543e35edcbdcfbdb0c45389456dd1eb2453a2c0b6e27b94dbc88a878d04": struct{}{}, - "36296059cca8b7c7c7a52ff12abe53eb374d509d480f69bb673ac2566ea0b53f": struct{}{}, - "781e1ba88473ef026d6d0ce6b0bd391d9952a9f2f526d6a4b796a083ed19e698": struct{}{}, - "9ed1515819dec61fd361d5fdabb57f41ecce1a5fe1fe263b98c0d6943b9b232e": struct{}{}, - "14d3f05bcced45b09675b22d9047df0fef5c9e889b23ee033f8aad6b93b981cf": struct{}{}, - "77049f6f8893dd97f1fcdb996b970035ea456b4d63e107edf383809a1e26e884": struct{}{}, - "2d998cb471a020c0c58428d556cb5125365cac15eba3508faf2301e94bd88429": struct{}{}, - "04e8e027a3946f3fe22bb077c400154afe7e4f5e70c133bfbd9c3f5b0ea4d0d6": struct{}{}, - "e763d2a097597014d37688bf831fc328e9315436dc97980c936c930748a48d58": struct{}{}, - "bcc070fa512eb7da7307b5dc52af4dc8bad1a3ca27bcdcdaffc953acf618857a": struct{}{}, - "e15111c1fd75d9f5e32d4d770a56f7c7c559e5edeb0c41b297194774e213b308": struct{}{}, - "c587def9bdafb3209e28da9d90f1f05b0456c7a23b17da16087696ad059ef341": struct{}{}, - "1c443cabb9dba1a497d14cf112033c555c575af775abb873842e5dbc31434b5d": struct{}{}, - "2e6887c8745b28fce2e729491ab7e4c63b527baf8c76a649f277623af34be211": struct{}{}, - "148bd3fdbb83f84886f4ea7898d4540eb5c446981ef51a4120982fcd673a7373": struct{}{}, - "7f2774b22f2ce1d0100fd552618dcedd6588ad4ea016659d9c5e11d8b71728db": struct{}{}, - "21665ca201d4070f6fcc99b84aa1e55ae49e7b47069a24ed524da86413381b23": struct{}{}, - "fa766fb8427d1f8febcb07f3f5f0eaff7eafe72c499b1f7c989c59a7177ae994": struct{}{}, - "8f613377728ddcd0a7574e664e1c9d9f29401d4e94ca0819d36f9bfb1338b63c": struct{}{}, - "705db0603fd5431451dab1171b964b4bd575e2230f40f4c300d70df6e65f5f1c": struct{}{}, - "084328c53a9de563b83b23b6f500521ff442af7cfe60f104574c2c8cce22cf2a": struct{}{}, - "13a5c202e320d0bf9bb2c6e2c7cf380a6f7de5d392509fee260b809c893ff2f9": struct{}{}, - "106a5842fc5fce6f663176285ed1516dbb1e3d15c05abab12fdca46d60b539b7": struct{}{}, - "81c16d337a1b73144ebf20b45661f2b02aa0b22e886a978d6b2ec929cdaaee9e": struct{}{}, - "bba9e6c09ec086e27f4f740b21f94101e8f04ac1c2db3ff516840fd386152c33": struct{}{}, - "6008c26f4452392acb19374bc12a5ec0c360ae17356bce8b786fb128c8720951": struct{}{}, - "c20363e6868899db8e5e215f09f8d2b1b3571853ef01827901d7d09e6605d382": struct{}{}, - "1718c24b10aeb8099e3fc44960ab6949ab76a267352459f203ea1036bec382c2": struct{}{}, - "552bdf4bbc9329b555b93e7c9b6a38f36c6edb58b0e7fca0392f79528cc1b9e6": struct{}{}, - "51713377f3eb960f7ee603c8aba731d11f8f3ca7951805421bbdd09621b22e09": struct{}{}, - "cb77a566afc3174fc8449dc5827824b9d32d4531ca4759aafb6c0942d976d3ca": struct{}{}, - "1a6c02c940b633fbdc7629086c29be7c06316fa44f84192e8d6984d85c513469": struct{}{}, - "89ab530ac44400011621a38306c61e50c6f7d067932f341b9e4d00e0f827bd63": struct{}{}, - "9bb8c09c1ccb03a9e01d46fa617658ff7c2c002f52c2920244d2e829d0d45100": struct{}{}, - "3b9d8298f1b5086d012618feebb2da1a394357c1dab7523443c9f6a743c4c84d": struct{}{}, - "052d99ea8f31a3741b672cf7063d40ed4c19358181ae58aad0b29aad7f3c4b2a": struct{}{}, - "330a3129c3fd08c523b660b3e1bb682f1cbee67770c7d29ce7354871b4c0b3c3": struct{}{}, - "7ea7d8882237e84a197611099c31a964cae871dc15a8e84bc04b3a6f11e33f42": struct{}{}, - "5c97b89159ff8b70392cde5856afde475dc0b94511ceeafc99469427121762f0": struct{}{}, - "9fd73ab2444ff5c4563225c73f417b08b90dbc7e9ebf830b882c0fcef3140bb4": struct{}{}, - "42029ef215256f8fa9fedb53542ee6553eef76027b116f8fac5346211b1e473c": struct{}{}, - "7d4c5c8894e016e772e9b7acab04d2b775ad4e7861d162ca5a48de7a7151a6a2": struct{}{}, - "336665b051dbac2e59b32911c20f519a1d4fa75d65407207d2e4a4d09e762275": struct{}{}, - "3b74dd038604835b4f7cde257ad2f74bf1d607fdb7614fc7dee0a46166ab270b": struct{}{}, - "aff64e4fd520bd185cb01adab98d2d20060f621c62d5cad5204712cfa2294ef7": struct{}{}, - "ba47c5529523a47459efe63d89f15618bcfa926a5944b1ce647f3ded4d2697d8": struct{}{}, - "5b75a73245ef49e4e4e4f941c0f9f68a1a3d68b06c10cc019e8a3ef3b2f82e5e": struct{}{}, - "7409b250d5b9f9a5941c96bd0d38b78cdc319e5aa93a094dd3f844c3aedf855f": struct{}{}, - "304b4a90a76a1cbe4c112e074b30e75181f54df43d60f883597457844293b341": struct{}{}, - "8a7be276f8fa620390a6e5e949823fcf0156ba5dc042777e6b6d956bad1c7508": struct{}{}, - "9bd642cdf4cd4757f027709d66df0779e35fe0fe5baa39f30021403ca10d4a9f": struct{}{}, - "4d9cb7a511c6f91c59281da6b82487e928c90b9f812f688b78ed7d6139719bdc": struct{}{}, - "2419329067823cab5b4e5ac5dd18a6abf1f57f45e753f5fc934292f3085a3717": struct{}{}, - "686ed176fc5983c9e7abc51a20ac78057fcc3543eb0b0c9d893efcf87a6e1d05": struct{}{}, - "793da17aec26d53f08e3ddcff38c18e08032a6372468a4a6704a3ba86a4fc155": struct{}{}, - "c6cb50e7eea0df1fd3eaf52ada2358f5423afd7c0b5ee2395231a9b3208ffcaf": struct{}{}, - "922b53ea837e15ffe640f2e755e3d2f379fb34d103c8d69f99ec32025434f020": struct{}{}, - "5efc60b80fa21baa18d66c9a9c33c51e2b87fba0876709d4c8909aa3ed7fbbde": struct{}{}, - "dcd79ebce907b98a97ac818318c9e467731e44068412590ea34280dcadcf02d9": struct{}{}, - "312433c28349f63c4f387953ff337046e794bea0f9b9ebfcb08e90046ded9c76": struct{}{}, - "b0d3518b1abfb59187800deb569decddae434fe85e383a2a0802325b04994ced": struct{}{}, - "683259feabbf5eb39895d7a5d3f4872d6989d5fa7a3eca713251d996d99a1630": struct{}{}, - "24611dec0a0a3ae6f9f3a63c27323a5615c029c22a8e472e2bc6ae3ca65a6293": struct{}{}, - "668e2b73ac556a2f051304702da290160b29bad3392ddcc72074fefbee80c55a": struct{}{}, - "4ea140588150773ce3aace786aeef7f4049ce100fa649c94fbbddb960f1da942": struct{}{}, - "d4595ee7d9bca0d6c0726ec3209a1d6153a6d32bfb0bd26c2e1fd4b2b91789ea": struct{}{}, - "b5610de1f6ffecc890aa0a487b94a60ebc5f3190a6b8df7a773b87059826ab47": struct{}{}, - "d299927fdea2d5a7e994a5c8d4ff3bee85c5c770be1a4f7fa3b9b81bc216bd90": struct{}{}, - "c775e7b757ede630cd0aa1113bd102661ab38829ca52a6422ab782862f268646": struct{}{}, - "d5e34819841c4c11c9e838146b0f19b3ef8a49c4eeff864396bc2d2f004b73a6": struct{}{}, - "913a53d7df2fad651bed414afb045378c3495b23d5174bead8091dcc8cdef8ff": struct{}{}, - "b258f8614815e4157b9eab23d55ac5d0ff77a97ef4a63800089141225d560eee": struct{}{}, - "3bfaf7682094d482efb878617e14b9a0326568bc7a64e024a31dfdc1d52a5744": struct{}{}, - "aecf0de197203581ca4b8eff4bccb1e063cde8a7d4fb3eae64222f6c0013cc62": struct{}{}, - "c2333a7e3a607935c67c1e6f6810395decc9f66f592b812aaada7db94ba215d6": struct{}{}, - "2101348579746d02ed47ce392c4ed67bafff06f86eddf6663e99d83d65977bea": struct{}{}, - "eb119ea6c963faece6289eeea76d20e058203acdd6f8bc86eaca77c2cbdf1141": struct{}{}, - "404d5354e1c6378083556388fa1ef5725ce117f8ecc655b2a28a828cf0f07610": struct{}{}, - "632db65f4b5accae489fa673e5687fa8e62a9b105ee330bc041ebb4a8bb5750f": struct{}{}, - "baa9c153079197ea131ce56cc01d84a76c25746fee31890f2c60e95558b6a0f2": struct{}{}, - "848be944013cc3374ddfef3655d6816c060610161c835c71a9665e6dc18f6542": struct{}{}, - "c57b3f1cd05e94f3c1ca812c4f0665040e47ab7b889ebb66cf190f84ce48ab62": struct{}{}, - "e1b615af26ac9ee93cdddceaa3c07d541230dc89a49aed772967b0900d077461": struct{}{}, - "e8c3dc48dfaa04f5f0a85903d8207a8637212c9001b4051b30b89c16cef9c064": struct{}{}, - "381ead3a3145bcc3c151a19f4c7d804d1038e902394e8e86932438a792f8e007": struct{}{}, - "f9b0078b5df596d2ea19010c001bbd009e651de2c57e8fb7e355f31eb9d3f739": struct{}{}, - "c22aecb641e3cc15f1ef941bdb5d6ee8794fe4d212eb38af0161205c2f33b260": struct{}{}, - "bdd65dcd9f231498fa89dad79b0e7263536b90125bd7f2a4900c5f7e23552a10": struct{}{}, - "b7dfa266adb6db500f72b90657c0d379939ea1b17ad72459455d617569622e4c": struct{}{}, - "5f40adcebbde4df07cbe0c8c995d6a8a2b538edb838313103acedd7ac6e68b32": struct{}{}, - "2961c5a0feb2a8c962decf37230d10a42a74a0b8ca7a38bd0a596f751157845a": struct{}{}, - "054e3b308708370ea029dc2ebd1646c498d59d7203c9e1a44cf0484df98e581a": struct{}{}, - "d81bc59a7f48ef98c648c68e6309db34f0f517978bf6d967e6a37ba31c53ffcd": struct{}{}, - "5ba4c5ad7f67ea1a40f3108d9af56d063ec36d3400d810573769eb5c07809af8": struct{}{}, - "157fc55121ab2c59852c35d78aca98d840047e5d9668c423e4d3b146e1577901": struct{}{}, - "39fdbdb8ddf75a006ffec2a3ba95c3a04ce5517c608a786ef9a042af9843bd8c": struct{}{}, - "befd812b4e37a441cacaa4d020568564278728d7361024b194c11c08af7ada2c": struct{}{}, - "91ed2ef15eee7102873d33d852cae9a195eff25e758269de6457723b1d8dc29a": struct{}{}, - "d3ecc5b7fe38ffd3397473362f2c42321fb82deb23083ed13cf6f20320ab6c92": struct{}{}, - "24d7f03d8dc3c3666969e6fa5bb1fac4736d3f1353c28307ed51b320f9dc42d3": struct{}{}, - "8ec3e99ade3055c2b616ee759de9a3ee9b672ad18959438137481b55bc69e8d0": struct{}{}, - "f27585c0348b460b2ddb8f4c1b2e00ac27adeacbec068dc9990bf94218c4b5a9": struct{}{}, - "f71c819a5ad3c36fe55d32d3072a7998b3a014ea59b4d1a2afef8cc49e8cde9f": struct{}{}, - "6c296fde55b852788be0818c74fc20be7554d5fede6bbd3d421965755dd3d0c1": struct{}{}, - "5dd46878bb0b580b3086cfd1091b7bf6204c7bd521f6ac371226367241c3ab53": struct{}{}, - "3f94158370f0a012f1e7479209ec0f38a231c428b1ed4cd670a8fd66f1d9f69d": struct{}{}, - "5311fde0181cf25deb2df5472a236df2f270d1238a448e7f049632d44bc49ac7": struct{}{}, - "5ed1ff6a5d4f0e7d5207c9d71fc13add9b754cdcbeb29be8ee1b0cb21ace2fda": struct{}{}, - "05ef130c628dac6868d8ab9a08049009d414ceaae8b90e2b0ebb3c5d4c80da6f": struct{}{}, - "28cd2c5c15d13978a6bc06d38092dbbafb31f5590aa3ac120f9f8d8c0e0708c4": struct{}{}, - "6db67ddbf0c9afdcdbd8eb875559b9f76a2b6aa3b08a154e24266e6510ebe90a": struct{}{}, - "21499b8293b51b616bbe1008434f3f604f527c37c93c156528fc868dc31d06c3": struct{}{}, - "23bcd2d83d4c0f270640ec65cbeb61a1784856255c3c98dd25ec340453458348": struct{}{}, - "c63d78703790f39fab3813c1c8ebfdc7ed878f254d0355b711582bf0df485004": struct{}{}, - "67b040f5eb22ade013e1963448135ec059bfb886d8f1fcbcdedbdbde1b87cd7e": struct{}{}, - "255c059316fbf623e18b57a6a8cae8a847be578cb74ab07b29ef46446ac0111c": struct{}{}, - "7826b958b79c70626801b880405eb5111557dadceb2fee2b1ed69a18eed0c6dc": struct{}{}, - "28877ae869af57c757d1eb26e7cd1784f6aa6bd8dad8d996ee3665b87edcba22": struct{}{}, - "13809668fb436386706f67dcb052ed584a04ba16a8c02a17d66d34c30fceda21": struct{}{}, - "60960e65c77aa11daed5d62c62e34b5326975f4d29f1093484cc5a3ccf1bcaec": struct{}{}, - "2348f998744212575d85959674f9607ab26f67708a917157472832386337c904": struct{}{}, - "861b7475a20565e1b7d87712682f089db3bb7ed747121de2fdfe6ad02dc9133e": struct{}{}, - "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7": struct{}{}, - "6bab3007f56e2a9175ff1222c2654ddcd08fa7981a1ddc42f1d95cfbd80ede47": struct{}{}, - "15065d771f7c8746bd30c125f9bb68a5ec7a84fccd7f0a82b38e760f39521c05": struct{}{}, - "2466fcafe0531db08547f61d39bd340224e92f3410d58837e04cb845d790b970": struct{}{}, - "51bf5502a7102c96f3ffb54d6d8762c0077dd473aa673701e233e4f3f989a149": struct{}{}, - "bfb301b26ca5590c4cd741bea37c36d5b4e5fb92dc4880e8c89448bf82b2b94c": struct{}{}, - "59b26182323507a5e1e06bbc4e437725bcad003b9e4e0593ce2b1079c5519aa3": struct{}{}, - "c8b2c8a3ed9bb2603e091984e0b0e00b2db24c6167b432d967e4ef9be35cfb37": struct{}{}, - "0d098b1c0162939e05719f059f0f844ed989472e9e6a53283a00fe92127ac27f": struct{}{}, - "35a47bd37cc0f6e747fcbe9dae3ff82855392c7ff8183d0fc1d4accb03331434": struct{}{}, - "c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2": struct{}{}, - "0209442e115ad7bc79fd281d91423a86b619e3c711fe574b7cc198d2e3c461c4": struct{}{}, - "c1a1e4aa1037bae1586d8d1fd01b472e1d236884e9e22a04a7932ea6abeaf08e": struct{}{}, - "ca55fdafb12b30271452fc46e5c00e33db90805ecd7e736291cd63ac2cc87e7c": struct{}{}, - "34a04005bcaf206eec990bd9637d9fdb6725e0a0c0d4aebf003f17f4c956eb5c": struct{}{}, - "5e643ab1175fdd73bea8d07c38b7c70f95b4a9f8cf3e9b96eaaa7d74acfffc8c": struct{}{}, - "428542e60db091d8fc522b036a42af8c5e4cbd6df3a643bc1de8f148c65f1e16": struct{}{}, - "84a6dae49cf0481203aa97872cc3301b73c406b8ac099ee36d2f849365cec06b": struct{}{}, - "5d702eb07928ed7b84626b777c86c39bf4cb403d4024f031d5f97a4b0664421f": struct{}{}, - "08234c948b5a00f9d33f57f120e68cfe627abf576decb1f8f9bc95cb2114e990": struct{}{}, - "867417e00dd66c36596429f00517ef68e64c9759fbf416af29726838258b1550": struct{}{}, - "273458a7ed88c9e8ba4ec0bd61984eb6bcf1413174d1ec3e32e19c4349f61310": struct{}{}, - "c23170dd8858f8bb5c711add79819907f61ae906ce9cdd2ea5bf8c59d8637247": struct{}{}, - "588d2c1d1f23963e1647ea4fb720eec44b113b82d057156b970e96df9a1be810": struct{}{}, - "c3449cd99d19554da590a1d8a779f7c583577738f0bc8af1956d5f74bd51194a": struct{}{}, - "a77de8a6daa1129516b14c8ca1e233a51a8414b202eac7c8acfbb9b485046ad4": struct{}{}, - "fd216818cecbc78c0aeb274521b1501a01a2226a23a9a6922abb824b12dd86c4": struct{}{}, - "b64866d9d481181a9b3cd74f1323d7e35cd0ba87b48945ac92c1619827694fd2": struct{}{}, - "9e78b43ea00edcac8299e0cc8df7f6f913078171335f733a21d5d911b6999132": struct{}{}, - "cc3c49ccc51e0bb804a695f67d9f4d29c7f476149d3e79e3455440a5c92f50e7": struct{}{}, - "11204a0a51c730aabf86f69ac7f3a2a46f4c5c6a7612194f4c4dabecee785a01": struct{}{}, - "133806ca086fa756e78e210c0aca10be245bd2f8ad9544cb599e355020bbd59b": struct{}{}, - "cc5569ca5f31264e11dfb7bbcec51338ae585a7076d0ddaf417af8148298631d": struct{}{}, - "87bbe879c7a5f5784a70384bb49fa9513a6a3fbe4c2d388635e3c87611c03fae": struct{}{}, - "ebb3de8a3d9a40366132eb5deb5af44e4c96c11696f8fc34ea2c1d8bd8399171": struct{}{}, - "aa4c1d2c3d1d648eed2c148e3efb617e3ae95905028a3da63d7d7a5ec2682d44": struct{}{}, - "a441b15fe9a3cf56661190a0b93b9dec7d04127288cc87250967cf3b52894d11": struct{}{}, - "a03ed82404969bf28725bbe595a0e68c6f457811f5fe23ee0c6b8e732d995532": struct{}{}, - "2d1a5249a77ea9fb0983541857a50af54ed8e83b22d47827d205e66700d4d70d": struct{}{}, - "5600ae99b14ef8e5eb751fb922eff6cd2a5a9dd1820a966e84eb8b4b2c70d0f1": struct{}{}, - "ae5a853873043c7b011c6300c464d8d4014bf833697a3c01817d83aa91a53166": struct{}{}, - "f424040f5afd8846f2948293acf1f499a502d081f49937bb4cf252d146a3d19e": struct{}{}, - "3b475d7264a40b3ec1398ba8bd79afbd4f72ac70344e50d60cecc6e48af27657": struct{}{}, - "008231aaf99956982ffbb132c5fe9d79272f6f109abdefb723afe455a4213765": struct{}{}, - "4098e1a88ff7d1811c0b9043066c8a5df457cfac8b810cbf1d7c5105d9ed0a0b": struct{}{}, - "5827d8b677972a2621c128e87f772c045958e5f84b7e44ac80d362dbf014a6bf": struct{}{}, - "c6d4c4e324bc8e9b532ebf4caeae5a1b36b4389a044cdb59aca3e8eca6483f3d": struct{}{}, - "dc3d50da8dac041f660e4ba85339928995e771d99c33cc868f5829800829e382": struct{}{}, - "6177321eac992341d1ad0823a07e76bfc4ee6909db120e377ea303fdc216756c": struct{}{}, - "9fe3b18c44622d610936f3e5c4c8736496cdc77af649c769fe626f4a107461cb": struct{}{}, - "1ff69c405a41cda970cf36c6c8192247650ea952227fa1df8048e707bf7e8d7a": struct{}{}, - "0e25d57dbe558245aead9ac9aa0089a2d18634aa26fa0dd81efbff7c4622d971": struct{}{}, - "6bfbd709d5fdc0618ab5ea71913d552464b1fad43b2fc34cf25e96f3d2a904dc": struct{}{}, - "f007ec3623c78f7d7b9a9044295cd58a9b384edeac69a8ce1b3c6d12629c1a2e": struct{}{}, - "c30b1267791ffcf2829bb86532a80cd74e71a7343149cfac5a24a8943c30ba51": struct{}{}, - "c6c2307ac025abfed680cb646bc38ca3c3d6e02662a0f2faa143dcff22268a49": struct{}{}, - "2df84a679ae22f29fcf4db23ec02d318e61e16e1bbe36a7aa3136a516febb611": struct{}{}, - "3f08d8fadb4b67fb056623565edbbc2c788091d78fd24cbc473fce3043ce3473": struct{}{}, - "d9be2780e768a8dc862386222b739ab9e4b688be3d6dbaa71c6189eb27e426b7": struct{}{}, - "79deb2b92c0790be930c9eaaac0044bc923a52398440506a3ad7bd94fd3719c2": struct{}{}, - "fbcaebefcb926027176bff9d66e266a50de140460873bfa9dba6165717a72ae3": struct{}{}, - "488db9fc70392d1a92a62fa4098651f69817c3f12d78ce10a1ab16e9c8674442": struct{}{}, - "b07f3fc75999732d068489fad851944e09aa103288130173555c0ef2a6c13dd6": struct{}{}, - "ed968e840d10d2d313a870bc131a4e2c311d7ad09bdf32b3418147221f51a6e2": struct{}{}, - "8d16498453b03d4420d90362d71e6c36bb1564aeea53f36f206746e7cb5a5175": struct{}{}, - "06fa1d3460055a9d49eb90c01e48d616d6c9f1b1ac8011253f0d4ba7e1c30d33": struct{}{}, - "0422d42c689b7e8046dba2d7042e855f6d21e04464bab168fd263f5b10f893ed": struct{}{}, - "74f1c3e2a366d2aa08a94f7e5d6edbf324f51b71fe0aff65ec2c06ce97568a80": struct{}{}, - "cbbb2b64949871442de2ccfe4fa432460dfb5df8069e88027ce69c3c4eb56c64": struct{}{}, - "ef51306214d9a6361ee1d5b452e6d2bb70dc7ebb85bf9e02c3d4747fb57d6bec": struct{}{}, - "7b2b547ab5377906300c69cd065aa48ecc143ddcd9501ef2b7c6408cc3592ac3": struct{}{}, - "e9de08263f4ad49131d3dd1dc28694d26baa1e9c3033f39a4885d7690b06d5b3": struct{}{}, - "4e20613ec3a63732f98630b9429e1600d47a08f059496d39ffd353f0c3038f39": struct{}{}, - "94aec9fbed989ece189a7e172c9cf41669050495152bc4c1dbf2a38d7fd85627": struct{}{}, - "e683679358a5aea345d8a6084369eae87b0a3db298631dd14aed132f84d3b4e8": struct{}{}, - "f86a26a7cf7eadc7c408141eb441479eacc34f0d1a423bf2f03e862b0196b2be": struct{}{}, - "b870d3e3827088d978fbc2606395548df80ab0027fbfbf806a300b7b4f9bbe01": struct{}{}, - "d4a59b06b3e82897a2c3fd4c05afd63708986a2968778382f5482f0c83f5d507": struct{}{}, - "dcb688df9822484857282e71bdeaa79ae387dc14d852bf5373f919eee574dcea": struct{}{}, - "bad62274d34583a3c6865eed8e8fbd347eb898e991783d2cb8621e0e65c930dc": struct{}{}, - "1bcb037d000607f70bc90e65b5f5da0ca356bb15ea17147e99183c191ed70fee": struct{}{}, - "28274b3b335a20014c0cfbeb93d60d76c79efe057a891638ceae7209b10b0598": struct{}{}, - "fc8db96bab2a414d1db52c4177a15d3e292fb9d4354d0992f29bfc3f4715825e": struct{}{}, - "e144b9e0817bba466774088529570ee10164859871f06137d6cae620c008c81f": struct{}{}, - "c9f400894a45846e0efba3a5172a28d2899f72c90dbfa9a1b5b45aba709fcf7b": struct{}{}, - "4a38989ac1848855bfe02dfeb9bd31b96fedeab9daf9c334fb9c5d443b195afc": struct{}{}, - "1d256b5d20bdd8be57ed1f3881d7f06bc92da0093f03eef4e2e62b4295cbe4fe": struct{}{}, - "e2271fcf39b42a922de26995c6773807c2acd61f7fa0d345d51915843f9525fb": struct{}{}, - "3f8d40ad6a1d0038b8c334303fe06e70df25a1b78220855f6acc3aec951ffb3b": struct{}{}, - "2341a4d5a6b462deab7d7657226f2e192425e4b94e61d5ae9e0a8bdd1591b12d": struct{}{}, - "b8dc2c143be8994682b08461f46487e05874e59dd9ab65cf973e3a3c67a763aa": struct{}{}, - "b026bccaaff344acbd5c046751242877fc311966541bc4409bac93f0a2a468fc": struct{}{}, - "9cdc6c47aa193ae7fdab6c57a88f118e0adbe254c82095d39531f5e5c1314bdd": struct{}{}, - "784109ed21a6f7c669eaa3d404cb2cd6ce91f3334b836bc7021b0fee26af26ed": struct{}{}, - "5958a2986751eef0e2b2d9109f012887edc0936a606bc8aa11ba6964d2d17309": struct{}{}, - "3ed1f621d7a2b5e2f1772c7f5324cbea691e079891f43a10d7ffa6ff4175b82f": struct{}{}, - "7a5179eecc0fe18760ba615f92603372ae3fe302860098a019e15927551fee3b": struct{}{}, - "6cd512a819ae26ee19992ea2a497017251ff198ced08b584f9d3b4525cf331af": struct{}{}, - "ebbcda893dcdef8c67fec06e6852c63a7a9eaae24fdcbf207e771abfded004da": struct{}{}, - "a67a41c8bc79d5da917b5051f1f0d3f5aeb4b63ba246b3546a961ef7a3c7d931": struct{}{}, - "3e17c0d0d0bf52527df907a18e3bd3449cc209e0c1632416fc606c2cf5c17446": struct{}{}, - "340259b9265f44201de535dd1d45612f9c9ba9ebe9028f34a2963f064c977750": struct{}{}, - "ffd0aee5edcfef725d6c7d4b39ccb66bd090e7cef9d67b80e0bd6c3c72637e2d": struct{}{}, - "9b2c63c77c9b40691e322179f60fe2938423ea9b9d78693efd099df7e9d54bf7": struct{}{}, - "3358acbe9391c9b3df4d07dea3239db95548d55d5dc56c610a3e266c38c90636": struct{}{}, - "55c64d0fcd6f9d5f7c828093857e3fdfda68478bb4e9bd24d481ef391c7804e8": struct{}{}, - "56363e580521c95f28b87d0fd1da302f0d5f93abaf0f8f4e60c16640df6205e8": struct{}{}, - "46497eba130c2f1b626942d5e45257bdb042d8871bb4785ae93b5155e9c0a137": struct{}{}, - "dd6643e9f01560d100a93e6b87af69804798d85c03651d7278396cd7961c22c7": struct{}{}, - "b9e40e48939d8dca68a7bf33dd69c93c17a0bb73fd9d4ee517a9ba27c15df391": struct{}{}, - "b3da3004c7c4fdc00aa382728c392688c810d4d0b2577e732c5599d8bc2c0d23": struct{}{}, - "ca20fb682feb5e81bc1af38d987915165e43aedd9d043c1f6cbc50236c2f6826": struct{}{}, - "3a0225063bea113271b5857584f76ea27759dc14cd39b4472056404bcd0a268e": struct{}{}, - "440369c78c353278146551200b4f3d5ade06ce356ff39fbde10a89cc867d37d4": struct{}{}, - "9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab": struct{}{}, - "decf513b9ee91bafc0d72813609481675a5f15412a7a911164fdaad4c5daa9f0": struct{}{}, - "b6ac44e3f415a3a8475647092d3ea45fda2c4480219b7a660cb67a32dcf0882d": struct{}{}, - "02e6744eba2c6eb0a0ddd920c6693f1725f950fbd711e3e879784a99fe9755b9": struct{}{}, - "1cf18a243c25a56a993c8207d1161a9c2de5f34b952d382704b94dc5e888b108": struct{}{}, - "23eeb69c681dfdb8eacc7ce9e55ea007d41c2dd5273848c597f1b3e49dbd86e1": struct{}{}, - "1c09e5d790356eec81d192865298e10cfd13cc9f2192e03406424d13325a2dc1": struct{}{}, - "4a07a4310034102668a862f2ec7d3ba7416937b2f85c90b38257cf5b13093b0c": struct{}{}, - "02ce0980eec9ba736c5ae669614179a00e2bdf15fb078dbbf9dc354757593775": struct{}{}, - "70cbe512b00274e995e4c303de2333457cd810ab0ee3ddfb575b01973d8b929f": struct{}{}, - "b025079c90813d4669136b2ed07512204ee05522ba3e647935f1a88daf00fd43": struct{}{}, - "0a348ac4d94f9da05a7866a206ed7936ad3f5504e659ac1c814e63adfcebff81": struct{}{}, - "38102b4b8d453fbae854c177c7baad3e4fa42407b104b220dfb42837fb562db2": struct{}{}, - "da70f5ec723be3296607caa726c8576bb29834144a3e6e391522146776c6263d": struct{}{}, - "07b8234d2e02edcf51d3635486cad7a66eb0d97540207179402af138c005cfe2": struct{}{}, - "08bfff7b6a94c081ac0f3ba8cbd9e9fc627275b19a95fb16a531b5669121a892": struct{}{}, - "f1a583b3f5beb4d29b978d51c153ce7d478abd4f60405365795b803cfd3f575c": struct{}{}, - "11148d8f6ad4d4f24b1a3e91b11f69d925a2bd03409bb4f0f486f13d311aea88": struct{}{}, - "3dffc72a72e95ac3fb42f674a88ee9f5785075be0f4683dc7f29187268762bfa": struct{}{}, - "eba4ae33f54ae0f96bed25bfc13abd887ae157380330cd3fd3f0a4d054ce3a3f": struct{}{}, - "8b9b28baf5f711d6490548a84f7fee1a591d9557891122fda745c6a9cde8a226": struct{}{}, - "a448294a6283de8a4438fdf1f5526cf406d41b6c738277d4546df716df709bed": struct{}{}, - "87c3bbd5b9a829bef126aeeb3ba9949b4aa168b1320a4349afee66ea624a28f9": struct{}{}, - "cb5815fe5c3c9d7bf15dbe4964571c528985bb6c3559d1752f11ebd00cfd451f": struct{}{}, - "9c3125a008064087e6d93acf34289862fd3a625256787183f3b05ed503050809": struct{}{}, - "8578593b9cf683d3f59e6d73b25312b62ccc42dd7d45a7749dda4e32ade8fef4": struct{}{}, - "91f25e3a2ff2783b05b9dc9f07e555d828317c6bc2024cbd81f00684ddb79c9d": struct{}{}, - "63f75c890b05405e6e1f047072642b40949dab5912aa9c0ae6f2e654dff943dd": struct{}{}, - "c2e10c20bf46daf0ba03f725218b012544a3d0d3b37334eeb1d4d81e5406e478": struct{}{}, - "bca563c46da4b684e4c29950c33263017e94bd58f974207e4f187d0b17ecf65c": struct{}{}, - "a0b8143c5650b15cca1cddb56df44f7adb22f7fa3174134e5aa5ef1b6e7366d3": struct{}{}, - "c00cf031587d12c309358e85de8876b2738d3ef2cadd88db6b07318ea0ba8973": struct{}{}, - "b1a24d272bd670879ac47c6e8d73a3f5959422cdb488365f49b455a7b578ba01": struct{}{}, - "f4c14a10405b1fd2f77ac7751827fbc712211f643a4e557fa149f3cdb64b2c3f": struct{}{}, - "5e05cb515b5e6aee84325a8ceeb28a92c2d8d01c6b0f4bb0e743bd04ece7557e": struct{}{}, - "904294d8c54b1c63e40832fa1d95bcde534b310df6d42882ce4baf28f3e0184a": struct{}{}, - "e560f6cb8384862b7c6768ebd40f2d8f4fa52b3ab407398a463a8b7b04297b2d": struct{}{}, - "750b0a1f894886df647233fb6bcd1465a152a1efe6dbc30d28f435942cf17247": struct{}{}, - "d6f9a6f9435c23f970f8650dc1953c96e9a625c8561bd37c087e4a3105fe8f16": struct{}{}, - "e7a4477ec945697c1003e467ec7b8fc2c485d90453882a203c08956d377d8cbd": struct{}{}, - "1980d3927f07fec9c8d727f187ddde727da7e45247dbb71c7555e5ae0eca66d5": struct{}{}, - "c6d17a3613b9914e68707fcfac8410f097643bc5840681bb533030d73cbb18f8": struct{}{}, - "7dbef27a673fea6454664f4fbe9685f3bc3cf1813eea89267bc99641e4d59517": struct{}{}, - "76d36e2a1c7a6749285d7c026d0f3bc4c642e09a3fad78b6e80c167281e23e89": struct{}{}, - "cf9c1cb89584bf8c4176a37c2c954a8dc56077d3ba65ee44011e62ab7c63ce2d": struct{}{}, - "3af8e4b69bdc2acdabfabc682417cc1d53b84d0437aeb3787a054bfc68d9b2d4": struct{}{}, - "bd5cff650821e43fd88ca5f9ade0e8b1987ff0d4288e511c941c7d241887a78e": struct{}{}, - "c60aac4fce685ecd8a3bc0eab2f9884c9488683d2be86e0ca950ae86c0843bce": struct{}{}, - "3c77f261a156a5308fee53720276395ef78d2e7367e4225a3d3d93f4accd1dd3": struct{}{}, - "72f7c894ad6c4ce2ef5483c768bb24bb83cc671b013d896e036ae64a8cbc35c1": struct{}{}, - "7902e27065cfa96f685ba02d8168505328e5046a6d39390fb24c0e5d780efa56": struct{}{}, - "e647ffe6133616f08e9e24cbcaedf50dba9e15c16850fd2c0cb79bd4870aa6ca": struct{}{}, - "cee73c8fd62a897329348ab9f0a4c9339f3433705bfaf36c9fbd448fddfd6223": struct{}{}, - "6c2d908d4726e707457a68f8632bb35ceaa60bcc0c09f4da86715c8e0f7e1a59": struct{}{}, - "cc8a30ea6faccd1bd3503e5a9001bbac91871be28a2140be347b2cc9d27d8031": struct{}{}, - "e00f9ef51a95f6e854862eed28dc0f1a68f154d9f75ddd841ab00de6ede9209b": struct{}{}, - "8ea748b09b117e6cc6919083ae680887324168be8e82565ff094302b0c260cb6": struct{}{}, - "5a5edf336a635cafefa91f7c281e4a83572831308c9e42f2dfe0d5ce68fb959e": struct{}{}, - "de2497ccd699b51eca535d86c2ea9f53a820318c0580dadb7bce2944cbdd9a99": struct{}{}, - "46adf0e4732a533782ba25f4ba0ad46d9a7f4281fbad0af29113fe24030d0b79": struct{}{}, - "20eb2d6033563aa5ccfd1139b30215effa968ab4b817590be3c81f4bd41f9315": struct{}{}, - "ac9fce49ac6263ad3ab6491bcc77a8f6485b0e1ac8289c0ddba2ce450e40d748": struct{}{}, - "7653097b0a56851e687b54aab2cd4ce5e61eb10b9ae82e8a6522018b70b6d477": struct{}{}, - "74fa5327cc0f4e947789dd5e989a61a8242986a596f170640ac90337b1da1ee4": struct{}{}, - "aeaa2cec33e27d65690e726e1710d3f4a99a2bf0ae9a3bd9087488f1dfb4d38d": struct{}{}, - "ff298e4c4f975caa25114d524167027dd47275f4bf7e414fce87b993ad12d058": struct{}{}, - "879d13f4257efdfd8c0c2731934ef109d5d5b2797d304d58764cc3f2de74e27c": struct{}{}, - "7020e57625b6a6695ffd51ed494fbfc56c699eaceca4e77bf7ea590c7ebf3879": struct{}{}, - "776488775500737b9e1072f1b38e50a1c9561c2a6ccf5d3e3ca4ec575a864192": struct{}{}, - "009dba7e54048b56a2bd3725cdf85248242cac1045ffd5011fc99b03e1ae9a3c": struct{}{}, - "b4e78f1caf9341674cb2b6c02e683b8e4b1fa5a495cc30bc7246f8658fadeffe": struct{}{}, - "c0e21a8ff85153deac82fe7f09c0da1b3bd90ac0ae204e78d7148753b4363c03": struct{}{}, - "ce0fee7e61f9c74f1110f0e5940a80b4f059f189217d0c3d26bb41960d4bf597": struct{}{}, - "34517c83d2a1c75c246a3565d1abbe1ddac6420d43cc2fc3023ae2e534dd9d3a": struct{}{}, - "ca37b0afd9f3b47fd58f4adad4d5763c0e9e9d41ea9e2a71901413c98f146a0c": struct{}{}, - "7a5df5ffa0dec2228d90b8d0a0f1b0767b748b0a41314c123075b8289e4e053f": struct{}{}, - "49dc32d144254d01d855278512b3136d081d8b3e48635d1d2da759d7257432b2": struct{}{}, - "9499e6aeffb2f5998ccd48fd550a2a74254575187d72e5fa0e8707edaa115de2": struct{}{}, - "93dbfb11695aabd44619dc641792a4dda59696e77188eb34f2db3138dc17ce4a": struct{}{}, - "6c7bbbc199d3b161dca15b7bf7a7c88f340c5373ccbe6fdc554231cbd5902450": struct{}{}, - "d936608baaacc6b762c14b0c356026fba3b84e77d5b22e86f2fc29d3da09c675": struct{}{}, - "076e71386ca0f0e8f4879ae3094aeaa1b6a32dd54824fac6a93d3aa20a88b272": struct{}{}, - "3edf9735de00fbd442979a178ac6dd7dc2794f49de10592148c66c662bd28149": struct{}{}, - "2d196c2acd92812ef99acd3dcdbb746b9a434221621e6926159493571dd8f71e": struct{}{}, - "6d205806e19dba5306a09df6e5b7205737f301bfaf15086e20200f356eb9ccef": struct{}{}, - "058ce9bd95f5595d789494945ff8f29948c9e608550bee23198c81333d6ac756": struct{}{}, - "ca46a3f7dabfcc476e496bf8b5100d6091734186c6cdd1c50d132ff61e0f4b7c": struct{}{}, - "b9d2c1bc403be29ba74883e6bad16ab0ceddf8417da2d9c761b07976cf80c1ef": struct{}{}, - "83fa74e70278e5b9ff3fa7995a40d04db8a0868b7b70695bc3dd7e4f96251451": struct{}{}, - "94a2d00996233e393e547a3ad49fa82efeeac1210a8fdf11dde35a23b7333ac0": struct{}{}, - "25c2c1522a862e62d8d9071f7affb07b314fa051594faab45e7fe3b189d9c0fa": struct{}{}, - "cb7a5742346e1c8560443d0d9b4bb7e1d3426500709d924a2e9fa7dd36984907": struct{}{}, - "a5762cbe2b09c01700a26ba4a6335c03088e8715d6cd354fbb933ca7f0d84698": struct{}{}, - "40f2d5de4a4456e3ac91fdfedf72b2bc2cf9bcb8300ff15a9f9e88439eff4a56": struct{}{}, - "ce8457d59078a699acb70416f88155a96a906b7b7aad43708402e3a3bcc8a4b4": struct{}{}, - "bdd8771653c35224300ff4c401cb3dbe774ed149bce566181538f723bfa81e6a": struct{}{}, - "6cc1efdf09dd35017ae63f538583c2d4be8ed1569a99d2c0bce8ae6af3fd5b39": struct{}{}, - "54c5b3dd459d5ef778bb2fa1e23a5fb0e1b62ae66970bcb436e8f81a1a1a8e41": struct{}{}, - "7be88da0f0940f5360e1862c2e978689a3a9b7bf2e4aee3bab934093e006ec7c": struct{}{}, - "d35a247e07491920dd7c0cf9a3d5719f6b5389160295d9a163dfd7b171c1ce86": struct{}{}, - "306331a6fe5c4bb7a01317402fd1b44b4927c118c450085491f294f5d3b290b6": struct{}{}, - "e7042ac7d09c7bc41c8cfa5749e41858f6980643bc0db1a83cc793d3e24d3f77": struct{}{}, - "52a29815b4df5a52f4a394ab950dd7dad5e43d189050c72530464b20827ff5a8": struct{}{}, - "6033dd6ed0058caa68b58a17cd367b146c900f43208e5316bfd584d03c290e11": struct{}{}, - "9274d49c27b7583a60a06420b820077e38c6ad785f6df54b667d42aa9a5b325c": struct{}{}, - "1c567a2ff1b9397ded28f921f1241db838cc2c1eda306341bde38977701e2852": struct{}{}, - "18cc4f0a66241122c86819e28935a275d9cbed5c72abb8bc8e5db6b65bce36f9": struct{}{}, - "72534c4a93ddc043fe3229ed46b1d526c4ccc747febdcd0f284f7f6057a37858": struct{}{}, - "b24d6d33736ecd5604a4b17bc9c6481039fac362bb7df044ef1c10a2bfd21db6": struct{}{}, - "aa6890829510931f9593852ec1d97209340be9883c8b675accdd54b582e32219": struct{}{}, - "ec3620470529f11448cb4ec5baeec3dc4395f58d69d5b99ce1891409a7c02ef7": struct{}{}, - "5dd52596b1ac068f8dcf355706800972a711cdfe45a91c6641f2587ceb70b958": struct{}{}, - "4cc742bf81aa756fa74066d890fca44bb9b3a286e08aa7116b958b58e0e6efb2": struct{}{}, - "56f09d7fa9eb77b92c741dd19409c0152ba19b22e0cde3bfa1e66970f8eeec57": struct{}{}, - "b1ebc709911cb51a7b0c9b4f6c94cb50a1c858dffd5c8b9904239f4b714f74a5": struct{}{}, - "2a6060b5e21aacf253c748f33b3ff1d088587a816bf8e997195c21543cf8ecf7": struct{}{}, - "2ac81299578013795576682809a4a16d1ecca67118a797ea576c113c41786849": struct{}{}, - "4c2af77ab7d1edff238c4b49c904acb8443b661000ead0fa44604e17c493f7cd": struct{}{}, - "63333e4b5d1cde5495c7538842b6b47ec01cbad8b87c94f569611fa7e0c64413": struct{}{}, - "4fc4f506ec9ef3a4e3f4c63c1d5ec83e5ab28165587936ed78a4717c0d1ffd76": struct{}{}, - "41a37f5b02ea65328a169b566cb3b46a3e74f5a910b7bd4d7328ff2c4142b3ce": struct{}{}, - "a4ecefa9f60571b47e69d56e22b2a6faabcd29b746e364f4eb655a9c7f927a24": struct{}{}, - "1a2d3e3e226b8c72dccb920c5b2f4ef7fcd7c4a58a9e26c44d7d0ca8f65cdd32": struct{}{}, - "6826b5fb2e3f645347c2e52a379f93737a6017892eedf7825a4c91718e68db98": struct{}{}, - "a8698d96861f332be3627b5461c3d4ac120295ae33c8e26b9c0db82afa657084": struct{}{}, - "dc814a25c3ae905ad4ba942072b101599f8e7c3617c79e3b10e9c20ca8952339": struct{}{}, - "aeaaedd290a4bb6ceb3e295d51c4235f815f2f2309d98fe68eaa365f6190a723": struct{}{}, - "e0c08c2b1f538cd00a812dee98b85fb52a7473b19c019da09ce539b3719a6805": struct{}{}, - "2cea274d0bedc39ec4ab6ba9e59ec889e3ed6fb56a1cf088a64d9b383378dc97": struct{}{}, - "d5a5d66b94e8da0cf63d4cd6d66cd489d78e77b697039c6c48e3ff8d81752139": struct{}{}, - "6cd4c9c389b460a1f60357bebdec27a788c4abbf1aa3dead4d3c0cfd0cebea2a": struct{}{}, - "8db049dc816a0d5a9b5567d71f09e642aa2ebfc26b2cae07a5afdec40b4a04df": struct{}{}, - "6ee4a469cd4e91053847f5d3fcb61dbcc91e8f0ef10be7748da4c4a1ba382d17": struct{}{}, - "8458b1d651d9faf2691730497b34526730947b758078610ec3a56ebe844fd1a3": struct{}{}, - "0bb9e54818a113b7490dbca2962246a313dd59b95613b679555cf66d7bd55931": struct{}{}, - "a8d6424c223b64ebd70977520cb069f81a9e15a119ff38bfb59f6eac0ac28f47": struct{}{}, - "e3ccbe1f95156325c612c031c18c66e0acae229edafa3e27aabb202486614043": struct{}{}, - "a57d374337978ca7ff450e4d6411113b7a349d47a6608afd72fcd7b8f3aab965": struct{}{}, - "c549addc80367e17fd46b5b6a094ee7f9958d5c92fba35f519e64c5a4304dde6": struct{}{}, - "84f081273670181b0d00600d4972de62686806e8e98e14fd423dd3ad6f9e0f5b": struct{}{}, - "be9b7607e070383c083b082c9c32d5509931bf9b297caf90bfdb7a692424c158": struct{}{}, - "df4ac416257333cf770e5b162da9c2a06b37e428d0a4035ec3a0f114df08d231": struct{}{}, - "27cc6994fc1c01ce6659c6bddca9b69c4c6a9418065e612c69d110b3f7b11f8a": struct{}{}, - "6ca879b0e26d002e180037ec58c256eb4c8a9e01024299a79779259d4445fd8a": struct{}{}, - "2a539aaf5aceccbfad6332230a84e699bfc385d570410f15fa8a2796d6c0ec79": struct{}{}, - "c349f8cfbb884b4735d054ee14ad6d5ab23b0d2fafa7082d0f1cf9bfdab67342": struct{}{}, - "f9d2c106af28efbe5f8322822defaa650e80ac7e33588f35a5f80bcc9738ff95": struct{}{}, - "496645fd7fc9302bc9955b4439722cdfd81a20b5eff797e5392e243f9cc86184": struct{}{}, - "83716a35515f728928d7ce0aaa73e3445679d939169124b88fec90b522c45179": struct{}{}, - "9ad241dcbf432e7b773cbd74812bb05a53418a1a385b304daaaa273947eaf544": struct{}{}, - "ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73": struct{}{}, - "17fc19d5d0ffa46dbe6a1c7c57e969aa6d5760544d96d5f2b0e96a1f66c7ea4b": struct{}{}, - "9a86f6d235b2165be08bbba962ac8b2e5dce57db71d3aa9e9b0771d828bd551c": struct{}{}, - "e871995955fd88922e9351f6a941fc9ba19bc25a885a2bd530b67990af9746d5": struct{}{}, - "fc51faad8db3c20edb316633958a93877f75496308e33f3ac0876bf4577ae313": struct{}{}, - "3abd72ec6352d6085d85e34f0478dca7d14ef8048f3c1986e28106d654713946": struct{}{}, - "a0e97f685e2a4e6dfa232df1cd4fdfc2f4609f42f519798aec31857409b5f0fb": struct{}{}, - "7aca6784a90c48650922820554679059fcc26abf90a34a3b0b4497ec6f9815d7": struct{}{}, - "4b63c345e706d2adfb0ad3e574b3bdb2307d5e9e79b122053b608c9dc86462fa": struct{}{}, - "5380b4faddb75750e020755413bd44fa783bd0a7c7c6bb09ee03c6944bbe881b": struct{}{}, - "a982c97d452cc71fde02409d6e6a623220c882930c3c13ab7733e61bc9870b6c": struct{}{}, - "d148bfa1bbe1ce4635f6bc654de582708d6efff9815b4ed28bd49b688830d194": struct{}{}, - "0f47dd87cde8f88e26dc1933266d37efc303a84dc0445df2fff3cafe7b13a091": struct{}{}, - "88a1881f0025cf7117501d07aefc5d4be6696656790d765678df8bc48ca52687": struct{}{}, - "59c9ae87c76b4851159c094254dbaebf32e807692b29d625e0b9351fd46be37b": struct{}{}, - "826ecad4ae11c8196ab3432ccbb22400691c248131b97fa4fe6f02dcf20f6049": struct{}{}, - "fd8ed7f18b1dd519356074c82b0628c97ad8c0a5a293fd634fa69dcc79a3381b": struct{}{}, - "27a0f645e20497a65f997aef769b2c72324d983a92faac73e9cd467173466641": struct{}{}, - "51d3ba50d3e136bc03ca019303427831f4f49d88b775b4a529685533c8ce0e65": struct{}{}, - "03d04f7fcebc3c449289bce40f9ec8c62aebca687e314ce13fe5ac33d02298d6": struct{}{}, - "55579b557896d0ce1764c47fed644f9b35f58bad620674af23f356d80ed0c503": struct{}{}, - "bbdefa2950f49882f295b1285d4fa9dec45fc4144bfb07ee6acc68762d12c2e3": struct{}{}, - "89e8b9518d92279489bbdee26f3dc646d921d89a15afbd3a3e0ad695328a3da0": struct{}{}, - "956baf0240928f5ac7576481bfb24926e8784b2e85ba337e1569a6238c915e16": struct{}{}, - "a4317424691a22559a3b3c42f64b5f2f9f923de8fddcba219146a1ba8373ab6d": struct{}{}, - "fa1baeb8e6f5c28f26997f63cc08bf08ff2632a58a578b8b971dd24d5c7d7863": struct{}{}, - "5f761acdc3b790a7bc2e24e9e55e44e17c3534f89585b6fc769f88ac8e55ac76": struct{}{}, - "517ae3f73216da641d8d3187ca354fec7ffae8fd5f41feb40b3395fb40cb4bef": struct{}{}, - "1801b094021884d7a92756d712748c8e26fc67e2bf98e60913bdbebca6ed751c": struct{}{}, - "1c6b6bb65eafa8aab6b7056da4da23ce2813eb7f9b01008ab9b87ad259dffb28": struct{}{}, - "400b181c878493867e7293db108d505116fe8e4d35686579769cca43008a272a": struct{}{}, - "002340b41aee7da76f4201bf18776291a812f796e20678c563b77b5b6c47c8a1": struct{}{}, - "864a2a8463c9d595dd4dff4304e1e3be1bb44951fe55df19eddc70c451fb3ea2": struct{}{}, - "075fbd08268bcc28d1b3a8f648393c1fa6e57576e670fbc1ffbddbb7cff627fb": struct{}{}, - "84cb65ccd3116c04d92bab796efdcda68d4ce24f9ccfdf8676f8f20e3944e509": struct{}{}, - "287782ef42356ef3d6551590f1ef0117b71d876df0f9b3eb58d088864770c74c": struct{}{}, - "dfd0dee33f082ff6b60d76ce06c45448716cfc8c1635bf1b0bfd939190e8dc51": struct{}{}, - "8a9bcf1e51e812d0af8465a8dbcc9f741064bf0af3b3d08e6b0246437c19f7fb": struct{}{}, - "e5fa94453a925017e514b2d91ac8b8013099059c3224e07281727104a93908db": struct{}{}, - "58abd882fba88d5d5d5f86aa60c1f825480353c496d0ebecb74760fc69001380": struct{}{}, - "3476ac0a0ad31ff107b215c718f456b79d4b62369a15b4b34559b9abb606834b": struct{}{}, - "3d7c42ace9e0f1f091ed192e346d4f18b890d0161bd1c4a94e85848f2108969d": struct{}{}, - "188a3c1a4978eb54d7a0669a1d04fb0d5c0519794428582814baf113bc39763a": struct{}{}, - "e35992a2d32be366cc26b48e6f5d306e20026ee8c845cc017837160036b1e26f": struct{}{}, - "2d2370db2447ff8cf4f3accd68c85aa119a9c893effd200a9b69176e9fc5eb98": struct{}{}, - "ecb0705c8343ba8dca57c2317dc23baae5f0a6fca6ea36b96aa698ed6db8d515": struct{}{}, - "7fdc3e8038e87ff56a0e429878710670c2b28ceaf3ece041f2e7e7866b002adb": struct{}{}, - "69e399851c8419993d46757025fd51e415e0277e8def8cb283dfbe7867212e8a": struct{}{}, - "2f0030c535193fc164e4e2b5371e8e676510cf0a64b2689d9aba6533e6ddea82": struct{}{}, - "04c2998f3d458c7724f8f3dde10dd431b4f2103e71db8b1e39aa2093f5c32e54": struct{}{}, - "4ecf565d4c1b1606facdba3ff1ee5dc3cc1fde93b74d5aa33e6d1c3690e5cade": struct{}{}, - "d0a800b5857c393719b487ff23ad207fcdc90da54ab6ec62f078808d17c5d8e9": struct{}{}, - "8382b57bf3739ad6689401dc5ad2c2c0ee86f7431cc90975df55e74bd17dfad7": struct{}{}, - "d254fa846ea7252ec95bf229075e9ac6bda6f8944f5e445e589a4d54184c931d": struct{}{}, - "6051c9461b4d52add5b7178b3cd16294899a4e70c33f2c9b747083d38648785b": struct{}{}, - "9b89025ce7a6d932b28f6e15132a70d402f723874a425e9b4c7cc3b179fa66ce": struct{}{}, - "dd39458a209af87dde4158d3cc8ad8ed07f0510f6527c5981778c1adc29a3306": struct{}{}, - "1642c1186f00eb28d57ffc9eb4110ee2aca5173d05e757258fc1195b836947d2": struct{}{}, - "381cfb6e1e428419d5d6b5d113be06a9023d6407ebe388791ec3d59100d87ec5": struct{}{}, - "25390976ce87338abd7440a4d0c20ab371f310e7ba3d024cfd3c48247423d65a": struct{}{}, - "c5b389beb081fe1e43ae92e895deca086b4eed5cf9efc7b78eebbbc9dc75c3f0": struct{}{}, - "fc5a299cd6cd644f40bdcc8f7ae00e89a4ae4fbc44031c4bc27e54dd4bcb9773": struct{}{}, - "52518386cc33022de894fa0af047bd62666a63c2a6a6e86650e26955058c5acf": struct{}{}, - "16c86833403ee05b5bb853e73ed3b64b6e925134d77aaf40d84e89ad7c32e2ef": struct{}{}, - "73c654b1cf677851b9bfb917f29cc64986bd9eea75bb0c0767778318f231b0e8": struct{}{}, - "01c02776d7290e999c60af8413927df1d389690aab8cac12503066cf62e899f6": struct{}{}, - "5515ea2b44a8208f9b2b9dc8d88b777c5adee7bf497b5d45f1d51cf2c0949abd": struct{}{}, - "21bb36ad06385b1ccc1f48489b71eea397b7e8b0fa39f91b920f51ce00af459e": struct{}{}, - "4e18123e0f06635b3cab7a620ecf4bbeff385a2f34cecd808dd232fb47b0e655": struct{}{}, - "bbccb9c002d5ed5994789fd8d38a8af3825f7691e10c44cac9b07dc54b15c2ee": struct{}{}, - "c02d8e6211ef7dc5af42085cbb323a122b76905ac098467ea1f85465ec14429d": struct{}{}, - "9c2364ba04c712fb921ed288c94cb6b14b85f9d342377d4f007ee4da089779da": struct{}{}, - "927a3aed189d610b2e151c4208913b3ed0cb38f6be613756819b1513c8924d7f": struct{}{}, - "ca4176138ea4086faada5ec84e0867418f7aa79a5a7269b16f0c2026fb8714f2": struct{}{}, - "95648b4276a5433d36acd80bbd898c5785b23a7ecc5f7a7c3cc20947e8d66ae1": struct{}{}, - "d8e161cf97a59773392c0b4305aa7bb59f42af85739dbfcefc39bb808c36c843": struct{}{}, - "211c963a9272a1405a2ae77464235dd515f0fb086fed0cf785e0ce5c13c07477": struct{}{}, - "ead6ef03d61ee60c533d6d450c50a1e559a8a37f6b796a4094cd0dac6b744428": struct{}{}, - "63cb946beb677e9f8d28ec5a8c6f6a929eb5b7dddc4b286f86345813c2d58e5a": struct{}{}, - "6aac1e2f1838b854ba9d1abf94f018fdf70cf4aedfb8e46f513f49854df3be4e": struct{}{}, - "e566be22896be151f664a7a1594d2eb47f785ca2da82623eb21dc61182431254": struct{}{}, - "a9e7a8af89a3e21f6b9ed2fc082a3bf5959de96015da2ab4d571b611398c0677": struct{}{}, - "5f2fd2b1e7efb00fc3fbe315ad28b695a67d28f3d1cec0182c28024b9539273b": struct{}{}, - "ceb00a91525da64fb171b103ae30ef3b817fa3bc78ecde99b884eedbe9131003": struct{}{}, - "7a6d85963363d1c23f410a9b01708742d7694efb7fb8b46a277786a0a7e9309d": struct{}{}, - "92d74bb8bddbbb9c8b50ba880f92f070bd5fe84e95fe68ee1e4e8a8c4440302b": struct{}{}, - "d62af84f475645962e8c15305f83e98a3e84d1e5ab59daba39b27f389f7d035b": struct{}{}, - "31fc15942b0c47f6c6408dd933a830b25dae3d9a0262dafe64d12e4492207e47": struct{}{}, - "5f55ac6c37dc9dee3b8e062a4f30e9ef7a32ad7eff40f69329f2dc36a3388eb4": struct{}{}, - "1bf0b8108131f88b2d98074f63df13139e4d1042909b2f21ed73ed0bef483a0b": struct{}{}, - "3f29e1b2b05f8371595dc761fed8e8b37544b38d56dfce81a551b46c82f2f56b": struct{}{}, - "defae04158b9c17d7b4a5f7bd72bd712dc1d10b0ddc1fbcc07bb76bb1d27b75f": struct{}{}, - "15d0b5ec64c82678556ee436658619e8e72aa9b730ba74872371a0f5796ae78c": struct{}{}, - "a925617886e8799cc5be05aee17f9b70fa3e91370a471e06c21277c10804d0dc": struct{}{}, - "50573ac999b4f7516852df798aafddb1ef893a0352b7c1c7559b984eb5a40bbc": struct{}{}, - "5359eec13763f1d5c49ee396739944e5bc4ca118ad681a0762f7da79e825edd6": struct{}{}, - "4f4a9410ffcdf895c4adb880659e9b5c0dd1f23a30790684340b3eaacb045398": struct{}{}, - "4c26991843b5498e99ef26e6cf45c4eecd9e6890436f619054aaa8790b35967c": struct{}{}, - "a51280f96d87bde0f6e5d292960f8d2d920dd6dcc6424d2f48543b87102d9a5d": struct{}{}, - "5336996af49cb064b439485455c14e0b378048f55d377224a2492429c65f64da": struct{}{}, - "5847cc4e0aed09218456f679deee2ef0d8c4bb6233118051e178d3efe106db22": struct{}{}, - "89d19d63941a517f4921ec8496fbf07c4b444537352cc585461a1b4de469fde5": struct{}{}, - "a48622b535728587fd351763d1296c7ec9cb5bc6743d5f22b011d5b5c3ef688f": struct{}{}, - "c7de95d6ead83b8cd59280b1c42c6cb4ebb0fbefb0e85fa9c385a0e5173d9509": struct{}{}, - "42110162e06395fd53647c77db6814026e22e26a10c24b98431ff20dc5eafbc3": struct{}{}, - "6915771be1c5aa0c886870b6951b03d7eafc121fea0e80a5ea83beb7c449f4ec": struct{}{}, - "2b999dbc95dba5e17c43a1e34ebcb097c0f674c167a2f897bd95cc195c40b679": struct{}{}, - "7477d53ca603e95aa1e0e0376b8414a7122a407cae1935fa44c6597db3bc8592": struct{}{}, - "3009be769fb8f956e8413ee9f3e0836e34968bc40457d0a10c549d2edcf00cc1": struct{}{}, - "f21dea74d898cfeaf836ecc99ad0331bade09711ff927365e91ada2ff4cb5caf": struct{}{}, - "fc0a05310e459e0ba64854688c7ffe0613c948e5756f3bd3ec79561dd9d39ef1": struct{}{}, - "a212f55ba9248db3c5c83e2efbdf2226dc936483170a7fbb5df49a9830201317": struct{}{}, - "9baed8fceea6e36d36670d72429d909547165efc038c293a14a41ef2edf83141": struct{}{}, - "7578da4482df9d21540cbd24115f066adb18ee769e07cb9bdaf974faa167245b": struct{}{}, - "7b3e7cdfdbca8bb5463ebc638eed83c93111f98f7464cbddd66a012630a88f29": struct{}{}, - "1057a9604e04b274da5a4de0c8f4b4868d9b230989f8c8c6a28221143cc5a755": struct{}{}, - "b78469618fb15871b9508defd1ff70014747c1f918e4185425c5f2bbea2a4e5d": struct{}{}, - "141256c5c1405ee342242e7937a27643cc87da3a54a0fb5cf6a703015ba9b0a5": struct{}{}, - "370b126df07859afa569cd82582bc43dfb2ce3ba8069dbbcbef6b7215b7a76c6": struct{}{}, - "9ed142d60c4571b67f1468ef4f75950a60a1d8dc1c38075cd0d13c80fea7aba5": struct{}{}, - "30fde358b34772de141e11ba599e28f9f44aa80ae89aaf243b73e6b9b9ebc896": struct{}{}, - "94a2b576a9f577e9a44f8df953328c8fe5caaa973be11c33276faaeeb6b297ce": struct{}{}, - "d39d350b3835c7dd15040ae50eab49bc92ede7ef9c49230f38ff665147eb038c": struct{}{}, - "749a1a16c343bf7b315a2142e36adcda6abecf8d04019f512d6b200286d660f4": struct{}{}, - "debee2ea79c08054276bbe1a28bc909e7878235fce05fd7badbb2d80168e9b2f": struct{}{}, - "29d8cda42c88fcf7e49d0f66bee969440926f49da5d2c575e64a40e815b86323": struct{}{}, - "d93acc6c69eade6fcbbb0ee120164ef9feb3ee30a6f4b756b868c16f951e0a51": struct{}{}, - "2623bb187d5aff1a31b932433e1ab0e40ed333efcdad4c6907b0e8430763d63c": struct{}{}, - "a3577b803a216a39c17d94541d6054bbe72fd11da8832e7b28d91277a2588048": struct{}{}, - "7b989fe5889d6645056f3124f6992096225bb949894c59e50fba3ef42a8b8fde": struct{}{}, - "ffad923c7afc100d1155fb4de849a025a9b725ffe7995bc33af8a091f60c5d68": struct{}{}, - "40a85d705de4f99101f6463a304a4db17395530d1277043a03c51af3ddda2016": struct{}{}, - "c83e25bd6aa0b4f5c9e3105c9c16b1d0be837f31a069246b4811c3e8e059a213": struct{}{}, - "ce39cc793733145876bc5ee6af24881d5ba8dc14cabc4895ca9ff4c4cd6f3454": struct{}{}, - "17c3a324486be8f84436b3835df56de14c8928f73504319e93c018e6fbd0205b": struct{}{}, - "960f984285701c6d8dba5dc71c35c55c0397ff276b06423146dde88741ddf1af": struct{}{}, - "8ce17c16d87a0874ac562bfd9a951a3611280a2c1166d37ffd1c8a8633399961": struct{}{}, - "27a96e38b0d13c91a58305f369791edc3ccadb88b979bffdb01713e88e357757": struct{}{}, - "3a9be8c308aa033dbcc45449f15bf28ac2523eb0c49a5acea4e504d8be846e1c": struct{}{}, - "7829dfe67fcf54311864fc39a99dd11ed01159c5a3c74e8d702265a6812878af": struct{}{}, - "f65cbc6391f5a2834478811d9c811b83f51fda4fc030462b9c08776fb6d78751": struct{}{}, - "c35d6aaf3b3885dfc9f36cddf48a65e93a919e13165fbbcfc0f9de5636279559": struct{}{}, - "ef74385116d34838bfff626124bd5f35786e40b6c57ebd85e14ef4dd5cae1d4c": struct{}{}, - "8b1e2a0d4f563fb671a9fec7acd396aa03a2d925db9ea007964b117a2bdc4dda": struct{}{}, - "eb10b8b7ce405250a382ae1793c8dc45028abd194529df9627e56833c3acb987": struct{}{}, - "4bfbdf7ebee257ff0658b04db68feca37c7b68ef3f419b437efef43d3f5d84b6": struct{}{}, - "5cf7d7ec1cb5c221981f277051f7f82a82e155b53c76ab9b6f75fde9641f6fa0": struct{}{}, - "cb37790a8586667fe9d52fbf3749dbbbdf05a5e6eab4152a8af7330c10ea60f5": struct{}{}, - "f28d18c311d2ddfec22673b086153fbdf91ea3b13175453076a143b494608d24": struct{}{}, - "313c1eca2a3941c23dbcd5f3804dded00c7e909746d56e065ad76d082002cb53": struct{}{}, - "f21adfd19983708d38559239a8fd880fb9f91b864d07fe7a80b447068a5d0960": struct{}{}, - "dc9c5edb8b2d479e697b4b0b8ab874f32b325138598ce9e7b759eb8292110622": struct{}{}, - "fdf1a318827fc75d565e808b26f2558dfb25f7abdee4352c4c3ed9c80104fd98": struct{}{}, - "4a046e33ecf7aced9bfd000747bb1fda7836c8ceeff662af33c2a2c288b4e78c": struct{}{}, - "327fc6597e6a5988c76a0d3c6ca06a2fc1484f56969e0755413a8b90ca4b89ff": struct{}{}, - "70b1e8e06785cae451496104850781f33faf6cc8e0777ecd3a9ccaaefb154b2d": struct{}{}, - "ca9cfcfa498aa21bd1c28fd0a3396fd6ddf4d97403d0b7f0ee88ea6b87ee3223": struct{}{}, - "266dd25dac03fce9c8fa8f7db4b64aebdfe91bbbae21b6c32ee6c9f899fe9a25": struct{}{}, - "68da4167b0cd79b79da4434a60e1adc3dbfe8dd789bb6e341e513c932f2f26b9": struct{}{}, - "11a4a60b518bf24989d481468076e5d5982884626aed9faeb35b8576fcd223e1": struct{}{}, - "54482595177116e6103b076dbf30648e5d0537dd1ed9cf5ae4562fa8a700d47b": struct{}{}, - "054442e3d5a92cba98291560bbb8adc85d34fcabca97bf780c3c20351b7474e8": struct{}{}, - "1e08e2ebac699af4054e21432bc9a1df1e96011ab607c542199404573bfc48a1": struct{}{}, - "8a3224be38e7bd4668d3b664ea7fee279b2ec661a5e3b7fbb36cd9d0a057b3c6": struct{}{}, - "d6a3891678e05bf86feeffaeb487f00ea394c659cae9fb15611c4ca50a1d38c5": struct{}{}, - "472bbe83616e93d3c09a79103ae47d8f71e3d35a966d6e8b22f743218d04171d": struct{}{}, - "0852de026b7efe21918afa89648ad82a8d3a211406709adcda46285c432ce099": struct{}{}, - "8dd835b4f3c41ad0b44f7a70c3cc24968e953c257f54ea7a90fd8478579dd816": struct{}{}, - "39bd9b8c2185593415bed100ac1cce569343d7078a64a6effa50488d594fdf24": struct{}{}, - "80993f054bf2fdd3708dac58005012d511cf335f5607a3c50b717df93cb174f6": struct{}{}, - "c31a1a405cf3f38898294e2e2f46915bd01cd5e9f802430c39b4e950f4a954e1": struct{}{}, - "b86a708eb9789fdbd4dbbab3848d86591f5da70f420ef60e39b405c77dbb393f": struct{}{}, - "65872ad399663af01a592f977d521f140e8dbb83a31aad1550d07492ca4901aa": struct{}{}, - "a1c2fb75a75ab8d81fd005016c77a9556d4d3e55bd0a670ee4330814e3031301": struct{}{}, - "27223b82963b3a8fea8429d8c46226e7060b180ba68f3914b51ccc119143ef65": struct{}{}, - "cdf497e1ec9505a326113e755fbb0816dbb3c80d2b3d3e5cd4b151ecc25f37e8": struct{}{}, - "22d2084f01936c564e690d170959976647b46c843bcb313ff7312fc169dc889c": struct{}{}, - "4e54907646fe2aa03e2ff2fd31f33efae134ec52c52b37df831c3ab6c097161b": struct{}{}, - "5c625117b5389ff38f1ef340cef16b9470b7873a512cc02c3522dcfba1ff1e12": struct{}{}, - "5d19115b2691291d373573fb347c57d65882836bcda5e3d8d6768e59a6311904": struct{}{}, - "4d78e62d899a4d7beea1ce3a7075e63336a7c6004828c4e318f9bfa18af222c1": struct{}{}, - "38bc14a268a3da3ecf3db0c08a82726c246193ba3e964c9165690e17fbffdb88": struct{}{}, - "f1bc1076b129ce68688f30bdf9e5a72e6460596c6a956afc96489ccf7b1f7a1e": struct{}{}, - "fac445f594a545116747701d3a307109432735698cabeebf65f09ac5d343d78b": struct{}{}, - "660f2dd5a2e102002a444e862b511ac51a4dc6636a46edba79d62037c738b9f2": struct{}{}, - "a7a4ded2d5035adb26a222c67032f04cfcd2279ab508cf2a7ff612aead97551e": struct{}{}, - "a8d92ff55b76e0bf5e39b0afa8a139ece43e420b6e56af16ab9ae14b576a2b5f": struct{}{}, - "91fbf526af406fe6eeb345580a747425764ed5f3dc45ec4440f8f70e518eb6be": struct{}{}, - "5d56039c42c9e9dd8c4ac940bf1116f02d6da1109a2b0698f00227cbb75413c8": struct{}{}, - "fa1eadc4c6995667412681c69ce33adfc9302a2965f521c40908549e670e2e4e": struct{}{}, - "36e6abf7ccb469f72d9b2ced171d6f62181e1f82eef6af8e36a3603a464c1248": struct{}{}, - "76063a534795d03a7268eeff1d2ebd3799210ed0c7dc4e2675447c81591544bb": struct{}{}, - "fe8787679c1af0e7bdf430f81f22a4d96e207adfd402d08e0e309471a89451f7": struct{}{}, - "a1c6cf1ec22f5d68bad3acfb42642e3b116b0689a01f12f40c28cae3b70d0df6": struct{}{}, - "35ed65b1eb2e87086e8d7ea0154e88d77febb8fc53af307f2a543b2d44767df0": struct{}{}, - "61dd29f2fd298a27f385c43ead74cbe5655322385b58438fb78078edcaf1843d": struct{}{}, - "966c12b03322d658ed99c9187efca250953b88ddf3ddca22764ce1f24b77249f": struct{}{}, - "caaeac3184e90c7f8587d692f03105bfe111982ab663ed6c6e1d0237eb3420f2": struct{}{}, - "5302acc68a1e82688d1e8fd5872b46ea23284127964d5e037f9083e3d734573a": struct{}{}, - "c7854c301113d781c6f3cc763390da65f6459ee30cc52359842f5901649abbee": struct{}{}, - "0f37b016b2b0c63b384229d1c514bc02a8033934d476c539b20db4e1067b107a": struct{}{}, - "9daec5c7b128bf6b9d85b1c9b570d265fd2c020568a40885569b9874846f37f2": struct{}{}, - "c26e57ce338c5af38f838e36fc6a398368b4dcd4fdc69993634345f602552a75": struct{}{}, - "a1a23d6d115b5fe7b9c3c0b58029968bcefc0c34d0afa05155029ab2e9cc8b07": struct{}{}, - "bf3f795d4bdeb5b070a878da64bad79a6c2a4c1f3039768d8dfd6ee85e845f55": struct{}{}, - "77d8f405f694e363abe1b9bfdb79fd4c497b78d8299532fb4743aea12f144128": struct{}{}, - "298c0a49ea75ef9b5737b01a997999e9a16a9abc000e4f4ff0ad1201adbdc4fa": struct{}{}, - "b6d56c61eac11d2b9474888513992d0de809da242200a45416b1eac6e4409446": struct{}{}, - "6941ce7bf0dc5b77b3c8876e8018830b67c60474f9ee3de608e27b390873fe31": struct{}{}, - "c314d9dc739577d53b765b0b1391fdd580b9c92a02626edec25f9c6dbd657b59": struct{}{}, - "4fc5c55702e343726497d2b4a882305a544a26dc376a1e297b4ffbd13617b189": struct{}{}, - "a1c8f4590a8806551ea4331fc86b781ea28639eaf11eae4f9023845f66d7cb9d": struct{}{}, - "52b03cddc5c7f312622a1e98bb4d7046511cfb7fa1abd74991043fd47deb77a2": struct{}{}, - "bc236a8abd26671b4cb71310f98c56a3222e3a13ae51af5ab59d38ffca2ec90a": struct{}{}, - "96672947a76c5f2cd0a4e39987466ba0fd04de224de465639da539be6a316d09": struct{}{}, - "4bdbc215d8dc3c571e802a69bced0c3071cc4a1f129ad97e15b357018aac6cd4": struct{}{}, - "fea57792e4dcc2fcb510e26d3a3610bb2528bb6aec99c3e28485212bd33a7d7e": struct{}{}, - "fa7c171ac94aac19ab64b4e84469f6956f9d89fe62fdf657b9323f34f83eee44": struct{}{}, - "6e4fa02c8da103154125c05f55a5e86ca6724c6ea981dddb69e234497aee5c84": struct{}{}, - "2a4af797ec28d01161e381cb6e8af633c5bbb6e3e227c04565efbf16da0b1a8a": struct{}{}, - "e8e9689deac5bac977b64e85c1105bd1419608f1223bdafb8e5fbdf6cf939879": struct{}{}, - "0e74acfc91692058bfe94761e22729b1bf2021ef8548e525107222869a6c7448": struct{}{}, - "c301f75ab52fa076c827231e613bbc976e26b2c1f7ddd01a319b2832b8ecdf9a": struct{}{}, - "53e03c06e295a8ef45405cd8093ee4c7e3b511326c7e21ea4b7259976ee6b31a": struct{}{}, - "802c038af88b868653c8905b8efd030c6c95341feeddfa795764265a60834af8": struct{}{}, - "cf2e5bff1ef03dcd20d1a0b18ef7d89fc80a3554434165753672f6f40fde1d25": struct{}{}, - "a75c0838e8467bc03dcdc6f75c12de9c1f825ffc14c859404e1b13fcf5f27b98": struct{}{}, - "467ef766006264233bc791aa57ebfbaa88a4daa3a5ebf7158a6d10ca03233911": struct{}{}, - "0d2c690e7dd5f94780383e9dfa1f4def044319104ad16ab15e45eeb2a8dfc81b": struct{}{}, - "5d5bddb577102d0a960bcf6fea9050c10fe5e9feddcb5c2170ccab872db9ee87": struct{}{}, - "d735fb5e9f2f61a33e8d9a6b862367bd645d2cff91c7597f4fbd17994b499eb8": struct{}{}, - "03b6b3e9163de296faebe3502b998148ae0a756cbb82382a18622a4c19e1cbee": struct{}{}, - "b13b35c916a90b68c08d5afb18f8f31fee1e2a284c780cbbe459628e68c97e49": struct{}{}, - "23670cabf30801a3160ef30746eed381ec94c674e9ccbb004916b1465c6ed8df": struct{}{}, - "7f18041501a6073856a11aeccde8725cb9fe20ddefe45b5772ae880290fb0f2c": struct{}{}, - "a5e7c002443743c5836758c7d1cd8ddefd9fcf2061daa0efaac683fb99966057": struct{}{}, - "bf0c97708b849de696e7373508b13c5ea92bafa972fc941d694443e494a4b84d": struct{}{}, - "40bbb39e6e686df2fa449a73428d9e704c22d6e8e5bf568cc6d5f2f392f38126": struct{}{}, - "f38a5076e7938612c4a5b7b601d6b8ac178a325d1f0bd03c7d3605756e26cc4d": struct{}{}, - "44ff7b02c80d38b26dd6aa31d9470aed81b32e10331a3c994fb1a9945fd847ba": struct{}{}, - "13ffbb2f9bbad7fa824816d9875b1c85cf0fb44fb7de46729cb35c8e3aacfae1": struct{}{}, - "1b3d40af84e3ff43d84fd0370eb4ac78408e5ee234a046b6cd93d86579fd3617": struct{}{}, - "8d20e19b8fc57df7cf425bb96337dd498403f13124ffc22bcd1cba5d9e8445d2": struct{}{}, - "7f2c7bc80b97eb6fd2b7932826ce72697648c1ac3284316f92aef0102a748c69": struct{}{}, - "3c13f6fd6f97586cbd0331bfe388acfb14cc5a45ef6359596faba428a85cb13e": struct{}{}, - "7c89187f8884524fa3226f25ecac2116520620e00c5c32464f48d00dda0ddc56": struct{}{}, - "9fbf261b62c1d7c00db73afb81dd97fdf20b3442e36e338cb9359b856a03bdc8": struct{}{}, - "e32e2774939d77f03ee6e90d3c97bf0b912e588da1850b2a484ba13a712fd829": struct{}{}, - "ec47860233d36cad00db3ed18c8ed7d57690cb8689caa86cce93ef75d767e7f1": struct{}{}, - "63331ed16f62645011bf44e29d81a2252405a5d1c007ae33208223bffa2cf4f1": struct{}{}, - "853cd68b703cbe0c70f16e39f18502024bf94039e77fd090ebbfc45d58b1b543": struct{}{}, - "97897a111d485af1048bece173d2980a15870ca079281b79c7ca755356847602": struct{}{}, - "acef2c15bcd349db900dffece73e1256e881c4416fc1f2d3a494640183490d9a": struct{}{}, - "214f3a838430f20ccd9ed41e1dc8983dfb989301ce22fd7676416ec73e34a6f9": struct{}{}, - "7937c8915fa6b530407bc78f3ea7e2de8a6f06bcac020513bf141f3eea661b44": struct{}{}, - "576c85c584f60555c2a024cbb99dfbbdb5f58c3bdcf28dbc8ea315fd46a388f3": struct{}{}, - "cc4b8681966824200a97403dcb08599b331a3bb63f61fba26a66458357fcaf4e": struct{}{}, - "ea3d7d28fd6e362047d6424bc9229093f3a54ed7d92b36f0d2858c70ed8d7005": struct{}{}, - "bc819456266596e4fa51e5dc58dd9bef751597719f6d9b1917edb807dffe929c": struct{}{}, - "14bbfb46cb710ab5cf477269d856fc79d4d5b31cfbe466a37a43b1537e0693a4": struct{}{}, - "753e295ed38b70b077ad5d61f6649d3af3f9cba89338ed75d291d34aedb0a944": struct{}{}, - "8de85d6d6394dce9a483b086c293bf02b0ed32545fbd470883b491a5e2b7f10b": struct{}{}, - "ecbd105358e381d778dd0173cd6a6db10f2f136e1810797c212b483fc42a02a2": struct{}{}, - "e87f468fb9123488462f1a724eda5ea081994db60e5dddd442c6f7a58d801323": struct{}{}, - "c33a70fa15e84f4abd822a354855ddfacd6da0ca4bc016a7743646f1a7af1209": struct{}{}, - "cccf52f3d03c5072ee64a136f74fc56b27e04f15ffe5d1d277d58ab18d94b327": struct{}{}, - "9d777935627a29c77604c57273520eb42635fd1847d2eeea1e7441fbaeb26253": struct{}{}, - "340b1ff7372469e7502bd59abb5edaed3d5af96770d79de01cefded797de75a6": struct{}{}, - "071fa0a12e65ebf73ae9a850e2758b7fd6291e23f4478d91c448d17d3c125a6c": struct{}{}, - "a0aaf2bf3dd5dd6c27d716ecf65df1e2980e6dad51615d3943ecd41568b71c8f": struct{}{}, - "68fcdd78afb0c7b3d671da85513374ade37d091722d4eae13d15cd4e975cc8c0": struct{}{}, - "3220a39228bc7a963b29aafcf511c8a83c17bc719b97210757123c93845060a8": struct{}{}, - "16f45b99abfda6364208cc5d3308269813641fab3ed9ad56affa18b1783280b7": struct{}{}, - "00aa8942525b763cbe72e4a0fbfbb027b909dd8299e9c6c85c28d3d42d2cc28b": struct{}{}, - "d461db619066bf79fdaaeabd94633bb4e0c1f1ffbd5251f3fdb6a90f62549749": struct{}{}, - "fa88de024364dbfcc278ce32fe7956ac7c2d058535f7ec9e8e017b8e3b77f805": struct{}{}, - "59279341ea59fbf34025024596b670b6df2c9f80e71b9ad19aea71ba43b083fc": struct{}{}, - "40fc6718a81fe76c5fb536e0c63d7dd95fc2f7f8a282cd1dbcd44c5b36c9d15a": struct{}{}, - "976e761dab368049ebdd82e3dc0e390d19d452db0f708d11314d31739c9dffd0": struct{}{}, - "afb69f68582d11e6058e5a4fd15d6ebc8a676ed0ce7b32d98f0d50f855403dae": struct{}{}, - "57f6c877d2c6118c8bde0754aeee6ffda08846339350ddff9fe2d1ce0512528d": struct{}{}, - "f6e6c79ba856ee1072851b63f9f042f5cb6814726ac5e8c14a91812cf98df0e9": struct{}{}, - "adec5f06f4290eca5edafd9d779f48f6aa4fc83c7a50cd1644545a04aeac2a7e": struct{}{}, - "16b02287e8a9f1d5bb5a4afacaf6ac7124c62846bad58d8f48acc27598a2cb7a": struct{}{}, - "381866f546ffc92b9dfef31e8ef8bde7ff39c2539f6596f77cc96cd5a0c18a9d": struct{}{}, - "7f8cafc8690f2707d1b7e8a40a59d8299e1af8f862576ba896acaca287fd4006": struct{}{}, - "7f957d1d021e91d92fe7894c697e7f3c81ea366d6720409f76b8b722538c212e": struct{}{}, - "73f91b8e33fa0f6931d3872a012785b54373c1c3712dad952a11d631d873c569": struct{}{}, - "4f32044a655f32e8528edea64dbfd11cba810b8790e6e6e23d28ad3a75980734": struct{}{}, - "e867882c95dd9c3f5a4f1d9d77fa71c54972b7e4849eba311be39b19bed707a6": struct{}{}, - "8ece232ca06333275d33d7601502bdb346d5986aec82ec74930a3aa8d31085bf": struct{}{}, - "d977c09350063e3e17f8c42740b796d03284b7f19036a2b223324c0a463f9a5e": struct{}{}, - "7ea4713e0da79b3e9bfb266986791f1840dfc018c761847e16be9e71334fce07": struct{}{}, - "ec0220043e08ee1fbca18af82bd357aa161e347fdd7d8e9c033496c8899bda0c": struct{}{}, - "a746222f09d85605c52d4e636788d6ffdc274698b98b8c5f3244c06958683a69": struct{}{}, - "55b11b41661ed342850364069b2d8b7cfaf41f2bcd8c3052c771d08eb8240008": struct{}{}, - "5ac14a0fe6eb7f49f401491907b2f3e2bfb9d3fe7d0014a40844446f5cc31f4f": struct{}{}, - "0f31618ba0cf91b57d27828e837d85bbfc6604fdedb5eb85a8e52562c2c8ac6b": struct{}{}, - "31bba0b69e94c90cfa7eec91222c770595748163573833cbe3589408ad5f64a7": struct{}{}, - "bd84dc633654d437de72ce67276ae9f9bd702392f7701cef86743dc571fb015a": struct{}{}, - "edfd3848b1c54746842714237c5e280daed12c2eae3d4ff9851d68f2cbf8ca5f": struct{}{}, - "f029f5c2d8fc9742c97458f6083c45585f3d010cc258d731b03593077cd5f717": struct{}{}, - "1db0046b8b195ee7f40e37963486baf6ed774f803e32049da6956eea3abf532c": struct{}{}, - "e175f9541ebaf21fc7a14e844918576a0c752e8a3cda53283de3d8170992b0fd": struct{}{}, - "e6bb5689beec52c4672bd4df92c3613d128d30cdc2b704f76953fe43d89c4ae8": struct{}{}, - "c8ce96a5ba0d15da2f247baa360670438c5f6cb2115514d8992c2e8c7c8ea7c0": struct{}{}, - "7cf6b0597fe4e3cb355932640c14cb6819d5359ac113af58d0b365c7a112fd31": struct{}{}, - "d0c04f4b1951e4aeaaec8223ed2039e542f3aae805a6fa7f6d794e5afff5d272": struct{}{}, - "e48115aaa0a686f3f8fffe2a96206834b027db2177781b3b71f6efbb032efea2": struct{}{}, - "5743abddddfa08c1e3a99fdebc2e8f3f1108fa12dcd2a8f58a42f141418c22ec": struct{}{}, - "1600725e92dc8cb0f0e19b982de5054c4cf2bd885ea84a1eeb2b68ae2ead25e8": struct{}{}, - "d93feaf8c84752d0b9b2fea5f92c86f1b95357f5cb18ba59121fe08bcb4c0b16": struct{}{}, - "d490c3e1528f59179e41747b5b1bc613b3105269ee07d04f9767af40af08face": struct{}{}, - "a0f2012daeb4a09f7b4676f0dcc5d312f3f79c0f612d8cc677c4b6d5cac64761": struct{}{}, - "bb44bf07cf9a2db0554bba63a03d822c927deae77df101874496df5a6a3e896d": struct{}{}, - "135e43893b558105420cd8de9cecef9e16161e78b218f5aae9ea89dfaf4f020a": struct{}{}, - "f6b640e738a5715f9128750b36b4bde7c9c5da9536caa20df60e3974e6901113": struct{}{}, - "12e1d34b00a928b8cebb6a5f739f656b339c0e9d2f4507148a5b1afdadd634d0": struct{}{}, - "088fd085c4ee8b621652675a55f7f1de6a73f653c09ad279f1603747356cb305": struct{}{}, - "6d594f4cca0d699cb0316849c24387b8c6c8bba8443230698ff87c2f234d54c9": struct{}{}, - "f902def4855ca65edd49983167186a7d9e9ea3386078ef59ce92970d291b2b8e": struct{}{}, - "34d250f31ee304607b7808661cda49ef7a11a19d7b7954e48d566e3435bd1ee3": struct{}{}, - "e9db0ee382178b350fc636773e9a6949f0c13d9fbc4844c4cb72e9a434792091": struct{}{}, - "06dec4024a1c9b785af9f9517784d9cb263b0bc57b688b9d02c3398d1082977d": struct{}{}, - "ad313e758ba53ea287c1f08054d22edc3d0857600323ad2c80c74f4dfe204eab": struct{}{}, - "7b6e2287c7edc5e9bc5b339092b492feae27ed8bf7f9e1a9f469594e1cc415cb": struct{}{}, - "f4c44266a2dfad9847930615218f2811c5ee3248c6df3d3921a6d2b8e67c8bf6": struct{}{}, - "1922b7fca936066b593f2b00c7f73fbd4e794946920cd715146f428e558f3516": struct{}{}, - "4ccd7412330594b4dccb7af06be1094f3c8f4b0675acf8cd73a3c5882298d4ec": struct{}{}, - "0a07895e533c6b44898aff547bffc7985a4c1eaadeabde909011aa5f86f149f9": struct{}{}, - "931eb6a232223dd22cb4a2fb453c6291d53f8c28d15f5402a1778fabd12d5fe2": struct{}{}, - "1bb216aee0416326c3924074dd98efb82d073f7686c64322a407c0e05de53b15": struct{}{}, - "a45e2df6b898e7c4fb895fd86ed479b7e158eb71246fae17e5f0b55e217d5bdc": struct{}{}, - "67e50b6f080a86a0fe8ad918933c482989c9d91394ccfa45a859c69bc4a6112c": struct{}{}, - "2da35120ab2f72d1843bfe5caca77222b9bf970e7b14542c54bafc04e441eb1e": struct{}{}, - "bbd0658cce24823c840689fa3ad8d66645a1eaf829349a542ceb40506838b586": struct{}{}, - "28cc570073da858ad7d33f685cf9d547e46cf2541aa91981f7f23cdd14233f76": struct{}{}, - "c33378f6784b0c2ea3b1db5f6f091dbface67d393dca85f127ada457b41e7e3a": struct{}{}, - "ffb572c3cdd46c7d2a4dd79eda5d9836ad414d95fd54830c5088f6b43a50ad42": struct{}{}, - "2976e152ef2a14ca931fcd5d6b8b36767f2f23a7a60668b8d42a08fceeade3ed": struct{}{}, - "3522b5775974ed99c1662d28b954c6682dba377ae7c9e9a2929ffaf0dab62b5a": struct{}{}, - "5a263031f40ce90052521bad4efa3fb3c0b83f389832218d46111a9ef44cebc6": struct{}{}, - "433c983628bcbf09e61fbdf18d48c34709bf2f28ce841f97856ca9f27c1c2523": struct{}{}, - "786b9e6f5aa97413101d5fba0119a0cc056d0a60f6f09838026781060d9b5983": struct{}{}, - "f062bde369b74d4edd20b634f695d383d031ff289389779f2b6966b6786b2248": struct{}{}, - "9c5b1e7ad20911a737c076f987b2b63bd5132257e36aba69af8afcc70e868d66": struct{}{}, - "6f110a959ad8eb088868a432495062260c1a9e66aeb23491ad24562251d72eed": struct{}{}, - "082c71c3ac0f7d08248e9a25ee9f83dc3e68814a226b7749c53ad52087be9afa": struct{}{}, - "b7f4af0c59a28f567251e0fc835a02c68687ff09dbe094d418421f891a4328ac": struct{}{}, - "81db8ebbbbc69c6c6ad4a6aa92b76e0c08af547da236b9e2c9dbe1d8285a8130": struct{}{}, - "ef72e8cef6e3cbf2be92e845b736a5859a001082f02c87362acbdce59a3f0b7e": struct{}{}, - "03e83a9d34d138e46b88ac3b1cf829daf96eef343dc2d29fd57415fe7d2857ad": struct{}{}, - "169cfc8ec9379ab480c27dfaac7d9185e5dafc936a033b5535437a541931eeb1": struct{}{}, - "082968e1da9cf580682e99ae94d78688d89684c0872e901c8b60cde135cd5369": struct{}{}, - "79b08f317051d4afc37ff26005772c7545aa741ae9f1e1a59754cbe807756754": struct{}{}, - "c55ec4bbe9c7c1614204f286194b109010ca0680f41325ec1a82302a34b4f3f7": struct{}{}, - "11a209155d1dc1eeea3d42c580b161fb43041463c328cbab45aa5fb74358a486": struct{}{}, - "d23ca746cc9a4da890ef6eb4d7bd5720b09743138b6def08d4bcf7e70731ef8f": struct{}{}, - "2ce62ac0f4f8fef6c69806a364e68810a75dc51b4e2e1155ce6170331e3fc9ef": struct{}{}, - "12ea12eace7d655f471ce55e34f89b1b77a3d9d05a445ca82877dd2235beaa51": struct{}{}, - "63e00d451a3dad9c4ef977c48c32b04366b1e9deddaaaf58e594e4de95b26cf9": struct{}{}, - "3fee73bd2891862f33bff6891002f86182b67f2a668cebc0384dbf336368cf0d": struct{}{}, - "22783f0252bcaaefacf263eacb5ec3603e66b4ce318cdcae0e3db9e624e3b014": struct{}{}, - "7cadab457ad8d811f134612436daaa5e5914b20dc2502865f714035b0f267680": struct{}{}, - "91326b67419dc5a29325fd7c37018b8bef4eea9f949d2a80eae6547b1b708c42": struct{}{}, - "225bf1776fa77021801be694c758a19978f8c284ee5a2aa876cc4763c266a6ea": struct{}{}, - "b75db7db79f27438332863e5e8d29af9402de70381280e6b1a86115f58ba16ca": struct{}{}, - "5310a6bdfa30f98e6483602c4cd758668dba50e64d7661c067926ca099c60ef1": struct{}{}, - "b3707838f2425b7dd13e573f15f57ec2fb4ab9265ca280bf70ba92653df99f11": struct{}{}, - "79b7a03cf4ad6ffbcbe06c67b36b6b6e175a3ebae47008955b2046626e29754a": struct{}{}, - "8ae9853763a9d41063c4d698cfd1231b7646873d6f61c6ae1d73fd103b04f941": struct{}{}, - "91ce3a1a85a2d3b9d4ee04faed038712bfb4ce60bb10ec5c58c568f3c1dc7299": struct{}{}, - "37aa022583059b190b36cffcd500e48dd024690cb202a9091a909995e6bbd46a": struct{}{}, - "db875b25b06f03288326ac9783c3bce4614677825e1025bb0df84d0dc2f537c9": struct{}{}, - "ca54eefa12ad778accd08856846e98a609a02faa9edcf9e946a798feafca3296": struct{}{}, - "af5df0db1afe88190f9215bb66b8eefab08a95f2a2b52b7c5b0e01a526843cdf": struct{}{}, - "f4182874e7466ab39fdf1dfcee79d988bdf5b9e3d371dcf4053c87c76736dcce": struct{}{}, - "915a4d79217889d31109054c716193f205980b6a17abaed50e3db5dc9c397ad7": struct{}{}, - "218d373c2875fac885aed35cd60d00febff0bbf61d28a799c39e815623a618fc": struct{}{}, - "0a7ab95329f703036d7f63b663fccc7b94ace84d75c480092bf49c65180c7dfc": struct{}{}, - "742d6900aa40c14fb7bd5f2110dcc9e56eb98fdaf4233825b3c9d28181d48c4e": struct{}{}, - "b9eadbe14eb5fbbbb69968420aa37cd1ca60a6402bf85edf72abfeffa5856abd": struct{}{}, - "01380046d1d8e2162c4da19af3749b5078881de44f2bf7fc354b12649274bed8": struct{}{}, - "29b8530f4725d2dd995db549dc127d5cf09974245c0a5d76d101a2d598d1123f": struct{}{}, - "24e4de8503edde8e5a73a40c3647c895215a8fda3de70d415ee60a52904bb3f9": struct{}{}, - "4ba739741e69e5d4df6d596c94901c58f72c48caaf8711be6fead80e2fa54ddd": struct{}{}, - "07fbfe7f7fb9fe5f799ab21bca5fb6014677643a314a46bee0e0a83eb0cf6642": struct{}{}, - "b1483029f303734b2255d7989d68b2f22619872bc53fd9247b960b46a679d52f": struct{}{}, - "3b75903cd12c5e8ad59f73feb4baa526ed3fe234f2d77d421d7b9d73fefb3f61": struct{}{}, - "2e52a7c82f850ba473093e6c20ac9ce5a107a3fb5b091367feaa804dbd86af2c": struct{}{}, - "cb947abb13104138fba00c631ec4198d97103adc34a28b0d05787bc474610bfb": struct{}{}, - "6f1b990f355b6b097dec7af77141f9ffe68fbffc2b98d4b0dddba87ffc1bfde2": struct{}{}, - "1b60eb2c637e75142ebb7f039cb8f7358b55c6517c8eb2cc1e90fa719424a52b": struct{}{}, - "0c6347f2a56013eb2d6f816a619d88e314d4ed0a8f0e3e72ff60b2a76f7aafcc": struct{}{}, - "1c1499bf9a69664a5cd92f84e8417cd778f33a4c13985993824e0e50c00de243": struct{}{}, - "49547872a664dd8e63283cdd747e9682229880a9111aaaa9a397c0458bbb0ae6": struct{}{}, - "b76aefa8fe4304f650962fb4af77b4db4b13d34ac9615391126027232ec16822": struct{}{}, - "23e2b040bab9c2ae5aafc7c03627ff499a824f6550ef7257b0874b0aff8a3174": struct{}{}, - "b6998c5a5e8f5937503c821c6563d204fa49dd079b241d12a3b865b15af6fee6": struct{}{}, - "5a8bb3a72359dfae11067f4e543b7af614e85acfeab8e0a4e2213e9809758f5f": struct{}{}, - "0a0667865bc17f9d624bcf11088057bbab46336e7dae65f3d5366f4f7a18333e": struct{}{}, - "fa76d99ad957d952fb3738ae1ed7282a18d3bca1531c7efc273d52e16b774bd6": struct{}{}, - "a7c871b8af5917609f00ee80ed04228f5ec939dcbac23a08a54f19f69af0a5b5": struct{}{}, - "19ca3a86e6e743148bb04e23011d562db20db443856f04a2846d5c91e0e5b98f": struct{}{}, - "3dddc14b1e23bbfc3c76544a4803864a4bd6d9e70022969c2d7109c2b8c91a08": struct{}{}, - "dbc936681ad69b9fc4e759a7958b45bc3315d7250c7ddf66000d65756378b276": struct{}{}, - "9850fdca22f422d915e2f282285378719ecea4770c7206b6cc37cd9e3d4968f3": struct{}{}, - "b94f6ca05eafa571681ccebc7e94b85e6bd5d93c559f00a4e5adcfe26f0a7c98": struct{}{}, - "8230de55977af18c6d0cd63db0d260cf5fbdf791ff79743060889968c53b0bd5": struct{}{}, - "7de728157ec38d950ddfc75c5764cf16d45e386f9d50d5e7e05a9ad99c1eaaef": struct{}{}, - "33a7d3da476a32ac237b3f603a1be62fad00299e0d4b5a8db8d913104edec629": struct{}{}, - "e4d6dc0f6e2842e950ae809a86e90456285822d9d350ccc4dae596e0a724d7a3": struct{}{}, - "5ee7931b7f82f7537d44f40d4f955bad832f1891235ac6e84725a08ada982752": struct{}{}, - "46ce6c77e7c928c2c081a66a75827e9f6c504a1eebcada9377a94aa0f7c193de": struct{}{}, - "fb878c6c166fdbc50efa9fdd312bd8d8fd5a672c57a42b9075b648dd43c25bc1": struct{}{}, - "81e31d25944c95f4d059d380ab2c6b38a4456fd011388de3e83fca540930debe": struct{}{}, - "1e30b14a8001c7393bcd26b1d6e093a55b527c57edbc4676ce8f3077a777df6f": struct{}{}, - "f7360ea7ebd04b36aed3a228b4a3fb5e24399dcd656bd705d36c402e2b2b171f": struct{}{}, - "522ce7057fd0523adcd6672db24bb671d09d1ffa2f1e7c97c13e6c68ae6fcb13": struct{}{}, - "a5cd1d7cbdd49c27aacc041890a06f107dc63b582e5fff92a45edb1c1f6106f2": struct{}{}, - "cf1aa821ddabafa6a16212eba3805fbcff92c6fc981d43967e1fca8657f8571c": struct{}{}, - "cd841ea5c2876e3f2db6bbba760b48d68f1804303956be76b4d730155bfd1665": struct{}{}, - "babc1bd8ec812c742b4598d05a104f582c7d9b824022f4972aa0c2d4a3b0c85e": struct{}{}, - "d482ba4b7d3218f3e841038c407ed1f94e9846a4dd68e56bab7718903962aa98": struct{}{}, - "d6d31104d1be4c4d10489ef907c229ab0aa157d057975e728406010142f5f79c": struct{}{}, - "259aa8ef98a8b91de574cd904138ef643240c23080cf24da4793a6f10a43fa9d": struct{}{}, - "fe4f74e910a5d6f3386bbbbb3409f050b100c7778381d6ecf5c58b1efe9ee715": struct{}{}, - "eae6d77e26d53f8b50aae22c0c31508293e4abe5056e6db46d13d681e9880f42": struct{}{}, - "6c94e35ccc352d4e9ef0b99562cff995a5741ce8de8ad11b568892934daee366": struct{}{}, - "35bafb1ce99aef3ab068afbaabae8f21fd9b9f02d3a9442e364fa92c0b3eeef0": struct{}{}, - "4f9f10b304cfe9b2b11fcb1387f694e18f08ea358c7e9f567434d3ad6cbd7fc4": struct{}{}, - "5cc3f82838ba7260203e4590ce03d00e1663d41f6a5167144f5c95d6be2166a0": struct{}{}, - "1c23bf80af726a00e023fff77ee026955af61c8427f1aba955068c80a21527f0": struct{}{}, - "27511e620b42e8fbec37edf4bfc765d490f326137a40a51837184f61b8aae39f": struct{}{}, - "3e2b8f7655a269367d0503ead3d27ef7e070fa31a0ccdff6d3150c67cc5c789e": struct{}{}, - "e5787dfc0650cea92482b08d9e7de9c1331a269248258c5e7fb36a4f6208a767": struct{}{}, - "e5a559c8ce04fb73d98cfc83e140713600c1134ac676d0b4debcc9838c09e2d7": struct{}{}, - "7c2523c985881fb2c2b4cfbe917eb12c4c4b61e898ad4e7160cfca487ca3c4f3": struct{}{}, - "b10e20a9092684b1b0a416950dc51e04a5c4001773c947adaaa9ddc8ba082870": struct{}{}, - "7276e0a742cf9880d1cfbb43a905e2ff6769bf461843399cfecfca0d68daf1bd": struct{}{}, - "65083d8b183b313cd39795a05e8ef3e4f44563e18442eef62e0410a56213904d": struct{}{}, - "fe6d3468cf5c74d8ec2a95b40f2e05338c37a4202f8fad692d2b64a9cf9b468a": struct{}{}, - "12775348cef809eb1b7bcb64f04f06867211ef3ea6b48a46e3a6a9e3a9d1b5cd": struct{}{}, - "4488b8b86b1ac061dbe37242297e5827dad889823fd1a5acaed43dec0108d048": struct{}{}, - "3b6dc2fe604d23d364a00bc0866da68b6afbf32296e74138c0c9cf08dcb9a4ae": struct{}{}, - "04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb": struct{}{}, - "807a09440428c0a8aef58bd3ece32938b0d76e638119e47619756f5c2c20ff3a": struct{}{}, - "4c3aada37cf7fd3819b2da502a15f78f7ce5a2ce6d584b630344ff00dffc74ac": struct{}{}, - "6bbed1b598cd62c008c238f05be1e5b5ae5caf07255352dcf63ea3e34c2aab32": struct{}{}, - "d3768aa924677985efce3a7f7e57522a67d99c07ab2f7086087205ab68d2b611": struct{}{}, - "6cb912de5b6ef87cb3ecf92c13052d80d02ed91771e6e640625d759cd444e941": struct{}{}, - "9c0736a282d77e28ea176f88efa2ed4d316a36f21e5f1e4b2b5cdfccc7faea64": struct{}{}, - "6f677963023a2ed99caf05f73ef9797d34022bca02970a2bd98c00366c4b1aa4": struct{}{}, - "5fd924625f6ab16a19cc9807c7c506ae1813490e4ba675f843d5a10e0baacdb8": struct{}{}, - "abd67856995c4e21abd6eb503ff8a267d0726f7d747f46a2a84ee4dfe57a5a2e": struct{}{}, - "61e24be6ea840d370643c4bb839fbd773fcb792dbbd77f7e7c26812b431f626e": struct{}{}, - "c7575274a20d035b5a72baa824b2b6f8fcc29ab184bd322947dd27b6168ab1aa": struct{}{}, - "d1775cdbcf90d7864101da3f728d64ef357441361dc31db4d6d62cf3e34c3656": struct{}{}, - "dc99e9aa86fab83a062cff5e0808391757071a3d5dbb942802d5f923aaead3b4": struct{}{}, - "2ae362226789f60cd68bac34360d177213feb7d395a6bec0e9e12267314345b6": struct{}{}, - "b2d35525e0cec4244fe6c1471a5a1f5e67f73b3860a638ab6cb9a2cd5de158ce": struct{}{}, - "fcc3a23fc7232cc89c7cb0f23d8774fefb73d7dc2ab22e6a1b6b8b202b4dcc91": struct{}{}, - "640ac649d15fb4262a30313816644f1f88714b824769549fe3c34fb0fe896af9": struct{}{}, - "f684da3ce7d63cde5292faedeb5e86cfb5f4002bd27a895028ea6c54ac7f6ae4": struct{}{}, - "33740da8a87dc7defbdbb7d06549dd8d82a33000a25293ceb9af687eb16bef22": struct{}{}, - "92c7d71b95dc6540fc58e891dbe649fe72ae5e93b5f42fd7fbdeefe6cef3e51d": struct{}{}, - "e8da3665e8c5a3d45213e3813c8a6059ad5c09824c2c308f66dcbd85803fa80f": struct{}{}, - "95bfacd3a466158c3bd4ef39eb9d29568b6a1aa2f28d072618c6098d63b0bb3a": struct{}{}, - "8001c27439650c5c5a6b4ed94163b5ddeb4476362c71380e613fa20dfffcef50": struct{}{}, - "31f6410360d73372ae62f506d309126e7f496ac7348eefd320fe2decff21bae0": struct{}{}, - "29f5f0cbeea2f41a57dfc489788abfd35e12b4264643ac0d6d298309964ecd8c": struct{}{}, - "11d1ff4edd53407199c92b00cbc3abe8c9c5d5bed4414ad07b55c896b0e7a713": struct{}{}, - "59a0838c05543ca73c58fbb65ff73c972ee47dd4a93e088ad22e8cdda9179f1d": struct{}{}, - "98fc07367b6df3e7cd043ee3cb37c3e4b69c94416e5b45494e9d3a4f1c1e3bbb": struct{}{}, - "63bd7065ef1e165a04255d4048836ce126dfe2b6a0777722ad3a5c6013129d19": struct{}{}, - "7d96acfad6e0dfa868ee9ad00fae5d4f50f8248a5e263789b9392604ccfb6a35": struct{}{}, - "a2e1c40da1ae335d4dffe729eb4d5ca23b74b9e51fc535f4a804a261080c294d": struct{}{}, - "5771742f0ab142bcf6defd21acfdba5539e62633afb3ccaf7b9931ae1367e9e5": struct{}{}, - "399e946a6df46ba43e7e31bbd1a72a50ef363f419f225c3956cec88bd332f2e4": struct{}{}, - "8e3f5e915594cdb4ef687b3c65e4706ffa0abefadc91934f0add9ce4570e4700": struct{}{}, - "42e1eb95ac3ebc9076c837c657265bdf53febef917d4ccdbc32c1a2aa03ee088": struct{}{}, - "ad1f3889d0032e7c2791053060e4c044837353325fbd42cdeeef65638ed31c2a": struct{}{}, - "ed823ec32c5d4e9ca9dd968bb0fe9366b7d904ce0cae615308ddd5b89f0e6a3a": struct{}{}, - "aa82088246685c17ebf16d48877686b831ed384ffdc42e76494283c271704d7a": struct{}{}, - "1317dfa6a0c51245a1fbd37c6de9819ac469d2e5f71f70a42eec6c6181a30fa7": struct{}{}, - "24d9c7b19834aa278ce9609a00d156f61bd83184c2634d1c2b1d6aaa9c235b3a": struct{}{}, - "4f682b71153ffa91e608445d7ea1257e2076d0d95eab6336cd1aa94b49680f11": struct{}{}, - "5fdb63561d5d9a409280a050f72f914e0568096bca53bffd83faf3ff242bfa67": struct{}{}, - "f91978d31296ff2daa5fe7e407be9eb378dea8440075c3d2ca1a6e11d1c00956": struct{}{}, - "0a95adbf8581859ae0cc477127abeaf4ad89916405c41855af8fbc482e1634e8": struct{}{}, - "17f8072c75acd1a2893bad0549c633a58b18e40db54cf72688908f91e056eafd": struct{}{}, - "1b365a4dfc8a9d9d77b27e0e07614e4001bb2e0ffb38676277f9155a082913c0": struct{}{}, - "d60384034b77c13c28402a35b432fb2a93a138a839a8f8f4d39d0262fb44651f": struct{}{}, - "5184a1cdb19a3bc3d64573002c3672095bc434254c2e53ccfba0a4801bb3a882": struct{}{}, - "2fdae8f4af69dec69db47f81e904a64cf38e870636f4ec90b9dbbf24d9678497": struct{}{}, - "bd08022e8fb80701512cd13c5f36b2ee06065bcde73329f41423dbd086c27281": struct{}{}, - "297581d6cd198a6e6df740f13288cb13a1e76cebe3f0ebc3fe259977addfd646": struct{}{}, - "7401b1cd29cd6dab9e4f5146a51b87921a5a2c6c33182b72c9fe28460980fab0": struct{}{}, - "b1178931be52cebcab5a0e5ef42521f6d42b34754a8b49f7c7e0eee34fa6e1fa": struct{}{}, - "6196fd3ecdea874b8005b7cd39f11d3c24e7fbf2d40e23a0f28cbeb56d5cf548": struct{}{}, - "1c4bba2c9841c310159fcdca086040b738abdb6da3e46093e82450b35d98c89f": struct{}{}, - "6aa072de79dc6569c2daad8af4a0f5ee38d2ae7801ee2c175aef67c4e8644089": struct{}{}, - "1f7d8737f70b022f518d7181ec439a24f09ca41df0b9a0cfc8b2622da537a10e": struct{}{}, - "c5c767165ca51f2522eed0ee579cffe84b6b16971fe6a1d545b66f57532c70ad": struct{}{}, - "c0232649e0451046f62aa7c778a522f9a6706deec8ac8690e31883459227749d": struct{}{}, - "49b191cf813fa62a2a280ca07b6812df23be3f3c2437604e7fcd2dee72f1f527": struct{}{}, - "99e367bcad7a502f3b7b2d0fe83ee65c16d44173e9425517c74d3c4c0f20fefb": struct{}{}, - "28a3a5e81d1e89f0efc70b63bf717b921373fc7fac70bc1b7e4d466799c0c6b0": struct{}{}, - "e564b4081d7a9ea4b00dada53bdae70c99b87b6fce869f0c3dd4d2bfa1e53e1c": struct{}{}, - "3972905cc58310b37c7989feb13592ea71cf0902fd0fe4415782ece0c469d22c": struct{}{}, - "051c2e380d07844ffaca43743957f8c0efe2bdf74c6c1e6a9dcccb8d1a3c596b": struct{}{}, - "dd29442deca69f52c50006b831cb216edf78a7da33748f0a80ff19f2ebe57ecd": struct{}{}, - "40265b78fb912d9d9eebca0ef5591ce210653c20ae287e1367c207c9c8736187": struct{}{}, - "10b05c446ea776cdcae1ab10f5dcdbad5eeb9250a8b6f62752e6d4e755850fab": struct{}{}, - "7a2ec40ff8a1247c532309355f798a779e00acff579c63eec3636ffb2902c1ac": struct{}{}, - "9f165139a8c2894a47aea23b77d330eca847264224a44d5a17b19db8b9a72c08": struct{}{}, - "1b83044895756d261c610bcf2192b2467ef43cb8b1807f8d38a7c55f80e389fc": struct{}{}, - "b49f56a7c39f0542fd8f3f01360743afea615e492ae56bf6b03f393d489b489d": struct{}{}, - "0bcf62b62f026b799d6245fc98591b58f6428db3a828303791f135360461d4ca": struct{}{}, - "0ebdc3317b75839f643387d783535adc360ca01f33c75f7c1e7373adcd675c0b": struct{}{}, - "e388075da4fa4e95da9b56166145f9685d1e4b08efc7bd5e493daa2f671b49cc": struct{}{}, - "850195f13775af5c90f37e774d700011299e722546a5116952e7ab37b880a2d1": struct{}{}, - "73a2af8864fc500fa49048bf3003776c19938f360e56bd03663866fb3087884a": struct{}{}, - "b37b8c0394e0257d333fe93d5ad1d44e1ad947f8f24ff5ee8ae15a6ce2f272ac": struct{}{}, - "fb9afa84739c3239a0f5a57153fa5e9e2367f1adb4d2ef9a2c47f2cba31a74b0": struct{}{}, - "a6a2729cbf6bcadce577a31f7f76201d5ce63c57d6c53318000d67714bb354ef": struct{}{}, - "5dc8037aa11096acfa23f0f76d5009acf149be3e5db49280f6aefb8951be5812": struct{}{}, - "a76b7f25b6ba5ec51bd9fa42f4143b63c2495996e783baa4d9f8459d314f6ad2": struct{}{}, - "e010fd1ce1acc173e3b4835b7635f8d4600d774869102adb5cb7b5d7895649ba": struct{}{}, - "0c64f3ba82e066a20f43719767ccd03d36fb2e2d96756e647abf7dbfac74168a": struct{}{}, - "a37c36f0e516abf8d53a43085a56f9e24343c6376304f61d6b000650160a4f29": struct{}{}, - "fb5e1549a36bedf179476db4ab6bbfc640e52a48edb67e6cf8c2428d6b516f08": struct{}{}, - "ba956e2d40f06d004341492e7015cc3773927ae155e12c7f7983d96d7dc60ea6": struct{}{}, - "82d243e857f79b158d8b0c0a0e610ead41aaa0b41faef577351c0e3871bd1a98": struct{}{}, - "7b3f758480080d56a2d09203897ba47cbb7bfdcf4c556cff99c3c35379d0bfa6": struct{}{}, - "1ce2cc4a11bfab284528ec8e3354251c202cc9207be419813dcaca5ad5c63395": struct{}{}, - "b20459ab886e03b9a00f952b4e2adc167f9af9b53b0909860e9c58c2f1cecd60": struct{}{}, - "8edcc26c0f6c7256a71a8ede6b954ad9ec8dabf88f127c5e9d34f13cadefe11a": struct{}{}, - "4dea5c7cb70f50322ec9d734aa4aa078be9227c05251e18991c596f387552370": struct{}{}, - "a1fb4e703a9ef1fa4936801721ff285a97ac85330856674412e054892afe6972": struct{}{}, - "7deead025aea61ac476bb9bb0d13b57a7306b15adea59c1f1b74e6f0abdaf7ad": struct{}{}, - "ad958d0a31b85cee3445c283105619ca75a123ce0cc809e8c8717c70d6b0a46b": struct{}{}, - "960a38ace75a2fe9426aa5d48b536cc6db18a2023b9bdd698e562fc3023585a6": struct{}{}, - "4a5098d9a7e98b798e599bfae322fd47ccfe46aff1937cce5181bb63d9eee00b": struct{}{}, - "59195c6c541c8307f1da2d1e768d6f2280c984df217ad5f4c64c3542b04111a4": struct{}{}, - "242ab4ac31522e28fb16163c45c0c19e4402c4a32bcb3b144e7c2edce675b150": struct{}{}, - "2572e799420e05c96454a89f1e5ae37546138611f946b4830fc374acccde4389": struct{}{}, - "e8eb8741d3daa5579df543903d9312e47f5b15a66af1c25a14f817c82763787a": struct{}{}, - "69abaec3d4243aa39755b273f5fe713736eba8cb6c937111698b8fb596aee137": struct{}{}, - "5a638a128ca67348f1073ba0aed26d85905a29128cbd71b250c9cf4e598c7f0d": struct{}{}, - "b78f00319bd91f39fcab8d53fc4d9a669c3667707028fd663223faf47c1ab195": struct{}{}, - "ccc68482d9e0eee0789e64c7674421076738f8836857ea89bcd0afb832bf3fc3": struct{}{}, - "984979b9928ae8d8622286c4a9cd8e99d964a22d59ed0f5722e1733eb280e635": struct{}{}, - "ed9f6f25068608efd412958da4dfc19328ca3511251fa6d5f9c42baf230e32f8": struct{}{}, - "72a415186c1324a6f058454bd6628ccfb2a2cace10f15c27c99c2422b5bbbf98": struct{}{}, - "00b8f9e229a8ee3e43e999f580113ea5458d9550352eb3b9366782171b6d5ea5": struct{}{}, - "2d5ff2a8a49505032f443c86ed9fc692bc3f85578757bfc93d44cceb990d8467": struct{}{}, - "083280ea67396bde2df5fde1839b36f48ab94fc6fbd978983b9a50cd59466a05": struct{}{}, - "97efedbc166abb3dfafb1c40a98730437a59555b70622d6aee4425689022e047": struct{}{}, - "57d128a0e308e095110d540bb58e7c5934839349a3fbf08d5578d66bd7978b35": struct{}{}, - "1a4553e03d660d5a752163ec3310f2bd77d2d26d7c1713e840eecf00634dc70a": struct{}{}, - "4387134784cf3aa4245b7fcf4ac045d0a449524e98ba5dab4955ff4631e89df9": struct{}{}, - "71be92cbdfe91357146afad81827a981ec58f5a3e69e9bd4527acd74b1927938": struct{}{}, - "9cca0703342e24806a9f64e08c053dca7f2cd90f10529af8ea872afb0a0c77d4": struct{}{}, - "f179a509d32bd92c923f236324daef85551c03a761bf0188be286a585281b4ac": struct{}{}, - "aa304b5d1d724b004084cbbda0c148aaadafd171bd6c5c519e42292ef18696f8": struct{}{}, - "2eff821d3e45468f13deac8d7f689a16aba442d6af2e5cd2e0bec42955f18ceb": struct{}{}, - "84f1bc54796086737e52fb3a28d3f7e33d030be3620594f315853c8aa40823b0": struct{}{}, - "3b03d4b408671f86c48dfe4391ab64fee06f40a439cb0b02750342339e7eec65": struct{}{}, - "aa0ab19b7b62c7888440607f13d6c1c5e76043d56a4fb079df5e1088ffbb4903": struct{}{}, - "83e19a9ce479dc064bab4bd50134db14918cc967debd3ad223bb8993c523788d": struct{}{}, - "cd4b0bba7f67328dcff29180fb217d06f0d3a43a95ed32d175797b60e3216f83": struct{}{}, - "18ccba186d8757c20cbf05d7a98b2c64f9f16eb64ea4a64659bbc5c9b7b3a7fe": struct{}{}, - "4812585e944994cb91cae8b4d8d87a155e6b1a165d8bdf5ab75752c4f04b9724": struct{}{}, - "3d7eb3d3dab17ee509a6911e0feb928eb46e956adcada792493614bfb97a7a9c": struct{}{}, - "b2c01c8a8a0d9a99f145f099a963021f010dc608a8e992bd1a2aec958b48f32d": struct{}{}, - "0b132ea2f6cd37dd630da9e84733f169a4473feaebbfa70359b449b759885ffe": struct{}{}, - "f98204ba6963009734f0398a80f8e44f9d3ef74ebb9c49e5d4f000bd1c102d29": struct{}{}, - "75fa219549af442b08a1b1fe8a9f718d37d6bc90095059e02e9774e8df17cbe2": struct{}{}, - "c8dacf657fe92b6064dbcde0f7888936b2c35871e6c61f7b4581accba92c278d": struct{}{}, - "e74ca13b4c0a61dcf7746ff71c1238e2cd1b5026838c8b3c5e147595aa627025": struct{}{}, - "779e6e7db9ae70c23eb9629c7fcda2d2b464c6e6eb300e2bf9ffc6035e65d301": struct{}{}, - "68d85a0a124d90d9eea4b9e3b436db429c8223911d52076d70aef4b78d9686c5": struct{}{}, - "794abe4382b236102732ccac42ed5772e6fbfe7863be2315b370c27adc1bd247": struct{}{}, - "d8710cb5d8462862cef18a37424c82ec5fbe971c2d91c4ab2e657d2566cbb413": struct{}{}, - "d16aed0a483a21d695fe5e210da62adec78c21631b5be5ff837e9fbedf2e6241": struct{}{}, - "b4d505244909f7a0e0bb8aae8846e0371c8428921a29403823b53388421ae8a7": struct{}{}, - "0ff352afddb9ea1e5d9e1075694c4f591406ba3ec87634d4aa7f35597e1c1069": struct{}{}, - "1b24e0c17e952e4c7caec5bfab3d9d82c41943dbb30c23fd3234b477c2451602": struct{}{}, - "3748fbd132cd645a37562a4dd529c91442bfa156adcf96a311c1e1ff98f46636": struct{}{}, - "ec54e99514663edb97adef400fbf34a77daae108303d3da8008a7dfb4cdf0f52": struct{}{}, - "0b2319dc1ccf78038936020d8eeef539ed8a6485fc6b9eb7826225a5276393b2": struct{}{}, - "9e1551e31b3e6fde97b3ad82f4e49aa2af71e0e684ae3a120ff7a228c7eed23c": struct{}{}, - "b9cb19b8f07ae256e1ac29489ea4a185ffd2a1762bd251378e5752dec3398386": struct{}{}, - "3815914799634fbdadf211431b8e372390fa35c0d54ed510993adb5b61525f48": struct{}{}, - "09166643ce02a8b4ea6dc3a4ba1e1008d74f7c8859bbdb4d9742e1819e3e6348": struct{}{}, - "4be1d4bba6cb8f95ff9817bafeea86dc609f16ec6beabef3131af7ddf3b436c2": struct{}{}, - "1e103fdeb16ccc3e413269a593820a1353c2f5ce1eae04284aafd0c133a932ef": struct{}{}, - "b7059a7ac907cd5965ebf254ec216d1d1cb58eb0205162d4c7ebd062cc893a5b": struct{}{}, - "9985c202d7aa32672eb27ec5069aaa799bc4132e854a94fd00b13baa954da1c9": struct{}{}, - "6def291378ec54715653340f91d2f1eb632090f69786e63cc9d12bf545762a2f": struct{}{}, - "0c44be9f7948957db073a1f24c266b07508b127ec030b328269ad052d7213aa7": struct{}{}, - "2123f6cda9b8e8b7d8fdd42fabaa5e23c82cce0270201fa74ac408dd5287de6a": struct{}{}, - "50828327cbf487d99d22c77ef8f980e5d7c8b93e2a1d1b084e2dc6760240e738": struct{}{}, - "2d6ccd34ad7af363159ed4bbe18c0e43c681f606877d9ffc96b62200720d7291": struct{}{}, - "8a628e2d207d7a6eadc59054da6af5dc80a84a1871a317be7f4cd40bf420b782": struct{}{}, - "01b3c1269215e1a058e7826eb2328ca23bb27e3f7fed88dfbd30141b4d7a086c": struct{}{}, - "aba4cc9b8af82df5d4efaade764d6a77466453f3e4156bb0b4e489710516b1c3": struct{}{}, - "3c0a579bbd1ad744d5bc792d120a2aa49dd8884b3fd7d423a03c7748c7854388": struct{}{}, - "c2665ea3e12568c37b740ffa21751b197aabf0a164a7ca562906bd7c094dde34": struct{}{}, - "627fd3d05a49a1839fad0ac397ca0321539307672bd923dbc90bad6218b993ae": struct{}{}, - "3e37cef1cf88ab02811dfd6a9f94c5de6c90283929e58ef8eeb47d862f9c1035": struct{}{}, - "6415fae65a2b6778589691301b8a0aebed88049b5433f9e3e409984ccc3b490c": struct{}{}, - "805c0cdff876faad854cd1e165694e85af9f156250676d281e808f9e2279a484": struct{}{}, - "c67c1263651459822c344e42d19ec6cef16c7d1e98ad491f96a0959a51ac15c8": struct{}{}, - "d4ffb2af32d16972d9f84678622fdc216e40cb0c6b7abb0d47042988ef1c79ef": struct{}{}, - "6f186f0b9303e2affde0b5d5e6586a633460a224b2a47f2a645cd5674185cf0b": struct{}{}, - "9fbb8412cdc93b8c5919da05fe115ab04af1b15acc0e14c91d025b249a88f268": struct{}{}, - "0e481e2667c3ae2f17a2c8107c67b22b2c296b2c550070c84050dc644a92cae5": struct{}{}, - "824ef7874151e9c8c4f7c3c821d0e5da563a40bbfad1675d991aafabecb73139": struct{}{}, - "128d9f96beb4ee77873d6f1b9009d0426a8308a1980adbd0c39a05510e01b119": struct{}{}, - "24024308c7962c1fbb1b0b8f481e167ec30215d9c54d19de8cad74a2e87bc70c": struct{}{}, - "cd36f58c1e9ee75f727713fa91e0809b863dfc6df2048e3c3a093173bf42b765": struct{}{}, - "2e0a7d656b3ea40966222c72d373f5d0aea6a99daa46fdb149e1846ede060801": struct{}{}, - "9a900403ac313ba27a1bc81f0932652b8020dac92c234d98fa0b06bf0040ecfd": struct{}{}, - "402eed114f0a583fb72bce76196539c9a25688cc8840c7fa44d54f811ac5ea32": struct{}{}, - "a9c3c1f7674502331a5ad3e54592d3eb2fc39cf78bb908004777ff4ac89119e4": struct{}{}, - "0376b2ffe93ba48301f7d99ebee86a1cad9daac0710a57dde79867ecd39e3127": struct{}{}, - "25cdb46d8d2115a24eb15c3c58f34f3330cfca4fec249ff70b7cad2bb560a0dd": struct{}{}, - "7b70d3ab4c7641542e1f158b458eeae7cfb7bdb815d4110cc6178bafcfdf43f8": struct{}{}, - "99546aa80b4aff4c96a297de944fc891d387ea03ac84a526d9c2c4d053acfd4c": struct{}{}, - "ef35ee2fbf0b1463273afbfd3c0041d989b1d00eb19a4c30a0a296f0109535a3": struct{}{}, - "162094c4d87101ff5aca2e655c39a40744052b4e1deb3f100391d45946db0bec": struct{}{}, - "8c59ba38922292b97661ebe3e61c8af913fa087a5bf535b0a032f5a373473c5c": struct{}{}, - "21f1df4cca171176e1e7f1e5744cebac5eff81ee761b6e695d2a2b37b0f8d49d": struct{}{}, - "856e8bd358ece398c72f1003391aef5d1cd4fb9146e9f280b86290ce670b5afc": struct{}{}, - "ee2aa44805b51b10883781d6754e94fd6c69bcaff7948f8f2b00c2ca02bc53e1": struct{}{}, - "e12f0205797766dcbd862d1c0bdc8a6a497ee7bfee39a48ae75917d1cf06b814": struct{}{}, - "cefdb23cf2ce0d0fe7eea4465da8428e1908e6cc50f0f38a71291e56a2c2dd20": struct{}{}, - "e6d974501f62a7a5d8a3c3a818d9ed30984921f6cde8190d55eb772cb185a377": struct{}{}, - "88669879cd6ae5e264a9fc42557664248ab45f267dc9906240e6f732ce7366fe": struct{}{}, - "784ba7f84648847e1eb6d3ee266b356e474bb14c808d055667ada615a95fc839": struct{}{}, - "8b04871d9242d20c23aee1c8b8018bf73414a1a3e1072adb9c34aece613c9fc7": struct{}{}, - "65304dac3823069673aa9d3b90dcb9f44938e2d12f58509addc915d08922b64b": struct{}{}, - "a2b507ecb20f46cf2cbe39c0beb1426e18a70e351db6bd05765e7d86b60d5639": struct{}{}, - "54d5cb2d332dbdb4850293caae4559ce88b65163f1ea5d4e4b3ac49d772ded14": struct{}{}, - "f876e8081636a84327a806dcec6be5fba9fe32dfc9fd5f60e86f5639f338af41": struct{}{}, - "9533327a239046b9fb62ee9b412bcd93a098721f6b4f72095b2612e4eedea38e": struct{}{}, - "21dfc18dd04cd6a356e2f1e36e8285bc060a5d589d3ac707e56ac9654903b589": struct{}{}, - "0cbd443a1d704e64a6da13d567496765c20361a18138b526257b5da4336a3fb5": struct{}{}, - "1bafa045e3a3f4b104d0a1afcbfc602c37fc6c5d7eccb16b99ca1bb5012934a7": struct{}{}, - "fcf3e61d45d82dffb8bf5e0d31ec1a37334624db53e4b08407b5606a04b38231": struct{}{}, - "2558a34d4d20964ca1d272ab26ccce9511d880579593cd4c9e01ab91ed00f325": struct{}{}, - "5c80565db6f29da0b01aa12522c37b32f121cbe47a861ef7f006cb22922dffa1": struct{}{}, - "c2d37dd0575766e4deedf7ba072ad35b87e9223af01d2829a81960ba7164f606": struct{}{}, - "0c3d12fd939e7a923853100f68bc9f1844adaf845c88091caa157336cc7cd874": struct{}{}, - "3e8e40c0af5f5ea5a94837f090631379e090fdb2fcf0c65a65859ae051e2c794": struct{}{}, - "99f7855eb789dfecf9ea66df3221a743f4ab8be59ae6b9c503499c1940a90fd5": struct{}{}, - "8aca4f36774f82a67c507cb9c96679482e2cc767f2d38502269557a566b092fb": struct{}{}, - "028a42e6a02c0c77481be187862d5bbca1e3a75316a1b2704d6af7a061ee49b6": struct{}{}, - "166692abaa3070a1460f932dab8836a167956d3f12573af3ff9b401c9825a27f": struct{}{}, - "abef947f2901d396510430bdcf07270fe5414f340dc84cdd8318a1ad08eeaa3e": struct{}{}, - "710482eaa280e6b7e9ed98958197686ea52772371d2374089e677ff77eae18eb": struct{}{}, - "c9344c5f1079f7ce9b007e604829f7e8e4516e9132e098ebd58e2cc7f2a5fd4c": struct{}{}, - "487ba2299be7f759d7c7bf6a4ac3a32cee81f1bb9332fc485947e32918864fb2": struct{}{}, - "5487d9e0d16f14a978c829697818b14aacc911ed4ee81bcc32b4c5a0bebcd578": struct{}{}, - "8f9602aa161e406b7426edafc6c17433c21e62c38d6b5a6b5f81862f9f7be6db": struct{}{}, - "74526f16c984df9b057949b19bcf3c7cd1050453d5d1069a736e753fdd4df447": struct{}{}, - "f7ace88a97d9f77d44e9e2be6b8baf2f83a14edd5db9d52c5f8456507156b557": struct{}{}, - "a12a0bd5407d1aa6af30e90bf49126637f58a61fde41ede2213bad594f18921f": struct{}{}, - "4bf55a9ce713ee0c1ca0a4d517cc5a5c3b83f7dd4dc5f9009b19b3346191e8cd": struct{}{}, - "ec5f0b0bf15f3e618dcfa50f21dd666b0ec96c4f38df0c68b8418c0d32b623d8": struct{}{}, - "3f5053b3ae70794bff96eb3501a85eedda4b3ee740c9207e6568df248a867c13": struct{}{}, - "400464e9e99bb03f0cd9732892d2547ac65073c5d32667c1d5e6639895b6bd6a": struct{}{}, - "6cadf2f0f34dc55acde751c0f5e4b7cae56694f304c41bbd77ae351421884008": struct{}{}, - "a4e463d7dec930ea7f5a7d92fc3b798a167c7c206438739d4c82d96d5ebf2cfe": struct{}{}, - "cf6e7cf6a07cd46118c5ca0032bdd4ff613fb990d5739bc5386d5a2c717c4be4": struct{}{}, - "847589e13d2376263d65710e4f230297cf575f632e722ec8e1c54e86e9147381": struct{}{}, - "e0550f209134e6202f5ffaa7733a9aa0bb20c1f25d106559b16290b685fa07ba": struct{}{}, - "3614e3639c0a98b1006a50ffe5744f054cf4499592fe8ef1b339601208e80066": struct{}{}, - "e15ad19de10bd476de9d996a9811e79a057b0ddd0bdfd092c6e2fc56700d2b7b": struct{}{}, - "4c3cc494be55f486303160f35351c4d56b5d02ceef087ca665e4cc8dd0b299bf": struct{}{}, - "e91c254ad58860a02c788dfb5c1a65d6a8846ab1dc649631c7db16fef4af2dec": struct{}{}, - "26ce1a1580f693873b6268fef54c5f0d0607f2896cad02ce2894c0c899a11575": struct{}{}, - "89102c5116c37daa55d86342ce2c47d35b1bc2ed407509732d940ea2901befbc": struct{}{}, - "6038ac4d789347a54e09f7f4e1103733da8ebbf5cdb29b2834d3ccecb2dac7aa": struct{}{}, - "06ac27791a2754770725be033497f19f58acdee79203c2c6e68a73b4e6e12981": struct{}{}, - "833dfc7e3eb11230369904322cc8481b2b9a166d965557f97d4832cd92e072af": struct{}{}, - "8028fa2ee1328603ede76823df73c43d62978921b76dd6123570721deec68b92": struct{}{}, - "48cf13f481ae6828d98963f106d1e60961e8eec53971d98dd652c55e9403fc78": struct{}{}, - "b88b5eb909d1bd5215ce6dd44e64244afad213dc77b77691cc124a4621b30ebc": struct{}{}, - "1763517219d2d492f2b40914cd2a8185e643da55f0bcbcf001d0166568ba20f1": struct{}{}, - "d133efd9800e396abbeb9de0cc7a6f873d58159121a0fc587f8a2ec1208ad50a": struct{}{}, - "6588ef4db6a357d6d9ca7d0c9feb69bd8e2f236ab88459da5c193b7fa9503187": struct{}{}, - "48f9460fe0dc9f272e7414963dd2b52287ec07d872d665d2e9364c957f163ab0": struct{}{}, - "b0e28b52361ccc296795cdb6956329d2570dda297e556c0a1ad9bd8daf30530e": struct{}{}, - "d2a662047a6fad77347b676dedf6755cedbfbf8aef67cae4dc18ba53346577b5": struct{}{}, - "3f92df367d627e2d26b0bd63d4e68c7e68da9d6f4dbbd8e99b78cae6691f3ea3": struct{}{}, - "67425801bf1a60fe7ca30d5eb62347636dc487ecd4de65b328beac381abfdd3d": struct{}{}, - "992fd892370c5c12338fa41dd020f89f9756373823c6267f080d1a1534b7309e": struct{}{}, - "386a85d8c88778b00b1355608363c7e3078857f3e9633cfd0802d3bf1c0b5b83": struct{}{}, - "2867f306661f287eb6d35ce40322fcd1399ae33741a0b516837a43a49fb24895": struct{}{}, - "9a6ece2ddd5f347183ff18cd0f5a0fbb2d28a48aa2f86d5f3b3b3ee8a93797b2": struct{}{}, - "1afa14ce9d38f3d89a68daa44edb9958ff556bda8c61214a9d6e4cd46b30484a": struct{}{}, - "ba24a2890228ef70a16259bb9b72fb1b42ad5f29d7c60a0298456adb4ee11737": struct{}{}, - "9fc26cce6e28b3113ee79002e63687f62578ec8085f80d2d4d6f2e4edc61d14a": struct{}{}, - "a18603086e5bdf9df88ccc9f5a083fed093e819976e87456b74dafcbd7011114": struct{}{}, - "8f53dd914ced58d84b4d816958f1442f59387ce7eadb03fa6aed193a512b2e5f": struct{}{}, - "e0f4f767ac88a9303e7317843ac20be980665a36f52397e5b26d4cc2bf54011d": struct{}{}, - "908aec4512d80ff4fefb1970899091e9de8e734b36b8fdb7678e77dc092f6959": struct{}{}, - "3ac14fcfc2867544dcc68a7ffba96759581269395bd34d9bd5c3e5993d499548": struct{}{}, - "599491574e0c67600b04916d5cc046934c3c4d0920d9731ecc13720c7670f1b2": struct{}{}, - "a1bd1312d23002be258c9bb4642bbea77580353869a8ee8844e6940b7e0278b7": struct{}{}, - "f76660f75ff19a500e061678a57a50c9ad0bba521cb5a2b7d91733c6dbb55855": struct{}{}, - "17f29b073143d8cd97b5bbe492bdeffec1c5fee55cc1fe2112c8b9335f8b6121": struct{}{}, - "b96482290a873ee9875236c0b4455988a10a7ec28bba60419d449429d0ced0e0": struct{}{}, - "b02620d925c8ba90109b4396bec9fe2e108a0fdb662c7148dd8e39724bbe09ee": struct{}{}, - "5b90071aea261a5b838035eadaa5066ac347a6cff1b528053c57f858c6508a27": struct{}{}, - "2e13513055e2daf87c461b7852d07946683effdd4a0801a0febc000c19f448b8": struct{}{}, - "7eedf57ebef3cf8f671adecd0ebbc265a88190022736057f8d4a7b94d03e6007": struct{}{}, - "176473d7313395b6e209bc6b1d57aa160b628706860aa0554d7af60a1d40ab87": struct{}{}, - "accb7fc4d0144dcaa760e485a8100fc977f3404d3bfdc8e589384f66ca934330": struct{}{}, - "41f3e15ff8a4e4117da46465954304497ef29bdf35afaa9e36d527864d24c266": struct{}{}, - "25231742218d8b45cb76ec403ec8b7ef37c8cd70f9504f4f9eaa95ab55bcc0e8": struct{}{}, - "da2da8e71c82b18aaec0e9dcf817ab09481a8b55061066f011b3e38188788f65": struct{}{}, - "e9f2a0186210e30a516d12b001717fc17b1887acad69faf5c2141067f3f6b094": struct{}{}, - "6d87f9f7aa10d11b2f2740b1fd7397323109c2c6f3c205f2c2a5324482973304": struct{}{}, - "54af2a2960e582263c45971cdd40da4ae31ede1db5395629d910f056479de12d": struct{}{}, - "e9b5f32da85941f4d747effafd36c5a1cd4b0de9632d76f147fca68f152eb9a6": struct{}{}, - "c0d542fcef483aaede5ec4c885205b30969f9545fab63e3a37d1304f7270eff2": struct{}{}, - "a844a5a23ed2570a7121c8f81767b23abf9cd0a2a1ac594b070d4f612efd2a47": struct{}{}, - "8652b6b8e1b1f91280c94fc023bcf3b50e8c2b17e515599ce20237dfb11e325f": struct{}{}, - "e2dbf8f5c4cc151480213d21f95c72aa73a001bce4915b17691ae40952dcd793": struct{}{}, - "81c52b4457b4966544ec582f4e1e6d2e72ec7091ebe68172b2d4dc634998719c": struct{}{}, - "a8c33e6ffbdcb11c67d4061ffed5780a894581b0b8ef67226b29fa2cabd537f9": struct{}{}, - "5ef2c394d5b63e4175cd331c74c8453c3e36eb8f47f6d648397ff6c1314fd705": struct{}{}, - "62228e6f9859b7298a5fe862ab0ce1275bc2bd046a9308a2102968196be803d2": struct{}{}, - "ff63d149bd73429d489fc9d1fa3cd1c5fb7b88a9c82694bb5e84ec700c4e0fad": struct{}{}, - "72b672427a922abe0e4297dd5cd1ab4921033417dd577a1e6d4e954e072a7139": struct{}{}, - "630471bc17c69d0bb5c0b2a31b8faf762b907eb27fba3204a0f15695d8f804b4": struct{}{}, - "3f49dbbfe051cb20cc038923424fedf8d18307cc805e1520e4168e9360e2eb38": struct{}{}, - "e244f187f696561d5fd7e00f618e7ba641dc52e3c137380f6fa23a854b773aac": struct{}{}, - "fa39223abacabf18c7732bcee269d084895286162186ed574ec169e47c5cb3b9": struct{}{}, - "fe68a21fc76bba7b3a3d8e454eca8cd258de68fd08dddf035f23ddbdce6fc049": struct{}{}, - "816e2845d395e7703abac2dcbf9d54e39236fd39133362bf7ad3fce70dd7d78e": struct{}{}, - "8c0e24f73bf5fe793dc45e5f90b5a9682c0cb345f524ddef77ab2859b9352d52": struct{}{}, - "6f4803ef167336c789f93d693c58eb9caef044f82c5d7907638365f158962537": struct{}{}, - "82451d3cc87c1ab4fdf9025bf0134ca574422a802c4a3543b5f80ecf0db3b166": struct{}{}, - "bf65266d0d8df3ad5d1b367f578e684433c4556c37548b6f5f37ce74923ee9c5": struct{}{}, - "902760138ab456912e88a9c4a0f1d2a79252fa844450c6af020a7cc903fc7d34": struct{}{}, - "15acfdc75fdb88851487238cd8442c5ecc8e0c31868ce9f52a4e2361ba899f2f": struct{}{}, - "2c371c2ada73a02e26e14416800e8a49f875d27b8b5ff31f1dbe37bdaa6d7faa": struct{}{}, - "ea71c25a7a602246b4c39824b855678894a96f43bb9b71319c39700a1e045222": struct{}{}, - "e2bc512240e4b9aeb3c695f3d9f29385fb87b759fa3141421d5d298c3fd1d1b7": struct{}{}, - "c5156f39a155dc85b08f47cc4181d75f21af40420e2c54283156c06c1d48e04c": struct{}{}, - "20ee235b5de5b36244da6f9aa1cbdd032a90867ba92276ccc8c38c0d0d57fcec": struct{}{}, - "8ca58d38650ac6f95ca752c2a80649a02cd59c099a272775137e50843d26b87c": struct{}{}, - "df736257aa81e4a7b6c3a0c0b315b2e1edee4bd2460b10ba0307aca3aba57378": struct{}{}, - "fa070df35818ee454c2bfd4b44cd7f4b838b5a26e8bd2bf9392763356a117d48": struct{}{}, - "d166139e8c419032c6773f8ba4bb04ae314c4ffb5880f693e5f64f46f4ef762f": struct{}{}, - "7baa68f2418ba82d2545a780c00d7a8778249bbcdaf7369114534874ea6d3bd6": struct{}{}, - "f02c96357481972443a2eb6d2861c803fa7790fb7d0225b2f54b6beba964f4ad": struct{}{}, - "fca30679635be3bbce1ca7d8a9ceb8f0daceaaa80e4cf645584db5ccc0dbf0b2": struct{}{}, - "ab0fcec08e18a7bccda1e81df4974e3434fcd36000518f7e776fb85495b5a494": struct{}{}, - "330fc5128df4ae06a4853268a71c7880f28aace282806be3b7dbd3b24a396b2a": struct{}{}, - "05b8b79eea5088487f0312ab244e3f542eb61d9545791b1a9992975767d6d227": struct{}{}, - "2cc730007ee3e13d3c87101733db0c12d49bcda32517204b63220caaf87d4ebc": struct{}{}, - "057d11446d7249dcbc77fa8af283768f575d4f35f8dd86dbc332fb4d690c28bb": struct{}{}, - "cbcef83653166e006cc8146019cec95afe680aeaeb8ede63a8283f759c48739b": struct{}{}, - "f9cde0beac8268e430eb18974225d88db6495c4bb31c1d3f6dd7407bdf5c9443": struct{}{}, - "12563a71aca6bdfcc2f529eefd79e76e66047b1d356f0ec1644100d5bc409db5": struct{}{}, - "2fa9eb130ea98eb629410e0bf631f0b8de277f37e8b60c783c75e6b56e42cdee": struct{}{}, - "2b5d2ba5803e6fe3a2bc08b5f3965f47a92918a9c44b8090618d5d5a2876cf8c": struct{}{}, - "cc399d73903f06ee694032ab0538f05634ff7e1ce5e8e50ac330a871484f34cf": struct{}{}, - "7a76e4f154a508ebfe238db49e2406900c6d8c9cbdbdc18d88c3d93239202499": struct{}{}, - "7e1c7ff27f0acb4ac84cb6fd4e734e7c7e0a599adae6c1c5a16e2cb195abe429": struct{}{}, - "ac7a89c282194aa6fd19ee0916325e2e5a7ccb0635056a2dcbcb084bc1f71fd8": struct{}{}, - "265ce76730503a448331353d6562c3d2092636a9da448c246d6366abd19bddb7": struct{}{}, - "ee0874170b7f6f32b8c2ac9573c428d35b575270a66b757c2c0185d2bd09718d": struct{}{}, - "81cc5b17018674b401b42f35ba07bb79e211239c23bffe658da1577e3e646877": struct{}{}, - "16f5e2322d05ddf58fa74658db9563e0d9a1b76fba5a9e1fcfdc0c929fa913c2": struct{}{}, - "7d87a3f2a6abb5f836eca15337fee30d868d11f77d049e12c9723685f27f2714": struct{}{}, - "42ce1e72d529cfe94e9ee314ff1512cd57a6fc0a8fa80d8c57b68a3ed2aa047f": struct{}{}, - "f78b1bbdaafa735f1912927e79c9e160bcf40c8a86c9c1507bb4427fdfd9f452": struct{}{}, - "b1efc77a39364d74c235222399e56d46bc1bf795e6eb1c39887a424cadaf5357": struct{}{}, - "1cc9ffadf620e6e34a907b17ab26ad2fe33a756c3d6b572d5c180cc821cf025c": struct{}{}, - "1c91aa13c5938d15e4e771fa65b286f7562761476104e5a473bfe0428b65e67f": struct{}{}, - "b2c3b64656b521eda2411690b5c38b0308b7e66dd374ff690aa6d293ddc5ba88": struct{}{}, - "7de97367c9cdc3c6db31aa114057b65cea1a7bafc71cf0595a2931011526a0a3": struct{}{}, - "7c68b70cea5555e5419f4bf32b38a9b7700a2c7e1d466d644878afcd48a867ec": struct{}{}, - "7e73b026cfd1b008c5580cc3d1d65bf7e59e70f8fd934508c40ad473835ea22a": struct{}{}, - "30dfbfe2e35dc3f37071155dc9c4165c5852fd7b974678bce03d5beecd1cb5cc": struct{}{}, - "7d445240c97cb8b39b22030981d77679608f91c7a4000e41a1794cde953a1846": struct{}{}, - "28fbf9617c161595d4892702d5909144f3d6702d5010fefbb9e46b8721ea7fb0": struct{}{}, - "9d888b8fe66478ef3375dfd1cb48d43b52d60fafb4d53f66a636ca3bfd8e4257": struct{}{}, - "a5add13655291fd6bbe9175d4ef176dc0d3ed156fa1f2b388326d514bead3d6f": struct{}{}, - "2d1fd0086934aca9129323fd90ba6b15f8bf49189888307a3a0a743e9211d084": struct{}{}, - "e8fcac1e4876234c00fa1911528a5224f1d8dfa99622bd13474132e10428c262": struct{}{}, - "0f6360072cf8ed9f46f90ce9d01fae4e42f5e8ff629499d2206c68ed403e2fe7": struct{}{}, - "4a9ca4596692e94f9d2912b06a0d007564a22ee750339a6021c2392149b25d6d": struct{}{}, - "0db79e63c5179b1263619f693ca7125017237cec447946bdb40a8d3175c1773d": struct{}{}, - "7ef452f538cd23a691a8dd6b24ac86bea18965bf6b2ca7073c38aaebc84d4561": struct{}{}, - "28ab4f89ad7d4a50cf30bbef7d5121f81a95a2c2249179580d51294e8645fefc": struct{}{}, - "08e486faf56b304cf08915984f92b64750984efaafbb8ea79a6a579860984a4e": struct{}{}, - "fd43322a4da6f0e914c9624d6248d376a1518a9964349ed64679d316d7af8290": struct{}{}, - "9551dadbf76a27457946e70d1aebebe2132f8d3bce6378d216c11853524dd3a6": struct{}{}, - "0139bc5debaaa4b84e9341efb6ffa3e470f45a084742310e8f0b63ea83380168": struct{}{}, - "23162791bbf79e01563d2a564e33664ca5110f9f9be088ccb886c98faa06286b": struct{}{}, - "409a14887e0958f1efcd8de91a587561397bfcc5d994b9d6cfc2d758c8b49296": struct{}{}, - "9e8bfb3d1a73b03e453fe7315844f3471b0937127b14d24ffae4af47cb1d3d9e": struct{}{}, - "b6196f7784bc3acc076eef7dbe22b39d7c8efd5be804685de6d5488013df79f8": struct{}{}, - "4cbe19716b1aa73a67dc4b28c34391879b503259fc76852082b4dafcf0de85b2": struct{}{}, - "cf4dd222c57921747fc6711dca81473a815857bf9cd96443adddfe4b1cec8085": struct{}{}, - "186e9a49c4b2db4c31d767ecf9f4fba306f8aa2b31895b24f0a5b761eed76e22": struct{}{}, - "559054694ada46f586fa9ad4cc7874eed1077162eae10e0d66d70671915d219f": struct{}{}, - "1bf417bd87162d5d77a5ce3259e81bdee9f8a0ef56bc0f12eadcd5805a94b708": struct{}{}, - "57ee892e9f0c6840c76d9d96bd00c42f741af7f3baffc4569d4eb396ba7e6d2b": struct{}{}, - "6c725c744767e0d745901fb74154ffd59924dce57f1cb2d0137e368606bdfb62": struct{}{}, - "1ccb95bd406a15c892fbc4842ecba309a913bee37682092929f737801f0b7a46": struct{}{}, - "55fc6c431ab2ec1d27608e0797bd5daae3d85937f64189f1bd23388c59bb6fb0": struct{}{}, - "a024f642d3115a27d180c7babb30ce0eb5e15c0b171aa440466374ff6e2206ca": struct{}{}, - "3a6d64c24cf80b69ccda37650406467e8266667b50cfd0b984beb3651b129ed7": struct{}{}, - "0fcb77e9e0077de5e520622bff1356c0e191146188f53e0011ddbd327f293896": struct{}{}, - "d316bbd564cba50794b11be234ee1648c490de3d8015848aee08ff3b778894ca": struct{}{}, - "5bf8aa57fc5a6bc547decf1cc6db63f10deb55a3c6c5df497d631fb3d95e1abf": struct{}{}, - "ea0dd664d2f384d88656b7c4eb7c4a3b09d60e25285973adebc6df9b412165e5": struct{}{}, - "1484ec7a82914a8882f2548759daa7e9112ec188378c98316b303c852b94baec": struct{}{}, - "0a713c96e29268d75350ac31a970ac9bb463d82ccfc3c082ca0f3abe1780424b": struct{}{}, - "ba723435a66e490530c3efdfeac868e06fde6e35dcc43fa8528fb1b2c9411ef5": struct{}{}, - "5f28944b632a26d6c4ab0cd7b085c0915c51e5925b009d0b6f558b96a58a31a1": struct{}{}, - "b6f2920002873556366ad9f9a44711e4f34b596a892bd175427071e4064a89cc": struct{}{}, - "8472638c24e61364a5cfbe3bb146f22058212be6aedd4fea0c78e27aa30d2ebc": struct{}{}, - "84983c60f7daadc1cb8698621f802c0d9f9a3c3c295c810748fb048115c186ec": struct{}{}, - "1cb7868225540d8a705342743beb02e21dfe79e7163675bd370a504d7a4cf3a9": struct{}{}, - "c90646fbf01ca0569ed4beec7957df6a95c36c66ad10406726c2ecdaab9c72cb": struct{}{}, - "6d69b8820ecfbc73d1d178a770d727ff0faa06481c50d4df22312f02f9101fbd": struct{}{}, - "ec5c41c127a484ebc9cc0b3ca294254623f11588ef3c71a3a148e1b19fbb79dd": struct{}{}, - "0f9c00b3f38f964ee172095f50e53fe9b9e01bd0e1a9f750d877bd26a84ffe18": struct{}{}, - "00048ba5d737b3c7117d4cc4657b4c7795924e02ca4ac749ad5b699a624a6a50": struct{}{}, - "6cd5a9fe2147915b9658843aace0f60aa7c95debdaa4dee6db3c4a42d8194a0b": struct{}{}, - "122fb083b74323ef9234f279cb04b787abb52fd4a83c7209395d633c857b8dec": struct{}{}, - "d7c7673ba8ca7b0f04b1af4df026cbea7fed5b8acf59b27d33ef988c60eff054": struct{}{}, - "ccadd99b16cd3d200c22d6db45d8b6630ef3d936767127347ec8a76ab992c2ea": struct{}{}, - "0199311728be829918c18b5f1486d315ba690ed58b69b4c8236a16416130bcd2": struct{}{}, - "25e82cf6024dcd436a5c1727e9d7ca0025bd992dcaddc2c47adb72eb331c5b3c": struct{}{}, - "76f753aacb0ed0f21e0675efdfb3a872c43cfa0062d1e2cfe0947ea7ca4bec42": struct{}{}, - "5e42e5c6483833eb0a4fb292c5e5aecfb53563724a5ea4145b6bbe5bb132c25b": struct{}{}, - "923c7d67310152adcfdb62b2e63016c7646d0e2cb9526843b81b62d72e0a5baa": struct{}{}, - "632249d0508ec3c960372a4fd4cad3fae2413619027a47b6bb5fe1c24f4a6ba4": struct{}{}, - "1614ceeec50a9336ebf690886caa747d6811c45d37086a3fa7b11c9e83926c6c": struct{}{}, - "1110834e8a7553259d84bb92bfbc8ff8c2626782e2e549330dc28abd87fc7d24": struct{}{}, - "076a89c23179cedfc61171fe400ecf01fb76b9a48a68fb82dd0cd688d684d900": struct{}{}, - "115dc3606fbf8691fb69f2aefec86f2ecd302362a0502b3a9648bf2c4dc8290f": struct{}{}, - "21aafe2f10e974a8dd7c1533b954116ff776f572fda8abf523641f33bd8e42bc": struct{}{}, - "092192c1bfbbcbdb22ba2d062a93a0545cdef607be927f8a456bc84161a41102": struct{}{}, - "2ce06a9e7f8c3e6bf85a6067d0b220fb0ad03de983053942a90c43e73ec76f89": struct{}{}, - "800a513f86f8f77828a8ed2ba091365f10759737c467dd5b2180fabd3c6689fc": struct{}{}, - "c20a7cc70378247649ca446557be4e7ba5cb04ccdae0ea4eddc2cde71cb04f95": struct{}{}, - "31f7a65e315586ac198bd798b6629ce4903d0899476d5741a9f32e2e521b6a66": struct{}{}, - "cdb2e0d0f873ce5326e87cf7dec48de8da3043cfc950a7eba05a059150e873f5": struct{}{}, - "58c9722fd3f93c30097963a0157c1d7088bd4ba3e7f2f1de88d5eaafeb7d3552": struct{}{}, - "f15cea39f11dc0371cfb9a4b7b1c38d5c636feb72d70e2759b0e505905ee9d01": struct{}{}, - "34497fe00eb865b11bcac7f8765143c914d3c3eb6ae19cd2a16ba7a10079c5a0": struct{}{}, - "7fd1f436af6f8fccf41f58e00a63a055822272e93017adc1acb944d9ec9d845c": struct{}{}, - "17779b11c2c91adc26b5bd225475a49508fcf54569463c9e9577428e0c05ad51": struct{}{}, - "cca457407f24b80c72d89dd061837112cb99a0aa050c155514b320b7aaffe95c": struct{}{}, - "5b7e6bf2dc4a32a6aa4770cd5639c2c7af890fc86c273b5c8567fe5382086bf3": struct{}{}, - "5f5a8ed8f139be6df7cf8f4a91d3e4649961a98c82fd521c18d6f292b031a319": struct{}{}, - "5b3d7e7a15e0c5ffbdf711e09ccbec4ef2e5c814379eec0610cc470466b977b6": struct{}{}, - "faae1b97e57e3e121216948b8dda2a429ea72d6dd81c164f227dc6a1e010c917": struct{}{}, - "214dc12efd3c0819ae541ffa1f18cf5642a5118d9aa82e570957d46393c59d24": struct{}{}, - "2af4c646c9969d2260615b414f63ca1c909c225d482cff1ff5261b3bd1bce770": struct{}{}, - "e99d55248f67be7623332be8dabcd143ce2495eb923860b4ed0d963621ece901": struct{}{}, - "38136d6e343860f65e61a6f8fb722586797fcaf3857d9131b96ad780d90af7b8": struct{}{}, - "87fea5e18c3263b4fec8fa6c29e067f6063c737268faed4647a39dfec5fbe809": struct{}{}, - "eb5094069f5e287ad7cfce5fc944f1c6565da2481af83fd5c24568e2481d09e3": struct{}{}, - "59067da166523e593ddaa80e29dea92e8d84b927e7515b7f0c739a361bb814b4": struct{}{}, - "43999461d22f67840fcd9b8824293eaa4f18146e57b2c651bcd925e3b3e4e429": struct{}{}, - "010bdf79aa0affed1ac5a7221aec28d425ce527326553454b1b7f89c1ab07596": struct{}{}, - "5781eca80ee166a0ff8c1fc112f61b281de5d2ec624ff75af52ff300a6e37d08": struct{}{}, - "1ec4ed037766aa181d8840ad04b9fc6e195fd37dedc04c98a5767a67d3758ece": struct{}{}, - "5b0e1ceb3173b8ff0de2a262df2abaa0d403bfaea700f399f82a4c9f7226398b": struct{}{}, - "48e74b9d9eae097f7dd3178939b268fb179183ff1bd9e98c44def6eed9c8f233": struct{}{}, - "46b6f0ae97088f7259ea1daf97deba6535971584da62c71874287406b980fd79": struct{}{}, - "da38a069467b80b184ec911adceb85ebf1beebd22cc4d2a590fbf222994efd85": struct{}{}, - "5c516a8ca909c728e98c6a921536334e0421a60b43f87f259a83c56c7f8d465c": struct{}{}, - "9894ff89e36803ddc06d6c7a169292e29a0a9e018f309869024a6c7521e672db": struct{}{}, - "97a22fa048ba039c7b0ba574b61caf4546eee22b0049b2df0f0b26b3ce7e70ee": struct{}{}, - "b98c13fbb0845f8c41b408922ce607dbe694412cc6adaf2c1bb96b9a7f6274c3": struct{}{}, - "fd0ac497cdc1a17f9d9aec6e299285a0dae324b060f56313c8eb97fe52410f7b": struct{}{}, - "1b99395b2df86d02b01fa00c1e39f481bbc66acd80017555004f5c8ee30affd6": struct{}{}, - "9e5db53b5cf0dace033c64479e32fd987a0d9a592bc3f22e6360e9e7c2bbfbe4": struct{}{}, - "68a55e5b1e43c67f4ef34065a86c4c583f532ae8e3cda7e36cc79b611802ac07": struct{}{}, - "2d51a3b3ca1cdf790485938566c720527b2ebbe5a1f0326316ce63aafbc385d4": struct{}{}, - "08f3a56d72948b96df29b61b9f7bcb750f9fbc22432d5ba768a398701a7ca492": struct{}{}, - "033a67827efbffd28c49351a9d3a415e9d12c6a4665e27b1e22030cea70f88ea": struct{}{}, - "9cc1ee455a3363ffc504f40006f70d0c8276648a5d3eb3f9524e94d1b7a83aef": struct{}{}, - "addb0f5e7826c857d7376d1bd9bc33c0c544790a2eac96144a8af22b1298c940": struct{}{}, - "293de0e6ac4ffc141cc473619f35b5514716aaee759efd027edfd2412c07fc51": struct{}{}, - "bd582ae432692458aef5c1e015db2fea76680058a4996ddd2bcbd3847bdfd0da": struct{}{}, - "04003b04f7f20c58da14514f40958a0047bc28a6dec3788f18f5d663988550c4": struct{}{}, - "253c2e786c2414dcaec8dbf11df515b5075371454b93a5687d24d96ddbf3b939": struct{}{}, - "b08b0a5c8892079ef20854589792e05957f60ac9bea6e58b0320550d752a0bb3": struct{}{}, - "50bccdb129f014c65dfc97b19e558bd5bb8705764882cadcabe64fcec19b06f3": struct{}{}, - "2fe30cb0267e3ff7324277ae35ed4687bd6b125dd56066221c10fc92cd0d714a": struct{}{}, - "445929267209c034d1e324834c17e0c8305df3dcb21d1710a639ac6ca08c648b": struct{}{}, - "29976ab4a7a41ddd19d70e00517845e08d42e41ee80dcd37a5d8e696f900451a": struct{}{}, - "be12db004bc848474cae513314a269e907f89fef33e4d350f3fee968ac3f9725": struct{}{}, - "c129db8be8904b40ac21c9cf5d9f5c0e24ef455d1d7a7bbfd7049fc6dc9d2429": struct{}{}, - "9d31047cacc85aa67090d9b3135a6b9b8bdff0c5b97d7b510d40b618cd684290": struct{}{}, - "986c99004163a6c5155bbd2bfadc37bee9e279b73c669211f432910425b8e6dd": struct{}{}, - "767b0acef329a6d38de505b4237ce83953855ab2e6f9af11e8184c1940326c3a": struct{}{}, - "4a0a339b0c6d0553897752a84115adc81c75812e1743eb2519258e5000f70deb": struct{}{}, - "a84bc6c9241b686477f363c02ffd4dd8765e38c47243010ffe64425a78bcb7f2": struct{}{}, - "f959be0fe628eac88a47ebdc1845fd17478e06225447ee7ac636adba8908b0c8": struct{}{}, - "092136e376e17ab5843512b0633bcbf7ded1e5053db3335ac42a13b256075c65": struct{}{}, - "54d00d867758cef816bc4685f58e327b949712b07ebd17c3485f3ffc9e9f5133": struct{}{}, - "541f42d7542b70062fa430bfccac434186c0c1bb433b45b6f1b76e6f46d4cb60": struct{}{}, - "14a40534ce45bcf4a072554d42d1758fb42cf954e151693dd2381701d934e126": struct{}{}, - "7175d41e052114d7e66372dbe3889f00cc8dbc9f2f05a9bf10dc98b9bc8029f5": struct{}{}, - "8d9b2e6444501a0224c9d82e8d11b27577880c17ad750d3cd72c5e3a073d7ad3": struct{}{}, - "8bc40343b1871aea69ae6e2b0c1f4145e3d9a348a67bcf347333fb5a6c7b026f": struct{}{}, - "075f4ab854e2a33c8ac11c0758796af4ae994ec4e37e36b6f11b503492c68289": struct{}{}, - "c857d09db23e6822e3600bc06ad8d58f92ed62bc8efd81c753f77048662cb97d": struct{}{}, - "e4ee06d13f52021bfbfc793ebce404608a0efd7786e1accad9d113d099fbab48": struct{}{}, - "304528b7f627f4d57e2f066ea2a96602caecdb8ee424445f5e9e626435b5cb5a": struct{}{}, - "af5768223b36fe49f168d2aa3d2bb54b4e21b7b9965ac96d29e9d18fafbdf8cc": struct{}{}, - "1d78b370ad96e7a8091888d902360187bdea87ab6a4af003ec63c791894513ec": struct{}{}, - "6e0b5b262cf4b523f12e075abdea8f27b6fb8539ccf4b38f5de2b03857354ec0": struct{}{}, - "97c10efe01d5c9c88704a12d361d8429b3a6aa2412290a0773109d5d2d603d5e": struct{}{}, - "3612420d8c3b21092784353eb724c26649a4144fc989cce3181712985d376cf9": struct{}{}, - "b5a7d3c79084b8f59745ade726a5d9fa2112bca7e137d2a252b3c7ba06f2d2a0": struct{}{}, - "962cd22346a772beb97a793d2680b0f518615d9de049ce34079d625fbe938f4d": struct{}{}, - "d85135edba8dcf03d17144a635d198b461fb36cb5f34fd689bb5c6738911b20d": struct{}{}, - "cbc62794911ff31b2864ecd3dbbbee7ebcb7ea41c5a42e2cba377f3cfdb42811": struct{}{}, - "db42328112177c2d6f2f6ca7f33c8e81084b8ff3e14202254137e22673bce2c8": struct{}{}, - "165842e84c16bb1c0d13a8fa05efc677ccc7c357b60ae2b0b0cdc16a6147c3c1": struct{}{}, - "28312536bf06bc9850cbdc8c1803c7eb931cc4fa788cb7cbf7bad82801d3ef67": struct{}{}, - "81e5b1e6c30e0e92118a8eb8a005d4a8001162db217d9a505393434cd178b024": struct{}{}, - "7d1daa47a549d00c204bd361cd458f16c18a97257fd56a308f15e291018e890f": struct{}{}, - "0c88cc86a73aa7c4058ff7c0e31d6f105c0ca3575d5a68e060e95d9c84a607a9": struct{}{}, - "ff5ae7e8d206066d2bcb44222c2ef583c8d4c35a4c00f00a87b6ddd964c844fa": struct{}{}, - "40cafc18e93464f86d5d304e726df74311237955bcf7d99909bcef3f0bcb90bb": struct{}{}, - "6be7e2cc6b2a2971e04d3bd54576ac2cf1236f8e771c0d2326ebfc691df30372": struct{}{}, - "6912a3425e538111c51897f57afe814022c7f8c75050d45efd20b3e905029c19": struct{}{}, - "fba2eac3ee47ff78190eab8acfc21686955e4aed6fd45a5524ad84b537aeeb23": struct{}{}, - "a6864eb339b0e1f6e00d75293a8840abf069a2c0fe82e6e53af6ac099793c1d5": struct{}{}, - "350434bad068a7a4be855bc362d7a4ad6e631e7f5f0d916a2a0452070066d317": struct{}{}, - "5ef68465886fa04d3e0bbe86b59d964dd98e5775e95717df978d8bedee6ff16c": struct{}{}, - "0729563253bc11cb72714d61132adfe7ba2346b581b02546c9ac4a65fc0c02d8": struct{}{}, - "e226306f3f87b8f3f73a51521b0c74d7fb5837c4f1f790f99c0a239c9fe5250e": struct{}{}, - "24cacf370e30833b646db1214dbbae9f6d5dafbadc832fdc5e17ebfbd52b5bf6": struct{}{}, - "d672fc6b0b7787033c9c3dc282cb9db131e5b7ae5c0caa962dbffa765bd916f2": struct{}{}, - "a5a077f15ad0506be85cc4839083067e2fed8a73aef080e6b0ce31d133400315": struct{}{}, - "9f0638fd60feedbc1a5dced8234040d707c084bd75ddd2efb9154dbc220894bb": struct{}{}, - "e5e72beb4e3c6926d3dc9e3e2ef7833ba50cd919c2460a782b244fd071e920de": struct{}{}, - "35439e40a0dcce876f9885ccba67769b4b3f021659ebfe7cd6b38261c848811e": struct{}{}, - "1f350552ae50d3c89758b5aea1405660575fea1b57756277457f14d6ead04ca4": struct{}{}, - "291ea5ef94dffda4f4441b10077652368378e096c28889e50d98cb9a82525d75": struct{}{}, - "b6c4ac412ac8822355239dd717c11ca5b07373e4db550d0423c1b6aeceef8493": struct{}{}, - "1aaf3025033c8e43fa0ae68b1736c07af81e07da835bab47b6afdbb6a4a3fb19": struct{}{}, - "3c482346f375027677fa8a0d6830a32714d4f13f9e94c2d9e215e0ac205ad4e5": struct{}{}, - "ee69e01083881b51286d796976d1df926e28d25c525bfbcc65c2c325d3ad01fe": struct{}{}, - "5afc47efb471c419c711ae78439caef6ee1910a1cb88fa25fd95a5f579c1d86f": struct{}{}, - "e5a726a3cf969c027e9734910739b05d0109a6cda58f86efe6bc1791384dca65": struct{}{}, - "ae8a622f14740b7595d05e335d38bdae3d69f535b9d97581546cdb63cbe9aed7": struct{}{}, - "976e400797327e291f4e52c48197e0351b30930f9b907559c1a4917a17a46f5f": struct{}{}, - "91f060a9ad4b39eb228d9537292fab19d252faed06c169d0fdcb4a90560f5676": struct{}{}, - "83ddce428797a1b9280febbfabc239971c36aebe7cf317af9d785f704d27c188": struct{}{}, - "46635b56d3c7f0b7bb26adae2a1692debbfd145d4a0986a9137fe91e73e70360": struct{}{}, - "1e3b00912e66875bbd027266557943609abf957587c8ee202578b9507ef7ded0": struct{}{}, - "56bf4444142bfcc4bedb39ddff216751c4a3ba019aa02c722e1de9b36ffac38a": struct{}{}, - "7fcd3df7dc2fff173c22ea6450ede4f11b3e27c7923816aab660d474e27df8c2": struct{}{}, - "957d04b311e4db226581c06fa4e6b452cfc63534b0434e1cdc0c2513ed8d693a": struct{}{}, - "f0af91a7df72644cb34b0ea65582f7b902cd9630df4cc6626590e6cf386dc357": struct{}{}, - "f6d89d3dfb434569af1a9e14f364c95f604031d458ab795bec8ccf8fb2906c0f": struct{}{}, - "f2475e087314821743cac8cc3c734844f73de81f04d7cd5b3ac47eff9701557d": struct{}{}, - "b9f9ab6a93532d055272dadadef56288b514ff25bf639b12507e062a9c37b0af": struct{}{}, - "20f65c28671b40937c5bf23acc7c6f37e5a5ec0622e347b57685725df5ba9e50": struct{}{}, - "6677c381bd0d73f14ce85877dfdda881ee2f2c22d37e872e982e7077931fc0b3": struct{}{}, - "b22d1d8fe5752533954028172c9bf3ac01b57f40c82946a3e7b1eaff389e2b87": struct{}{}, - "795b6904e54f82411df4b0e27a373a55eea3f9d66dac5a9bce1dd92f7b401da5": struct{}{}, - "4fb5ca3b980f7bc3ec7a71345c4e025aa55c604e8faca407614ca648e8a35331": struct{}{}, - "6cc29d46cdc848d38301080fb4bd2d3aa506aa11d3bb137ac98ef642735d2bd4": struct{}{}, - "a6b78c3080f6587975d33fcd8eba2336896589dd379848f2228f4d067cc43e5a": struct{}{}, - "18c313e37abc0f3e63302ab405130303e6379a961c2b0dae0ff6a570d4833d93": struct{}{}, - "d33b57e3138875e9fce0a017675d868bdbd4818dfa568f010ae0f48fcbbf50ce": struct{}{}, - "d524c1a0811da49592f841085cc0063eb62b3001252a94542795d1ca9824a941": struct{}{}, - "f683740cded6680d9d94dfe6a258e44c96a27278a89e15acb52c0b8f32453d38": struct{}{}, - "6c1a07b2b9487162ce84abb1f007f91d3d50e026464577ec6b7550fbb9d0f374": struct{}{}, - "c2cb46f8b213d3a1ec17b82b0cecf7f20981b64b1d20400092d5c53754d2cd8a": struct{}{}, - "90aa3d6e463557c122eadb71b5b0d7a5b485e5eb975b473d76e442446611a831": struct{}{}, - "7c9bbe5ec9b3fb774e8fa0f54247e93c34ddf8e5d16fe3073420de0ae81a262d": struct{}{}, - "72ea19b1792401b447fbe97d153320af2c86c0426df9c40b7182df40d00bcb98": struct{}{}, - "6e7e2ef0fbf160847f3a3f905528807e0dd6261d8020ce9ed58609b60e43aa7c": struct{}{}, - "395206f0b4db515138c16bd568659cb91850a0474adf61ed08d748a67f2522eb": struct{}{}, - "52a39733bfab90ad7c9b874cf0b8254a5c2857549126341f198c39df2a343c0d": struct{}{}, - "2be663431f0072506a04499c2ac99c3232e7adbe3fc679c139a0609aace61bf7": struct{}{}, - "8bd3ae2dfab4b3d9d1cfc9b60caefbdb4cd8328c2a9ea02630b5d1bfe5c96f89": struct{}{}, - "5b7868dd6c3e9902a8fa654cd6049f7753bb92c59becfdba7b64a29643995c7b": struct{}{}, - "fad68de0366cdcbbba040891fd694529cce0a154f4e55bdc8d334c4df2b5735c": struct{}{}, - "28561c6b68fca875e23c3e7832ec04e9de88fc97ca07420b7855fc4abd9438c9": struct{}{}, - "1b2fc9341a16ae4e30082965d537ae47c21a0f27fd43eab78330ed81751ae6db": struct{}{}, - "69c04ffd98674f1d86cf0460bc1bb17d41512763afbdabdd44232e1bb30629f1": struct{}{}, - "d14b6c9eeebdd0d6db62dccd34678ab88e04e48a55e3029f5595ecf791c0381b": struct{}{}, - "eff26b18165ece9cd15978f5a124cd19afc08bfc76b4fda4ed11a35a66217051": struct{}{}, - "705133eab07d15b18a0e4938c8f389d202f2178e59b1790c5ec38179a5182c53": struct{}{}, - "cbbb3e0a470fece892f326f01363ebfa878ef59737d339607e1208f04f520e4d": struct{}{}, - "d78d1a3f3bcf22d7b74f7e559090240cc8d0bc88470a8a23383a8e4f3328bf8a": struct{}{}, - "310ff200149b44a32f124023d7caba19a1a890763a980606813d3a3d4a085d36": struct{}{}, - "7e92c11499fd4106a899eeb457db8277bd6f09dd29e683f33ae704e12fa29cae": struct{}{}, - "a744578e1dbe627a8df5ea9af28a6b4af4e8ce0c8f5bb24ea6fbdd01e801e1b0": struct{}{}, - "4a3484b3bf2fcb0ff5f717f18904abd29d228bc13098114df68b2ed9a4626503": struct{}{}, - "d8d997e381c7defa12908912423232609dfd56509919413430c2a7b5b69228e1": struct{}{}, - "92a4116a9d555214c4950c1ade02abb2c1b93019769b513ef1e19f25c4a7ec34": struct{}{}, - "c4c77d8e745a02baee77f4edfbfb8104718030842dc7342b3b9abe44963c391c": struct{}{}, - "f8e9dd3ccb18797be7d2e9da2d48f7806182ba6476527a1520f3dbceec9d076c": struct{}{}, - "c0814c71ab990bc04fe2fcc232df05322e8e9964fa79cc6ff36be31099096225": struct{}{}, - "ed62c6fd9f1b99007f2f0e108e1c5c97184fa69f3c43feb8038b389d80896476": struct{}{}, - "1411242b2139f9fa57a802e1dc172e3e1ca7655ac2d06d83b22958951072261b": struct{}{}, - "4458e908dd9a5440ad70b4d0ee13ebe96f0d25bb263b80169b3de5a9a0b90e3d": struct{}{}, - "c9b5fcdd10039e8001ab16564b46f7117b96ed78a174a8c8eb60bcf0e97f757d": struct{}{}, - "745cc9f7c761ae00709dd707b9276fd9c987c1e86efebc11e5a6e0175947ab98": struct{}{}, - "cf699d661b15a4451cabb5ddfe67a0a5111f6604197cf35f955640e5706bed8b": struct{}{}, - "38a0963a6364b09ad867aa9a66c6d009673c21e182015461da236ec361877f77": struct{}{}, - "c1f35ffd9e5268dec53413e3d2d88b306788f5b95b747812f2a479241f742bf9": struct{}{}, - "dd144e35ff3258d3bc5b973febb19e991f0dc8b46928c312e2a0a3316c50a0a4": struct{}{}, - "678411328e6fde36bdf5505a031adcf0aec5597ca1a80b74ec4205b863485134": struct{}{}, - "b3282a2f2a28757b3a18ab833de16a9c54518c0b0cf493e3f0a7cf09386f326a": struct{}{}, - "6a0ada27606c77495bd8f20db85c73b8b1329f0f0eabfc733924240ce8e074e7": struct{}{}, - "f342a935930b9102b18915b82e964a9e7ee05273fcf6cd81c5e71900321098c1": struct{}{}, - "3e1297acb89f759b1f7365c7749c1eccb3264eec3442b4d4c17f5e248803a7ef": struct{}{}, - "427e50fad6b595fd6f6d167a8c0c8c21ab3316cd7a428bff73a9b9589b944ce9": struct{}{}, - "3bfc46640be4fe9e435f96aef61db5a6bacff92ba0198546f8ca321542980946": struct{}{}, - "11a62c23412b77477a71481aa2dc7323bcc61d076c8449076c4c58a8356c1bb1": struct{}{}, - "ae5508454322538ff6ae798700b315111c38fcd165f41232120bf3452f5f8ca0": struct{}{}, - "ff19a384c13a612256e934171ee656a69f27765367729058412b4fd0d00160f3": struct{}{}, - "b11c64a28271b83db3a7bdaf18f2a4b3a64960e002169587e254590451b7725c": struct{}{}, - "8294547e9fe9367f291a22598513d897928d1a0eeb713ca2b41f31977192c5d1": struct{}{}, - "4e8ff5bd7ff87fe64023989bffa734211cbe7e84488092fd621d5750e89b68c8": struct{}{}, - "8422833208cdb674ab7f07644152524df019dcd25dda0177f831298af2e1a5d7": struct{}{}, - "1d384fc04da06b138f9e2e80622d6840d3ae65bce2dde44c44fd7b65380b06bd": struct{}{}, - "9a1e05c0fe0d0b721c7973b2979cd1052fa5e0675d7c6be0ba6797903ed97260": struct{}{}, - "b6801def9ff57051cf4f15bc847ddf39f1474887481a4967b30ba48546442953": struct{}{}, - "701fd6f18a46f7c72397c91b9cb1a6353744b9cca3aa329af5e5e1124b6b8c5a": struct{}{}, - "74273b01236c1ddf9e57c145950d53c27fd03f301ae79402629c2bc94f7daa26": struct{}{}, - "3e9df2cb288efe7a21cc992d70d29fc1de1b04306d5ad3ab4378713381657b7c": struct{}{}, - "ef7d74898c22a4bf0be437d30987bc945310dcdf1ddb48a6bce085f376b302ae": struct{}{}, - "b48f2581eb2b70e98dd0f4d536314eb162f092a1fb7f7ab8c53353160f1943c0": struct{}{}, - "1d4a11c275c18edcdefbaf75162576931ec92aca7076ab4ce775453d01fdc888": struct{}{}, - "eabb681553c580d8734763fcc60de95da0631429dae3639f748620bb4df6e252": struct{}{}, - "cec7a3ddfbcd7dcae01d8a6c971d45447c3b4abbd83b9ad4cd9a5bc16e89f957": struct{}{}, - "8cfde6efdfc4ed5ab1f6acbbd1ba49bf31932f84d0a4c090eb41c7d151e8b180": struct{}{}, - "cb3b5639a043f55b783e3f8fc58a2f6252db89b390a27c67ba199b6a897d0e30": struct{}{}, - "76a796208b71779c0331e79e8ac17df979c7d2c5bd10b491c94bd1e16d422088": struct{}{}, - "987b774381a0bc00b2c1622526d9fee5d51f74675091aa8b5d014d7d1e21832f": struct{}{}, - "a9a3e38ee26db300babf5cae260726d1896b450b80b567694731ace5bae80077": struct{}{}, - "c1fad2a7582ba76d09ed4e06188d2b289319316f12f42339033e252babd3c5a2": struct{}{}, - "1cac4d101ee696fd7a7089a7a97c1e9149409536e170f93ecba08d110d12dcfe": struct{}{}, - "e753a6d3c0e9d22b462b24665fc9b5ee37b54454e219943c73417be91bc07465": struct{}{}, - "b880a902236b655d3ab588c1dd97379e2a18f96e393f89b00651933fd0ac52a6": struct{}{}, - "83a70052504733134861af25c442ffd741935da314c948f2e015a93b2d0af6ed": struct{}{}, - "72ade48362af5c2b33ef3a950c1c461e95b1a5d2cfb09471573fbaeade51c28f": struct{}{}, - "5d10c166177ef188ac4e5234c56b0d2faf4c5b92ac87140b10fc86f014578741": struct{}{}, - "aff6301bc3335c6c952d9dcb872a5f31b020e01da1b904b855846362e00c43c7": struct{}{}, - "72239e8b21c5b0d1435b672ce16340acb3d9672bcfa890a1517a495853c61366": struct{}{}, - "7dcf407fa84a0e0519c7991154c4148de0244d7589020c0d9842db9efad82094": struct{}{}, - "8e9c543a5078f214519170192b6c94ff35050b83cf0c946a950a1b5c731f9041": struct{}{}, - "c9ae5a10ae8824d79d22c3e571ef302f5368e51afa1f551b374f06cbbd682410": struct{}{}, - "a53963c17d8d15aa585eebabd685b7d623e29fa149f8830a775b1961b145a05b": struct{}{}, - "3cb968a982080be1d7a5df98dc49673a8c052d2642ef7730b7753cee5b87c3dd": struct{}{}, - "b30409da44d7a3b9fee96cf55eba87c08821be278e0a16317727d9ba3b929ec7": struct{}{}, - "2ec716e4dc4329cdd25b5714267892d55d4c035f82418b32226be31278cbef79": struct{}{}, - "90f3f77b509382b385cb631e56e032463dd8085d0a917929a2a9e5241195e4dc": struct{}{}, - "ccc5ed3d02159c90808324f2b8ff09859bce1e0bc0ab55af98b12679752affec": struct{}{}, - "2b7847b7b705781d7cf21a05e9c1bb37cbf078aea103bc3edcc6aca52ab65453": struct{}{}, - "97b8824f49642cc915dbe0ea6b37f707ec812661384ad55a9437ad0a3b41c917": struct{}{}, - "899e468e1413205f093af31d33be5862e59509e7994ec7051e53b69caaf5632c": struct{}{}, - "3f2e23b2ee1991da34bdc689a44a229d1b64f3b617d6569ffb6e19f760acfded": struct{}{}, - "51005b4bf3f6b238f1b3d457c6cab27c213b95e1b1225706372b6e1d0ba1cdf5": struct{}{}, - "221b37fcdb52d0f7c39bbd0be211db0e1c00ca5fbecd5788780463026c6b964b": struct{}{}, - "dde6e8974b46a1eddcd7ea3bbb899342f48cad896b47275a6f806062ec5ca14c": struct{}{}, - "773f29873b6e31d0a7364f074fc48b7a004eafb1ccf846883e99de6beef8ada0": struct{}{}, - "c7b6ea529bd7802dd4fbf3a6b662b1f34b4dab34cbdd92a31aef0d3e8102e14e": struct{}{}, - "f763ca7aa9ae2ff29c8f0869e04e9b4a9f0a0e950f603cd111624a9fd24aaefc": struct{}{}, - "6556c1a58444e10f074e59b510fc392d61dd39d04227671272a61617dcecc094": struct{}{}, - "3ae643d65f437d9103f74f38ab66465aafb6e1b521a1662359d5165d447ce494": struct{}{}, - "1b92476c91e54c83eec1a720f137d7f716dc3e3d0b1cdaed505e80012c7d1e71": struct{}{}, - "3c4f4909f02e770ef482e1cb6136f9fc78b88cfbab0087a2183fcd3a94ba38dc": struct{}{}, - "d31f9b0cc1fd2416567cbaa83c399f55119ab14be7e82c5a1e4989b50a924b9a": struct{}{}, - "3e794348ed3512c457acc003e9b51c20e64eaacbde70e86baeb2371f3527e72c": struct{}{}, - "8b17a06fa40359eb1c9025f8cd8d4c1a58fbdbccb5f1e0fa181f85da0842cdf3": struct{}{}, - "e86940f1161a9f79a84677c49613a1718840abbc63c205d1b85c6fb7707ba717": struct{}{}, - "264c8c381bf16c982a4e59b0dd4c6f7808c51a05f64c35db42cc78a2a72875bb": struct{}{}, - "a44c47b1ee24665db1a36438203d8f9617a9889f9c34d3f2aa004ccbba672116": struct{}{}, - "a986248f8215bca42737ffe11091f8b9638cf7b668d9f2933108e8c8fba7f799": struct{}{}, - "36033babfb48ec64e197c97fb40d65e6c79f81e04c61aeccef3009e01645ab8d": struct{}{}, - "fbae041b02c41ed0fd8a4efb039bc780dd6af4a1f0c420f42561ae705dda43fe": struct{}{}, - "587d392b6c7680ec7656baf143511491e62c4ec6f459c21cce6953e6f1707bd7": struct{}{}, - "7710e1b184cbc633d23324649f6f50c0650df3d2bbf939ed9e60763bd9610921": struct{}{}, - "9ee6c0facbe8d06e39da87ff43bf3a4ce510aff2015518e84194fcdf6bae17cf": struct{}{}, - "c5c2e54de9d2833a896306dbe2babf0c2a0d7091c25efbaa0b3138aceca4a058": struct{}{}, - "27d9812ed83c1d618bf297e8a45a85d361bf9969eb9c75cf3ac9d6fb16e43338": struct{}{}, - "ca68c91636f5c3b099d0490405ba104d5c3857904b14f197fa35f08cfaa12a81": struct{}{}, - "99834619b3c160248b69c7f42ba868f945a0ea04cd31cf2f60dc4bc8f7d13b8a": struct{}{}, - "9ebf9b5978e6d486b0a66423715469b0e96f4b04c23d5061ec6c69bb29813a3b": struct{}{}, - "192c33caac3d89ed647f6dc54419161c2bbf4b57d12bb8c546e41d6448597571": struct{}{}, - "dea7cd279319a43df759b7b07433e75b053f8bdc2f759e328a8331d07cacd40a": struct{}{}, - "ec80b5b888d60bef18fe37166489a21bcd4beb25b10baed15baa42f3ac93db68": struct{}{}, - "41104bdc0dafdad13638672240ad2d1f9d2d46e6913486631c524d95bdb7d74a": struct{}{}, - "d634bf7c0146789601e97f4f6ab6877a0163fb0c80fefc3ff60b673d2e110360": struct{}{}, - "8326e09ce4e90a419a510ed74895cf6713f2fb42b0b3ffc126527ec2b9e95872": struct{}{}, - "c0e7aec81a1a194e9f54f6b297f6544041188c741ae8f55c2133b5c510a7dc1d": struct{}{}, - "444a1ca77fbee3fcab5f2d93942e04b9ea97cd89a0e01dbb44220275e1a7dd3e": struct{}{}, - "45e8f3d947cf74cea9a04e7172c768a99b3c43862b48bc2f4dad348c63a3a409": struct{}{}, - "7fdd65bbaaa687d48dc85a26a6dd7ef17fa379994fd8f3b26e19fe36976aeadf": struct{}{}, - "a744d93f2c6e8830a70b434113ecdebdaf568ba8c0557c63bd0914528760ae53": struct{}{}, - "c97133ac009ba36fb0d6a56b37111447afdbf1c1cf6637483a919e2df4af6e3c": struct{}{}, - "c61fdf555c7b1d09103b24840781bd212195c80ffa22cba5ac8062620b6f4ebf": struct{}{}, - "d3751d33f9cd5049c4af2b462735457e4d3baf130bcbb87f389e349fbaeb20b9": struct{}{}, - "ad1eacd5e3e14017d67c79f3cd858a1d1a2730a88a2fe0d4d5780196969a0cd8": struct{}{}, - "527a29b1f688f8c01f573018903c73088b8ca7ac83e243657d0bb0daea4c8cda": struct{}{}, - "b6564e12882d39b1aa5765403c61f0c0ccf8e06a72e9877b17f59c51a3bd5c03": struct{}{}, - "af8405be62dc7fd73aa49a588dbd7681185efc78127776f5165ca9d8273a9ddf": struct{}{}, - "4cf35cf87435b1ba3c6d96ce33914cceb116138913ece44e6501ba0f0388f19b": struct{}{}, - "0728c355c79c3a0800646144284eec1fcb1496082837b901f4575b546958c4a5": struct{}{}, - "bc16f5825a9dafc8c245dc7cd93f4e671a9885035a73e6ef29c04f2d3863bfab": struct{}{}, - "f1cd299a53c51670b926ffb780ee1380b945105b292071732179695e2eb5f76b": struct{}{}, - "3749ad2680c15aade0095cc3072736e551fc5ba89ace5bcbb61549db931bee26": struct{}{}, - "a10089eeb0bd6b43905befa8a6382ab245c0243437d73b340c2594b93c420ca8": struct{}{}, - "28c47cc606ad0783b8f419df71ec4ca60ab6eb09fe53e469fe5862925551ff15": struct{}{}, - "bd862cc1107a5352efbc4f4edc6905607146a1c99f6a39867786e926543c423c": struct{}{}, - "f7e7dd8d23cf268aaf103d2f6f730618478d6e1167691b41ec3fd56607ffaf44": struct{}{}, - "32783cef30bc23d9549623aa48aa8556346d78bd3ca604f277d63d6e573e8ce0": struct{}{}, - "82e35a63ceba37e9646434c5dd412ea577147f1e4a41ccde1614253187e3dbf9": struct{}{}, - "6cb97d99d496929757e80d5b758e4f7c446f70970154f54736879b5a317aed00": struct{}{}, - "2b93b177b55445f513d73ff1f0f30376d6ec181bcc1bd5cd19cccb970f4ee0d2": struct{}{}, - "fffc563b0002130a9502f9e89eceae8558291e4100dd95cdf5e86c37d2214ae2": struct{}{}, - "de25b05b8bb5cfaa5fa8532030fccd7d31b5e8464a66d4e3b518b85f36c81463": struct{}{}, - "d51801e48851b76597b5e57eb89c144d378cbd1cf82792941882da564026ad8b": struct{}{}, - "322f965a2919f46725dece842eb487fc569656d59bf3e1d35cc33cf8a9dcdfec": struct{}{}, - "4025eb7e4ff6ef9310a7363966f4685074a53301c08df52676e659f9bc25f669": struct{}{}, - "e971ff47d204448448c66b9aadfb4b84a8db258537c240697cc729a8da83202a": struct{}{}, - "0c08c0d223af7f43cbf3543b4a3559cd0cc0b37893c38a2fc8319e204e80c2c2": struct{}{}, - "9084b5b85013951e6df43648ab4830eb61ef9e39b7f576aed5a2a92cafde51a2": struct{}{}, - "b91136286b50c5be49bd3fdbd00648a98aded623894ebd9debdaa91ad844ca5c": struct{}{}, - "611733af996a4ad4d52c3775a5198fb42ec4f0b5957ae1aa8045c6dc3a1a22c1": struct{}{}, - "f7ac69722a0706c533afa393b1574a761a073df0a8280094d3500a4bbc9c2877": struct{}{}, - "f13fb31a54690182a806f071a0059317a1f52cf989339e2f020db837a46c485f": struct{}{}, - "ea68e4e2ad68ef05d4a9da946b735da0841bb862e104be1ea0a5f2f4961e0c8b": struct{}{}, - "069f9bba675b89a36d7f5025497bf17407c8b077f9a0a9e952da7339f5e5c7e9": struct{}{}, - "7a97152dd8ab4ef241e75a78d006f1ee72ca08b01e69b95d5e23ed82d80276a2": struct{}{}, - "9b4ef3e5b7b4d1759cb8f880e34dbd5ea8c3fcd79ce33b89d7357bf64fcbfc91": struct{}{}, - "80fd73d0ae9df67b132c85fcb38da0a3930d207e209636f5c75f4e158b32ee3b": struct{}{}, - "b04b4d34485fc1156e616d71cba6c5bbc03a324353275421c443c9b2e49ebfbd": struct{}{}, - "6422b12d9831df965e5257212752e652ec76571b3901f4cbc165be46cc00c6c3": struct{}{}, - "e2bbb74d442a1d0f6ce104959bb9ff7c7d1d044d2cfeabfd330372e86e8e6b67": struct{}{}, - "0a99b793f44bcc55fc977083c87520a1f1379ec0928dc4b24e21604ba27b5e46": struct{}{}, - "301b23f91f2d5f90315b0cff3d71cb57b36c77ca54db6ce49657d3ff66ff9013": struct{}{}, - "c071cf5f5ed6f884cc70155b6f05f755fd46a302d05e4261b7e92ce878bbfed8": struct{}{}, - "ef6787fe03c9883914c27ca6bcab4d506a154703914fcdf88f1220394e228413": struct{}{}, - "ccb3a0183fe8d2fbda1f9a7d0dad1f13c22269a87f32850ed2117fd709fb5f75": struct{}{}, - "ba70e1dacc17e1a77072ce9705bcbe3bc0063b5792d590f20eefdcd3d876daee": struct{}{}, - "11fbd13579eda1782dbfcd505e5574a1b83d747f9ccd82a528bfa61a548b9371": struct{}{}, - "7499aced43869b27f505701e4edc737f0cc346add1240d4ba86fbfa251e0fc35": struct{}{}, - "b0dd268328002062d2a76f5746caae05906520482bca9d9f00b3e740a0f83b47": struct{}{}, - "c29277f55fb31aeb8d4026132e53513a1fb755e5d9c13bf21405b72b9570373f": struct{}{}, - "384dac3368de6f658d7bc66e8fd4c8206b91c17a9084498948c7dd6e44d4a055": struct{}{}, - "b404ebce0ed6aee2206af04d3c059267b7bf820ea67c28e695413c4094a7d8bf": struct{}{}, - "fd9611a734f35c38d82aa884a21b8be69c559c51ca2bb0763696ade5a36386c7": struct{}{}, - "2a0e58476fd26859b877ca6f925da7cf42959c7fb3e0580459509bedb7d952ab": struct{}{}, - "c7c483c9a88eb1e995223950719d1f4b699e37ea9837b9473fa04e4aaed03b09": struct{}{}, - "8081a4830f184c3873b202e7c46b6da6ad130d68a4aa6c9ddc4e6bc3a9230717": struct{}{}, - "e2fc89848f72337bc7a7638135b34fdbbc61ac57ea893f6a7f1acf2cc0635d0c": struct{}{}, - "127278762a70fea92147a0c32c8baec2bd8f0cf8b6597613ba5c17c6263e886f": struct{}{}, - "26bfaf86a23b785215eabe6d344afb7a5b0342d9c59f8fddb1fe421192e340e7": struct{}{}, - "7374df08a7a6cf109af41565ca431fc53c9c1b9207294cd5f584cdc568b997ed": struct{}{}, - "72ca296a4d08bb1bc08ae079ee1f13c0e7fc9406bb2c1ba00f89ca8444af44b9": struct{}{}, - "f5b1f644f7c128b6522f2f49b300557655c5f2db68ef110729c9de88b7c5c0a9": struct{}{}, - "9202af6ce925b26ae6b25adfff0b2705147e195fa38dd58ae6ecc58ed263751f": struct{}{}, - "319b44c570a417ff3444896cd4aa77f052b6781773fc2f9aa1f1180ac745005c": struct{}{}, - "53ca16c8e7d74929521373809414c6e57b8642c9ce3037e4bacc504a529f46a4": struct{}{}, - "6ee83d0b8a97e77f4a6171d1926822102743ea305d8965524f6268475083349d": struct{}{}, - "36bbe50ed96841d10443bcb670d6554f0a34b761be67ec9c4a8ad2c0c44ca42c": struct{}{}, - "2931f7f6b6c90582e9d2f0f560c1072a45076b11d547f788d52575dea11bbe1d": struct{}{}, - "d0cb58d4d6ac665b624be38e148250f7f2185fccecd270c3ff21d3efbd17a5fe": struct{}{}, - "b585c27da10ee9830b6e519a8c8883f3eacc1d02a09503c7bf8ac24c6b2e0264": struct{}{}, - "f8a8f21a3505b68568afbef2a7ea3c4a9118e5fc8097099ca07fb39fe919ee1d": struct{}{}, - "759369fbe8e3e2c58df8bf0c2109c5210a9d27da4c29fd2892dc6dac7223c538": struct{}{}, - "51240d98ade23397f398f9a455b3b8137c9eb2fc7e9e33cb3d04af09a970eb20": struct{}{}, - "ae06995ac4f88b7a97574efc57fb3ccc63142150b93d25bec60dd6b4434350c8": struct{}{}, - "9f2a08fde416cef2158943241fee1dd8f3cca092afddef1d53ae82318e05f63e": struct{}{}, - "2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90": struct{}{}, - "a405eba78bf2e6db44ebe0b28bbc9cdc449f9ac990d2029c50a15e6853cfdf20": struct{}{}, - "7c0cb64eab5ee04a18830c717bc427b96c61082bf3af9d09ca5d0534112a3587": struct{}{}, - "f3b4cd4944a5f266843434e5aacfa2bb79f466424c19dc172e8953fb8a83bc97": struct{}{}, - "2f501595d6f227955686aa8d6bb70e0682acf6682a5cd5c83eb56f8d2e29ba58": struct{}{}, - "1cb0f5a9e3a8e4ddd72322c677990833aa4c67ff300b3ebfbfb726894f1a1058": struct{}{}, - "251ec19d457ab9ea7f611515f4061a91ffb43cc15ce42c2ef6f8abf9a3c10f74": struct{}{}, - "1241936d4dd3aad68fe7bfbdfe854b935926bc678fc72377e15166078916227a": struct{}{}, - "a8adf040d5380e118d2d20dfab9bd77c90e20683bab66777a9ce69bc465ca40e": struct{}{}, - "28c9483d0805aa42d0bfe2feab26d4fb95dd995b6412faecb40267182415d271": struct{}{}, - "3910df2fbded45107cc2e45237d1b0270374d7ccc0d48de2c986885aa162e38b": struct{}{}, - "e7722eb5ff44160d298c4b0d4b0dacfaadf4be32a0ed36402f63dc993ac536ce": struct{}{}, - "fac6a3243ed4705f97a025bb5bc79766fe694d965bb48a29b6c667364c9a2e6c": struct{}{}, - "96ee59df0b588d3d0c2402e6bf6f51403e94332a6da5924c3a087f92659aa44e": struct{}{}, - "0a1d18a485f77dcee53ea81f1010276b67153b745219afc4eac4288045f5ca3d": struct{}{}, - "8b63510ba2c2ce7fba51da1f0e86a98385edb7eaa074bae9f7642669fbe2cf40": struct{}{}, - "a00f29548b406b5c6a4952fc0f84864f5fd2f0d9314527079ea4998e03caa31a": struct{}{}, - "2db76f21528566a3f39cdaa9f9c8794ae3193e62c8262fd1a476d9cc335f45b5": struct{}{}, - "e530b18c482f68a420bcbf75cfd0067141f53c1e3eb5b65af61c7208d8e16af0": struct{}{}, - "a1f95e4f6e5e4d17aafbe143c1357c9923e0854d74938aa75e89e836ef1ce077": struct{}{}, - "45d25abffe8c792d74d30346429b5bc244b815eeb378a9c38395f7a466cf6894": struct{}{}, - "49faaade493be8b6b6164ee67f7e4d101812a5dda970d6ca693dda8b8cf82e4b": struct{}{}, - "e5d6ed8ce508a51b0d6bdb400f8f9688fda90c99344335a92dd49c5dcfac4158": struct{}{}, - "c24a8984ee7314b804c2187137f6f24dd36c619de9d27e9e2c380091468d79a2": struct{}{}, - "6636b31811ee0bbf593d11526ceb655267fe96d8daa4a164929664426952c348": struct{}{}, - "62080f96a2bfc48794326c5b9750942d15886e6a9746fc215cd0d04127196db2": struct{}{}, - "56247e707f880a5f6a51734f358155167b628c74c3241cd00c5de57bb87524c9": struct{}{}, - "87eee136f73cd0a771068277f170bd4ad82d929fd17507e3939a5182186d40c1": struct{}{}, - "b8b22aedc372aa891df895be9a7626e6d9ddc6d39ba85d202ca68de8c52ad782": struct{}{}, - "b73846dd535927acb39ffca85c45f41197d0380b1477e3448a041b9b94a222b1": struct{}{}, - "e31d426b5d3ff7257745b1d03bf1fcd5c7dea1d955dfc73d6ea8999d6dbd823b": struct{}{}, - "464d5b7bb200576f36f2e9121d74fead444d5edaf1abce30010e3a88d4ee9b80": struct{}{}, - "00e13ed7af55b27622f1d6eab5bec0147e68efe28dc2b12461117afa1a5ed40e": struct{}{}, - "b93006774cbdd4b299389a03ac3d88c3a76b460d538795bc12718011a909fba5": struct{}{}, - "9a4eac2dfadaf64af4b84ed99ba74a8acb078c7c870769d46000379af9f0c399": struct{}{}, - "eae5d7d418fa418d42a66e45eb8b0b78580e43a51bf593eb5a555ecd2ffbf450": struct{}{}, - "909802b269dee093387e26c6c7ace636f180eac29f58c7d8009b637c4babf1ce": struct{}{}, - "4b25923a4f31b83195ceb0d160e0a1a6d1556b5a2cd4c582ff40aa0498ae1578": struct{}{}, - "3beb460e56ea841b9eb9fd8a297fa680562e9a1d33df7540479c2ed037ab4883": struct{}{}, - "eef8b9fd5d437dd991ce2695fcce6c416ea3068e5b448963f0a7c3cbd9ad98eb": struct{}{}, - "17373ca1c7634a4be4a13861a891abd975ec916caab8f3dcf5142fa4997d0b96": struct{}{}, - "03ecab669ba200ba17f994677e88894400fd627e6f70439247ce0cbda8ca9a20": struct{}{}, - "46437ab18a6657040b4535297ff247b20c535c02263713f88b6a9e17484f1f3f": struct{}{}, - "bd01b0b648c2c64eb1bddd9361d9972ea684b344fedc4d166654a85e8919e7ad": struct{}{}, - "4627e00ddc49e55b22ba8420be6dd0b94607bec7c4ccdd9d20e37e252fefa776": struct{}{}, - "4a3690b2b0f1e34bd76c2a93560d5ecbbfbc8fbd3a4fd0d94c96b3dff6b86cb6": struct{}{}, - "01227d53f02c8c9120b86a3390972dcb019d6500cbe962c01224b3622dc8f02f": struct{}{}, - "b4c9e14061c2fd453b36700e3b0da008db2189c711ac629f0f583089164e267d": struct{}{}, - "b4bf5d7e5fcf89ef8adb64ec9c624db850d10f2afef020ed9ef23892df0833af": struct{}{}, - "127ee625c38029527531c7cc2394e91a0c978dfc57f3472627be1a7fb0171d1e": struct{}{}, - "5f185095980968abaaba01215789d324d4f3860749df8a48638a83882b565323": struct{}{}, - "c1a7c2039e211435543788a1e035c93fc7b44c7c7ef24d1d099f6c18c5160aff": struct{}{}, - "5667ef73dac709effcfdc518a98ea9238365ec62b0eb5f478392e28818a2efd5": struct{}{}, - "5de94d691ae3039a3f5d362fea9a35964fe0cc4ae44a6a84266d3614755c69b2": struct{}{}, - "5ed728c2fa5d767bc6c1ec6a732db1e37c343be46913e6498d340f7782691f14": struct{}{}, - "fc59487712bbe89b488847b77b5744fb6b815b8fc65ef2ab18149958edb61464": struct{}{}, - "707ff1c8a83dad594edba8737452858e66f6029a7c78e57b282c0171fa17ee81": struct{}{}, - "c06f5d9112e70a0521967f3ab5651dc448b04275d19b356a2e8ece4e2449320c": struct{}{}, - "bd667ad0ead553f0b441e8d111b993ff99c2eab6f35cb367b151b463ba1c1ef9": struct{}{}, - "cbd31fe312a05a4718b4d67151a8c4052c9aa091f4012fbf5a77ba5da8df41ef": struct{}{}, - "9af211329b2fc82e5efe906062c730082819b23fe8394bc435e0b1bf0458eb54": struct{}{}, - "e46240714b5db3a23eee60479a623efba4d633d27fe4f03c904b9e219a7fbe60": struct{}{}, - "34c5320eeb1ce31bbd65eccb901fabd6fd33125f6672e022f02268c4c8f49558": struct{}{}, - "d6f12e4e2cb4b9fb9d3c4a498a2ea0f5456e4a187a15632b2c368dc40c86de96": struct{}{}, - "b96e47cc910f7e99b3fd78142da92034c9f6fa57bf9a21a7152cf8fe90cb87f4": struct{}{}, - "bcca893d39c87999316339d97e93b6db549410ce16afba0a9d23787b541c2a5a": struct{}{}, - "b3c4b40750a97212e8981e4ac494d1ec77053f1eaf4e0934c276b74fc4f87c48": struct{}{}, - "9e456f26807c93ec5a495efd82c1938addaa115ab37f624d6af5e7cd212ece3a": struct{}{}, - "2d1d92b1f4cc7fd3ef215da88c2826ab3ec2ced95b388b395b658c41b349c90e": struct{}{}, - "5543778a2ec70e80dcde389698d25b30568119e3e0e5928dcbdb2fbd8d962670": struct{}{}, - "e30c2ea7a76f83b7c1a975ddc24286b675e714ebbbc72ccd5f0401730231ab57": struct{}{}, - "e273db21d19ee957f90c3615c7ac673fdcf27755f1a0a3ea14d1324eaa6f1160": struct{}{}, - "cc7b318a05d752cb1183d89545bc39017fdd811565520073fd17e323b738c283": struct{}{}, - "72bec4bd8f6d91a8818eb2ec6539c1d7d9b4c95178663c6b346d27c8425124a3": struct{}{}, - "da925a30e31f7fdaa7044e3e5ba4ae17670de82d677b0e7adf5700428a137a36": struct{}{}, - "35937bbb06a2c1b26c0b190c9a58c1f50d30dfc8f3b347adc0f0e94808b4da68": struct{}{}, - "c157a0b0d40f9d9506c72fae584069d1692b816da16cd76c648d5761c588057a": struct{}{}, - "da53e084ffc9334a10bc1fda0d15a39edafdd6c5aa2336b88ec316721b068b4a": struct{}{}, - "badb8f127e03a5f5c6357214c2f7d5ed81dac20bb0db6a72da03a88cb0a6ad4d": struct{}{}, - "720036c8101f751b82cdba6e74fbd217419a2d478dd49f6d7ba6697ed3810ece": struct{}{}, - "f89767726a7827c6f785b40aee1ca2ade74d951d6a2d50e27cc0f0e5072a12b2": struct{}{}, - "f526795c95399cea27c055c842c3d6ab018ed0fa4f66f701c28ab22dec28237b": struct{}{}, - "97e9d88850497a10d773603db17c4a7faab28e78448aef071a56edbaf5d379fe": struct{}{}, - "3e4748d72b0a0c5d4538b52a2ade025a0f52a1e8642384ac14e5af0f90b8ab8a": struct{}{}, - "556f9cb498932264dda4a1ae1b3de372e01343166c5501ef619ac4f068e567d3": struct{}{}, - "90c7cc4cf709afe28427e1990fda7d20002e06cdd995e0d128d85f7c47c97dec": struct{}{}, - "07ae38b93d8054d84aac37039c71ad114a8685a3857084d568f260ce69f0737f": struct{}{}, - "5b92408a0fd2f97797f739d6c5928189c298508f74ba96671f165d24bc728c37": struct{}{}, - "e2f5b64740756963da92628857f84dbae511976eb85f15e5b452f17294cf09fb": struct{}{}, - "be72176c19a9480415d377147654bc1a641005f8479ae210f56f3e1d13b76ff0": struct{}{}, - "f49c6320e08eb5ed523dc99e8c512888e2718ec6020201997d01b41754a61502": struct{}{}, - "19ea4ac2e1a53b1267fe5a61a3b6b81f760ce4223a25b495a5e2b6183da68717": struct{}{}, - "0f3b19453dde7c91cf64090c9cf7f3b3789bccfebe07b968c4260345e77e4ff1": struct{}{}, - "0ddf8cbefa6ed1a5d0c71ea559734bcd2c6261bd3ae34cfba7a007859cb366e5": struct{}{}, - "6985640856c5af76417c4bdac46ae0b512f070c7b4241c87ec5ba84cf6b55c18": struct{}{}, - "1e672dccc8cb80a9010872572f0ca44ac15678d37fe1053b7f53ee137ed11200": struct{}{}, - "cbbee1e4310e4d978c13b8c11afe499a1ef298901520f6e2b94bf9f3d924abd9": struct{}{}, - "c6a1427d7119cd3c049d4fe08cc02f1058d15291fba68a994816ea209af73f0e": struct{}{}, - "823d95a9a728f14d626df9a894705dda27417b8927412364255b515e9f2ac2e2": struct{}{}, - "b9f195c5cc7ef6afadbfbc42892ad47d3b24c6bc94bb510c4564a90a14e8b799": struct{}{}, - "ec450786e26a5154173545c402bcc03b20e2e0477d6086fa632a55c85bdcdd83": struct{}{}, - "7da6940c6ef99d18fbf4bf9d83b3b62ce5e1de7889d4aa8a815ad23c754aa327": struct{}{}, - "3e2e0aabcefdcb8971282c913a1ce1f71692f613012a7f18ab22911b92090b18": struct{}{}, - "b055840ebde6c0a922ec31f418a31c43ff26bca067ba08837d9330823c6986d8": struct{}{}, - "cd8c96ec55d9e751115b4949f199498e32f223654e12f7e5530f4002e571fa63": struct{}{}, - "f61a20da9eaa68a9f06dbc1710b10ef0a67208b2059b1f576af6deac23c215f5": struct{}{}, - "9b8769a4a742959a2d0298c36fb70623f2dfacda8436237df08d8dfd5b37374c": struct{}{}, - "382635c9325bf3273d195ff1b8a44e5b11afd7d97addeb8863ea35feb98c1a07": struct{}{}, - "ac3180d2bc33069c4914922d14c2e58dcf765d6881a2d8cf7037d854b1085c43": struct{}{}, - "6c6ada59ba1a60fbfc14a9d712d0776574fa8598421f368dcb943a18e0bc8ed4": struct{}{}, - "f1997691d4681a0b1adaf29af6bfcf1d11f5e20f9698a67cb4ad6bfe26ecf4ee": struct{}{}, - "2394522d62f099379fa30bd0cd8cee35b5faa1b9f0ce9bdb469444e1b0634207": struct{}{}, - "2cb4b1431b84ec15d35ed83bb927e27e8967d75f4bcd9cc4b25c8d879ae23e18": struct{}{}, - "b3140286ac71ad2acf69681f4f2a907b0b83d8edfbffdd4e0a38c05a23180495": struct{}{}, - "6606753e5a126d7068012a526d44c3eb2f7fcd09d5faeb30c77dbfd87ca7e758": struct{}{}, - "7904841598a49a875b5d688ec91bf0ce762b98fb27feb7fc3a658f46a9903fc3": struct{}{}, - "faa5af7cea8ae5022bcc38f8942164e92b1d713152ce3e2aa07725c836e009bd": struct{}{}, - "edb6f43338a1df492ec267f473f5653480104682e3e5da84a6746bffd570248b": struct{}{}, - "35bada4711a03a72c15b0e44f791fe965af1eb09704ec7fc795125e499e1e215": struct{}{}, - "45b18c438c1581658b531d36a9c8955ea56fbfd83e88b25e7f94c7e07d93d92b": struct{}{}, - "0d1709cc9c8ea6bbcab8c477f9b8ec62a40aa826433ceda836598a9c6c1b6f23": struct{}{}, - "e7e35527b66e2f3ca46b0d68b3009528d5ce4570c6abf648fd41f58c116662ab": struct{}{}, - "c427a238488d0e14d9cc00782407acb9e8a4ef5d564d78c5c6593d3bb26bbc2d": struct{}{}, - "afbfb89027a4dae87c6033eaa07896e93f3f1ddc2214ca43658982e8aa74b4d4": struct{}{}, - "5e846c64f2db12266e6b658a8e5b5b42cc225419b3ee1fca88acbb181ddfdb52": struct{}{}, - "96b835f30c212eaafd0ed0cf2c7b76f63d0885c009f97d6f116ccfa7acb389e5": struct{}{}, - "2dec26043f679fdd6bb0394cdb70c5b0c83e5619413824a1c11ff48730002898": struct{}{}, - "ec71f5c39a2cf0280c4d1236079e0f7402c8b58ef370ae8a5a999c09b5665bf5": struct{}{}, - "16af0577252ea2fc2b73260d8fe6a4e73155e9f83bb234588b561ab01c9bca6b": struct{}{}, - "f5d36c673cea1d80af3d33404d506e0c3e7aeaa0b8c1a21e085fa3caa8928b3b": struct{}{}, - "8b49bda2606fd82b02065c7a4a6215fd76159579f0a46afb19af9c29139ebf1b": struct{}{}, - "dd158c2153887182638d14b64da06f71a8fbaaea6f277de3cfbea148a539cde4": struct{}{}, - "1696c27ef5a17b20609e4a3e99a677ade376906d2d1a4c3613662f3106d2247e": struct{}{}, - "e06e309b66b35433fd311e9bab12a9843671de60cb122636d43ebd87754ded62": struct{}{}, - "468f0a7911353a75f474a3309bc5276c14bb24c966b7e2c3cc9359867746817d": struct{}{}, - "73660a4f7bbfb98b3e04cd38b257f69b017fbb52d5d864a59459cc9e40c92e6a": struct{}{}, - "15551266fa4ff7c4ce9ad70df33963ffe3cf6acf2c1c1821ec9f3abd53500cf3": struct{}{}, - "f086593e8c45e04d39a35ccaba3f368f23dfeca6a402eb03cb7499d8b28f4a31": struct{}{}, - "bda2cf3b8083f1c78711a47f70013c5aaf4a341aefa9b282a4aa216a787c4dfb": struct{}{}, - "9676b38cb889a36dc987c10c3f459979140e6fcfa625f9d8376bb40983c26862": struct{}{}, - "a5246b03e83a72386b42c62b3d945e5d153c2fcef3b9a72a4f4de5018624a700": struct{}{}, - "4d391982d0c8e1c2ee78ba31a5dfe7a455b394c931f12bdd33fe8521a9389420": struct{}{}, - "1155c23d603d643e73f8ba72c5d6b3f6cd00d7c725a24694e3c6ec95da1635ea": struct{}{}, - "c47061a74209f85f5acf6bcc4aae65bfb512d80f613883bf9337f226bad416a1": struct{}{}, - "8a81e9a2dbdc9ccf4ac5091a8a3458b529de766c3320138cf1f9708e537afb01": struct{}{}, - "5d326708016b2b0dd05457ecdd38077b173feb76c222f0206457387a4fffe1cb": struct{}{}, - "e88e96150c1ced912cf9a514eae381fdf173270c43934ef5c53c9e2a69175e92": struct{}{}, - "42af43e3616eba4a133a755540a88e6c61085dfe66c6e49e6ab8eaf9daf0c0da": struct{}{}, - "4d11818aece668175264af668c9566408e1306260642c7e1a36512c46c65310f": struct{}{}, - "30caae2fcb7c34ecadfddc45e0a27e9103bd7cfc87730d7818cc096b1266a683": struct{}{}, - "74c25a699f03afc5db8f9690982bb00a2e3077fcec4bc51c0333d130ade2a6c7": struct{}{}, - "dc86739e2e62b47388153c27633cb054d68bcff006511b17e13a4f636ece83bf": struct{}{}, - "d94072a73664bc1b46e7dc737e9c779bd08561bb2fdba6ac1b67bddbb121fbaf": struct{}{}, - "5fb6a47e368e12e5d8b19280796e6a3d146fe391ed2e967d5f95c55bfb0f9c2f": struct{}{}, - "c91bd9c288c0a7c5ca1bb807cbd5d89c5ab33764862eb46808417733da2af873": struct{}{}, - "913bd193848b5a46d5417cf91249bc806f61b40c7d0c06719152dea1e2344449": struct{}{}, - "12765295db4f1e7911ddb4f051191091a0432570270e1fca2076e718e61ddae0": struct{}{}, - "cca0151aa5e3da95848b714a753db0dc9f4f5b2aec7d47724811afd986677961": struct{}{}, - "d4d6cb99915dfaa46ca7528ee7808edd647313302c8153bc6a3bf1d6b5c60687": struct{}{}, - "39e48f935fdcee139f5f41bc4777012fb0c6a21d59d58f06925d1fbbf5c24dc5": struct{}{}, - "a03ea09072d789adff29aff6a3758e9294c96ce803915c1456384eaa6e2d2df9": struct{}{}, - "632388bce7ff095c84558aace4f52dab2c7a689a95d9b3460270a0aba1d91de4": struct{}{}, - "eda15ce4834ac303532bc6973df5ff8a5deb75d7dfceb9fc2cdf1bb450a01cf5": struct{}{}, - "92562d10902f789f2ee39dbd77a1bb0f4776d1b792e641f3d711b9966a1de001": struct{}{}, - "cced28c6dc3f99c2396a5eaad732bf6b28142335892b1cd0e6af6cdb53f5ccfa": struct{}{}, - "183936b010c67d8925410a4504ac61753c89d462b066b69bb5c1ee0419022413": struct{}{}, - "26e3b2a5dc54dd5812fe7e6917172e2c9a70c72a0f5d2e97a5b8eb90434b7ffa": struct{}{}, - "e500ae891593f43729d52e3c363bf10566e5cf5260648f7f1300e0d9d46e2c30": struct{}{}, - "80d9b5aabd393f83011a928046502d1b45747ed896a9985b3f899319624cfae7": struct{}{}, - "1f254faa04cffa0d0a8c75f8514f0087429c37cc3516f7abb69c6660db2e6407": struct{}{}, - "9a430b439239f1570d7b2158f1b26bf94dd8a01444a2d2b460d06771486d3cb3": struct{}{}, - "bba53a5b33d33921e5d0e341009629fabd54f283b4d819ee852c1b80096dcb81": struct{}{}, - "224239d9662d3e7574cadacf62487785eb3974a05ba84cda4483534a052a5818": struct{}{}, - "b12a020b7fdd57547b41b43fecee4b92441d617422bfbe8b7ea88488d2371d89": struct{}{}, - "17684a3307c6d0527b4487b695b843d916a6f370a19cbbf2d994f37c0b0b1f2b": struct{}{}, - "c3e2d78f3ff335724b4029f06505b511e4f8f01e258828de29849b36b40b55d0": struct{}{}, - "59cea62bbdfb4dc0ccac8a9b92f35bf8b4d96762871626956217907c09fc035b": struct{}{}, - "0ec5f7c0abe435d22cca61c6d4ed58226e83f9cecef7dcbfda3b48c449c77b35": struct{}{}, - "c1cf024576e9c756b252bd5035efc64c72c17affe236909ded190d266a5bfdf1": struct{}{}, - "2e7530f0815270ffacb39d2f049f2adc550e1438211b93d8f621bdd171921bac": struct{}{}, - "392b4c486143606c0296ce70c0eb7df1d583f9ec093d4bbc7501f20d58eb5a86": struct{}{}, - "1cac3a7ac1ac607543fc9d4816f886c45cdcb5841d79c2d584443df62bddb6c4": struct{}{}, - "9285827b8031a1dbe7d1d04eb8a08c8891ee424a8002cfc7dd2df3d82cbff611": struct{}{}, - "dfc3376b8266c66e8c24736645128a5f93ccf1df6f381286ffbda654fec8f21c": struct{}{}, - "a7937b64b8caa58f03721bb6bacf5c78cb235febe0e70b1b84cd99541461a08e": struct{}{}, - "554c07a8809dc232861c323f5df0e114272963b780415622b1541ddf47604993": struct{}{}, - "d41dc6385e804fd6c6fe049ecd56a3c1bafa61e669d4f3b49082ff56f8ade10d": struct{}{}, - "4eb7ef52a0e1337a674211b8ebd5709a92871d0f31e813d791cdd83ac56ff706": struct{}{}, - "349dbf5a87cb88844b4ebccc18df4c232da5984d34aeaa9bb5d4f44e8d8fc78d": struct{}{}, - "6234c4dc4560ecd116b9f4f40fe497217e92ee7bb60ca0f3ecddc4f8b079c483": struct{}{}, - "414906c2ea25fff1301366ba9768ad441cb5442019432847ebf3fda6afeacb6a": struct{}{}, - "a5e9ff2839154120f1b27ac40d8d96556a09c5dd95bcdec427b0f688bc027980": struct{}{}, - "7a256a74c191a9fd0d1a408d7ef357eff0f016467bc5570e096a6a74932c8f17": struct{}{}, - "0e7f3c5c909ce8afb978a208d639e85af2171df2ec0003a07f13ad97878ef053": struct{}{}, - "1f7b6ec245a66bf49bf30cb9fd073a98e86ccf0dad28a809af95b1caeb8d0259": struct{}{}, - "9a2550cb37ef324611f1def65fd44623f85996ecc5a94ebfed4b18bcb710c50c": struct{}{}, - "26d9a1f3e4ec5bc151fc16e62eba02b2e106346b619c12c31bc6941319a84921": struct{}{}, - "259bd361f0ad11c1d18a52f8dbff8c2bba6901900010f6f3422ef5b5fe65694f": struct{}{}, - "9e7f3375709b613eac449f47489c7749d7fd986874dc5d1a69cd28b6ddd4ad93": struct{}{}, - "b86262f9bc892590bb31dfa5a4ee870d555188e43214a1a0ae261bd95a2a4470": struct{}{}, - "8d0c2780d4abc1a40fbe36c66183d72abc6ec14ccf6571b5b512f687a71fa5ca": struct{}{}, - "fff0d3547c0bcc2c2c7ba6e4aa58efca6ee230da013adceb3c223df89df1b45e": struct{}{}, - "a0f33cd88909b15dff8f434d8703779906af49c489f2f959c45f969ce5038516": struct{}{}, - "8cd626bd979d05205337b47dc6a80edf6372b902e3dd1d10bc99dc3ecfd38f9e": struct{}{}, - "03fbe1b933af6b95f33492398c079b2ddcdd09145c4d1f43719e208348fa8dc9": struct{}{}, - "f34e25300011e8e3e0ce61b42edf1fda417d3d7f1896cca9ec724427a56ad44d": struct{}{}, - "4c26d9074c27d89ede59270c0ac14b71e071b15239519f75474b2f3ba63481f5": struct{}{}, - "6fa599fe7b02fec029e98dfa0f64c6ec946cc0ebc57916355dcaaf27bcac5a16": struct{}{}, - "afac327e73694e68c02f5c62f3bc0bcf0f239744ff12921e781487a0f9b3d5d0": struct{}{}, - "f59ddf918f384a1b7e1d1011c49c3f3fd38421fc3ed3d90dfaa9bb1633325478": struct{}{}, - "73a7d6f94949c6fc296249ee8f91110ae28880e6196f097c9cd0f526c43c82bf": struct{}{}, - "046b79f495da73e1f696602c80d167a81f62fdc0864bee8dd44d0b228f7ce5d8": struct{}{}, - "91dbdd4a1c1bbe3af464f2618161e115977d02a5d49cc0e23313937c31580143": struct{}{}, - "5ff9c7fd38a0396421cb91a1fe97adbd4088f24c963c07a48ee515d11e75cf8c": struct{}{}, - "e979074ebd5633a7263aa48c4b7dea055a769115fbc0a7de459f8a74cd2efd2b": struct{}{}, - "331d85f0258782c704e39e8877605b87f57166600b5b7e27a6847b1e745b9d19": struct{}{}, - "58943fc0ea6be26aefa12a46d3343b0cefc510a2eac090f1ad320ef8dc7f69d5": struct{}{}, - "77173ace11f118f4cfaaa33d7c35ec612a1ca67dae67d54a4a677a465124b087": struct{}{}, - "5c972e0f84f07e06ce46afababea662a699c68e36cb23b2523db7dfb60dc9ca0": struct{}{}, - "d29af8a2e160dd867fe45a75f70bf805b1b6c1cf92017cf53cdf1d5cf390c916": struct{}{}, - "ad28b35084eabdb7edd22df20378748eb7575aef1342775f151efdc79abda430": struct{}{}, - "da0daaba4b156961c049ab4b85b6d0bcba2872200b7dedb4aa77f9208c601444": struct{}{}, - "42570c45c81fdb5d1964f9e7aaead823df063412a3ae28ddca46dadb25bf6282": struct{}{}, - "afb47e00531153e93808589e43d02c11f6398c5bc877f7924cebca8211c8dd18": struct{}{}, - "b4b568ab61e07150322fff327765e9c9df03aca5197f14b416e30ce6a9c4ad3b": struct{}{}, - "31d58043e5d08e036adf3f2839325970070f178f2eb3a08b0d25506a15314eb4": struct{}{}, - "bab3a05cd6cd339ab3bdcbbc774084e555b5eb151ddd301b0450734d66b39827": struct{}{}, - "d7ac1d220e6a2a27d87c0efe88f4a80a490d6d8e4f34ba1788babd716e6b4b0f": struct{}{}, - "f931c308fc5b60b421c09969912839dff2776957d98b8d2f91c554ed8fc80f78": struct{}{}, - "333046d06ee498921370419b5a8ed7030f2d8aa1b8670d9fece8b81345d98823": struct{}{}, - "c51faa148557a08cbf790156578b7a82b41f22dd01227f7dde057e34c18a365f": struct{}{}, - "8c673a0108578213eb4368ed8f9d3799b3a5c90053c53b2e53c5ba4b60174cf6": struct{}{}, - "d14620964103e55fabdeb6bdc65890ca9db70ed53354c42900a6a8c459883297": struct{}{}, - "fd4dc09d586efdabaaf43298bedb5d2fe2e2a7f941629b133674a066940ea667": struct{}{}, - "712df00b36e4528d3e317251adf8e602d17a5380001ac65d7c1b5aa3300a2084": struct{}{}, - "af4764571f217a9bd2c50d8e97c54239bcacb15c835100e59fda84cb33603d14": struct{}{}, - "392a5bcbd71a7db2cfb9796c633326f7fba6730bdb0c801d3b0fd30886821000": struct{}{}, - "0ecf003efc72a2722c79c22584cf5ea02d1d21a17aac999c53fd0b1e5a4d62fa": struct{}{}, - "187897ce0afcf20b50ba2b37dca84a951b7046f29ed5ab94f010619f69d6e189": struct{}{}, - "f3f1383331d1d83c98f0bd8265afb58b90c97bdd21854a349dd092a26d829483": struct{}{}, - "dd8f9ad342562b31e09cb0b77a0e9056728f3797faae43f86be3dc5f9a65d03a": struct{}{}, - "3a5b8990e60ffa74e15461d7547e0f62aa94d19de4e3de687bd31176f8b6d17b": struct{}{}, - "908aa043e7c01be31fc69405625dc44037853ca7667ab2e7b9a375cde6f80cd4": struct{}{}, - "6b2457d7e5f309dc4e09bf459f4bf9d71205a6f1bcec4326d4d3ab63b345cdb3": struct{}{}, - "98bbbfb591fcce33ee1282175bf675aa2423e7eb1b2a1250618d1bb80d2578ba": struct{}{}, - "ffb58fa63fe1988242d6642b9b68e32da3a2fa84257ed31d6002ee848cd01ea4": struct{}{}, - "7001a6ad95a6eb9448fd1aa58cf5ad4d9ef130778d5ac21d547ef9087eccc75b": struct{}{}, - "f0da7cf69a74f344e114ecdc553aea62abd4d299468a96ae40ec8de321fe6bee": struct{}{}, - "3f56e90b04e7a4b50e3ec2446c13055e9cb1d0851c17c9ce8a52ea3ca76623cc": struct{}{}, - "cd6fdc9a04bb5d7e3afe50f4437b8e65d3150ce22ce4aa328fee6021db0bb295": struct{}{}, - "88f94eaba345a40deb3989f475e2c23402ca50973aa66cfc9cf0c11b1ea5e64e": struct{}{}, - "8f388ed94f5ff3d417b9b3f897bf9fc4d56a2d0dd6778905d8440a938558d30a": struct{}{}, - "4d4f26369171994f3a46776ee2d88494fb9955800a5bb6261c016c4bb9f30b56": struct{}{}, - "bcb1cebb4bd8d7a53bfb4597c53266b406231ca8c09da47f0906eabc2eee5396": struct{}{}, - "7f2426a460c22a3befa3f7c887b23ef40341292a64508d2491aaa77118bd82ac": struct{}{}, - "542a1f355b0ffe9f272c03f159846e1d87660c7c43da40b2f1e71b9a2736ba4c": struct{}{}, - "764037136ae37157eb3c5c7805dafbe09840dc08b8f716f9e1e79fa22e700a2b": struct{}{}, - "81f8f6dde88365f3928796ec7aa53f72820b06db8664f5fe76a7eb13e24546a2": struct{}{}, - "061b67d6e7ab579da2610e27e631aa4beda1ffe75c0a1b3f3f1c0b97780b4052": struct{}{}, - "37c5f3e49d924869c8b8702049deff129d82d318251a3f227e56d8a483231741": struct{}{}, - "34e049b1c46aa37d88ad6d669e54dab4112eed8621565605495e54643fd00025": struct{}{}, - "4d5b660943625c0af95e935e7bb6fcbb803a79eaff63cd5c0a62d6627cc06733": struct{}{}, - "db044f19bc0137185286345ccf1f067d2d9ce1c8efb38349b2479696850db69f": struct{}{}, - "3046f751cdc6f2ebb9dac47dea910d8ec6bbb8060b3b17399aad9ac96eac8dd1": struct{}{}, - "fadb17cf87563cf5093084b8df099551fce3452a17f3e31b6439a4c874c9ff0e": struct{}{}, - "fe28f9515ffd370567f6925b4cb5a17f1862b255ebdb0a1943861d05c0a79b91": struct{}{}, - "a47a2e1872852966caf818c682a89d18bb7819d45c2c9d1d73c58613b99ad0ef": struct{}{}, - "22737d36997eacfb0667c5b4c13aa8aa09e1520b6477ab759743da4e517e3c2e": struct{}{}, - "f29a448b780745bf2e10667f46c442b102e75e76a46a1fff969641866225ab56": struct{}{}, - "0c37812beefb524512dbd614783b224ea23b673624d412fce7b519e4049b0bdb": struct{}{}, - "74e58c1c66c6e35588941b36144af074ee76369c789f9939152e3f1c77ea293e": struct{}{}, - "7cbccda65959a4fe629dbdf546ae3ddea9328ae5a53498785f4a27394fe26515": struct{}{}, - "46b09f79db8c6c5531756e39860cfd41fa1b311104f1a416e0c6397a93e63059": struct{}{}, - "eca1dfda3321f728c946b275d7ca31efc5a7f2912c50d524aea977f28dd83aa7": struct{}{}, - "a7a8a3d9836180a0cdfd51c30239a40f087c9713e7d759b8b565c9e559ad806a": struct{}{}, - "49742b4b8dd3a7ff2a2a32410e34f55a57d09a2327edb26d726a30e400960966": struct{}{}, - "b2d22825f41e65847453195bac3dd27ed8366e264db203cddcd833dc4ca3c62e": struct{}{}, - "a5c032f4faf0c5b1a73cfd99b4d481311ada25cee1cf24fe367ef975796a1ced": struct{}{}, - "de569a7f6852ba73f8b21af13af36c8d1fa438d7018ad26fa2fa65140d15249f": struct{}{}, - "07356e8a472bca7eeb7a79dc0fe9a27df6d1d19ad82caee2bebc122463a1d42c": struct{}{}, - "87f97f8c3484294c58e102897a96b260fd768fe9cb5bbd36413711f5ad30d656": struct{}{}, - "30e363d3e8c59f2c1319f8d73d48e3ad26db5e087951a4d7ab809c6f5401aea8": struct{}{}, - "ee5cd7d5d96c8874117891b2c92a036f96918e66c102bc698ae77542c186f981": struct{}{}, - "afba7666b3be6126c99214027b8a0f945074aebaff4e3f96a5ca81b15d5b6d5d": struct{}{}, - "66f62d1807d3821a3865f2573b69c74be033f1341240ac861fefc6d430bff5e0": struct{}{}, - "4ed379d418bb86290a01117e9ceb0debffc4d1b7087db6c04882fbc4e50132f4": struct{}{}, - "e1f3c7a127733502613cc17ce9577b61b69411937ea6e36770b8dd2e89063798": struct{}{}, - "e5d9106e3bf44281ac57aa2004b5e61a9e9ced7b0b4f09a00930c286bfba534c": struct{}{}, - "b6fbd675f98e2abd22d4ed29fdc83150fedc48597e92dd1a7a24381d44a27451": struct{}{}, - "c40727c66d04b725818d4579040bcc69d5cfd9b430889a1af92f02e2cdb1bde2": struct{}{}, - "d5fe46b1204cb7bb339b929b1777e04d2c7235915d7a50d6baa134e20dbc80c6": struct{}{}, - "4bbf0a689a70583d77eca39ffd1bcda9fab56cb66c9551fe7d3ea06ef8966830": struct{}{}, - "a0126546bff6c68c724dd39db17ce9e4b7a754cca0a79025d5e50afe99a0150d": struct{}{}, - "d191c57f0ed7fc3e0ab5e997a9a963f9a209cca3cf81ab5535aaa9996e2c8173": struct{}{}, - "4a630b8e79a0cd2fbae3f58e751abb28d0f4918f76af188d8996f13fabe08af8": struct{}{}, - "1579a6e3112caba1b6031ef7c507f3014d7b0a408fefd1b4d4a2c8c32d2274ff": struct{}{}, - "07307784035fdc5cb2e003cb3d905a4782d034858b6a4699bf26c9804a378cae": struct{}{}, - "d2dbacce3e851bf99053241e00ac5398bf17b61e228900dd22af0c5528c0e90b": struct{}{}, - "5a5617c22af86eea0e3eed87cab7e08d65bdd0320d9ff4427d91a89e7b4c56a6": struct{}{}, - "968d067df7b1124988a5fa4fecc3489dc9516d68b3d344426b82a33622e9ba37": struct{}{}, - "2ec45b88c61c277aa7ce152a3385f3e08ba5aa7a47445a4f24f4fb0a14783d26": struct{}{}, - "4d30c878c44ec3d52deb0318ae71d9a9b7a91391a46a9d704697570af06426f0": struct{}{}, - "00e51906df651a7ee922446590f487cff433ec9816aedc44dc49952a05cd16df": struct{}{}, - "3b3b53c2a6bdd088d8b0fa6b73274972db439f6ae25393f680a77d6112cded94": struct{}{}, - "873fc42a5952181eea451cee3681747710ae38f7c901a1d38c198eb1da473d1a": struct{}{}, - "13984a4e7e0edce830a005d227b04ad31cd1056020fd0a7b9760364161735aa4": struct{}{}, - "7c8ccc86c11654af029457d90fdd9d013ce6fb011ee8fdb1374832268cc8d967": struct{}{}, - "dc5321cfe2b29a8856229f03b89cb5c597c5b19f255bed7dc888f0867b25c78d": struct{}{}, - "6ecf763ff6e7cef7b47e6611e1bf76fe2608a2e32a97b2d88b083ae1d8d02c82": struct{}{}, - "502bb65877272ca293642466b2f3c7534eca394a6cb69c396a80878d08fd683e": struct{}{}, - "ac0a953d6542c7b40dbc12d3f8f62afd78244d38770ac2ae019b1f7c38dd6382": struct{}{}, - "c3334e9c038aac79f765c9eaf93edae51ad07b3c14670e9de8bd6e4c7f170be8": struct{}{}, - "a7cdf5d0586b392473dd0cd08c9ba833240006a8a7310bf9bc8bf1aefdfaeadb": struct{}{}, - "ccb711f092ac8ef1805b5045fab7e8a6189cb97ad04565e21b5fbcfc9e542e42": struct{}{}, - "6e100364803bdef1515086726ec2cc71f52b655648718faf2840949a4acc5f1b": struct{}{}, - "02025dbaaec42b09f28920eae1fc939c5a17ade1a63d30e30cafb9e6547ea175": struct{}{}, - "b7933ed6180d9e2895a9d2df885e2a8b92e998a3c16297581f5b8698daacf492": struct{}{}, - "dbff5341acad5e2a58db4efd5e72e2d9a0a843a28e02b1183c68162d0a3a3de6": struct{}{}, - "5d347fd948b66308f502c3f65c8f7e12ff1c5cf8c760bcdfb188ae1ec7b8b618": struct{}{}, - "9d72156f3968b9dd0b796a8b6dbfe374909059bf80df0880236539d80ef88b27": struct{}{}, - "61a3c22a310f6b6328941b54bab34d646e3ccd15a67504c7403ddef7d80f0e43": struct{}{}, - "1490d2a7cce45795d3fe94ac08f1ef9b90226361c3ce4d8e6ce1b2205e64fd31": struct{}{}, - "d9fd60a8cf992ec3d554ec2df8dd4cb345e77de7ecb4df4772920897b1d51bc5": struct{}{}, - "6f46a730a6ae6530de5a17a2793f1623083669d9f5cdfb90c957a30a8ab5e6f2": struct{}{}, - "ee7cd3962ea25933955f44f7c8e31005420e0a4a087c1906664730e1d57e8140": struct{}{}, - "a30a997579a6d8733555003b7cc698864186fb708731dfdcd14c5e0a22a945e9": struct{}{}, - "a0670a0fd3b5c9ae86b9186fac1543e3ceef4a103e5d4efb9db38787c3a9c1d1": struct{}{}, - "a49c1d0380688dd9e1898df37e8a7f9e7747212a5b47494173bd2e4a91452fb7": struct{}{}, - "d1d470d61a1e5e55f9958c8f42c38112e3ba26e2c2e48039b6c68ce800a9be6f": struct{}{}, - "4a0f33f4f5276321fd55d94158947482208eedf057cfd81d461d6b021b420900": struct{}{}, - "0e06a93f7888d926e3f96ca2d5607e220ed183f20b5332e885a0bfae947d2241": struct{}{}, - "a3896cfd1afca32bd25c7d030d5d604c6bdb56bc8327319cacf0c55cdaf20fb5": struct{}{}, - "50c0152c2952082aeaf427885a2f617d67cf6de183a8816c0955ea5b875a216b": struct{}{}, - "bd2555f468f3181c5632bfd3a3257b9f4a02f7dd46413a064900bc173da577c5": struct{}{}, - "a2c00ba38748def139044ddff9f7f30996820568deee58f22bf6e69354c58211": struct{}{}, - "8501e59580016d378589f9fb84a23e4ea89b778552741252fc9f4898f2a11d2d": struct{}{}, - "c850c75d30a9360f81eff0a01abcf9125ba034ef9a2dd9f95bbc073a60032299": struct{}{}, - "dc6b394a3afdb8425bf4d27928930728256e3359188751d0e61715cf770ee5d5": struct{}{}, - "3cc849279ba298b587a34cabaeffc5ecb3a044bbf97c516fab7ede9d1af77cfa": struct{}{}, - "116f54c41d0405dbb10e7b04ebc31e262a5b8c85c1233fcf36eaee344d91ae58": struct{}{}, - "0d88ee7bb1d78fe3de28052692b085799fa8062f6957e2ee7589e5b5287d500a": struct{}{}, - "6bfac23fe6c76e0257ca4525073b034fcace206211a92876c6db5a865354e53e": struct{}{}, - "2f4fd1be015dcbcf24aba2fb99057c06101215896ef11448c9a55d5c64963c07": struct{}{}, - "433453e9984c904b6dc38d2537a6a45257d4040060f4407458212ad3c976e6b2": struct{}{}, - "ffce3618dce7683055e273cedafadfa7ea65b7f352a5348b410240fd3ec6fd44": struct{}{}, - "05a331a7f4f1929faaca289a66f98d5a05b387d9cbeb4f8c2dd8204a6b023a14": struct{}{}, - "17d0eefd73b7e58d63e9f27416e188822b34a83d75f72176efd10306fb524ba9": struct{}{}, - "d1de890e17658eecf046b00d7863a3c2511ea2522bb4aa4db419003ce31adf16": struct{}{}, - "749ab2c0d06c42ae3b841b79e79875f02b3a042e43c92378cd28bd444c04d284": struct{}{}, - "9671014645ce9d6f8bae746fded25064937658d712004bd01d8f4c093c387bf3": struct{}{}, - "d212b2ac736e3f60408ffe9fbc1d26ae901f8f477fb8b08ec03c9d6b0208ac2b": struct{}{}, - "08dc3a5b49269b3b2d1d26dab38893e933afb1bb19b9caaa925d320eaa4af754": struct{}{}, - "163c18e0068f0a59fdca66b6a22253a252a413966e33b35a0e45dda3c5add351": struct{}{}, - "1237cd8663a70a7f619818ddb1df0037ff0f6a8c3b4b12e76015a7471e2550b4": struct{}{}, - "5986bde91db988ebf22bc8ab401d83af9bfc9714ef4afe2415ad8b02c8a98743": struct{}{}, - "28e51044f4a9cbae2bbd3d8a9d8c902ad1455d42208277ac4a913b003038a3dc": struct{}{}, - "d70f6838cce70ead390fb1f1d7055aa93171b72ff492f8c9e5b082134b58504c": struct{}{}, - "f0fd52e74857e4e8cf2dba8cf46befa7f6283264a7b28b3725c80718c59b32bd": struct{}{}, - "b8262e86e0bcadfc8e03ef6d51999137d78774cdae980fe12d78bdc9943d75a6": struct{}{}, - "d098ab5e44b9aabb755f76d806598f43573c662b35e4a2eab1e312ec9ad195e2": struct{}{}, - "319ec3e742d3a04898f95a619fe42172baba030c0c00c5deb32968108ffa41e4": struct{}{}, - "eda565a9067e4049556890611dca70670d0b5a298722ecd6a40bf14b1bb0acf0": struct{}{}, - "233e1cded61ace811d7a930947b06acb18cda1dc7b67e215941e1830fdec3b7c": struct{}{}, - "c5ff177a86e82441f93e3772da700d5f6838157fa1bfdc0bb689d7f7e55e7aba": struct{}{}, - "3c01eba119e00d79c82b6f65d70bc5f1044d568618bf41377e6d1432023fc2b8": struct{}{}, - "bf962e39ec4bb94f0643a12a307eba14062674f83d71456608d789968912c9a7": struct{}{}, - "fd5f56b40a79a385708428e7b32ab996a681080a166a2206e750eb4819186145": struct{}{}, - "93433e08c1065c0fe8ebd151a329e2f8733495e7e6ef2a23249a6f12600de103": struct{}{}, - "92e7dd7306dbdd412c8d6b626b7c808f0c3fc692c9297aedf047ae918b11be58": struct{}{}, - "288aa2a8991efe00897f54273cb879b651ae3dd13a1861feafb21b0ef2a5ca19": struct{}{}, - "f8eb31164222d7342e12b949e43fb4cc5e6bf2de3a16fc484e1e04d078de2cea": struct{}{}, - "47d140cbea401190e9612d5c7e3bd78a5fbb63def20832036a1b2c4fbbac636b": struct{}{}, - "eefd0d7defd4cb5a881d5e8b5dd21b3d3ee1b0e32773d4246caa7734fe231e69": struct{}{}, - "76c6590b875761412c621e6ef9690e449b2bbe486fed46fb1b42186f6fffb1bf": struct{}{}, - "74cac794aefecdbb2241a77c6f6031f19900ceba4ad62a1625fb4e01671ff7d0": struct{}{}, - "c316cefbbec1689c8d5a2209679cfbd3de03b4fcd904c7459bca71b7d61eec11": struct{}{}, - "f64d45f47222938919465c311e4c3720b75bfdafde1be795dea208ad14fd832e": struct{}{}, - "e1cf6aef6260915e8b5cc5bb45a5b2ee6bac1cf91551c2475b5fe5b1d859013b": struct{}{}, - "96f80aa1b9a84b36eca9ea80943227de4e1c1d69390b3cce6e1a633a009a21ab": struct{}{}, - "845845ec9e847a2c03a6f556fcd2530ab92714c23606afed0e85d51751679465": struct{}{}, - "ee139bd0fd941847bf43d49bff0fc08d5f51ee0b245942b1e6f99acf9c9a66be": struct{}{}, - "e5fa18d8cd683bfa64dd37dd667369ac8f82026a352fe4d3ab8a4a881b1bc7c3": struct{}{}, - "e936fa73c6e351f3a6097d818a149f72f21bb5f902482e58719f28154557d7d5": struct{}{}, - "120ed1f4810cfd54fd41e7cd34f2531911a71bfbeb595c71b02b037157180c81": struct{}{}, - "ba9b8d8118f7cd6974d4d4281adcc41ecd4becc496d1075962c0d5db8ce02f75": struct{}{}, - "b68bb7a4d58c3964b240b50cbe1e4ade3f4aa54a02c19ef1866bb5d0534d0c95": struct{}{}, - "51e3cb9928494ecb1d9d9cac074eee44bf60126fee3b247981a40087067eee82": struct{}{}, - "48ec6b3dcabf45154ec53708533bc094355c68a09df4f23a3ede16d39d69aeb1": struct{}{}, - "6ef7c9b15ecdd69083724b84cfdc2100351963488b51b4ea2fcbddf493fbec94": struct{}{}, - "fd70ad909b94deb27b460692084d9f2b1dbc9df3c6bcfd3caee571e707031e3f": struct{}{}, - "f4aa0655cdb8d4fcf6f719c7a786de10556783c70bfb8ef1d78923482fe6ebbc": struct{}{}, - "2bda9b4dfbabfdb17cd74d827768da5198ae2b8155002a494524bbeaf6f5cdba": struct{}{}, - "6b8af1b6071b9ff27baa9252bf80feb10b92be9dcad595d38e255a27fa36874d": struct{}{}, - "fee5861463a2d43deeebb159cc3aaeaaa4a4f4507b625d365076890e208a01c3": struct{}{}, - "ab2417230072c3b0932f5f358ac5eb8424bd587c25fa0528d1815feed6ebfe54": struct{}{}, - "516c5747bd51090791a7e4c5e9784a2d4cce84f5d32f2886819cbbed5b98f760": struct{}{}, - "a78f19952edd18bf02b3c9eb704b088e2120941d6acb22f6f795c42796e60252": struct{}{}, - "b84768373e3b4256962ae76f47a4f25a390a2a05935d1bf58af1a8e9a88e94f7": struct{}{}, - "12e6274e4309293e2d480272b49a6c7c73a6a6b22678ba226b533c67006c17d1": struct{}{}, - "da30e45705f79aa7555b538e098d0ab284fbc6c890f92ed8ad1722b63aa69201": struct{}{}, - "fff54945efa0b4034ef0fc2edefdc6ca0d20c218c501e1abc39ff5dd4db5eef6": struct{}{}, - "fffc1b1c24a3bc9772dc86c9e6e55886e5f7a65b378727031d04e39902c0a735": struct{}{}, - "b1a36d25d9633ed2ac04939fcb614ccb2b513243c148f18694592ae037f9d35f": struct{}{}, - "f754b29153779d8930d379d62258d3092be893d5dda80a218d809dce16e41fd9": struct{}{}, - "6dbe0f85a074203f382f5880efc910c717f5e215b613f46c9639c4206d60236d": struct{}{}, - "a6b46dd0d1ae5e86cbc8f37e75ceeb6760230c1ca4ffbcb0c97b96dd7d9c464b": struct{}{}, - "ed068fe14bdd683f4f8387d2d47881f360261d22ded821294a882d46e72116c2": struct{}{}, - "e12dc8ccc151caf5bfc0c99fcc6a289e7440f4039624ae437d5a0078f2d76891": struct{}{}, - "b16f173eaf978090de28e9d135fc2e0ceb64a7b8dc35e726ee544e61e9bbf470": struct{}{}, - "a14001de5a81954398896a8dee68f979b20600f9ccce92a9c4eb70fcad43edfe": struct{}{}, - "b986162d9376812660a7723f7f3aa300cf1aa017a5e28a7b2180d830c160a684": struct{}{}, - "b7158b64a98516b31d0c23609f69265a868c594dda5b3c8da9e13159e209c9b6": struct{}{}, - "eab8ff114cc63fd8ab3d9f42249e20b8ce5ecce463e8368e98747f03c50eeabb": struct{}{}, - "76e4aa1b6d2ca68b43d031ae7b5da9daac32daf2c0dc13c7075f9c0d0cc5e62e": struct{}{}, - "2de7de14998303e31a88a1e52a7dfb6dc2f34c35250e3494e0f27f4c236a4423": struct{}{}, - "309d267f086d5fe5433d5bcc11cf83c14ffb5518dca95fc59b17363bb421e28e": struct{}{}, - "4a76d6a798907e691961385448fc6b9c9174f7e205c36e838ff5e765d7eb8817": struct{}{}, - "48faf589cbad0aa47802da41f11b6ba24ef771975b4d64dfd69cc06bba836249": struct{}{}, - "297e619db5ee855a68d5c326ef45d2e5dcceb9b448b647fd3d87cac8109e9b11": struct{}{}, - "f4eb4544aa2679e20ff47ee7bdd9feaead84146a6e25c60e715ff98285ec3873": struct{}{}, - "ecc4873a1694eb0de0b33d68f0df091d6816127c1422266399700d02599a8628": struct{}{}, - "fb609bdcdece3f067149bd817b9e09729a94e2a0be8fe9cf9eef2d29f6fb89dd": struct{}{}, - "4f0d9e5f2d159c8bd63247f9470d09f15e1ff431cb88d5a101df49c4c01882e6": struct{}{}, - "24c15bdcf0144615360f6154593dd73b54d5d159b92dfeab2faddcb4ad06d74b": struct{}{}, - "7def9c79e5be6d7a70022168b8b099ce1e707a2fd809a60fab73de6de578884b": struct{}{}, - "81dda343c4ce894e9cc038ed37de2a78aababe8f4e607e2e0e33851e80308d6b": struct{}{}, - "f58262c8005bb64b8f99ec6083faf050c502d099d9929ae37ffed2fe1bb954fb": struct{}{}, - "769232cd377fb40509cdc0b96f2abd5a7e1419b0af79130d958c3151c5b2dc8e": struct{}{}, - "b3deb173436f3977aca49f7e5c055eda924a5b6d801a1d4700242539f2ed5351": struct{}{}, - "d54123de468bd42ea00dafbd777f85fe5fa1ff6404d9838c007953c25c92a1c5": struct{}{}, - "d937a9f37df4a9b5b11ba0a7d09d56c3abe94f46ffa55744d2d4e73f1f2de3c9": struct{}{}, - "23eb91dc1ce8ee1f0bed7b4ab9badecc48a3f62b5c07abe91da458bccc0a78af": struct{}{}, - "2c99e051cc1bc435aaca827b548db6b411f8862b1115ede37b1999f6ca50f496": struct{}{}, - "833a63294a4cb1fc9c61f2d8c3632651c7523f799d7f5c1f40fa390f66d4dd2c": struct{}{}, - "db905278f90cd6f8c0b9e56bb0da6deb2c6a41cf312ab56b9d319b8c5ce3e407": struct{}{}, - "de2165ef8869e188a11655a966c6b8e60afbc31009a1f312f10ae38081069cdf": struct{}{}, - "d0b00c77c1f851ea4553a62e87f4c740357e1e6fe14b60dcabfbd3acf1d34018": struct{}{}, - "124c279931d346a69cb12bcd88d406db1d90613180b9b0fb31acd2468b9cd10a": struct{}{}, - "0b54232dc6edf74f68a47c20828d44043b4bc4162d2521b12271c9fa54963f6f": struct{}{}, - "f0d9991c5e47e0d26a350c1618bd3154cd0f9f2461d3df671a753c393fe7a6a7": struct{}{}, - "8cb6279a8e1b12a9677497cc5c71e90337f2dfad637b35a9d1be0b0668a85f06": struct{}{}, - "81582fba89023120ac07079aa2fdda467132499765e042d0b538424688c3a17a": struct{}{}, - "1fecbb595f20bbc595de1a7d5e4dbec3ad728aa6e15b99b3966bce3c17c85acc": struct{}{}, - "8dd59e41294e53ee0d715ea9b7c1ed7b6a8eefb08e674db1a8496fb5fd0f0a6d": struct{}{}, - "16f78a7d6317f102bbd95fc9a4f3ff2e3249287690b8bdad6b7810f82b34ace3": struct{}{}, - "5296c024e03e439ba3d72c8e432d4f890b3cd0d2c36d67d428aa1e401e675b69": struct{}{}, - "1b7432d5feb356909f6e88340fac5419f914113f13ff2084e672f712ec7aa45f": struct{}{}, - "b25d00fbde36dbc370ba89eef3e92a164e56d2c5e7d9fc5a3b43364c5b0eb1d7": struct{}{}, - "b4d02e2460d370291fa0fc80127d1d7b30fa3cd7ceaf134383ef9829448946ac": struct{}{}, - "9ce65878c9231a3701d1d123f5f152261542c3a2aba67da323312b8bf6c7450b": struct{}{}, - "fcec91509759ad995c2cd14bcb26b2720993faf61c29d379b270d442d92290eb": struct{}{}, - "b54f08623ae4039f55bcecba4961037fb4513d2ba9cb2b0667c5db970ac94911": struct{}{}, - "765531e0c5c33d5a3ab4dd5bb4f0bf63384f509a755fe2af024e1cc40ee7a73c": struct{}{}, - "ff36e0c700a2a71dccf56e8db5d4355902add8ae5ba7b4ce233e61092d0d189a": struct{}{}, - "dc10e452bd360df5fe3f9454bc7169c5779f4821e6e7189eef6666ab331621e3": struct{}{}, - "b068c5093aed09a10a9732de95a450c094e59f0ac8afede78474b8c75eda95b3": struct{}{}, - "0363f1a98b0c507a237977267358886724194e58198ae5efc58c659238ee8fc3": struct{}{}, - "27cac5503836765cd10751d27ab4a6e17d7a80d4c948430a5a81513973f9b51e": struct{}{}, - "2c89306d40e0dcefab292a7832476616b2a94eef027f616be6686083a58e9edb": struct{}{}, - "b99d85aaa979a25ba56b3a6c0ef18eabc9761b8799766ebb8cd9736fda5bdcf0": struct{}{}, - "6f6a6d94fb43a9266140b199d18ed06c4e142349faea75e6e91d54d7ebf84567": struct{}{}, - "24d62c1dece7f9b08df0de725823600c5b9aa8376d4e5519cb87f216aec708ed": struct{}{}, - "6d7413d361c1c5009b7ce330b3c5ff57761557bd7a73870c0dd46453d2db4ac7": struct{}{}, - "9813624fc500e823daf47323be22045e555cd5af7c15d7db8ab245c62bacf2a5": struct{}{}, - "a9b46c39b8ebae313f9f39b09868b7bbc592d90ed563fe27f9dce3215d71f192": struct{}{}, - "706358289b679b065abd3992a1415adf0885e0e8edd4f5a9dfbafd1f2a0035f9": struct{}{}, - "f468bfcd4cb3a862d9771d1aaa14d22f2c406162e3e41216b9bea9d68f0c4be3": struct{}{}, - "06249168f7d8bdaa2414ed9bf8c95495d8bc5d70d41ae9fe03e444a863f6749f": struct{}{}, - "a58e8cf90692dc568b0f32d3ba45c13798dcc7698be8de56df522d6328034926": struct{}{}, - "cbdc48550e32db1c2196c070ca0325e49e0c8ff31596969bfd6382d10031734c": struct{}{}, - "cd3587aeb206a7b7d87ecd01b4437a1ce6f8d77e0504415605646931b3b355ce": struct{}{}, - "58cbded0f081d3db8a41da9f6dafd908d008ef985929d5d22eb974627703faf9": struct{}{}, - "03fd72f81572805dd59f829b94fd8a6f82077fb435ca2b406d9595718e521afa": struct{}{}, - "fb2b9bb163acf7e3ad50dd8d950b56ba0065d96aedb36ffcaa87dc44b9000f2a": struct{}{}, - "5129a4a62c66ddf286d47951fb2ac60a3a207ea9867cc887dc719d9a88866c24": struct{}{}, - "733c0de9bb34380988d22095b3d0faa61075e16c3c4796e24d57fa0fd63e45e7": struct{}{}, - "86bfc4a61c205902a060b42cb88869ebc51994ce0568a37e9ef14bcf4b2bb6df": struct{}{}, - "35864ba5082fd4f2e1896a4ad0929fe3cb3e91cadc4477f5ab749bc56fd8e800": struct{}{}, - "e186022d0931afe9fe0690857e32f85e50165e7fbe0966d49609ef1981f920c6": struct{}{}, - "db8e363f0316fe053dddecb10880d6cd11e4cde1547a29e0bbd4dc8d5caa86fa": struct{}{}, - "377f5566ac747710aaf6d412b733c194536a3bb76011b9daa65e465091ac53ec": struct{}{}, - "813d2fbb481e0a17bc053fcd720c5cca09c30c89fbdde699b73fc0a57737092e": struct{}{}, - "c0e97207d7c6a85d5a99c72d8a42bccfd6e6377648fda3a6168719d058b46d1f": struct{}{}, - "9171f3c0277b462e122098a403adf5da9cae6efa80481022c503070497cffead": struct{}{}, - "48deb732e8de8fe7995bbc2816520798fcab815f62165af4d53bbc24434c963e": struct{}{}, - "7b094ced6473bebdecd313a346dc5d70ed2414d41bacb5d914534ab741e30c9c": struct{}{}, - "35c615424f700c3d2b441e000afd050fd2603035cedb019b481b3612f1f561a3": struct{}{}, - "94ce18dded2e761fa5ec7e0843f8f57edc8d2465a58ecae91331bcff21cfaf5e": struct{}{}, - "e0d98dc324387a546d6e41b5d196102a03c713e1b2cb30f477bf00f11bd9153a": struct{}{}, - "7f4d032dd4ecb46acdb74744d7de22faf25f70ce584574aa32749d45f513d58e": struct{}{}, - "3755b417b0f937026ac1b867a397d6dec80dfd463c232c2daaf1de974b93da82": struct{}{}, - "6c7dd80b5bd41f6203993e638cd33fc6df34dbe2a8e42fe0ac4740f6291dad0a": struct{}{}, - "a05571c258b84f60a7f0b9c1feb0566261ed082945cac8cb39c3d90226379af0": struct{}{}, - "89351fc3452d4c8d8fec9ca61768d0c6939e1122aded5b26f39dc15b68d6e261": struct{}{}, - "fdc43e3f26ed40db965fb370601ef4571e92f0beaeb011c2a12e16b61ee8730b": struct{}{}, - "e66b23921f6a8dc59fd33b69edf8d5d393bfdd3150a6a23c0d9cea6aed8e8060": struct{}{}, - "e4d693d24e646cb7878426225d90d2607fd40e2e7c82f4cf8246b2cc3e0decb8": struct{}{}, - "3492ad65d05a973fef8c825521eeb41ae64625a672a8aeeeabc696e16d62a020": struct{}{}, - "3f9cfd5198dcf9c3a786e769a2b59248e2efbb56a5b66a05aed30589e55e5df6": struct{}{}, - "69bfe1e6e44821df7f8a0927bd7e61ef208fdb25deaa4353450bc3fb904abd52": struct{}{}, - "be78e93242a0374425c7ad95e9cf06f569bd3d5c3bd0c062287bb6e0ea632925": struct{}{}, - "db944067d6c7762bee2cd7cc3c55e9f35ccea4e885fa17930856aefe784a1b52": struct{}{}, - "71a1f6864dba3fea257e36ce4016672e293bd867426766b17690cb40f353cafc": struct{}{}, - "541c053166dfb3d2a49d39604a6fcb43dec3ac278ba87faa5b0bc87e08548319": struct{}{}, - "98f4270f09c07130b91985c80ab067e3f09d61f24636eb30784634aea6958a69": struct{}{}, - "f7e89b1d59f3047abf49981b17c7b2746bb653fb02a4ffdbe6a87f02dc03856d": struct{}{}, - "55c5499bde37ff12c646d9c9ee6507fd4f97a47c78ed8bb62496569b610aa667": struct{}{}, - "2185d4b5d29d9c15d60415a08abf2e191031b148e1a86104511ef51d159c3547": struct{}{}, - "71d7575667922ac5c970baaee34eedd23815ef5a0608db867cd49a9c25686577": struct{}{}, - "71f30df563d16be28f301c845e02bd4c501b3219b2fa4fe6f3a659e9a94f84e7": struct{}{}, - "9dd198e5fff6c45d22d364eeab5e63d59a027a1a243df7152b7bd3785fa3ce32": struct{}{}, - "a04afb368a4946b069ebf8e3ceeeba94ff114ddb477c7b5a5e133c5d52544d43": struct{}{}, - "66fd03c4d3af5139da2f2f4e9c00fc38d4d295072775e2bc7d365dbf1de488bf": struct{}{}, - "ee4a4d2d0f10b5f8ac34888ffbb8ec1f638dff5cfdd01a6ae7e5d0e5488c46dc": struct{}{}, - "b808e156d18d1cecdcc1456375f8cae994c36549a07c8c2315b473dd9d7f404f": struct{}{}, - "fb0f00141e6ce1aebe2c0236cac48142a91a92c37bae31bb01e2a064c2dda69b": struct{}{}, - "30de917ac4a73075ad4314d06c62a5be39a63e4eab2ef24e14577b07ed1e10eb": struct{}{}, - "505aea3bcfd1200ba71bbb139a3be6efd72847213023acddaaa537bb6e8dd6e4": struct{}{}, - "82ee927c614909da41dea17617a8d493683bc0909d00d2451d1dc0145828667b": struct{}{}, - "93722a04fa35a4daf711970c7bc4ad6a66eb690236929f5449e3323db268d610": struct{}{}, - "cc5be2469af0300f262941273e6e64111aedd4d65f72c412b56af2569a5fcc44": struct{}{}, - "74bd50f98d4b8b512e5856f1c718974d5b066fd040a879ff72a84e0501563625": struct{}{}, - "c50cd89c9e694e10a43bacd669bef5a290617453f7e72b37b663eba825e8748f": struct{}{}, - "65ea0d9bd9dec84aac1265a5309897444116b12fd92b914d6e3a82beab17aae0": struct{}{}, - "36aa04f4333ebc94260a496b27815226e288d9f946e5eca92362e42c09626290": struct{}{}, - "0e35db05f8d17d0a07b7a3616d020d3f197afe45e955c658b55b9529819ea1c7": struct{}{}, - "5e608d7beca7513cab18e6a396ea894f2c5466379dcc1e3888cc23d685aea600": struct{}{}, - "431f2c410c461003045b198bdf1ca9791965803b32baac97550fd4ddc1399da5": struct{}{}, - "6197595503f01ee2a34e403fe08d2e1d9d0c14cf1cdfc2b74739895dc9a15a04": struct{}{}, - "f58d35d3f2e59ce55e01a8b8970f3c73fcb81a869713da0925fc2ae8396e1c0b": struct{}{}, - "59b62c1176e668fd64e01012551a70f2a3abecc72f8edc8e84b4062d24d1d2cf": struct{}{}, - "070210ba6396d1b80e446f410fb67f2481db3a09056c0d9ad78f8456b6feec14": struct{}{}, - "ebf1eee550e1decd319d641155cfd344c68bc24bafcc3f5fa0c650f9645f6892": struct{}{}, - "974df8590f7e794e357169c3e3d18655f96396c3b5ff3b3b933441f4104f910a": struct{}{}, - "274dfec6e079fb08d6b5771537c54d3f0bd36c64c3d8ed0a4e6d2f201b489274": struct{}{}, - "35a5ea9db6c0b4a0e946902c33e1759a9156c50fd3678078d5c6628a6ec62722": struct{}{}, - "991540dcc68da0c2b8ac716f02e053a52671036a60ce5e122711e7857199e16a": struct{}{}, - "1ba7573640bbbb13a9e25d5b6946b560307af27e6c9cd040840583b186d280b5": struct{}{}, - "1ef8f25405f826823de51967c989cc74211ee7bc0f2f7b0363a4c251ab8ae273": struct{}{}, - "04695fa805d662a7ca811a5d9568e6cc080c07e6ec9f81360f8146eccb701cc6": struct{}{}, - "385e72535d252ebe80fe5a1bf98aa56939a2d06e89bef677238fb2b55487dd51": struct{}{}, - "c39006dae3d2cc34b14c7b9ae77812f6ffa97892b0c53932aea9cdbebb014c75": struct{}{}, - "b0e18c0332f39c44531d5bc1e09a47936e07a284ecaf4ef3ef92aa1fd4af9876": struct{}{}, - "778cc70263f4727de8a4b1a0b44f10accd7ae4569099479e2cb2702a186e2bd3": struct{}{}, - "cf5ae4c2ef2d0395a40c4ffb89a578f2212847213e5553c67b482646c60d7201": struct{}{}, - "765f5d1c36c48d4e6c3abf94b530f9b04d5c74ac27566feb7b28148c08cde6fd": struct{}{}, - "32f556c7248e9893205497fad5588b52a815c9a2008d165b36c015a90f534bfa": struct{}{}, - "4d3971de62245c94cd3dd8d4d3a72b8ff527d902832eedc8529a9717a969f1d1": struct{}{}, - "b0d51c58c8b9c1f458fadf16c7d375630ef51da4df81915893b05c0fa4ed8bc6": struct{}{}, - "b71094ef1b8c0fa08da9e2e2b0721cb2eb7dd3c65a4031d0405fa3d235c20a9b": struct{}{}, - "7246d2b2b9ceb9cbbf5b0c7612f3258b97856f6bdb57a79d935308fcbf08270d": struct{}{}, - "237ab3365842e475721a65d4a2a2623b2c4b24bf063cbfdc7c78dde01f95df93": struct{}{}, - "bf141eb335060ee2aaf05c8cdeeb4a234a37418cfa086b8d414c60cbf9104f2f": struct{}{}, - "5dd95c98aff2e783a09348f600def0d5e4e476f71e85e9be91de86aab36544cd": struct{}{}, - "234e5c3558c8ca314ebeb8b0b1caa10b5d8fb355e09fe9792d8e41dfc66cf3ae": struct{}{}, - "7eb76e118de60ad5a6edd5974094688c21aa393bfb7a135cd6ccf7267b24ae34": struct{}{}, - "b6234d2ea0d6022be63db80d7b80e221097fe4a469dc44febcd2a9241effdeba": struct{}{}, - "658e92f4f47fa18c36155d06bce88b2b9f37d12437c43ee16f24eb5d04a69450": struct{}{}, - "8b5e678f62578828f7bea930e7ba16e36291fc403e14f60f54edce8a4de2c316": struct{}{}, - "aa35818bff67437abfd17097ecc3e59b1b15498eb7027d7a02ccbb850ef43c95": struct{}{}, - "bcc001318078b15d38c4118731a405aedc12618dffc1ac29d148e71163e5156e": struct{}{}, - "2053dbbf6ec7135c4e994d3464c478db6f48d3ca21052c8f44915edc96e02c39": struct{}{}, - "f77582eb346bb0fe8e2d49c748d49b494bd1be36259d721c0f41d4d99fce77c4": struct{}{}, - "bd778891dbe86ede8cdb26c8d9c48419d7e5a8797cf1599cf58ee3087f514c7a": struct{}{}, - "705d0099fc64938376bd2a9b38d98ec693c4a69e5ba07e421c52eff714fde241": struct{}{}, - "e737ae5f6ead7b65152fe4c9c0bceda7b6b252c868dcdb456bd16a2e27956542": struct{}{}, - "5a468dab0c23e11ab5d3bedf1d00ad6d2849c06e60716860b60fdef82034f1e6": struct{}{}, - "eb1798d2f7c40233043cc98e5b298d31c0c163037a9cb821d5b34d9d03ecbd29": struct{}{}, - "6c8a739536961bcf34dccc343908406d48139344da4754d4cfe43dcf8d662607": struct{}{}, - "9de7933bab6f5b5be516af79a3c6a239e2498fd3d03a63570fe98e04f7232785": struct{}{}, - "1ee2fda849e9741c380e9e61346ddc85f0299b9a875f712924adc0bdd04e6914": struct{}{}, - "a0fd0fa62fe7fa3009dd3208868b3047007377bdce456afc68dc32fe59ba33e0": struct{}{}, - "707a59bd60638bab5184ef9fff3365c4c8f96e573d3327fe99ab034f62fe336f": struct{}{}, - "fb4e71f75b173583843c5dbb5604c53025493bc66bd5dbf2dd59968f37e01bfb": struct{}{}, - "9666181bcee9e6342e45c43e3accc9146a82378993b422a5b284be11975faff1": struct{}{}, - "9093f2bb8d45a4d70a44fdf56e0b62f7d6c9d7cb0748093b0cf3ff9d35b3ebbe": struct{}{}, - "0df3f23e48281f4bbb34918189963dedbb461ae70754330a5a00262d49ad68ff": struct{}{}, - "8fb7cf7a46995c95da6ad4cca750efb66946b771aca070d5948ea80ca33237b4": struct{}{}, - "219c7cdbedac43d32726742c274c52d064fc9f4cf963b68b0f2fa8384051169a": struct{}{}, - "c07bb1ea023e79019971f4e3e17f53b9f5a2dcc4a9828ac0ad638a1507afdd32": struct{}{}, - "327a7380d2cc7cf09ed5820e1ecdb8abe585d696b5b5526986dfebe70acec59e": struct{}{}, - "e505bfe95959b8b966f9ce50fcd854ce7db3d4e80e025fabc1241eb5e79bc095": struct{}{}, - "fddbb38a8c0c18b628a066436f44c84b57931307f274b62255e96d50d8627b8f": struct{}{}, - "04db248bd13040d52df9ada78044e23e9826953c783597340bfad2577de9aa0e": struct{}{}, - "e890ab756b3a37abcf33d52b38d318bcf93b1fb8b6a888c8964dfc9cd8391e94": struct{}{}, - "6577be77529947bf04c41fca275a6405b66698659cbb7889c0b5f97ecded21b0": struct{}{}, - "dbd423c9f1af1e46b8c491329bb4a1d4fb646ac9d0a2b6695907c0e48570ff9f": struct{}{}, - "d4f781faaca288eced6bd558bd2a78b302a871994fd802d86bd0541852f4d306": struct{}{}, - "5c0b1ae7ef3b0e1552cd215596a4449a8bcd5d060f18511da8e63b87f67c11f6": struct{}{}, - "e3e93b60bd722ced25a041f65afd4e396e2bafe57e0c3de0c8b6b0aa8b054506": struct{}{}, - "5a0ce224a457ab07daefcf79334d814c9b6c99ace7664f85f563b5d84bcf3538": struct{}{}, - "df41164e151227b31ca29bb188c936923a0afb26d9bd0a8e4ec6d05d4815c991": struct{}{}, - "91cbb8837861a24143fe06d54e4c3e8f7ad41cac5860016a44decdf1059832dc": struct{}{}, - "58e989955f4b358feb6e4580eff3c7a04f5d9fa2d8381a6479cdfffa4cbe1211": struct{}{}, - "1fd6202c9bb335c65040575f892b4b9269651729adf08036bf11bc4694d9421d": struct{}{}, - "0cae5b42cc1651b3a807a6ee557454d072b1cfb95d054d75d875fe8d4e78af72": struct{}{}, - "05eeec2d70845694d3ba89ad9e1c34b6b4d806275c3c63b84241a40ae4591ca4": struct{}{}, - "2e5ec44990a6c367eab22c53b4571f6d75f78547ba96654cfd90bc1cc03f0647": struct{}{}, - "832defad5e400860125df3678f2c59c9f2c8d727ad5a142553c6a22dec5164f4": struct{}{}, - "9c804f2550e31d8f98ac9b460cfe7fbfc676c5e4452a261a2899a1ea168c0a50": struct{}{}, - "f2e89c093680a9fbc5eaf86ac58eaaca034b888bf969aa23b105a1c0201043d8": struct{}{}, - "15830f493c714309a282f596af26224493a4243024368ca97437ce0a58e255a3": struct{}{}, - "ed20a96cba67c2896b494c7edfd907a96c35910ace41d0e1a54b65c06befa2d2": struct{}{}, - "64ca2ebbe8356fb7abb34c4bf9d5d630298d768eff85173d57af31cd89c6a4ad": struct{}{}, - "2c50624bea38c570f59fb28e3fffd11330856e2cd512760acaff9eb82d3df8ca": struct{}{}, - "0c7aae56ebe5d422f7f0f5b97da9856b135de81ac462c9c1a85ee53850fec479": struct{}{}, - "5e860a3d6d086d3c3af5a23e743f9311be0fd2634fbed6fdf9bc5bf971b3e9ed": struct{}{}, - "938b345cbdb06fd5ff6c3c2817c12f7b6ecba579640cdde2c37078dcaa543487": struct{}{}, - "3eb906846fa9951e30e264eb41e8d21b89d3297a2e44ef64ec88708ef12969f7": struct{}{}, - "cee515720c19b3ad855d35ab72eddd9107bde3496c8d9b0603293edc8b66d11b": struct{}{}, - "d92f4cf1e2d7e8129d7536dda13e3b1898da728ee64e2f9566cb7c86619bbda5": struct{}{}, - "838f8d9acc45bd36e3213c47c3222e644f44c959fa370bbfa6df46b171c02f0c": struct{}{}, - "9fff3bcb10ca5e87b8109ccde9e9452012d634a005942afc46cf2b7fa307526a": struct{}{}, - "c1143705b37c3d419473fc304a6b5f4f19105e807c95bca911951a8442318f08": struct{}{}, - "684a69e3a9ab5cdf9215bbfce92c19cb0c2215f290359b88eefc11fa3560f201": struct{}{}, - "4c67aa086d2b2c9debcb8ea2571d00f9d4f00873508828e9f8089079d80af8b2": struct{}{}, - "91b6bfdefd4d1a4f7713e19297a2e18db8ac6657cb53f575d4bc6003e5ecf11b": struct{}{}, - "1de83442dea1fe1bfbdce37aed114912d7e34a240bf4228cfaaf245dd4e62537": struct{}{}, - "9255d1c9e0f9d78b0425283cb09ea333f630fa9d252164e9a025a7bd2e73b000": struct{}{}, - "4c63f163e41a37f4c2e034705b0b917bb475ae60a3dc86a2e0993042d80e1a9c": struct{}{}, - "b00ef262afae566b51f740124a6b12d982a2cff1bbf2ab5632cb548e98da6feb": struct{}{}, - "92fe86ae3b66c45b85fef47ff41feb854a4881da3ecf92e76b4fa3a3669e358a": struct{}{}, - "929df7fc8cdf44855c5c13c8f4d7ef67a27e5a24453fd2974c214e2fde89eb47": struct{}{}, - "4ff17bc8ee5f240c792b8a41bfa2c58af726d83b925cf696af0c811627714c85": struct{}{}, - "016e2e0233be6174b5c1713c552b565d71b16cad9730e9132c0e016283e23827": struct{}{}, - "eaef53d9b579688e06f0ffda25b907cf7a2a08dd98d34debf9ad3d1a9e2514ea": struct{}{}, - "7993bbce88ec23ed01374be2dab1de81ea89d3504d8505dbb53d1a23ba22c051": struct{}{}, - "3c6252b4d95b89290f1a05d679bc3899b686657837f3c75ae699a9e0bff80da5": struct{}{}, - "b0c5f417a5a9c8af7e19cfb341d9fad0869baa9d473652fcba4ae5a872db6b30": struct{}{}, - "2534e2de1dec24fedab4c00266b55ba9ba60dde03a349c5b5662189dd561ba9e": struct{}{}, - "4f8bae3ebff459761d167d33eb1330cdd878a2c1bc255b710efd7828c0b7f6c7": struct{}{}, - "4598b401d22ae5b69dd6f9bf38a60ef91c5ada6b4b3d4a27295dfc0fdbd2e976": struct{}{}, - "7dd7122ad9bf240f04fdf988a0df4a2552098ad8ed8df429bed1056ebdb64387": struct{}{}, - "44e5bf55761da5636a92efd9e8cafe3cf3454d9799b09826406cbf7718ed2676": struct{}{}, - "21b3cebcc514340c829d81d1cf9bff130d8215d24d458ea3ff48180cca21c596": struct{}{}, - "2c769f9515b9d52af9ce9c802ae6347379ece479943650441bf7aa7bf279711f": struct{}{}, - "4cc7a4b341d8ce3315ea112ebb01e664bd328e88781db0f4bbc0e3f65e201bf9": struct{}{}, - "4706484582ca019815d7824470954fa03beef340e1335c66367923bab0f2f9c5": struct{}{}, - "b53a7292b38e011dbe6efc79c22d028ddd364da2e6b9aa182915572742330ea9": struct{}{}, - "cd34b367c1ad4228bbf28bfb0e8cefdd175356df08c5af6dc985a4939c06aed9": struct{}{}, - "ccacc3f402107b6f53288cead560305b9f8230b080280283efa23ae77904180a": struct{}{}, - "b492dfe3c3a200a05df5664b4f24752c954c8d35e395abc5864096efa6dc1690": struct{}{}, - "8cf37351b60f00084392043ce2e6256b96cea92949f90c7abce464cf164fbfa6": struct{}{}, - "c013d16a335e2e40edf7d91d2c1f48930e52f3b76a5347010ed25a2334cee872": struct{}{}, - "8134894cc0b99b8115a472458fc0db9d372b8a1d13821e256fcef809d9339bc9": struct{}{}, - "fb398cc690e15ddba43ee811b6c0d3ec190901ad3df377fec9a1f9004b919a06": struct{}{}, - "b3fa55f98fcfcaf6a15a7c4eb7cdd1b593693d3fef2fb7aec3b6768fd7c6a4ce": struct{}{}, - "4623f30b926198d4284b8b9b502f1717fc4161a977960219738de203bb699a96": struct{}{}, - "aa76d267777dfd63c4dcbc1ddb2cb482956eaa533d57c1d325f0ae2ba772dae1": struct{}{}, - "32d88265324261e86e9d727c4a98ae0ffce689c3e69d7c324690a1e52cd7e07d": struct{}{}, - "7991d51fe699b9e7da97be2edcc528fc5a364e88e6c0e5d83cc96f4e865a64c2": struct{}{}, - "11edc1f22761db0f5555a68f3b8ea6fe05f94e3ad9d18bd5ffed29aa554d4e31": struct{}{}, - "6c1fee00e7fcd17424728d40f2daf4fd121dd1c6c648806e599fae3643f90062": struct{}{}, - "e177e948f4288fe9f3c35f8ea92107cd7dd0671d7ac64ba587a4f15073536ea1": struct{}{}, - "f2cac285f3f33976c43749c5c4aa99ac5db5b36b74444aee45661a9d88eb48a4": struct{}{}, - "197d84bc4550d91f11f72704957d32a3becfee032a11a912ff3882c916e2ac5f": struct{}{}, - "5208dce8de0fed86f96119e8259e7f99b9fdda607810db3c54e36aa6ac163469": struct{}{}, - "bdc1e182d3b5e3b3e9bce4fcd579671ac7ce8ddcc577714a90677565f60ec9e0": struct{}{}, - "fd8cc779bce474283c3ec89ef0ee9340b979e150d515b04796decedd809b0638": struct{}{}, - "f108b8419e3e0881bacfc9d8c2a7afbfd2f60ef764373747a0aed5f14381fd20": struct{}{}, - "591e94c45b4cb95db6c585bdfe89908bddcb7d27b5a2821e139f2bd950c20006": struct{}{}, - "0df89317e02535902d116be0f27294a75145339bf4af53fb35131aea8071a0e1": struct{}{}, - "1370740a875453464479654d3296f61ed3d41a76c6311c697367a88a6e3f872b": struct{}{}, - "449e7c4ff29002ee7d7481d9fb9fddfb2ae374501f96d8675b8d18feee414f19": struct{}{}, - "9757da17b7f90a6ca812ac70e28742c81878f52710466ea29f4e912cfabe9cb2": struct{}{}, - "af7c752bf81f575efe5ac39f7ab8c61a3668ff0cf7e950114194fa5267cc830a": struct{}{}, - "6b4a1673b225e8bf5f093b91be8c864427df32ca41b17cc0b82112b8f0185e41": struct{}{}, - "18b3dd50ce9a32b4f6d76d19ce199e559660f793269e88cc4279ada9b64762f1": struct{}{}, - "d7afa135a6c4f59914bf29c0a8d8d8384218aa832f46443d49d600f06945992e": struct{}{}, - "e8a434aefc8b73e5c87a9d445893e02c784c59b0993ea38cfe663b7666860de0": struct{}{}, - "3ecc9f981fc71aaf9cd9a4c2af50c41c190e0366d21da19a465a9918aafe3b0c": struct{}{}, - "f6000d4ef895fd961f1357ef1cdc690203b81a06fd8f19a54a0e37ce75900038": struct{}{}, - "47eebdc3e48d2242c4c226d7f67be39eb725545258f159203491b6a5955f6810": struct{}{}, - "400281df24a3da583ff6318eb4901953c59f9912aae678f5b98495cd0317f3e3": struct{}{}, - "3fde9a502dda3612011fa5a268a44fcda89be418753e7c93b9a197a20be74c12": struct{}{}, - "74ef7ad4913f65345462d64f8ca09345ec8881c3dde2a041da9ae89b0c1620e2": struct{}{}, - "ccc35fb4b392ea6a833d54b86f3fc757f2a92d62496f5001484f83bb88738113": struct{}{}, - "5a9cea259640cac7ec4428c3d584606845c2cb0d6c353252c6487b2cc0be6653": struct{}{}, - "d987bfe99e73ce846a884e2500db73ea8d266f849650038b70d6251e7a22ea14": struct{}{}, - "72caba8ee566741147c00f150658a978660a36726373d51a8c567e6580fdd704": struct{}{}, - "2097c4939e521b23cf33a816167d89e7def3e0fc8e9ed44e2d9cfc69a22fd2fe": struct{}{}, - "31c697bcc028c91a5ac6d64921fcc738e498fa55958c2343e20c43613710d000": struct{}{}, - "d9070c1ac291d3f9d6bdd8292e7f0d14827c6fc69fe1f41a382ce73d4403bf3f": struct{}{}, - "c898ab690b82d903b50458f17f5d2da9316ee335619a208c823b723317c443bf": struct{}{}, - "abd8cfba151826bb7bf3de57387e9653e9d7f54d61e2e107213b3add4383cf91": struct{}{}, - "dfc64d310dc0410d16841cf582636d050d11222d0a1fcdd832323597e2e95538": struct{}{}, - "76c32abc03629dd0977b4b9f1ae43c5d9f52421dd65d31df7a274ec3e1289049": struct{}{}, - "3614f7e180d9000547fe4b19cbdb4d222cdd4d4675a023588a69fbe2d1140189": struct{}{}, - "d4c519bc99747f1dd8cd16956726ba4b29afd185e9051ab64e0f3bb4c62a8437": struct{}{}, - "8218d7171c3dd0b1a102e393fc54735c42c9b1628c02d1279a02b8a349059ad1": struct{}{}, - "819423a698f9ea9ba3577f20993cb0da98a79ea22ce5d6550b65b69fb36fd438": struct{}{}, - "6382b3cc881412b77bfcaeed026001c00d9e3025e66c20f6e7e92f079851462a": struct{}{}, - "f6612099829d493615679b3a250aeb3c15758b1bafaa8c2201b23ec822d33771": struct{}{}, - "01e76a28977874f8b72265d0d39fa47c4105083556013f84ded1dad7798d01f7": struct{}{}, - "07f5937c9760f50867a78fa68982b1096cec0798448abf9395cd778fcded6f8d": struct{}{}, - "7b8ad5d2420b1c7d41b678313a7460f7d3ec06890fbea4ded708c2cf09e0ef3c": struct{}{}, - "263df4565e2ff1a307063e087e68d88190ad01ae3dbb164aa1e8ec6781b9fd26": struct{}{}, - "0fe7c9ef9683fcb85c7fecbcc9311abf97601b4517a6486ff70fb96ac472c906": struct{}{}, - "c463cdd645eaf27cc479f3e151536d719c1e5db8dd8cf8fb9061108a65f4017f": struct{}{}, - "408567641e9fdc6b4cd95708d55b3beff1757a6477c36ac50e27751119a3538f": struct{}{}, - "aa8e17bf49a55a4d9a0192aef5ff9274b92fd6f010318edf7eb6c0bc46ffbadb": struct{}{}, - "0945afc5bad2bdd34bbd1e802d3eb3ae0dac1945a96f53518d68458b97c41ffd": struct{}{}, - "466311e2f4ac3fa5a64eb263f57dad94290191735be59597360edfb4b656a2a1": struct{}{}, - "2a1af0fcb280fff11283c40324049b24812c59d2670472765e5d60640b4465a5": struct{}{}, - "4b928f077362aa16836f338bd04ac17fb8d77d821fbee74715b62fed54c1e1e2": struct{}{}, - "59d6d61431fce7d91388d0c60374ddaadc1acd8370221e11b029621656d5ccec": struct{}{}, - "27eecf97f30dd4cfae9368a50630f2cbc72d8cea30c927599bb37679d96e3ed0": struct{}{}, - "e0b22bb8eb6e8b88d287c723d7ff25eb807d4f1a3702e297aa736d9fcfe4e1fe": struct{}{}, - "4697c20f8a70fcad6323e007d553cfe05d4433f81be70884ea3b4834b147f4c1": struct{}{}, - "82197eec45d7752945d9281296cd8aab6d725d54fb120169693fe05759c5c0d4": struct{}{}, - "3481d95f26325bf6e7ceb5e9aabd5e46d9df0bf6e9eabbe7d59e90f5d0aebc34": struct{}{}, - "a53deaefbc39492bc7dffd5537ad7f1aaaeaff4958cbb90e6158031438b2ba75": struct{}{}, - "5a9cb6b54ea56d52a69891af8c21afb73d3841611bbabb5d7c61312d81e6e041": struct{}{}, - "608738e4ac086c407df30170a91d0df99790ad277058b64cd8ee0b24cb29dfa3": struct{}{}, - "a6e0110108a92b4207ef5ce36e51330ab1493513a37214252c8ab8561800ac81": struct{}{}, - "27d0b0931c0048983aa4b48e71fa07e6355c18488404b2ba80dca53ab7edbe28": struct{}{}, - "8ed3d27fe50e9b60dc78bd80ae75788ecfe22f4af26b10bbf249eef194b8879d": struct{}{}, - "a90a207374d29a74673633015607a6b636efcaf06267a5ce5c90af13d1ca8132": struct{}{}, - "bc5fb9abe8d5e72eb49cf00b3dbd173cbf914835281fadd674d5a2b680e47d50": struct{}{}, - "9691cf90923cba9d50a547cd5af5c04ebc0f40055a10f2f38a8b020a02e83c9d": struct{}{}, - "ecaf69bb4b06f3f9f249c6ed835af7d9825493fe03e146535916323148be9607": struct{}{}, - "9680d29e9c2e7f5d02daa5e01106b12897a1120833d0d1b76df6cec67f75ea29": struct{}{}, - "114049bf85580c9d5a699a09bb8971425d0c9f641b6bfd326758333672ec73a5": struct{}{}, - "f77fcbb21ddefc82222ab3ef47e7281dae0298b3b661ce405e62dfd4c9d7cd74": struct{}{}, - "2c8cbdc2b318b91b5d33603d773147405b6f15558bc8ea112ca095b6b9604305": struct{}{}, - "a467a313e8c33fdde103ed3a2c37e4ac445427fceb248e66b6bcc117d9ca683c": struct{}{}, - "57a9a17457c5917516fa107320e5d60754ade681fe47c45e0677d7d8ff87993a": struct{}{}, - "78e370b587b145920213731b7c7c725e512b3b6577c51c800218a7c764c532ae": struct{}{}, - "8b8b7218078d6dc37714d262de5746a3c89af5218f08c5d82f033a1192b504c1": struct{}{}, - "8571c2311a02792abc8d9ee85ed330f2b036e1b03628310c12812989ab394ba6": struct{}{}, - "dda3dabc3a047166da03ca66d076cc5a7faebd7981dea4a693b21c7d1734dff4": struct{}{}, - "2161403032b8314a5249774f9418acf04317a84cc1dddba989d108e763d557ac": struct{}{}, - "65f445d935fda970979f1cf3b5d127867e277dd013294fa3dad472d7eed4d4f4": struct{}{}, - "b67b8e2e8472a0a87a3d64d75995193c1da90bf098123586ceba69881880fbb1": struct{}{}, - "c13e7fab3dc66f9813e56218d0d09cb2c836b217138b9c6ef53f5ee2e080dc6d": struct{}{}, - "2f33c5e4d173ec6be332eb10d059e928e3de227037bc910640ba34ccd24458b6": struct{}{}, - "71aaca2df6978aba4c91b4be3e4d98c2bea0ca99f6580174918d5b329934cd36": struct{}{}, - "051375546db9782e3debc25e0241edf1d5e5e2ec0f183dd8634ca5b2c8968bb8": struct{}{}, - "f72a0856b4e0f020c9ca2e46c925c782aee5e55ded6faefd1771c79575f81dbd": struct{}{}, - "10910087de24bfcabe854fda62b5023a61fe531705043b9979fdf00f12c4c0ec": struct{}{}, - "ff6668c9c0541301b18b3da3be4f719151eb0f873f3b74dbb036ee00434cee0f": struct{}{}, - "3608e4bd0e693177369e17f48cdf750eb962b86aaac1bf6b50c7a46d52f7d94b": struct{}{}, - "b73b6cac0271f5fece398cdca927b0a5e72ffdd0ee869127285c9362190dbe70": struct{}{}, - "ef2af8d7a9663b18f3010435cfe4eaf2cb99c961915dd00d87d5f0ea9928ded8": struct{}{}, - "11c8f75cc035a5b3c3a84ec6812e832d66b7876a0ee798e7b39620244b557ab6": struct{}{}, - "7ae4495c83dd7323b71c94739ddc7a45dfe3cb6a851c31fc1d040cdd47ced985": struct{}{}, - "96879611650f80a81392a52e0db9b0237669087c4518e1c130e541a505e0eeef": struct{}{}, - "e782df09e757c73ddcb696c2d71e508861815267e4507d338f6770bda671f553": struct{}{}, - "4dd3f666e4d079f917eeb0034041161ee9ea322d20aefdb8df4f2851fad835da": struct{}{}, - "fa3ce2c007b2f1e2ba1ba90fcc76fe2276033c7297487edbadd034955044f652": struct{}{}, - "74611c1d6455b534323a21f8133a6f43dc3a8188e7b946f96dcc28dde932fcb2": struct{}{}, - "f446f09d80da354cdf1ed70a2a002fa5dfc2a8aeb501e1823e3e8a4c2b2b3017": struct{}{}, - "2c9e0a2585dc7406589a3724f0027811506e0f133726303a15d6779d532a2573": struct{}{}, - "e5c678e639779ec8461466974b3230d06e4641fc013b809ecfe0df685af04470": struct{}{}, - "d5948e71d76097cbaa54d55ae21efaa228c882f2e720bad05828765c5f5995ab": struct{}{}, - "8ee33cb1612204b475c92b4157cca81efba5a934a91eda766ddb6a1cdc89f346": struct{}{}, - "577f54f8f8648285cd5e2be078f3a67a453a365c236448fa59cf7d6920f8fed1": struct{}{}, - "5db6810e7cf9ff073c275265cfed5712b136fee26d7281715196e55f36f00279": struct{}{}, - "9426ee389a5cae080cd327cc7c66e4f52fcd89d439e983d71039ed898be366d3": struct{}{}, - "d7d5dcc369426e2e5f8dcb89af4308b0daed6e55910d53395ce38bd6dd1a9456": struct{}{}, - "e8ea01955a20c1a6a59308db33ad1ffe59c8b7cefbbf97d32e72820f917c52eb": struct{}{}, - "3d5c95aa66ecc74e56b4e1f0d7d6705544062dfb4b5dadb1a177c6dedb3a9a4b": struct{}{}, - "fd0bf83bea46c356d692dfbef382b65d5d15d3b8cb0ab93a5a6f834b040fd3ee": struct{}{}, - "7be88e1226c652f0477a1a71391425612cbd94a5d63ce391103933acf76c03de": struct{}{}, - "95077276ff3dc739e2d07f23f891a1ab5564f2a86ef0f7597f5d408e43684e59": struct{}{}, - "1498644a105b06881c57e984b40eb529d35b6cf68fe6bf32661a3cdb72d21112": struct{}{}, - "853dbc39c711e07347cfb240f4dd4905f3d2d83e5a14397030449f3737face9f": struct{}{}, - "d277670919a94ba361be1887d39852c3f31d7eed817343cbb70fcd8910841f86": struct{}{}, - "c14758dbf2d47ec5511fa42ca83f88d04ad78c5cb27990114b372baea8869753": struct{}{}, - "aec98b8bafafa93791d6204b4f6ac28f7562e1b6e641a67af23e9a4692dbd073": struct{}{}, - "13d42ec7b376df6c11302fa26aad8ae9f70f2cf6820c332fa94324f61720d7a0": struct{}{}, - "673a21f1243ad530b63eaf29148b951d450ba496be5db6b0e6b930bdd59c2635": struct{}{}, - "65ef5c0a2693179c203283f66015f04929c1279fcadbf006a82e8fef52d4eb83": struct{}{}, - "fc5a79fc1f9898cee2b920f7438e0381fc2a6759444f9328a04e6143e23257e9": struct{}{}, - "16e5f60f207a1a0073451c35a9d315d17fdc17d7fab66449b00cdfaadc8b249e": struct{}{}, - "88d201935450c2abebeeac0b5ff45839282b790cb4409ff583a5f7d0d54495d8": struct{}{}, - "131db0b57a618771d4d791b8e065c3286ff3b0fd92afb2dcdd6119256688f94e": struct{}{}, - "2886b0b748b7eddb7e5f8f1e07f19997061156cd03320cc8b26576957e346f19": struct{}{}, - "b10989f1c95d1be52cb7d3e09ac468ba2756c4d1a78e7f83959cfe1f9a5890b5": struct{}{}, - "a851d44c6ef276e18b2c8cc6c89a73ef0423ef74ec36606d9b4016ee05c29498": struct{}{}, - "331c96b8e9d67741d82c3d0a30da8d0e397ed2d39400e3714988a0d7e6182c79": struct{}{}, - "53008a4021d501893a3fc103c1dd05c23342327d230db16793a6302775dd07b1": struct{}{}, - "c838909c31e21644737062488eda9fdd0544375e3fe7ee5fb8c7728fc74f427d": struct{}{}, - "1d45adac4616bb214f4788af0a64779a1d0ee43a58a16c5dd5b328c09c70f353": struct{}{}, - "05cfbe283381aab8902e93d508ff031c0bcd3f95501c59ee28ac34b21b240fa3": struct{}{}, - "76a260b0154e9b6cc48e76accf8fa5b247446c85c80693c5dff34709c7ef13ef": struct{}{}, - "6bf3d7f77d878d880a274272b6717bb4dafd5e147573060980ec2028d63ecaa3": struct{}{}, - "4928cddea529a569d729ac6815cb331974f4380bfe9394838ff51a683db30689": struct{}{}, - "5335c1c78b99ea77b73cc03f735adc472835dc47c10e553d20f6e7ba338c0da3": struct{}{}, - "ad621a23bd5c2474c1fe0909a8a38bb5fcccd382e855f97ef06d63a06b22dd7d": struct{}{}, - "823bc3e0155c7318a880da686266af5dd4bdbd8bd4731cfca8e1f8231ce8e347": struct{}{}, - "d2a165878801f511a9460f1edbab3dc61448664ab44b5eb9722fb1a1aa9b1546": struct{}{}, - "c304ab9c07e27e49d2ae4a20349e405078429001167657728cb55f2db8a96e6f": struct{}{}, - "d47c6f591a677ba80bd2f4e884f3a7f15fd9b4aba36c68df161517f594f8e942": struct{}{}, - "c40677cf313fa90dc4584b00c6e7211fcc1520d3fdbbfc941b93f03889b284e4": struct{}{}, - "e8782cc5d553b697984936eb242d9632abb64468292caf585ad5aea7a718a264": struct{}{}, - "a0132bd0487e889f18a50ba8bd17644fafdb37e5fcf23515b8815eb451f20c10": struct{}{}, - "9e500bbd76f464b465f48f9f4d33a64ec9fa00f77ddc9a08ebebcdcd3824ea62": struct{}{}, - "97e42128566d696e4262d5bdd46c22e3a110c9b716839f53329790988f50088f": struct{}{}, - "fee4822e086bde0ca5facf96fd4a17210cc3745f0c5ef638aac0b80a6b835699": struct{}{}, - "dc0b83026234a3f5a07456285aff07081ad832d5a6ea6088fbc1ffa084324adf": struct{}{}, - "12ac05147efdd0b67f567003c7d4e231b74ab82cc728dabc86093e8d89af2c29": struct{}{}, - "8286ee4ea8f54f0276bcd4a125de6fd38a58534eebe24df396f05febeee3a91a": struct{}{}, - "4a71c27904b98b02b8bfc39ccb066093ab9028270771f5cb43ba26b3d24f679e": struct{}{}, - "9e334420f8c18aa18343c5178448e91b302147a4bcfae6e8178f1e15e4bc481f": struct{}{}, - "f7c5e245e64a60c10c3cc2792a0a49e48ea03e6d227e8eed5b7a4de2c9d4e760": struct{}{}, - "d444019213e4ac99b381c9fc1d7d05d1a48c45d8e9a9eb8ed1dec0d12f3467b2": struct{}{}, - "7f3c9428e7c6cde040dac9e0eda6684b4eae846bb5ba4345f271f8f1f86f5793": struct{}{}, - "1c07b8c298928265d1176411792c6f59c4d8fc6839598b674e2088e9a4d403bf": struct{}{}, - "70b5bdb210f523e8260aedd5cf84fc9c684aa93ede98669204b715dd4289efda": struct{}{}, - "968ee05a9b93a6c8c1202a24262cffb4f899fea10402b47cce3ec66ac9102bbd": struct{}{}, - "d284bfa94436b560f38c8d0bc1b126728bc8e889264e8bf2ee172d593ae8f02a": struct{}{}, - "7dc4694b0349d563a23058532548bcd6f31674d5dcbc518bca7267c125a3f6f0": struct{}{}, - "64e8abab66c84c7eac2f7be83e5b6bf84a97547b54c8109bc2816bbaad527850": struct{}{}, - "eada3e9ab4711c38e854bfec51d026b92ba9041a81507b9389635f2b0568693b": struct{}{}, - "74e1bb62f8dabb8125a58852b63bdf6eaef667cb56ac7f7cdba6d7305c50a22f": struct{}{}, - "ba7955598f2007bc8cee7329ece0f63ee59f2dbb40d59ad17aadfee76b1092ae": struct{}{}, - "94db8e2a7a42ca0b32bda0e0aec932ebec958889ad8b029a1ef634abc577e099": struct{}{}, - "c7111f14778cd1d81d1b51c68378b2aac5c8d71cf13e659cfc887b4968e13afe": struct{}{}, - "1a205486da90ab5351ddd3d38e6295dbcdc270cb2a4b7fb78a8feac0237559ac": struct{}{}, - "4fbb1929d4e0878cd55279ce2369d13c4f7399eb36da2fa4427e9cde5629c7b4": struct{}{}, - "1554fea732db61c9bead5b0df7d0d2085ecaad0955aaa22122c2cbc5e2c36c39": struct{}{}, - "dd4cb9cf52f0a9f1529c3b994274c8ab304b6b9f21ac69403a46f639fd026b1a": struct{}{}, - "45569da57f4b7bf472d7a864ef4781451cae6383fee9fb0ae40c59aa1ce475b7": struct{}{}, - "ee55ae5509afec08103d324a15221908dec9690aa68a5989aa956af5a3cf6e87": struct{}{}, - "b2715212d939ec804d6dba79efbb62ba7c0b2ae61772a83c1a8c8256ae1d9ad8": struct{}{}, - "c550c2f4a0d4fd8c16293e0ab094b22605cc84f7e77ba9ef40df516d8df0fbc6": struct{}{}, - "96c91fa847821d6e526a7f4437efbf43ede6dc52e1c38b5642a3357ba1aa6a77": struct{}{}, - "840cfaddfed15e5449b2dcba2e1d1bf5598a2892ea0cdaec63a8fddabec9ec99": struct{}{}, - "2a2318eee11eab3b61d86dc45f04a061adc043f0c8b31dd851e3fdc1e64302bb": struct{}{}, - "33e9f8a4d7f1e87025250a318275f8f84482fe613b861380c790de019658b74e": struct{}{}, - "7a5356659c5b128bf2a1cfa958aca12b66573ecc89e0089dd5a74018541868aa": struct{}{}, - "d9e218ac870d788c2b761701c15ad2bef9c4849926963abeba20011d2538930e": struct{}{}, - "222b0bd51fcef7e65c2e62db2ed65457013bab56be6fafeb19ee11d453153c80": struct{}{}, - "9113a84fdf3be2173e0a20ac1c3d3f5edceb0f42aa153b9e6c8e1fc5e0fbfd34": struct{}{}, - "3addea6406e681ba143c53e9956a80698c6bc561ba02266c6cae411641fced0b": struct{}{}, - "d15690f08a575024650b01ffac892cfd2b93e6c57c140f1b6d9e47753cabd579": struct{}{}, - "4991c47202960c5755d6886af261b0d8f6ccc929551d867a6f557a9353d4f1c5": struct{}{}, - "ac3160b0a933ac03d7fb269baf8443e65936aa4322881e30c60443d7dda152d5": struct{}{}, - "287dc6923bef88eb5268423dc4498096b5547f71a6c7a72f0ffa26261fd79d84": struct{}{}, - "19dd5949ec28497b5c2794c5874e30f00ea5775896322966cb70325a069e2614": struct{}{}, - "696cb6a2fa76011c20f987ad4406865cac28b649650fb4c1be7448865b410f8b": struct{}{}, - "9c0c2a78300d97b718aaba071b3fa96edf98e0101fb77514094964d7e8c69fd3": struct{}{}, - "36301544da6322b2f83faacb0f2c650ca5c70ede4ed4b79f6a57493289f4498b": struct{}{}, - "82807f178d72f26eb05854804dccb8c7173de2c32228222014a167024745060c": struct{}{}, - "1f3d870f63b930e4b0ef9f0f7faabf9a4fd38c9f41eabf9c9526304fe1519280": struct{}{}, - "097092286f79d3a15821858d951883d4054c5bd0dcd89374fb64dda7ac6a05a8": struct{}{}, - "06a7928f52327a6422bd4bc6e47545264092bf5701f8b1ddcc3ec00cecae15ef": struct{}{}, - "8870d3486fd46f77182ccf3ba8ff1bdb15dce450ca5b109eb2a4b6cb29ae97da": struct{}{}, - "80348a765e5b2971cc36f5f653cac9afc65e39aee6d38b81ad415f34ead2b4de": struct{}{}, - "d0d54148901decda61ce2897388dadd4b058a6127381432ed6047279aecce7ee": struct{}{}, - "90b69be848db91f56c1d97e22fab15b529a69928f536e8ef15f55eea7eb4182c": struct{}{}, - "c5e80ac8d1e88db988a45af63d10fc1855193794f399efd4a0e592e35ed9e21c": struct{}{}, - "d7c075af0cd0ca7f1336e5b8aae82c2c8992b178866b600930c7d0a1e0cbd620": struct{}{}, - "949c9b3445673a730f7b3d4420bb894a610e3be1da040f3d893d760405037f04": struct{}{}, - "ce98eb1cf7abbe7ed6a49678c73cc2bbfecbc756891e5223537c327deb7e73d3": struct{}{}, - "2a0213c7d9741c04aebf6d2256c6684310acb7315374b510ce3cd908eafdeb12": struct{}{}, - "d436f5f7265e988443a70c61c935ceaed32743d812119d17fc7dd78cfd9255c4": struct{}{}, - "15cbfab2cca18f28ee61e8a8c28b11cbadcbd1cc93f528d2f9e48e4000500004": struct{}{}, - "f816c569e17d8aa1cd61992072899e5f2f7354e0aa8c9ddac97bbf1bd0522c97": struct{}{}, - "6cd1442aa2721a94defc8aa44278b15a608b385c232d8ba4f1bf8718cf1e479c": struct{}{}, - "cde56892bbebbf6c5fe56cf3317f558aec67aeb0ba19504ca3c9e6801cb45b28": struct{}{}, - "172d5da515b39889f58a986825727a07c483657c7db06f1d8881f958eff11a80": struct{}{}, - "ec52e16ec05fccc1ca4c9f9be714fb45e4fab9649e64d82e3f8c839cb9118e98": struct{}{}, - "5b6c5e019d0ca317aa0711c7db84731abd34d6019f6a9ef07ae74d424b9d634b": struct{}{}, - "00c00790c439a4cc074d6ab9baa60520127890e2f7ae0ab4fe78e0a6d256a19b": struct{}{}, - "697b62c258b2260d0a744352b5fd5d2be48b71f60f67ce200f05e9b9afeed7e5": struct{}{}, - "e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904": struct{}{}, - "fe1ce1f8c675028ad574b2c5093b3bc4744e824a28e5992b6a65547203297b08": struct{}{}, - "2374d91794b79f4fb4ec9587d2cf00aecc4f9953fab3ed1dd12ab147a0b721f9": struct{}{}, - "5e77061a9b955ac03d9a574a8a68d8a58005949fa4fdd3d9e38dbd263028ffe8": struct{}{}, - "45066dd976d8bf0c05dc8dd4d58727945c3437e6eb361ba9870097968db7a0da": struct{}{}, - "bce6e56d30ad9fe09977ce1eabf36a35fe83fb8eda7bfbc3971fc5afd73b1984": struct{}{}, - "1502fe3254c08a797a35078d979660fb66a51393dec732df85d8fd284521ecea": struct{}{}, - "8e4f30f0e782df4297c0eb8757560472eec82897eae8eb11142ed383eba21582": struct{}{}, - "3a7a3762ef7b56d077cef121801431a291a34bd96f11613f508eff91a5c4ffac": struct{}{}, - "ea377f45367c8842dff46d5034c553b0e6de3a76d84acd6778bc58a2f91a444e": struct{}{}, - "62b06d112603d518b8c769ad26618c3c76350914ba87d6f8f77d303ad832dfb0": struct{}{}, - "19e05df6b2e5fb94f3ee7eed2c02d340a1128a00231f5f6949641a143ab3b57a": struct{}{}, - "405af113a11357d58639d1e79e1a2131207e1a1024d9377cc7a6f9703befeba3": struct{}{}, - "6cb51aee46136232e04652b4db51fcc1b951b81b3640014631414a02a30a5e9f": struct{}{}, - "c49cef71e327a4b22843025578e6c2a2ab69ea6c17fa8a6054db86c1b342e3b0": struct{}{}, - "3d07a4f91e7bb145e7ab64f7be4bcdb40c7932e07cd6601a576e31292df3de81": struct{}{}, - "5ce5fd5a3ab952c8f9febd617126a81dc4a2b35f0777478d543e4b3eb2bf98c4": struct{}{}, - "b304adc33719d6cddb96d6c4f1aa46628d927547b49b70c981c7fea953e0600a": struct{}{}, - "4ccf3d5681660ab6e759a9c90bcf270ffdc1ad57678f15c40d1336f6174c7f6d": struct{}{}, - "15d7c1aa0d1b04dc11e92189ac893dea16797adffe417e4f116fcd78e2d06e4a": struct{}{}, - "879c5a31577aa0b47ff63fd95a9cfd8684d83d7f127ccac6f3cb10b4dab411aa": struct{}{}, - "13506e0712c5c6933fee43d8d1e1d7f0bd3feae7b5e502447280700bbc46bbd2": struct{}{}, - "cfa5b483895333db22f123afb255da2f637acb591c1c2a2277b68a928d94d8a1": struct{}{}, - "16552853f9506d93252aa532361a78ddc4ae14bcd51716868ef4b7449c27ea9d": struct{}{}, - "44ae8f26b0979a8a9df7f86b1c0a786f314974ade37cc3ccd9cfa7c4ade23d47": struct{}{}, - "397e53a194b68b89e0a492961020f642a727efba077258491c7b149dabbb79b5": struct{}{}, - "4c583ebdb08357ac0b17b3702c31020418e2473076d1821901a2a2306788768b": struct{}{}, - "915a0068dc84a301798cd3c42814be2ff4000c08353b84c1aa8f9668d01ec625": struct{}{}, - "6304fbfe2b22557c34c42a70056616786a733b3d09fb326308c813d6ab712ec0": struct{}{}, - "27a0113e9ce8bfd5ba29c172f6ea84f16fd3086b3fc0065e72d6101c016433aa": struct{}{}, - "fcc2f4047467806543e2197c32a737aa745c889f162884cbbaced3c2b4bd48a1": struct{}{}, - "9f1277d878f36577f1a8d0f68773d1db220546fa956e565ce790ed78338c9aee": struct{}{}, - "8025e243840aa3f44a615e513ecc245271ec19ca05e57de0b94c420056254b62": struct{}{}, - "4a173307c12a52d82e6ad896a4dedcfb3b98c2724f557579c73a309d49d04413": struct{}{}, - "a5c5a15eec449ca189f1bdd67808dc2e3738a6851e85a931ba2ce43100195927": struct{}{}, - "d11f3961726f0347fb81a987813d6f21d9ef6502b2afe11dc130894b228d5770": struct{}{}, - "065ec384cc2993d900b4f800a36e21f561eb0e7b6b42f73d1deacbdb90e9b633": struct{}{}, - "3db84bec8c36d5672f67e2aa445dc59b06e38c54a44157b0e63e729e63725953": struct{}{}, - "7f89f6215e120ca7ebade16233e776e1d4f3286ce3ba802d0eadaecbc3d34d5e": struct{}{}, - "04aa39fcb509e7842f0bd5b135b6181b0b57c8a74422838362f430351fc364f2": struct{}{}, - "9fc4779ab338c50761a5db6badb02c7ca960885ffba789f417829bfa060d4a46": struct{}{}, - "bd28869fda46dc126b974e3ad5fe2257c51b1f3bda5e798ae60ca6de8032b0ed": struct{}{}, - "295099c764e6439694e99d2c5be4b5f9fbd84b7826dcfcc7704f16585fbf42bf": struct{}{}, - "a667e04dd7c8c794ad2719b5a41e6e203c3a6d35dc603a09e03413b24834b0f1": struct{}{}, - "22830e31fa7eab7e62237e1b95a3fb7ada7f01915815580a35e5ffba4abe5c8e": struct{}{}, - "d7ef0a04f3c8055644677299a9414a75adcb15916eb48417416c9317ace2ff4f": struct{}{}, - "8a48abbd5535a6de051dd167111e33abe996ddc7091aeac8808ae2c335d505ce": struct{}{}, - "02d2c8a77642c129484a576baff80dd186b1ae30e0551ed15d07165e895f420a": struct{}{}, - "1d8fd8ed17e653139ff2a219d5d03af0d10e7341c9ef0336b23f0aaf93ef35e4": struct{}{}, - "37b5fecebce720fddf3581eb8afc6f4300f5b5fc91432214883fb732a4ca8cb4": struct{}{}, - "e6339e07ad266019859f299946c9efed68f4f666a92e361164d8563d7498e4a8": struct{}{}, - "4be045cf685bbc402f2d7347f64ce104ce71f08158f95a0a28d9e61ebcd80141": struct{}{}, - "b2fd5cdba2b1484db46c26a3426f70878d2b31f44f18f806fd262f34887a6320": struct{}{}, - "d07bce046a93196a8b94f89f54f2f70670474cad4ae05e5499451e547e80f6cd": struct{}{}, - "ceebe4612a1cd8058d45ae56fe55b8276375c8f1c46fe082175112b9c973706e": struct{}{}, - "007ac4200b49a365708b5c1032ebda275ceb56a5c1114751da2c1ffd707ed689": struct{}{}, - "d7cea7305a333d5c9cebd7891abbee78960632bbb1fa5dd24122b940ff823ace": struct{}{}, - "22e4f6912a6980d6fd59e0c5409247407491282cc8dbd009734066b8393a5761": struct{}{}, - "4a72918a8dab1e6dd9ad489e7877424c93be84b6a28f4dbf572db4619e91f7a4": struct{}{}, - "53e2975b5e720223b6f4e8317049d74dcddef8669e42f9fd5b94cc86378350e7": struct{}{}, - "0282d9b79f42c74c1550b20ff2dd16aafc3fe5d8ae9a00b2f66996d0ae882775": struct{}{}, - "0ecf0aab6df42da0ff0b6ddb2e07e8514913f5aaf8cdb860a9f070c8ee1b919e": struct{}{}, - "ddf23c1d9fc212aed27ea02623b51bc3a83a703a3b70a669dc81e353f3cdab22": struct{}{}, - "1dc0c13f4de4bff5c12a9553baf183d5a860ac85b5b6ad52784dd5f926c17d58": struct{}{}, - "790fad4744a9da8d301e23c83b386f1baabd229611dbe5265df62a97b5357393": struct{}{}, - "6e4d16b59d638c31975231a93bdf63555abc74ebe733b9b3ebdb7280678a2f7b": struct{}{}, - "f0446bfa08fc8831699fc0a09e3243ab07c853eeab4ac088e36c93792aa41417": struct{}{}, - "198e2e06d664ea92fee6c743fe1ef98f9d28666ed15d730349e2f1268708bdfd": struct{}{}, - "7e14ee53fb143ffb4b43bd87b46da573de050b3762f4c936c27093c23ecda10e": struct{}{}, - "d0a88eca2303f7ac7530fedb6aba66ab55c317c50c16d81b2870ad31ac9f3a78": struct{}{}, - "efa1f375d76194fa51a3556a97e641e61685f914d446979da50a551a4333ffd7": struct{}{}, - "3f4aba76661a2088d9f97598515cfc9350da9005fc3c6e2a179e18f6c0c19751": struct{}{}, - "1b6d6d8b061bfd142b6f12e7d1bc1dc3f68ce57a1b2e4b5dce1f7d376ae2161d": struct{}{}, - "55f83af98ca9393690d40f656f7dd955d3a0e59c761a07974f213b300dc42aee": struct{}{}, - "09f025915c1b54a80b69ac25f9bfac6c6993a6fa4dee5d25ccdfa1d30fe9ea04": struct{}{}, - "617034c22db68a3a6529ae910d1dc5aa5cda6230d177340bc94f66e9740bb480": struct{}{}, - "91dfa44406bfe5bca8a072ca92eb3876177d1597eb1c92b66bd97367537bafcc": struct{}{}, - "66f86b1f3c61159b9ad9824e52cd4dbd3d87ab45afcdb60943437e1bc0159433": struct{}{}, - "53e5e7c5a884893739c4cf64528572aeb846f8e4ca1986188287475c65900fab": struct{}{}, - "a02d538d50989a60bd74402f65db21f7ba02979efb1cd2ce54567cd79cd42340": struct{}{}, - "8b6832f35a015a55df7267b59aa6acc19ccaacc2146f391027fed2586688ebc3": struct{}{}, - "90c1cf88e17e77e8bd94f05746f60861d396fac205b976c9cc1865100a2964c6": struct{}{}, - "f98f3d8cbcfa62aac83f1abb6411e40c5da9b2cf9ed67c0227b8675351a04500": struct{}{}, - "10f902fa9a78b5a8edc529fa2f3a67d4445d0820d1814b81337a559f0655c142": struct{}{}, - "b79ea17b7c5ca8fe9cccd8cdba6e8f8ed0b3c948f9f709ed0f47d2fd47fcba82": struct{}{}, - "eb41e8cb3d9f669db9caaf3f789ebb6a7b8b01dd0981b66b12b1ee2c6a841e6e": struct{}{}, - "5b11618c2e44027877d0cd0921ed166b9f176f50587fc91e7534dd2946db77d6": struct{}{}, - "1c999646c80ca97ba6029fc744b20b170963b509065b0a4d22b49ff692e35b18": struct{}{}, - "d6710296dc34ea4ab634a6ff665c15228d5338ece04ae9c7c66f39cbd02859d2": struct{}{}, - "df0d2b1fefdba87b43df6cfc85eaa7a0938b479b30c3bed092b62ceeee9fa154": struct{}{}, - "e93e24c1b877a15df5e4a8cbb20f351b891b750779cf5e8fb99119a7142d89cf": struct{}{}, - "35390f5453e810b6248028c63e5860ce4b2ec6401d3849a7f4f871c0d183c22b": struct{}{}, - "a1abef611f2399049e141a55a46cc844641ba3c76c1dae917cb7bc1d0549c688": struct{}{}, - "9ecc81f0a4b6dfb0c30e78370c42a1e0c118d2b426ec4dd1ee06c9f62a832040": struct{}{}, - "359f7793cbd65f175164737b137ecef5b1a3d2d2e9a57d34559f88cadacb9ea1": struct{}{}, - "c49fea7425fa7f8699897a97c159c6690267d9003bb78c53fafa8fc15c325d84": struct{}{}, - "c87f42bd454c031b875b76c76b0412feb4649fe67a17cc9bde2c2f31a74fbaf4": struct{}{}, - "cce4bcea63748e8cc0231e0bd1bd37347c061dbcb5e4284f844e5689fc97a646": struct{}{}, - "f41de4b0fac86793cc6968949f90ac736963856b6948697786e03d72c4878f71": struct{}{}, - "7b85f25bc9d03ae6cd57829bdbaeff297000575e3f1e71a598dd2edb814c2405": struct{}{}, - "42883de235c26da7b7727177d4a1f7cc727acea1dba4641520322e5080f255b9": struct{}{}, - "3ba1646b38ed703c386f6ea5b295df22c33a77f09001185bcd2fdff027675193": struct{}{}, - "7c12db546b50a55fc5f3af0a5c5106d8d7f1df09e55ed4d533cd48e71d48d892": struct{}{}, - "c4e4866953bba378413a4558ee984a68ad52e4ce2fa039d4966182fefa632901": struct{}{}, - "3a2eaa65e023007a40e560e24a15953139e94c462e953c7df967984e785e8cb9": struct{}{}, - "dbfd3d295d8f00d3cf13343c7dd004894d180ef0f4163b58811c9ab75abffd39": struct{}{}, - "b475efdb49506e29a7134974fe1d3e34272f2d875b831610272268d5a36a5e31": struct{}{}, - "dd601fcffc0805d3cddc10cc22cf5d390b4e63c109cdae89503328d6c5086c28": struct{}{}, - "b52115ea553c55b7f70ceb81a97870163e02cb159d7d4717abb82d4e606e2465": struct{}{}, - "6105d8625ada3c9bf514062b5696e9d7aa11e4b5e2c3d8629713f0bf2e45f6fd": struct{}{}, - "4c1b9bb3405f53cf46731af89f07b01d1ffe974f944d81085cb962abc45ee9c3": struct{}{}, - "6c5f285a9edbdabdfcacfa8d9799d6c9671d9695ac7d34689c837d377c5a494e": struct{}{}, - "b0f1fcecfcc082c55c391313a629b625d696c4bb5360ef76a90ed44475d5ab4e": struct{}{}, - "51d9d1043fdf9f85a3bac9464dcb10f30198875af54980461d75362ab6c3f41c": struct{}{}, - "da573fcb2d848db7870b9d4735390bf0f858d934b68eb8bd031deab409149808": struct{}{}, - "02d08359f754fb6b3afcf916e7784a2a41eb8196f5c43d647e368aeb0ad47597": struct{}{}, - "4995baa63e0e2487000039c1a829fadc3e90a69b115a47b2280064292497bbc0": struct{}{}, - "d5dcbe23801b0e17c57e625dd572bc6e569a4bdff6d4302c1626fd212b6a1be9": struct{}{}, - "deada9b601a1d59969551d788135e2fa7d2c83c2ef818c16dabe4fde18c435a1": struct{}{}, - "f9194e73f9e9459e3450ea10a179cdf77aafa695beecd3b9344a98d111622243": struct{}{}, - "fe675fe7aaee830b6fed09b64e034f84dcbdaeb429d9cccd4ebb90e15af8dd71": struct{}{}, - "9f3a4329d8325465f71ec2b21bfe6ddeede2976800a1929f753be66b8447648e": struct{}{}, - "281a37477f772e7821789956a6de408682d8d2c932ad5a65c485da55c4f83f69": struct{}{}, - "16367aacb67a4a017c8da8ab95682ccb390863780f7114dda0a0e0c55644c7c4": struct{}{}, - "7d0f8e028157d707f5a08e2ee8274585f828df9ef06214f8d85280d213449013": struct{}{}, - "c73c2439657961570873ea43f4eab3d7143bf0b5a693b2e7dc1f4440b35aa48a": struct{}{}, - "c05aa0b25910071cc76dcdb70eaff85a1ce41c46399a1b63445de8193c86e826": struct{}{}, - "e2fa544f1c0634380bf56b3560a2e294fda360d9a5c2727d05c46e625352031e": struct{}{}, - "a73020bfe0253c456e773f2b981ad368c91a043dafa5feb33b33d0b0cdf057c4": struct{}{}, - "937e8d5fbb48bd4949536cd65b8d35c426b80d2f830c5c308e2cdec422ae2244": struct{}{}, - "fd963e4fd45949aa80c5da01f86dc754da4b243fdb90033c854c52c7e4927c9b": struct{}{}, - "0626b633129acbe99b1f52a9112f3c4dd7bbd13c86763764c5a18c0b72d9cf8b": struct{}{}, - "3e3c7a74cf3d0639d4e0bb9da3c13de4907daac33297aafa606a01cddc9da41f": struct{}{}, - "c0fa1ef864788c455b22e34d6b11eb282603eeec08b2b10d38c37683ef46952c": struct{}{}, - "ed5b8120601641c516d02ed9dc643a59648524248d5e2af877da39ea253c723e": struct{}{}, - "83006a438f94daf3a7dd9c7b27f70c15e443c0ca55d58fcdfa76899ae466b455": struct{}{}, - "e24dd2210803b4737a9bd9e3163a4ca807b63201c3bc32b68fb122ca52efff36": struct{}{}, - "9bab4a316550f8d40bd110eea7706adfe666a9ee8e045c3de434ca9b77368233": struct{}{}, - "05fa4cb74fc7f6294552019df2572d6a694ab7ba79925b8bcff33e09eb7eee4c": struct{}{}, - "f7d5d2aa07500f016664a006944078dbbf086c136b4bedb8d033bd8c82da212f": struct{}{}, - "d89c5a6a7d09971ac83ef8dfda99f69671d9321ea569d2f4e5ad586978713fc0": struct{}{}, - "9b06f24716e0c57955d3aa1bdb48b2d524ce046f1b018d3373af3cf95e23f52c": struct{}{}, - "bc23bcff995b8b85a6f7627b29046ae132c49272553581eac8090875b5d30df9": struct{}{}, - "5b2e4c7665e0ad06b170df8d639952553c2a0ae193e0a78c1bc028a4a2db9484": struct{}{}, - "32500d7500ce0755c1a0f96ef86b10c3100f70f9ca6d8f4d3673d2925afc2151": struct{}{}, - "8540da5c93317afcb8a7ecf7f30c26ab474f70bfb4873f7cceb1df70aee469bf": struct{}{}, - "b579d6ddaea9eb9bf4caa199451a84d2b35d775cc4bc989cf211061524f5b0f3": struct{}{}, - "c3c4ac8d3766bd7b8327a33c9a0eda161bf323127261912000b1c776ffc5288e": struct{}{}, - "aa3a05d9d57c08d694cf230a36386221f56890092e54cf8d6b04fab1b2cc0702": struct{}{}, - "12f06bc93647b0ab92e305851db89e82f53f83bfbf85fb8b8fc3539a122b1676": struct{}{}, - "df59c257785d70706a411e70e123aff2844d6d57eb19f3a071afc8e019f5f2d8": struct{}{}, - "7281ac5300c2836c027ee8b6cd827bdfa0d867e0ab715771ef1378d8c536fd79": struct{}{}, - "696925329ada66fbeb414e28eb3b05bc5290fdb0fd3fb01b1fc0e09e5053a631": struct{}{}, - "ecbfb23a44dd829d9bbdc57a50e2ebdc0711b65810bb513b81a4def46b7a1380": struct{}{}, - "45c4c492be1ef3dbae2a8895704346d3c372efd1360b4647900a2d004086b3ed": struct{}{}, - "bf9b5951c550f519c08a4515282f5dc69e0a9e55152d5d316570436b9fa101dc": struct{}{}, - "c023b7b07742e120d8a6b04143594cac2ea04491b8f9f71fddd0ffd942bae1e5": struct{}{}, - "503afc4bd66d51aeda05cbcdbf07ad0d560d03fe0819f365629c48299e92b539": struct{}{}, - "92b0419a5083e988d7f609c56810e8035116d203e046126c077960b66927b822": struct{}{}, - "1932c2f921140a8dc783bb229f4b6310e6ee40dc8dce67f84e0daada7cb1a6e9": struct{}{}, - "c1c0714bd931bb02a0548e9db9d54a894491c5e73fb0b477cff354a9174930f0": struct{}{}, - "e96e35a376f8bb39cd7cb24e11f9efbfbe372aba9477a9e90bd094915c71fa9c": struct{}{}, - "0c081477c9bcc99ed2f0b1449dcb034db402ab44c5a73faf01e75303a31f3a64": struct{}{}, - "33dc0ba86008f4434bd43d050df9022209367483c1eef5280b25da861c32f6ad": struct{}{}, - "1efddc6ece76d85ef80c15d9a0df1939b859d16bcfdd84cb71f19daa5dade4e1": struct{}{}, - "29220f1c8eb8b39e7f5730175c3ad5706cf37197ec7853d92a41d0e45d9e74c6": struct{}{}, - "40510175845988f13f6162ed8526f0b09f73384467fa855e1e79b44a56562a58": struct{}{}, - "c634605151456b0a4f018df2ac528a53174d86890a3a7374983b51b2acf82a0a": struct{}{}, - "b526aece29170b6c0a5c05bb26b68dae47981a7d7acf694f618871b88fae2b3e": struct{}{}, - "a994696540befd55c96017a162c7ae2685f2010a7fd3224c0ada25241913933b": struct{}{}, - "6f11b71cb32c316b2d9f24afbb7ca15087993441c11c2587ce764591111fcbde": struct{}{}, - "a8fd10a421f0bfd3f8c25360f47b6809113881876b0f3c1aeaac480039d1afde": struct{}{}, - "0d53a85e1f38ea0c422f0b31edba9c65661aad84f8a383c50989fb8a4b07af1d": struct{}{}, - "bca3a752cdb7fe21b332445fec700612e31c5aadd8411c341db8106f2e9df145": struct{}{}, - "247610f4dedd4ab7247d07dbda19c81ca9817f85820742cad49d407ffae9e4ed": struct{}{}, - "6c05814e0bd51957f5f1221444d42c903a2c79e0c934c5391563fbd39de3665d": struct{}{}, - "e83c1c4388ff0e57a76d5fc7aeb2ae76800665f6a5a2d63fe561c40d26ccd6eb": struct{}{}, - "ee9c1331c074457bded9b9bdd0ff27bf8db1bba1d8babf771d89550f9b9db17f": struct{}{}, - "5751e048eb6fee3eb9bb4ea70f107f19c35863766bee5100044e52ab60d9edcf": struct{}{}, - "2ce2c277148ffa4b6ecd8f915e83f664186c6660357aaa8304d830396de031cf": struct{}{}, - "0a7a27bedd1f822ac176d55217c0cdc9e8573f173d2b1a525e3607a7614a29b7": struct{}{}, - "89b69941335e71467cddd61e1fd6aedad1217e20f04f80d445ac7e5238576e99": struct{}{}, - "d1fe0b3c9056a1d41dcdb0b7aedbc5ed3ff6f3993580a621032b61df265a90a9": struct{}{}, - "5b21da3d9fcbaf80242afc67e0fc70ef3b105a73ad3007ca9f09acf17cc8a3b5": struct{}{}, - "0f3930189e5709d3ce12e7aef7e4ba2e627c5e82e85ba82299d0bf6264b26d4f": struct{}{}, - "8096fbd699644e118fe239389e5f7a98208a4d611f690e918982929d34b48909": struct{}{}, - "db1a60bbff220d0910f017477df18cb7cd90892722985ac91dbfdaa1c955220f": struct{}{}, - "493a40dcc9a2a4d846ccc8ee8f426060ce242f3440d4432aaf9378594c575e2e": struct{}{}, - "34b14e32823e90fe23253847e4a6ddb8b0e973717ca231d675921337a850b606": struct{}{}, - "11c4a847b3b962007a8599b3c6b141c3c8db0e3732dacdccba62fe40c97c24e8": struct{}{}, - "0545493de4891b35576aecdc44e0aacc9c08a4a26b39be3db4dcaa8f489a2bdc": struct{}{}, - "38cbb6e49c62244468fb38172b090126c488501e4223c84504472c4f6701d2e0": struct{}{}, - "7d13e30df93ed330c3d8d5e08daca36adb268708d046bb5e1e240bfb688f0886": struct{}{}, - "16814e8cd706b29de1ba77f011fd96f1b245ca6641b3af63b0f787af54284f90": struct{}{}, - "00daaedab47dddd30651a89f808876846f43be825272d820b56ceb01f87b5c53": struct{}{}, - "071513909e35d2417ead9d398af8088af590c2ac99bb6df9084048e63939c5a7": struct{}{}, - "b64608345c88e3e506318d8cce5cdf6682dd52433cca7abf29491b358011cbf1": struct{}{}, - "4019f26e6a78551fff73b65ef5bdd922da5f0490d25014764825bfb4a0393737": struct{}{}, - "00154761637ca746c354a6d9cfbf1da1a92e79afa6bb127bb8a1c434e9c73170": struct{}{}, - "d3aa80dae30696b303880e370a00a1bf4201d7f9e29529731b772d12d022fe8f": struct{}{}, - "7dd1aeff3475d1aea4662c65b75914cb24275740accc9266e7f22d82ba1dd5ed": struct{}{}, - "aedad4dfac4747d17e5d2323b7e25954e2c46a2be524653fe4a13861206c45f3": struct{}{}, - "c2837591e6e2a132fe453d516042ccd9d10088ecc03f18cb8504cace8019df7a": struct{}{}, - "0d517047196cd116936085bd5289a98ac4db85f2c9f5ee66c919cb39f26bbe2c": struct{}{}, - "f66930493ccc8b69a82344051fb426e9b92a2e61b0f1cd98f0ecb565f1f0fd53": struct{}{}, - "dbb48ca31b7dc5622e96e39bb6723feefe085953e2129155bf6bafcc4297832d": struct{}{}, - "4ab17eebd8ab6696a0cc3ccd69e4aa2818911e5800b0ff335bf2fe6d2c11cd23": struct{}{}, - "7063d51d1b2da165eee042de5d33cc27281ea80e1a291488c903b7fb5fc31da7": struct{}{}, - "d1cadfb3734a15e4bf9d7d337f94e60b2f39c40fcf0c99c71ad0229009ef7fc4": struct{}{}, - "ff8fff1691f9fd88ef722a3b3e933f9f595e8b46c87f52819b7557686c3647ad": struct{}{}, - "613bcee5ec761d59757a01e63d8bc1be5293e6f05225dcddbaf825f86173318d": struct{}{}, - "8674759434deee159d70cb3005a047cce7646dda71ee61ac5373305d71f202fc": struct{}{}, - "0e01b13d3d6cf21759a0cd3d36c583cfe30a63db413ec05664a142a44cca7971": struct{}{}, - "525b91f5781182f7fa81bc8aca7d37652632cd55f09b139a5817b112007b3e02": struct{}{}, - "93db1708732f87bc45a1b7d4a8d5752c99f3542fa669fa8a67b97c2d352a0820": struct{}{}, - "29d206102becea673c7006fe5777b388c58dfd67699b99b538d151644c7688dc": struct{}{}, - "02c855bcf02c2a398162b00840dc2bf82e6d83bd714f444f9dc629ba54b10768": struct{}{}, - "c91f057c5d8016057a6e489ebaa58f67166a6a278b6384ecf7745ce018b9640c": struct{}{}, - "308301fb06e9ac280c30d7639029b3f962125f1a3140d0c2d34d822381116a5d": struct{}{}, - "b34e00e1626247d3d20460fe9ac37136c84307aa5aaa3c84be83903b3b6bb6a2": struct{}{}, - "20c9f33d9744edc2835fd9652c7b22427fbb9fb500404793ddd640774f2fb077": struct{}{}, - "d23be8d1141403ea5dd4df238b3b374b09c14e6671fb22a6c2e40c72cef92e26": struct{}{}, - "ed171d9046dbd9b848429534a7d544900ed154d64059088ca3436fd597836bcf": struct{}{}, - "f4d24820582dcae174b675144e2324d79101e5ad63a601c844dd2536f20d318a": struct{}{}, - "23ebce72c4424e81ef48a495bc9ca88c93b8a34ab398bed854b76f1c784ab227": struct{}{}, - "515e5750f940bf87758df8c99ee067ff8980e5f1b08ad56e240966ccd2b03c4d": struct{}{}, - "093e7d5fdbaacfa92448861b11075b3fba532a07d945b49a44919252f5e830ad": struct{}{}, - "bb59c48559a866d2169115860634793e9ecd6bfe9584599af7cf55d8f4bdf742": struct{}{}, - "1e4f52a959f00546bff6e2f2adf6bbea729b86ae97c1032aa7330c1148761bc4": struct{}{}, - "df6b07176a9b17cc4c9afc257bd404732e7d09b76436c7890f7b7be14e579794": struct{}{}, - "79ce988ba7b2e8c079a716b8957164384dcc9933ec9c6ed84683b783383a729d": struct{}{}, - "bc8a6e7a09074e7bce4dd468de2b7365b4c78c8b807120a24f3db021f7bb7bdb": struct{}{}, - "4726a1bce9706083939a56b88b657a4c6fe83d9ae0543889df2938517ed1e4f5": struct{}{}, - "39ee4551970726c324df4ea3bf0760fe4ff35252340bfba7b1d4564454f9ccce": struct{}{}, - "3d486fe5bc88e9b66c18509b0d8b17bdc16e5d8e426a4afb5e3c16c7c7be97be": struct{}{}, - "63dc6f00c16cf11bfec2b3250064ede6ee67b0d4603e9e7346fef2a8ef0bfb90": struct{}{}, - "a741cd947357ff974c201c22a98389d7fa1aa09215fe6cec4203f0297dc12d9b": struct{}{}, - "9aec25da37c5143655198925302a995797eeef538933800b67b87c48de7ad120": struct{}{}, - "41b3ab146fd067194352ee20901347a73366adfdf94122d428c3260a1b689ee2": struct{}{}, - "dcd47cb48e478888eb556dca5a317aed85c7de8d5c0da4be6e8168760a3cdea2": struct{}{}, - "e91b7ee99879d20aa114201d3f605019f5043929719f02abfd76b99276ed884b": struct{}{}, - "d395aad2239dd56732bcb49289fa5047b6e3aab5f689314fa14de1be694ed92e": struct{}{}, - "4ffd117e39940730ef4fa0a228ff97ed53507d6916400cbecdcb592761add990": struct{}{}, - "f4946d1234689b077c017045d050ca33dd89091567740df9d55b3e669766f866": struct{}{}, - "d653ea2cd46dc85c83f98c41afcc4457a66cfcff22983401824282c783934d78": struct{}{}, - "3049a1f8327e0215ea924b9e4e04cd4b0ff1800c74a536d9b81d3d8ced9994d3": struct{}{}, - "5eec0dc419aa8337bf725f026fda9c78c1cb1c642eeaff9d6e1112f37783e942": struct{}{}, - "89b3d4f59392d466b8ab11e2d55faf92b9cbb1c2136789e9030db7954c7b4b7d": struct{}{}, - "ed7c48ccfebd25f3a866292d88e70ddc0d2e6efcf55b7eb30927ede61b653a65": struct{}{}, - "80db4218688d7406fb73c9cb141228ac73fbaa0eaffa559d9187b1c3109a5b15": struct{}{}, - "ea415a61bd19915084366a0a2fdaebe070a9c3168877ecdb5e36f4905b5f8aa3": struct{}{}, - "240bfc15948ee23a5556da3d411f516a5fe9dee140635f6b64198dd60cd6ce33": struct{}{}, - "6527ffd0577d459b7f4eb1a234817ae56ed9dcb014b42091c78922000b2ab4d6": struct{}{}, - "ba8d3e6a2d8b5ed67ba21927de648785e0dccf8fdf6efacde32b8456f534f70b": struct{}{}, - "a40fea3a72a41543a4d0b9ff2cc6734e04409c563f3c413220e7f33c35b7a590": struct{}{}, - "3d0a234093378eaffc3fe01cd6edbc3cd4302a37457b0c3bb3dd464a52c5a070": struct{}{}, - "b983e26b1409ccf1698fa1cc5bc80caa873ada20c1f79fe46ef919aa44072fc8": struct{}{}, - "f75c752658645ad42bc43fab5385ad94d68f3fa9cea77573e288d54aee4ddd2d": struct{}{}, - "999a8cf63e071817b46053e2e76d69c22d37534f842bc7fa64ff68280ad06a56": struct{}{}, - "c0ee0bec526a7114cb176543ad6472809c0935c45e0e4add0c34015d99a964db": struct{}{}, - "0df2b318462f5a520d9afa4cc3de851e08302a52ebf0b9789a13132f34e2ab3a": struct{}{}, - "9477cd7760a4e106c2ea95b4f8b246afb36c42df466ab9325bc3b5ccb0c09e3f": struct{}{}, - "12886f9d00055adf24c40579e22d31b2b45f2023f892954ffd5567beb60825f8": struct{}{}, - "afa6e9ab3ac6574d50e6b5674d5e76cc95833823a314b397fd6c499dc673ec9c": struct{}{}, - "a96361ebfd8c958bdde74f1340104fdfef552202c6c327db0114c69ed3711267": struct{}{}, - "a1c7a5eca184c0316aa170fa2f9b26378e6240189559eb7f4ff0a1e599638e09": struct{}{}, - "851d49d3087657e971533b2fd8b28be2ff490c34411c2e3cd2b400fe66f3a3c3": struct{}{}, - "b5c4425b015d27e79182ed49614e609a758b6eec06db406852fa97e502abbb75": struct{}{}, - "9d45b795e1edb9f99fbbc6e6016e66ffb83249f508302c8c77d03915c310ec99": struct{}{}, - "d3ef6182c947611e380c21f40f21794a1d19a486ae72cae7b9b70c82a1025afe": struct{}{}, - "344267b97985919bd8c182a8c70e31d42b2793504e759f91791a835924266495": struct{}{}, - "bccae4ce7be65100a8472740c829b76610a653a6816a7b88c7f7def8ae8e9b0c": struct{}{}, - "6740b27c9625034bdace8ed62215b93bd5994b65bfd2052025422569027506a4": struct{}{}, - "532e66930cb6c09dd378ec5e0ca725e288ebaa530b70695249b426fe56f1e9cb": struct{}{}, - "2c2615191ab7fad38763f6eceaa480299facbb3856f75332c8add27e88f901a6": struct{}{}, - "ea0803c364284eb83e571029e3d8f03325995d7c4ef75f69e15b0cdaf0c1d07d": struct{}{}, - "d7c8fbed35270bfd99ac98384e756124b9e4c87fc9f57381f14a9ed70d43a3cb": struct{}{}, - "f8b7cea0124e76f00d8a0508152697df1d5bd90bca14e76f1d290451b3d06010": struct{}{}, - "907488e8fe0306f3d73f8de6de7c28284c8770269fce2a1f54cedba72b61047d": struct{}{}, - "537a1c8103394e16142c1e6273da4d22cdc7ba074f95591ba4dc4b79edbdb07c": struct{}{}, - "6fe56055d56cbf8d952798c53995eaf924d91e04e057b0acd4e097e5c381608f": struct{}{}, - "7c9e7c1494b2684ab7c19d6aff737e460fa9e98d5a234da1310c97ddf5691834": struct{}{}, - "3e3d6a28351293395ba3a345e79593de3780723a42321204c54b7da49bf3da45": struct{}{}, - "99e4ce19020332ce2c0bc0920a1f001ffedb9587e2c5d80911b04f0a47d191f2": struct{}{}, - "ebba1c05c27bbcb6a8d023aeac138d76059480b7464413108a144bc84e406e5a": struct{}{}, - "f230ebadf6fbc6800f7f93afdda7cecaa8f2491ab3b8a2e9e626908b29973f96": struct{}{}, - "449914e6829aba1f85621a263c9024758255e7e3e7aa8029de5ecd9f395b474d": struct{}{}, - "158869a97379229b7681efae9d7f9c9214134e836d649ba53477c0c111414d59": struct{}{}, - "591b66b11758731f2d661ed864bab0ee82ad13ec6bb99c9abc9444bc91f647d6": struct{}{}, - "0ed4f7a409b23069fb120bf5bbfda76ec660ddf281e20082fae90cd274627b94": struct{}{}, - "37d9bb85d3ccef7d85cbaf4455428db0abeef9e54cdbb8c5d0c8a86c09424467": struct{}{}, - "9cbe90905c4d5bee3d5d8fe28f908472867dfc29a30f8d1b385226f0eac4a34b": struct{}{}, - "8802c7af09f51002f8829a202aeb1b8d97003c64414f677162d55824ca836172": struct{}{}, - "21b747de22dbb757fde3b699afe057849d4e6b49b63629e4c40e75dd479dd375": struct{}{}, - "643398328fdd3199049096b220c79782b6b001a4714dd7d206fbd538fc2177f2": struct{}{}, - "98ffa21907a013f442798b8cc32ebb505e40e23f156c7c84b2d435554d9f12bc": struct{}{}, - "6ce60cb0b9741bc12379eddab9900b0c7a602201ed7100267c6104de2c8c5bcd": struct{}{}, - "2a2f822ad5319117db5016955c0d89d98a59e50866789cf8883fc6abdd2a1e17": struct{}{}, - "47afad612b1f9cc8fd0ab7ba2360845d7b932a9d88e6b704e1f5804295c6ee97": struct{}{}, - "d54e8c0e9f538b7b268e7e23d54a8a70721e9d6ef33a0390d150441fc0434073": struct{}{}, - "c48923a26463dad51006e66d270a1fee051401970a59706c94fe08dfe156e403": struct{}{}, - "7d69277374ab81b076deac5d8492b65219e6a5a8cd9b595bac68a1025e80aa12": struct{}{}, - "5cb6ba4a9d88885cf1f3cd94d83904f380733abde8143b3c3ab8623cac2ce87d": struct{}{}, - "2ab6d53e717a0e0d773ccfdb8b0e84ac494729c6b567c72d256b883a9db17ea8": struct{}{}, - "8c3b850e11a3d9b53c444ae372c6f97b8a2b485860c58bab745d988dcb8cff65": struct{}{}, - "d6573971cc8f14d36138ef93fa79ec4d96a7de3cb70920ca5614c3f1d6e18e2c": struct{}{}, - "aaaccad5920635c14a62a30b9a6aa7b80579346e05000e56bbd0fd489086c09d": struct{}{}, - "392245974134021aa670d26753655baa3f14e09c2148169ff9172ee6d8e4f03a": struct{}{}, - "ad6da8a17847823f0b901ea9dba3e1100982cb0b4befe1ace7df5afdf0bbbc4c": struct{}{}, - "c8c738c493efa4015396b5c305b307b915415e7ea3761d71799f1f5ae8a8006f": struct{}{}, - "c9a8f545e10a6a9b6223fe778e4e178fbb83ef4727e23799f9e6a805a128825c": struct{}{}, - "98f3aaa79f6ba1759e046f873955785d869eec78b60ff7ad2f1bb62d50ea8a0a": struct{}{}, - "b85bf0f7330be07933314afcfc04aa8e8bb33827eb03bdf2f65ff26fd32444f5": struct{}{}, - "07802747a243e8246da7219d1ae6935e07de1f0b185b345220b0cd58958ec627": struct{}{}, - "97e66fa85fc7a65803533fd9b8ef1d654a7f6e2e16e325aa3164112ad1fc9e50": struct{}{}, - "5364f2f2fc4f54e9d47ad29cfb08ef430c8153394bf2a0dff5cbe77a0ffef861": struct{}{}, - "aefe4867ea407215f6c85fc459306f2451950efbe8ea2e91fe5521c249368b88": struct{}{}, - "04323cbd77432fd835553d5a99eb19c58a24e6452f287974f3535264f8cb8d8e": struct{}{}, - "ebc0cd897d5539e8ad44292dbb3a22161169c8b35d05c332b6a9eef9f81b4cb2": struct{}{}, - "d009f3d6264fb7b3ec3125fded3fdff5bd732e31cf5d738b40395f90e0f32f5c": struct{}{}, - "8830759f4ad111d24b727ddedee798870b9669c72da7e77b722d832ae5a53d1f": struct{}{}, - "e39eef82f61b21e2e7f762fcc4307358f165757f2e77ec855d6992f7e0191932": struct{}{}, - "710c3906ca8b54f86c9e020d989792d03b9a0d8904d7b57b3f57e74ab1746625": struct{}{}, - "735701f285cb9253fcff9649a0f7a09f27e5b5967030ded8baba4c5731683636": struct{}{}, - "e6608c0847924a1661e1fc721525d4d1b10b1e66e1b0ac3699a30722c624c1d9": struct{}{}, - "40bda430db0c36ff0a8c532620d6b5edf4d041b3a90c73212e935c9baa7fa524": struct{}{}, - "23cbb06d0a098076c0e9ff5d6338fec664da205f020c419057d3ee00985c0e5e": struct{}{}, - "5480f08f35968440ebe8135a8bf9e58c8c944bf4e3ba0f45acb141e474bd0c9c": struct{}{}, - "b9138194ffe9e7c8bb6d79d1ed56259553d18d9cb60b66e3ba5aa2e5b078055a": struct{}{}, - "89b909c43fb9ad3cb5d3519d99d4426e1cc0a35c2c47d66ca75e3f5391e9d5e0": struct{}{}, - "8e424db8e5664ade76226356bcf5ef6ad9d0879bdad6377db835868b17c443ba": struct{}{}, - "54dc0b5492166fa08520afd71c030f398bd72ef90dbb73bc2e056363ec01b540": struct{}{}, - "20729aedcd8042d4013f447eb70c027d57ae464ebde16b020929dfdc651ee034": struct{}{}, - "ecec4a847142ca9d9b1623fc350a68ffbeb694ba43cf98ba345f61822cebd72a": struct{}{}, - "97590a84e25762db1d92ddc33fe73adb67b94f610f39082219e0b95c799af5e0": struct{}{}, - "74ae323f9b9abcff9669a5a3f88af295529db26bff271851737f47cb6f270698": struct{}{}, - "af6ee99106758696406520ca2ac7d9e9925a302e17ec7fbde1141495b5256fc9": struct{}{}, - "cc2585df74ecdb8b43e3d9f1f793ee9ccd21d617a49e8e5698cb5e052c453dac": struct{}{}, - "6cdfd271da635d491e37a2b4a1044b306e6e9e039aeadee95bb355efadf8cb33": struct{}{}, - "9ad08878dc38c1d6baecc3a02ad8874a41cbc09212b27ec92547df7ec561ddb3": struct{}{}, - "b541fd78860b2f67f4bd3b2ecc581e2f338fbe316dec05d63d05478749057b98": struct{}{}, - "c5879b8d250ca6e74ca6bba56d9d89d3902cdc521520c040f051f7893d9555ac": struct{}{}, - "a48d5070d43504bd148373eeeefbc5390b5d31d12422a5467c0bef6835f4f090": struct{}{}, - "3afdc96b35e60a6c3d98fc06ca8647ad5a106c862503cb64f982d260928c7285": struct{}{}, - "cf8cd670427f47eec93b8bc41f9a68af5ac66598e83da254f896cec1ca93a1f3": struct{}{}, - "6226a5df90c2257f1924c24c024002be9eac5c98deecbf43d499e4fb611b6a76": struct{}{}, - "38083c7ee9121e17401883566a148aa5c2e2d55dc53bc4a94a026517dbff3c6b": struct{}{}, - "56976aefb305dbfae27a1fb0d60cb1c709fa90be16a31ceeff89fae48a92f83c": struct{}{}, - "dd9287bcbc7b4dd17372029bdc074b62ac2e6ba3dab8e537297dfbf6cc95c760": struct{}{}, - "9975df775a995cdc875708b4d635fb60bf8f4ef99ad02085952b37dd9e5bb79a": struct{}{}, - "32c6e20f1e2679a339ec8135dfac19717d2296596c9c0456851b1535278f390c": struct{}{}, - "16a3a923a6143b7b1ebc73ea64ef23fc41ed7b26b883f51c8e90bb32a9cb3dc4": struct{}{}, - "54bb6a0d2ea7d49744e886aa20859d70b6fc4ee0b9f144353ecb4b39195767f3": struct{}{}, - "ce635c4eabff5e4f56dba8fb1e39ca235530aa2b6b18533eef1af3862016c577": struct{}{}, - "40f6e6e9371619adacee2593eb043a80a8f0930673d34b09bbc2d4cd60c33a7f": struct{}{}, - "fb47867120f4ab89d04d477c055233da203206d970709fb25c165309fd7696b1": struct{}{}, - "8950abfda7b727630760dd35bcf5c3daa7631aff223a90f7728c0d2521dde10c": struct{}{}, - "d522dac10004460120fb1a81a9ae8684a7d03524450cad97c9d687abc90d6f14": struct{}{}, - "67ee5cbb45201ea669b6e3fc13add0ed264f988359cb12027d83417c44a588f8": struct{}{}, - "db8ffbe1cd19708b8ffbec1ddabb56f3c996c7710e7352455faeafeb7e161101": struct{}{}, - "165940940a02a187e4463ff467090930038c5af8fc26107bf301e714f599a1da": struct{}{}, - "8b95ceb73735efb9a62e795f717234f74d8c72fa046fb79ed8a7b976148acb7d": struct{}{}, - "c51e263f9aa21501a3790abb5fee8cd2e8fbd4dcdecf3684c9fcb7b4dc7bbe03": struct{}{}, - "5954bb93abae3afe7ef38818093caddecf2d77b31e81ea4916888ca64344446f": struct{}{}, - "5a7e7a8a9bdc586be12dbd7a179d25531420805ebf09054c004fc891df57f205": struct{}{}, - "b2bafec94691622980343dfcd8c97c3e703b039db4e99528036947aee743d492": struct{}{}, - "b3252bdb4f2160a89f4e8e0ba93592998c26c7f2431a708f98a17ec5656ff996": struct{}{}, - "87e9fe01b35b3ab7d8f4fe989a219b73a7a577eb22c3139626f70781627769c3": struct{}{}, - "9fd13a8c6c17da6b9ed242f788efd8fe9fd5143e3444091b8cc5aa6a9c263114": struct{}{}, - "6265d9f642778444973425bdfcff3793dfe490d1247553608eccd94820836a92": struct{}{}, - "9606c8df4d302092cbd68ef3772e16248bcb2a287d23d2e18e69e860a50f3bcb": struct{}{}, - "7bc6f36c4aeaffb839ec9eb2f2804ebd6aa253bf628dc3fa589bc772a77eb57c": struct{}{}, - "56965e2a1a995c74da500088947af11dfa27951cc350d0b97d0633075969c31b": struct{}{}, - "dd83c7e1b0fa030f74a6213666e55b3a195bfc50830cc0b95f4f9646738cb6ae": struct{}{}, - "69c0d2416d173ba6e1c841b566fa4ca31bb41a3f7440d7fa0bd60beff2669cd1": struct{}{}, - "ae118a7acd2ff2f77598c7316bced68d92bf8c8f0ca30ffb28f4edf6fc7c2f3d": struct{}{}, - "0beca1c4f3e61feb51b43b1e53b5faa4d9caa939935cee2c771f5c9a6344bd13": struct{}{}, - "39cf521014dcd032af0e9358f4086b0453180470ca67bd912c5fb112737c0be1": struct{}{}, - "75ba680421d78e41caf21332a07416a98a8c0725edf38bd1be7aa4a774f44518": struct{}{}, - "e0cb05d98fc5f38266f3ea8376d84c61a8c16050240e439e7ae96f12e2cb30a4": struct{}{}, - "ccb36736d8720940a0f3299a386c4ac1d6757bdbfff5eff408b9a847f9108641": struct{}{}, - "82280bb7b30905f97322961a56e3c119150a246cb47293607c23385110c73d52": struct{}{}, - "8e334f8632dbe10f26f360f0866f14ec7cc2a630dae5f1efbc5fc47333fabf0b": struct{}{}, - "8bafa567642f12dcf2ccfe06e16a72debb40e9ceb2bd9d59badc381c4966f58b": struct{}{}, - "4558505e43ad952755f6430c60ab8e68044d37867411a1a17e8fd04c506729e6": struct{}{}, - "e759968ca9278b8dde9f515ff1957813343dc35dbd2e5f68b38b5a17e29fe541": struct{}{}, - "77b5c3302d1c4d1735e66eb3a520c6158141abbde020f778cc82cdcd46b8abd1": struct{}{}, - "1be2e452b46d7a0d9656bbb1f768e8248eba1b75baed65f5d99eafa948899a6a": struct{}{}, - "1dcb2415a0dc30e17bf2797120c11b14c774205b38306a9d359dea6557f5a5ef": struct{}{}, - "824fc479e44fe9f3e47341ef3e84e56f44243f8c951b258e14c72493cf6604a6": struct{}{}, - "85356064d03872ac4bed179b8bbe8318ab67a9626be55d0d72288ee14e165265": struct{}{}, - "6bdaf554e42029bd0dbee9471eb48a5f3626213c9859a58120a61c62ace4c968": struct{}{}, - "38868dc905f4d6d6de71dc05a83a876faeda1c0fc998ab582ac8bf62a47c4ae8": struct{}{}, - "f7bcd2d4e37276f4ba57288b3b04c2983864e5b8b679af2d36b2e7d825266edc": struct{}{}, - "fa75b5c25fc089798cf96479474da0ebe84c6cb743a1f1010bb05fd8756275b7": struct{}{}, - "6519e440bf4ec6347de1eb2f7b293b730b31e20b240f30db91e62339ea2141d9": struct{}{}, - "63fbb88660d663c810e688c046f105745723fd9248c3cfcc8d9519ca9d348147": struct{}{}, - "7f861bcee185de001377d79e08af62e94b1e7718e2470e08520c917f8d953602": struct{}{}, - "ebc2695ae005f1a6276b02bc516e0499656c7a6c3d3d4364b234dbaa6418fdb8": struct{}{}, - "862709e2f8215f6f91a34c8ac6cd0156697acc18cf0cdce1bea0090ef2574367": struct{}{}, - "67c632a353f631c870c5050e2c661b8c26137b6b52085b0bf09ed41aabbe8982": struct{}{}, - "503aa80948f79284abb84893afc9c52db8fe65f0d19d0317794902b991505427": struct{}{}, - "0cca55b0c6cb2020c0877942b82d35b28b1306df9ee0a2f188d814eff1d38595": struct{}{}, - "e358e2e3781477fc07883cfc26e10cc0a4d8641c061cf7a613d8d9a9f9e34651": struct{}{}, - "b18d5846c7cd30b17212a5fa01148d6adabd98f6b3defb2e07c3a655b73ac6fa": struct{}{}, - "702bb423a648b3ddfd67e9459366ad38cec0ef7b1e8c0cd02d5f013ba7bdb0ba": struct{}{}, - "129ce50dd90bf244858763d3f10932a9f6d8a521ad4f2c946574e9a566e04054": struct{}{}, - "bafff323021ee771fa6331aab24392e0351dbac52af54b3e7e7268f1d453adaf": struct{}{}, - "b23a6a8439c0dde5515893e7c90c1e3233b8616e634470f20dc4928bcf3609bc": struct{}{}, - "b45a53c5004c62f9c58f4a6d88cb5b24fce1f1b63ffc50ec1ae2b2fced3037e9": struct{}{}, - "52e68cf314c0d12b61e1e153b3bfbcdbb550594f63436ab547be2b14fa4828cb": struct{}{}, - "c58ed9e7453ad4bb588882ae2ef6a86dab87d940909842665d1699988bde4b96": struct{}{}, - "5a73b2a9532398fc23ba093b7c41af50ec73b74b94f2058ae909ef43de0cc4f6": struct{}{}, - "3da8348e00b9f75b6c1767363758c05b9426d734c4fd06f419935830de05cc40": struct{}{}, - "97d2ef8a21656cc908ed097da29c9e586cec09e0314510196420a97353b26848": struct{}{}, - "21abb4bd519369d615d5155de2efb586d02de051005d1937bf9749dc41f6a5b8": struct{}{}, - "113a326a8fa2559148fedb4d000f61f943cdb7af7f5f533342db6fd9e13a541b": struct{}{}, - "c23b31a0179b550f8a18fb06bc52a26e32333540369073d646da5c84a4dc341f": struct{}{}, - "44ff30f3c7242b3c1bc814ba06c74ad1bad6e0796e5e99f48898f65f93d815ca": struct{}{}, - "48fab7a56479b7ec15bd571024dee1cdfe85678289c5cca3ed95769793797c6e": struct{}{}, - "08b5c415580df07d661a655b0bf0a82ce9610a8ed79d25a6a94ee85de0c85e62": struct{}{}, - "f1ee529ef49111208f1c1646c53c8c311c9f093fd7891c1b46d77e98210b018d": struct{}{}, - "a3749d0f485dbed25bcd639eb828bcb1b7ff0f297e06b055759d80c828ba7115": struct{}{}, - "a0ec06301bf1814970a70f89d1d373afdff9a36d1ba6675fc02f8a975f4efaeb": struct{}{}, - "545f5aaa374f2dfc7812f4f4c69cb7a30bdfd39f42c2240230fc374a0287114a": struct{}{}, - "f44c7a0904e47fc88ed2f773c03dfb0c4e9000e6c7dbddf79ba217e8d083504b": struct{}{}, - "e656f6cd1caedec309d0616246cfdcbc2c7cccfdd858440e0b47335d94001aab": struct{}{}, - "0454d527a7fa92d74a43d6470a274c87b0383fce739c0d1ac17c973c57f1c60c": struct{}{}, - "9fc4c2d1ab7591f35231b82b7dfddf343dda09d901f0a1244712a1ab08666bac": struct{}{}, - "ed872309250a9f1fe84330f0093758a4c7bc1d9bf7b4a3fc39f61aea4b3af867": struct{}{}, - "74305616b2bc16c06f19034ca9db19d2aa49d6cf5cb481a0d0eb6a9c4da65c0e": struct{}{}, - "f34496305470eb1d23d57c253b849fb4840781a127e8eb13aa56b8bb65ee5d1e": struct{}{}, - "95f33aca87239c2179260855dcb4ff289127a76e5f728833cd1b4c42149f95fc": struct{}{}, - "679b3fe58ea4b7378c418d79dbe3e89efb352ae7ea58f293a356e6c38e20f1f1": struct{}{}, - "a60946cfbced6c968698c327d1640b9153ea014020e96c619e71436ecdccc494": struct{}{}, - "fb4683e1bc592e05e5cd68d305f4aa1f0641af38faa8c02e7bccc9f5654162c5": struct{}{}, - "5fdf250a1a18b3f5aee82c9a79c6e631c252f644b23cb27f06ab2846e0395fe0": struct{}{}, - "caa11029786e778b1fb6dd5e47d611204d2bca1d09b92d46449e7dd2dbda8279": struct{}{}, - "1fcc8fda7ef16ba59418cbfdb874927938e52ab32195518fab72167f3f48ec9f": struct{}{}, - "1b2c24a640b226eb92cb09e7dfb0fed3c72315f7fcb140b627d0e33d12b014fa": struct{}{}, - "b5fb511d37d358b8ed75bf3a10e12d842395b4f22ec50428a074fa8d0636efaa": struct{}{}, - "de28020079bb7c4b64ee7041d19424fb7cfe42bb6ee07ad591c08ad56d5c95a3": struct{}{}, - "a3ca6741c993e279d6f67ef214e94e6aaf8bf5ccdbff84819e90549fb969dc8f": struct{}{}, - "152fc4dddcedc0be83ea10cd2667d9e4218b00439c65cb352f4a048e409203b2": struct{}{}, - "6013de0381f648b7f21518885c02b40b7583adfb33c6d9b64d3aed52c3934798": struct{}{}, - "acf45488978d9ff451e305194e5dd85452b76f75ed6ca223942cd4069ed52da0": struct{}{}, - "80ab45e31f0ac0b5f367b4e57d864611d31434059d6541a55e366645a9ceec4e": struct{}{}, - "8bee569effff17714a7478210fa1f4be983f4157da39f05f032f725ae5b1c6d3": struct{}{}, - "cd50fc998e7e535b8908c8efc8233cfc25ffa79ed9067abf1ebd32c52ffc87df": struct{}{}, - "9cd0ed6852091b50de2094e03c3a788d809b0bebcd07cb89661dcbceff887b25": struct{}{}, - "d07ee7e529af02ace472e74ef4be1bd92f3604f6c3a5b11602aad4496161ecb3": struct{}{}, - "b348b8d47eed9692fb9acb59234943298ef551f95b4859a891cca19e1f8a258f": struct{}{}, - "1313c15c22701f9fd383b7f7d69efe7b86783605998990a9fb04b84f817defab": struct{}{}, - "14443a5cf76b1f9490f462edfc733251fe07dd5093b6275309f19b7b665b4e5b": struct{}{}, - "582862aa53906968584e032fc1f1da023b01ec9a9865164f5720d97903e02c48": struct{}{}, - "b8a6a1fbf085fd41d6652a910e2dfdc80c5d36af38d3b426fb0f2b13f2cd6f31": struct{}{}, - "05bef36ba8aa210ea506c0e87a348bd668403f54cd50a289a3fc1f493ef7541b": struct{}{}, - "e86da1244e289dd2a57686851996dc138aac82ca675ea0b9fd2a33ec62d5fa49": struct{}{}, - "bae366da14f2fb5b65c4cd7668e892c0f1156c6f1360ee7f672506a4e7fbe99e": struct{}{}, - "1219929bd7c1c9f41265f9b466be9f4bf449ee2142b8691a85067baafc698d39": struct{}{}, - "af1336222ff748fcbb2884d0d559716fce12d6dd021d8d1e85915e51802d42db": struct{}{}, - "392e98bd37203b489ec2d73030efddd156949035a1dffede8c328ad3e9a69c22": struct{}{}, - "1180412af43d78604a73f14d5a7ef83d3d3a74251ce8fb7b232043a511fbf161": struct{}{}, - "eeecfc48f5fd28cba53205a6b97b15bfc2317408fad1519b3aa4cdede386aa99": struct{}{}, - "1f64847ea58525bd846baf310370b01c25080aa244700aece9bd71a95ed22b1e": struct{}{}, - "e15d12924c0e09018c699d261b4e8bf0980ecd63fdda28102764850aee4f890a": struct{}{}, - "d42a05fa6dfc18236d887aee59df3574ef5cd76114e0c2b14ae6d10b45ab6515": struct{}{}, - "4b29228fc720429159f62f43a6375b783b4c96b7aa7ee409461e9b2ae15aba87": struct{}{}, - "00f2e65596ab8c1915dc21331298a28a711a8212d873c5913d45b672c47f6e7d": struct{}{}, - "c7fe3e1926d3e6fb7f64a2cc84accf23333aa53b9cf33110a42faa611716c6ff": struct{}{}, - "90b8de4051f02b7a29484341f3a903e1b2c6a233f5465e19c634535c7b315e6f": struct{}{}, - "66ffa06b965023cb54634852e3a8c8c5ba0c43cc2706aa8ff7b5b21e7da41259": struct{}{}, - "5770011ff65738feaf0c1d009caffb035651bb8a7e16799a433a301c0756003a": struct{}{}, - "f5851e2edbc52b448f0013da16077809b6377c83e30565f9cdb8953b3a5d36ff": struct{}{}, - "1b00799f51dabbe68e969319428f3c13c86e1eae7f8045ab3779cd3ad35817a4": struct{}{}, - "a0978d4c311943296edb1ff2ec69ed5f30155554e650f49f3efc484dc22170a1": struct{}{}, - "3f95b1b8a32c2c0251dfdbc3c8a30aab6d6e680cf0ef03e8af84a65dff0c4a85": struct{}{}, - "cfe6a8b0e4e0de6ea50d90edec658e83c4d99bc277d437aaae06e6f2dfd60352": struct{}{}, - "9df1c9c7e3f92085d6b2c14c3d084d1dfcef5d1d6e3c1298859a50ffd67afeb1": struct{}{}, - "ca01a484fdadd80a6fb61c29b4efec11f2f3bd0f0e531ee34d9e58a360bf707a": struct{}{}, - "6c40651d4680d8b172385983fc4066abec99c001df4882dbbbd327f84a7c6348": struct{}{}, - "06b29e8f04a486cb28a9e7dabb4de6a4eb57f884403aabc6312771d6a5247843": struct{}{}, - "ac1a1c7143cb2199eb400993a5db42bc2f6d98f6769bb696d04b48d84c68a56f": struct{}{}, - "de58ea343f4567502a792f29f7d3f04276d99c7c2e0b552e19c86a3e399fba19": struct{}{}, - "56297dbc3da652f8a75e32e6dee47d5fb0177270a66414d63b12c814b36fc2c7": struct{}{}, - "c40e36253c9be87e0101822f8c195ae3d4b330f3fdbc2af637ef5b9b38fd0a3c": struct{}{}, - "94f94c9c97bfa92bd267f70e2abd266b069428c282f30ad521d486a069918925": struct{}{}, - "1fd94f4fbeab10e8f9a4432118d60bbcfdfd1214a99c553fdcd4afe01566c6e0": struct{}{}, - "c102c46945edffb3247ded9b524040af95768673835aa1f6a20e8c6437553c8b": struct{}{}, - "f144a6907dc4284d1f9fe6a7d9b9ff53c02c1d07ba68f24d413d7ff7f757a782": struct{}{}, - "49de5b57c70636e66021650e776632a27c8ef705a56c31fc03d29c42af529263": struct{}{}, - "9372c470eeadd5ecd9c3c74c2b3cb633f8e2f2fad799250a0f70d652b6b825e4": struct{}{}, - "4c3280c17f9d982ccbac882ae1f48dad224dba938bee87bf18e31c76b0c3b88b": struct{}{}, - "3b6e7f348f4e23fa349e42cad746a6efab3383219a773d15e38cd34931fb6fc8": struct{}{}, - "a8dd125444af192c7a0ee878d3d043abb2a22cccf9693d8045f497ef90a19429": struct{}{}, - "91b23d25540f88e33a193e20f3530a895ed29d9ffc21eb6de4b9469d4bc19b23": struct{}{}, - "be167ae3efd26cadb97e366da5d8cdd7e85cc6d3eb5a6d08999394f808c69189": struct{}{}, - "bd76bfff4122d2b240b2871b6e11eebdf42088b9bfa48d17ac4b6df36c66c1b5": struct{}{}, - "6b8990da20ed50ce50b888058f9069f60ed9a6eaf9a2067c4e522add9aa25d0e": struct{}{}, - "0c6f878d037127bbebd2ddfd13af61545f668cfd54f36465388e55ab1a8765f4": struct{}{}, - "baed3578ff126a2adbd293d669439e3eb1d5ec1c091c2496e4a380f0d866db70": struct{}{}, - "1d4b41c9db9172e5f151e4a5fe3c57ca3f98b8e6ba807450b10d1897c84ce72b": struct{}{}, - "695cd23d95a04afe5ab92ba2f130f1b249fd7ecbe2fc0b858d63cad2869efe72": struct{}{}, - "43f1efecd33031b0ccd142b1c5cccc44ea19ad3e7a947965c5b0c16a632b5d7b": struct{}{}, - "2d5d6ea604fd79420784bc644f6e2be7ec65eee043b49e5c2f6aa5299b406879": struct{}{}, - "9ddcab3ac3610db0f0304fee83297946ced6c9c8a2b0032e7d85329a74de65e7": struct{}{}, - "e8a4d338288b9d710c98ab193114ffd3f391f6039874c347cae3a4d1cf698e9a": struct{}{}, - "f157bff67be77ce722ef65c4f9b5653d4a50198101cbd18dbe85493bbe41862c": struct{}{}, - "339d4e6cdcca310008d56d211a3c9a405685b6bd84f07f5ec109aaeb3f5e2ba0": struct{}{}, - "84dd965676b21ac5e7524602147abbeac3fda8bdcdab221f34f04bb4cceb9bdb": struct{}{}, - "0ae2f6c706cff60bff7c0125b99cae8c53e1a1702f02316848bc99e597d084db": struct{}{}, - "cd7ac4c61fd8bd3e76fe77e03b810a442d82f21d7dd08be0f82a58e2ad17a4e5": struct{}{}, - "99e2254b134a33cf2d8bef866012e20301f3d1946b718a00619fd8b7a2e4c079": struct{}{}, - "f37da4e2d2d57a8980eb0c0bbe47e8f58016f285d5381cb564b31e5a114a226a": struct{}{}, - "8fbefc95205d5645b24823f75d1935b607af6fd3ff792b14d74db9241c43c884": struct{}{}, - "29f5a042a8db9485a1878842eb14b3f8dce14f6a36028841d8b5c466cae969a2": struct{}{}, - "4e254d523289bca7cf436d818bcda9d1241f5a722ecd3b24f4b9ca46da36bbd9": struct{}{}, - "8a88fbb234ec0452991a71276cb1be9e6aca02cafbe4718831d165373b968a9b": struct{}{}, - "34c93892acf64bff984d30016b8491df92a29c054a6f1870f7f26b8f20ace262": struct{}{}, - "0edbdffac1b7169c3d9d2702dc280aa30796941d2c56d9cf8f49ecda2f8ff623": struct{}{}, - "455eef85e86ebd899fa977106017dfa559c1a3ce055fc598ffdfb005a99913f8": struct{}{}, - "ba27444a56b473225c5ca7aa0b24853f7b18d006a7a185c5587d77ee7931301c": struct{}{}, - "b0eaeafbf3fadbe6d70268e131724ec7421ffc3d18653e2fb33a7db10e70ac83": struct{}{}, - "47c4d6406db7b34454aa1994657c43340e3b2ab22f9aa3bc83ad51a80d017a2d": struct{}{}, - "01cf9f56a3003b2184c49820f5d4dcf35a113455d9f8b1bc4550bca5c6e78ef1": struct{}{}, - "05370c7b1258b44e114a3f0ba60c4af308f3b3dd877635a42ff48e8a357f3d5f": struct{}{}, - "384be34744a9578a680fc12eb40e857831ecc2e6d99f85a16c70fe7554cb98d3": struct{}{}, - "c6b438b0c071b0875f3579207f8391b6a60e1319b25832a0ad3fe4bc0a5ae7ea": struct{}{}, - "fc3086f6d1e06809f75e0cc0a4b15ac7cb250744496f92c38989e3c13a12ad3d": struct{}{}, - "ae2efb3ca5d4b9166e4c3b0076417107e4e1db821b6bdc67d4f086a5d94a8ba2": struct{}{}, - "75f2e8dd89577620c6a5a7f910ca860942dd65b6008042398b7ae18dc3985bea": struct{}{}, - "8f1524d33c75d3b1950655b70e811775fb32272be26f1cd27a0c080af07eded0": struct{}{}, - "ebe4136fc8889cbf55609976f5426b499cf40683b2d1117f4ef9e1ecb6993a5a": struct{}{}, - "05a1dfc495bf88277862a8a97c7834a1f15c5ab926b16599d2b94d93948e7ae5": struct{}{}, - "e1bec087e777e94ae726031ad4c17efc14911bbe08d6a3d75c2e33215f50e41c": struct{}{}, - "eab4d102a7bf4499223e46c14e0656c2d2087045fcf789eb26f851b0e10e9c3e": struct{}{}, - "82da66edfe84e106638d8af7425d31a5241f30b6caebc0c69328b8895ce59f61": struct{}{}, - "94e901841eb6585b46f65d95ea202933088bc897a39b8f21721a46da46e5b03c": struct{}{}, - "29f02fa6bd9a4500b99326046abb85edac28e1a81daef5ce119eafde08138655": struct{}{}, - "bc033c3cb6abd2f5568157bc1bd47ac1157bee5e2ccf3a4a69db082241177b59": struct{}{}, - "5b720147b6918dfc19baa0d7767cab75b76e17998837d04af43f2f3463c5350f": struct{}{}, - "7804cc0abacc62b5adbb9ab207b3493a7cc434921c45f74c3d721944fa7ff77e": struct{}{}, - "00477ad8a4431d45d6eada7e5dacf08532d2525003597ae29d43dbc5011f9ec2": struct{}{}, - "4381dc2ab14285160c808659aee005d51255add7264b318d07c7417292c7442c": struct{}{}, - "5fd9db45972b368e3a178e2a233a5a2db06923cb0f45712af235cd518341226c": struct{}{}, - "8d8c8448f501d77b040fdbea5e66367a751add0aae4abd10082ee871c4e30bd5": struct{}{}, - "f8fc0f6c8e5471b9baad84e0904e16b982e6295540caa83153ceed574b88ce41": struct{}{}, - "0245178074fd042e19b7c3885b360fc21064b30e73f5626c7e3b005d048069c5": struct{}{}, - "a6b2aa44123280902bcf19bc455d3e8ef2c3858c98fab1b23ab30237b86630ab": struct{}{}, - "d7ce921514f75e6a2bab5447338249665acd1b7f02fd319dd26c60eccf72d47e": struct{}{}, - "096b3764d961902778a5be89d855dba5968443126cd74effcd5280989e2673a6": struct{}{}, - "76f75e6129fe30135bd44d80ab7cc46fdba81907758dc808f3e2517beef2b1e9": struct{}{}, - "431e876d148bdfb0e477d9f3d90aa11ef7220508a71c112898ff01140ed99777": struct{}{}, - "b23fcb8820e567d1143f4a15cfeaf04ba660bafd89eeb496e99ffd8bd1079f71": struct{}{}, - "d51f791051c2e5f9112c57109acd4d7b9b5788df79db36fa24c09c3c9ee8a569": struct{}{}, - "acf5bfd986de639d820d029874a8c52b8734fd9bf2e1440ab6e928f86db2de49": struct{}{}, - "2d2332f38d6c02130dce71dee87829d91cde00534d841847cd30398154b7f1b3": struct{}{}, - "b32914cd620087fa50645bbba8268fc3bc9d92aeb427bc6b985477b9b4a65830": struct{}{}, - "4eccdc6a3062e3625fd62ce70d072c4507c06d0022351274608905314118e26f": struct{}{}, - "58316f3a94b6bee86763f6427baec86c6ee8807463abb6e479b36f2fdda7e881": struct{}{}, - "1ec7d6744ab01a7dc28b67e6c477d6f67f24c8ee564a3735fd8202022879d326": struct{}{}, - "a5c299e2fdd21869360a5e52cb764eafc7b94bd0eed0a2080c427684024242e0": struct{}{}, - "9b97ac5e767e5393cd0eed09a8e51b6f3775eba38f792593ea016e8ba9e996b0": struct{}{}, - "0766fa0a0cd628539962c6464ec047994482dc5dee9a1cb77847abefc3e88a1c": struct{}{}, - "6dd95bdc9ba1a9dcfd05adfbfd966555cc907d3f9a9c271d96d969b059edc220": struct{}{}, - "077824e65b500c632817be6b675a5503d7fc4022b337bdbeabc3cfdd262bf857": struct{}{}, - "5e22363936e77b200d078a778a0eef2db904174476b41e4569df1344839ced8e": struct{}{}, - "1fd6227a465211891ddf2da7cd8b8f143097d5ebefa804e65afe3724ee127a20": struct{}{}, - "8ffe8459134b46975acd31df13a50c51dbeacf1c19a764bf1602ba7c73ffc8fb": struct{}{}, - "2dcfaa82e59a8502118ace94a5723cc93f35887d8110a0a2c00f06eab31fb1c0": struct{}{}, - "6eb228fb5b59ab49a45a48bdd9e5f0ed65b43afb52ec72cc825a567986630827": struct{}{}, - "b33ac90d20309f24a3cd63417c1a255b253af99a8fe4ddac010128d272e7c4e9": struct{}{}, - "2ae4f2da880796d39d4ee87c8a9896a8208e0e6d616656d1a951dc01a651e4e3": struct{}{}, - "fe9bbd400bb6cb314531e3462507661401959afc69aae96bc6aec2c213b83bc1": struct{}{}, - "8270f2824111e04d9278c01a92b388147d9d02e0b50d946d25d00db375ff1282": struct{}{}, - "81c320ebd209ddddf590e3dd7a339709e27feeed8e544f56699e40d627423a2d": struct{}{}, - "7300431619db273275511b922f7696db61020086a6dcd7ba1cb9e8e08001ec58": struct{}{}, - "74a2a3d228c40bb43ced9e4854b97d78ccb0dbac1ea94ecded7e548f0723eab7": struct{}{}, - "59d15bc62d871260e09b775e54835be5db2e009a873d67e5f23d11ea9b39aa70": struct{}{}, - "9918f0455cefecd78b99a6d8daf7da74732ccd50fdbafda860ecc0f050f2e725": struct{}{}, - "1ff04ae809f67fb578fe359722f44601ab4c24eca2a41772bf4fbd1ad9a82c0d": struct{}{}, - "7430a3be93e8686df2aefef45982fb607272e16d72a389a8a880aee271ec3ca0": struct{}{}, - "b8d56ccd76edfd34e8b4d05b06a8817e34330659afc83d16f431b9210f63fde1": struct{}{}, - "6bae31bb3642ce2c1838fb713ac1d70fe22a8d20e6eacfff4ead6c6513586424": struct{}{}, - "e5bdf693664a83a742784b95caa586910c06166cece4935af4146989f26e8bf8": struct{}{}, - "0682c5f2076f099c34cfdd15a9e063849ed437a49677e6fcc5b4198c76575be5": struct{}{}, - "e5e947a61fb13af43691e506c5cf43f7373c5522ed881b2b835b873437aef531": struct{}{}, - "1915f013ae9e42e3e24f6e3894662de84b42fc6f645a027830891e384882535f": struct{}{}, - "48e99e083427951b44520d7543f4705c5dd6d0ed010c99f3f12fca92b15bb53a": struct{}{}, - "42e4a2ae7cef23577bdb13418835bbfc217afdaccee96d9809bbe2f3cdc4a2d8": struct{}{}, - "5d1d44f5438e8cacd597fb7b3db97bcd8dd380845c03315175202dc96aa3320a": struct{}{}, - "9e8acdbe27242c09470aa04338b7694f599f259a9e4918f2ead07fe46354aa9c": struct{}{}, - "091d29ac0b09248a6e71774911c4842c6f127355f06e596c9e533670ea37bd7b": struct{}{}, - "8305b93c4c9c500521caad8de4d37e4502cfe4d02a7fdeb68f2fcfc03723d64a": struct{}{}, - "404267835521b93069c5fcde86845534c2f0b03b4d77cb911cd61fc9b84b2082": struct{}{}, - "3878221012d3785e4f21eef37119410a7ed8ebb5de28ef82c0cad48d8cdc5d04": struct{}{}, - "41e193b762fcbc7a71ebee538cb092cbd6e8b4845a688d25dd53e43531f89c60": struct{}{}, - "e519d416c8b2623331c17695e7c6ab641e96cd0ce3f636b097b1be68fd793e16": struct{}{}, - "a5c9d5a3224838d33975f065f751ca57218b1f39023e4bebb80cc56a57bd2161": struct{}{}, - "b3eacd33433b31b5252351032c9b3e7a2e7aa7738d5decdf0dd6c62680853c06": struct{}{}, - "6a934b45144e3758911efa29ed68fb2d420fa7bd568739cdcda9251fa9609b1e": struct{}{}, - "30b8795437a029dcf03c1ab42c380da7d04a96c1a97253ec3b0006b6ea48a377": struct{}{}, - "586abb54469a8ddcc16171b711cd2dd6add6b2c7f88e2d75d6da21c749a682f3": struct{}{}, - "b42aedc25c137632cb251c6834fc8f5e1457a078e8dab85960dbe8b011e547d5": struct{}{}, - "7ea1957aa29166bc3c92ed7d2ef6b243f8748678e78f518eb4270949980683ab": struct{}{}, - "3e364126a95ec0309d105792cbdea90e078a2ed53aa1153b57f70253b9b18424": struct{}{}, - "ff5ab159a3f87b7f37af61f146692c68f6180591793154b1b54ee491f7bcfc7c": struct{}{}, - "22e15f613c5814a58cbed85e29ccb140163120a2078d81fdcc8e0ddadc18297a": struct{}{}, - "94d8fe2cffbe16b26a3ac857188c729bd3a43544f808ea69cf237f13d25a213a": struct{}{}, - "dec58ab7d7f9fb6bd366cea633274ef3632f8eaa823bf811c14bed255d60e339": struct{}{}, - "5fd726bc56c3e86906cba8814a9b97d97f2ab209c48d2441ca5ac9e52b09e630": struct{}{}, - "b9ade24799e0d3c94b99495e429ddd1f4d9faf8739a5c771a7aff5e42be6eea4": struct{}{}, - "be217770533ff039c978ec94b081d42e6f0bf46610bd19ef87c2ac982f0cdd87": struct{}{}, - "443ae12a7b8ebf46a12a14d9dbec2ef4b0e772d31e7cb4ca88ce40c79a134275": struct{}{}, - "6850ca355247469531b982f5b7544a75b136cb414770a261b0c6d1765f55e1b3": struct{}{}, - "5e6c6911ce29b53330a23c1e677556a3afa567dbf2509f9d0593fb05659ef5e1": struct{}{}, - "9dca528f10fb8bdaa4cb641e7a2b5149379d4319f9491e99abe1ae0def3aa02c": struct{}{}, - "75ead06d4290b21d5214aa79a4deed530c54229ccffe64e54a99f93b6028b949": struct{}{}, - "12dd9774cc96e18b5b1b5d4a4b1e0724c4b3e3f37a3eed1af6a1dac7cfdbc3e3": struct{}{}, - "6ca5cab77e702c787b4c14b3d3bf26bad43da606be6eed04ab0b9720120ae081": struct{}{}, - "877400dccdd18db5b4abc0ac761b4d647259d8e06bace1cc908c80eaa9d4969d": struct{}{}, - "aa4143c7e1d5667b1095cd719d8434b259f8eba65e27bb40794ff023078adfb0": struct{}{}, - "e4cd302972b0cf4e227de8277f328e7631b1cd98655ebf610b401514d47338f7": struct{}{}, - "0d6be69b264717f2dd33652e212b173104b4a647b7c11ae72e9885f11cd312fb": struct{}{}, - "f9430a30431829bfa9420368fc97bbb354b2218bde0d0dacf03cc5cb701082ae": struct{}{}, - "2cab953f2b3607b36259abeb3703329d6b301b31277402ebf9f2b3b93e31dd53": struct{}{}, - "5f16795c54ab7de419edf8e9c6da6065f7dd448f122fcbc9815c67daa566ba8e": struct{}{}, - "dffc504aa55359b9265cbebe1e4032fe600b64475ae3fd29c07d23223334d0af": struct{}{}, - "0b83cd023137f13e4413462e372e09251bf24f9e8774909f0d529af62c873e5c": struct{}{}, - "d4cec2e4188245f3553c6198b7580cbec12afa19ae5a6960ced8ac9a32310a02": struct{}{}, - "83ebccea9fff4079ead732d05b9a762edbcda1cf3eab78bc7f3512abed918ab0": struct{}{}, - "c8f0baac2d64e7d7a7edba9ae4cced1da9cb8fe5fb72e9283e0a494a217aee0e": struct{}{}, - "a4987577c74bb7ea62d9e59e8b6b287dba7253408a8bd36a7372915109ad047f": struct{}{}, - "6f5a5c61ccdbd8ff2a620d28c86c721feda2dcc809d23b29e1983af5cc50bd85": struct{}{}, - "a8a487ae656f3d308035d14c7dcfacf31d0e3aed91812f473b57e3379fcc425a": struct{}{}, - "5def3f23a381e5709525830620217ab93938af1584a4d07e8e2399f43e7018de": struct{}{}, - "ea2786ec499f18e997830468655729b31e4446d98bb7a8c14c1b20a23353babc": struct{}{}, - "86c82461f370e0300346d1110c9dd2b08cbadf103e3c88482257e4b9fd8e7b44": struct{}{}, - "0d145618b19a0f851fe7760f7cf702160c429fa19aa58a8fa05a1f8eadc1ce0a": struct{}{}, - "fbcd25e535753518d83d1ed7b922ddc919b3ca5704d2709e5367d307b7b925bf": struct{}{}, - "56236673afff601e0ab50c8f567bfba8a0ed234a1cc6f9bb1b87266359f3657e": struct{}{}, - "8e195c4a0f8cf5a16588caca74f0fd3b7a055ff060557b4a6a55e4c9fa193ec0": struct{}{}, - "4e5ee2648e77dca53da40407e8c7738d48e86607f46f277bae5c970b865b5752": struct{}{}, - "f44e64e75f3948e9f73f8dfa94721c4ce8cbb4f265c4790c702b2d41cfbf2753": struct{}{}, - "d9915477f78f67dbc78f8d16814921107b875658c297ec236551d6d1e5cc8807": struct{}{}, - "70e0378cb382c367677a60f53555984eb6d6dfbe1a510c9b35921d979134bc83": struct{}{}, - "049b67a68c8831d9f69aa63ddde62d6ab352bcd177467cace4b48a0360e9113d": struct{}{}, - "ca20947c0a06692ba035eb3629a76cd172d300bd49e5c576ddb8f3e7d5954ee0": struct{}{}, - "38a81e87e79631e602bf5fbd307ce2fcd382b1670c585ea09032aac778a80531": struct{}{}, - "824e9d59300bc8c13271e99242329ec1b0dc7b4e04aca33c1f10da49bbed708d": struct{}{}, - "a93fcdf7dbae1c2f165aae3ee372a6cedc28effc66ebdbc08ae6eaa951ef53f6": struct{}{}, - "36b53ead480d41766c568fea0d36d5f9a747789300ac0c8d1a9be07ee02ee6a8": struct{}{}, - "71d7ef001384bce4abbfbb0c93509dfe3b20a936280c40e77d87db3a73fc21ee": struct{}{}, - "e2257349c139799fafab4f3e96e068da96773117c4f108f122343ddb19a249ed": struct{}{}, - "2bc5ec0ae141fa98f0894160bed5fee96dfe037305a418561feac9048ba4e6ed": struct{}{}, - "926dc6fb2c9459c7f480ab99b7213678b68c8bd77532d6d00973dc433d993ae5": struct{}{}, - "9b200e506f1852fe4a14187499c52b6ac0a377b85416093d5106f05dfd4c8657": struct{}{}, - "9a942066bf52b569ec803e1a71d46c96c98d6e37008966f3cc108d5001faf811": struct{}{}, - "3b58987bda9d71e8b63f146fad22ea921b3c84b9a733c711a854c7f6dd78b2d3": struct{}{}, - "02023546b4039abe3b9f355c23dafd9119570f301a024e2fd2ff3186ae54060c": struct{}{}, - "6ac9a32c0ca8e84e5bf0c3eccf128cc298ef5cc009ee295209465b79b55a9367": struct{}{}, - "afd3d4471f2410c1b277915a75ff1d8ce5ea267c5a73de9d60ef830516a15066": struct{}{}, - "cba88e33e6497f6cec6acbf95910a16e2c9fc44222612e0c3e1c6926cef4ac4c": struct{}{}, - "33001aa1977963adf55a10e38c4a80f79dc2d843389da9bdd5b47c215eb6209d": struct{}{}, - "777addbc9f618b134908e300782cb83c63d9e59120051b6b36e32cae3ecf2f0d": struct{}{}, - "668edb7aada9952ca21e1a10965cacf94c7e038b05c05baa0534e30b25938773": struct{}{}, - "85e451106fb409544d0edca70f030bbe905ed7b7d1de93ca20e5390a0a7f3fd5": struct{}{}, - "034bca3116c2da587f536842b2984a3d9b154222a5218e795f640573bb5d9477": struct{}{}, - "7c492f3824ca5deae2eb70f2685abe516228f4dba100d5ad3a7477a82415690a": struct{}{}, - "a722a7c8b3b45af562156a0e8b586616ae408b594ac6410c307423949aade8bb": struct{}{}, - "c2ed60eb1f5f3ff15b5c56678f4f865900a03d56ed3e922adfaa3ce0fbe89b36": struct{}{}, - "7c59e00dc4b25ce4b045e969210e20ffdb15ce0bf47468e5bfee3520bcd34565": struct{}{}, - "672a81ef923ec75d16b0300d97c2136fded06aed4061cacf082bda0f2137d84a": struct{}{}, - "5ca8a34eac37884841c6925cc3ce17323a6ed90543d759706c97f2582af8fb03": struct{}{}, - "50ac81f970325a3e011f2437818adde426e1278dee5670dd359edfca43a72d79": struct{}{}, - "c721f0c3e3ed6a1bc6c76deda4bd38e2da768971da2879513c69e541b529382e": struct{}{}, - "0a3f13c673af2389ecb9576de99764a56b808071862d4ba81083519d9fdba28f": struct{}{}, - "e320ed6db43c3d7d1165d05a79007a4e64ac5a569c25f29364f8da4b389dce23": struct{}{}, - "6973fd7e86117c9f67498b947695cbfea303db7a2e8b8e3ec785532708855188": struct{}{}, - "01307b41e7ff0bb31cca03fb7ff5bea9fdb3dd83afaca63d588a0c07e7ae98b8": struct{}{}, - "59803041f0ffac339cd78aecf27faad6f346b950938d4427edf927537d80be36": struct{}{}, - "07b1b32a84a2f392495428206f84fc536151a48d175d91bbe8ff75f12e7890c9": struct{}{}, - "1d43cfed6bbe6dd9919678dea66a37b1576a9890cf5142a7e0d3eddf57f1e123": struct{}{}, - "a87b3e348ccb598ecb71bb83300a2bf8d91b780d0717aec6c91fb46ce6fbe3d8": struct{}{}, - "115584860cd5620e40fd03402e771e185a3f789038d1f70c9d651ee60580c3bb": struct{}{}, - "8a066a46c4489bb5d9ba6e6dd51661d33ea4891b4a20ea59349dbee48e1d8da6": struct{}{}, - "4b2ee3d7626c8614ce6e02a3286267ca3c287aacfddd981c7767f21373e66cfc": struct{}{}, - "352b5b6699d83cdc4efde9671693f7d9ca961ca986a90a6b80550a7d173fae4b": struct{}{}, - "d51d8e75a4cbcac2ae99a20052cf3e1bff9b723a475dca085086340cea2e3a49": struct{}{}, - "baed831623943be39ebf0ef44ae1e9c8fc3eeab51b5f89c113ca410d76a34c1d": struct{}{}, - "70d87f860706b54c2a496d45afb004967d12dbec2e31c15a6a476d809fb8b1ac": struct{}{}, - "6e55ee918da364900705bf2ebd7aaf273bc4743446d0bae11217d022115e7915": struct{}{}, - "0bdd5f970575805c8055c9142b48a5721365e26d249ed3062b2e187c7fa63a17": struct{}{}, - "98c1eb4ee93476743763878fcb96a25fbc9a175074d64004779ecb5242f645e6": struct{}{}, - "03a1e2f302e5b332d25c9920d59f14965da3c9b0a7f54e1f6e148be4f51677c3": struct{}{}, - "9342f3d2b32788efc18b45e30804216e6378b0bac77925161c5bc697fa33d6bc": struct{}{}, - "71132fbb5b12518402f26900bb7d7df98c554534e5163fa2031d7b385e79bd1e": struct{}{}, - "ae3b8a7de92b1c7704d586e0d4642662f851b2e1f198150c860cbb810bbb05f4": struct{}{}, - "57f0e30b29126a4866ff1ba8da6f62d104007d322e40ddbdeee93c8a4a771f78": struct{}{}, - "0c086cdc772aa579506fd17a735af6e7d14db0ad1d49730c301e007c3e663d36": struct{}{}, - "44a214b4b4c28a5fe65772a567f89bf4c7f7b82f292f82ee374d92a9cae7eb3b": struct{}{}, - "9e604abbd95a8c24e853c34cc7faadc55eb2ec4a41716be864bc17bd30c4ef26": struct{}{}, - "492441706d00d4c5b22b540f2a4d3ced21ea1344c93bd3bbecd292a0e2adcbdd": struct{}{}, - "c1cf9ec4642e50e5db572e370d3f7141e6eea795327c1a8f42cfa4faa2819e59": struct{}{}, - "4f929de7d79a16a156fd1d0c5c563a6e7a158e46052dd96a92a353708a2ec4d8": struct{}{}, - "0aead8727b6b1a5f1116400c7136de097e070561598b1e75030361f959e232c9": struct{}{}, - "2a784e014211cfcd07595437cefd62509b93cbfa984142c3db1b7661e63b90bd": struct{}{}, - "b074f351d609f839111c88fa3ba8247e130f45e170d1d151dc4c8bbe2f2eaab1": struct{}{}, - "f3e055913a0b1eb0f07317896f9a1bc466b9a50db85a7f882f3ffde9ffb23aca": struct{}{}, - "22758a64a65ec6f056669f7e7936720b8e9e8e4361ff8f2e0a5a920fcd860834": struct{}{}, - "140e0d2deeb6d6b1b803087c03821448c95f3be61ffd27c89f6c391a3288a838": struct{}{}, - "3ab8fa69d3458631d1a2727253a277c0145f8554e2d82082fa2b5972e9a15576": struct{}{}, - "8fd5b48f3822dd9b7626f16b411dbc3aec05fd594db0fd9d603d4fdb8a65c173": struct{}{}, - "992a2a71df2b8a30ba1181ecd6d314c1735c748294bf7566d570f9aa05105506": struct{}{}, - "8367cd66fdd136bba8ba23f8805bb050dd6289401c8ec3b0be44a3c233eef90d": struct{}{}, - "c419c05c98ca778d3d2e22e67866bdd3fcb3a01f8bafb34debd52d42ef183540": struct{}{}, - "6699537fca2497bac935e0480e87cc66739804c8e3b3828847c95fee9b1d5bd3": struct{}{}, - "89522780aaa304a376442b35176c3272b75e273d475544ad15da7e38f06d4f18": struct{}{}, - "2c6dc5f11594c6b7326de858a6e644973c076b9e0185e9294e9058d6ac904900": struct{}{}, - "79ca2057b15f4a24e5c8e620b58187666f234208e3318b8f71f6b14be68fa4dd": struct{}{}, - "9a869c1ff4397a5b1d91a20f3b70efd32d6a52ce7aebbbb8bdb63a36eff1f3d4": struct{}{}, - "5d6e0eecd32302adf4ad5e712f8f4e1db7cd9f9d37b0a1c1503aca6ee8ea7e44": struct{}{}, - "4209fcf0702262840e93f40abf4df012722838494df760ff04aa1ce529a86f16": struct{}{}, - "fbbafe476b8426ac0a29f87f80bfa9bffdf354d163e0125c50c657a0d3f7eadf": struct{}{}, - "9fb87b21510201a390e278768a09517e7146f85d27c21e19f64ed70a2e3718ac": struct{}{}, - "e243e071e3e1322195753202c4122190662bbc3b5daab7adb726026fa1b3d919": struct{}{}, - "e9ce6c6ef7d71740f40b26413e47d885c75d5cb4e89f644f01b78114ec2d0cce": struct{}{}, - "7c2c4df740f720b0c861920dc27692c4d4b5daa65d6330a593c931d35f8ac737": struct{}{}, - "41ff8b1f8874e7763d421867504d7a8157a3543e75b0427b4bf8c6b5dd6b4813": struct{}{}, - "0bc26e0941de568a4e56061688bbad799ffe3d6e53437a071c2436aac119230d": struct{}{}, - "f168ccdd91548c5a8c871bd4b9b757c5e889a1672e774bddef4a2714759a7a8f": struct{}{}, - "584038b5e84259de027a89338faaa094b37b57b7572cfb409fdf4ffc9f9c4f14": struct{}{}, - "682a4851da0c4e3624684cc306a5b37fb4b9153cdbbfd8e196d8f6adb07f07de": struct{}{}, - "77ed02c760c37deb558b72d96e48b2c7bdb9a5272bb07ab02b2896b99fd8c6e9": struct{}{}, - "fe9cc099f5c02135e8f89f3dd14d37d1b413f8ecc85e5558fc6b66543dec1525": struct{}{}, - "ba035a0f695ffbb3d7637c1f74a0d8e3940a9367520d50a8a9720b5c2145b22b": struct{}{}, - "7cfcadb95e65e8a659ec64c2b20d7ae6e5950743331980622572b9927ecbf597": struct{}{}, - "84f8f82bd4eb1cb3e78e0adf5969e94c1015df419ca17efca597d5886ba0c7b7": struct{}{}, - "23734a74e5ef6010f0c63c69674a9b22225ad4e2c456ec7a4ebd1b9139fb2c3e": struct{}{}, - "ae74f72d212fb9871302a2459aeaf7b20bc2f792e4852be648a7d4e63967d9b1": struct{}{}, - "d999056550fd648de6de0537f389c3da467f846a4fd6117a62cb54ba1c23cf3c": struct{}{}, - "64407f4d33e018635aa222cf4754e381869c9554526f888558d64d97f23cc09f": struct{}{}, - "e0b40776703d9edb4a79819475de9371c533789fa0bc1b9d4d8b525f6c631bb6": struct{}{}, - "8825e76530f87ff5c737b3254c6936bc0a90bba3a10a91bbba739268208d378a": struct{}{}, - "adf27cc54e26ca64846f6b05e1b81338e2b1a14fb01b91e7589c73f4da87e3b7": struct{}{}, - "008ac993f7dc28aec0391e805ee397cb0fd8f4690dc74e93901ae57088d18dc1": struct{}{}, - "1c6333509debf060200eb6bbe28db307508da67c0e3c58088393e4cf09de596d": struct{}{}, - "77011a88f932024a3d5d10b0b66cac8d5c9c1ed6f4fe74e365acc18205098d01": struct{}{}, - "8b5b9db0c13db24256c829aa364aa90c6d2eba318b9232a4ab9313b954d3555f": struct{}{}, - "7b443e7a5d6f7b0b3b700b540cd46338be7808dfc815acf83a9e80111c4bdd97": struct{}{}, - "4b493fc85b64fc1e6ffe2965d8a82a11810a8a9d5a76c1f43a8a6ba974e2ee94": struct{}{}, - "b05cf07b00632cad81694f6fb64aaee0fc9f3116ca89cc2c0abfb1fe852cc2f8": struct{}{}, - "05c7dbac14dc2bf3e8a063a6593eb6b76198a9507d740839efd55940fa6c0cd0": struct{}{}, - "22a2fa7d04248931a8853a7714b86546610afd01b2b1841890e979ba7ba6bcae": struct{}{}, - "26578569a290ecbe82b32e2829bf9e1cc77dcc917475354fce845f9dec9dbbbf": struct{}{}, - "aedb71445fd9dbd8b0476dcd1c1c5d3780f9adb59344643bf2adeb7b55881408": struct{}{}, - "8828e8d5e4b7f8a1340742d8c51551e38dd7597a416b135a92fea0d49eae47cc": struct{}{}, - "362dc532a7d66792039f03a99687d44bd5b2b54728f9a5b633d39d577687f897": struct{}{}, - "5ac52c67c10a6cea6daa62876e90a9dcf62523352f0876dad99c55788392849a": struct{}{}, - "dca1e939662b6f03708dcc734e360c0e3454c503345a3e2a66b65398c13eb680": struct{}{}, - "766da84f2bcd80d695740d3d23a95a78d96fc19bc37d439c80e615609dc89211": struct{}{}, - "21a0270b7f66a1e4c25933f13a1e5a1bbb4757578072930c8189131f9c6aaae1": struct{}{}, - "8647ed424daa9acffd1fa33e26a5d1bfb6fac5933788c65f0cb907c3ed40f4fd": struct{}{}, - "924d83b97ca0ad09281263341d751738fbc15ff8b04cf53253cefaa715a543fe": struct{}{}, - "da9438be5820e751f9f7ceb3a9032fe7a9643c01572dd83a86d4d88ba9ddbf5d": struct{}{}, - "fc9184134ae5672878413fff088dd0a1db30d18dc2a855da6345c7ab592ac46f": struct{}{}, - "dbecc523f97637a16c098a75505acc3c01f9d6ca886590296d0246dbac26c706": struct{}{}, - "8d8f469ed2ef36f537797449f494f738ff6e498cfa63e3761df5196dbc0a36ad": struct{}{}, - "59405314053222d46e63a405db9f44164a0e85eb2cd332f255faabe1668d3bdd": struct{}{}, - "b055e3a6947a2cac29782c0a19cd3f4e3ffbefab5e104f4e2b2225d5ac4bfaf2": struct{}{}, - "fcfd075cbe367c158d5cfaa31fa06656a3e68f626388d96ee81b35dda4310b58": struct{}{}, - "18c3bc28bde14691d7ce2a9657d9e93d205c224fc084b733e5190ee9fd2a80bc": struct{}{}, - "62b5a39b1dce298400da0e6937110645daa22527b57642c3836ccd879a5865d1": struct{}{}, - "48e56efd9546bea1ec46d777fddb262f37b16a5a68f4af5e3003e751ec5f1074": struct{}{}, - "5a2a0ed9aa3ab7d82b53e8209b13bf598377870fd38f8e1063bfbe0c4d7d7f96": struct{}{}, - "0f1128046248f83dc9b9ab187e16fad0ff596128f1524d05a9a77c4ad932f10a": struct{}{}, - "23d67ac46e6aa0d83f746303f79ce9ae51450ed3ccef56d8ad0cb8b4a4958d20": struct{}{}, - "6d4b9ee62028b80981988484e790bac1fdd5a9eb9f30022c6268397e5dd2b79f": struct{}{}, - "ac7d2632806224512da6942cdb76f60cb749a2f6fdaa8304f1a301bc3b97a15d": struct{}{}, - "e78c07e3e57b58602c02589a79931db9f8974fca06b5a435252423eb2f5917f4": struct{}{}, - "fc17bc013d0a7f769366cbd73b290229ab01b2f03ca1ff3a6383d79e6ddafa4e": struct{}{}, - "8266498d969081c29737b8daeb5b51d60e56d008fff243a39d16c3032d42f6cf": struct{}{}, - "32987c4b7a9fb90e729425fc63e7bb81ce2cb1f80140ddeddc968aa79a34e8f8": struct{}{}, - "b316f9499b2e0dc737c89feefd17b2945d99a771b68ace1377e1d9ec854e6baa": struct{}{}, - "fba8d44e0512d5fdb8b16f28e6278498cee2b3a25f8f4c49f6c41a7cb5904529": struct{}{}, - "702b023f619f27aa4a5072fb02876fa6a5eb0164ac60222b51bdf40a018836b7": struct{}{}, - "dd5ace9e018dbd62336cbe039916ad7c817d0e9bc9934f70d67f3ff75544da88": struct{}{}, - "4ff366d40ef0729acab03cc66f04d5d9d2c7075037e51d0ad44b551c0c77df63": struct{}{}, - "971fa55cfccf2558e609043f98b71f502380ca99eb35406b289153eed43eba7f": struct{}{}, - "936c8fdcbac6279f19049ec83ce55f19cc0fff0c16f422210b22ae21ad629441": struct{}{}, - "410dfe855a039bfa20d442df83257da790b082ff466db29f6c56b35cacf7a4f6": struct{}{}, - "7e686559137bcbcc97d90b5450fef265c586ba1fd0702e8b7d2748e5e1ac3c82": struct{}{}, - "bafd151a86f9a573b153e76e3a8a0053b3340ce304a301a5354288fb18bce848": struct{}{}, - "18efface25be78c043801b859502f247ad09e2de7edccfc272d8fdc2ff14c42f": struct{}{}, - "a889fa49b78d9e524860f9ac82cac7c1440837b5e95d651602b26cba46d600bd": struct{}{}, - "f225398f29e2c3081714c5fbec6ef484fccdd8a5ab35e8c7bcd5b0559a59a743": struct{}{}, - "3a30f714924a84ed05dd319a6e82015e64c09f2b0237c5049e64790ddf611613": struct{}{}, - "9cb0ef2cb50ae6b9c9d7b7feb6ae38ee38a83ccb3213486c61ac1246647e7bc8": struct{}{}, - "c064fbca9d9de8dd9bb0624984403b28d0da807a69365d4f7fb09123ecb0c405": struct{}{}, - "5a3909eed1ec71a971f25a3643b7728da3f61d1ba5452b2598a81cf6d228c335": struct{}{}, - "28c9cf4c033fd718243952ca9d4a3f7b10fa09cfacef0d57987752b88e32296b": struct{}{}, - "864ea39fe7e2b27155a29e46fb9458d05e2ccade4151d02b7c851b10257eda7a": struct{}{}, - "b67cab457e406488e88a0ebd4aea395d9b8c8ea72ab3e905f4f86ab698a9eea5": struct{}{}, - "2e451fc7c1c910865c2bb5127b894974a9d386fca85e4d3fd8addd138b7734c7": struct{}{}, - "449f7e9884f85f306185e120b6b35cc25b87109d40a832a95368263e00b00265": struct{}{}, - "d080f84404f9bb1c04302090fd1fd63547edcae9abdb140ec1be1e6f45231493": struct{}{}, - "fafc74436ac60075d4470b338747f068fe83a06dd9f79d127e8393a367d8ae7b": struct{}{}, - "47acf82a48cfa5c340ea536cdd66c75ef85eb8d3fcff468fc7c8abcaceb15ed0": struct{}{}, - "5bf0fce1d20599214c084b052fe4399c7d2080d9d30f3c0b0b92be00b705a1ea": struct{}{}, - "4fb5ba9b3115cdabcc305e403a3f49b4a08876dce98ff105a072262fdae71c57": struct{}{}, - "6011d7a8f33ed8ec9a98ae8103b24310067dd7eb1a6dff6baa3c478612d313a1": struct{}{}, - "15fa97b5a488542761982797f17fd16206a9c1481fb141db5cfe08a38b8f1243": struct{}{}, - "2d5cc34c33f17e87912f165c19e7820b3ed4867f33583bbdbf77d60d11651514": struct{}{}, - "1a47d5f99b6c94ce8553382f2f48f8de329af5d809d52cafbf719f3c08d817a8": struct{}{}, - "901ad244ed23ebc2c4205cbbd904ed42ead03a1b2d27d22a7c76bf1b30e1f334": struct{}{}, - "dc365e79d41cfae1c3c0d969319ae62165b81b1815a86c32bbc6c64cbdf2311b": struct{}{}, - "81f52337ebb4cb1669bb802c708807dde0519d15cb102a6313d26ad5cd821713": struct{}{}, - "f7052dd321cbdccab52951ce1bf09f0f4f0a24bc5025052410c549dfcd86049e": struct{}{}, - "f21cc83ae6b213d41a62e16705899e98be3d431180268f1104efe2b61c91f37f": struct{}{}, - "b06514d8bc65875cc670a226aa945c492c126ef2da71f226c3ed938aacb27f30": struct{}{}, - "ef5dd02a1a621f2a963cf26f6de92fde2beaaeb8911f71a9693f71b207812393": struct{}{}, - "572a7c9abd1dfdca6936d4f3d0a1daaecb139ee3a30f8896c2952ae0e0acb36e": struct{}{}, - "62d7a6b1211d627650e2bf0c869b69b564e2cd74290ae1dd78ae4b5e20b0cfe7": struct{}{}, - "28165145364d8dc5ba3be30a34cffba366caa596d25eb680abe9aac109036631": struct{}{}, - "cf8b8303d22e296198b966b3ff5f69e2488c605fee453a3be252dc6153b89a90": struct{}{}, - "40011c325e5305a8aaab651bb4328480576bd14c2186500ca49f5c171fbe916d": struct{}{}, - "19791736f0722f9578c6bef3eb908b2663262240750c02d63491fa6e26ca0786": struct{}{}, - "c3cd777828af93b767c80b01228925a9d173f5e16176e54b76de1ef001ee34cb": struct{}{}, - "c48b4df565b0c96f84fedf18f26596ed40aa9f46f11021af7125d34d1d3acffe": struct{}{}, - "d3d37edd61d5728276d7be966cc21edca8141804727a7c5285515909e5b2e353": struct{}{}, - "b1b1bdb480c61d075300d9bff7d9cb69cf31695ea048e478facadf426e8d0fb0": struct{}{}, - "251a2ef40ef49231cfa9b9b548d424c71ef2dc2e56b4a006f233a423085aa718": struct{}{}, - "d077e58dee24d6cc828a2b7ba7644912dc991d4f19a1f9b6ec825e45406bbe2f": struct{}{}, - "3c453cbd7314d461c3920a9bb6c34c41e9821cd9afcf02319e268fc512074a91": struct{}{}, - "14593326dc5861e14cd69af0f10d6fd6c7687e862d1aca15640d0b26900189f7": struct{}{}, - "090b235e9eb8f197f2dd927937222c570396d971222d9009a9189e2b6cc0a2c1": struct{}{}, - "373d338065c3e6bce7f76c177341dee0ec4cff8af28bf595af377aa50fba2e1a": struct{}{}, - "33f1ac9818eb1eb88c0156eada16bae1fc00fb98e89645aab3c084a86f66bc63": struct{}{}, - "eac7ccb170414fd1990186409a7c9d58b4652db8fe37cb0aabed24583417e515": struct{}{}, - "f154837d06d522a28bca65273052532f343763c8fca975959e3b9025ffeb7515": struct{}{}, - "14f31dd96aa8e3223bd7ecb1b37ae56bc01e398242dd119f0dca52717bdf970f": struct{}{}, - "5e6a095158283acb536da57b919553874aefec0b10ffad8322f97e194bf66ef5": struct{}{}, - "b4b101f4b6a45841fd826b707f5934975e5fa99bfdb6ceeab8bd99e109af2fae": struct{}{}, - "7062520c5a0ea9deac825278c9f4f0cbad48864b2c7d0c7f1ebccdb752afb058": struct{}{}, - "3cf2cf1a2ede4014b21e8990dd0ce30defd4e4a97af316ce7d62453d22375c20": struct{}{}, - "20f645c703944a0027acf6fad92ec465247842450605c5406b50676ff0dcd5ea": struct{}{}, - "ccc9ff052aee8da1c7d607bee2283e981bd0bdce2de409fe3789ada64e20e080": struct{}{}, - "06363555054de198a87c4c743572dd5113a5fa5d298b9478a30c29ebb9d38b97": struct{}{}, - "eb11e4e5fa504b952259a318c740cd8e5fb9da0ae2df9d14fb213a5018c9ff0d": struct{}{}, - "8d988fa37098528770f03e9869cf40bb65ddb72865da230a7ad8c299cdc74171": struct{}{}, - "fb927ebde04edea30948e6bf82c5f1a3c4c71c9b3c913a225d6c240bba81f398": struct{}{}, - "5f9d24ec86df6a40cc6e7ed5ce5da1e5e7ff6776653e9d66456443b86004c35c": struct{}{}, - "6497e4b3d7bed16979a343a7db4efa6d57725529f5ac3cec45c1f08fabcbdafc": struct{}{}, - "e3460d5f033911b6ef5f75b3f709f2d7c033acd90d73d01e8a7a238a461dc65f": struct{}{}, - "4cefa6b13a8f592f55bfd46b3719f879859c3eb348e20fe34ab86bcb5cb2ca27": struct{}{}, - "1c6cba22062f73086c323485b19fd859e0136b0889cb3bbaef061f0f85111edf": struct{}{}, - "6e5b8376d6ae2f181893d700e7f992576cd7e7f784d1c37b9e23ef20af46f76f": struct{}{}, - "1a80b0691b696fe18dbc9e94722bc7bb2325d76078bda9fdd0c1760bd4cd9864": struct{}{}, - "534f529cd7b6bda6096c1cadcbd658c36787ac4abca325042d9ede2abf4b0b49": struct{}{}, - "bd77b1b05a24c315dd96e1d5dee59564c5c8bef8d953aefab6765c65ba111787": struct{}{}, - "133e3175b965cc926c4b6f3b15fc91c69bde03b77a2dad9f4516e609030111e3": struct{}{}, - "522e6198a268c62c01c9944cc2c06902d8308d65e6444eb8ad10bbe98dc362b6": struct{}{}, - "cff47fe5d92c58d654b08b2624bbb62aa5034530e3e5eff1f4a7186cba2e03fa": struct{}{}, - "972df5ef6bfd96473c8ac2d2d0b5ef1f2dad8692fc3d3e61a084361d33b2399a": struct{}{}, - "1d78e018e405ef26b7f7581336a2a4c556c3783eaa763978cbd7d2ddd0adb3ad": struct{}{}, - "20b04a4f018b89f369ce009213afc6369f59fe1c903c0770732ec7402163c358": struct{}{}, - "653fbb8e581542e136e234719e818be2e58720274fc169a11d3107681116c482": struct{}{}, - "e54a06c66c3afeee88d7bcb756d12bd0f10e20f0ea5022d3fcb032a6c0c4dc49": struct{}{}, - "d849e9d0e31af2a2590caf07f4b28b2d3a28d25aaa36e2d8838c3cc95ab531e5": struct{}{}, - "424e9661130eeeb05edccfd89125d903eafd4e9f9cdf6d0b88fff6b51df92a69": struct{}{}, - "2a0abe451aede7f7139e3b7be00c2adff97b5ef4a50c6b5f4a2165125b55cc15": struct{}{}, - "68c51aba8bd761fec11657cd7781d9abac8ef27ecaba0320b55961b8e49001b5": struct{}{}, - "cd2c0f63ad37d585e77b3f1c584539e26bf7928710ac8fb19fd377f61b7326e6": struct{}{}, - "f9a6b8ad79425a526c7a0855b622a15727038f52c624cbad576aa5fa12ab5ffa": struct{}{}, - "99a068115036b15533037fd29b16ce8ccb08107d38e6b199b508b94434483db3": struct{}{}, - "793f4be48150059eba684b905f0c31a938fa5f04afc89c26f4ef139b1835dd7f": struct{}{}, - "1a9df7c539541cdd98af1483fe5926ab868e2cbe9cba866b9d859ab9fc93723c": struct{}{}, - "1a7648bc484b3d9ed9e2226d223a6193d64e5e1fcacd97868adec665fe12b924": struct{}{}, - "b839f5d45d2b9e614744b6caa25c077325195c7207c3c488b9f7870ef5d92320": struct{}{}, - "e662e60988813fdd0977a6c2223d407cda56a8c9f4020e7d172e01720ac09750": struct{}{}, - "9d7fd6a32018eb55f4fb6fc06949b683003e5a04b0721932a32ed785aec22372": struct{}{}, - "1f556d48301bae699b81d0a36e8e8aee3a079475acbdd94c99ba9c6162593634": struct{}{}, - "13c7d14e12cdab630e5b9b295bbefd7e9cfe6f52a97647cca5370750ab5b3b7f": struct{}{}, - "da71dc7b9761be9a0ecf336fd1df4a81e96dba62152a5a6c73df2faef4efa3fd": struct{}{}, - "6e4ae039e66d2b374311fbaf24a47cb50ff1439764a4a4df33967481e28b2c25": struct{}{}, - "6a0bc6c429198d65ac54e09ff7c7c8674099cd84c00bea1eaced225cb52f9e88": struct{}{}, - "f6bc1168c61950cf1021f702c4f4db376575d40fce8ca19a9cfec604fef561ba": struct{}{}, - "c21eeea69128673773e801e915f11394d13724afed771b9098794e817df35e07": struct{}{}, - "7a7f04c0aa384e6ed4890273c07683c942e373f23e78e39e9418f9792e9a798d": struct{}{}, - "5697abca7a318e6871d9519dbc0742f13935d3fbfe4939d140eedd83696f765b": struct{}{}, - "4ea55208c4e962c3a4d1695c319e16e8043c8f706120f01d79eb098c42314b37": struct{}{}, - "80c95d0e9e710a56937209d81da3043b5dfed8174bb6acc01e334ecaa6408a97": struct{}{}, - "62484e22a6a5ade1ba25cb1b7c55c4b8861de24caddab73c9409742734008b26": struct{}{}, - "bbe81278780d62460410002d6f99e90c2210dc848cf788bfc5102081670f2632": struct{}{}, - "c39b1c7a7d02f4ef65ee6fbaa87e27f42c6c8ec31d6b3423b1177aedd1aed299": struct{}{}, - "045218d1e817bf24bcd8e317c794395ea74e01a073fa834546a44ca08fbc5780": struct{}{}, - "eb1ac1615f9e8c1c2f1e9ad7715176cc72868ecc3c13d235acae8de92f3c9524": struct{}{}, - "ccf721ebbbfd4aabfb0c101ae1df46a585c945b75ecf92640807cab55902c858": struct{}{}, - "987fa996fdadcccc64e215dfc146ae10507af68fce7eec6007a154c8bd444e1a": struct{}{}, - "e2efdd7db924f31dc81b659db07e6eba303497f110acbe8dddb8c13f2e3786d7": struct{}{}, - "e46da30081ffb976ed805142656306ccdeb14f8356268fc5700844679f3fb2c9": struct{}{}, - "607351f2ab44119ebae056926904bcbd4a3785afa28c1f1411ed4af9ebc60788": struct{}{}, - "fe18f7b388833c184e778d6636bb7872ac996f107a2524e5f5097453cbe86d5b": struct{}{}, - "3a4e73aa9516b838ba489d51cdd7de62a78e05a4f7e1b93fef6f2666e8feb908": struct{}{}, - "6195df46a1ee6b6984140dc3c3e6d772765010745d6c3f1ddd736ddbbd564cca": struct{}{}, - "79059b443ce844d9e9fd0f26e064e6ccf02d9481285936b908449be0d74c93f1": struct{}{}, - "80e7ce58b2497f675a071b8cfe0bd2c628529f4db297574e7c8dfeb21af08a14": struct{}{}, - "274c6be124cf759a4f6b96c0dcf66bb6df8b005a9e7954a7ea10da6c15652b1a": struct{}{}, - "f078209dfece953500ee03333012e3c8ada6c36df35a053f00031becab3cf620": struct{}{}, - "d47c40bea34483aafa4af5bd36af4f6adff17bda66d1cedbaf9c93e9bb9cf387": struct{}{}, - "3d66265bce7d9efb67bc7bcb8dbf4892c7a28a40bf5d99260d5c2fc99ca73f26": struct{}{}, - "78154ea1e08a54f73633b2dda74331da41665ecf2534f508a6859f7018b2cbd8": struct{}{}, - "5a9b9fc8e986cc2fc9439778b39f965ea4b55fa030548123d951986d386d4c70": struct{}{}, - "9028983a555ab64650a80e31f44e6bdd57bc7ef1aa437575040bbc9dfdb1353d": struct{}{}, - "8c7ad921eb8dd3697c98e3b04c00b7bafd3f78780457867775e57b26d2280946": struct{}{}, - "ddf453b50d03809f5d1c9e678f79da024a97500ec16c03cac9354e3456e1433f": struct{}{}, - "55670ca5d4f03286858d7e4374a328ece72957b2bd89e0f65c4d971d8874b721": struct{}{}, - "43d26e8db66a76f646f2d559e592493070c7e02048ada7fd8b96fcc0369f5d11": struct{}{}, - "25ba25a573a67c20e6c6530edeaa9a263b74cf0ba2658f5a8ec4c3a253dad77e": struct{}{}, - "24eb4da0920590c5771633b31198fc5685002677fcfc6d8748c8b3d820c75955": struct{}{}, - "73f5c1233741e5cb12b22c7529f55e46c65b96d7eecce91bc412ca872c317e45": struct{}{}, - "948dbde67a8ef961ee8b24e10eac195791e61a4460cc984ce509210979d7539c": struct{}{}, - "a0d31c7525ce5dd238a933d6495c9048d43dec6c8cce5e4347c101d30eefc146": struct{}{}, - "eed5fe88eedf687582e394a4afc70a7b8c6afbf20072c1a1d4a935ef5ef94640": struct{}{}, - "40483593a133deb82430cd80773b64f571db2d715958e3845a1be4b2225c0bf7": struct{}{}, - "a6791e0d129923242466ffd8c93e3bb9958791b249e802c9b3b3e2c3ac7c0ef1": struct{}{}, - "4e70addcb70a8a2c9c463558679849f93cfa70fa33125436eb513bd56f8bfbaa": struct{}{}, - "85228c1a6ab0e937affcf5248845c9644da9667377962d005e5e670d961638ee": struct{}{}, - "2796c527960920c161bf5712c873aaa7e3b66d1301ae584045b0634695fed96d": struct{}{}, - "9d8d5f6169bab0509e140bfb888aa8bb97f8917cfc3a91f9de4fb212a1cc8882": struct{}{}, - "79b1bc81314de30fec0e2a48bf3306e218fd3ef836063d8d7eaa04c1ad3e7d5b": struct{}{}, - "132a1a391cce181c490a2c4350597221fad9ae207714d1f9a10f67bcb1c8288d": struct{}{}, - "4cd6c2914887dd4a68e4c9ffbed8b077f048cf795d6cfa0b801d43e0ea5a1560": struct{}{}, - "6cc058c5cd9578c5611b0fd9532317bdc45d1a8309945ef4d1c5d2e3a2268152": struct{}{}, - "aa43038afb6314a3b24c02613eb13eb4821b24187af19c86431563f46f11ce6f": struct{}{}, - "660e63d5d80a2c4d12c967c4b5bebd608dd85713d85ab911168feb29f037de23": struct{}{}, - "54444aa7bc07ccec734fddb6f35b9918f914123ad2a0a6e5223d2cda95568de5": struct{}{}, - "925e2b1a37e8212265f53faaeff8f154ddc9d1e20e0cc021ec206d0a65cc5e4e": struct{}{}, - "a1b8219ca7752f97224334fa767729a888427b952d6454e9a1a1209f34e81d75": struct{}{}, - "798a404ebcb80d0819b10a75b87bd0976d86828b4c341bb2efda0cab234bc7f0": struct{}{}, - "00d8d3f11739d2f3537099982b4674c29fc59a8fda350fca1379613adbb09119": struct{}{}, - "23bea2a87a029e9721107fd2a4b03655b84a6fd7e92a015f1bc3a2aeffac209d": struct{}{}, - "910daa97a4052206954d9a34471078f0a82539d7fe9b4724bc381f8f4ce8468f": struct{}{}, - "3ca800b667fe40d7579f7420fa497ccd8106be4581b0c364deacb74ae053ea2c": struct{}{}, - "1b7486f938cf99b36e6ec7352a5dc281a0dc25b0801c96992830145757c1697c": struct{}{}, - "cb9fa2a3b15a4d2e76d27bf339ea5f0d34623b527be80a03186b23ea42a814bf": struct{}{}, - "428a29e002608339314e9a9180b92b8fa564f64a1c425840af63c385a1931cb6": struct{}{}, - "4d6723e5e454779e2bd12b4e2a40e2224a5064842a954efdf7031f396f43a485": struct{}{}, - "bb9c8e7be8e5822758befed4f6013f77c1eee05261dc710ffd8eee053ce56f84": struct{}{}, - "cad624c280b58dad356099d5a574906a2537ebe31a84c7b822440defb559a08a": struct{}{}, - "59a51cf3880f1df488de9426bf5ecf83a4905419c95ad9cea826e84a2bbe74df": struct{}{}, - "04e84b3a86c2a0163fbad3c88dc6b8dc987faad300b456195703f93e137e0130": struct{}{}, - "3100486406b39efc3f3d3565bc97cc3b9e2d7b6e3427b194f4442ef4beb05b41": struct{}{}, - "9ee02cf58b3e846af82f22021b1e21009f43e5214fce47dd6d600bec68c788f9": struct{}{}, - "22131ec0d05e0854ba3ba2659ba895e902b9385aee004e4b87daa65f4fd9a805": struct{}{}, - "77459b9b941bcb4714d0c121313c900ecf30541d158eb2b9b178cdb8eca6457e": struct{}{}, - "2dc0269fa54d269a87536810ec453cb095b4b92f45e63826a21dff1c2e76f169": struct{}{}, - "ac299f2442d5489e295b07f8a1c5504d04dea442219e6b74706c9331c5f2fedd": struct{}{}, - "75490bd7b93e6fa7d18cfdea90cc6bcb983d5f3ea326249d2709ca6c94bc07ba": struct{}{}, - "e6c18fdbe59783dfefef3595cd288bcb7ce912d36854b5e8faaef31235d9031b": struct{}{}, - "53ae74be57bd6d25b80b7f295a52e8da0983001ef25668f493a20e7519b492d0": struct{}{}, - "4ba69735ca53765ed6a709edb56c6ea236b7193a3b29a6b390c346f0f4340e4e": struct{}{}, - "3afe33347d9931bbf9ab4db4a22f9f2041bdcb4a1dc6e1cadc1548c238174bd6": struct{}{}, - "467f6325e44a77a08a6b1e04ef73198d45613d3e4f1c8c035c22a4e992eba38e": struct{}{}, - "33c5ebbb01d608c254b3b12413bdb03e46c12797e591770ccf20f5e2819929b2": struct{}{}, - "24cfb44d899764efd72b7f4ca9822f9f96d5c82d7811f4abd1a838b792ac4dbf": struct{}{}, - "e3c073a9b0c43b04fd91c5ed61a66dc62897880e3192c5ed12dd99e4b14bb1bf": struct{}{}, - "721c9525ade2ea8903d343ef25cf68b9bf4ab0aad56bb7b01fbe48d09bc7fcf4": struct{}{}, - "a28a9ca63e8460b03dff84b5645c6c2a30f48149c0e5b273525cf4b80fe8a8ca": struct{}{}, - "823e291431cff346861d254b6919a7f81aae326620bcab48baf225b77521cfc8": struct{}{}, - "93b5ff16dcf99c0a777d61bde95d9d836349c2a23df05c5f0caa79b77d9c70f5": struct{}{}, - "b71199ebd070b36beab7317920c2c2f1d777df8d05e5527d8458fda57cb17a7a": struct{}{}, - "d70afca615203838da3d858e60dda12f900e83e4a6fb03a63cfadb8576ca8985": struct{}{}, - "1af32fa0f14ba931c1711cb180a3a30d6169c544706d0018a7dea3fac9387337": struct{}{}, - "b0ab628c9e14621846c58b4eb35060ef3885253a457d2d76136716d4850bad45": struct{}{}, - "8c704e3a6730d5654e02588991b8d4e98e7281ae405615df9f99398019d3d7b9": struct{}{}, - "cc99443fa1c12968ddbfd20a6b71d591977a0d9068dbf1662162f7c1b45b201e": struct{}{}, - "308aef596857aa1e3cf5128836134d0b789135db1d30d30628429187e175e113": struct{}{}, - "4861c021a6c9592ac0c7f7c4cc8a42ad02999b2ba94ead5d8fc8abedbaf29110": struct{}{}, - "1eed958f2f24e119f608835ec0ed78a6c4bcd579ab34ff8a6b3aa15b3085589a": struct{}{}, - "4c448a406dcdf064f72628200461936808f1d5c6956078106bfef933c2ae0967": struct{}{}, - "0f437662f9381946343168bcaf8b9aab39d1b66cb82d8d4cb839c92717fa4e0f": struct{}{}, - "5066498c3b49f03dee495e55ed0fcfe225c051d2042a5da87739dfd275c9b973": struct{}{}, - "6815f3c300383519de8e437497e2c3e97852fe8d717a5419d5aafb00cb43c494": struct{}{}, - "7729727efe4ea07963c8bf8976a4ba6225110c9811c28f9181d6ff8114e9b69b": struct{}{}, - "06ea17a8981d25b0315ff17bd1ebad3d95c15a3f8b764eaadb93612d9b61a54e": struct{}{}, - "b0b5f54b20114024d9a08e1ea68051fe795be045f5a83ce86cffd76d3bf56304": struct{}{}, - "cd47314bcb85fa19411daaf0cf62de826a0b6dc7ab7b0e4f759c3c67d02c62d5": struct{}{}, - "06dc30c518d5c7ed4ed44ad653de60972eb502463809f733073354103444a281": struct{}{}, - "b3986952b145da5f0e4bd416f3b948e9864b57895675886c1deb83c16a9beadc": struct{}{}, - "323cf83bfed7baf15fa12c2310e3a3cce30383fab197bb069a9699bc219b17f3": struct{}{}, - "19fba0e995b9794fc2c26217bf3b725c2f0d9eeda16719fe75e3ba23ca73bfc4": struct{}{}, - "b2e605c9aaa8c5e85ae8e97825bb8e623f52b26e44323745599642852a8bcd9d": struct{}{}, - "99a77c8602a90163d7a121e6c57ad233daabc34e5ceaa02983d70a7291370d28": struct{}{}, - "57ca7679d6ae8f72ab9e2078baf76338fe19c6822ffa08effb0f8566844ee17b": struct{}{}, - "3de47205e772b39d369b811a8cc515a31cf310511bf1f4529b7234988509da55": struct{}{}, - "b4472cf9e37c221a956bbcde136b681f6e49402258993cf22f910f18d07e7382": struct{}{}, - "d3072ef675cec32c46c9573be5f2f7097ee6191b5dee8e461ae5b3b73af7a6aa": struct{}{}, - "26450087406f54f17ee6d6eb91e8d36b148840814ec9a3ac49ae643da58bf959": struct{}{}, - "cc6b69b40bf71fc62e83faa18f2cb716c60f15129aaf013904019e20c2970852": struct{}{}, - "57d8759063aff1e794917f056a5c66fce188e18d975e25fd11e49d8d86e25650": struct{}{}, - "05585f55fed267f914a371e8113fac058d53a5a6ed4c5c214846d1df835752d4": struct{}{}, - "1534cf2af76ecd84b803010b700287c00446599c68e8d81befa9c569f03e64dd": struct{}{}, - "60f51d969903e3d8f14336df16844be123829b794e85daeede76275d23166726": struct{}{}, - "a94e7bcfcbed3c846d491d4f47c948e53a23908d480248b3ffe9e126e83ea865": struct{}{}, - "27042f4e6eca7d0b2a7ee4026df2ecfa51d3339e6d122aa099118ecd8563bad9": struct{}{}, - "9780c227e341a7208ac30c35c0d06677a9d41b03f5b4d1a4f8ffbc2d285875bc": struct{}{}, - "6428a8fbbb0e120528bb604a66787102280e1b5e26db76633ce37c8a4526e445": struct{}{}, - "fd79757d98d22c6a6c08f8965b0c960222d4ae2399d36adbb05f2a2c3b7c958b": struct{}{}, - "e41ef8df66c03959df4a08d3d01ea42f23a159ec6b786ce789f7eb5e081f4feb": struct{}{}, - "e7adc837a3baffa82e0033f709339397c5dd2ef9b040fb2f124987c69572fce7": struct{}{}, - "1f58b1e965af65127ac83fca0a4daff741d74d4fe07f143bc44d46ebeb34ecf0": struct{}{}, - "3cac7236a548c8a04e5bc9d5c913a4aa7506a35827011f31f240a67689549041": struct{}{}, - "23b6a8f399dd913a23fa046fad13ffcb478f75d55795feb487bc851ea983ae78": struct{}{}, - "d27608cca9b65f91aef225be8ce8d50de8474b36d5bdbae15ab3ff9c5dca7187": struct{}{}, - "bd94dcda26fccb4e68d6a31f9b5aac0b571ae266d822620e901ef7ebe3a11d4f": struct{}{}, - "a80b568a237f50391d2f1f97beaf99564e33d2e1c8a2e5cac21ceda701570312": struct{}{}, - "0c8d68a94b0dd7efd522233a6a13d4f381c2f7eb6d536df43cf6d3f090a02256": struct{}{}, - "26079e41910bcde04be636fbeecc9045379882b5ad3fe7f70b762436c6d98055": struct{}{}, - "0ce2d4c7087733c06b1087b28db95e114d7caeb515b841c6cdec8960cf884654": struct{}{}, - "6558a6a3379db4f310398d1ddd7c8bb400a9e9a2e7aa22339247855fe400aff1": struct{}{}, - "2916fa2bb61281073571eed18305bf569eca21466084a335587f4405ba98a94e": struct{}{}, - "f9f66427da5cc2c77c7c049005f56baf213fae6ebc9afb96c26822e80bf5d2cf": struct{}{}, - "106341ed6691d80fa296bb3cee87190c154e6579e048716d28ab5e6a336814ae": struct{}{}, - "f1937e8bf25a4b8c9e6aa45da3a80b92f6f9b210f26ce296e8ac70bb86cbb451": struct{}{}, - "8a39178d327e72a92d3b9800e55c5e95930f7ef61605464fe8cb43899abd1367": struct{}{}, - "20d4583b20d6b3dd1378a624c103a0ef9c8e576761f00e308ceb8258af5c25b8": struct{}{}, - "6717bef540f31c5e034c840a27b4b052ee1491afed4dcc5cd7088750747d3952": struct{}{}, - "7f82ec7e2862b56289ceaaeb057640d924ec668bbc3262444a441a1e638ff139": struct{}{}, - "c630cb195f52c883a377b3c1d5e403df5743bd33088e3812aa507d12909dbd10": struct{}{}, - "7cbccb0c4caadf9fcdb51ee457a828cc72a45879831b5b978ae2e2cefc449705": struct{}{}, - "b59eb2cdfa9210fb20513f1d07289d11b6e6002c2e1b54053ad8ff051da1ab3e": struct{}{}, - "13adffea089b50908ce4d41d4ee483ea99cc375350fd664461ee979263fae45b": struct{}{}, - "a6e72ddf63690811d8c562df941f39a867471fced4683c73bdc3aa40aae5fa95": struct{}{}, - "ab55a60854e1b699c68a692f0a4980dbc5771ac32e641c89b636c00bb05c7cf5": struct{}{}, - "f3fb5428392674c123adc9798d8482876663ded63ef0b4d7a65adc290e6ced3f": struct{}{}, - "a95e7a5eb45c835b74551d44c7547f625849082d8b0e678988ab049174898980": struct{}{}, - "60547bb41210c0784400e46ad19aa8ce7ee08be8f6ac8153e47b4f602b733c6a": struct{}{}, - "0bfe298af0976c9460d159cd802af953354c3c15344ef3240ceb16a15d23819a": struct{}{}, - "2fb2f533e014433cb7a29e59ba3822b398d6e3947d66958608460bc9f90747b1": struct{}{}, - "6448904534ab4c2741455dcf1927a7fe6c932c5008e587b2c0f7f43c75f0e41e": struct{}{}, - "e727e094815907104d8a4d98676ca7554a03202e02e9767ebcab182e417d289a": struct{}{}, - "2224512ef44a62e580bb1c0dcb33aff688f4e7da8a488aeb4e7ca402c5cacf45": struct{}{}, - "7954d6463914386775169f7d0fe01ca17da7084e25a6b67466a0cf367527af9c": struct{}{}, - "2daa8d4a05dfc643abb44c5d5966430ca30c976e437e0f786de55ffc864b9a98": struct{}{}, - "4785c505e3399917970b1ee359a7ec0d2d9808ae0b639880f46c052cba2af003": struct{}{}, - "85fbc026fff32bcd0f77c25d576bf3a44f192c8924d0119370153b2c52dc5f16": struct{}{}, - "a095c7713e337faa7c397ce5fc77edf4b5fad49409b53ef54f382ac3b5b60a03": struct{}{}, - "dd14a884ef303d84bff5ff103b39dc077a0a28833a320855d0ada14d3f0cbb8d": struct{}{}, - "953151e36d7d64cc82b0115dc515da802006012a2175c9d729d54a8344c8c9ef": struct{}{}, - "aa1d1ac2e612a92ebe7c8f9b976c73c88b097faf8f32af47b4cdce97a694dd60": struct{}{}, - "3c830284b77292b84c3f7e0e6a757ed6d386beab838e17a2b9ff45f7796c7378": struct{}{}, - "23f7c8c88742998e1171625284bee182b53efb5dc8dfb4c291c75bc68afc7c2f": struct{}{}, - "34acc78373b55bb185c82742dfece0006feb437107fd3076dadababbfef25dd0": struct{}{}, - "704920010b8fa885144b4df0dcc3650c74c9cb384e06a8c534f912a1c2f89169": struct{}{}, - "86daa3cfcb2dd8aa5c50d4728664db49ee62b451bbc7b57f6f9981a67a3b5c15": struct{}{}, - "df835dcf8d6c492c3d340604b9e58d8603d98cd5f90e754a3a85cf174488e17d": struct{}{}, - "b9987dcb78ee4d401fd66748ece2202d18daf9c9bb0c5974308082bd2619a8be": struct{}{}, - "381d9ef64788ee4d21d61faed2e39c72d862b92d58689a9d9c991d38c6b4b315": struct{}{}, - "cf9b9e85b99a49d2f1eb0d981825c64f069daf0a60c5fc19f8745bf5e3d2909e": struct{}{}, - "d2435e88f3575be3ee762a3183629548165f9ed6a81a6ab13725967e3c72ef36": struct{}{}, - "d39bda2bb8268719d4bbd925b4940fd8209cb4688723bfdd88f64bbfa863e721": struct{}{}, - "23392d2b061b1366a21969627f799a28065e4f74ac6b7e162e1de8509af29b9b": struct{}{}, - "c2bf273baa98714c24340d7c27535a0238233eebbec90e2a8be1c4b27c92953a": struct{}{}, - "56681010b753e1abe52c449d0aab291b28f1808a3a91b6baeaa726883baad4b0": struct{}{}, - "f454a3df66a4530dd11a801cde442f28f19c459c120ddf2ebe3fa6375f815479": struct{}{}, - "e272deecde720ffc773d7a6a81adabbc366ccbb64da5abdb6b26f52cd3f952cb": struct{}{}, - "c654f984e7a36fd4e834bbedc4181ff62a5972283dc0801d8749fd59fc825d05": struct{}{}, - "7c8b29e7dc08862f3ec49e7cd5182153f345b327c60a042228d68845b870fa04": struct{}{}, - "fabf822884002cf6bf0fcef64b54392598ee4389a22ae539e0cf7bbcf25f34e2": struct{}{}, - "4737a15e2c762fa0758b8723c88dcb8af130ba8a5d3ee7c2a594404f7c9e770a": struct{}{}, - "cd3ebf2708df4d4c6cdb3d90e5e7456c6bc080c03e8cd4573a31671d15adff94": struct{}{}, - "ba736bded891aff9275b327d9534c9ef933d1f9d9d422500d95fa91a1524f459": struct{}{}, - "55c4aab9fe2cc78915ba5b59c40d956c46b3ab0c656fdaab35c5b98aeb5679e0": struct{}{}, - "a26c130b61075a715d19c2b57ff15e875bd42ca6b87b3af5ea46f89faff0835f": struct{}{}, - "4bfef6015e2f05a156e8185cdd753ed6c5119db677f1c7c176f99747e358242c": struct{}{}, - "0556fdbacdeaf845150ebdc9a8300e6d2773ee5faf7977b7a8d58ffc65f6ce27": struct{}{}, - "a511aeeeb8a119931a67038a63b7974faee48712de1c79391ebe2c9b929678e9": struct{}{}, - "28ca48ae89e86fd2d4ba5510c187bdd832f89c6cdc40651585b7df677117a45a": struct{}{}, - "676cb75018edccf10fce6f376f2124e02c3293fa3fe8f953c75386198c714514": struct{}{}, - "183d17bfaf501732c201913ab684b2c5504b354d51cb8f99eebbf04df48a0e35": struct{}{}, - "216e683ff0d2d25165b8bb7ba608c9a628ef299924ca49ab981ec7d2fecd6dad": struct{}{}, - "4b16e7e231bd44df8675066552a00f25dcb2d65d9aa46ac4c8dfd8f18f93b89d": struct{}{}, - "4cf2d35a0a637fbd4ea88a9be47d5f53c931b2173e0c67ca0076b8173ee7a631": struct{}{}, - "102d8f85b30812d0c08c8e4d5f45f2101f785a4dba3164d2c3c2ed0b3310d3b8": struct{}{}, - "9124e29e0c92eb63b3834c1403bd2632aa7508b74006d890cd087c9fbf3b195e": struct{}{}, - "12451ac0a598501cf2fa18f8120af572012ac704c2eeb6d617e175af125a0e1f": struct{}{}, - "17495df59baebfcc4e412e9e402dd65a1ed5441d0feae36c99760416112c598b": struct{}{}, - "b8a6cab2f36a36432029e2656683f710161e887fe5f097ad6a4631498e05af1d": struct{}{}, - "c6f797bedb14a0d4bc031d550bf039fecc8c908639db7c21b1345eeb04549ccb": struct{}{}, - "a555b5daab9df299c88fbd5ed138505d447ec189707d464a7d6fdc22deac2aef": struct{}{}, - "7f411c23e7d9d268b51470b27920d362905e71fca86a2ef5c70747984b30a29a": struct{}{}, - "573abb3ca06e6abb11e0db4e8f70ec85f9901b632282de35479388017f6b41de": struct{}{}, - "0206a97843b1ba4fbb147d472550ec3b5ee8aacadf3707522157240940d1bebd": struct{}{}, - "1f0dea80a1af4eefa352c04bdaaa79f79433a7b458aaeaa927cb73ac9f63326a": struct{}{}, - "0a364e56fd7fd701861bb6bf44cd905d366aa2d483c471f5ed60d8f4bdb8c5f9": struct{}{}, - "b1f8e41d467deb5a22301b320e60366806d87c0b707c46447b754251a86409d6": struct{}{}, - "fe8a0bf1fdfe357349d0a81df7a4d81ae9de9067f127fdf0dc9f7e3842ca9161": struct{}{}, - "c023d5796452ad1d80263a05d11dc2a42b8c19c5d7c88c0e84ae3731b73a3d34": struct{}{}, - "59a7713351302ca34931578b1392c674e46db4d542fcaa2cfdd32800825d7a24": struct{}{}, - "cb9e263e341e0f4fed15db8866a0b7f3777617d7ea80a2873d7959c3806c94f4": struct{}{}, - "8b285aa098f20922bc0a7e6320b5e2785628589728bc2b52f79a58dc7672eeeb": struct{}{}, - "58cc6181518fbfb49da9c50db08ce31aa92feebaecc6c46a3a8084e87b50f037": struct{}{}, - "a1e832187cff64f67dc72a277d910089be8bb8b46cab3f3c3dc5ea70694ada26": struct{}{}, - "8bc345e4ff215112b0e9e71c0915121094f0a85f0fca2fc36207fd11570d03cb": struct{}{}, - "ad86c2bb09b404c625a386bda4a635d9e97441bf53719835609cc766a92b5f81": struct{}{}, - "809c398466693e00e01e0e22d5a7096bf63011aa58dc4db9b46d18b870a0ea26": struct{}{}, - "173218eeca55239cb81e0e5d4b15b23751ffa14f651ac2a862c55e0efcb42d63": struct{}{}, - "50f5d3a0117236c1735f05ec170f3e7ec7d89837c74355c4a9ea7c4bb897693c": struct{}{}, - "f081f7b8d4310e67a7572f60b6070a3034d5f1ae1465b3fe4f8dafca9213a0e3": struct{}{}, - "beeb20fbd67695085c26c5dfa7b5ece4d1985d201cb6f0dd9aa66a36ed987ce5": struct{}{}, - "3deaf968b56510d8421b122f7b401eb2e4616bf1388fd9098b7d63f072441089": struct{}{}, - "2651228672a5312ce635c6c7556632d8aec0bc8d93af2ba907d05777674b6a81": struct{}{}, - "e195b8562e0b911022bd0b2e147abae843d0dff267e4e90509452202b63a2aac": struct{}{}, - "d0e2ece166bdef23adedfaa3f1c47c9f969270f0fc519185514d58b4c7f6d199": struct{}{}, - "df108922e732f36d1805f839880815580356669b8d1ff8cd94bcb1ca34e177e1": struct{}{}, - "102b80b69acd541e6215dbdaed5eff8431f89423b4e374d7c2ad6ead9b0e04d2": struct{}{}, - "064a43832b4f4f1dc9cefded25aaeb634a2fdfa9cfe9e26d4ad3297d9b2e4150": struct{}{}, - "d417301910974c87b4c81212f7cce0f4dc433a2645eedc62e45322c6f0846b4a": struct{}{}, - "1507c5a2cc46ad2273c97eac7a30eee815794272217ae512a3151ea2059af82d": struct{}{}, - "a154d0b155ed5bd13be11317ca122f2944805a41aa08aaed8ff820f42260f0ff": struct{}{}, - "db9e0e81988b9b0e007d67caa8c67eae02c4fa14c90ddc5e362590d2ccabdb59": struct{}{}, - "b11f203e8e6ce90d0ab5a01a37ac248669987afb2a50fe0224fa8b8c5c985a73": struct{}{}, - "f7e5792b9f896f0f5803195cbe8d11e52af78f9cb0feb9001b652f881a769d28": struct{}{}, - "61e0625d5fb4810762d61a4147641b427847d4c7da0322ba48a4bfbba5d8945e": struct{}{}, - "96ab3709cdd992cf9a64890b128d4d7b799a095cc15371f3d174954ce6fdd57d": struct{}{}, - "59238e0fc422f6f424a60a492cbaacbeb30abf24981226e17f51db523718e27b": struct{}{}, - "fea8e163d5ee89b6b224861a7b1286ec2d84ddeca5e1f9e0acaf16f1786f2453": struct{}{}, - "5f395d07369071a505ef926527de2ac53e8c29e103dc63398315bc276224b81a": struct{}{}, - "7c9c9aa1724e22d278a8235ebf4f8db69ef173b6d7bb3e0ab09bcd559faefc98": struct{}{}, - "c0ebfa7cbe3d26ec5dac5c2a33fabb3b30903ba47b6ebb15b31ae310fc52b299": struct{}{}, - "7f480e744a79953eb916b68f540e0eeec6f9cf23edf4aa08cc1cdf5f077c0f6f": struct{}{}, - "434f6b0459e27050efa9be131947164566dccd3da72c9b958c2e714d8c3d382d": struct{}{}, - "e42ddd748c23785103b4e0f99a0e33954a36bf871a6710f6392a49b77992a00a": struct{}{}, - "83d15dec6c82566b20585401412cee8869743b986df8e806e326cc22ee6c70b4": struct{}{}, - "4e686af7bdcc5ae005a247624fd8c7283257c2514f6b3ad2ff5d4cb6d95196e6": struct{}{}, - "2df8200821fdc1aada3e6d00f136c65574a4e15d5c7e0eb8cb8fa068632eb258": struct{}{}, - "fb1f4423452af270e30212dbc81a51aa39588ee5f742b177d4834f4c2dfd0531": struct{}{}, - "9ea0e6c6f5b4cad6e53059c46cc14ef2b56097957fa589eca3f4cf3b25b3defc": struct{}{}, - "16b3f07f0ec2f9dd127b71dc2e6c791b7d93116a1c3bc99d057eaa7a8a077e73": struct{}{}, - "346b3efb4b899e8205c4b35e91f5a4605a54f89730ae65ca2c43ab464e76ca99": struct{}{}, - "4eaae75f1df2f52bda44f6b18a400542d51c81bd7c00b0e720be5dc2c997575d": struct{}{}, - "2c8b871e52d4e5f5db5ff84a82a45327e20df77edef961c4b6fa0e9c3d97ce5b": struct{}{}, - "023f5351b94db0bdcde8dd21da240ac75adc1fc82371c516543b25485cb900de": struct{}{}, - "047364b9be67f665eca384b8061d688b8642a8b47b3de35c22a012ed6fe69242": struct{}{}, - "44da1b8a8189d583a58d1f4a65966905772888efc147a8db6a56292990144ca0": struct{}{}, - "973a75aa8a05af6f8ab00dd055246e911c24142fd4bafffc18d08f103dde516f": struct{}{}, - "23661ebec0d785b2f26aae81a86c556db678fb85bdb9ea2622297472ebaaa43d": struct{}{}, - "291b6b06921b28e99ddbf6fdd6a5c9c9315099eecc60d81760efd70487d58664": struct{}{}, - "545a20faf7cbb6f33a5aae9c0cbb6adb38c1139a4309144634d7e70527cf7c94": struct{}{}, - "02246bf5d4e18e16f338b07551948756bed083724c86f03766d2aab4004be011": struct{}{}, - "8dff221794320ade0ee78ce4cfbb0940035e368d7433aac8343dbdaceb026d28": struct{}{}, - "04efaf080f5a3e74e1c29d1ca6a48569382cbbcd324e8d59d2b83ef21c039f00": struct{}{}, - "a7fa358c57fb50d64752a64572d32a7f456519eca2c055c56460e8b5e1475e02": struct{}{}, - "74f11276b5ca0087b0c419de0e1d802c81d184b9d1349a50c5e1de6507089d18": struct{}{}, - "81fcbba2c584e6bc656f5f0b36e98e8ce5d5a888bfeb1816425c7f9a35b5e2ce": struct{}{}, - "d28efbe33b48ce12e1f500f1f920602481886b9815c0cf3b049bd5dcacaae332": struct{}{}, - "bea359bea8f7d88ecff298e31876c7206ca36df3e5344293020ea745f9577378": struct{}{}, - "13a3714f3ff179531edc457c5c56f8c4f66601917d77d4770ce7efe6d1d7e023": struct{}{}, - "84006bc7ad5274b9a9909c529e0e649a8264f5e42560f25927e0e82f4d42aac3": struct{}{}, - "9c01236c4702a07d498d876f502ebf793fb2bc4eb4b4019c82893f921535f75a": struct{}{}, - "0c076265ea5754bb4272797a57699ee9b1d64c5c0ef9a6fa7b2f05bd79ac7957": struct{}{}, - "3e9f0ba9fea0d9acf951e24c1b6d62f5ef2be308a70fbd67eac88338bff66071": struct{}{}, - "b0fbddb4123cbb2344fa3cd6d78588ff6d24914059bb2175aacaa0c40ae36c68": struct{}{}, - "90e6b8626e8ff4c0ce06d7d2a8f4e20574c3c16398e90eba9013daafb47a1104": struct{}{}, - "96b8b43b198b278c2242dd44ed27e80dd3dcd860be69cda1f805ef50e2667760": struct{}{}, - "94ed4de4354a74a8fbf411462b91467d66823e323c93362e58c5aaa38725a3c8": struct{}{}, - "594b1159d6adf3dee2866b34415fad4e41d62019f0f92d1f6b590f12210f2c6a": struct{}{}, - "42e93b9bb77d8a73e8412111b8f3d6befab66bf48fdcdefa80bb111819aa0cb1": struct{}{}, - "069705965977393f404e2d3a500beadc1c7e3b264ff7a719b33da4223d5eed25": struct{}{}, - "de19c97d557a0e8a8cb2eb074915a22154e5f7606bd248aaf4ae6d24782f1409": struct{}{}, - "799516680fabfcad98a4a1908f3d84f9e1110e1059a534b83ea8f7399dfb9d37": struct{}{}, - "16a0b4d2205fd1719b31cc7ddd14b3ba55588c3dc04df8d4a6911b45b9c4ad65": struct{}{}, - "8d7ee79eb249d9a127d42ee4242ccfd2da1ef73ecf1807d57792dc11241b0046": struct{}{}, - "d37f48d804113b47254fe29515b2c084a7e63c3ea81b88c475ce0fe50728962a": struct{}{}, - "eda7a59466adce3a5349eaac69812f703155f2c8f37b10146a334281977f61a0": struct{}{}, - "c5220b3c4266890f047190c18dc4243e0dac079d43ca8065899c4f9f3cf124ef": struct{}{}, - "22d8ec27fc129158128c5bf5fecae0342ea0937913c5cb1fe381a0c5e5d4cf09": struct{}{}, - "397b0066107f06a1f9025849f51e5e9d13075bddd40945bbaf011bbe94e6b524": struct{}{}, - "d6627d040cd79d6f6843ef39994bd931c9bf0e4d925db3e629b4f53204fbbd96": struct{}{}, - "de3d43caad2bd3c4f0622fc60deecd06b34a0f25a80e30b81fe051a3c54799bb": struct{}{}, - "52b07f18c831ba4b08ba0b8bc84c6e023a825dfd385d20d8dcbf410d11c546e7": struct{}{}, - "89a87efc19f20c057d890285060a5d12fcc5a753762a2b97de11cf3fe9d732c6": struct{}{}, - "dc4d8b85395ae472d955f12fb68283870e484254d8185afaf5c29385581567fb": struct{}{}, - "add0b887395735c131c7ac69fa576ad5fe748571f1fe0c2a8614c5230c63235f": struct{}{}, - "89623921737742a919ee035f7dd57df1ebe19ebba477b916cb93738e02a368f2": struct{}{}, - "6f87c55c427281bedff941e77a227fdb7bd9c0cdc8074a9fc88f79105d991e19": struct{}{}, - "940f8ea4fd216e227abae1b3077ef7be58c0ebd6a59137dfc643c8c6057a23e7": struct{}{}, - "b193ce3c0dc5e1a8724e2c784e34968e79f44cd4eb009878e4e57860c2f052a7": struct{}{}, - "db02f20e1939becaf7fad76d86e255b0da5a214cabfb23f85f035542253278d9": struct{}{}, - "19a79f0389e61e45e06fee423608d405418a670afb56f93abccfeae663128e3c": struct{}{}, - "96067a7838cc36a258f11f457d4b0ca2d5fe60b0b92436116853d41eb832b603": struct{}{}, - "118a7a2fe6054a34438d5e8c2be8cf29cdbb1ffb3f51a037a1b9fe9d03c5406e": struct{}{}, - "62377ad5b12991689899ad2026d9a0f9b073578a3b5aa6cd460d77132494b908": struct{}{}, - "c2720445a45267813688ff73fa188aa060c1b661aefaf1650d42f690697b5ab3": struct{}{}, - "747355bdc2a224032fd405b1b9e8985bfca47e45b34668f7d0a70ee4789bd855": struct{}{}, - "8a741d2f635ef2b4be6ada535c9029049aaee447e9126ea9c6ae7e2e5c134492": struct{}{}, - "ff59d1536450368c449d43def2a6045daa9a16ee3641d01da5709bb692bfdd28": struct{}{}, - "813cf1ca6462bbb705fb271973c7a98604e6bd7636a2997864f0a63752f65314": struct{}{}, - "923fe53966c6cd9343e11af776cd4b05be315ea4b200b02e4d5dfb0f929b73bf": struct{}{}, - "00ab0cae98ad4c0fe66dbbcd9ec7f763eec477bf1ef1043f146ed7d0ac5fba0c": struct{}{}, - "53904166bfa38092ed280ef9e3fa53e862e969483f501d48a48f5d2b82044758": struct{}{}, - "a2f257bd34b48cf32c56e82fb753acd316a097ffdaff7bd4956e4e82fa7ceffc": struct{}{}, - "8ddeaa4152e7c59f2240ce6716fed058771ed72efa6cfd991dae35ce7464610a": struct{}{}, - "a495a4b86c1eac64327d8cad2598044314d9176f0ac069fbea0cf0baf08b6c9b": struct{}{}, - "f0a72890897acefdb2c6c8c06134339a73cc6205833ca38dba6f9fdc94b60596": struct{}{}, - "6c45cb72a36e63d522aa54ed8adbd7a29a989474f2f77e0458af8800564ef3cb": struct{}{}, - "b29bbb80056439d958b338bfa601b8cd537b4ddb7e502245036b8f7a0089e69c": struct{}{}, - "d6bbc3166b14dd5fa3ad6edaa1493bdfc5f5521d450e27b71cd5a5054ee142cc": struct{}{}, - "0c957e9f09a2444a8868737bf42b16f40d4e3533b57dc31a61124c2cd1bae31b": struct{}{}, - "cd8028c53afbf33357a57a5bc96d55e1f244315381b8ce650f98604d5260c4f3": struct{}{}, - "c78862c4afddaa20fd3ff12e5e270480706499341ca5d1d7437ec9668556805b": struct{}{}, - "280b42295be466b74fb6c14fd3bc2396abaa1258b73d5a1682c8899a0bf790b5": struct{}{}, - "c89d33260ec38adb6769e1983934beabd1b43432ff1356f7d05e26d3cb5171ee": struct{}{}, - "f46fa9bc09ef7d40de851ade6a43ac74f32c14a27e3a879cccd69f9ae31c90ef": struct{}{}, - "ad8b0b1dd267c5bf55fcdf79a074897a8b4d977e0690aed1199396c823572443": struct{}{}, - "8c585378513f5f7a2e1456ee54042605fdb890392becefadd2ab180fd02fb341": struct{}{}, - "1db4a0041876241916ff8b935a46b680de655e06456c77c1d2970688ea2838b9": struct{}{}, - "a9efed8daa6c9cccb8da927d19127e2746028c65c05409fee133da963d405a58": struct{}{}, - "07aa899546a855e8adb093a3314a7452f723ad89f296cc8832d367a32eb919cc": struct{}{}, - "4839625482ad85764b6c56518dfe587d7ecb61be04ea3891fb59daeca26cf7b2": struct{}{}, - "1e84ad8842d5888638aae3e89993c3eab0481a725b78670fd0a771ee1b739039": struct{}{}, - "afdcba2e170f8107dd68c4227dbbbf0bc44c7b9ae55506ca331c5c168840ee80": struct{}{}, - "eb0b9c61cefd623588503c130be4db393c92887fe5718d9ddf82010be625757b": struct{}{}, - "81534ea73fe40623c058bbdb672e5b537112b5e6dc04e318c421d123e421e35f": struct{}{}, - "cd4f3afa6982937e1fcc283c451f1cd9d368e998b554ff7b06eabe6e723a39c2": struct{}{}, - "8f5083e3e5c7dc8932f2bf58212f963f3a44752618c96297f82623f736c52738": struct{}{}, - "55aff0ff427489c25422b18a2582190ee8f0cd19515d72e6cf98bf8d695f0b0d": struct{}{}, - "ffb304816a1090313e833215c08dae3d209cfad1ffd1f674f0909a2ae99e1394": struct{}{}, - "c06dbed7f6fe0fa30544a935c4cd22fa1612262a88a10059d5b66be7e2ecb2be": struct{}{}, - "7e692a6bd210312e9928fe2588676ec5efbaa4f1943682f83e6306b4a8dbb944": struct{}{}, - "e674f78d2bf55fd93c878f7fe14448abe677e7c3160f820ac6a012e457520b81": struct{}{}, - "7bc0ee8330e4bb4c7bc0fdfb995c7e1bc2ea1cc5a741eaa988a0b3018f78f8be": struct{}{}, - "4ca9f5719b991a47e1fbb6f82ffc9283b993bf3f5d923aaa13c4a2830ea80b3a": struct{}{}, - "ecc94905724ef0aabca6227901f4a2b9ce70f2eca56142857e8341f5c180e41d": struct{}{}, - "909c272051bb6b905677de2b386786cdd9eb0b0f361e5d817663006b87e4b695": struct{}{}, - "32d6e4a20c1cd7a5aabd13bbea315a557c08ea5a944b15345b89b0d713027b6c": struct{}{}, - "bf8c10763e4a48eb947eac3493febd04ffd57aee15710daa8b6f6b4f091f41af": struct{}{}, - "8cface989a3a91edb14989e82ff3ab67cfd3046906d0cee828f8882a8e137823": struct{}{}, - "acd8d61b6983eb358899e61e41cedc3b8cdfde68479dcc5710b539df4a13b00e": struct{}{}, - "87a02ea969a0d9e88e81aa405cea0dd2e30f7e1bba7ccb3c9e8159446201535b": struct{}{}, - "ba5edce0be3a9b8f6ac9b84c72935192b2289b3a341ad432021256c7144b59f4": struct{}{}, - "de5c9050dc18c72f8e4766c91b9a603119637fa4fc5df7bf75246ee849727adb": struct{}{}, - "57258619e01ee69537bc4e753414f18fefad9fd50189f1cf9b5e12cf5eda6c47": struct{}{}, - "dd64799f8276c61ffe876cc538539ecdbc5fd17c823b110c14c2cef028e2cfb2": struct{}{}, - "888da5db853449fff82b07cbdbf7c755ece0783aa670bb36cc5c4cc9a68fb864": struct{}{}, - "964e3ca93015246b360daf5c050aa55f9214b4a884b2ed88dbcd79eda066d8c2": struct{}{}, - "3c9d411f67bbdd7fdf25697c9fb9d62946ceb33f7667b594b0d53da9b89a547b": struct{}{}, - "eab229168fab8507a8578cc6e5d17a32abeada8382f0ff2b9253c847ba91dc0d": struct{}{}, - "8870e6cbc014734d787e5fc27e777f1fc8009bd258357141a36e763269b124b9": struct{}{}, - "814e2ae0398348d9a87ff91c0372dcb76186ba2d2504b1235c8d5350bf52edda": struct{}{}, - "1e22560cee2c4b727c6a117792e04a6769efbe2395f8e2528c603a153a446477": struct{}{}, - "592fbed6f4ef4a643b0c5dec00a9a32f69a4027aedb972265cd9237b7b31d564": struct{}{}, - "0d81684688d4057da4d9f6df64b28154b68afc2f1946a756302613c92fdd4986": struct{}{}, - "f8f474a55befde8bbea518f35f96edfaa2a72a282104b96df2bf521ebde6a675": struct{}{}, - "656d5be9b84397105deefd04cb2e83f4f3d448bccfaa953d6f62021b50f6488a": struct{}{}, - "485aa48853c72017c452a967ba522a2061ccc74701361cb11767094888c43608": struct{}{}, - "8d41fdf30282e16c7b6dba5060a21a42446f080e460bcf229334817063b9f281": struct{}{}, - "6106568e8cedbba50c79f1df0ed570222672c79d9822f3b692178643f6736190": struct{}{}, - "330723c9aa3518de2766da1efd0b9eb171402502d98f39d7f46f0aeafec78d50": struct{}{}, - "9795c5ff8937f23526ccb207a5684c1fc94a7854e19c021b39d944e51f5baef2": struct{}{}, - "0c24734d7bf5954f08e5bfe4820b0b2f63da8d658df37c9d50080485e034b721": struct{}{}, - "d158be41db87d7055fa9548fe9c0e3cb685983fedb4f2d648d7a25c046d72d9d": struct{}{}, - "363549b7d5608f24edc548a23cbb5095ad740bcf821d4877eac71a08012cc9c7": struct{}{}, - "d20bcf177b60169a92529f6b5b71c8647583a0ed940f93ae5af62c127856cb1d": struct{}{}, - "facf8b54e5c0b8c426bb1c4bf5a00abfeaa064dc89ba8298dfa0c083746eee5b": struct{}{}, - "6be9a35c56415dceb21a907b87708634f5953a9b7fa97f282a0fa5e98c4b00dd": struct{}{}, - "a7c80db3a705530716c7dd99bb8a8b0a80bf40fc563385d151260dcc0d1171c3": struct{}{}, - "29a221c6980aad1537b35beeeb6b24cf714b1e394adf38aef8697adb9d06818b": struct{}{}, - "9fa10fc879d17c2711872c9ddb2d9b4bc792b5f326d0eceb94f81e629caf1222": struct{}{}, - "fce1e6449b2c4f2a9ac8b3c0db68f8b4f5b07b8a850993f62e1b4dca83bef51d": struct{}{}, - "b76e7301585862f1f28213faff649b75029d513f9ea44c9135f462c8313ca124": struct{}{}, - "319bd30c7a232e903e34bd986b77ca8babf31525cf05a55d3d9acc3fecd2e675": struct{}{}, - "a7996bbcd2b449e77e67eec4d2b0ba8872fbd9069dedf52ff2d8d06e12df9cd3": struct{}{}, - "6933022a42537d6b6de0624b5e674f5f3c759a7442ff3166db69f7128e0deaea": struct{}{}, - "3f6abb944cb07550fe28aba0d1b146c74d7961d7dc35a19f27b69786f9813b96": struct{}{}, - "ed6bf432aec21f9ac59d1d0b889beb2630eafc48d43fb162dcc6af28ec924544": struct{}{}, - "cacd11bd743c36ced69d57e0c13a4dfc5a714c932c35571bc2318403dabd7c58": struct{}{}, - "79648183530ff7c5bd27b9d110fb17980272a0cfb0c37e31350ed61bcf0d96b7": struct{}{}, - "06e55b633481f7bb072957eabcf110c972e86691c3cfedabe088024bffe42f23": struct{}{}, - "d71f0bd623e5e0b40eb4b4355091d057d5e40644e1e6cda16b33213f8ae7541f": struct{}{}, - "9a380c788b413913dfc4cad4654e120869d1c0b8e76e2821117b691ff4a86ba3": struct{}{}, - "72231043bc1807e6f740b235eb7511ecb33255a6a375435631196de8a9750d4b": struct{}{}, - "79063e8037fff16d297a1fe65136f1251126cddb2cc9870ecf8d653835538e85": struct{}{}, - "626b208a365327e7ecd6ad5af2f39c8d08c2ad85a264bd0006450c4d034ba740": struct{}{}, - "f6ad9187c3c4ba42fbbf9c341864d77cd7463aceeda5ddd491b31ffc80442e2b": struct{}{}, - "cf83f0f9828551b6247bfc9288d0740dd5b3ccb8501f8caed37c19b912ae4438": struct{}{}, - "fc1303c309eae54ec2fa4a013c30839196dedf4fd5e7baafd48bf9b916716e35": struct{}{}, - "110812f67fa1e1f0117f6f3d70241c1a42a7b07711a93c2477cc516d9042f9db": struct{}{}, - "e0f167bc84b881bc06f6884fb48e02f41dfc5579e25489db6c6bde238e4aed15": struct{}{}, - "a43df7c2ff1300c557948ae3b8553489dcee2e3df3817dba140c4b9357a1da8c": struct{}{}, - "92a5dc04bd6f9fb8f29f8066fed8a5c1e81bc59ad48a11283b63736867e4f2a8": struct{}{}, - "df8eb42b37df4f774564ac36bc946beb2f4294f2e739f8ad1ce1c7e3ee9d60d1": struct{}{}, - "8216179121f3774a82642ca6ca1cc284766fc51ff9e18fd97dee09bc7b3bb604": struct{}{}, - "92719fe0cf8cd51592af31ee8a5736d79f7273777fa3f7b70bfe993a4cd32180": struct{}{}, - "21dafa5eab70fe93a71960d79e9479f16deadb53215c48f2d31ad81a40c4e753": struct{}{}, - "67a7743cdaba48d6130508dc328bc07888e11df19cd352bc83aa8d0aeb46289a": struct{}{}, - "84d002da566536128bc84f4efb1d08ebd55be039c741aa37ac8becf860321846": struct{}{}, - "452fe4ea83193ed65410f4c4a8ed1d77b47a77bb31dca844b30f2ee278b4ecfb": struct{}{}, - "c195d2d8756234367242ba7616c5c60369bc25ced2dcb5b92808d31b58ef217a": struct{}{}, - "7b74b418a352d67108173c20c1b16b4b726bad8606be65711ff924dbf9a40670": struct{}{}, - "467783b71d6a8e85760d2354ad59d752e274960c37688a09d4a55738a185c5a2": struct{}{}, - "7069c5de53370260026eab9c3df1f840a6f5bcf6586d0260fa191de1822cc229": struct{}{}, - "02587876979917e4d3aa757ee7af8a7d1ebc2a325aaa1145a2ed5ac6291ff4ee": struct{}{}, - "742b9bb2306eb494bbd3b50593a32cfe621fa9b3228ccfdc119dfd44155b5662": struct{}{}, - "824d80d71985f082a26997a8db88b5d1dd45b777d73585d03d236303e21bde97": struct{}{}, - "0315b4020af3eccab7706679580ac87a710d82970733b8719e70af9b57e7b9e6": struct{}{}, - "de5d048571dd0d4477eb17bed2483b39b2c33cb48c464fd8cc328e90f03201b3": struct{}{}, - "129db695045bea10fdb7633147f336309062dccb667c8bb6e43bf58d992ada43": struct{}{}, - "ac5fbcddc025d0c8487553072e75d7ede77704bdb8ec30fa07e7be76f7e49f1b": struct{}{}, - "ba19d2296aad0e7013d92e505957696716aee597c896012c06fdfa5e33e06841": struct{}{}, - "bb21cd8328ef5bba967600cbdd307b879f53bac572b90810cd60098a2b0d01af": struct{}{}, - "9c0e1061c8e49a033a1b1e4257e5d799e62fff14eefbc2a46b94d1679e8db24a": struct{}{}, - "6ccbb591132809439163541e1f9413501e3a7ac22cb466fea655497720e8db56": struct{}{}, - "839984abdef5ce5ba52672ee2aa2bbc13659f9747e10245accefdbff22f275e0": struct{}{}, - "494414ded24da13c451b13b424928821351c78fce49f93d9e1b55f102790c206": struct{}{}, - "0da70dc4f31a4fec98605a93565e24aa51c6ecfe5459a5dad0a12624ca021537": struct{}{}, - "71db428976f15f4fcbf4c2179ab12952a014124b557cb58f9b431666f7c7924f": struct{}{}, - "274298cfc022bbf4da968f84f1f6519e4787ca7430c74265af3ec8328091c4ba": struct{}{}, - "9a046660abc6d16a4fb3e837e8b24faf9a454adcfe480a0e8119e9e0a49b457b": struct{}{}, - "4a0fe7e3c914b07b9400dfa7eaef5f3c608c3ff3dc4732ae9f64be16e26c28f4": struct{}{}, - "530c4fc0e55038578184991c3f991043d4ec5bdceae63ed8f80743d313fb382d": struct{}{}, - "3a2cf9a91aec0ea54963a2be007c6764908e5164310d8701bcdb738bba087875": struct{}{}, - "77320cc7d2b0ac8402130a3892a8c81f601f30b8c8088c6179c976512e8dd58d": struct{}{}, - "acc52dd0db42151c25160f0a31124f5c513929fd859a5a1e224c57e298cdbace": struct{}{}, - "c1f026582fe6e8cb620d0c85a72fe421ddded756662a8ec00ed4c297ad10676b": struct{}{}, - "4071572612d01c01f89b61bd04c889ab355b6071d02d6c42969551cff5187211": struct{}{}, - "8f5c570f55dd7921c9861e941be98a9492991d1a862d05283f6ddad56c891cca": struct{}{}, - "bcc08a889a92032a26937e3e26f38f3c66d3da0f6ee3fefd960120b8a54c0c8a": struct{}{}, - "b62a51a45f7cdef7a0aad9fb7be7e693908c140a6379b43049ba1cca333c52b6": struct{}{}, - "208be34b55c3475df168f93331e3c32b718fab092c6dfa72d31464a710a6c8d9": struct{}{}, - "ff2e963ff6d0c33aae94a7f823b6e2bf7358004063f4275ba525539cb6ec6734": struct{}{}, - "6ce53691b126808d3745d72b9016384ef0a174004dc9d3ab151e3c82da186ba5": struct{}{}, - "16ea09fc78ca83ca502cbcf2377acdf280bf18f61e259153f0868405eedab5ef": struct{}{}, - "9c3c2ae516c9afc827576f2dc82c7c796d594311d774b0828010c22379a7e953": struct{}{}, - "11353194bbff01263cfc2213cb6a4ace3db8f809223d26dc1f65ba865f186e32": struct{}{}, - "6039f6746952536ce242e29d4bd82178b74ad920ad053de35ae61dff6ed8bd90": struct{}{}, - "cbdd10d72000b3a4068a0ea6c99aec6b306c0d49beb065c105143ee2e815256f": struct{}{}, - "315393e576685d59a397279e85965d573529c7ac4d0a3836f7a84af23844bdaf": struct{}{}, - "3d011e92f7e25ea02d9c66fe90bccfd0b40200d66b9d0230d424312ba3a71f99": struct{}{}, - "c73bcc47523d630ddeaa01a10ae58d3d5ce82daf5eeffe7fdede5e27c6d9be77": struct{}{}, - "b7679dfa95094e8a9417a32c43225f08bda3af21cafe41460486ffb1055e7b2d": struct{}{}, - "cc84de6ab52e1de076d37f017dfdbd8e1ac026fe5d243f7307c1161438f5aec3": struct{}{}, - "b27fc3c8b512c166957acb54ea319126c26b390dea4353f8ff28adaf00fce56d": struct{}{}, - "1ef393f2c0772064cae9403f23e7f8fc6d49bb2939f463f23c4e637231e84da4": struct{}{}, - "00e3e584e242f926ff6b26bdd0cd26aac15b7ae023a7306c81a7d5708ce8459a": struct{}{}, - "5d86d506f4f54726d14d3ff3621922e97dfe6124ef8d24a140808107af89a3a2": struct{}{}, - "943c3eeb252dbd176df2295566dad51a65ddc81165de0df9873a4ae9f7bf09bd": struct{}{}, - "bdb7ee287dc77780364d365837560c36ce08a7c234ebb3dc2554dde27a21f940": struct{}{}, - "88a5c49d8cb7f295df955627440db159fecd962f91d56017b451fc9d6facd79f": struct{}{}, - "73e79b6bcd4aa1e2169ea6b98c03fb07bf020eb6a7679be5f8672e4ba48cbf38": struct{}{}, - "daec37dd6b12f3bc0fd2a10130131f773e358d38c57f33f33728487dbaf69442": struct{}{}, - "a54367715f472f6683339f49335b92022b61e51022695361ab0980fb15ae473a": struct{}{}, - "76402f618e3f0c0c0260b6680e22547a376c97f867a0bf36c3cc16935078ca7c": struct{}{}, - "bdf2681d671bc2f62265381139c15762ce17877a306aff2dea89f2215155eadb": struct{}{}, - "b39a6e69cb3b601eb4a446aad2dc4e941c5629e2c77db89f686478dd08fdd4f1": struct{}{}, - "5f6121bc06e18e209920d57d2f16b17cc82dfc2ade1d375d6951b99c65d1b89d": struct{}{}, - "e78536a3c2b5ad885c06053687d93e0443d156cef9c5cb798751e6877e67a6ce": struct{}{}, - "cb86eb2d914d37df6857c3cfe4827b5f770b01a86578120618b0b21572160be8": struct{}{}, - "df0c4dd19ae70dc64b7b03ab398e9bf652ae7171072dd8a7d933ff909b26acee": struct{}{}, - "4ff9961269bf7630d32e15276569c94470174a5da79fa567c0f62251aa9a36b9": struct{}{}, - "f464d7d71c06e47a535ce441aa202aa717cddeab902a45b0c283aac7a9a090d7": struct{}{}, - "11c04e80d548134a9d95a1fa623ae7960050588611def7e00c8fc06777ea7597": struct{}{}, - "3b409d9dcb082d72842502c561d97386a175af91b85e6e67f056dc949549e66e": struct{}{}, - "0faa155716f526ef52b450e853aca61ed7c037d51372ea62f7a303598a226f09": struct{}{}, - "d4c363025fb95b88563b72ac9f9914ba5e04b66d6e6b39591b90fffdd97a5f75": struct{}{}, - "000e02e40e441be798211aaf86d037bacd3dedbc8a6318ca5f9a5c72ad52ffbe": struct{}{}, - "8c7ed2d9be59e21cf7cfc7da75c6c0cb7dd751f4a870f0f9236f0f35cbaf584e": struct{}{}, - "f9de8545cac4782227db7303c8ee243f62f5cbc3eddc5583ab8376f8d4004bb4": struct{}{}, - "809dfd482e188ccf15e8d7d08de0c5bc8baf7489a17a71c8592ae5a5731854f3": struct{}{}, - "6cb7c3f534e7ed68d9ba681b58643dee51f6cc765d50c04f2820421ae03c983f": struct{}{}, - "38e3607df174e5f398b0916cb3c0adbf4e4ee062992871f9c55e85f8513178e7": struct{}{}, - "8813d406421e0063ef8ec81fc45170338b4cfd9547ee1ab157b90b46bcb6e2a7": struct{}{}, - "f95cc9a3432cf59ced5c693f244ec5bfc81eba92795a406c0756697c9a60fbb2": struct{}{}, - "a44ba123189855990795e3260a64b34cdae6b29bf1c941818a34cba8bbc45575": struct{}{}, - "df2b7e245fea79d476536a0fda7e92c200dd86c65254753d9e4052a80e5616cc": struct{}{}, - "0782cb514029008de13d7e71aa1662c310b08d0d0abb29b3220466c0f3b08c1f": struct{}{}, - "ab3a903f4bc80a359f6b12c910b75f94eb134b4cad6c805ef7fac014ccd1a2bf": struct{}{}, - "74dcfbc208bb6aa08c90fb05bda0f2bc53285713e89611dfdd97ae129b5f6195": struct{}{}, - "46fabd563503609959149d705476ec44af96794ba3d951eb70ed135f31699e02": struct{}{}, - "efe899c74558f20b08bbc19bf0228c0c25bddb7871d80bd34ac8b33c030b3698": struct{}{}, - "1f394c408136f18d77f78228d97eeda1c4ed134d2bcc8b4f925629c1f51b973d": struct{}{}, - "0fbe6f2d952a74b67f811ac2ebf0846b4c96884c6b17380e3be633c05cf8cceb": struct{}{}, - "251bd5d862e06788516d8610c67f7688c03d79315cbbadadc6f2f2d911748f23": struct{}{}, - "2a97516c354b68848cdbd8f54a226a0a55b21ed138e207ad6c5cbb9c00aa5aea": struct{}{}, - "aa2eeb54d5f1a063ac0e89b3b324ca9eadd3e5b7addf4f507e93912a2f3ef57b": struct{}{}, - "317b32c143692b9939c197f6a5df54f9698df9a4882fe8bf19608968662be4fa": struct{}{}, - "084f1cadd37c14b43f0170ede90a2a3d7f8165142536a9f5a10d94beda9fb6c9": struct{}{}, - "589f6feca8b16ba637fc8a8ea35eea5c224b27e0a65f306e19de14cb0398965a": struct{}{}, - "7bb317c349b19196fa267191d7f48c86a40b1329d862a27638da36c5136c38a8": struct{}{}, - "f44fa27fe300f86b7385babc3d127788dd77d561364e9d1b27c913b7f1eff396": struct{}{}, - "9b304b757bff1b09e2ce14a8d2b3223972e591523c0553250509e0ed8abfdf5c": struct{}{}, - "ae91d279770045247fb6fe6ac59bfef0bcdae686d2bd4f291b4d11805365407b": struct{}{}, - "88b1deea6a18cfd0d1df1f0048d0946332fe61d1adc2bacb25e5f6a774b56cc5": struct{}{}, - "88c38cdeca21c5fd56c246a8a4cb07876afd57c1591ee1ac347ffdcf3be07f47": struct{}{}, - "fbb16e8cdeeadd5ecb52b2f74f4e5d69cb8215096f198e41c6c46bb0dd1d534f": struct{}{}, - "d2718df7633386861dfd7103afc60258f7e846c2594ac1351e2017d6b2bd7068": struct{}{}, - "36757032e753a2902fd1449fcb8e854d0210ba2456787752cee47335e97027de": struct{}{}, - "2a6f9298e3430bc95913682186712932d89a2eaab04bbf2122c2e75a3b31ab21": struct{}{}, - "ed3c7bc96fed7441c591e20c2ceebaf1b7166df1d707c975d177246ad791bf9c": struct{}{}, - "f968b6a56647cf5b791f3c6d77073a8c385202034a85692417f328493fbe1c72": struct{}{}, - "b552e632666bbf6125e3109e28a4fecc08340d017e44109dadbf497a280b8f82": struct{}{}, - "c0892d9b2c10640d6d016f7c7f9ef57a5b9c9873d37b7cb6a1eefd8bc4dd2c53": struct{}{}, - "c629f93e48d3094d892315170c66b91c6098be8cfe98845a806d416c7e604a3e": struct{}{}, - "e8172d9cdbd45be38f3ae1a952ce2fdfa929032c4cca83c59ed356b286d3acee": struct{}{}, - "0c08d7b9b45c0753aed44e5395aedb56cd4856c02b7ffc409d4a24d3c729db3c": struct{}{}, - "1a0fde97da6cf414a218c0f406d5322d2337c5b707cb1c4758cd8887b6d688f3": struct{}{}, - "0ac40d976122b3fee3d9319ca58d77586ef4252394a443426f815b73ae5ec9bd": struct{}{}, - "e7eca62ed935b3c46f3a42c0a6f735ca5af99e16c664a3ba97ec6c621529d3c1": struct{}{}, - "0a0ecbb7a6ea8511818619dcd2a180a3efbb56da0684d015a1abebea996ff9d3": struct{}{}, - "9f79fd6cdc880e3914ac75b50d71222d29d4e5e8680bc14e18e5ef28c1981484": struct{}{}, - "7c942e7f59dcc59d131134a97d39754a428f829c5a6e318be269cce4571cd83c": struct{}{}, - "fc74a147ce4012736d3fdc3a045d145e7cda11651dab02454e0459da139c3ce2": struct{}{}, - "202bd009f816aa8a69e24c98a91ac0740899df316c88d06bad37f5db032de33f": struct{}{}, - "3b0e98ef7923166602d7a9f327782eea090454960cf8bfe1af3b9c8620cd5680": struct{}{}, - "4dd4f3835505cd95950fc062a9b9418958b2d820eb07ef5d545f7c99de091e3b": struct{}{}, - "849760fea0863a753ce531afa5196801689dd4300c46fdda2f249dc26f174158": struct{}{}, - "59e05cb70719a29ea770cbbdd53c84cb5edaa928d69e3e9b56cfdde3d32ffbab": struct{}{}, - "0df55addf230c0040da973a7a30da952d9c107bb055314a7d3bdba3335ec099d": struct{}{}, - "e85940bbb038c2c50b283e54e6a74160cdb7d596b82bc1dfe2d00e81d2461319": struct{}{}, - "b165caf5932074318ea129f4ec3cc1e4a63ce84f582e3a80e49af69015fd7a5c": struct{}{}, - "57eb5aedb42b638bbf6c7c7fc40416a238ab7edb082ac90ae8b30fa95a96c33b": struct{}{}, - "4361cc01e665e9d5dfa0bbd56f0ec8272a35dd9289b868d8490d606997ad1567": struct{}{}, - "6ef23b06d3851b08c8d7f1c24e410749140abfbdbbfc82c18d0055133fc57fa6": struct{}{}, - "850a8cd2f456ce15daac01ae00585e6da35f4da62fe6cbe5b67b2d19643141a9": struct{}{}, - "800ad6207fe609eac8f8522fc0e0ab414f70e863fd95cc831116a4d83690acbe": struct{}{}, - "b846e9cb7e32db748183c2216e737ebe83dad0ee4f50408f3439438ef49d6885": struct{}{}, - "d4ef0c1431e0c1d35909a8e83d10903023a7d85fe83d148355165f9e5778a808": struct{}{}, - "ab40406c1454f6f7d27da42e51c1820a757ce26c7d3404e1ad16bf983a4a12dc": struct{}{}, - "ac0d51a6ddcd7fd9b005036c58fd99ce5f83ac59219c75648bcbb0375a727025": struct{}{}, - "f796e2f28ae5811737ccb8233f34e09f8bb75d2511a135543d1ca37be0199a1d": struct{}{}, - "ad2015263f339bff96818238148b790bd02be561472f4ea7db44498caa0b6558": struct{}{}, - "071a36ed1084aedc35ce6587bc0183bc24fa16cb1c288e6feb24722f6682dcd0": struct{}{}, - "eabfab6a81a2324f17e5bfcfd6fa29dd941830224cb1327ffb3b0fac429d885f": struct{}{}, - "d04baed6caa39c30437152ad7397ba610259357c44130915f141501479d99dca": struct{}{}, - "9390ef32addf32bfdea786bc1e20679592498068bcbfcd357ea10ab64ea35e47": struct{}{}, - "a00fd910545c20dcc3c4c6cb07d5b55105101b2c2b75b7765fc256068fba3c09": struct{}{}, - "b315f7c418b68b3ae262c96ef163d2e56d20358d3fef4dbe5c9fb6dfd4ce336a": struct{}{}, - "9078e43e365a0d2849587c33e1623ccdbd92ad1ea81c5762414e9fbee6f20c03": struct{}{}, - "3037f596f81898d23ef25cd12cf0a000a71aaf740a230ba5af39f2a5c3744011": struct{}{}, - "3885c9933d6ffbe3cf724d8227c2144637df182940cf663da3cf4f8cf265adf9": struct{}{}, - "fa6da9bceb6df00c5c6afd724a889d7f5775b9ee09c51ee468b44ecb37896a32": struct{}{}, - "687fcfab7fcfa1c837c2d737f677a47e37afa3772f6537751d9a7262f7e2a754": struct{}{}, - "987cd5ad67a400691a09a48fb6cd2989a0dd77aba094d5000dd16da930fe3cdf": struct{}{}, - "6b94d7792c9f4657ff8988debeae79e4db86ab7bc0b857063700351f1a398087": struct{}{}, - "67b55d55dddfbbeb5d8e1ae4dac71dea37b3e67e1639e56b8046c2fa6543e1c6": struct{}{}, - "0dcb8a74b608c22a862606a340c3080e7888c45941543ea6c5a9e73e88ca3572": struct{}{}, - "5c599616ac500daae9e6b0bc875e22ea609a6f8f1170cbf14187e1a9f270d422": struct{}{}, - "20e8cdb5b4093e18eb565f0a0c148e44da1f0b8c5447f27c47881675bfa86c10": struct{}{}, - "754eb644cac7a905defa2b3da7e94f4a2535fdd1dee3d371e9828915b31d4781": struct{}{}, - "8369175262bd0551e0a50663319db8c986777735c3f976122546b4848b34c294": struct{}{}, - "3e0b8bd843e63a4aa4e66c5433fc2db959b362734e7efb308708296870bbf7c9": struct{}{}, - "c780513e13bc9d2137eb766e9e1c912050fdf3d1f42095db5f5cbc038f522c90": struct{}{}, - "98d2f76be3876ad8b39bf8a5cb111bd23b8684b6b008d91900fe55faf7035af6": struct{}{}, - "67c888af8ad80f0232832431fb0bbb478f12740ff8b451d8d4ce0238a2d8b63a": struct{}{}, - "a6c673d0c920f74666501877aa267008d6e41ecc3d8b6b5cd2655d339732ea23": struct{}{}, - "9f8f85d036c72c70e596958eac400cd84d81a4f51d38073e3a8c5a1c216c82b6": struct{}{}, - "97429efd1864b8bd784cb85987753462465a133b8cbde0404b3b408c6429ce02": struct{}{}, - "31fb020df6bbc8f59d332ee1acbfd316772c0f1f3aab103c8d05fe49e7882c2c": struct{}{}, - "d0c6b8c989dc262ab47f768d0e2e1a744be3f347cc3441685dbe79b0c4ffe2ec": struct{}{}, - "10c932ece8048a7386160bf1463a4db09c26613801369bd5d1d00a7ed6c001dd": struct{}{}, - "27b2c10cd2421b24c00363111ec7fb635abe73b7bf2dabb93255835f0a2ddbab": struct{}{}, - "bac816c9cb0330a07e170bc92c9b322563d4dbd1a2ba659d73226c074bcaa996": struct{}{}, - "4ffc0a1145b5a2c7b12a81844b5989acab01b6bef6139ce874b38c72a9cf520f": struct{}{}, - "a575b79868a2336db378ac89cb6c7d93272b4bb8e74d1096e8630765cf480ef5": struct{}{}, - "a33805cfe8452895be97edd6552029f20be635f67944c9c8978004f84b502649": struct{}{}, - "c5b5c43c0cf0ab0243f7c1334f3e11d07ed59a62587d2b94189f9c55f9a6983d": struct{}{}, - "7e27f3f14e6b7c3500b5fd65627d9f3eadcd7b283b23b3bd994e3f9c70895711": struct{}{}, - "5f0d494e65fa5539168eb81e06ea012e70a92c5adab39d0197c2442d39b3eded": struct{}{}, - "eb172d9dba811d29fdf3c2bd694c6b5fbac12a25a1eb5d4e6ba67aea875e8b92": struct{}{}, - "79891e980747ffbd21e690297394efe764fa56d7e37750f800879fbb2d34571a": struct{}{}, - "217974f51a9cde7e2d8fcf0ebfa31ccba6d1d07992d0697f62cff338075677e0": struct{}{}, - "71f780a111c4a0c1e744c31a1ae05299a3bd32467dd60caddf4a961ff793d181": struct{}{}, - "c1b54e8ce3e495850160bb877d72d30a513231425ef2f4752114912e20b7a97d": struct{}{}, - "73bd4486e69489d3bf9c723cd0cad950d82cfd5b0e9a1070be934c2e11f2e685": struct{}{}, - "bbbf7a6412d6d3e8244ac1fda5e35a20037acee661288cb95b7b18cf469980aa": struct{}{}, - "44de9b7b036b9b8d28f364fa364b76b7af64d9e0b9efe17d7536033772a04871": struct{}{}, - "ca6b23df9fe4e527f9fcf325ed75ae92891ab54475e4f818dbca3be78a546d3a": struct{}{}, - "cd0299854a830cfdbcfb7d6887ddb636c09496099c873f6ce5496aec9c3f044a": struct{}{}, - "4cc3f3b3897d1ed8511251ae70b84b95d40da6970bf201a397c33a4ce0268afd": struct{}{}, - "7c5232c54cf5dce6103482a7434cb317aad0880eb44115b9d43898cb164ab0b7": struct{}{}, - "b2ef6b273ee0b510734ac57df2636f72c780ccb5a13266b604b56b5425a7cb33": struct{}{}, - "2499e63cb269d5144487b33690a57f09ae790b5533b576e7fd6c531d9c08cb73": struct{}{}, - "b18b8dc45aae0245746cb2a898a52a5173a7b2f1219d466da71d22dc7703d73a": struct{}{}, - "90a6171f1109a746aa8888c39ef468075502de106a34d0679e86cf2c43e2e465": struct{}{}, - "23635671b14ff3f69e01d9f32f74ae309de09a953f6b49385b3a81b3239237db": struct{}{}, - "533d0cdae18c5df1c0bd6e1e6d7ca95b183d9017738b50486c64691608b3af33": struct{}{}, - "cedecc04410b09449796069aab69802e94021cc75458a096915e5305ec1e8479": struct{}{}, - "9c9b13446cd96efb130cde2bed0fcd1bb8c313459ca991608636ab202ed425c7": struct{}{}, - "b738968c0dd57384b2eadc27c7564e6011b775255f7b0b3a089831ce5902df69": struct{}{}, - "4f69dd532490f3cf6d4013bbe9f98c7da74fa7376a203e8135069db19135fd24": struct{}{}, - "5c17109d20746ad75bc1bb9d345b114adc6dca927bd8ed3980c2aa8b1f37d944": struct{}{}, - "e12bba8d5c4a1ad3213381926a06c6d406b24900c4aca2c7fbfe9a8ddf12a3e8": struct{}{}, - "beb9149b4874db1214d038deb8bb4314e3c0e9312aa39880a15fcfba1ff7fc70": struct{}{}, - "8a64ff09886cc35fe5f56cd45a1f87251bca00056ff939ddd81b2f8d93f1cdd4": struct{}{}, - "8fa67f555030ddc7b714e010692997ed8afd538d9d2282ba4a4c0bc5cea0705d": struct{}{}, - "aa33996d60e89311b4d1a920dae03c6d7fa3ae1956c52662e273aad4683e577f": struct{}{}, - "fa3cfb3f1bb823aa9501f88f1f95f732ee6fef2c3a48be7f1d38037b216a549f": struct{}{}, - "237320d509717dc3f0d6bdcd5e8dc8f88f8fd94b06c728c1aaf94118ed34af38": struct{}{}, - "d93e854302620b3c7e40d11a22160f10e38ae708b0dbbbff546cb38e294adce2": struct{}{}, - "0a92efb1b91ac02c858ab205fbb6baf44d67e8d1e625600a11020cfae50065da": struct{}{}, - "823c60769e2bb2b386dfd0bb31dea6c63a7b1d2b4fcbeff32ed6279acdd9cc6c": struct{}{}, - "7572326b43b6fa45a62ebe7d7c3868ba8cc5a980aca604e1a2f42c17114ce7c0": struct{}{}, - "99c2bb3e7c93dd54a206c77388ee09708e39db1a6f544ff6887612495bce3920": struct{}{}, - "125ec1a292b92eed0b53aa47aeb7a086fdb8985b89fc887eaed79c7c177ae784": struct{}{}, - "571543876c684a8b50a6980db3ae8f0336d573981fcf6c48759648e03d0c3092": struct{}{}, - "96ad073f2b7ad270600d7ac91f4b9f5d8b416ad71d3cb0f51cff58c5ed32efbb": struct{}{}, - "69e36568cd8b4659389b9fcfbe1167547f6421e78168bfb439dada2ffddb0146": struct{}{}, - "e2c3dd9e4abec6ebf6b88ce5fd18926a355f06d8b4684c50ab26034af641e755": struct{}{}, - "d8e60ebbbb2b1a9d5f0dd2cfc7e810688583ad8afb626fcc6c357f756e799530": struct{}{}, - "fbd8dafe1f79f47371dd79d334d9a6c1aaab28c14b9533417462629d576639f3": struct{}{}, - "6247b537b5e554f6cd13b0afd338dd4f51d596ead330a21e0a3b73b6b6e48944": struct{}{}, - "f75a2137075cf634bdd5dcfeb799767749683b6ca320ee67bea315864646a03a": struct{}{}, - "86f426b91150406161f2781857fa82495bc433d32659d536a26b021cc31ab503": struct{}{}, - "c20c769db9e1d1118841c9f453800d03dad2d9854e2ba589b4e0fb6c0f42f886": struct{}{}, - "51a3adb0501259de14cb655de17f8bfe8893672446f0521e4b4878beebd444f0": struct{}{}, - "571ceddcae178dcc2cbe40c51342a0853f819b426c334606a1a555f919d1a7b2": struct{}{}, - "29d807300a961b0cbdb4f22cc5db866a43d20d87b1e17b7f50c91bab25089940": struct{}{}, - "8deabb7096168b8941814ec432b017c880705933870e221f04531bed2e53e675": struct{}{}, - "3fd725a89717d2175289be8011e2b983ab6428bc0064f788f1a9d6eb014c2253": struct{}{}, - "ce23b71ee8b2a7fc0ddf09fb3865546ff47f6241554ecbe4ab9d95550be9ec30": struct{}{}, - "3fd6b90411449ec718b1fa929c1ba72c52d89f2540c8b3a56ca9cb95fb63d00d": struct{}{}, - "3201e9a0597268d9aa4b0a90c1c47024b4921c2be57518761f0d1cfa03d0d3c4": struct{}{}, - "5db6c5772473e0b01be451fe33c85f128fd1e6ab33c9519a13b177b0a2fd8d54": struct{}{}, - "9a7ec836ce6dbced6836f0a6a8f49bd454d067101fd33dc14cc06c9f321d0ba6": struct{}{}, - "8ebdadcc159d8d64bbfc9cee1ca0f980bd9f8092cb73225b33533ba3a49fe663": struct{}{}, - "8dbb1fe7268af062c8355080af9ebb362c24be6c68b78290cb04fba6def7e0a4": struct{}{}, - "6b91a64d33b80830e8a2c803ee43ee171f45f9e0312b200d1235fc9836b30544": struct{}{}, - "9010072f85ea546925888515cbacd8cf44204c2a640bc4d60ac61ff65ecb73e7": struct{}{}, - "96ae70b8d33e99ddfc296056cfa24e900f17e5b355ea04c2c1ed889c6b412454": struct{}{}, - "305238273bb0fac2a73b43256e38d1515fdf61ec1d30ef161446c3588b93a97b": struct{}{}, - "7da212ed087eb1c41b55d4a17f84a819a4b9b596ddec1ef382cb598e641830ba": struct{}{}, - "b77207f916d465247e21f5d0936948d34f03d40448bbcf45a6292382632b2c99": struct{}{}, - "a5961f3f5ac8a594d1c084d5948a7fa0b44d181b67ede0973c6641216ec06920": struct{}{}, - "5f6630178c787427ddebe6e488925f4ef3521647be7709af4a56405938b78143": struct{}{}, - "52bb7cc738dbf5e5eee2c76754d87904e6750c63eda5e0a866197d2e16d9c142": struct{}{}, - "d14ef745bdd7e560072f513c2fe083e31032702a5f8625af002a036626bf9be4": struct{}{}, - "bbe8e9531e011540d71bdd2ba9addd73edf7e56313c446d5cf014f09e4e42eb1": struct{}{}, - "9cfa8d8f56cdee05c98db6884d505cb28bb96329e4a027096c3fe8222033b1af": struct{}{}, - "ef83ad02ddf4d480a68a1f57062ee88c6cdef2ea717013fd6b787424ec72e482": struct{}{}, - "2fca7af39df396e0890de73cf93628cc50261c1c5bff980995ce740279a740f9": struct{}{}, - "5bd6502ccf37fa3f7db88b21c52ae7a43295ceabaf7292cb7d6758feb38f62d1": struct{}{}, - "589307cc0675b8fdf6549ccb0b9332748c522a110dc4d684d36bd174ada1740f": struct{}{}, - "526f9816ffcea84585ef76c1c7cce429f3f4fc1be2a6ab0fe7db806d1a0fca5d": struct{}{}, - "0b1880f1fbf3015ac6b7c07bac15028c57a5c79dcb57ead568dbd07921da13c3": struct{}{}, - "46248ac689828800502186d8753cc5717c5c2b47712e8158705a510dc892f00b": struct{}{}, - "a2878fa1780ef40101891b4f698b509cc8937ee93675e50e4107e5ceae34a65b": struct{}{}, - "3121771b9c5973e0f089fc009419975232f6f374f1f95a118e8259c98dd5ded4": struct{}{}, - "1eb79602411ef02cf6fe117897015fff89f80face4eccd50425c45149b148408": struct{}{}, - "03688820a0300a8a71d130bb77f0b8d58fefb1d97a1024ad27ab9bd60cf34858": struct{}{}, - "89490cd8a5ad9eb4fb480e37795b37f50926cd7d8c0201968f98b6a0b928b5b5": struct{}{}, - "0fae56d5786cade88b348cf55a9e4a217406ff5359e517cddf9fea2bb686ea6f": struct{}{}, - "d3aeadbcaafc890ebe45319b039afb23178da02039652a3f6d91b29cc0ecd07a": struct{}{}, - "befb90274c7db469f79d9d4b4d54788127121dc5d457282099226adc743f2d92": struct{}{}, - "607f125fc57bc1608047a1522c77ed178d151e2595e3052e43081511224db871": struct{}{}, - "16b974583155fdcb11fb6832a37be09c18c7acecbfb352c25a35512bfd755a33": struct{}{}, - "2ecefe59ab6ae4e734bb1adec3bbb706be8c6fae0b39500d05f7a6665ffa7390": struct{}{}, - "01431b46f1839c1e618e12e888517c14111dc35c9308d59efcb15eed5c2f178d": struct{}{}, - "dcd3daf2e9ac9afdcdd81d29a2549e554dbd7d938673fa21ebbf52a5f8784ab2": struct{}{}, - "2e8db3aceb4b0eb09d42bd545be707ece82981a09e728aa4616d4bdd0e3e11cc": struct{}{}, - "c2b5fe7f9da62b8061a0e7d42ff491e5fc963082d9ef09567be50fa89fa9e34e": struct{}{}, - "65ef0c20629fb89b64e88bf2134c9a4f675b7c8d66cc34f8a49bc38f8a5d10c6": struct{}{}, - "b46bcc668e54b0de157bb802967a269b5372c6d29174fc74915fca3afcb15ed4": struct{}{}, - "ff2a8a3987ef4645f85dd41ccf34338ef0044fcc1a5a6ae11887aa305ac7a997": struct{}{}, - "3b8e2189ec98b9791a6b8df2558fc00017d06974c5d6899de5589111b1b73995": struct{}{}, - "af1e6fb28225e83c948c3308c8a602db27dca155b09d698e1c0652cd688e1f2b": struct{}{}, - "f811eb5c56eeb90a48b9dfc01b860424a8c7b4bd349119d07a8969dd7811b127": struct{}{}, - "a74257354942f6295dc1342e29f825aef893bb2d9b58e3f1f891fda9154e6165": struct{}{}, - "300908b9d3815e2d3ba5f5534fda3bf44c8d3baaed7e7ca55fdf83a6ecad5854": struct{}{}, - "3d9da0471c648d3cc834e068ae09d5043a70ea06795b50f478f0a8fa84e75a31": struct{}{}, - "79737ac46dad121166483e084a0727e5d6769fb47fa9b0b627eba4107e696078": struct{}{}, - "b83c588da0c6931625f42e0948054a3ade722bfd02c27816305742ed7390ac6c": struct{}{}, - "44b1237a067c3a8675ca9d24247e270d7861ed8f20120915877866bde7069f09": struct{}{}, - "ebe81467cfaebe55b4fa59ab750aae06bd3ca8e6d6b0f5d1118289b9e918a2e7": struct{}{}, - "b683c639a01a77189743e42b7bc64d351db16cc28ba478438755ceaca89d3d30": struct{}{}, - "0f82fae1f33a768274a3b1c7a036abb56aa229c2dc32d585995fd97fff8e73bd": struct{}{}, - "08a841e996781e9e77d30a4e4420a8f501a280b00624e6d1224bf54aaff73eba": struct{}{}, - "846f6a76ffc111552f1c9ca3a06d989d0c9c9b79c4fc25ff67f6207be512955c": struct{}{}, - "db1cde77f8e68aa51ce22df926dafc7a4672e22cad388541639ac6ad8fe8b6d8": struct{}{}, - "dbe3f81d60da497f396c77f1e9008d35164931de5d7c72a130c2dc2054c1d113": struct{}{}, - "a0406c6138f1456b16e900f72a54892ff40132663ced7694a63c20d98ead8141": struct{}{}, - "be26ede0c0ecebc5f04de14d9fe7e0d0d46fa703777f937a1faabfc750ce7c58": struct{}{}, - "333fd8874248459f54c93690b8a8563f85d5df3ad354931774e8be15eb5e862a": struct{}{}, - "8ac140ceb6ca8d6e51a987a9828b9f97b95bbc3ae6bdb0558e2631cb8da232b8": struct{}{}, - "fb54e9062429a93785559529beda15c55f62c29be22267811c0e8346c14846d3": struct{}{}, - "78eb0bb7d5c01cfc1f63de12698e4468141af4f472da18ae1c292da186d58655": struct{}{}, - "8f8472a2f6ec348bfc7577a035c7f34a04c62f0c757b54687e1175236dcf393b": struct{}{}, - "483029d526219f816e8e8f6a9de07b422633dba180ffc26faac22862a017519f": struct{}{}, - "8f4375ec65c2c4092a1cc21e76712472eae0de999384e8327c495957121db9c2": struct{}{}, - "ac811aa0c42fd2e1465d0a6313c0466c97d1e9c947e3f110ce965095acc4162f": struct{}{}, - "607bb9dc4e64e0267900f0efdb2a62507f901eac8ecb4592b61ec282621e45e5": struct{}{}, - "787188ffa5cca48212ed291e62cb03e11c1f8279df07feb1d2b0e02e0e4aa9e4": struct{}{}, - "590433118f455fdaabb569fb1c948bc78e224765a4b5a995ee2619c27f39797d": struct{}{}, - "e6b88623f7209b6c11fc49805c68752941074f4bc5eddd8c33bcdffc5419420d": struct{}{}, - "02cf1cce1b9414df96996756be90dc9f025acfd806a060a07ae5bcf5b133fe12": struct{}{}, - "2103677d9ceccaa37ee15e51b9efdc49f623e1f040367b56c7b49887bba69107": struct{}{}, - "e11d8cb94b54e0a2fd0e780f93dd51837fd39bf0c9b86f21e760d02a8550ddf7": struct{}{}, - "be5220c4102d65865bde1ef9740d3d2093a02192e5773c56f23e27d917f17958": struct{}{}, - "65afad3ac0021e33f71a8e3e5a3b7101c2b010f4abe075b951b30df4a13c0dcc": struct{}{}, - "8f8e24f65c57e9d506fe81adfca35ef329983d6e33af935dd388a03c579613de": struct{}{}, - "c4406297b9a7350897ef036ab15b997ed02843637533e4d4f42b8eb1233371d3": struct{}{}, - "38de90475bb334fb3dea5d54f250500aba60fe2c6158115d342b06bcb46e39bf": struct{}{}, - "6f2b5fc6f6019005f3c3cc0d9f1e8bf7e2190c4b1dd5fa47a6780d164a0747ea": struct{}{}, - "4e388ab32b10dc8dbc7e28144f552830adc74787c1e2c0824032078a79f227fb": struct{}{}, - "251fecd592e56e6c4a95121752f4a73bb802dffc1cc4bce6100206ee2b7cb650": struct{}{}, - "a2242ead55c94c3deb7cf2340bfef9d5bcaca22dfe66e646745ee4371c633fc8": struct{}{}, - "14069429150abcbf244c4c2fb2c4c46a49c93ca9457263ecd9ebb176de3a58bb": struct{}{}, - "a7c83bb7adfecc5f03de15f205f4b11df596eca6d8fe7e9e06595da55c459102": struct{}{}, - "a6761ccff1191f3ee53acada4f7965241538511ef6eb52d37974507ab5a9023e": struct{}{}, - "d9dee27071b09e35ef8dcc54bd1c7c6cdd4b988e77bf362f6306ae2a0f560c50": struct{}{}, - "a82ce870f999b01b17b9135b72d1f4b6d31217aec9cbee69d5bfc9659018ca06": struct{}{}, - "f00f2e7bca65e9f8409fdb3bcddfa031664224255d7bd2f6b3de8ff11ababe20": struct{}{}, - "1f29f2d29f02f2608eb72d45625ba3a851eda1ee2be1bda22427a584b787c722": struct{}{}, - "90e62b92a956add6e1c3871b7f592616665e610f12c6468eaad9533ef50834bc": struct{}{}, - "8e66a7a836a8de2c49d566a63b42e835a7456f5af99af29a8b773654ca8f5b35": struct{}{}, - "17d963d800866585b1b68f03daede3f197f4e84124329b139d2d8997ae356348": struct{}{}, - "5da6a98ce54ca941f343a07ec10fa1b9001c2e518e1a7a5f65e572b60a2175af": struct{}{}, - "87e1315329cf22c9ce1432d1626cbbb1f8e2798faf0494040d7c4616ba9ea854": struct{}{}, - "add1743eae5bbd8ea17b3d3b3531d0f3de072d10599edc0d8e107af90e52245a": struct{}{}, - "9753835fd39764f254097edb985bedbeae28bb91bd9c3e5dafd06d2f3041a081": struct{}{}, - "bd129e2f86fb0e20588d31322d84b99ad7af68c3087a063d94f25e69b656fa7e": struct{}{}, - "6955b2914394a4d173715e0c6fd3922cb2e4397463351d60c40eac27882fac0c": struct{}{}, - "f8638b979b2f4f793ddb6dbd197e0ee25a7a6ea32b0ae22f5e3c5d119d839e75": struct{}{}, - "67ae969a9b23e9b165c1c8be661cce59346afb18ff32bbbd0bf68761a9b0ab72": struct{}{}, - "8e4002280dafe2856db1267e2cee2035abe3b0c961c0157982c7d4a7b69d3722": struct{}{}, - "4215f461cb8cf26fa9b2aa90fba3bc84ccd5d784ad38bce78a5d0a4b58df7d0d": struct{}{}, - "a57c4e7933ced6b6d2275544e393a4ab6ec2946f86eb585091539e6a512df3fa": struct{}{}, - "f0be1f30623191572183c86019ea1bdd904721014f9935884fbf1334b1bcdff4": struct{}{}, - "b043864b7ffb308f63b438a86e60c6d231aae29e020be80c944801e065229e15": struct{}{}, - "9aa43b23f22d9e87b80ccdcdfd5a8a2dcd154b2dc43de96babc41149764bd36d": struct{}{}, - "43f5c361f95e270c432a3b2933172e78b1c90020eab20f6239dad8588d25dad6": struct{}{}, - "a2afeec49a4a4c3d4cbc8c4bb60fdf30c54f575c3b3dfa565f59c57028da8568": struct{}{}, - "ed05dd4d068fc00dd22381f9261bf6a8598937b134adb1b29befe90b49a82e03": struct{}{}, - "e473b9cc7a35a203853a785dac017b4c81366433476a8ca96eabd870c9d8e94a": struct{}{}, - "fcc8f1a7e291a1904e5a39debf3a8d41b6fcb9a13cf3bfd9bd60a43adfbd4fe6": struct{}{}, - "357ea13433b9a752f96814074849f6e58ae3183f9ced80f3250b57f125f7478c": struct{}{}, - "97ff4beaaeac2cb69b38a6923512e954211d82d65fde8c303eee8ec6fbe8423f": struct{}{}, - "e6597c5c78bb2bfaf8f810bccf29cf1720cca73fd24ea19107200d59e4bd0a6f": struct{}{}, - "ffbfabc54ca8a26ef654e1dfecce9802d96326869199f4b1dbac0adb1fe7de06": struct{}{}, - "f1f5175cac7219d5274210b1b36acd6e2693a84fe41be92d5810cca2dd7104ff": struct{}{}, - "befede35d5bf88da6e7f44a8377497aaaf4b0af2b2fd21e28b404136b63267af": struct{}{}, - "d5ec874bab35e552238f40506b5357dc2679fa9ea221498952c8f666431c8111": struct{}{}, - "695a39bb308fa7bcec843c6ff4f7a6b7f44cff2db8711451f08cbb6f26856567": struct{}{}, - "2b82ba8aab66f192f5977551a98666d1d285cf3e4b081da0ec4714c29486ac9e": struct{}{}, - "049da3907b7944180337a37b58dd4dd6970ae1d6cc610b1b4d92caaa2a6f18b3": struct{}{}, - "0cafc5b5fe1e9db1e2311d3b9664f0b2d32c4a84d54b53f28b39e3addf61d310": struct{}{}, - "027434cce1114811be52fa56af57a6550bda1c7777be20f4e51f4a6952574c72": struct{}{}, - "67671a2f53dd910a8b35840edb6a0a1e751ae5532178ca7f025b823eee317992": struct{}{}, - "fa0422d5025ecfa96b9f58a222dc7422f2bdfa184e0880954e28563974e393a6": struct{}{}, - "5de475c54f292d357b4665c4a06673354d0af583abec2ac51b752fdf06fcdbbd": struct{}{}, - "52c279ad597187db0cdc6246fd652bfd0ad9b299bfae17faca29906fe3523a6a": struct{}{}, - "8c981f6306a145c823762db9bda42f78b36d6e0b10d51097922c8b0235aa1911": struct{}{}, - "060672b8531404f598515957df33d6387e0647cbc382d35ef286fa3466362384": struct{}{}, - "3510a92fc7dd04f69b1aeeef5489ef168dbf1d9c4d528b8f4af4240a8b295ab4": struct{}{}, - "332e59d1b415d4ff66902f683fef92b43932595e64a520e18e17c00b04c279ca": struct{}{}, - "68e7a2f3421df4ba0c77eb5bab41d97d540fb6c12292f08acd0cdfade0e7adc2": struct{}{}, - "3bb78535cc9555ff19fe3556aaa41c78a0a45c64d49ba2bc564507648a8e77a1": struct{}{}, - "fe1f22537f50d81b6371a0c7b9197b73f9595373d28d1a54c55a9023fde0f408": struct{}{}, - "07c05679b1cfed895de0d8383a02cafb7a040d5db41878fa2c47103fe7aba541": struct{}{}, - "c8a44b10e153fc3a2997ae56a1bb53067ac868d9b1f6663fa0e294bab2393682": struct{}{}, - "632e93023886160a2a5494cd49aeb72994fc61f6834355d175a423b99715a9df": struct{}{}, - "8a5ed07148aeb38f3a2f6ae89bdc66d1959886b98f134d4a5dd3a4f7f7454da5": struct{}{}, - "b159594e0556278919980ad53676a2c96e482e4e7a3b99724c2a3d375e579c3a": struct{}{}, - "86e1755eb25249e474a19f58f6b825ceb1eaf8dbe160089013b208d5579e885a": struct{}{}, - "bc5d416e8df605dfe9c24ca029bb35e95f183d1fd5c0dd5a59e8e418974e80d1": struct{}{}, - "d9ed988571ebe2fe382682edf99f1c954b0f4a65616dfeef166c1a4f0ea63abc": struct{}{}, - "adef63093d51be86b28dfb1a7a53d66672757f1325ee5de0819a46b4acefc20a": struct{}{}, - "470f75bd5316d090729657b103728c14ed6b003d5be5eb1beefd29e485e8e968": struct{}{}, - "13d249f2cb4127b40cfa757866850278793f814ded3c587fe5889e889a7a9f6c": struct{}{}, - "f80105efab6d863fd8fc243d269094469e2277e8f12e5a0a9f401e88494f7b4b": struct{}{}, - "d93244e7b08131ef10eda16a0717627450cf0e51d1ca713c41d301a51b9a6e54": struct{}{}, - "3b6f68916865acbb2536797944ee5d1a572306de2fbabab6b0a3e277b2b04738": struct{}{}, - "1af293a88816a01f6cb8dc62c63f58c03e9b017e3348cf703b6e0dea370036c3": struct{}{}, - "1372c63b35ea72a55bc5afac621482ea61a41dea630ba19583412545d4197641": struct{}{}, - "0d866ba9f9fd0f2cbb2134daf52356d2021a3686352d5c19d967305bf9e4bbdc": struct{}{}, - "4727010b0ff5d76ebd6b91b817d768470d9ee6978cacd4a0f434e7ebf1eb07c3": struct{}{}, - "305264b8799855a71e3e0344f3c66b95998be4ff044594b29c50a96ce9e0c8e7": struct{}{}, - "de4fe5754815aa2c42891d328886fc4a0c478b9e9aea1794254838c99665be66": struct{}{}, - "68a46bb53e153b61e4eff5403024b792063e6beb27e0a0ba824d29901a99a0ce": struct{}{}, - "28d79d50ff515a797803c4d5297b8feb12e0bbdb60fd38441aba6ffaf42a0b5f": struct{}{}, - "f59ac0828b9a32293b348e398a0efd342b1e4377a687f3a9055ee2871dff35e4": struct{}{}, - "cd71fa4239dac4232ed0f85d682be555798551f732d1cf655f65f86884121fe4": struct{}{}, - "47f6ac508035dd58c289538e103b911da012b06cdf9612e6a104bd2031c2ed67": struct{}{}, - "030923893f54c3d04b0bc141bad644e6c501ec1257339e1e66dc02a1618d4046": struct{}{}, - "a6bb8dbfa39c18865a9604b60d260babc06ec65e7b495c569c0cb056c7a58eb4": struct{}{}, - "9200f647267d36cd5ec7fc2c747aa3d3130ffe37195e229a96ca09a0beda23c4": struct{}{}, - "a54787c09f5511a2431550d8e39a2c267a2518ca1c59172d1ab90ce907275713": struct{}{}, - "982945308d3682d16636fd628c314e293499e99c00120acd9b693f5ab16e1648": struct{}{}, - "6e1f3da6cf8847b5c2ced23d0fa63272e6cd630dabe13b73f1d2666b0cb1444d": struct{}{}, - "3513d9452af7c1768eeba7e47bdd0920aeb18360dc15a1a7759fc0f0e4f3ca02": struct{}{}, - "2d0c7f73540de525665793bb7a8c970ecaaf4c8f9a08920327819803648e4006": struct{}{}, - "93c6b09e0fc292a99a2d60d862f484b66c8709f78fd3a30ceaec6422a2197824": struct{}{}, - "389564f915ac5f7b98bb0257665a88791e79dd8a712b5261d7568c4d85894bf3": struct{}{}, - "c737e8142a927072977437977d3846acf36f5057d167cf45ea95c7423ed27a0f": struct{}{}, - "30962bab156745e04ce95daf4a29a59529e3f9e64570fe791e23baa2df85da3e": struct{}{}, - "5fa896b2fbacdfc6676d6e4f53028a9cc4b215ba7bfa9f341a4b5029e97d711f": struct{}{}, - "b5c1fb2efc6d6b4674c2fdcc48ce01b43a3b7c03763c0c3355de0099ee0f8c73": struct{}{}, - "0f27d53a9e09c7a9a80e6402665e9c2dba9a7a51e64cdfe929c61213e3969fe6": struct{}{}, - "8d38ab757b01814047517899dcfc2b8501bfadcb5c6b66a2839fa9788658fa9b": struct{}{}, - "cbc5449c66f96806007ea297624da1d121f95481ac080a2e526e8f2125348dd6": struct{}{}, - "0985b889a1fe4f4e1fb925061ac6fb2247f10875f5fcbe63eec2ab55ed68970e": struct{}{}, - "d922b9cc939edb49c970bd71d4d92335c2130d56f69a8446c2c0c094f671562b": struct{}{}, - "73cce42f1f0e9099688b8eb6294f7a9963c937a8e47315ccb759f499ada648d9": struct{}{}, - "75117463d25de9722940c44ba307ae0a7a6f66c1ad07f1f08051f0c4665b0217": struct{}{}, - "3152be9705e161a0baf6f2eff8621afe3ffbe952b378406e55d7f28b73ac4627": struct{}{}, - "0178fa91fd0fe5bdd059ffecf865aed15fd1a94904f46f311c1047c21663fe2c": struct{}{}, - "3c4e4e3ce23cf17558e13edd1b633ff49ad9d9f08847b6b25f2332ba50f790dc": struct{}{}, - "dbdc5bd1887ff0b34183a253b6784b9c9d42832120be5a03ab993c39daa894af": struct{}{}, - "a1df6dffb5e4869e2f750c4121219699b9f6d748d16b7118494d1f7dcf1585e5": struct{}{}, - "3cb25825a619415e255ae83eb0cd9c26684de310fb3c377ac289362e043affe7": struct{}{}, - "8cac5c3feac4bd84d518ece194f2bbf165e901d81549c5877e646163768a7eae": struct{}{}, - "7c05112240adc48cc0db5f93ce9b9ddfd8a60d11fcc252e18835b0d23ae21405": struct{}{}, - "def3735d7a0d2696775d6d72f379e4536c4d9e3cd6367f27a0bcb7f40d4558fb": struct{}{}, - "9cbbdd6cbd5335f528ec9f858ee4ddd727186dca99e33f8fc3273d39681d54a8": struct{}{}, - "af5e48b47d822d9e12501315634c41727feac64d511622a9d1329144d33fe36c": struct{}{}, - "70acfc625492efec37c803423ddee34ae4560d7a31945c3117eb23e42a9654a3": struct{}{}, - "74f7fd1cf08b145c5859b9f402422440c0ddd4a459b584f23e51f2dbfe17380f": struct{}{}, - "b23b6d25324f518146005aea01fd7a7c64ec137532780572e3852f9d8e8556f4": struct{}{}, - "5eeae0a61904575a21a74d306ebc477e3b2198be04a207b73e2f6d3810f71ebf": struct{}{}, - "7bf555b2970ee1b61a495a2537bb565bc5d75c86839c89c552c1bd03d586bfe9": struct{}{}, - "6c98949c42d37d908f1131dac78cc009e27a0a32aa37eace34f07ace0d545cdf": struct{}{}, - "701667eff73a8289f9750f69c6aa675fda2e131b505486639a9bd8fd5121a1e7": struct{}{}, - "4c5f790f58d481ca86c51d7243682c6786a5011dc6f0cde0c6d01b3c9b422b01": struct{}{}, - "fe268b3259240bd47ff4b18e412cdb6d3e451e87f9e88dcfaf9c926d8e8b99a6": struct{}{}, - "6a64551c19303783af0f8a9d18e776cc24ecdccf602b631bd69130f6a8864114": struct{}{}, - "1d281b33068fbb45b5294466d44acbae05cd679f5947041c911d577d6d2bbd88": struct{}{}, - "9b4f1d0e11dcc029c3493d945e44ee077b68978466c0aab6d1ce453aac5f0384": struct{}{}, - "2ec42bc1f3e672fc1cf7fdcfc23246415dcf20f03c0d4a5bbbd92a185708f5b7": struct{}{}, - "ac9d5be2874a08ebbcc0df6c36331952d07173e42b31248cc232154002a0833a": struct{}{}, - "c7bceb196601ed9dba8a5915d494ef6ad7942f7b73872c241936ae161817f8a6": struct{}{}, - "e84dde508a606a3efca9e890fc31f60bcb07c796ebb94e1cfaff46bebeaeec8b": struct{}{}, - "34ca6bb1036d148d7b9ae5650fab821701ac8c8543226650d2ad6e86b0b8f8c0": struct{}{}, - "b1ed6d99814162a3aac906bc6019a7086339a39d8e474f372ce482943a09cc80": struct{}{}, - "0183a505fdbfdcb46141872dab34d8c39772f6037bda3e7304c5c87ae7aca4c1": struct{}{}, - "601f213c52aea26a02ed50b99b7d5bec4f0ba34fb60b0ffbc3311522b9f6032c": struct{}{}, - "36515a322efde414a1991048da44bc65623c8e1c31f8c30c652aeee05428c237": struct{}{}, - "7edb360f06acaef2cc80dba16cf563f199d347db4443da04da0c8173e3f9e4ed": struct{}{}, - "ae391de9c5171466f31ef5ac920cfd3901bcc01aa28c2b02dc13b1ab3d6f2063": struct{}{}, - "5911d0156c5d8e31d9c5414b5cbe145db00cb3aae7c65311dbf16229c63195f5": struct{}{}, - "030ad17f79e246d20db265cb6d4014c44a6070405c2ba7f735b7ef87a7f09dd0": struct{}{}, - "00f1169d141c596f3c4d918cda031fc0fe8077abaae478ef1e1baefe4441a597": struct{}{}, - "f2ff83860a4dc203988ed1a22ba1f21237f04abdbd0c4c951103cfbed121de78": struct{}{}, - "ab1e340a93819bcf362e922c1ea53102eea03b3906881b9834812c8f1cc8072d": struct{}{}, - "0d446e52b5c81d3ad8df03427308cbb79b65d4200c406cd96250c686827146af": struct{}{}, - "25b6746d5172ed6352966a013d93ac846e1110d5a25e8f183b5931f4688842a1": struct{}{}, - "69ec5bc4de1589f98b63970587332fcc0847b9fd31854c0043eb9db692ce834c": struct{}{}, - "ba0a5096cdb80573f8a07adf2617e526f7777d5a12f5cbeb65dcc02b2cc8a3fd": struct{}{}, - "6cf615d5bcaac778352a8f1f3360d23f02f34ec182e259897fd6ce485d7870d4": struct{}{}, - "8fa487cc5c1e37f4e92fa5c4dfc72d19f12bda8e30caf8b243a48f367ffcab63": struct{}{}, - "1a4620b9b4790cd3182466af7d10adb92ba9bea493be38d6450be11b4ad5094d": struct{}{}, - "227c42310d1b2220c59b39347f59e0348c28df0754cbeea077ce0161439d915d": struct{}{}, - "57c4991869b96345e62c3f9a886b2503ea942aa6e50c129f51c1e1a48d8430ea": struct{}{}, - "183e1b1c9f919196c4bd50a1c3798d06439b1ae8df688bf711b6195dc8238917": struct{}{}, - "425ed264c86619b9975b134ea3aeddc0f9618662aa247d1bc519b7d6925aa3dc": struct{}{}, - "c56a7bbdeb94e20dd9c1e075e7019489762f2598a29a78dc4619033a25e0ef96": struct{}{}, - "9085c2525ca01674052fc873981d2032297b81966475a6da075020b388bd0ab2": struct{}{}, - "5a5d4e918da240618b43c67df87e1cb4d9e702e674ac7a00db7ca45f28b06b08": struct{}{}, - "cdce572f18ac9fd89019a8f55fa8f1f64d29864c2f8a8771274a8fdf0db5b04c": struct{}{}, - "be1f4743f21488917c4fccfec99385eedf039b57b77deba78b80d5ea04bccb1e": struct{}{}, - "4ea7ea4917057a1fcbb3bffdb673602d9b961ff14b239cc7a8d96933b8a18b51": struct{}{}, - "0dfc55bc4222517a4fdde51524963f11e9778781d0c5e8920f57cac2ef10cd1d": struct{}{}, - "0478721f1106c2a631a90181bac7efc77767a3903eb9220687bff8a14e940fa7": struct{}{}, - "5ceaf53d465c4e57489ea3a188de3ad6629978871f12310dc8c75ecccd56a5cf": struct{}{}, - "b21b17b4ecc60cdbfa7b8dfd13b611a862f8c0a8416f0a0958356cffef8e26f2": struct{}{}, - "3df2151b4a7a502b64c48670c9b276cd1a5008f09c5864bb902ef38626e5ddba": struct{}{}, - "db905d0d33c8768fb394dcfc349e54272b59230168290f7b60cd2cf49dfebcaa": struct{}{}, - "8d3514d5506b831af3ad83ce0b2442e49b3d4318ba81ecf8ade73f7f8284b083": struct{}{}, - "2c249473aa501c43f154cc5bd2332c4c5ab5281cdc7c22ba049724882eda25f7": struct{}{}, - "3171d89ad00530ffa19a244f040e9401a657903cbbbca724996b90a56df2c189": struct{}{}, - "0b32f06a18e4aa617dbaf6449c04e24e37363441389b5341426f6f05d599f46e": struct{}{}, - "db139295cb55e9e4ad53ca4d0e9edb94a774878d2ae5c1e7f15f2c58a44a6d0c": struct{}{}, - "6f61f46c23a66607af759dcd348ac132846385d8f39534e69b6cd934bdabad8b": struct{}{}, - "bb8417f48f576d95ac72e44c74ea9f1c9527a344fd99afefb459584a367e90f8": struct{}{}, - "473287f8298dba7163a897908958f7c0eae733e25d2e027992ea2edc9bed2fa8": struct{}{}, - "0afa5bb5d5f1787945e555e91474675beb6f34dd9a490423c4d0460e4d454885": struct{}{}, - "a913d604cd8e4f4fbf9148a19455d28e0d577bbda90329f71cfe39f77106224d": struct{}{}, - "31266ac89c39db56992420cb0d4f53d912ac183e2c0f399d4389e60a5f88110d": struct{}{}, - "23bf0d244a4cb62c85fc3aa0e8ebf8eb7eb970522a51326adf1ac015fc738c7c": struct{}{}, - "62f5d588fd39c2bbb6b72efa9c5e95a317903f4b2906cb4599c109cf9c25e0f1": struct{}{}, - "14e32f3cbdd3b4c414be5f81c8c6b602bc9e814c0cad7290a07fe109ea9830a7": struct{}{}, - "49ead2b1066bda1e127f6ae0bd163778d08587e3e97d9ed58e8cc99972460a1c": struct{}{}, - "89454991fbdf6ce9919a525b9fd6ae2382987a5a313d719434dd4c5261c0e1b5": struct{}{}, - "72099cb2f0e789a71d57261a6c3d384ec6f119b4561d1169aebd1a3adbf11df4": struct{}{}, - "0fbccedd61528383fcf15a836bea22491d2af4064931de4231f46ea23e75bfcc": struct{}{}, - "186641861d6d9f2fc590cc66b89e9d8c8b99c3e63df2c7ebe81c96a322a51782": struct{}{}, - "ccd758e72a8a8cb5f140bab26837f363908550f2558ed86d229ec9016fed49b9": struct{}{}, - "1443b0215715d16d2edd70428eae588e2dc1b9a710f573e4f8b132f5a6e64fce": struct{}{}, - "4d588af13f84f9e5d3347f6620b88c1dcc6ed4cc10cf42d8f1a99cc090dd5d64": struct{}{}, - "a9e37cfc3566f08c2303889eea5f394f841373a97d37aad6ddd69b700e650b64": struct{}{}, - "f3d1701e1d575e1294786989517866986bc97343e07af63e201f46ba0be5806a": struct{}{}, - "f6cc5b085b61387ac2c125bd1289137d02bc58a07af9a766fa0de45e0e5066f8": struct{}{}, - "5a871fa86152de7715c59badf2eb23e18035c1e6ceca15153d468a2c5d6c3055": struct{}{}, - "06d92f17a33b28585d90927b2226c4bd8eb0985ffdc41036b2e6cf6106289181": struct{}{}, - "f7d0a3282d9cedcc14f8bef5b9099186f51c6d1ab213d309c7c8b24e30647359": struct{}{}, - "e7bf78c94f618255197e53b92692a68e8a36c861e78c7151fbbc9f515e16ef78": struct{}{}, - "fb0ffdb4b0dd82b85a71c66b3a1607a47188d3d62ecc06e73a0c562e10a6723d": struct{}{}, - "f44b7e30470bbf28871de3873a55b67990840158ee529629db3f03eda0319b5d": struct{}{}, - "038a6024ee44ade006f7ab25a0c82202d075f24211671d450fe9ea522d940598": struct{}{}, - "49674618a290b8b3885e033b8d3b8eced6f652c94c2144830982d3d5508908e3": struct{}{}, - "130afba430ca3e43b558c0f3a3fd1b28d8936d86f6cfbe15897ced95b9b6d9de": struct{}{}, - "a718e2dc6b281463de9407ed10b37237e8f490b22511eafbfa2a20e828976f90": struct{}{}, - "6f8e1dca175aa06b58a1c92feb0ce45837c81dd85ec0e2a2dd1a2a2bd5402cbb": struct{}{}, - "4ee3dd007932cb1bec9119269712836e6a3b804c5987d105e51aa883727eb9ce": struct{}{}, - "c9c350aa9290457351d72ad2a21f6a5704a37dfaeaceb4d61f2c88ef2f8a73fd": struct{}{}, - "227bb0ec86ad982179c31a1afa84e81f37c2fae8de34c0dbeeb80081a65db390": struct{}{}, - "6b6cb0ea05a0b0dacede24b6e887cad86bfd71a3e4a897e5b85ca6fe5e429e7c": struct{}{}, - "2dfc11a4366a5baf30981d10406ac99f59521bf188f6012ff4042e7a12dcebe0": struct{}{}, - "f03903dbbe2ef9dc94be656c761f27565a26015e724a5a6abf8db55c2ed17c6e": struct{}{}, - "f78b6cb9224209fe21027f4f6addf57ef2fdf4392e9362095ea8de7377f5451e": struct{}{}, - "6d691623fc99417b6836ef5357fd426452811f670917204895ecf248e10d6073": struct{}{}, - "cfa64ec0f2f473fc09e2184ef9d9a5ece8ac49da1d7c877aa37d09622c7d2e23": struct{}{}, - "2825f4f238690847c2d2301225fec4e733d9c4c7ab20deacb93c3107e1277a06": struct{}{}, - "21b9c3992ac18fd303940d5982db7edb1edbab361fcb544d4e3e49efdd3a7ec3": struct{}{}, - "8c1f72a8d7b57cfb4d22aca6859c76eac89d5503962f2bd86c72565f7778f377": struct{}{}, - "edcd8e701a2df0cd66a39bae6aa156cf16fe2b9653ef65f7d31742e2352421e4": struct{}{}, - "2f1649b4083b7532f5fa1eb1884c20252691b3b1f40eb1dd761a35c9814a8b5e": struct{}{}, - "230d8358dc8e8890b4c58deeb62912ee2f20357ae92a5cc861b98e68fe31acb5": struct{}{}, - "dd683c7bfea5c7aa3f75aa7ad45d128ff0d7124b378eea0fa47328478c59359a": struct{}{}, - "8b586eb3abed3104c46127bb62abfc45e00f7f340f4c2b85165813fb1c831bf4": struct{}{}, - "b46b09d2ce7ff94cd0559fbe5d9a57842242b384e0e33bf2c8ddd214f6fcc770": struct{}{}, - "aeb019f7914cca9f0fabe660a1326d53226df247c1e24e946216288043704b9f": struct{}{}, - "4b125ec99a74470517f9f51ac1a70eafa3dbd1ddd4bae409fe59a98ed720a3a4": struct{}{}, - "171aac72c04f6eaa2516d2dbf87b4916b98903ce6567359ed1b23e60c4ad78fa": struct{}{}, - "15c45977edc544960301cea5eeb3f6d0ac2b96b2d542fd42518ab24db9c7f829": struct{}{}, - "8a1bcba8e0a71b638685e93d07b5e377814568ca1b274554acae690404c34ecf": struct{}{}, - "448ff49f79b249a748decb4003d7e17f957351d79344c34cf9d31cec5fe5f2cc": struct{}{}, - "543e33c48b3c23d3b3ef151358533bc206fa0225ba66b89933a795451f016479": struct{}{}, - "ace437343ab2812434c0d6e92d7d4149b8e0044d60e79fd8c55dcb0c2591d85c": struct{}{}, - "07500d6ebc31a038972b0f3be5f1e9a8561677ed415ff78ffcc46fb9f5043733": struct{}{}, - "8bbe1842c6dc006ad49abdb814a52d34d82a21d018b988595f53d43dc8b1dd41": struct{}{}, - "4c96f214fb33ae6b30760f5741284a07dfe5a30d3872fbf18139a6b1fb0fc0fd": struct{}{}, - "42a73cf9f258b8e49d85085dc5c9ada7c23c9ac2fc785313c297772806030cee": struct{}{}, - "733c02878170a2d94190f438ac97327cdacca736a3792122e8cc3b06d084d981": struct{}{}, - "dbc6e3a9ab5e4c094b8ca3169bac4e298110b9865cb5ed6d503f7e1df90ba1d6": struct{}{}, - "3ad87d480554432e8fb02589a49bb9553150c357b12c2fa70c40e9f36edd03f4": struct{}{}, - "b11833a4396b84cb8262131881463ac91b52adbe0364da7678beedbf702a6408": struct{}{}, - "ae0facccf0a3723adefbdfe700c097b6c843dbfd06e5cd325602ec13b2361f8b": struct{}{}, - "d5e5116f74e50ccd3ba9304f8e92dacd1840db6b6bd6d5384382cff6a9faa6c5": struct{}{}, - "89199c0826f16e0b2f4bef168e09cfabb0c8a37facd2b684eeed5897525242ed": struct{}{}, - "afc0d772a2bbe700da332d7df72b783509ef96510ca4d293447cd8eabd3d68e1": struct{}{}, - "496bd66f8b9cf04295243d76beb1cfdb949488d8d84d151f1e9f5f9d8844f9e9": struct{}{}, - "3b2804b10235eb650eb3f5933a962f35486320df3003be68090fc933ab8b56ae": struct{}{}, - "82fdc9ea323c41a1d4c6598a3e168be73beb644d7e47199b1136d085955ac25f": struct{}{}, - "38a9c1e721584da284b90ceea4ed7eb8db15ac4ea86c2c75ec925d5f8799fcf0": struct{}{}, - "c812d24d005e5f7050041bfabed22f09b61e1f99d61cb29ad12cd2cc048ab46d": struct{}{}, - "adca677915ea4ef261f61dc669c04f7d5b79ac528d36145f8bf58cce27bc55e8": struct{}{}, - "65b433145f0e28e8b5cd90e55851bf246712da3836df26ea653e5c5e5effe2c7": struct{}{}, - "af766babb9f08e9c1a10a6eae5a048e53169f4d3824e3861f759f750a85b029b": struct{}{}, - "415b5af3d920ecf773ec007857b184146d663ef75750de7c498014c6b4200d8c": struct{}{}, - "f14217fe2281c795c8bae01747cb3db957ed01326b915ef899dd2381d33b13df": struct{}{}, - "18d63be10ad544a04a22c944dee01d6d864ec69b797a58edae92e6a44ad8fdbf": struct{}{}, - "6629ddae3736e894e89cb4a1300a9d2c5c0fad418f8ea06a341b81f2a98bb491": struct{}{}, - "94b4e3c82fc7e6fc81180ac6155db7b0c6e07c26095a67a802d4bd87f2f441f3": struct{}{}, - "651c11550d7ac7f5d968d74e691739feaaedb5744d6d158783f79806eb37b780": struct{}{}, - "f1abe1b083d12d181ae136cfc75b8d18a8ecb43ac4e9d1a36d6a9c75b6016b61": struct{}{}, - "36f431b236970cf28bb0b994c4e726037d7ff308f77361f3e2c478931fca6411": struct{}{}, - "8399e57405627368722830c9ff3db81fba0afa6120a439c7a2568a5be31b8295": struct{}{}, - "c8cf701f30ad11d3ee877dced0d774ab2da9159894296887b8439a63c9aa0655": struct{}{}, - "26cfd27dc7ca0c1b40f23d61f6ab2d8d4b4bee0eb9e1092f175d1316679891da": struct{}{}, - "917c69dcb0f414ac4064b83a3b35c6fe9fadd2e781718d2bd8cdb66552733d6b": struct{}{}, - "d778b673710a7c96d31cae27a4cb6bdf7ce7b8470747b922091ee18ac2fa1dd7": struct{}{}, - "4fd07c173f7b2787b29ba6d2ee1fa9b54bee554c3e8ca40fffda973956201947": struct{}{}, - "4e5aa69abae351334eed8431e02b51275cfcd3cb31bc544887544eb6a9d2e155": struct{}{}, - "376805920e55765119c1da5f3924aab0393fb8fd3c0163cecc50cfb9383976e3": struct{}{}, - "c8cf49ca3d8f0a884e24389c9e7090c22f09f63d1b6b9e6c0b2376ad64d4dd3f": struct{}{}, - "e3284ec004f6278b5ae09a2cd70a0982a4f3bf06c26a1c78aff789dbca003fce": struct{}{}, - "dda209044ad44962b012ececcc2cb2f7efd35c72d22465586e294b6493bc21c1": struct{}{}, - "3c7cd0f797ee5bca10edaad10a78891be61f411cc59ac487530f9ee04f41fc5e": struct{}{}, - "7f3fa48ca885678134842fa7456f3ece53a97f843b610185d900ac4e467c7490": struct{}{}, - "89a0d30ea1b4cb9aff84758a418e835f06993f9f2446851ef331d30c27173828": struct{}{}, - "6a096a0be941840e116a6f6467c345b021e6b4c73b67cd4e8cf249e2b0ed60c6": struct{}{}, - "f4401dcb1d828500df6bdc67f211a1f252ba8b766afb42d6cb1577877f2d5668": struct{}{}, - "0d0c417ba77ab8709375ea1c10c4bbb29d79860aaef7c222fd262dc4a19d0bd2": struct{}{}, - "59a6978d251daa612141b5f772874311ce30f55607290638dd8f0d2f3ff55af1": struct{}{}, - "045ddeb7f631b771488698c829dba97f89222ddf764dfc7d451c0f5653903ab4": struct{}{}, - "86cd02bdf97b21b2a1307ec21edb6af71d91673ee26c0b0d23544580d38f02a1": struct{}{}, - "1792b31816db934120056d9c8f645b238253e91a6d900897cf09b4b547f12351": struct{}{}, - "e714b7d37de7c070a1a26e46383362b40fcbb0a224f15b729011ebabba608de8": struct{}{}, - "33f31bcc2c556c2f34d0a69e72c6efc7797d579bc0d6e61bac8c91d8e57b6ebf": struct{}{}, - "8750b1c70c66ee87a31cede20e17d62a458de31a5f7bbcb5fe5aea08579db229": struct{}{}, - "f534f9a1d37260cca6bc0d3074e6a3ae10d61221f8cfb48348aaf6d193f07cec": struct{}{}, - "9d0d14dbca5aeb2984ede0a61985ee9947f782e37337a5c8e0d6032f9f7ddac9": struct{}{}, - "b6c45863875e34487ca3c155ed145efe12a74581e27befec5aa661b8ee8ca6dd": struct{}{}, - "7400ec1250e1475bb7d0a01aa73e2763b470ce88f64e314a7f05372b34299176": struct{}{}, - "b22170202e79ee42190b78f4c436017bc48b2c0d54c141edf8d19a1e83ddce9f": struct{}{}, - "089abf29a622fa3d5c1da272069728827654d039e3b5bcab4877ba5fcaba1925": struct{}{}, - "49ad70ab6fdb477d63ead368c561c0101d5af3a2f7b2be1ec64629d2bfac31cb": struct{}{}, - "e41f61423f6361a85038f9c821092328dd7c56f6db0e31e0146cf04eafd29cbd": struct{}{}, - "c7cc8ab01d8d6279f962413c048c9a64a01e5b22874570225011267ef724597f": struct{}{}, - "b12b89c7bba2b22a5216d08b085f565d5787e4bdecfc736ef1bbc5396908332f": struct{}{}, - "04378cb17256214b784b08d285487a14b2e96ee362da244a3116f2434046e56d": struct{}{}, - "c8dee78f8c7b466c881847accc196998bad00e2b96c5ef913dfbe454d3807c96": struct{}{}, - "f904e0950a85debe83e79dff8659f38fd26825f6febc58cd4ad066d7e0b3ce9b": struct{}{}, - "d048461a9161625ffa33dba5b635dd530045d05d3fc5dae644b29848d50f116b": struct{}{}, - "8cf217cdfe89a7a4b9693ab912a1ba56af38832656648e087834b0803bd2e38f": struct{}{}, - "e04dc6405154e1be4887eb34e8aa6237661a15a975e2c13a5e78e056d161099f": struct{}{}, - "7d45f0863f53cf495d56e3a3f7c95913ab30129cd202d937c0c17f8362b33708": struct{}{}, - "06aca62677f73f8634f5a37586327e15714dc592ee67f03ac3aeaf2e8d65eb24": struct{}{}, - "884a339b96f4273d9ca4dd40bec0ef67491e64f6a03f475cacb3325dfe372017": struct{}{}, - "80327042b96b9f1c00d9d04db816e84af4e3616db1d0694b13ab86f49fd251bf": struct{}{}, - "887d814b066634a8b8ba0b65c506b5352df0e23a5c9ea98628e463cdc392dbc6": struct{}{}, - "69f5b43fa4a7ec67cc1e0aac24cc739f1273dbe1579d6f6439ed78405a15326d": struct{}{}, - "c793f8a09df6465e9faf2625a978af7e617928c01911141a5250dbe162b24469": struct{}{}, - "f74fc122ff882a4fb82b72d8f48c164c4afea0d5d789351720ccc141da54194c": struct{}{}, - "479ff2e0d7c8a60e3d704797058fff211ce05be31b7b1a1e3c0d61cee52bc2f7": struct{}{}, - "32fa8aba152be876fb8ba0292b114170e84c1246defc777b36421b3b2c48448a": struct{}{}, - "5c8f1b3150905c4b4a8db85289407102d534773a019c6c6b8aa7a7a60f3e21b7": struct{}{}, - "f5c46ad951785ee91f6c3460b49d0f1c74ed10b81abede0a37ed50859c4753f5": struct{}{}, - "be2bdbb3100e41191874b192057ae7418f549e9d4ec8b3eb1a3ada7b9453d365": struct{}{}, - "a48a707eb53f0722dc2ff8babc54d3028cb6fe79280ae40a7776fa54bc75d2de": struct{}{}, - "7405e3ef4da7fd778b4ce109a90d176e0957190dff8097a380b62b1e362d9c51": struct{}{}, - "0c4ac837abc0830c9da02f051ec60711ac97d5b1aa55b181e6416779a182614f": struct{}{}, - "4643768fa07cfa6e1b8f326d99210ac0a3fd49fec3c64a85745036002b92c3b3": struct{}{}, - "2769fc20451b51737baa457ef96532b84370d3664150af08b3f310f30448921f": struct{}{}, - "5a48828e2c529f4b742a2b58e521199604dc82fd8a0cc2c4259855200445e69a": struct{}{}, - "2514c824ed210aae0f188681f6293d81ee6e7a83e77e90c9b4f420b5f80f3baa": struct{}{}, - "54fa0ac6fb7ba4b0ec2dc9fc8f7d5c553c5c3be6f964e3f6dd49795adda51010": struct{}{}, - "5b39bfccb1447d4aae30e7a4fb0f4ba37e79ea96ec54b5ba7223979a15e4d0ae": struct{}{}, - "f1ed827790a615542f9bff69afd74917efd3af71d1b5fb9baccdb3cc8f0c0a30": struct{}{}, - "8f89787470c69d950600216e43aa860449660a37f94d9c141ff4f1959442aff3": struct{}{}, - "4739ee3bd29e4f415da8ba9298a087e0fdc9c61378420ba8fbbab298bd74c4df": struct{}{}, - "fe2fccce197e23f2948c32c05678fc18f04a0aa3db2e2fef9fd0c4e4f518040d": struct{}{}, - "a3dbe29f1bb15c00138e10b3d440f7506002a5d753e9f3669c905a314b737142": struct{}{}, - "b51026e4444f98ecdbe1d7cb1f310427a47d7a6e7659b37ce3d00010b09af252": struct{}{}, - "26db532931d57a616e72a35f9fc1c26643e36d113c70837541cd37b106d08678": struct{}{}, - "8b7d3d77384a9756e3596dd3e81bca2e370c0890556f2f6d7a9cbf60493e68e4": struct{}{}, - "cfff5313bf2234224d0d5f87be0df90dd97cfd210ebec4b215c00209a5d45189": struct{}{}, - "0515aa086a21d9b65d30b41aeb325de3783a40bd9f93ecb3110ef4845aa4848f": struct{}{}, - "e04eb29020eaa961e99d3162635e9fe9585c5a1121bd88784c1378aa8837195c": struct{}{}, - "e3f9d6b8938ac8d4a3b80f8584570a67ea73fd547274e7358aad81b3112838a4": struct{}{}, - "c3838a8256696f47188d6641d945bfa308260c137b04e0e4de327e39d54cb615": struct{}{}, - "b47137bc5bfccd076b79642f5093d83891c53bfd67531dba69863df8134a3410": struct{}{}, - "25e7b7865ca64bd2f393c9d7a7efa1e140a53d0205cbcba4c8e93e3950d11a53": struct{}{}, - "cc3da5331df88e232ac0885e142ef9c027c814323df9da1cda745b538f4e950e": struct{}{}, - "ea841fbbb09f8669adbef851e7c08257cd37ae30c4b5da48fbab118cf4d0788c": struct{}{}, - "bd42f9875b2c2a608bba6b2a0ecf13e671253e1d1df2dd75355d483146a0da5e": struct{}{}, - "a7a45ea923c6229b341e2d21ff8fde3a4ca180f38c920a479dcfb7679389841b": struct{}{}, - "e3926fa710e6096ed98e6cd9b2606d4e569dfef8dd6d88c5ed8b3b4026ef88b0": struct{}{}, - "800955a7f19d86bd5cca3153050c7299b28f30e91c7fe854f08a878c158f5aa6": struct{}{}, - "8fc4c1975c2d6c7debc98ecac41c5152e6f9849eef52401aaeb725b38602c42a": struct{}{}, - "635bce98abdb7be2e126652003c4d0fd1a56bfa524eed7276ccf5ae05ba5dd2b": struct{}{}, - "e00811bb53630e3bb65a55380e7bb6c427b8ef895015915d7711a9fb9c93660a": struct{}{}, - "8a4262979b06fa58a105b926c7f6def0a0cd3b70b4c5321f06792879053b4366": struct{}{}, - "0281c104ff8e659f95c814a834ddcdd84ec7a0baa1852c0441d505859bda0e5c": struct{}{}, - "d1068beeb96a6a79937661d5cc9f290dddaa5730e64b7ab2b238078a1194c614": struct{}{}, - "d8fc57d50eea1cd47671094b59a73a198399e82f5ffd0321acfb574b55eeca0f": struct{}{}, - "b1a4a198b55693d27a047fa86c6a4ba67e661aab5b235d503d7b96c70b53c75a": struct{}{}, - "93dbb58e9146a6031df078d0c5b6c33654b8aef892cdaf0ac3775e105daca26c": struct{}{}, - "e9fb9ad9ddf2bebe82eec9c1e0295bee4f0942ceae1d08443421b3f30988b851": struct{}{}, - "98077f8609507309a74df5e399d7ad7357ddc53af2fe543b2e6d7efa93d18fce": struct{}{}, - "fcb1f6100676208c94f1a3ebc96790a3732b756855ef1e6cfb83cb4bc90cef80": struct{}{}, - "8d059c3640b97180dd2ee453e20d34ab0cb0f2eccbe87d01915a8e578a202b11": struct{}{}, - "85e8b79a62dabce662341727dd6a73da19fd14fd172b69b76c90ecb50e4ac06b": struct{}{}, - "fcbbcd5a59bf48a18e719e27bab7b6f86349400b1f3f3a64a7773058c605d8ad": struct{}{}, - "dbb5e2f4b518abd50091208d53286a2b12d6b51179322f32032388ef815d1e04": struct{}{}, - "65e2f4d2b52ff16cc739a55cf06819f6fec5e182a9e87c5e597bc040d99fc87a": struct{}{}, - "9fa3f4b8c563896e2e404145a141a6ec1d96ea737dade6627af029637413d9f1": struct{}{}, - "013ec4e1175f04bb390b9a3d8339ad998fc15fefc7c4c9fd41d29ca4ecabaac2": struct{}{}, - "59b494e72142ed8dce6dcae1774e2188787e149560742391fb78afdadd3624d8": struct{}{}, - "8faa4116cd7247b17494d2ff27f68512b2e7a490b220dcd483535ad029f4935b": struct{}{}, - "9c528dbcc589566cafc4f0044b10a01e524007f035186ee11a5d1a3e6737cb7d": struct{}{}, - "ffa7e158c8727f00251c8ac562714b6885c0c73962f7997b2c1faaec86d1ac43": struct{}{}, - "1c663410abd50866ef1e20b333735e3db06569403f4b5faad55a1d60eb03cce8": struct{}{}, - "f69f93b059cf27adbc6955fbd566186b26dbe19cacfa0ad1309dfb4eab058085": struct{}{}, - "352c00e4c6860a34dd5dd17e338af9386b8485026a50ce45bc6c737609616303": struct{}{}, - "aacca8bcb37aefa0c533aaf84f4bcdb9c15dfbf7175da4c6c1ce9d3ec5ba4a02": struct{}{}, - "03fc45dfc311e6f18e86c9454c13b8590965d24863dcd923504da01e83c4ad89": struct{}{}, - "3f5f3806e425deac33023e9764d08ac98397e6f1bc8599743cbb15a9cfdf8929": struct{}{}, - "03b0bd366e8184f8d871c3a7c7cc26c73c25b54ff54c64b28b10b898242cdc8a": struct{}{}, - "82887006d04d939ffca870bc268a940df6cf01dbdf12e228ccf476d07d7c9424": struct{}{}, - "19e58efc7f71d3ec0bd46d451e84674f072ccc74c3128f4f017e6981d4e92543": struct{}{}, - "149a6fecd007577613fda7ea18b30d1ed3c025689e9537f7f3dceaca080b22c4": struct{}{}, - "59d98c516f8b4e28e5548bbcaaa54679d9c85297231f678e63d50478a473c34b": struct{}{}, - "f617936d4a0533dbc234decb0ac05a18233389953de6bc3259d061d35d8d7d7d": struct{}{}, - "64382a27e115c395467e383e836bf3b959ae25fdca7a1f52537089879d0f2f25": struct{}{}, - "e531352262538e0cee9d2d7aabad338edd7b7a838bd8212dca7f477c9ee57122": struct{}{}, - "d8239e97d96a60109c65d65e8a480988fe9a8dc86d9a980ec51c7391d1396637": struct{}{}, - "04cebed85eb4d89bc2ced91fb40eca3654d84053c0986d0995237df27466ffe9": struct{}{}, - "4ba3e8e3765f2970eb37fae535353dd623d40a0507848c3c1dd240a5a7eb995e": struct{}{}, - "a4a838f45638da1b6bcbb71c006ee354e67ce038707ee0dd3a1472b4437a0728": struct{}{}, - "8e3fcc57e3146b406081b72d201dcb6abad5bddf9410b1ebd4cdae70bc1905e6": struct{}{}, - "13d65aceb53ab1887e7decba787dcad6d8022a39d63e9949b38edfe051a9e036": struct{}{}, - "ef875a1705a5fdac206be996f4dc1f726ea6b68861eb741c37def7277f179e37": struct{}{}, - "bec385de658e17f92027f04bc6925e2a22670a9800c9cceb2ffc88b0df3c8a1c": struct{}{}, - "cb40f8a770c07c96d583e8eb0da1864773f7aab7a7942010b1f97aa1f1c75128": struct{}{}, - "57906b2660e4e2e227e96b86ec676dbac58f2fbc9673c2e03a63ceb29f94b220": struct{}{}, - "ddc6af1a32422cb2d518f52e8d3cdb5da32c4408e3035946f582fe6e17a89fa7": struct{}{}, - "d6a65e53e15d6e00fe0341ed844d848186e52e05f4b243a0a498112dca7e0097": struct{}{}, - "c19b4c4566117c72340308a7dd89b825c23e643ead70a2bd194f2a69616e180f": struct{}{}, - "aff87f637a2bd1966c04b922b712dfddad3890687d7a94bb9ce129fb06316374": struct{}{}, - "d7c5630b7dd9c989d3b2ee96fc50080787c7946595175ae77d3b828ac83ff6cc": struct{}{}, - "f707fdda7c874ff49ebfb2c88a2860c5ff4ce3d94a21efb76566ad0f92c9ad57": struct{}{}, - "ed053874ca199cc53e11c9f4aeaeccd07da652d1c19e3cbdbad5fd9fadba2532": struct{}{}, - "3e34b5dc434bcf3186f089d362691cfac1b17231601f2f402dc79015be878d83": struct{}{}, - "8a62e967fcd6dfa5d75308c37808b4668a7faf1cdb06e09ac0a7161827603887": struct{}{}, - "693b286515bd1dd00865e7b60e4e53556537bbe4b1cc90ab608d94eb7c56fdc6": struct{}{}, - "47394a13d3e98d9c0abce678e7c45c272547fd93968a79583833f007de02055a": struct{}{}, - "f315d9ecad52f84972fc251230a456bb42ab07ab1a17e17976acb58158723725": struct{}{}, - "16771578794e44505c9da8b3c2d5b4062bf0047580a1088d2b08a043ce5d2edc": struct{}{}, - "b07be5238280b3e4bf19915e22a3d125ac52611ae1f97e8b1063721331bd6bbc": struct{}{}, - "c8206bdbdb7666ed3d86532bb7700f5b1574fa0e71794d4e15eb497d833bfbd7": struct{}{}, - "a85f33e02da25d88c3fe8383cffd14195a66f4347678d0073d37f49606813148": struct{}{}, - "ba93f18f3c035cc1fc941d72cb63555c404d96fb399c4430965c8a600cc51c30": struct{}{}, - "88a1092514f5fca17d8aca0751c2a3978495d731c3a7a5d6b7ebf53e2ba0f1a2": struct{}{}, - "bbdb785c1b2e138a2c6b83030b1678dd41b1f98f900a50aed465a502336d78cb": struct{}{}, - "09825fb3ec43d949dbfd13f09612323367b64c75f94810b171060262adbc1d3a": struct{}{}, - "bf6bcae946299d56d3c4551fcf751142f27da40856c1336f5b4f07de919ba80f": struct{}{}, - "1994d77b7cf4860a616b25003579fbb20cbe24899deb22868fb4f857746cf7e1": struct{}{}, - "4492aabd733f37cb76fc0ed1fd75a11c5dd05ba8977988e28487739a20975b85": struct{}{}, - "190a6bb36b2650e98066e601f98cf89bb1a7fb699a1683b17f2e9f723b1c80e5": struct{}{}, - "b7744263995462d2bdc72348d7592e70999f17c4c7a17c7616e6443469d03564": struct{}{}, - "b542e09e81079488918de26b089409e104c5f364cd8b933eca05717c6efa02fd": struct{}{}, - "593ce72b565242b3b5ff3a77524b1c469d8fc53282b6578d0123157e55884370": struct{}{}, - "bcb1302280d067eda329539921e4dc78656adffac7f199eff7fecf78a5fc6183": struct{}{}, - "7b0aaacd69a8b83a07fe186dbef1c704a3ca72e72eb8a03b0389eae32ed45c3f": struct{}{}, - "ed7aa50d187fda240b52d794e3fb9fea08dc8b2c45ba3aeeae7e9c4ea3aefd5a": struct{}{}, - "3067b379a64cfc4b98d10bca8c6bcb116e7eb70280a5e31c8eed2392a6f576aa": struct{}{}, - "7975b4132aaa77d75069a5d2ab3c501413eb91d11d046815158103d5628d7405": struct{}{}, - "81e1c7698c14721fb4d63abbe797ae0984b7be7b1569ee0899b2c4ecdd89ef7a": struct{}{}, - "e61f7ced5002b4b809522da1ac824ec11d1288106d6f734b4f1316f14734c8f0": struct{}{}, - "f57dbb83bc838bda3c0cf7d6091e9a3c16f824cef7ebef8faad554bf5ebc7ad4": struct{}{}, - "dec0b5d2a5904462fd3d87dd56f9cba28763d5acb74a9ff27da3715e4120b7ae": struct{}{}, - "780d06d3a2af1b22aa2f657aa1385e3813a9d2ecc16edffb23f6e30b05dc4b45": struct{}{}, - "38a7c72ae26e78a85898f1370808e4799abc205b9a064d37ddb97750df485f6d": struct{}{}, - "199cb2710d8512620ef652599510e6e5fc76771c7c95f4166d4f52221c81ac28": struct{}{}, - "8154b3f90044eb04122489f9dc0612834f1e0bd5a8bb3bd376ce4bfb6b5141b6": struct{}{}, - "d4bbee5774535ac755871b4d0cfc5199af576bad985b493501ec7e898394f8c6": struct{}{}, - "91262d0ed191e42340af6888b3fa533b7a88339d14c32e0b371faef31b887637": struct{}{}, - "d23614389143edead9e017243c6c2849c35433efb413e8867ea08fdcdc281456": struct{}{}, - "bc1d65610464d4fdbbb2717c49ee716904aa8dd0c92da716c79a394a2de65940": struct{}{}, - "30c087c2791f13c8d7841f72985339ac1791f5a40a472ad1a2a608262c19fcc8": struct{}{}, - "4e4f98005860ff87e96be7440c1ca2852cf5662a9a7c751815ad5074c5a04445": struct{}{}, - "53a8ae77c1348659e9140c764651f3129d657f0567969be6a768e77663a49159": struct{}{}, - "4a2a663f1b4a1cfba87dd56990edec880eecd3d8f189adad8084ed8514609014": struct{}{}, - "dc52f10e00f5fca81bc3a20289ebd8fc1aeecc32831eb5a88f1121ef282da51d": struct{}{}, - "3b04837881bb07b621ae7dc7abae37f3b6981584bdf3e22ece731a5b91eaf395": struct{}{}, - "2a59d59e3809f827ce709d3815e3950eef4a6a93af5557a93a7fdfba71460843": struct{}{}, - "be4cedb74fe235339434d4923c1b8e86c19a2b5ccc0c5bcfb4a7592f4abf2e96": struct{}{}, - "26205d66c217a123c72d1e2bccd0536a12c39d65b085c224c4bbd0c75fcb3634": struct{}{}, - "9f6f4d9ade14fca7dc4043511da7840e80a37185dac51bfea03fa69cb4d2cfdd": struct{}{}, - "c743eff1b98ceec512406f7e1e8b2db3c4fa3aa7fa9e9b83cfc6a633e370842d": struct{}{}, - "3f8375f34e9c5d1c54f61b8763f9c322884f43edfae0bbceb75dc720b1d27d69": struct{}{}, - "90eae63487cae2df5b793b07c622680d2df6c9a024891c8a9bd98d08d5bc3d92": struct{}{}, - "1a859d997999c691dddb8499298c26031634dc8c3d734ebf51bfe3f83f3b6463": struct{}{}, - "204fb6b2a5fddfef1e9a7c5cf4b07b3c381df335026abc58a881bc477204250e": struct{}{}, - "d6f1bfc6296a9faca282a57cb925854f6b1bea2025cbe829ab0d68d52a591d8e": struct{}{}, - "362a284952d3a7250e4bdd72a659ea78e8ec79804e0e6a2053dfe4c10e7d79f1": struct{}{}, - "845e91831319e89c4d656bdb80c278ac09a7230d61e5dfd2e1b1fbb436ac8917": struct{}{}, - "f296867839c8befafed32b55a7c11ab4ad14387d2434b970a55237d537bc9353": struct{}{}, - "7a345ba5e18955831fb1f543443b78bac5a823eeb8d5747e8fcb2c5591b31313": struct{}{}, - "11cdf86d5723eecce5af1f33e5fde9f066e608d0a1068f445d99820eef5c19ae": struct{}{}, - "a4a00c840fc355991927407e89fe73159d81f97fa7bfff1b5e40256348ca9690": struct{}{}, - "4b325568e344db63ac4b580fb8439013c336e31a889dea10ec987c57bd73493f": struct{}{}, - "eaada1689c1884038610030efcbfa33d84839f8eaf5256ec7603bd43faef76fe": struct{}{}, - "c0ab9a904821f8ed8b2f9eae40ec99511009876eb4d4c8810141ceedf25e397f": struct{}{}, - "4eb41a2a9dfe70722ee4671a6d1fcc6921c26cc8bcb54e5632f5e7d740352940": struct{}{}, - "32c8fea43be873a2f1186b801603ead805ce1259698588a3c3ed88f405db33b0": struct{}{}, - "326ec838558bf00fcfb3c3445fca9912fa346d63def9834f71bad812427b0b5d": struct{}{}, - "31207a2065f46a5b948fce6fe5c13e85abaf5631e2f894b47dcd4fce14f6c57b": struct{}{}, - "8bf32e2c369af96bc8ced55d65b6f2162f727f8df26c75b85fce1c527d50d677": struct{}{}, - "1cba997a725e91beb1f4cb8437075e45772d68a0cc63c8eb61c74fa9d5259d8d": struct{}{}, - "fe1a598ae1c33e9579552af5dd96f7ab3e962388f83a8d05fecd5d518eb62609": struct{}{}, - "72554af938a3921665267ee03cef95ade6290625a9f99d6c57ae49809900fbad": struct{}{}, - "e5f0d2bc06f4abc1cdcc51b99159a8285b9bf83aeb70e682bb168448d96613eb": struct{}{}, - "edd0dfd02b346db0a9a76e3df741276b46947be8f4ca23917e4d8c6a037ec3b7": struct{}{}, - "37c9aa3186e19d9e3cfb4f2ec195d3a3ba27e280be967bda536828003c94a0d2": struct{}{}, - "01303d3beb16b992be93426c567ae5d5adeb1981d9a1063c9194752e9f284b9d": struct{}{}, - "0e55c6badc25fe8e5375f36f749a031d955cb09808b7ccb327d3fa4311b41968": struct{}{}, - "e81d3b0e9d82feaaf5f6e55bdff24731d7eee08632ffa63801e6397290c5d20a": struct{}{}, - "0ad2e0351be5fbd48cac905da60f5acb31737190445561b96c4c85facdab649b": struct{}{}, - "21f99353b0ce99fd8d35343fa8147af1d01e54d1fed8c5e3fb3ca05dbd1b230b": struct{}{}, - "896c5c9df7769fad445d5b4ac8047d7f8466f919e30abe1232bde41e8453afb9": struct{}{}, - "55e658ec13f067344854ab608e1ff342fedbadc0df78a53f3bd512030bbddffe": struct{}{}, - "2b17053fc664cad22aa956ba338357ee8b374ef48ad46d8a5a7de3f9347742f8": struct{}{}, - "9f135f9d311b569d69fa33c73cee94e5dd6e2507e3ad44a308d7c4a360cea06a": struct{}{}, - "7791a27c9c09d1678b9dc90242c62cca91552b3ed6b3d370b81d2de352c17950": struct{}{}, - "650031a60e1b0459a0d9594a0a469391986f798331bd1bcc3dceb0420b242f19": struct{}{}, - "39ee8144f049e66e533e1ad77bd89d6fb5bc7de8900b31dbe80e2481e3a5e3e8": struct{}{}, - "49ee5d7a4358b1688d64564be9ae68f2d6cc5ed615723da2aeb074c14feffaa4": struct{}{}, - "5d0c0ab127fdea24d94e7e3326b6ceb965ab82ad897f9de1bd6b1a9a23f87578": struct{}{}, - "5ee292d33dad69d01a1059ac43af23c4494a3c7d4a681a899d42700e9af2c660": struct{}{}, - "685ee53b55aa432dc84736bff82a5ac5c8aaf1a7c8c4e62699bf040764bcd364": struct{}{}, - "1e142e6277b12b7e1110478a24caee8f006a9349e86970c890203d6266209463": struct{}{}, - "8eec27653c19ed078b2f3bae16ff901d16347d7917d2b8e2317914e2437bf324": struct{}{}, - "7bbbc83b16b2405a7d600c2794dd13d94ed92c7f7b7cfa25994a5ed96846faa9": struct{}{}, - "5933b0b35d5d3e9d1f53fbce9403c5672883fff37f429dc1da26881f47415672": struct{}{}, - "6ed701cfedb16ebd91b2d185383115c6f9ce12f2f9c744d7d605be0002109362": struct{}{}, - "b5aa80fcc17130484d8e07a96fdb3f910442510f1f677de8aae0aeb069ca8489": struct{}{}, - "c3628e0e2861040ea88ab3bb5e43ffc1cbba94652a91ccc5d47497f45de946ef": struct{}{}, - "31cc9650f3dd1bca7fdcd1f40a4cd1a77f7a82f0d333be132fec3502ec9d1515": struct{}{}, - "d1a1efb9b136badf76763c7797de39b2f75a9885d3c5358a49b961a72460e60e": struct{}{}, - "b565f1f68c61fdb5cd688229602efc080448f17026fcf8082dd2ce8b2031b756": struct{}{}, - "7ac41e467f19c3ec005067395cfd6aedcd98d02bb758a475382a60c64b92e7bf": struct{}{}, - "4dd68e2ab3a30973318ea903e088b3d3480655ef4236109fe47272c1c1582880": struct{}{}, - "fafa1f76da27f4c815a675aa02dd02412497787ded10e3ae00b346e46a51a7db": struct{}{}, - "696e7bf2bff770382d0ce99d05be6eb5c5d94f0aa077aad7fff27bf2ceda477a": struct{}{}, - "588ee66b491955575c00b0aed2ed44034b56c777761234bc2ff13f808bdb42e8": struct{}{}, - "a9deecea37c9b86d34309c009f1001eb05177b78a9a1b730b96247795e0e75a6": struct{}{}, - "57b124867ebddfa13756dbf3016d77780e5a4f79ebd165b9670681b773ee044b": struct{}{}, - "4993eee03e310a11b1705384b2b987b40bf20c56395c8874531891a7397f7e1b": struct{}{}, - "b84b0faf393e0c0ca2f76a6095fad0d7e7a0a127b5e9cd5c43b0727d7fef6c19": struct{}{}, - "bbc646bb0b1593497bc9113d5d5cfcae231de4aa9fd3c04db9d4cac4113db645": struct{}{}, - "05eb88618ee3ff805b6f893e356cfb92fb453797c5ff495310fab4601562420c": struct{}{}, - "68221e4fa174f00b49c6c49e2834d86b680f6488427f5197ae93bd50467db187": struct{}{}, - "e238b3c89bf52b9e18565d2102589956b57394b8425f8be72431d3f245f9c913": struct{}{}, - "6f3054d4bcceb8ac8df92309ae37d2ed51fd23c803c2397b6a5440dcd0e0304a": struct{}{}, - "06926a5203efb021c91f58f63ac8b106efecd712b60b8e79473851222ab3dbd6": struct{}{}, - "00390de2b7074071bb6494e818e84884ef6331ceb0b1e70948bde3ef4ba57b92": struct{}{}, - "3958de59a1ae60b4330e99d6a5b791897717cdd2347260d0f71df22d60b01062": struct{}{}, - "41d42da01baa7b1fb1b8b926b294e7ae31920596e176f81dc2701a1d65b887fa": struct{}{}, - "f0a36448c29c843879dc58d50464f2fb1b5d98ab5ecd7826fbd44e3788283803": struct{}{}, - "929260ad9b9ea9fe0f3553dd964f4ff3deb5792efd031a2b90f573fe91f012bb": struct{}{}, - "26787dd2f72cc993706ee8209c6c8b2e77b8b6c7bbc8d168550dcfc1c8b28818": struct{}{}, - "79c6702755c69af400ba269cf58852efa6e90f938f4e43deece255e5bce72106": struct{}{}, - "6b8ae3329753d46a4af298ff5b30508b0697a279de761189985bfc7b879e25f2": struct{}{}, - "8783b7dff7a7109035e8df613232d21b3f4a4aa532fb8f53d1059046814dc657": struct{}{}, - "0bc07441d695c947e7025bfa8ca2aa8bc812b4fa952dbec2ec693b26d421dd74": struct{}{}, - "810c4dc129e30e975c84e9b8f968fcc3e44316c41d196eb037c2100cd69691a8": struct{}{}, - "8628fd461fe29d6d47663535c29cf55bf00423d6deebdf56600f00c254ef5842": struct{}{}, - "142193d5100dcc47ba7d8d46c375d718fbbeb6d50c1267b87674c936811116c6": struct{}{}, - "5252f52cb79e2276783cfdca50304fed06a0eabc8dbcc3abfe3aaac5792c4fc6": struct{}{}, - "23b6ac8d08a9c29887fadd913df4102d3c3312d310505ebcce716280a90c4337": struct{}{}, - "288971671685b8da56623362c82e1ead68186c5150a35e3b35b5ef74cd7ceebc": struct{}{}, - "6672d6e4bdbc05a0e460d5091a05adb228b3cbd79d984e39b76a654ba0902bb6": struct{}{}, - "3583e2784d4accd7b12ddebc153b0dacb41db7e947a5736a58230a3f03935eb1": struct{}{}, - "67a619457aae3e869af3e7c92078424a773397c1520a9cec76fde54ee8350137": struct{}{}, - "22c72aa82ce77c82e2ca65a711c79eaa4b51c57f85f91489ceeacc7b385943ba": struct{}{}, - "7bb35b720c001477f4fd20f23287b310e9eca24144c24c0dd2a8f69a0b7d1af5": struct{}{}, - "998a364b79915f8c78d2646f885217ef1b0e30994d2e376439ec0741b366e331": struct{}{}, - "999119382bb554cc5a59bedb0bb89c70ebab1a41f7fdbe6308285ba6867d158b": struct{}{}, - "fab862fc97dbb716f555be7aa56401af1546fd64dee4b4e4b8df1186225d05d0": struct{}{}, - "81f315850e64fd88ef903f081e8d96ccd12e29874ee5a4b008e47f8b124b4122": struct{}{}, - "d9da0b2c7a7bf2f65cf8924837d3cd741e3d64abbbcc5cdbe167ab4f26ca2bfa": struct{}{}, - "a2107d05602fd806c32cf547c45cc6a92f25a5291af7cb206d9da18a9bc11d5c": struct{}{}, - "d37c5e7961d0ed500ecf0475346027cf2d7010b7083c411f953936b0e38b1ab2": struct{}{}, - "bec4ffaa14c6316d298b20595ae5055d669f308fd835bbcd9b13f32fce8e7982": struct{}{}, - "7dc917b8467946430a3e5354d5472ece9f156762fefa61f5a1c74724f8eee400": struct{}{}, - "b684e5a7d5767cce17964ffa279952b91971d22dcafa7a9c17097d77a4135544": struct{}{}, - "c384e174bbbbc54c6660bc669a8206cb48375c17a59676d37cc02fe074edd571": struct{}{}, - "4151f71c176ed43c0320cffb8b2a2afc2bd8f016323c5f3f4462a7e70c16c5a0": struct{}{}, - "eb0afd90509c50678e298bdf1b15f195b2b77c31466211f5f9118be1a9788e1f": struct{}{}, - "d9bb8452ecaf5a05ad22ef4b9553f443bedb9c2c0811b28a349d954113c0703c": struct{}{}, - "faa2a9bdcc837728fffdebbca0ada344aca67ebe6aa54836d298377820590de2": struct{}{}, - "a9e4ba6a8c661826ebfad6a3d8ca2bf5fec22e9108ff2c956058c71ad8b543ab": struct{}{}, - "1a5afeda973d776e31d1d7266f184468f84d99bed311d88d3dcb67015934f9f9": struct{}{}, - "8218ddfb1ef7fbc17e4d4a09a69d6c0c22f6b426e88470c782321d121d720cd2": struct{}{}, - "b4fa9c61375f1aa365b9572a36b1926ddc5b4a0ac0e305ff1421a6e9caa46444": struct{}{}, - "0a05d7b27cc7a2b1ca704adcbd1d6e3ab2c19ece000586f03bceeabf24547e43": struct{}{}, - "475368189e17ec9d12cf43f873f4ba27b0ae9ccbd0686fa21f5caad89ed57243": struct{}{}, - "c8a1de33e14e4e0cbed9b4dcfa503b3e45eb1612f4351fefac11ab52b0f8af77": struct{}{}, - "c59d4aff30b0cba314b35c62e1704eee1bd05941aa576efdb965220b072f8864": struct{}{}, - "982d3cec0c8e060d5b7175d368c2eb3171c291a20c97f83e29b386275174f4dc": struct{}{}, - "44e724254cc1859ee9f00c141b25b100f210168fb71f0fa44f351beed91eb6be": struct{}{}, - "1ea2f89d934cb4a2af0b486736609cf9cb4bdafdc6e946e79aecb02b9d9dceb4": struct{}{}, - "f43dc8790ed6215684924d362753e3864cb66c0f06084e89ad36b57e15986c6e": struct{}{}, - "8dccea5217902053b636f0e105c590eb30db4280b8a7a51806110f7ff7b5177e": struct{}{}, - "9c1911155f0b57a0b7c438eda83e9621ea5cae5e5190adcf6bd7a2e8ae8fa0a6": struct{}{}, - "a1a14393d26df16733a047d3a4b6e0ff5acac6861bd4e94da66d4e2fadee5d37": struct{}{}, - "2cff982a584448d9b76ebaf89505905d635e000273de0313f6939f323d3575c5": struct{}{}, - "f7eab37c35b6adc939351d80469308c73c8ac17e7fa9a2b0ac631c8f3f52446a": struct{}{}, - "1cdf95bd12e993d5aaf216f5f4ac1148b4b003ed0a253b781b25866eeaa27af3": struct{}{}, - "40a7e9acb06295d6ccc4de8b5790aa4cea3456f9bb1dd3e91f192ba5ca98bf97": struct{}{}, - "60bffa20b02813481ce58a667e003f28f40a1baaf047ca7016cdcc857c3bc167": struct{}{}, - "0acd1f0f8b1db946fb5d668bdab0eb71d5a0bde85e385e7cf61bbd3608f6b3e9": struct{}{}, - "5f447cf8a94ce53e901247bcd4dea84dece622114a646bf9d279c96c11ded2e9": struct{}{}, - "a88f2d320bf9a9adc6186e51b9a007e5afefb5e20849e9cdfb1bf8b98e6501c4": struct{}{}, - "e75a6cd43a16c2f31d1a3c17700af64d3658a380c49d65b20cc75b1f7c0e001b": struct{}{}, - "7e94a3fb0675c7d6dbe2d8c53ec26dd5fe79a202e3d5d79b77fc0c56376969eb": struct{}{}, - "f2e9a23ac36c4b227b16387b4e0d37793e9459f3912942069981886d5bffe90e": struct{}{}, - "69ce8be9e2cc5028d0a75eeb54447891c1ffa931e783a854656ba3aa460a2d13": struct{}{}, - "5ad0e75a5119ff6de36d3fab783202c0224e685aa48461aed16aa72aa3960d21": struct{}{}, - "cc15b25e8503479ab224effa3b8387612a1d14cc5a4ca2460efb9426999240e9": struct{}{}, - "a8cd7e237c89657111ed3f61bb04a7385e7d4328ac48587d2e5876b67063de31": struct{}{}, - "4e448897946ead9e39e8c61c2994b2ada30968ed9d14ac21ebaba710d823db64": struct{}{}, - "4b63a9017b09f194f6c071411d4313fe30be077085a05e44da0b481e9c9c8c80": struct{}{}, - "eab762a03fd979a04cc4706e6536d382bc89d2d1356afcd054a16b2235ecd471": struct{}{}, - "4e37203828b0aedee7ae315af76705d419c7ed88686fd4e433d3bae213aa24e6": struct{}{}, - "f9df0da4d63965b0f0387c55985b31616dcfb4a634c20c15983ce5a7e3f8c8d2": struct{}{}, - "0e0532cac27f275485f2bead4c8398385e5505a514716a9960b2f2b6b4880013": struct{}{}, - "79082e07de34a101868e209caa4c29ca8c296923edfaf203e22b8fbbab744f96": struct{}{}, - "6f61ec5c7a0ab645b7b956d703d6e91bbd804f2c4dce1cc81058ef9b17e0ccaa": struct{}{}, - "8bddf4c0f769a0125ce2a2a0c6464bbd8fb9a679acc1ace032a7d08b0c7a5b5b": struct{}{}, - "a11a75e0feeecc1a9eff682b06ef0b1461ed4ee7a4d6da4ae333bdc1041d9d51": struct{}{}, - "3660315a9af3df255d8f19ab077e4797822b41488a0e2a04bc6af71213c23274": struct{}{}, - "2b42be4d4fb82c937af73995cf3ddb968edbad225f21e2dc51ee80253ac3ca1f": struct{}{}, - "0e66633604475ead7445c5e5f987d6450c23687eccf37665e3924541a7f8f6b3": struct{}{}, - "8adce0a3431e8b11ef69e7f7765021d3ee0b70fff58e0480cadb4c468d78105f": struct{}{}, - "17bfc9476bc0940c5955fd88703798c689073749ce712a2178b33a2c84b9517a": struct{}{}, - "c2fb788c7deedbeaa296e424d4c2921b871a4f6cb4cf393c1c1105653ab399b4": struct{}{}, - "6fa2299be882ba4c8e020d6d72d22f30fa623dd47660fc81b1a745236ddb4935": struct{}{}, - "551867e820484ba53bf1ae0739fe80fbe394f6046d480015fd94681234d027c0": struct{}{}, - "0bab077049082769d72e551a66c3a00e8662c491e119c8f813efa11e1969addc": struct{}{}, - "b41b0281f078381283c84128d3ded383cb4fbb24c2cd398810278868b53573e8": struct{}{}, - "c4410f72e4467dfe7d9cd78edbb2f5786bdccaa54a6010782b2c411f988e717a": struct{}{}, - "f83c31f2a4558a2223e1f58d52cd7b3c8e6cf42da50dfc455f61b02b454f7bb0": struct{}{}, - "71eaea56f7f31be16276ab904b8b04144daf720badab79d5cf291edbbfa950fd": struct{}{}, - "098593a0be4d6eebefce7933245f2299c43baff43d85a8663a3a50f3f338d4da": struct{}{}, - "751aebc295d7c8866f27ab057e63160b64e81b016ee8c529fcde99fa4100de1b": struct{}{}, - "3bbf5c9d6a235e56eb3a1f5ce9b6d6cdf52cce969f623ee42340b1b310e67f84": struct{}{}, - "8b5d7649d443334e3a9b1dd80704d4838db6578ace1d3d72cd0541dd289ee069": struct{}{}, - "96eebba49dbbf422d245f02290f9d4ed0eb02da9daa6bbceefb162800ff42481": struct{}{}, - "dda820158d142a0b73380cd3efa9ff7d9fa11336e9ba4b3693098964537d61a3": struct{}{}, - "0af65319109aa452dd33590a470105ce7b1944f3fdcd209c79fd42942b3bf7c1": struct{}{}, - "305800b71062b49b350208327a02ec378199b4cf35e60eeb971611bef4928394": struct{}{}, - "294bfda9baa3309666cbbf507e3b437a63902a36a6f9946dd2554200adfebc0e": struct{}{}, - "e202474bdf0550cec77c0dad077a01f0b1253253b83289cc50b7de7b8e916da6": struct{}{}, - "9eabc6d9a24a9744ad5a1bb1990ec2231ab70a75031da921805aee89523a97e9": struct{}{}, - "8c922a76dd1e56f061da99834d2242b705d3264906108aba030094a6174d00c3": struct{}{}, - "cf98f59c0c00358277374644241def187c6ff10d7d6a61bf0eb75bd4476344a3": struct{}{}, - "90a7578caf8760be75c2b36f8b110073e494adf0a3264e624874098ed93acbab": struct{}{}, - "ae8987487e1fb5d6ef8d1b650f28e879fae719844d26db62e6c3d917cf51ab06": struct{}{}, - "9cda38ea3a2bd78cbaecd4b8bbd646abc14cee8d99c59860ac2a301f722b8be0": struct{}{}, - "fb1484ec19172fbfc3e662f9ffd80f2add2ec6ed17128cabdffb1eaf9551e3ef": struct{}{}, - "615899c6d13cff65381d9c0e8f9b556d3e8fc067a8b58a804620d896c8c06a79": struct{}{}, - "06dbfe29d4f9f2c3ebecca5de041a8136c6fffe1e712264216e50780a386a9d1": struct{}{}, - "544e2d6145f6ea1d858b94f75a392dac7f53df9403948acd1e939e6dfbc64104": struct{}{}, - "166a73112532054434c5de387c9523623b20256e2889b445729d1042d117b499": struct{}{}, - "49ca4ea3175e82bfd11d5f8f4511f2bb490f3a35d8c160cc17e78dae6b370ee4": struct{}{}, - "3346de30bd79ff0bfae4f822163244026e88b86f05e1b27baaf18062428f457e": struct{}{}, - "11c76099b96eeb56e117e50f4d9023fb6b1137b3b08edf16e6f4dcce4171a4bc": struct{}{}, - "2cb9dd97d89fce08f06fa10b0cb0e8929f2991e0a895fd83ccb608b327995121": struct{}{}, - "7b508dace774f8adc1b121c1812b740904be6dfd37e3b5e3579bc81fbf6f5553": struct{}{}, - "eeda7c0714b04fadb0387262c4709f030ae49f1c3663137862ce92b8eb171f92": struct{}{}, - "4a418d86c709e6a5beb5a338260592fc40966607da4e1846ef3af0860a090a8e": struct{}{}, - "66abc0811b0f7eb555275dd9d969ef0936da58a6aada8bcbadb7cb142d1f00d0": struct{}{}, - "50cd37cc9f893d13383ab62a6d2e2244033fef1c785728b6f6811294df2b1be8": struct{}{}, - "4cba30b1576a69a36b00387b97a96a937699eecc1e6965e95e468fe431c6a7e6": struct{}{}, - "382b34d681fca561eea04996874976b654ca18fbd502aad5f84de38ddf79b456": struct{}{}, - "b34161dfe9ded5ce0ab680af5781524607e5c48fed7a8ba6763141335dc0d82e": struct{}{}, - "806a7e7b173c799ef93ec90b450953651fe2d3db2468a560bb1a42fa16125ae8": struct{}{}, - "e0c69eae8afb38872fa425c2cdba794176f3b9d97e8eefb7b0e7c831f566458f": struct{}{}, - "6ed8919ce20490a5e3ad8630a4fab69475297abd07db73918dd5f36fcfaeb11b": struct{}{}, - "43d3778c6b4de7e4ddaec2de4443345f78fcedd9e723906adef99c3cc4bfb6f7": struct{}{}, - "cfb858c8bc39bb9b4badb01a58b2c385bc61c320c75a0e6440e179bb1e51b527": struct{}{}, - "1eb4d6a000efefe9aadbb4910424c708727d10c018bdb188fe09b4e07cdeed61": struct{}{}, - "b2133cc3beef2779a324891c7cf4ba62f26f7108093cb2d3394ab1b0998f4ae3": struct{}{}, - "28d320d496d43abe2d36cfdaae161fb66e874ab2bc760c0fb45b8e68e81cece3": struct{}{}, - "b0b889633adb992bd412c1fe80ce4fbaa71b99bd6966f55fa0c62702117583de": struct{}{}, - "f861c31e817d16abe1f853b6897bd0f55a51324a990eb74eeb42652a999432c6": struct{}{}, - "194557e31d7d5b9c7c91a0c7dde8ea18edbfd464d7e91aea114be9228b227d68": struct{}{}, - "a688972e74f0d9227476bb32d20f95039082213f5030933f1905563a765ae004": struct{}{}, - "3b03fb24a09b786227b3718c3050e257a767a93f794ae6675b9f838c60354e64": struct{}{}, - "355550250b12c4d2ac1b14b6bc9b1a58d6529dc9d21cf9241d01c7ea77e7b35d": struct{}{}, - "b6dc9083da372fed2119ace11ae9ba8713f7e30827e854371eb5d2335aec664b": struct{}{}, - "cc6255797ea7c55e8c7124dd864822ecb8aee3e7b349197a3b1bf401c8d8175d": struct{}{}, - "19706b9b7cde7d294853e6b9cca8e6aaf7187bdfce75cf19aa891b1573daa28c": struct{}{}, - "061511a165f65374b260089d411cddc5dcb9283302dc52c2057d188c5faf9cb1": struct{}{}, - "2baa32e509482715781eadc6127ec10f4b780a46c98c4686babb046cd2b5b277": struct{}{}, - "504fbd43409b143492f74263312add70e8ccfa04a0823f6cc71f3ff979d13133": struct{}{}, - "a1b45fb1f111dd5546c8ca507ca67f14c9561d662cea83827a6ce9f04e3b2c9b": struct{}{}, - "c0a497761b175379ed63397cc980546559faa84ca9cbeede773117c31508b6ac": struct{}{}, - "e59389d9c8173527647450cc7e781d201e8e7122f5d85c017528ea5c2add8bfe": struct{}{}, - "42918bcc531588a6ba0387bc1ddc30176c08c390532074a8685f118bdea05a48": struct{}{}, - "70f98078fb2c7d7bfb3ae17330b91eaa018110b03896979b4c88bfaed3805906": struct{}{}, - "244210e48437b6556980a70249a99369934a352429034cef9d7bd253b3bf2c01": struct{}{}, - "9f714d1e5a2306c4550a3f2a1dc7c8e44f6de4f7f74c5372433d83118fdab7e6": struct{}{}, - "64adee43129ba7373fe1a7145200bee34edc359d293efe1be60c51610ede3390": struct{}{}, - "6e5f6170d4e1f2560ecb8d0db7aebe03576a7504b7e305ba338f9b635ad02f67": struct{}{}, - "f2ec69646df3ab3bf9a78ef8137df16f0f94a62595345f4f8a1a99640dad8166": struct{}{}, - "a2bdc78162b620e9e831d1943a8320b320affa98a52441155013b6d6f0f95246": struct{}{}, - "0d9a8474f428bdcfc3e048b495f81fe7b18e8bc13c3bf20808dd9b4f0902bb97": struct{}{}, - "987658abee25fafd0b15a435191a77382aeb59d0ad39f6ae2661e0d877906808": struct{}{}, - "fa12958f03ef0b181d8a8a2223a6503496b2edf7f8b5949a60a93059a3b982ee": struct{}{}, - "d14b127c0ce938443163a3a8cbf4c02f94df976280ceeb6cc2f472e8dd90a625": struct{}{}, - "6182c678fea761efb447c6df8d5ec261bd81e36369b1f537283607574db931d3": struct{}{}, - "4e7c524ef47cc92470c4beb25253005553f9668108f7ed26f2f8e7c84a63ef57": struct{}{}, - "5e580eee1a39851ef636172a5b2939d4a51eeeeaa62dd4e01128d5ab0dc008ea": struct{}{}, - "8849853b957fe153b7056d0e7d65f99fb21070daf5122ddf1d7c942d4643c33d": struct{}{}, - "6ebf3c8d63ef6b217bcee69e31f77f3634bbbef1346de27e229c17122974e27b": struct{}{}, - "f39889eb42412593927c5136480e66f4ff0b813e071f2e5ddab70c14154692e4": struct{}{}, - "07f15cde5b181425db8524becd96263d600c9652c7de5b89fe1a644f8fd0724b": struct{}{}, - "110c6c5a86fa0de59f9170494cb9841e767243e1f611bb6c513b86e4641284c8": struct{}{}, - "b040d4d30e09d538f55f92f7d29947b7a7902174d0ee031e7cd5cba037796483": struct{}{}, - "152df37ffd521ffbbb02f065677c3f3cab9c22ccc4973443d4c69818dee70834": struct{}{}, - "0ecfdfe8ab2fd6704b0c875089f9760562ac8665c15cad5d54b569734b2d6344": struct{}{}, - "b88efb5313188196e9568e12a31baf521a4ec49a557a13f5a5ece3841e9bed38": struct{}{}, - "3113c9e45ff0b8e19f06e443deaa361cedb08d420b0e63606219cd5881f5fa27": struct{}{}, - "ee09198e46224875cf39928511ef2b855895c43ee86d1de894ee82bc7c990afa": struct{}{}, - "f9eeb63bf773f4b0854dae2f22b7c57a8cd129443f1712c46e7344e3b05f1149": struct{}{}, - "ab9cd482d68a0a4ec3d4300ec7d0f8986bf799195f4dcd18c4ea887a22e098b3": struct{}{}, - "6191544641e69f313bd06685ccc1259e2f674ec9c0cc30815d8532a6073e32aa": struct{}{}, - "1dbfed665657cb4bc80d72d7e2625bab7590466aab7a1a2bd76f80422fd0b4bf": struct{}{}, - "2a424875a8475a99175f543fa6fc65964e53e87efc161f7b1f9e370efe6ddd16": struct{}{}, - "528159b45697166be9a6bc3cf325f4234b7ca1c9658217dacca5fbfeff3e7a60": struct{}{}, - "3c2cd97763a54eacfc6db7c5cd2b2be639cd8dfbe6dee26abea1e79cc857cab7": struct{}{}, - "1c1b211f273d3984055dfad610a2898da223ddc2f33beb7e236ddc03e9fa903d": struct{}{}, - "5bd9dc7b756a242f1b00b118768734d2a04337ac3ea6d743cd59a3ae2a0f9202": struct{}{}, - "f20bb5c117e12d17ffb6d30edbd6af6fc37bf903eb80c600f7e483f7e36fe6d2": struct{}{}, - "3d0941964aa3ebdcb00ccef58b1bb399f9f898465e9886d5aec7f31090a0fb30": struct{}{}, - "f3d8f6ef539025ac9ace9265e1d7ae4f12528fc8c9bd1c33e3c0478b56710fa1": struct{}{}, - "75aa593103275bfee107fcce1aea432edd3daa8bb73587d7230521f908e7c053": struct{}{}, - "e049bd056f936d1b39b704abcf824eaef39bd8877fcbb82b2a213ceec82bdf26": struct{}{}, - "e7faa8b075ab5b412691a8b097ebfee4bb5fd87c448bfffc35ed519a449702ce": struct{}{}, - "fe91a760983d401d9b679fb092b689488d1f46d92f3af5e9e93363326f3e8aa4": struct{}{}, - "31059d093e280fc15da440c5f534c4f6a680d7e7322e85baa843c12237ffb396": struct{}{}, - "3259138318d8d440dc78dd1fda6e1d67e418a87d1cd4720cb9b3b50c7b8c28ae": struct{}{}, - "0db52f4076c082518412afd3dd3576e2cb0c63703fd7fed5e23ade60efef31d9": struct{}{}, - "5abd93a2ce1a00b5d51a00f17e3d3c20d71747a0b1306b5d9cde3fc84d84f0f9": struct{}{}, - "cae662172fd450bb0cd710a769079c05bfc5d8e35efa6576edc7d0377afdd4a2": struct{}{}, - "1275302e01cef631a7f8180c513d119a4e81fa7c160f337155da254d4f093e7d": struct{}{}, - "0689211a62ca8488b19cf30dedf9059089477c004924026bf7e1095f49a21b80": struct{}{}, - "9e299bb0a44dfd48b7d03548220cb0b402c72b8f0c7fa4b9dc53334709553ab5": struct{}{}, - "b0397da4f608acbfa6963687d0e2eace84f0bd095fae34e97af02f78dd20011e": struct{}{}, - "3f132995c0a8d3d7d6c4297505802cd58fdda5e8b63c879baaf54395519037ad": struct{}{}, - "d9705d8ee25f3f783a0c0a5e4b15bd7f6279dcfe11fa053504b2c14c5fb2a8b9": struct{}{}, - "8dcdbe1af332a3c8d792420367e5f2a259fb3f4efa7657aacd1f3bfc96d41807": struct{}{}, - "3fcdd536f10a79ffd728da453dcc48178d9a44d0101096af473612e3117ca82a": struct{}{}, - "9151c861ccd74f0c14b83796c20412f44608d8a5bde892904b673f94acea09ee": struct{}{}, - "d94e004b1713f511d7678b49c6ea78559a7366b5229f70beabff7f48560f8c07": struct{}{}, - "b46852fb83db1d391b988aa28b0c9857adb7fe1658571edb5cd43040ba87ac56": struct{}{}, - "3bea44f95b17af14e750df7ab567b80b5e9aaaab87c2b4c1204f2269f98af653": struct{}{}, - "e6c3da5b206634d7f3f3586d747ffdb36b5c675757b380c6a5fe5c570c714349": struct{}{}, - "970ec274ca867815174ebe4eff19282000f9495a6c7254e94991d1fb4dc3df30": struct{}{}, - "317d65ec8aa12bd7ef26ab15ed67ff049f73bd1e362a7a15d2389c256071c219": struct{}{}, - "f3257f39ab6659b2928769030e51382031c17bd99ba406cd7eeec2926f3be89f": struct{}{}, - "473e54eaad2484c6a6d7b2d9eaa097f5f1e20c5b50f447093a076e86daff8a53": struct{}{}, - "f3c2984127dd2db50a555194925ca0d55c3c7b676e889c9406b2e6875a67e29c": struct{}{}, - "6a62362c11e91c9f8205c47bbb30833a257c979f663b9a73d3c814ce228fe3dd": struct{}{}, - "34a389d790fc2bdd8458e5f1cde6933c97d11eeda65cf328b64460f0d68f04b5": struct{}{}, - "08ebdc04958ae808a153c2ef16e0e77d6fad0e259987635ce2b8d00e835a2a8f": struct{}{}, - "8c6986a3ada34e0ab3779b8b07d3c581b1dda49e9783de07c76b359a9fe8ffca": struct{}{}, - "f98407019f8c4af6fbb6c9dd39c6fc88ed31c41709afc3b758fd9928fd75c42b": struct{}{}, - "4f56fe65c8bd5296ca6a5f95faa0d65fb54b1ad8a87a1f816c7206803bcff938": struct{}{}, - "39c7ad800c5792108f3347580ce42c803545112e8cae98b20addb78a1e5295f6": struct{}{}, - "e73b79a0b10f8cdb6ac7dbe4c0a5e25776e1148784b86cf98f7d6719d472af69": struct{}{}, - "11102e6ea692987aa6358753f9d02cae6e853a53df3202b0bfdac11ecdcb6a31": struct{}{}, - "91d6cfb05ead27a6aae0bdfc2ee3b4c5cbd0e3fb8170e196beb2aacb8d1f6835": struct{}{}, - "27ec4ab2cc3f87a8c160fc241a7c331810a974573c60db010b788c600c644fb5": struct{}{}, - "83dd9b6d31cdc84522868c3963af3a0524cab6f91a586fe39359ff7e40874a47": struct{}{}, - "bad48609bec87ad5fac3cfecdd146c4d37893a0f1d4f8ab44dce94f5c127a85d": struct{}{}, - "faf9838ac653b1fe66cd949d9862f251532ddfefed66b69e45d918413dd7207b": struct{}{}, - "7c3136173017802c225d5c723764ed00b962cec6a55d83b713849e4065e2dd95": struct{}{}, - "c6d329865da35e1258725ba20aa2bb8f8df6dbd2d5992edce8db101252e2a1f4": struct{}{}, - "4552238334fd54d896d3ec33a499a386b5b23f395945373e053c8569135d9b5c": struct{}{}, - "6abd21d065d8cc0f4b3f25a5695a455d7906a17cb2198839849fb1647c47c01e": struct{}{}, - "9b58e68145a79ca0e19957f3e5c88fd92ff2e28a7bd09ed30ee365fc4f9aafe0": struct{}{}, - "650e077f4dc2bf2d8219bbc441ccd17172cff0eb667df0a33e944f0cf52373a5": struct{}{}, - "6f7e0b4e3bab4a864ad3baa6647c0befaa86d81008213f2cf004392dcb652b2e": struct{}{}, - "828408e40d4fe651488b14696b60b613c9683ab9862e03f4e72aec7c1fda7635": struct{}{}, - "93ff074ca77b5f7e61e63320b615081149b818863599189b0e356ec3889d51f7": struct{}{}, - "bedea6b1da0fd81cf23ca71f1c5a5a972f6796776b15813039dfed02526815b1": struct{}{}, - "4900d64ca9ed63269636902baf5ec8da0a86a832d271b134404ebedb5a407e41": struct{}{}, - "daf4814562fe9714fbfc8dc958c8e5dba31d352e9dfb8d225e41add4e71a9e0a": struct{}{}, - "f6b4bce4342605819102a71065709293803d966bca1979f788b5f2ca00633a09": struct{}{}, - "2be3fd90798c54c6e6c4eca5b1c62b2abab90a2cc901221c881ba31208b24a15": struct{}{}, - "bff91a0a6392e0d654ac35cd0464f60d99e1a126c2626c3f8aba72950840f261": struct{}{}, - "5304378d007825ccb10495d27c4dbddbcc77040639b83e69bda4ebea0703cfbd": struct{}{}, - "b74b7e3fcb623d805dacf98db27530f845760c47e3b0faa702b84e9ff3902c37": struct{}{}, - "4f00345a5e37c82f42bec9d7f9118470786728ab9806994a35b34fd3aeeeac1a": struct{}{}, - "8280871c69a2a97d89b67d653659c41ad21b3d06b1416faa70ba51259ef672e1": struct{}{}, - "9509e39121c0376bf8cc141e4545fbd9892039a88dac72ac68943f54683ef3dc": struct{}{}, - "742b27454919b5e1b2e834033f639088fc207c45a05b00635ec148a25939b6e9": struct{}{}, - "c3aa9a8cc69dc3bfa65a91bcb75b8522e72ab10dfb700ddc626bdcd572214b78": struct{}{}, - "235cf3677fd27ebeab75006f45ea113d3af7acd5273e97f0c0929e1a7e53000e": struct{}{}, - "1b34942add2c85bbca6a87ba114b9ffb1c92f1a6fde8bec4ebd98a4dd18d74a7": struct{}{}, - "c2f19b1f5ebee889ca459b3c8c7c6b81d1e81623ef51526236592f57ff2e4cfa": struct{}{}, - "404cdd7bc109c432f8cc2443b45bcfe95980f5107215c645236e577929ac3e52": struct{}{}, - "0f20a0190d87a4c4659060f79fec24fb0bef76808950c6b8fd4b52daf7fc23d5": struct{}{}, - "acfe1fecda8bf79f258fb28a532d3a5b901217497a8f7e86b6d5e81baa27de17": struct{}{}, - "26c98ec3b46b0a4f7bd5e464268df74a0ca7af24f2f1ec6cfdc27b5122a598fa": struct{}{}, - "9237995cf4b01b5a02963adf9160463cfc81418476c7cef6799f3271fb0e6c94": struct{}{}, - "f0161ee053b9d46b5d926b093c8d54e86816be9e1be28f34a9f152de6491df30": struct{}{}, - "67d9df3bfae39fd24f91ddc3cd9b35145b3fca9ff228d2bdc2953711e74176e9": struct{}{}, - "9d82529905cccc378c5b93cadaa0e57869d4da07916c8282a4e98a22c8f6a39c": struct{}{}, - "ab6fffc55e083569a7ed9e0ad1a7ce6e4e93ffdc0eba5445f1b8d2fcfb220369": struct{}{}, - "59945da25d2521045b4bc84db7d5fd44b2c5511fe7cc247a8ce5a79bcd74a1c2": struct{}{}, - "0d8f70470368af7741d5901b797cc898454788ba6ef36be8a14dc002587c76bb": struct{}{}, - "79996373623b60c0b7fb4b8915de9b094752ab718792ab9af2723cd87ff92217": struct{}{}, - "510bcb74ab0a8b40c6a4458e28a6fbe7ccc95a640769d24a86efa2c9bfb63191": struct{}{}, - "76f0362224621511964aa1cce31b91afe626c9ac69c6aef7b7cc3993d6239df2": struct{}{}, - "3e30da3972953c020ac40149f6ef4d25d1c28c8b9ea58e68bdd236716cdc932e": struct{}{}, - "e371f42ec5d6ff329ad988db736d34199f51281da3fdb61b189971d7233b9194": struct{}{}, - "c5c4bad89ee44b4da0321344964f145dd3023fc1ab0d9c2473e2716b788481ae": struct{}{}, - "eafe895eb8119e6e5d06463590b2ef81b3651c157d5c8e18f1889186c7fd0ac0": struct{}{}, - "c2bd1c67555df4bd3a537e6a02514604382ddcd01543476d1a032a0952f796dd": struct{}{}, - "dd6c5828ddc6ba888a648e27042199abeb08a93c381b78e73469c1a9a892350e": struct{}{}, - "abaf909df44257e8c67828aade41d8c7955e8ae1d76777c2da504cf20a25a7f9": struct{}{}, - "208c504354242a036cf51f3306f859c07a63fef724aaa70c1658b8f64d8c0566": struct{}{}, - "19a517a5258e9b02ddce1a03a44b9d76251cc331aaa41c1b1e12bffc8a7fc32f": struct{}{}, - "4c940ea0356e5be464daedec7435902a9a8cf68d31a3c0dcfe8cfcc2e997e0b3": struct{}{}, - "5c815bee4c7213b22207820cd9ad18b7437d60abeed6f78c3fee441dcfd2c90a": struct{}{}, - "4448517812432e09767084d8f673daad7f07db5a8ee04dd098ab812786d80996": struct{}{}, - "b221d9dbb083a7f33428d7c2a3c3198ae925614d70210e28716ccaa7cd4ddb79": struct{}{}, - "b675f2f6f1f675bb7be2e6694f55af82c76d063fcdf8c4606839d32bf505ef23": struct{}{}, - "f7aed67787d1ee094577f74598989acddd33a20f6d8abd760e65d56a392a9bb2": struct{}{}, - "a0dbbeef4fc2e5edf20af100ffc61313f0829368313f553d10e5f30ba6bc6ce8": struct{}{}, - "27d300fe53b3b94f115cfd63be02d868bcb8f755e56893709418084c1bfab1cd": struct{}{}, - "9ab2ead74b31b26891532db2a75c9ef720f74fec4a36b4cca245e7900fc8e020": struct{}{}, - "af8f827a0c20db87fdd3be7f5ac462a2efcde95a827dc9c2bdf0be2a6da6bcde": struct{}{}, - "554ba2997b63432b6edacc7f4cbf573dbcfb3e972fd277c047335886af8ade64": struct{}{}, - "49137e714810a6d7072111b40194c28ec1b1d6d5e891c5ceec50fe21eb03e6e5": struct{}{}, - "28780a3cd242408c72876ba3fdbba4044875334b10eef93c9201b55d4f98dd63": struct{}{}, - "88009219a0ae463e91ea7a71768a2389c1987393ce6b6bb5203bdb6aefcb9242": struct{}{}, - "3ac20461609ad24a20c40de4cf247ab37d56f1e0197096c25ed35895da32ab6d": struct{}{}, - "9a23acac1e064c33fada5abcdffa013860bdcbb7caeae1d0f4754316955ca093": struct{}{}, - "dca83e717b1f64eb141057a7415a330ad1361f51703efa2e4776f40047898a04": struct{}{}, - "a0f9ef9e841e1acfef0bb7f94ea435c8cd69c8e5aa5210d88ba439de159a87f5": struct{}{}, - "71051b81d921dddf2ef1252512314e4c65a3e6134c6a293d07e5ec0930de0b66": struct{}{}, - "f6232b17e7153479299ccd69aa8103604a8f7a249d5a5dc73f2f068d5062d4ce": struct{}{}, - "f05cf0e1b0f53e4962118589d0dea67fcc461280dc7f1fbdc297ba2ec3d1070a": struct{}{}, - "0b22fca0ad0a2ff8b00fe24a312b9d8b84d6219e5514a35023e2e86e15e287df": struct{}{}, - "36b4e265a5a06de1a897d6bcf33cb676dc306be5606bea0022bab08baddbff37": struct{}{}, - "713ec46512c8f37127281f0e10c5eecb7f41e493c29aaebfc3105e919131cc43": struct{}{}, - "8d9001d32c6a703d95921a77115050f33dd823d3f1730bd35215dcbecad6dc20": struct{}{}, - "103f2228c6271e5072d7623635d4fa9d5729a49e71a1dbb62c7c90bd3decda30": struct{}{}, - "ac6da7ef01460f0f0c6285b26868b5286c61d31f1b879d1073e783a02fa13815": struct{}{}, - "8a741eb9c8922e9e08fc1b0538535461c44d22bd48e1ed6bd7424f3fe7d78745": struct{}{}, - "24499c33fc2716389ac9d668b7dc31964fd15c900d51a20cc5b52b5c1651e5bf": struct{}{}, - "906db3f0fbd4678fac45d2d53f014cb4869656c7ee378f717dc439304fea5ee9": struct{}{}, - "728ccfce930df7514aaa74f83bd607258dfb34fe67a0906a6f00fec6199d11ff": struct{}{}, - "2ab248f36c5237774d9e16e682c39dab8e747f144cfcfbcaf54755103036abe7": struct{}{}, - "af03e34aa45c9b4771bf91825a0f1eebc4b71012dd28640254876fa92a81d2dd": struct{}{}, - "50279190d022bb3402ad821cb65fabb05c21c139f65ecf67ea0305ff90102ccd": struct{}{}, - "ed121aec34f231b951ace7d295590585427e9d224b316d296a8ce0a3797e97d8": struct{}{}, - "1e9bc7186db4a082f011c88d3cc1a2ef5ad1320f58a35f7fcc36f9c548a3cae3": struct{}{}, - "a6c9d976140ccf54d2fc91a9c96e9ac40fe5400ca7c04d166fd443d5d99c6a62": struct{}{}, - "46217c55895d5ec63be131d605d360767ecafab58eb0fc2f2933e880c1ecae41": struct{}{}, - "d0bd3cab2080dc8b07d92e3373c76676ffd9c70659a6169ffc8b3ebddbb119a7": struct{}{}, - "e4010593d2e934d21b7cad2594ab0a3827e183d707435de19912df33902a8b14": struct{}{}, - "94baebcb5e5e0290f41dab6886613e1e89d92554fe2405a42a1fdfdaffea0887": struct{}{}, - "84816ff383c826e9c11e26fdbd8d66e4edd7cf1ec1a4b23f8d10d84aa56e0d8f": struct{}{}, - "5239ea501ef63222c9321261bea495d46d2f8b2f7985d80bf4c7c4b542d6bd28": struct{}{}, - "7ecfa4d8426ccffa072c931adf09d9c292689e1f761a67184a611d2dbfe40646": struct{}{}, - "fa0d79033b24b26ff7c52fd79990906daf5ed2b246824f700eb4d3a705fda35b": struct{}{}, - "4f04da6477b991afedb52543e62350fe65f0198703f843340dd57a2a4b1d282c": struct{}{}, - "7187f0675eb3827939741acf7342ba78836ecec21a31ecf3f34a55309d3bee8a": struct{}{}, - "8ba35ebe3d5c74320285c75c019a06197837d4b53ea93a3f6834d725be957117": struct{}{}, - "f95e67f453e632eeff8f474db94e95897f0bdc5d1b7405c8e749b72847ca6c6b": struct{}{}, - "f3daf83f5feeabd2c18068d6880dabdb219464065b7d5febebc64f38f188fdde": struct{}{}, - "17f1a04a96af62070f02357456e4dead6575cf8be0ea4effd4850552ba7bf074": struct{}{}, - "edc254af2701b950236d95fff251d7a765a6f20df5cd93c917f11ac6d98814e2": struct{}{}, - "b1c1e8f5ee88d9eb4e64952b686b10ad52c449a953f1c6ebe4d7421e5333fe54": struct{}{}, - "42db918fc5b131df622630362b1f61b9b16bef2381556399b98695bb9a2373f9": struct{}{}, - "446925ce895da089979b9f663aa808ee89c4b01a7387f59ad519a536740a8e7d": struct{}{}, - "955cbd535dc97340ee966e09a4bcb72069541ccdcff13d8e3c9f572c47f28866": struct{}{}, - "9b7db63b1e95c2891660533fdcc7e5300af42e36966247b4928db477cd5a0f77": struct{}{}, - "b9c45bea047acdbc383f4a0f14b59ee58dc3d7ce73d3577e7cf9ef2804a1a30a": struct{}{}, - "f6382a96b5d6341052b224283eb21da05a39d13a1d57b17bc5b525ee6873ad9d": struct{}{}, - "0358f6aa59788a6deebad09b3ab91f6f7dcd17363c621064be75e7a8bc588a74": struct{}{}, - "36230b2d0f53d53cc1e76485bb3d2c77f878b7ecf5d8e04eaa40b52bdb135c3a": struct{}{}, - "2fdfb2a32ee1c40f9c00c987c2d81788e6e968ce271b8daec716447a390ed713": struct{}{}, - "4a2b5e1822ca11588586eb912320817f3cf0c11cd5ec8937e78a8209505f4e84": struct{}{}, - "cb5858524fa74946781dbb0e8fff1848c19adb2e2d8cd136a2ab5cc2210039f3": struct{}{}, - "7fc2d4968d2e68c6f8ee5b883348dcb2c2b37d19ef12bb1023169bbca33b529d": struct{}{}, - "25d635c647506ddb36d60816e25ca098795fe29246e71ab59dc2ab8d303ec49a": struct{}{}, - "438183683accfc370de79832e8ce11d734bbfd6b57d43d424a47db054ce9c00f": struct{}{}, - "5d09224b66e553f1e5bfafb735cc2ed098bf2ffde7696a58ade5c8303538dde6": struct{}{}, - "bbc093b9819f87c7fa17f135d95ad07ad830ad4a8e74ff0c609709809bf1f1e0": struct{}{}, - "78d3c5c059cb1b1868d3cb06d811ecbe8414eed0360320a0cab252998111d29b": struct{}{}, - "09a4cf7fdd12c74bdc4fe0321f1b40222756606b9fa9213637a158f0e6ff1fb3": struct{}{}, - "2bd2d3a31934d76198acc030caca4c31965474fe5fa48f35fef79d0fd74ee1b2": struct{}{}, - "ea41a3ca621c24ed11f9e99915e88b2db0d650fede891b456a0f3eb36482cb8b": struct{}{}, - "9dc08e4033827c93f48cabc2f61e3f9817b51a7f1bcfa513a7537dcbc5d162e7": struct{}{}, - "7e049cddb0137a8e06a01920abd7cfe090f7d774aa39338c0a28e56edb18c2a3": struct{}{}, - "d7bf0c9ea6ea590ff1a44ef444aa38d0db5491e29fab83efbbbe882ac9314e84": struct{}{}, - "522788b65f01ccc096a0e2f1147aeca5f4052ddd88059f3ec367ef14ae9f2183": struct{}{}, - "3b96c2072e1d9bc4819b34fe96a64166c24ee1999465abaeaa05c3e8d5a516ad": struct{}{}, - "9f032140ee068497c8308af82880da3c90f4e0cb90011090926073b88823796e": struct{}{}, - "c64dce4aa7c48e5261a5f6840844edc9aeed06d11e8c738c386584304af12f53": struct{}{}, - "e710290485ec5c7092747afb2f19c313b3e7511182b514fbf75b3e91b867fc33": struct{}{}, - "6b41c8833935982aac8608bcdf0f768688067844fdf6777729e83227eaa417bb": struct{}{}, - "7ee29bca117d5f53856056edaa7a01d849e01261b2680df7d14e320c78563484": struct{}{}, - "94a9c6fd83039f5d9bb820a93ceb099ba1e74817124f81adaf38df9d34c7e5ef": struct{}{}, - "0fc4816bb32221ae6b754a5fc2c9dd4c33257c8a6e784b9492f76071668d762b": struct{}{}, - "c2765df0ee704b5cdf647b9d4e6852d5e8c3c98cf451a5827d6db13eb6775da0": struct{}{}, - "24a86a184ecdd7e67fdb1d3d5e7624ee64a3264f110a0b3339039b64c3f3f866": struct{}{}, - "6e48a35e9a36e1405dad7fb5311090f02680182bc8c323738542e4abfa8d5c20": struct{}{}, - "4c66d7d593d3a8f1ca6c32a8124b7a6ed61d070212a79256338590fc43c4751d": struct{}{}, - "8cb3018c43ba24199a91ea383068878effbcec417e9f8fe9e31a38666ef16ffa": struct{}{}, - "9cceb6b9b2c293de6f924dbe556ca9740c2f54ae993044aa9e6e52e0d10747d9": struct{}{}, - "c8ccaa4383657cd5e791388d7e5bdac754d834bd2509a22017e0419fb1f2344e": struct{}{}, - "bb7aa507ee307613a18d2b1eabb42e76a5e3ff8a00d021b738f6a5a8f299ed29": struct{}{}, - "b51ccb65551f8d2fae1ad1ff0d33e59dde42f3d962f0bf8f1f3cce4391e4f3ce": struct{}{}, - "3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7": struct{}{}, - "f62e610834f27439cddf899d823243a003e662680789d3d1a84618c079f1cc42": struct{}{}, - "88bb047bcdfcde8a8c8eab6d9aeb88dc63afa21f8109fddaea39648aa6481626": struct{}{}, - "630fbed3da16a488cf0a78372a9340540db1757556b610dd60c8e55f178a46db": struct{}{}, - "127f117b2c181f4b882b0faad742f2eeac1220a260e07a789a3e909e261ed277": struct{}{}, - "54fa7b1e5ac007419a678c0bf7cd6a92c551c1543e0752c3bf40113c7b1ea92f": struct{}{}, - "2fe3924cba8cd9f1decb699a6ca633544faea07b81fdf6180066f2aa9ec1bbb6": struct{}{}, - "43ec0947516f5f6e29b37475e009d916792e7f637de6aa8471d933ec58d6418c": struct{}{}, - "8ec71d0d6b17a5c960a214d88d720acc4d467d9d1f589edb3bdbf896b8d45537": struct{}{}, - "c81a7b1e755bdf87160ff008f94c8ecc21bc2a710a23bf5e1351300edc0231a1": struct{}{}, - "88b3a463f5a8b77c73434ce59fc1f5e08ddcb07c8cb3202b374c486bcb029654": struct{}{}, - "6853f8a7e0611b7ba4624add69fa7b9673d1c6aa07bb1b9950cf65bb67ac1657": struct{}{}, - "7a360decea30b17c681612ecf4ee74e233495cda04a53b70fc2109c7a354ab0c": struct{}{}, - "afd1a8fffb87f7a091c924e0b851f41be19d55450cf7946a5f03cc02221b6481": struct{}{}, - "a85225383333b5121c283ce29d19df80b965ab18c2005e36234b542317463bcb": struct{}{}, - "dd71c56d96649d33f10d1da2c367d6275c4ca033d9339af12ff9e00808992197": struct{}{}, - "3b331be2afd45c4056555aaf080a2b2feb757f76ae3cd0bd9085576ae1f579e4": struct{}{}, - "03df4be6896332d9232e70af232135b7aa9d54105c400fc9574ce18d3c311103": struct{}{}, - "7fb754c0792cd52ea50ce5b8862b5b07d5aa45af1dbfa035f862cdb6a030b427": struct{}{}, - "8ba5ef4e282bf7bc5cf13f731a1b9f525bd3b1f69cbfa24c1c69c303c0ac7019": struct{}{}, - "8ac62741510b61a53169ed0d90f3485a26202c538bdf548420ff5f0157e3ce66": struct{}{}, - "4a8d0a7e4affe6f596ad8f64e8c4bde71ff0d91a5e4eb6f9e3d9333977470283": struct{}{}, - "00762934084c61ecfe510385161fb6202717e9ab0375ce86212cfab59b0d2461": struct{}{}, - "6e5cbe23514b46b1496fb18ad46c901f436e88158d5fae98476e84b5360723f3": struct{}{}, - "5ccdf895a0ea4ac0aaee8bdcb3f94df48b9690217dca1c8c3578cf2f0524d32d": struct{}{}, - "4e0227879fc12b66dc2571615a5466cb7bd2582efe1c00d888e35aa0cc254089": struct{}{}, - "01ea5ddd3be5477ac3eae2366fa12064615c485a541682db3e62d283241055a6": struct{}{}, - "b6ef5d1ff0d73da95e3db1dc2d8183f97ee86f054eea14dab63f12c5fae5d5da": struct{}{}, - "557e4489db5fb3fbe2ad822a6799f9cb688cdf730f1123ed77d3cb1be264bfa9": struct{}{}, - "b3d7489cb114471f208b090bd4eb673ec0b0ab32eb1be8fddf82a0f4dd9bd71c": struct{}{}, - "e3c33e889e0e1b62cb7f65c63b60c42bd77275d0e730432fc37b7e624b09ad1f": struct{}{}, - "99f3c4fc54015e1c9c1fab57f4073d17c163d76265cdba7344653b88074969b2": struct{}{}, - "802c9d6e6a9085bb07e1a0ce0b264aa466580c7a0485b5b7b42819eb62540ce5": struct{}{}, - "6f021f0268d5dd0d4e286ad5202f57335f2a8cc568704c4f7865c97879443ef8": struct{}{}, - "b0b4df86a1580b5f33f2b436c83219b0d7efd32209747fbb2883b70855d7c21b": struct{}{}, - "7793ba1ad02f2d82e91cebd75e0649fe412476dd3907e8d4213450a9191b1dd3": struct{}{}, - "b51000ef0db2025c124e92cc29880418e2cefbe7fa5badd472cc32c41dfdba57": struct{}{}, - "46f1e1f5f994e395ab93e339cf5d486197b94bb72cf3facbbce31683796f9c17": struct{}{}, - "3dd9c0995d54c0abd51a90f1d57b1ce77bc885fc8a7cea52dcad3c2540dda5ee": struct{}{}, - "20ec7d50aade52a87959a8ba5b60e42036750f4c847836e9d319ddf477b5bdc4": struct{}{}, - "dc2abe4ed2d8a5492838ffcde8903ef6372ff67bcff30fec79d427c88a01a58b": struct{}{}, - "ba36afbec22ca29deeec989495f987c0e032d8394d90583be22aaff0cf3e59ed": struct{}{}, - "40c7eaa8d3f64f1ca12cd50bf1741d2183a2c0cb430ce8dd1d248a81fad3d9ff": struct{}{}, - "26a8dab0bff77fe1821477c0e0b7c09883419b136ae6a745e85a01af664d2db3": struct{}{}, - "d087ee8196afa2ff461aed248e9b90d5cca64e9fa0dfdbec351849610ae12c47": struct{}{}, - "b59a2aa4cfb38f4a6dfb1ebe4ef42c1b321afb7543086910c06e8c74f115d1e9": struct{}{}, - "4dc2159bba05da394c3b94c6f54354db1f1f43b321ac4bbdfc2f658237858c70": struct{}{}, - "0335aa820af252dbc11b1030480167406d0a444f4733234ea84dd9d99d8afcce": struct{}{}, - "1c3d8c1cc1f56cbf3d959af9878c999048f7fc032ac22e12c4f1c6643b6ba620": struct{}{}, - "7385cae611099e13d37640d1c0ee8d0b7e577ebbd7a62b047a879159038e2fdf": struct{}{}, - "fd782ad6a7d31feedc1ea128c85526be3cad24a27ff92ef24f562eb52ac5dba2": struct{}{}, - "e2d3b41db9567ddf0334e14b2ff58556581630566061a0c643fec42d12c47122": struct{}{}, - "faa69b10d8d1a8c427e71839a0141e4e83a99c443b5c16e7f7e1cdaca96054de": struct{}{}, - "f5965b14af6932a1595cf07db72e0968fa52aec8e8f661c732f8c34df7d8a9ec": struct{}{}, - "1862981fda7e0b434adfa5885db838ce5185c540bdfbf8fdc88ffec00d2e582a": struct{}{}, - "7f912abfefacfcb55ed0a87dd25f2c471d52d9eeb9c08983b52585472e9319b4": struct{}{}, - "469c0fdb6020d4bffe12ec93e76150a08ef02e116fc856a84b24eb02a1d1a155": struct{}{}, - "ce47cbff84ae1a17c23f32f0f19cfa93a3fb04352fc47f8bcc1a6b7955383d25": struct{}{}, - "1ac880f97cd8bd06e6dd7d319f774c7ebb908e349d7c6a10545e464a815ab1d2": struct{}{}, - "085dbed2039f5b5e84d655868004648132533bf1964c6a1350732c23d12ac359": struct{}{}, - "9b60072912a0ac132010e90dbf8230e4e5c5187b754c986122490238ce00c712": struct{}{}, - "7314222aaec2219364deebac62e895e600be38c9cd92d1fc38d07fc6b28ded47": struct{}{}, - "888b90a08f6cea93eec0441a86195799d0fee5d2617e18b97b1f62275fa63a31": struct{}{}, - "534064cfa2ab0cd31c8c21464a1f95f29794457d707472afbab70d02e0266110": struct{}{}, - "63d81dc574543a4e9eca3a1fd5f4938a8ed5b1fea99f681147c0d25c24239ff5": struct{}{}, - "8dd9b085ad93930d5e9930e5b7e7007ef703936bc5a8a036ec0e394f07d0316d": struct{}{}, - "e4d916fa9d2e6d4c0d993976bd68952dd00add15ca1289b32750280b3f3cd45f": struct{}{}, - "38a34def2a6913f38145afee607e4463fecd8c1b053ad3c0cf96dfca0c40e973": struct{}{}, - "7a039460d488c46aab1a70265bf960d045684b20ff94c89e65a284f6a788a069": struct{}{}, - "03fb94180aa0ff2c583d97bbb1fc6d84a6ba7df5bde3c267459bb9a9b8c9841b": struct{}{}, - "b815843425f6ac6c018ef9ee434b4a401202a8b175ded4b53b4ba0fd8069887f": struct{}{}, - "f59a9f36d8ebd94fd2e2574d7db879f7bf586294db4cbabdefe5fd7b1be11739": struct{}{}, - "11336bc0d91358ca2d117e81fc24e61304d90b7e86139e0fb2fb7ced2ff9535b": struct{}{}, - "9d0c52ae7023170cf8437d8683aeee910389238e7792b12c08f04f15c305ca9a": struct{}{}, - "edf86c8e86461f782f02ddfe965b28a1e97dbfaf3245a76088896793793bc77a": struct{}{}, - "bda0f4154d3677a2db14b3bb26c4cbe77d6f0a750dae95994ac108473766dc81": struct{}{}, - "c278be9e224723ac90087b41a7b39f53fbe57be34a75b27d5f0659a7c130e977": struct{}{}, - "12c831a6727403add81e8f458d91047f7771edd8b1f717413d5ad02ffd01b795": struct{}{}, - "f77b12a53ece5f6b7050800bbdbf8cc5ebe87f1b1387cf739f243e43e2ce886b": struct{}{}, - "36d812231eee1a34cc6c39f00b8593d70c0d086e116c4a0212c58bbd507dba81": struct{}{}, - "8b4df85a07e71f5eaa57b5061838c44f5e1ab972a37e8051f85afb7978947df5": struct{}{}, - "f91c3b6b3ec826aca3dfaf46d47a32cc627d2ba92e2d63d945fbd98b87b2b002": struct{}{}, - "d8fe183f5b8e732496891b98c9ea53304c704f12714cdf92873401055195f99f": struct{}{}, - "55aab7a86c9641ea86cbf30914ac9311d33bb4fa19849cdb4496da9d0d5f29bf": struct{}{}, - "c7d253870ab8de3825e3a9b5ee603e21abd0dfe62763e8e2fc1fc9f4684e8a19": struct{}{}, - "7b8bd6c0abf53d22888beafc48830e1156907dd4ec7e6ea31e55a0dd6dc5a969": struct{}{}, - "2032198f0e6cfb437078fffc5c102d2ba8bdaa22eabf9d6c3c8a32325dd9953e": struct{}{}, - "b4e0d08459cde1445c9fd150d3b796dd9ee08cd112fe9ec879288f793b6ac1b6": struct{}{}, - "d120a3a7c6bac69d03024b4791da7de09d2c07cb34f9fa9565b43c80ea9fcdd7": struct{}{}, - "2b2187b4797fb4636522605f09d5ea0f95534c37e9d363508c220057af6eb41a": struct{}{}, - "50cd57a690ca220dbf82d077b4ec2c9a7f5bb6434930de2856a6e314c6f1b840": struct{}{}, - "7d76705ffdb239c87d78f21ad14e5679571f1135bf8889e94837a06a6e2ca6b9": struct{}{}, - "127232940cef437e141cd826ca6d3bd3b94ca5ebd75e0b24e1ac9d67e8bcd7ff": struct{}{}, - "3331cf8f9c61d50160a9421af6282fdae8688e895035a5e8aa6708245f30a898": struct{}{}, - "b281bc2c616cb3c3a097215fdc9397ae87e6e06b156cc34e656be7a1a9ce8839": struct{}{}, - "78b49fb2cc2d2ed6c1bb8383b3d267b3bc623a8a0fb4aff4aa5d3db74c3b4967": struct{}{}, - "6305ab6ec28f18b46320357ff1ac07fdeab209690f81c3169f6720168076c92b": struct{}{}, - "b411746bdf09bde7f1fe70ddc8fa57241a0cb79d14d6e9c27e598635ca7dae4d": struct{}{}, - "0127ea1e956be78c62bb103bd6453eee623c2c29f526344472dda87757eee7f7": struct{}{}, - "c183fffb6c55e10e291375e968e985f506ebd4527e2344db681ec83e7a1b70a7": struct{}{}, - "b4077d14684da53e45553f3ed6363c6775e444be5583bf09d4e4b792776a07b1": struct{}{}, - "baeb0e6d5705acba33d1f6ead0c8188dd687f5a5c482ebb08392608ec31b3da0": struct{}{}, - "5953a824eb25859ede2c13acce92616cb1c2ed2b1fa4e9a1e3cbfa21bb1dec86": struct{}{}, - "f00dbe22643b0203139f12afc188dcc9bea52dcc3da1a2ca4497042da79f2ee4": struct{}{}, - "1b7c4862802f894d7c079550ab7d1aa58dc9e92d60216a234680603398f719e6": struct{}{}, - "1bf822ee0278440b9aac6e5d96ef81a92e4257dee56416c4a6425611a9068540": struct{}{}, - "89a179b380e3dd6caa18d544b6803e13f62ff0f8ab3a633ceb4fac71b2856f53": struct{}{}, - "1dc545b102daff0fb59245b92b29cb92da244d432047606c341b34a2ffbb5a87": struct{}{}, - "4aa59b5a3761c43828cf2c0f44a385d94d078934936482f66e573cb444e7e481": struct{}{}, - "b718f1354f7247312eca086d9a024afe5fa717ddea5adeddd6f12bcf945b2e8c": struct{}{}, - "4d883ebd4e285e6e0f33a3bcb872e194bcef129080d5fb611e91dd1cd3d3aadf": struct{}{}, - "9caeb25ba8e9ffac919cb1942ac60bda5b5be27499b976f7b49a3b7c9627a164": struct{}{}, - "93c2719048aa7d51a3fe4af830f02bb67093ccca8c226f8ee9a18010fadeb82e": struct{}{}, - "a27d6fe29184d35eb8a103c02c8fad6bbb74b39b1299c4f45e78278c4d06f6ea": struct{}{}, - "c7ae6d1cd92aa8ad14012b85cb3bcd9fa15c329bc607d0ef48a384a4a9f8531a": struct{}{}, - "1143fa1332740079b7721205c8b679390a90458933064c0d02590755695ff3f4": struct{}{}, - "51bca1fa4eb57493adc2ee2d7bef301816c907db79d292cc8b477ed3b010dffe": struct{}{}, - "bfe0a8205d1917c9f1097aee9ea4f150d33d8e4527152c1ac435aad767293fe1": struct{}{}, - "a24efab8d957af0a0ff2e7d94dc6a3715007a1a4b5bdf02b79a12daa0468c934": struct{}{}, - "8a2fcc00d1d35ca4d12ed93f0eb7bbb5c8bebe1468f27c86e404ba407f1fa37a": struct{}{}, - "d0565bc4843c5bc8f340a66862232baf1a88f7951496ad6d6bcea641c57dc111": struct{}{}, - "a4c632e31d600fcfc95effce7974069074a82804e1f3bdce6296ce3b3e284abc": struct{}{}, - "eabeb12fe2b96e94c6518e259d6603f7c4ecc4dd42af6e6d90d16899bb612235": struct{}{}, - "2ad6410bf05c65c58c2ee29265e9788c0535b674d0b9988485562e40c1ec04eb": struct{}{}, - "80f70433d57288d62ef10a048a3c4e393d647fc0fc89bb7282694c81c9bb49a8": struct{}{}, - "c81d50bac2606c2f8cdcf54377a66a2a508e599de0fe853fff8affe9c48825c7": struct{}{}, - "23b2a54839ee86a3ad02b56727a10e212689a5108f0daaaf2f548a746892ffd7": struct{}{}, - "94b5f0fa59e958580234a46304f711dbf0276bcee26ce8ef14e9c750fcaa5a2b": struct{}{}, - "f45061436f0ba676be99f099299eb082df9a0cac60b1bbaa292d5efeb16db73c": struct{}{}, - "053ca5d2b4374e09b76c872b001242b9e035d992093671ac9170c70953d07428": struct{}{}, - "ae6eb3decb4c5ff0f157f15125fcaa0f3ad5c7e0c6c3c05056dcae6f34006272": struct{}{}, - "2d8c911ed45699a9ba26b93c45d3d80f96ad103db0be19323cba7713cbad7501": struct{}{}, - "65b2444ab8093a8ec9cbbd207e13603c432b76d44f72be2dfa6fd9440d81630a": struct{}{}, - "343e398e4e99a68e7de6ec57f00b6a14a8e6d0a7dd714efbab4dcbd385f4f303": struct{}{}, - "068b7e6c97f2284a48f4d7ff0fd0bffe449f4544da079e04cacd52aba5df28c2": struct{}{}, - "6f931093b5e93819d2daf5b5a30d440377cfcfbb680e0c4dd6c2f2acad2d4e92": struct{}{}, - "d8198efa3604d164853468608c55efa148bc56e3564d5a30232bf98b8ab43aeb": struct{}{}, - "59ed62437509c72701513168692ce604e3b94883ea8ecd0661cda983b0c8aec0": struct{}{}, - "1f52f85774b71b2e058195d7da19946327faafd980c0b686dee20c10ca358d8e": struct{}{}, - "7930bb539a0fe39bcd75b2173c28a44341fcbe7db4c71fdc1e90e170dc4aa36d": struct{}{}, - "84938191021c881180909908525dc2124cbc214081adf074cf609de9f9ab5ab6": struct{}{}, - "8dac437f247a83699011992c993adac7cb899e2e6f10f336a9a4fc9f694e91d3": struct{}{}, - "9a97a03203ec8f8591b5a6031f095283edf2c1f04ccd9dfe5ecd468ccef1ef1e": struct{}{}, - "3b9b05c720497601bfacaf66e58e8e37e3545ea9ab671665ad2e72e483c4872f": struct{}{}, - "ce741fee73f964f4d36ebc1d1ead6ca7dacd444c87242703a28f998d964b0306": struct{}{}, - "e84021f134c67ccf75a344c553cdf14e61c5a49fe5d93824f98048522fd7ab09": struct{}{}, - "d09710023003ed91f4ceba910c0e3b956855238e43750b4ea3947fdf5ad11e0c": struct{}{}, - "d0d0c04e795f8e380e0128593cc32518131ef3a093cf915e1ecdd6958b43be41": struct{}{}, - "4221a8f26bb25551c649dcd39d478bbc891e97d2878cb04754a4f14157538ca8": struct{}{}, - "bd91194c43467a38a6187fc6635a78acfb5fa01ea9b8ed6121396f84b71e5ee4": struct{}{}, - "d3cd17514917a9abcccd1cb4c3adec256d9429bbc7c7d483d6b38b4d6a025a29": struct{}{}, - "5b75539f8576e7257617c81cc1c888fb90fae9526cc9caefdeb5a1adedac18a3": struct{}{}, - "0ec99078e538c33050f7902064f3c203eac239077dddd511d2274ebdbd8962b4": struct{}{}, - "4419c07588b7aa80658eeffee32e9ad2aee30ca9de98ffa96a17cbe171e2a726": struct{}{}, - "9058aa3ff37e12d036accd5f135329e6f0457c6fb086a3e8af06181861d6e9d7": struct{}{}, - "3db11731f4fce639e51be160b4eba909000bc9f729cfca45a4cfc30f39805cb1": struct{}{}, - "b39a60f59bbf47bd5e334bca6a223a8111b4cadfd181e92db68994940989b475": struct{}{}, - "74a9f66334902d69b4a15654c0ecdfaff8819edd73ae18ab075f779f1df225ad": struct{}{}, - "f13a72ce0108ae051250aece3707655ea7625002f0ecc987bc9b5e9b516eb5d3": struct{}{}, - "1e38dc674b89f665fae69c89a8885fbdf8df4a7df139d1b621c532e41e378b16": struct{}{}, - "3072c9efc4158f770338c9f78850df6cd670791e81784b27ee610b915eea2682": struct{}{}, - "d7813b652c7f46fd1cbb901e4c467e8ba13163627c3e501d6a2c069de8dae849": struct{}{}, - "d45ec3b2bc7e9d583a4c46e3a07e96bb84ad7628dabbcda73b8da150c8615ac6": struct{}{}, - "a7fda0b61e2047f0f1057d1f5f064c272fd5d490961c531f4df64b0dd354683a": struct{}{}, - "c57a5df4f97ed6914116e5fd1348406064834f22503d890eb571af442ac4b4c3": struct{}{}, - "f71a59e61939400f3556358063bb57fc445c7165d6062754950867406462ca93": struct{}{}, - "c74cc85badcf13b0d9704ec9f8645aee901600d4167e32d00527481fcb8aa25f": struct{}{}, - "249d1f9cb846410c407c6569aa28ba7ac9f8cd6d95685ba6321a627fa0997db5": struct{}{}, - "7f17dfab0c3ea669fa75ff5e107a8a56b619f40622d584887ab0b85f8fe68ff2": struct{}{}, - "7957bdf8ea97bea330a7e03361617f09d58f59a9db836ecac295571e45a6314e": struct{}{}, - "ab080737bac518b30720f4cc3012b53c6c9c3b444cb337cb2319b95f386fa099": struct{}{}, - "2f8a0c01f668bca73ec51c0d8da77d419582347055e99d5d04fcf73b47bebced": struct{}{}, - "a4895eb44afc336fecbba6e520cd67e178dace0276655d102fceffa8e5f70570": struct{}{}, - "3c9a179ca98d39da020b44dc8e46a80dfddb79643ab181dd4e1bacc0a362b475": struct{}{}, - "3b06360941a279cc9224516c58e8edabccab39cb2c4cfe85ba25b1d745aaf26c": struct{}{}, - "647308d9030070514ac1045115ee02cd480270f0c20333f39ba612d6f08fa373": struct{}{}, - "87059bb5dd92b899013cbd41df01c73807738a8436b4f0b30d4593f4dc86fe44": struct{}{}, - "81803dd4e410a92bc5d510ea539a5d38493e3fe26b707dbac6a63b9e3a7b4d90": struct{}{}, - "932f3c1b56257ce8539ac269d7aab42550dacf8818d075f0bdf1990562aae3ef": struct{}{}, - "52a6eb687cd22e80d3342eac6fcc7f2e19209e8f83eb9b82e81c6f3e6f30743b": struct{}{}, - "521141b8141c44f8e248ad5b5b39ec119c411ac37f669e822c144a78d5ec411e": struct{}{}, - "909c99c07883cb8400be24cef9e172712e27b87f4f843be129f9406ff8f2ae11": struct{}{}, - "27d526f25019ed96d116e69736c50dd98f06ff1e488165fefa48153af64fe3f6": struct{}{}, - "7aa7ecc2a319385e1fd583eeaa85e8e32b7c90345991926762f0d40eeb8b567f": struct{}{}, - "3cbaeb57c64020ee4df47e9274d1d0d90f3148a3c14af8190006fa680fb10ebd": struct{}{}, - "370aed697dc503aecee7d8faad5de775f4f7019c86e672e8d93f3df590527058": struct{}{}, - "e11886944efdd42aefe9ee792b0bd5b025f3059f98ce1423877f8a17ab23868c": struct{}{}, - "f021639b1ecab41d3e8a465610493f0af062e10dd5f4d060dbcda7be827d6f99": struct{}{}, - "ad738ad3ef49c04909173e4927aa2a3a50198bffd7b1dcb43877ba3e24c7f04e": struct{}{}, - "a79dea865e52486df837e202c48b9b2c8805df8ae3700b695458dfe73a55d752": struct{}{}, - "a40010d6046616915d64e2b0c761a63858860ef38d35a3a19193d6e42ed14d1a": struct{}{}, - "8dadf41ace77edb63e91704b1a1ec79f1ce8f2e80300fdb51bd3bf59eac770aa": struct{}{}, - "c0d2856b74d0df05b9d4456b177950351bd88e98b77f12574dfb7a911acee0d0": struct{}{}, - "4f7329d42fb0611fcf8fe4bde92019258948f69a09d42b74c4b32cc05f3ab2b5": struct{}{}, - "41c0838dc92f978aadd91106a57715bfca9b4fdc671c338c54624748167a5b3f": struct{}{}, - "c5dd6aaa3e73366c702ad650a118a3511b107f6e2c7e3a5f54bed2075308517e": struct{}{}, - "fd6e050d8cb25086ceee209cd5a70ee163365e20b1d971e3baec0cea43e7853d": struct{}{}, - "5980c38e1671df6744e665feb0365be2f64995205242458a8abfb072d5822b2e": struct{}{}, - "c7311d4137b8a37bc1218bec360eba3db8a7a297a82b25f063547054036838e3": struct{}{}, - "7cc44eb19abccf91739a72392909971b727f3e4220422df91bb1eb56344ab0ac": struct{}{}, - "bf3a4b89f937367e3b8475a151544c7240b5d699d95bb216e5f5484f8047cf83": struct{}{}, - "a1edfc930ac08f267a53dfd890d30ea2b668a326b51397124a4e16ee6b27c41f": struct{}{}, - "2752a2b5446f68dd231ee74418d3f8345ec4188aac528bca3fdf10bcf2069810": struct{}{}, - "8b9200b2cd16cb81b61273ce74d318e75e820d8613682c9658ee13b533d35f54": struct{}{}, - "4862f447f2c7f272fa2f4aaf89dadb3b1ac09105bd5864f8d1a0c9452bb0a226": struct{}{}, - "5c78c8e7160e76ba9b6c41f74247bb7ba4887f5c06faecd6b9a009785b248c72": struct{}{}, - "749b0454b89226f5893211ed630015cb2f10f4a935ab66cc4b32b27a580c8800": struct{}{}, - "b24384fa9a20ad2d83459d0d9e3e405c0ea2a17b1cbfc159d88f3f47feed64f1": struct{}{}, - "dd01aa418b1e4ea72b0c7d2997e14305ca5357606df48349c6847a392f1b0600": struct{}{}, - "e06da30434b904a063a13413df8e2f14d71645168515d4e0bf3b46807df3bb7d": struct{}{}, - "aea96c13c0540a330ad58a15158d323d42c2eeabdbc83a80feea280b5809c02e": struct{}{}, - "69c5c5d45dabb5b7a2cf35e9d92d844f585bb19d61aa4a63c93012bef979fb26": struct{}{}, - "0c2c9ffe23cf3f9068b2c68ea196822198a069e7b9c8efce543d6aaa4503428e": struct{}{}, - "2bb8bf7259c4ea0edaef5b8552247e06dde993959d3d33e4a2b32f32e1144c0a": struct{}{}, - "8e9498bf9ee96ee667bc610f4b50df7282f70114ee1b08c018f4e2f619c36754": struct{}{}, - "f07d989f1acc093b83de885830225dee36fe364df71b995fef3008184ea7a6dd": struct{}{}, - "47ca2eb2d9674f1afa0e3eb9b2cbad67b47c7ee174b62a5259a06a87fbeed9bf": struct{}{}, - "42be22c266603b34e0931de4d85a66ac830546d63afbb777a1a623e8e376ae35": struct{}{}, - "00652261d8f5e7a4521daae4c82d601c42ac334acafc208438ebc9fc384f725b": struct{}{}, - "03391382a15ee47c608b6bb39685ab682a8eecc3c451f33c96edbe4f984340a4": struct{}{}, - "6ebb5ac9686c6f7eb9bc4b10c900dfe5ba1fb3eb8d72f54e9488317a6e80b767": struct{}{}, - "81ec56f72a7d5fa388d9cf3d39cd04a25dea0a4b36d539e5b1ec0b670520f156": struct{}{}, - "e605778c536c53468e4c60477f9192fd132c29674d18cf783cc62e2c60fc89ca": struct{}{}, - "14ebe56a5008e7c251101e9e1fdbe281ab0a82bd6fa00a5cef746b9ee0dd31d1": struct{}{}, - "72bb0d667f8ddae0e323ffc1ac3e7abda4a41a635d7f9d10990212a31b9e20af": struct{}{}, - "50c67e6197ad7c7323fb69e3ff661c07388002da587ae30240869a8c19a0394c": struct{}{}, - "a008b489625e7328841c75d3a231b19f3060d6aabefed338c887aa5de3c4510a": struct{}{}, - "bfd225f5fe24d2a774f4e47fbf5faa2231f8c1d447dc624414b5b4ea95174c60": struct{}{}, - "de34ddf5af5bcbda0219a7280880a0b7c6ae7b12885160996fe3effaa67733a3": struct{}{}, - "a7e31aa22c6106301d370017e683e3dad6097b4b7b2484becf3e1a2d23261939": struct{}{}, - "b4ad95e3decba61fb2f13bdd7f360fff7ce009e833eece72ba4587c59812cee6": struct{}{}, - "e02516e8b734263b8bf6a7059e715896fa8359bd06ed0bd8a663dbcd6f472399": struct{}{}, - "56977b5ae3300aa416618f5fac69bb5fca5fd394cf375c379ea08df45660f4f1": struct{}{}, - "fa3560e1dffb3fddbb5e51b45f4477bcd9cbf18c2ebaa33c6aded55970493380": struct{}{}, - "e9daf3e4235e6aa3faa3364394c76ccc6d660912fc0f9d526d80289521d3cbba": struct{}{}, - "07a695986ffb3351d82ff0e0c3261fd5d619fd6088f8ba6aaacb891ce43175a0": struct{}{}, - "e4eeb4413d9e1e0f33adb9a6e975b919b6a23b2657031574caea2c2a18fc55c2": struct{}{}, - "c0bbec68cf418ee446ab37fd20422a8e8e0ff75ba273c84df60ee7a04c7f426b": struct{}{}, - "cd0b9452fc376fc4c35a60087b366f70d883fc901524daf1f122fbd319384f6a": struct{}{}, - "8a58a09a9b11c129b903c60ba6a56b0d904644a4c75cef4035242494bc612fd4": struct{}{}, - "cc2e018aa6eb9612ccd027bbdcdc9b8c8d351789f14cae4d688a876c18938235": struct{}{}, - "82fe26c04749cb12e13ea775b8d16a28e4cac1cc508c635f4639115ed8446029": struct{}{}, - "5bcc1d8da6094415e2b3abdcceb95c897a6a8ecabec84d340d010bb987568640": struct{}{}, - "a3bdd2b3f49c01e3a32bf6fbd13475afc545f55ad4d276e11a630d38b80a34f5": struct{}{}, - "8874cad5961bfcfc10e3215346bc033dc71810b698f4c3795ebc96823ae3e04f": struct{}{}, - "b9af17af4fc5708e9ba636512213b9d0cf6f894c0c3ba6e1d970c13a921c5db9": struct{}{}, - "bcbac47c6b7c244acea485e5791fb23daf096220e42d1bf68aedbae3794b4a63": struct{}{}, - "9ea3c603a7af7e6926f2b550c21f559e26c4ee4a58ce1294c1e8ab69e0d492f1": struct{}{}, - "d11166efbc6e055a591d985ea445f5f1aec05b336446ba2fed83b1a08ebd4e43": struct{}{}, - "ef1c4b45f2ed763283f8de890730defb2e7d1f8f0cc4214b740dd8cc5f10364d": struct{}{}, - "c6d6c0b4e927e62655d78bfc03c704ca5fd2d1a77690c6f861428b053c497d95": struct{}{}, - "c5ec167f80ad6618da9eb97198763d6ebaed69da2ee19aa9b308604ce8377a7d": struct{}{}, - "ebcf3260e0ac71e79ea05daad0893d35b1131feaca3ce5b9ac1ecbca8a6a3565": struct{}{}, - "eeecf85814215b61dd9f7728f2d2011fd2b615b4fdaee0edd207dfefe3612cdf": struct{}{}, - "0892f5ba91dd9511c6279fb9aa6887b5c9b7fa2481e945498eca97b6c4362d22": struct{}{}, - "66e9f9f374476012a2bc89cc8b2d386a27a9e855f0df308b117f7d404c0bcf39": struct{}{}, - "14cf83dca489b21646524bbccb754d4161060bd5d3f11366a49cf1590a3230c8": struct{}{}, - "2bf61a3bc850596a77a73c6a9fd279d593974b093bf223fd591e6f052dcaf0e0": struct{}{}, - "fb05a9fee1e1e55fbc347ddb0738a9c1ffb022cfc4eca121c1d285035864a2d7": struct{}{}, - "a0662bc3b277f3b2a8191f1e8a24738bf0e2efb44c26e7f3b6027f4ef550078b": struct{}{}, - "d9ee788d82d739279ecb223f22df5971e59efe1b9e86afaaf71fe4b17b7f6e53": struct{}{}, - "a4888af4e46c129c695ee32775a8c233f113c82e7cd4e6fd3cbb1fda5659f36a": struct{}{}, - "5378a871ba280e774db86c84e086362b33882a4e79da29c0e1c06f606b7cb1ee": struct{}{}, - "793a84a351bd364d2f0323b67b39407711e54bc4748c439fb32734538ef8dd15": struct{}{}, - "d66a93cd9ae701f79c00514fff5ac6846623e5b61c3113b0a1482369b4861a6c": struct{}{}, - "4811d0a5bec0aad60ef8ef6d6568aad887fadf820c86bd423023c2143c77d1a2": struct{}{}, - "504e11e5c35ec3182b2ab7c783bd0f1eee9729a1d4b7fd82be910fb95b00b829": struct{}{}, - "e00b3c76911327ca972f10e1a457772e345be7dd0b9ed6cb0c6643023176409c": struct{}{}, - "d18cfcd69ebecfb239b732f63e8cab7944fd816520e2714e8d2eb0d1d74d7c97": struct{}{}, - "24b67c211eac2e6373009bfc846c8389d74e5d2b00b460c689768696dc01bc78": struct{}{}, - "efc0c180c86492bc7bcd30c9e0b79f364347206d94613f3c86bab7aea63f8877": struct{}{}, - "afe431f2f73e4931eca6007dcfd04c6c5546b49ee4167a157afadc96fd5da8b2": struct{}{}, - "c8c3d2e4ed91e9d5d7a7e82ee2b912e18f7f8e601551e1f80d4b04513f583bf4": struct{}{}, - "3f421210432073bcd7671ad5a8ddd84001447b4a39057e6dba9ff5ae07c6fbb1": struct{}{}, - "24e0f0ae0ed7bf630f6e09cd94877bbe94720dac0ed81ec9bb94fd1567e8b6f8": struct{}{}, - "c1df611335a1674c8ef8462f2eb2e54ac755a0d580b20383cf88542f0d900baa": struct{}{}, - "daaad6e5604e8e17bd9f108d91e26afe6281dac8fda0091040a7a6d7bd9b43b5": struct{}{}, - "ee38e188a007eda765c58c33ce73d2f98922d6badc19ef0c81a9e678cc6d7d5d": struct{}{}, - "609a9020414880accc484b0d8fd5082e2799aa828d83dfa37a1ed5f77d50861a": struct{}{}, - "50293808b92274b9d0164bbdd901fd3a678f4eff19109ff2f3e5b1f92555dffc": struct{}{}, - "29e52f8bd667e2b23433c3e8e1808ebde21f2c10ffdaac627e0169d4d388c39a": struct{}{}, - "551916735aeecf1474109865fda11948173d76b1a705293de527130b8dc7271b": struct{}{}, - "50c5dbee615affab6d335bff06cfb09760ccd57c041c63be7f884013d17ff535": struct{}{}, - "08910cf1f0d77bbd5141419cb2dcfc7934aa936d86134b379dffe98bc4029529": struct{}{}, - "c393dc9abe8a42321531bd119d1bc37ba2580db997221d041efe52b558df48fd": struct{}{}, - "1f9489f5feb46a3d6d1c18e398b084102c612654d9829a30b287919f7606b140": struct{}{}, - "1630418519dd43ea99e390a4769091e1920215526f75843c3d48825d528a89cf": struct{}{}, - "1b6405d1ef5a816105210b20a8f0fc129869a25876e45c0891e4f6d972bf74c2": struct{}{}, - "b18aaa6c6b929b866051b69a785a6cdce5bdd564d41be247c7d5ef7c2e2e2271": struct{}{}, - "14d4568a981d9288ffdfaf689bd77637dd38a72b4844c65b51e1b9da37083eb2": struct{}{}, - "4bbfa1f1b0462b4844b74c34fdd297aba2a82ac2355e64308578f5acf1f5e8bf": struct{}{}, - "b7459169edc6a685450b177261b16f8c717045c5ca7f9b7c80ec067a88ccf2f3": struct{}{}, - "0dd22cbbe837c5da77feef83d7a24dc5de0cc21deb6df40a3b151bf8af77ab71": struct{}{}, - "bbb9ac888931898dd428a3c63f7708fa9f95550d00422e85cd62248d805fc752": struct{}{}, - "330f5de75f031a5cbab6916f83c6994bd2a25398dc24b76d9d33aa65b03c50c7": struct{}{}, - "dbab72514b323483a2777a6f8ea5e5ffac45f4edfe2f36c7e80bec3be6951776": struct{}{}, - "42a296fc88751910a7ac778096d1af842a38ca770509d1c47a89640404c54161": struct{}{}, - "780d88acf70df7b68290f99f4e4544c267562b3e8fcfd1d75dac6842942575cf": struct{}{}, - "7e011e86d30361fad6a2e7e322e4d66b73501ddbd43560e07ce87ae9dedfa1ee": struct{}{}, - "7376c22801fbd6e01009830a70028820f280958165e6d3c2bac9683dab28feb7": struct{}{}, - "cf68d632c3845fc16c2d0b161c332d3a3c9dbfc5e4512ab70519f4865ec0bf3b": struct{}{}, - "e17ae4d0819d74dbba749389e4b1a8a5856113b39629a8d93ab976cc5fd72a3c": struct{}{}, - "2548f31aef0b49b8c60eae8e3a3eff5938cb8154b6a6f286d21c8c82e6a66563": struct{}{}, - "acf118f37b4c5723a2afcbcaa880bfa01d33639cf1a1b5132441313ea83975f5": struct{}{}, - "32c4feed996880bc92a062dc476f9b8cdb2596a989f2cc5246e9cef605bd5c78": struct{}{}, - "e78f27ab3ef177a9926e6b90e572b9853ce6cf4d87512836e9ae85807ec9d7fe": struct{}{}, - "b07ec327d75c97599409702ae5b5a44e76ce1b69e9cba41ac2348529e66851b4": struct{}{}, - "8420c85c2aadaebac268ffa047b24ba8d4e3d7d64c7e8d97d79fe93bfada1981": struct{}{}, - "cb9b5a0f4a8b09ba490e3acc902f38acf85205797cd59654645e5c1ef8c1ada0": struct{}{}, - "1441527e59d3fb804b0ea020a47faf759f21271464dfdfe1e36e68ab01843d3e": struct{}{}, - "5d25fc58d21680e3604e309cb9dd1c32dffaf9c8aa5230b23678a7224544ff7f": struct{}{}, - "ca6e293b092aeca59649f120c2522f1df248d5c61f043b3ebfddb691a0b52814": struct{}{}, - "b22985604e511716b7a227d68df65ea307a3eee407af5b8652cd85d0433d7634": struct{}{}, - "cb83aea2a76c47853d1bad74f3ee82157afb34c62dd1e0a37a1f03fdcec520c9": struct{}{}, - "96d61fe343ddf5e0d8d68b1d0b08b9af50973a0ebdd38021fae327bbea1b4c13": struct{}{}, - "e87f314e6f2183a5804fc8491c946716b365e23c9a964baf584da96fb90ce13b": struct{}{}, - "dd221319b058fd80bf9028981e5d9e892d6102c48eec8b71b97863739a176874": struct{}{}, - "e23c3d7ff76f6e6235ce091f2fcd5fd35748677799d1637acf5ba2bca350e258": struct{}{}, - "ca199e07da0323415cebc4c2723529684a890490623f25c75e3cbb18667d3f2e": struct{}{}, - "f6111a9559104464099c592617555c75b5cf87ee3352c8b400a420660c2f2395": struct{}{}, - "a9dd98f82c7de3323b17d5f79acfdf51b30c779a45e97619a3fc98b7d83bab89": struct{}{}, - "b56fa535a1095939996e076ca5ec10c4661fc85d8a79732ffdf66b993a2d22ad": struct{}{}, - "fcc54d5b1690d1319cdd27cbc5440623b5e3553beda0e16ff52b0f6a5baac2b8": struct{}{}, - "e8cf94be9bfc6c936450a685a829f91b24123295646e72a858dfa07de14b7d38": struct{}{}, - "310ced37200b1a0dae25edb263fe52c491f6e467268acab0ffec06666e2ed959": struct{}{}, - "cbd278ae9fa043e78619747bbf7687397d39c6e4b22cc3e27f5ddb3261b8ee74": struct{}{}, - "96bc3efe34f172edcb35becbc0eb9217933eedbe00eff8ee88589d74d0c407cb": struct{}{}, - "7c82602500857aa6ed0cf38c4c3e4ec645bdcaa82c00b9155eb08be100c778a9": struct{}{}, - "be07ac9b4c2270f2719dd9851b3249b14b155b0ccc21f8340448e696063aaffa": struct{}{}, - "669e758a004b1c2592dbf15d429b9ab247b842097267f19ab675a64820442bc8": struct{}{}, - "f88594fcb8a375fd6d6dd9f78811ff6cd170a5e4d90acafd9f3559f7b2f983fb": struct{}{}, - "6b1a23007ea2135d33f4b8c81ed0183445b2915a46ac5c5dcffd99a3c146da0f": struct{}{}, - "c6c4fa47fc72bd2570e9f589ef2e74aa932143f788f973cbfd693419f1257345": struct{}{}, - "3ba537b48f04bd4aa598e595590a6f36707d1e3564a827a0c7e1d5678131760d": struct{}{}, - "45651f2cb0f1173a7ad64ff37ab8347649aca6dbe3d334b0e930a56c93d68b84": struct{}{}, - "41a2f9148d09011888d087833c99dd4350f1ed1669b3ae1f0ec196c292dec156": struct{}{}, - "04d52bfb8ce8b5a37e6a15b8c002419d2555543855cb4a7972ca80b2d8eadbf0": struct{}{}, - "a3863c5d7ce546d24186581bcba0b0a157db9d318d9174d46d0db3f3f38cc1a6": struct{}{}, - "e2151225cc9b1a5439f4119c8f42351ac4a028b5f4aa32a4cde8ec24d02327d2": struct{}{}, - "9fdd214866bef9474efca4ceee036966cb509e8fe657d9d8bed562064cd5363a": struct{}{}, - "f7a241127b76ea41742a840e18e32b13811260932d281ebb5ff99f1e36932110": struct{}{}, - "32ecde7cceb6d713e0a21342f313e7fd0b4092ff96d9f936e66a8575cab31de2": struct{}{}, - "06c8aaa93d80a768829b6005973fa92e34612849b79910c8be8e3b006cf91c61": struct{}{}, - "b74e6b8e7391a61cba6b58d086a0c6ae784f5f9a4e5f85f3780db88694ef581d": struct{}{}, - "0de4e1d834c19683e362bd51f64a5ff107f8390576a61fb83e4ada50684d682e": struct{}{}, - "c88edd21a19a7b1d15fc317998106a2a76b2d300e09ece607702da4a91030b4c": struct{}{}, - "22ddc25aa854579f9c3405eb1fa24993f14878fbb4999bf2f0eeb0faaae7e51c": struct{}{}, - "f79d7d24558304d8bce3b1a7622fd4084d9708daa9477124a70e89472b68a465": struct{}{}, - "e3e9fc033c2647b79bac54f75d0965c0715c6856e662fd02da8742100e5cda22": struct{}{}, - "c444d3c813279c7bb9da6c547b6651cb76bca8cd38568743de40300d65205273": struct{}{}, - "20896337b687f1027170e4f14904ff2eeece80fc4b66d20b1a9f699f604bbdac": struct{}{}, - "be2cd435597a04536646c3c039f006710e2b795b14170da7a7dcf16f48359ac8": struct{}{}, - "0f068c4fa50e2febe2add8a3024f47d0bb20ec7902e7f409840971ec0bd1eb3b": struct{}{}, - "fa6598317163f260c9f3bb0959f80974868eb5ff3f6bf80a092f54e042071aa2": struct{}{}, - "b3b13d054fb24bfe995f9f0e85e1ce58c09628bc80b3609f1ae2a40c732749dc": struct{}{}, - "eab2f490657fe42bf20ed90aa348707122be885743f44f5158f059c17e6d1c4c": struct{}{}, - "0c030586945fe504b604ecc2e875c38ede400cd5cd73da9730302162e6b02c6f": struct{}{}, - "a1d648cffa6e2ba0f94c51f51e0cb8f2be4fa6c3d227176016291f9ce64e90e7": struct{}{}, - "470664bd59f587f32e6d933f058464084d1810ee2b3a81b0d4d59d6dd9623da2": struct{}{}, - "3e4bfa367197004cc38f6cdbb67afac142b7f775864fea99628636a36891edf3": struct{}{}, - "951ee85eb4ac725ad18891719bf200b06e20886f36ed1b4abf344a839db45728": struct{}{}, - "535dd0515d428e72591346d0206af1f8b46d1f7d3574e203e829aacf3695776f": struct{}{}, - "27f576cafbb263ed44be8bd094f66114da26877706f96c4c31d5a97ffebf2e29": struct{}{}, - "4871ee3aa1446057cec1b0c84cab9f9a13a20764bd69828b41eb035e8997d0f3": struct{}{}, - "d4d6f7752759a758df9e737387d6ca241fa857fd2aee58580b8b3f3a4f6d6be1": struct{}{}, - "25f43b1486ad95a1398e3eeb3d83bc4010015fcc9bedb35b432e00298d5021f7": struct{}{}, - "af3b7a26bddd5de85f59064ba3533a078aef0c8402b6e5533fd70b9c5820b0eb": struct{}{}, - "f5e224ddeaa8f31a8b688f6697ab2041d382b00edd38d2353af4aaac2f80371a": struct{}{}, - "afaaed8875ea5ecb75d3853966151a782129b9bd734d5a9477be484fc1c631d5": struct{}{}, - "6a2643bb2922fb4229ad7011d02b4ac52d2cc3179b8eb1d522f84ce2c751f695": struct{}{}, - "fded586871aa2ef47b1547e38751d20e1414ddb4ee4d64b3cc9d94865f6c41b0": struct{}{}, - "44bf4783892f9985df0ecbb077dc0423fdb45f5b665e34fb18166bd9faad3a98": struct{}{}, - "4c1001c251c1c923bca00789638afb17e908d526bf3e9975407c65d2b03f4b10": struct{}{}, - "328a0375f5a92aa3d6874188d147b9380b6d70a0407fbb4d153cc6fe69b88cbf": struct{}{}, - "e520e26fdbe7c9f8333c878d7d549c870487f9247993ae74f7c1de82bd5508e8": struct{}{}, - "f3c16f525a1b7c204fc953d6d7db7168d84ebf4902f83c3a37d113b18c28981f": struct{}{}, - "e4de6c318aca791ea78bb1407e93ed44c9729cb3ef4a4ddc06202678ab6e9b5b": struct{}{}, - "c5fa04c653012b9b75db2c41676459bc7cebd725e4c249de7cff14dc2d94ba44": struct{}{}, - "ba9a1dca54a92ea415e6dc34db97d3e912ba1770d19a6feaaf5055e6434dee5c": struct{}{}, - "143284e6aac9411ffd7b6180c0db8e29c978a79cfdbdf92963c5c39b60776d32": struct{}{}, - "0f1324de378fb2e399bc66843abb736ca47eb638b6a05bacb23a82efb5ffd62b": struct{}{}, - "03105d508afac3108f115aef7a1c685b848f06631b81f2345535458c5937631f": struct{}{}, - "08ba9a99f75d6627feef4eb9e28a6357835b8e78c9b6e92e568ab2595762cf4b": struct{}{}, - "5589037e53751ab94d68a2f30e2de02420f1d188f434942363e1d0fd466cd824": struct{}{}, - "26d61718bbdfe43a086223bfb740483d12001f4e935b64d206310f89107e3f83": struct{}{}, - "1ede960ebbb5dadbac573858b2e33b37e7fee696f89515fa27080d47e120be83": struct{}{}, - "fc1200c473e0d19eae5a49238136d9a5b7dc20de74911a6a1b9066796e87bab4": struct{}{}, - "6f0c8e358658667594a4672b822781527ce2e7468957db9ac6fa7b679b2baf25": struct{}{}, - "1749bd687e9fb1e9a510d329452d02814a60632ee845851b8bd750a4858b1aad": struct{}{}, - "fd99a551dec7bc0e521953e4432205e255c3de6b3e1b28a8dbc3f3f7241f13ee": struct{}{}, - "1bc3201a9f24a2fe48f634f90d406aaf6cbf5e36e292870ecba98d74b065ee1b": struct{}{}, - "8e9b669109df89620b94f2387dc53206a82ddc71d658f8f7a2b3a9b417370d3e": struct{}{}, - "a754049ffb01baaea795203c823895120373619b79ac87fee9351ad3dea41064": struct{}{}, - "bb79caae5f8c42a67e56cdff03f974ec0b187b5acfab4d8a3b274dc962776e89": struct{}{}, - "1c8816b1e1c99f5dbcbffc2206eb1510da5ccc598b047e2f76926927181eb584": struct{}{}, - "a6ac558e3cc659a5e6cbd200141d984ffd3c06f357101f411cc78624f212a976": struct{}{}, - "d8c92791d2280abe5985ffaeaa07063cf0c8b9abf5338d688504e6074cf99093": struct{}{}, - "200afd0641fa765e51d19b17c10e967d04ff3c959bd9ba894044841e0aaa1adf": struct{}{}, - "f338f4c08c4eb9a62331c60091e0f301da3a8fe4b1466c2ee8b8461c82d6e0dc": struct{}{}, - "4205501b2d802fd46d82e42f1e87718fe13ddd49816614655c0a43ddd34d664e": struct{}{}, - "3a792932af28712e7a4484886cf99299eb236eb5820d0481d7b636351ff2f397": struct{}{}, - "1a5e497a2bfa7bfd8aab38a1d576ed882f4a82e855ec610880b4c186ec3f4e73": struct{}{}, - "aa9495db5af81fbd6f18bece4ae5aeaa636b96ae738e2ac6580a56726ec9856a": struct{}{}, - "80ceac1b9f159b0fd6c2680c6ed4c4096ad866dfe8b2e79cf6f9b061107f4839": struct{}{}, - "451e05f319f6894f97ce2950ea3e0a9fad58f0ffdad21a96feb6871190cc956c": struct{}{}, - "77061f1bfad2f62c8b5fb54f0fbfb2691674982fd7200c664a1e7fef35eb18a0": struct{}{}, - "29cf15ffed8d00a2c73f15cddff1690f93cc86694e82a7106d462a5da0a27ea3": struct{}{}, - "3f13c4cc99b8c89004d43c7fe0a442e91e8da6a5d39d98e7ce8ccdb4094af527": struct{}{}, - "06f4a82c3a3a9fa757c4937baac94f88e498460b248be67a35663de077576f7b": struct{}{}, - "ad6322ed9b6d1d3276e3f63761e63f51f8a00ef090eccb5b1d78d61ed61841ac": struct{}{}, - "22ea282ecfa5c6d2d953358b80707161a867e5208924e61f95d8dab2d4942139": struct{}{}, - "1c723b6f3f7844f9101a24d5a191bdfc3b9a04ba06e60affc374a5b1f5aefdb2": struct{}{}, - "c2ba7b6915ec2cb92823fad911089e5f223797fbc060f73bba664d4d8597657b": struct{}{}, - "7acd982e55ea7b6584d1af5b5514198515bf28aee906f74561e4f1647731c84a": struct{}{}, - "7ca840127ca5a1264a588d4da8c0aa751780f33d73db484fe9251d68c60f5427": struct{}{}, - "05a642215a9ffabdf33cdcb5fb0e909fe8e51e662fb792d4f94d423982a32cb6": struct{}{}, - "cf929d06a4e69002b7b2d3fd991a129969aa5545c6c855f9efc2f17b4e7e3128": struct{}{}, - "57626bcd9a191c81bb9b9500002c79cec1de96ec63c29d539394dab0c7187ac2": struct{}{}, - "8c9a013ab70c0434313e3e881c310b9ff24aff1075255ceede3f2c239c231623": struct{}{}, - "60f256c7ccbc748036024fa0440e45fe1574db20a874e4041a16faf0105a01ff": struct{}{}, - "b68e6884a9f49cb6a1d333a027961df2128dbf2c16aa50a7408a2fd03313691d": struct{}{}, - "46746963873bf173853454d33fb244e02b531fbfdd62df253fe2616012afdd02": struct{}{}, - "52b490be7ef6bf808a2c1c0d84a88e4ab7293717b2b68bfa82669997f6ce9670": struct{}{}, - "652607e181622fd24a788d3128a697e40530dc11af62d36fcd89214388bb5898": struct{}{}, - "f552c72213e2ab863dbd2acabca499d71e96e6510daef39b95bf89fddb79a2b4": struct{}{}, - "03aaef0fd45d47ee37afee60b41f0a80010f58f95d3d34e9b7dc253c8558bf2a": struct{}{}, - "658a39f88a25d0fb5b4b79454e789e07c4e82e197ab3027433cc07240578f7a0": struct{}{}, - "9d42c416e9a5c69ff0c16f1752893ef94097e90505d9692bc25f6f328633ccb4": struct{}{}, - "6b2f2942a3f8867223b395a9e56c226b62ab30ae33fabcd5b2fd667060bf87b2": struct{}{}, - "67b71bbc0f0203f43703eafde6fa5d41278fcda77c711d08a42a4ab4357dc2d3": struct{}{}, - "7e8eea5cc60980270c9ceb75ce8c087d48d726110fd3d17921f774eefd8e18d8": struct{}{}, - "62eab5829d0fe041ad1ff8a85e450f8cb96276475d447f0ea27f6d5c164e8f45": struct{}{}, - "37c9f351a5cbc4bf5b1e84e5257a0bc661331e77ed188b03aaf4ecf0b76d3cc2": struct{}{}, - "e60865b55a8da29ca2af155eed3073577eee126a1466e3f011b29eb5d89aacf1": struct{}{}, - "39c824dcac73d365d300430ea0592b492039d46089752e53ee3240ee75163d18": struct{}{}, - "d4ae460df79c91260082fc943e4d99d21c10431a5d72c4ad9e77e4643bc5e545": struct{}{}, - "8a2e7ca933fb2f760a71226f9f861b6776ab86bf50321193832afb3dbb42de6a": struct{}{}, - "cf6317330479b9c8a099790e1d077ddf722ffd79ae2357bd61a9062180d0f742": struct{}{}, - "79fe4c43a919209a10138fab243ac8e28a2b23208e16c1a1108d6913fcdc674f": struct{}{}, - "8a0995aecf4d900810279638eff481e4e4bb69f2d0ec2a642dbdf2a0d0397323": struct{}{}, - "00e48a815525529ba9d33f8761a167588fe00c47bc82f515cf791c482ed99ecc": struct{}{}, - "ad589ceb8b5ccca048911a0e4dcf5466a1f14dc9f50517c9a2c53988d7de2521": struct{}{}, - "742639f0f3442e8ffc6072d19d0588437c604e37e2eb6b7d6ff54b588b07364e": struct{}{}, - "79631cb1b7b63b6e84aba908f774286c1bb4642be880ee3de73c12abd53f0c1b": struct{}{}, - "73834addcec75fafd7d73717ca1614a1324652ffa70b4b6c44fa359ae5a69dab": struct{}{}, - "101f0771a4b0e328e951747f3fa569d1294f823a477b2b47b612dfc952562d90": struct{}{}, - "019b168eaafb161252c4c22965fe76e5f1e309f7b4e4a3c65e3e088abad2bb0a": struct{}{}, - "54b27319b12ff031d50fe6d77c718227e8be2293031cafc55a5075b0c09b300c": struct{}{}, - "d1a749ad879b6c07e1f356b260ba81f9cb5b4376072280c9994a5c125165b701": struct{}{}, - "787f90add4991fea070980120d253864bf36aa508af261ea4cadf3f03c840f18": struct{}{}, - "f39fd9073457c8ee45820dace49b45d4b9fab3456cacc18a2083df023a95b203": struct{}{}, - "ad08d3b8858573b09fa771168398757d8200ad923dd88a8ab2163f384a4ebf55": struct{}{}, - "1fb565c514350bd302b6311e93127aa2720342b57851517e0cb6fbcb637800c2": struct{}{}, - "306ff1642fcfaad183cc4895d2c0da6120b2f1046ce9b7d521c6c2ae93245549": struct{}{}, - "ec8ce1a8be0e259a75805a0a6397489d6413413e2c322bacbf3e3a2b874fd5de": struct{}{}, - "7d8a8aa1271c24df7c5670390749875f15743c2199852794134b311d432b3338": struct{}{}, - "b863d2d6b10fc1a84879b7c614e79cfea8795902700a65bb4405cf748123624c": struct{}{}, - "355593035828b00bd3db642efe0b29a3ed072f632f3d28c7e81aa842fdd52319": struct{}{}, - "fb8d793c57c891eed7298afb9f4097f24ed56c722088cc720921d0cd5f5a1c5e": struct{}{}, - "02983856320b180ca3332cff530e4942f51b9eb9c0f58b806df6c4897148b687": struct{}{}, - "7108c55aa1fafdbd3234d57dd2de806472aa17f9d4e71dd269b2c9bd31fd4c9d": struct{}{}, - "96ef50b6513e10daa1a0c26e06e61cabe9a652c803ebdfc7fdbb5849b260c399": struct{}{}, - "e388596bd32eaa054188ecd15595045b0d1d56dbf132f64c343c120012fc5114": struct{}{}, - "5fab5f43d0b2f97526e7daacb1e42ffd178e2067c59a0036d0d984a3bec289fb": struct{}{}, - "e8d44050873dba865aa7c170ab4cce64d90839a34dcfd6cf71d14e0205443b1b": struct{}{}, - "4bd9354bb652233497a0f42647464b81b83a83654fcf1511263a5d0120d74c91": struct{}{}, - "2898a07b2cf23dda8530b14b6aa522e67b002886d170c02219acc3598fdb50f3": struct{}{}, - "097141049d4e2882db6b88476bb1535e8153b2618d3f91b947aaa321e421b638": struct{}{}, - "739bf484f7f1c288c0e576b5ff1a6c96fd1bd0a33893f5f077fd5cdfbf6fcf1a": struct{}{}, - "7b3381e10a07c5399cf5610199a23ee6caad82217cb8a09583a54ddd158c2ecc": struct{}{}, - "c8a7d56741b3b3baa1a40ed5307b35f6a54f750e726d79a3611b174eb32cc225": struct{}{}, - "4c7a13352465820b55d64a633fdcb2182d4b841e22cb30a8d6b4cf40c9f5d34d": struct{}{}, - "0730d1dabc8ed4e8d22ca6c87335141f9808b33f2dc650fe6d263880e118e7d3": struct{}{}, - "8e0046c6e2b27a3db7614c12893b74b5c9abe3f887942999bd4a8d17de3400c3": struct{}{}, - "42533baa348b200bbd30e67f02db512a3dff07b7d50f44320ac852feeaa33f1d": struct{}{}, - "0a4cf523e20c6dc2b5ef49f601aebd9349cd780e3d40b9464b213f6fc8bba47f": struct{}{}, - "5ccca5bb6d737c724c872058d9633da7faa3faecc75a81ac96b87126d6829816": struct{}{}, - "ee3a28f57e9d8665cc55b30435482a86e29dca5dd6ca22afbd0ea4997094c75a": struct{}{}, - "e65c9c2f651149b030ed423820a4014842ff89c11e4a76872232b83c998f77c9": struct{}{}, - "5d1db4ea494bfbabd6e089b55b184d34973f90a20ad9229a2592158e9d9c7b6a": struct{}{}, - "0a6d5939af40c85469f0c8ec31a4d8dd6ef1fd6fc689ff5fc65ff68f82b0745b": struct{}{}, - "5fcf9c99c9b561be0e3e4933d3063f235fb14e9a455af96e6489edbc9942e1e6": struct{}{}, - "7ba6d88bd0dd15f5881b31ccf24246cfd23c585ea3ef655e012e5220b0df9427": struct{}{}, - "9b18feb2e018d29c4a00e75976820c11d9cfa7c743c9b5721b39ebea0bb094a5": struct{}{}, - "ca8b22d0db83a22db163b560b3e4e51527e533d31d067b614a0c33c4d2df8432": struct{}{}, - "dab15e9fc243cf5ffc4f386f770b97e2b716e334e05972eb96dda08d107e1c96": struct{}{}, - "24d68d2ed7fd903b021432e964ea485efa001ca1ab32b2a7c8671b1769e1e666": struct{}{}, - "c0740a01ebd347310aa940c8d2a43fd342c9fcfa624765e4a3ec03e535ca5798": struct{}{}, - "09792abb14956e93ecf2a709affde76344ca3ed396e57ffcbf65436cce83fe3c": struct{}{}, - "e4f4467dcec75f893f7869b090e405b431d9316da330ea5401f8b4d82306e598": struct{}{}, - "dec64ba9548f8906d2eb0b9fa8d4b455e09a60505ff709fa0275167881c5ec6e": struct{}{}, - "38fefd0d2dad12d0b15f5988f837973cd04455f26e6da93e9fe59e0f9b1f4ad4": struct{}{}, - "1dd31cd072fdd30f9bbb81f6d4d3eeacfef383028f6f8bb1376840f07ef6d370": struct{}{}, - "e078bcb1e6e0e6860854cd77e25c9ba0b5fda815914742f2a67e2eae8c1058d6": struct{}{}, - "d2cfe4e4a92c90a4cbb7dc161123127b38e144d5e947b007f3339f8f7189759a": struct{}{}, - "121639758d50ce8fa4a641da2ad8c5ca2f48e0e8e1a9165cee0cddea64871e38": struct{}{}, - "14b5cc1f2283528355fa32474ebec6a024d6c46c0e9992ae35643d2e025855c8": struct{}{}, - "38631d061ed79a8be272bc62941cd24379989726f6f1d06c72605274befd88d8": struct{}{}, - "bc088dc3e02f8a9738018961d0a2197f1102f1e43e18edad47121223b2814e84": struct{}{}, - "b9483869e223173a3946f9ce75265df2b9470b57fedc93e5cf9e81cd0b2c2c14": struct{}{}, - "695e7e93da4db3da35cb18aaf4de599c90f60e0ad293141f02328abdfcc17d13": struct{}{}, - "7da81df85f4bca6d545279c5411935e6548bd536905d4142d7e78038cbaab467": struct{}{}, - "e8eba2d40aaba3dee240218d0e9725b4e36b639dd97dab75964ad4ff79fefe93": struct{}{}, - "02c0ba759bf8461b5d62aeee6451002ee7338927b5c0f59e8b6de970e8d181a2": struct{}{}, - "36efd659185a4db7d3446707cf69184246e812e14b63d281409a04f6fe59ee3d": struct{}{}, - "e9ab39f01d431c5250493a3dc493bba9c43f73a4461c72b5135ee09738582af7": struct{}{}, - "049a68c15c0d6e26c8b4a0743e6b87f074864c2fae5983c88956cb2882d608f5": struct{}{}, - "fe7ca4ea0a8715222bf5f4d6c6d1bb962ddc1189f072c6c7e2d20688b1083e81": struct{}{}, - "5a34c820e6d369fe403a9255a2fb5b9231b865c4a28de40abd4559d693617361": struct{}{}, - "7db4d408c93a7ed1f0d8914648cbaeacaa504495aba495315112a59d1b222f83": struct{}{}, - "3ddc1ff96bab63039e1d098bbb082c0a0df483bf1cac7c7ece6993811e642603": struct{}{}, - "9b7a0f2e159a5d55c77b8a79af6c557a4e9b4be4ccee9ece4cd7dc254fdfcc4e": struct{}{}, - "24fa74107107be23919a666f108522cef177e886397714156dd0a36e6aef9f7a": struct{}{}, - "6ca7eb771b937f1f5faead4f803bfcdc7372d2f9ac619da7d93dd9bfed00fcb7": struct{}{}, - "3a2c164208e1dc8ded97bcc07f3f3d6bdac52cf054805788fd956a932db95ec4": struct{}{}, - "ab7a9032a205045ac82ea11013ba03e55acd10c9f2e8db7fb45d92d5caf3b057": struct{}{}, - "b8bff3bb4bcf8cd7676749d4b6d0771d442f03f045d3b799cb37bfd42c76bcd2": struct{}{}, - "976c49391de9909cda71c0b17f580f9e9c58b6264516ce8466ebd10c24caf0a2": struct{}{}, - "38ed809d7c07fdcb6cf7990ba7caa137149b495b475e1889441aa45b3074827a": struct{}{}, - "7d6e18d0ff4bed3b1d1f3afc82ffa124f83ff2b43c83bf14a69562c8e22f0b2c": struct{}{}, - "fb640606a2e763d088e78d5bb15940f39182c45bba9878c9561af2204b1c6d40": struct{}{}, - "4d1c39dbb90939fbd720474946b9450ae04743157c7b32af2eb643699ce4b23b": struct{}{}, - "a3ea87259dcfc124f76450bac4b6d33d2250f38552b0619945c1dc8d6d8ebbad": struct{}{}, - "81bce8841c197f3ec46b9c19eb0597607469a834408212b4d66274f48abe0659": struct{}{}, - "bf97c7347cde85721c2e4f23d3f34254009b6b29718d8c9c093e6eee34a498cc": struct{}{}, - "8ffaaf97add883ad7d0225541ef2250def2abe58e0c419083a60afc1c15ae39c": struct{}{}, - "bd9dc5f78dac9fad83a604327bf29bf368b8921f8cedfb3d3673efbe1f4eb449": struct{}{}, - "18a4369db7d8cd8e2ed5fd1a14e34c0081b8b01398c32fa2fc38a7ab15a2fd72": struct{}{}, - "be9d587defa1f0c09ef49eb17e206983a5f8f8289e4281860bd0ee5a19592c67": struct{}{}, - "4ef0368d3dc7c3624384ed79f1ad9aebb2ef75f849269ed3471a88f38fb184bd": struct{}{}, - "902a88e0e4b78386a87df1c2bd3435b28bd979673dcd672e9f685201ae3d50ea": struct{}{}, - "09f5936097a05b835d2225498a654f70e8f0861c44a572e386e8e77d24858581": struct{}{}, - "7d8a0838ce8e19b26e45fdd0b72954aac7e712a8d8f0cd7bedca3281217b5c76": struct{}{}, - "58eb5aff132a9c1a8aae7f191518f64f883fbbd1216729f6a7c450b58f510920": struct{}{}, - "b5e0eee6e28efca6d6ad05d7b8a94631576037ec9e5ff6d305fe89faa0e1032e": struct{}{}, - "870dc23d21836b97b58a7753922edc8512764e83c02586f3d8f14c11f760550b": struct{}{}, - "e53b40f6c4a7449503a892d51275ae5a06356f84d354be8610e864add3e7ff70": struct{}{}, - "b7e60b19dbf9d2bcb319ba66eec45eb9c67f205f537f36ec18f2896f9febb742": struct{}{}, - "36f583dd16f4e1e201eb1e6f6d8e35a2ccb3bbe2658de46b4ffae7b0e9ed872e": struct{}{}, - "3331186e700623e40c5b91dffd2effee214f5e1623113d705a6faae75e9193df": struct{}{}, - "e2418f59acf2d80eecee3f6f43a73eba804f127dd652fbacf3e7ba20abcf2e4a": struct{}{}, - "cc54f370f9a662158dbda40625c42eee2141f9b1d25a951335bfbe495bdc54cf": struct{}{}, - "bc72295ded3c5904defe076f6762ac0a9a4bb91494890421f36a9fcaf840b4c3": struct{}{}, - "4957900f389e6b2e7976fe410015f2d1916c125246bd01c0af6f480b32e6fb93": struct{}{}, - "721152d9d7ea07f8d55b35ff2d6e7eb6b471d8ac2be01262bca7dceb13ec8a76": struct{}{}, - "d64ad9c2fb65905ace261e20022b017c82f03b268807234f08cfa161750ca819": struct{}{}, - "23b611dc72bae9791d2ad6422c936bb66fc9fc46ddff11aa08667c4c16bf77b0": struct{}{}, - "ad6556353c66d1432c5c062566395adc612dca1e6e7222bd033ff047250c3c6b": struct{}{}, - "3902eb874fcc6c8a1dbfeecd3d5997e8dae7f1fb861ada76094c61396fbaeba6": struct{}{}, - "7baffe52be79e52d13570c402b98503e6165128a5d2a43d916c6af375c073003": struct{}{}, - "79eebb1dfa182ee126fd005418dad7f9f3f4af1f3a183bb1a9e5a887e6b2c757": struct{}{}, - "cb6cd590e92c2ae5427994fe3276673899e1143a89f25dad9dd5393c2389caff": struct{}{}, - "a113a5b1896d78f9870713903f4ed9acb0f56bb25cf1c7247310489edf1f41b3": struct{}{}, - "0db10f2c2f332cd27cf1407fa16c686337b2b23f46125d6e17740dbfc6df427e": struct{}{}, - "1a2220adba6d9d0cdd42ec7fd828438f857c3f42b4d1ad37d1a9855740f498e4": struct{}{}, - "43385991f0be5fed6db7fb640c5f510d41bcaac6ecca6c842a0c2a2d2d2bf510": struct{}{}, - "9cef50437a5f8e497b0cfeff7dc443e8fcc39b3ff14cd615c51559137e3cbd2f": struct{}{}, - "863498dfa3f35f634f532b3cab89b0f8b6baaf9a2a94722c52ca2ef44377efc2": struct{}{}, - "aceca4267090190f097599b4d75addf54426f2086582cfb23b070892a89740ac": struct{}{}, - "576bb94858e6d3ef1619bea75bd73d05e4b7d63a3ca18c0e8317aa7683f21f58": struct{}{}, - "c30d8b13bcc759e8b965c2e3406e850caf074147f084c57b20e6aaa987e4f458": struct{}{}, - "75eb93fd0de5cc3546585e9604f9ecacd9a1c3a4dd81ba6447a5a711d13a76a2": struct{}{}, - "26fa3ebe21340e4e62f4a260877095092eff5f4a619ce7a60ee57a355aa4adfb": struct{}{}, - "ed946f65d2c785d90e827c5ffd879ce3b49c68d4c88013074176a7e73bc58bcf": struct{}{}, - "ceb7ab7561b538dc4401fe7983d035aab5e39466c2b3dc736959e70bfc5f86c9": struct{}{}, - "f6d11c2046ef3917aad12b1c657639104db6f9bf6b20a90823c84f246e04071e": struct{}{}, - "309ba947a2b51b702973410eeb07b703832ecccbd5ca7237276c24ad6f67eb3b": struct{}{}, - "588d3cff31162034f6f61decaf1873ae4445214a626b449d0214467d7a9469f7": struct{}{}, - "778f580f8b6026a104ad98a360b220400d806a12370bc639745e3ee5be737a21": struct{}{}, - "b6742b8cd72b650563c09f142e24ff4e0b3d425d77e71b1e77378e87c85eee80": struct{}{}, - "d599668f93b812a8723f7b684b39f28b55a6750f79deb918c3190f8b48342e0d": struct{}{}, - "6ac0acd7387bf88cd0b70021a796c09af17a460b92f6b9ce436bd7573e3993d7": struct{}{}, - "78e067019f9ec37052edbaa9e36429dec7d75eb00512cd5f976d27d7c4c2ae1b": struct{}{}, - "de6a5cfda9fcd0a5e830b8ae822567d9b55aac499267bfdd516c147e9a4805b3": struct{}{}, - "b50c135402fcbd64717170372000a9f3dc99601d2d45d5b138c3b30505d7fdc4": struct{}{}, - "9f416ae05152ba3b8cda65e380fac1c16e6a28bf30f0c3b96ad873482e411764": struct{}{}, - "7371126a0f7f858cb70738d09b649e454132c14dac1f5729b124762541dc5e77": struct{}{}, - "00bddb36a62f5719e4711f0209a8cfffdda5f6c96770c1082799601b5bcb169f": struct{}{}, - "f33e2eb7263afd5e8b72ec8ca104c6e62daabf3a5af752c931ad01f1ee25ea46": struct{}{}, - "b90d33f2b12789d32691050a2083be28eb99985601a1f1a72efc9232e49306fd": struct{}{}, - "ffca33121a6e81663a60df4324db88050a47fc02a38e2a5c3ec057d3b4e6a838": struct{}{}, - "f77d1bb58da886e3cbeebbf35a0b3d217b003506792268052c6a730fbc5ec9bc": struct{}{}, - "d776f7cf4e43af5c0bc12b5634fa50843c0158263f1552540447d3e28e7528a2": struct{}{}, - "161dd80e783b6883a072e90401843946efdf14dfd748d8f36ac036828c79c2d8": struct{}{}, - "c2713b62c903791bdefc5a6a99df04d4330de491bbc7a0ca6a5007337e4a6028": struct{}{}, - "d7c3dcedd1b06926f9d8d957ea6d1b6987083e60e3fe7b3110405cd5f8860477": struct{}{}, - "75f8f82f56d1f48657f010cc4c397300c44b0f4619e729f6f0ad787c1366aa63": struct{}{}, - "5c6360ebde6753a61316003587d9c92d9617ad5f52ab4964858a94e2d1da1803": struct{}{}, - "d9d771df57e86d113220244726d7c795611a3583075927d292a4b159b24a5e4d": struct{}{}, - "8bce501689ad6f337a953c2d510de61ed5fe622ca11b988f95d3ef07267a5a97": struct{}{}, - "090779da02e2c43b416d8ebcee91070dfe78bf908833e301060b71e6f48e5ee0": struct{}{}, - "409e364456fa93642cd24878f532fffe31e3aae5944d04239a7d023e66ebdbe3": struct{}{}, - "70ce871f8a3d3fb449bc3c3ace6547cef02dfc74ffe48d912532a724bfdbe5b9": struct{}{}, - "cd0e182ad3831bc4c16616f25911a503b9e269259d1e0a7534d08be90aa8b33f": struct{}{}, - "15c1a8549f0683f5e60d4d705f5b38e026843e81e831c81c57df439a9a0c2fd0": struct{}{}, - "736e537f0f664a3d8208e88c114f2c5a16fff5800e5c146b0b83b1c43213d003": struct{}{}, - "45297c633d331e6ac35169ebaaf75bc7fafd206ebb59ba4efd80566936e46eb0": struct{}{}, - "a73060afb61efe1b7c817645d00c342df02407f65435a64c88d251d56150ff42": struct{}{}, - "e66f404f78b6875bb24ff40d80d21b78418607abff2e68d3084f75de4a239095": struct{}{}, - "ae448ac86c4e8e4dec645729708ef41873ae79c6dff84eff73360989487f08e5": struct{}{}, - "308a173ee6765335a4b104cd7bbdbf3b23aaced3ed27cf9557a2e0a9749b4317": struct{}{}, - "f5885b5927323e158a49b9d6f375f39163f567658d215905eb14d6482ab96090": struct{}{}, - "c1d64b2d4cb30f1be788cc4d246ddd9fc1fe85a3eb3a85a3de33829dfaf6ce7d": struct{}{}, - "896ce7625000a4689f51b1277333ad1236163932c24e684aa26180d9a7a268a7": struct{}{}, - "1ae57c21b3ae26e79f9a91afeab2a9ec80138e6b99aa3896d8c0bb484f54bf31": struct{}{}, - "a923477a8eb68f2cded3a648573fe256505ced000a29ba2c431a0b5e4efcfdd1": struct{}{}, - "782433a9f1ba5f77c091931d6169d1811e87625dc847d6e3100cee8b2d103c83": struct{}{}, - "0f6bb6ae7394e2f0b6e669e5e1c0989f39c024ea687564b54aef690855cc6ba2": struct{}{}, - "55e052349a76a2c9792b62669f1f0806cdec5042c28cb0ce5cc420954a2537f8": struct{}{}, - "d59463832bb1e6321aa98a1b848e2bdbc9ea3a7a4ac9f6bef57d01614ac7145b": struct{}{}, - "4a689c3cbf0204b0a7b1cc432f7522e6cc5f1238b65501fbdf335456a419a8ea": struct{}{}, - "493a15e7bda88b07272071cbcfd3cb05920b999872b6f3cfa6e4d16712dbf434": struct{}{}, - "68885fec0c7db0ea7729a0a13b6f80ff4d355d8e47ae3de0a865dd958f9e98ff": struct{}{}, - "7f3806e77be78814aa3da52f05dd518b0d236c919504fee6c9f5b839f52cbffc": struct{}{}, - "2be04fe2ae242318289279388fb1b2369cab80e33d507a542163cad3ed7b48bd": struct{}{}, - "c1bbfe449f1639e45aa3b4f75eb861e608b9e466b61da04cc256dad0e86775cb": struct{}{}, - "34f5290adc138ebbfbc3ec2b828cc7873c6942ef4f7ad942ae615661eabadb38": struct{}{}, - "1f296fb869758ebc3ed6c40d9c78a8498519cdf5353f3075e0b0ee4f653470f1": struct{}{}, - "8c6025f0cd4d7dce119013bf02111bde9c6df1218dfc10cf963fab537c7577f2": struct{}{}, - "7d5a682016186a9e125e50d4ead07a9a2e3d486f64927ea8be67aade5959f1db": struct{}{}, - "4e2295dd929e424aa1afde7049924cb231f451c9884d7915ae33690b73b227ec": struct{}{}, - "75c1354b3457f0e5d64dd66f4a00614abfaef73fa8b540784b2b231d1ea978cd": struct{}{}, - "b5f90a96e680140d51e2ba945461716e44cc385940aee21f66020e66193647b7": struct{}{}, - "ff479f9d891005c535ec4313d3747a095ff4dee31999c881227d5f74e09d85d0": struct{}{}, - "a75cf14626ff689e806e95e045a1efcddb85c7bbf7855a9a5e9ecb133408985d": struct{}{}, - "8fc84bd4f04b5ca02a3c3b2a786489743181876ddff3f3320954111da4557c7b": struct{}{}, - "3ae83660f67600220d1793cab11212571c61529952f4fccc4e1028272ece4d98": struct{}{}, - "8261ed6d55c45baafd9b7fc4e88b5b9ebe558ee0e6b7f3140d682da631eb4d40": struct{}{}, - "37dcdd8d56719b6cea34409de2d1d34be683f881ea01de636827f60944fc7c28": struct{}{}, - "aa228da180be79c057f9f72314901f132f9e5b8e4b0a8def7977e0ea546d0c05": struct{}{}, - "0c3ba4a48996d5f173458774a77a35a22724430959d877195a99a96eaa32a10b": struct{}{}, - "68d6641ff9421a6a305e56d4f1f79c1d426c977a1bcd3cadcd0d337ee4a6d323": struct{}{}, - "e07cb26ba2d66fdb6c1d081d85ee6493cb60353b71398329530b7be6b97c6afb": struct{}{}, - "c22dfc36d54b660afda8468bc315dd63ef1b062fc8decf7d414f208cddd35e81": struct{}{}, - "6f744188ef937ce4aebc87127b9e5a0c9d17fefb66f6ad6fe684eab568c083d6": struct{}{}, - "cc04effa2069db88d0b528e3448c4c19580986dd22690a9794b02c8d76544bf7": struct{}{}, - "4dce9969c881f46e85da176311a3623b8ffec9841b1fd0be81cfa9aaad68b8fd": struct{}{}, - "a1cb100f57e971cacf269e7c26e4630a25a8e9d4bdd35e32df1a80b66b896254": struct{}{}, - "a5804f3cee44f88777e99c5586e32e917dc473e5cbaf433723428cc694890a9e": struct{}{}, - "bd576611e860bf15883a37b8d628c77e6dfb519757793a9d759fd861da067f77": struct{}{}, - "4eeafe728035f5ecd18b019cbba9b565f961a05202599fbb4dab535da03eb272": struct{}{}, - "7dcfa8078e2c5e3d5d33573c115531f28dbc45afa4df8acd78751d5f9d730fe9": struct{}{}, - "d704d0ba3a2cb075b50c2a96417ecb1461afa259a543d83a3f23e2d6f87ba2ce": struct{}{}, - "a27fd1720a7c30a644351e9d80659326a48b6e2f421286dbee282d235a23f53c": struct{}{}, - "cd24c9092810dc38e0e1776f9328e1f53a73067100937e3e2521e12969fc19fb": struct{}{}, - "d58da41e0f5515574c3045d5a2047f88b127cb9de1ccbbde2bf57f23f987d69c": struct{}{}, - "5f14f9e6d80f802a65269804f2552ef9889f2c7ccec5067214e58a1e48e0b3ff": struct{}{}, - "694bc30adc87c072b134496ff7bd3bdd82462b6b65116735a4f4a422dc4bbab4": struct{}{}, - "9113b98df80f877c7a2ee5d865a04c9514b4e9bf25a49d315b0b15f115d2f0d2": struct{}{}, - "744b93f9950fc38dad705556931ea48193b99dcb191cc9bd77097f65fbe2f0b8": struct{}{}, - "0b7d75c068ed5165be960279e7dacd2e900d50cbae19b654d02efc5b0fa68ee8": struct{}{}, - "0550b85bc85a0b8c3004a92db6ce96e61d26719adbe663f9d6dfc03afe53cc33": struct{}{}, - "1df80a0541cf3a978d0b50cfb08aa4f921acb3f6f6a33c35b833a04777db934d": struct{}{}, - "92ae3bf30b2ee3ef09d4ea02cc1ae8eed19eb6ba521d567abe63dc619af7a75b": struct{}{}, - "498946ae91785990b2b9675b8bb3bd159f56f9d77977986894f097b6f1ffbace": struct{}{}, - "262f86db0ac788ad9c3c7cd7aa6b1df5388cfe9df8407b8aa5c4c06d67ecf493": struct{}{}, - "2f934b37bf18abd13ee7fb522b20675d16c8ddeb27d3723f51c5b5573cee566b": struct{}{}, - "00e0648b161b26e45dd7067cc3c56fc577303d61d7bcbcdc2a641841fd58df40": struct{}{}, - "307302c4a4e4ecc6d79fe7f076fc8cf5064a75a3e49fdd5535d772bc1785934e": struct{}{}, - "696c668e0aecf7a2557f0087a6e38bad9e4286c82becb96764865cb1bda84574": struct{}{}, - "1eafe826860f97afa5d2b4a6e0d6b0c7bf3814d8b052de9665713055287ded99": struct{}{}, - "ed19097781f5578ce4cb69700f315ef10ea969c97584531e8d5d8ba32268c2af": struct{}{}, - "44e8b24a5964af433c79e0e76db1fdadcc44ed4dfc7ed6efcf7d5728eb33760d": struct{}{}, - "62465eeaaf884af254199f7e828ceb92ae8d2f099f79542ffab30eb5f1944afb": struct{}{}, - "73d0a219cada10f035a06dca225de294147e760b1d669744e6913e33f6a96fa8": struct{}{}, - "0f0e99f465a65c3cb8a7514fa35714553fc70d91c948679dc7a91fb3d79e6a06": struct{}{}, - "a4d11a1f114343d66c0be720e5be47e1b6a511f9e58cf1e9b37819a74aa48bc0": struct{}{}, - "6adc452d6b74df402271a8d67ce0321e8f217fcf536804dbd0b6a80107a86fd7": struct{}{}, - "87e4571bc7b47363d74a65592c50a1eccb5afdc5bd3036d119f8c84c934b37da": struct{}{}, - "bfe7aed882c372f401be8b849c503547d9872088e4ad361d71babd34123bf51b": struct{}{}, - "60303ae22b998861bce3b28f33eec1be758a213c86c93c076dbe9f558c11c752": struct{}{}, - "d05c34e72352778bfa9070db07730b8b4a061d2f9afeaa8b66d76dcf7275b34d": struct{}{}, - "7b1291e7ed8a6f3e8aa82ce5a4176a5b1877b17e7cb62f638ac6fdddd8ecfe11": struct{}{}, - "cc9dbb9a6bc9dda4fc8278349cedbc49fe937bca516526f3233e6807c73b41e0": struct{}{}, - "21b37af13efb41d20fdbb658ea6dba140408c5b4bfc935fde6c064e3d6417101": struct{}{}, - "187c9bceeb919e1b3e6d20fa50ecabf7d9d50b5343e8f9a3d912abb13929102e": struct{}{}, - "fa7418ee05920ee361543a7b04543eaa55343e3a6eba148d59cba6170c0b20eb": struct{}{}, - "46a11f1333d6b2079f8da41e63d810406e9de5b6eb18c80f434f6c79da1f525b": struct{}{}, - "e7462a4f5295b5001cdb93eb3d6c65775910324ce38faacdf9e19403f4a3ca43": struct{}{}, - "8fe9c2c44847ab572a08f2c5d689077ca1e07377e82a4f82364e8ce39b162257": struct{}{}, - "6d02f7d61e02d070944a9a37856f43d78fb89f8adea6150cd2fdcb53d2a4cc4a": struct{}{}, - "e66ccdc8aff0cbebd98ac52ac3e54b3ee8a0f5216dc148a526353ac7e5298c06": struct{}{}, - "c72098290920d802b0768d037ebfbf0f2b6dd43cd0d2284d1cc80d16570a47e0": struct{}{}, - "6ef9cd1988144dd6d3aaa4a42776d8f577f9da7e4d534ea2cb00045d2bfe3916": struct{}{}, - "9989a37538faecc495bbe774b30fb447a1582b8f1bc14ac88dfd84996a7b799a": struct{}{}, - "6028e2a87c5080933ca436888c9d26630d7f34146c814e5d9b16c8dc21c5e223": struct{}{}, - "eeafcf2e9d8037ab51da8788043fdd0829500b7d8cc36aeb4503128b8a864cc5": struct{}{}, - "54b688a517f7654563a6c64d945a3670880a4c602ec67a065bbebbcd2b22edd5": struct{}{}, - "958272842345dc514f401ef3db2ea2be3010287944c62498abf312218895ee9d": struct{}{}, - "ff060a7012202da6c5c59256eec82c6e6b4239f1a2c92bb999980ab5828357e4": struct{}{}, - "cadc62047f58dce349fe916385c2b3802c37490b02bc2135b298253d8f17b6f7": struct{}{}, - "c21a2dd112b6352e9c95842c16033824918c73fa061b6d2f5bda5b7fa84fab00": struct{}{}, - "de79dda4d18e5f791300f0151864d675911ea35862651e6e55c744a96a367530": struct{}{}, - "e647708a52060743eb1dc963732cfedc1ea1db3a1785da3fb9c50dd3954fd708": struct{}{}, - "0a813f5bd634e3fc2111550b0c33d5ad841d7bdfeee93191669f951b3f1ba342": struct{}{}, - "349f4319479e3143148643d32214fb91a3ef1764faa9ccb2453fc1359f9e51c1": struct{}{}, - "b559b891d40f5434f2c603569357a6dd17c084487052183cc77d40dae080daa1": struct{}{}, - "1602b4048b35882a346c6887360d0343cde72797314f81a36065c71320562615": struct{}{}, - "de650d61f5bd166a91f8ccec3158297db18b9d50eaedca238cd29dc3a214a916": struct{}{}, - "a4985e5ebeaa5ef0f5fbeab600e9eb193ba2cff20923be1a77a7a468ac70d90e": struct{}{}, - "f92842d2dcda040d1532f144b59c077e08c61a257e4741a77baf2b588b41b3cc": struct{}{}, - "2a7a52ef951a2a60412e2ece5cf45af86c8c1c295f8038fd6d96339e7e311764": struct{}{}, - "ac739dccd121f71261d87461256e9ca70d8e3b6dbbcc9076d5f3b50c73a5d22d": struct{}{}, - "2a87fb36539cf37b688fd2084a4277e3cf8e1c67aec97e1865226867081e64e3": struct{}{}, - "31f7e329b99c3d6f3c7facaf5a4cb5261b9a7cafebd975a316097a15c4819545": struct{}{}, - "3786488b6f8ef36db49b93f33ea84ed8358a963c362d30764c319d4a34eaf4ba": struct{}{}, - "795421f89d30b4f0355e4dfbcc3ced8e87d7d78ae3cc54c67334eab09eb116ee": struct{}{}, - "2fdd995c4800afce6e0fda2585bd522b06d7631972cbddd72cd3033a79cdf37e": struct{}{}, - "f7e5cc70385fc58338bd8328fab5a7257c24ff660c4277d1ef7b7c621468bf1b": struct{}{}, - "77b44cb698cc7a3dd09a2e13ba089a7516232c1c90a8effa16c986d70f9d5e6a": struct{}{}, - "379a2ec73e44293b8e5e93587d54484008f352f489c0b7b845cbfa634486aa51": struct{}{}, - "2870b96cfef35429140e130a8f3457fc3d7b09e172b82e07b6651f714ac04616": struct{}{}, - "942d9d5b793c9316da4664b0d84b00148166d26ce3ee1f9138fd7b4848fe2921": struct{}{}, - "5792d2981981be5a2677cd353db6f55cd9d2779570061ae8d86176635b3cc745": struct{}{}, - "910ecd3e43c7d241425a16a378dae72ea48201f9bada186f036d9bdeb4368444": struct{}{}, - "2d69120f4a37384f5b712c447e7bd630eda348a5ad96ce3356900d6410935b56": struct{}{}, - "f0b2481d6b24d018036a9e3d03c97b1d9e4b466b017dde25cb9c6a00fadaac11": struct{}{}, - "946f2cde0f344ac2567a4518693ba2ee0758c27ece0b924456733ebe933255be": struct{}{}, - "0cf67fc72b3c86c7a454f6d86b43ed245a8e491d0e5288d4da8c7ff43a7bcdb0": struct{}{}, - "797a7b871328b03700fdeafcac5f7d56d5c4ce26b88b08a18e379f3acedfc9f3": struct{}{}, - "ed44f66d7f01feaa16f12ae6225cb9189a66f971b6a79663529b07037baa4ac2": struct{}{}, - "4da3d2865722b9e75c92fafe107ea3c4c92d50edde9a945bc9a894e81a06ef25": struct{}{}, - "cc87762711efa0119541db13a17b766c20be32f0514ffd1f9fbcc9e5a04cf608": struct{}{}, - "e7a3a82b5c33f35caeff7eb092b3a8a963dc83c7cb8e2ef8b1a2916a4ed6d1de": struct{}{}, - "270fd076df620dfc6234c4b2bae509d432be5c662b5ba77f10c856eecf894533": struct{}{}, - "1b0b7e51ee5d8e25cc87c83d5b4fbe933d91e1a70f7ee27d98be29c348b04950": struct{}{}, - "7de6a7f6484fef745f3becd30520eb69915a469498ed7eab090f37e6ddb203ea": struct{}{}, - "859facc5a4c9b80ac2eef78916c1953bcccaab6014bb11b9de8337430ea34f0c": struct{}{}, - "9128975a74a3fe1d7f5d4b3d0fcf8361112aa01b8f484c02f59e467358d65a64": struct{}{}, - "73e7ec2aff1cd6bd5a13cca3054e3d2849698cf934c2623e98e10631d339fd65": struct{}{}, - "ea7b9f35156c4abc37f3d0f292b6801c556fc8469f8d62d7cd91ac7585de6e35": struct{}{}, - "e6ccce075e98c4f4c79bbb5c4488635aaf9554b7032479f6d31950479a7b77ba": struct{}{}, - "075afc2bab555807bcdeb47c4d8484154723e460d41a0f39f5a8cdd23636b3fe": struct{}{}, - "7c2852817b05e3215fe48cd18493ff6f0367ed76a2430137574f956d2964d9dd": struct{}{}, - "f5be4b62e1c45f6151b247dcae513da8a91906942535f0e2c232bf7efe4750bd": struct{}{}, - "69ebd5b4d828836c0ac72672eaad146b2bcf4776354a94baf9e3bc1ff99decab": struct{}{}, - "9f5ae78121a79913ed7952b9fa280d09390e2f14af4ecad373de701180e3e70f": struct{}{}, - "e1823098f42ea75f05e3d2a576a14ecd4e002f8892ad3ae5ce02bd6aa13105f5": struct{}{}, - "b91986dc94329dbe1871bc4ccbdb54320648a99f2b6ea59fb9cda40b3fa5a021": struct{}{}, - "2a097b21dfbe7d9b3356a0a4979c5d8f5d4bb47111a9a39656f00dab3d0ddd09": struct{}{}, - "0907628098f9ec20590f516e2bbddef0f4f1ac844cb9bb558d09239019f1f6a0": struct{}{}, - "3f83e9ad5be63bd5bf2fd009fffe6b7dd4066243975bc962edc37459c17e65b9": struct{}{}, - "5bb65bb2635517bf0382d1baa5d32a898159938418b9c07ab40929c9e417c342": struct{}{}, - "e14a747f150b845a183569bbfc821b5a9f64f6a2434487fe2b79419162884cb6": struct{}{}, - "ba15ef92502cba0a967e4b5c3c4a94c404e2190e9861a8a3d5e6504b9c830ccd": struct{}{}, - "b472d7cf36d04af4995f3305ed0a3197472b2cf250fc93480f2f5056aec82d27": struct{}{}, - "39038c1dcd05727bc258206da554a4062432022f8390023307dd753acee12cce": struct{}{}, - "d3f66299e7310312c4d9e256ea5477191e86e3d6a1fdb3a0bec322dde4db0ca6": struct{}{}, - "f27f91a181b0dbb1d03f6592e372ad7995a9b0b8d875251b259197676a09fe25": struct{}{}, - "c72136287e12d381795c8b24cc1e02b1a467a41b55a9409b0beaabeb4f39327a": struct{}{}, - "fc5de2f511f1ce11830075984d8e495388f57281ebfe69bc9027b3dce67ad9fc": struct{}{}, - "9c4aeaaff9e6d7b5d3fafe650a15d50500d4f71ff80f4a220e9998deb8d6cd0a": struct{}{}, - "4ef5add54b87a2f33a2674fe630c7c9e3532684f8a3e5fa4c39189af3e29a671": struct{}{}, - "a0052a10f0c6b204f4b7935354718ef8f15a15378ed9bcd69dc6e5bd85c810ee": struct{}{}, - "f11dc86946e27a2426b77fa057b38eb7953f18592866835a30ffdbc747e7c149": struct{}{}, - "010f167a05df2bb89c4c39023e57dab1f411bd2f087c36a40b5779949aba6f44": struct{}{}, - "02c2a43e3eb3b889a7e5168015f44d18aa0d5f02c2f76b4083a9103b5f1d0ca8": struct{}{}, - "5dc26a6dd825f127fc712a0731c3213c581c09eda051833a2d59c2335c462e56": struct{}{}, - "3631428badf3a7b0dff8223c4d2bc13eba642c8d82fa6ea3e0242667238d8bdc": struct{}{}, - "7e1ddfc85ae45a95330209c0834c59876011aa587be693354cbd1f40bf637fcd": struct{}{}, - "6e71138a331037ba576d354dcf359dd1e101dfcdd448aa54cbf0b98b2b2d70a8": struct{}{}, - "a9eba7c405ea67a1992992d7464ad4f04b7e8dffa39e249c6b26aaa54cc853de": struct{}{}, - "f28d147a9104fa3bdbf2ad90e564748ef71dd36b54610e64a229e342a5a06dc7": struct{}{}, - "0188eaa432b3f6d6630f9daaf63ec361f32af168dfc8c21a38b7879b1ad71bac": struct{}{}, - "e3677635d58ff95e3ad515835ec8225c3a69322de10280fd8308099ed97cdaa2": struct{}{}, - "c51c35446efc7e70e901c7de15503a58cf01b21d7fc14f1b9eecd35a164c3b14": struct{}{}, - "cb835c490922ac919ccb1f8a82dacda2341aa01a016fb9fca13b164047b1af1f": struct{}{}, - "03f95cac61cd821e3a01eebf15992023572cd8224b4c324a977bebf9212caecd": struct{}{}, - "428e626f22d0273bb2741c8747418eaff1828bcd397fe7d00f3ec30d4519d95d": struct{}{}, - "ebb3fb3fa05585c909e9142104d86f136f11b03d04721b6e9710ade1d20f2bff": struct{}{}, - "209f76418ece7c936b65ff4777a578d860f762c37ad6c7f08f5826242199ef51": struct{}{}, - "4813494d137e1631bba301d5acab6e7bb7aa74ce1185d456565ef51d737677b2": struct{}{}, - "585579aed1c5241bcec5569704f0863f09f42a230a3329daf11e8314025a0e28": struct{}{}, - "934f8b275e0ccb3318ba0c4fd1a5c90f696cf2c652cc4b6d8d71cebc981d5022": struct{}{}, - "f2dc38a3e1e17bc2f794d49ae0193f6098469bdc8096bb55c7c2ad831110e9e6": struct{}{}, - "b7e507f7b30caff568e11c613de215eba2f861b8545ef8c30298fdf9ddcd97e8": struct{}{}, - "d945bb35a4c92b37db71f54fe6e7017d00f2dfcb8adb0e2c3ae92c14ef22a141": struct{}{}, - "11a7e23be5e5dd4f89d2453ab9b688894a80b62e5fb0e87c24679608e2be7d05": struct{}{}, - "d3e1af605cc84c4bf7da5bb5981293230d442490f575030a9d4359355c5d5f8f": struct{}{}, - "c6de72fbfb89efcf82e78393d9b7736ebc531f493a9902bd120202101d35925c": struct{}{}, - "c2464d0af1adf47c8b8a772bbc3962007163662ec7b76b76bc58c298e16ebded": struct{}{}, - "17a79e78333a54be9aa73849ccaca7953ce67ac1564fb7cb3f22761b174445fc": struct{}{}, - "67627795bbc4b6146494628c930749eee41a963e3d4bf47b7e120220f4c616fe": struct{}{}, - "616a7881e858b678cf38c4c04cfb96c9de14f09d52b4c2a5c9748ebc04adc608": struct{}{}, - "11faaada218efca6d82ec73ff825c1f9b84d438a524aba91646c1d999a647765": struct{}{}, - "06cca3daa6a7169f98b52c406ddbb2db655f7557597fe9fa8762a29d57da7826": struct{}{}, - "3b9c2144ccfa84fcb90406914c128f88f7bd23cd42161c591ec6fb9258e8d8a2": struct{}{}, - "89b4d488fa02d85dcb19c43b6e6953fc4eb60fb27613609be56d6bb780243e1f": struct{}{}, - "88a326e3e3d2a1650310f396d93abb7b7eade342a91f2cc8879dd796ae74a342": struct{}{}, - "da61d340a01040b02c99f7408582507a14b1255c118e5d880ca94ba0e2cc6c57": struct{}{}, - "5241ac9b5a1b8ba90df68511266b06c6b7afa9ee81e3069ecf78e4ac2bc442ec": struct{}{}, - "e7e51cd08384f255f24920581ac9d8f07547bca093da04acf40d4960efd2350e": struct{}{}, - "1faf23fc4ed5880a64cfd5a2340d74628c99f50b8314d38935a2802c553fc950": struct{}{}, - "1747bfebc4206b6330d65b3f549dc796bcee472a60d7c5692a555f938ab4d1ad": struct{}{}, - "075a421a01fe4984b4ade4a89afec861f9a435f54b5bced6d0a0e5a8792e521c": struct{}{}, - "75ebb7b67cd3d83161af8abafefdd968b31bb545a2fa4d2abe1a151adac22d1c": struct{}{}, - "dce35d38906a1717ebf63a19020cca97f500129d5f001ee59fe4aa9696fcc95a": struct{}{}, - "a793ab8fcc2afce5e42b3044090719ddaff19741697d93b3b7f36838dd5e825c": struct{}{}, - "a9c1034fb623883b5786268bbde25a871b72c9920a4ff708d516e4852bcf7da0": struct{}{}, - "a5f21652f5a4b0c61f4c138b2dff6e2c8ef264c9e989dc3d8ad0bb73ff48d952": struct{}{}, - "959e0952d86d5a76fccdf73147881788df6e9ffa2a040a659e02679adacdbf37": struct{}{}, - "bbdfe4a9b28c108927a0179818401f7dfdd6d3876f1a7e737ec8acf5f0118473": struct{}{}, - "3bfdff1876d31998b0143f91f005c124ad60ed7fbd1685874c907db8588a0609": struct{}{}, - "254dc5b4cc6404712076d475877a1111398d2060db0f457bb5fb32baea3dc1bc": struct{}{}, - "bcd8af9004d8b5d24647b3f3cd5759fc8d3dc72a5c3fe932d97be2960d57b0e2": struct{}{}, - "4b02e0c6135a5924521217d4b679493c2a717aa57fcb56760d51f7b2aea7abda": struct{}{}, - "097a2cd802e4f68beb1bc00317bec1d6647ea53e9511ab296c91e38b74a17c47": struct{}{}, - "ef3ff703d7565ab5215cdbe043858f2b8a0415b2b35dd17a3da1354f86009b98": struct{}{}, - "1e6243a2b17026f8b37bdaa6db841aa1d68733ac98dce46ef9aee051f38c1772": struct{}{}, - "2189c0ed714f0c54ea91fc1d8355e3b1d68723a4fb580e99b088651c80a50f26": struct{}{}, - "965abb254b10e268bd85fa6d2e8fc44c2483dd44066db334c98c0425f1c96e11": struct{}{}, - "648916b158d4df78f767da71ee7828f64422ab99eef1d0df68b0acb0f0218a34": struct{}{}, - "cc5892849bda4b6e6beb4a2131adc1411d0c8ee1450d0552358319bcdb94e20d": struct{}{}, - "70fc4b74eac4319f1f5558f146671b5753a19267e8821f2bae06f54a0d99eac1": struct{}{}, - "6cf6873c4b106dc0dbb19ef81fc02e8ec3c89d906417ce9c5f62654f81e0d83b": struct{}{}, - "9f220b7b8c337969db7612f3f0c3fd39e06b8a10d448f90eaaa198e1f38b2609": struct{}{}, - "d9078313c20e82941879e10262ab7a761f29a61e835f9a76a742f3f914cea941": struct{}{}, - "c8bd849e3a5aebe0398a3c88c0527ab0814deb5db8a864f4376f4219425d2ef7": struct{}{}, - "4733f0b0ddd035942d0ef395e9c62c36de882a66f2917f1d8c072cc9470bb2e9": struct{}{}, - "f2ed4886e20f1a035f5e45c9ae06e5c07a700e08a9b1cf63282e16a7943e0c86": struct{}{}, - "0bb72198c10c480e58e169909b885621f577f46dc43f47824ef6ec2ff16630b8": struct{}{}, - "1b232b3c131fc49e42a328285b77dbf602051fd1731dcac611b93bfc0764f8ba": struct{}{}, - "0a1c91f5c36e085b476ccf8dac7526da5788eacf354c704d205fe2610abe118f": struct{}{}, - "bb9be1891d6c25c3917633adeac6d7089c615cccea5ca56dc3f20230459511ad": struct{}{}, - "3abdae75474b462a562c568bb6df620ff7d53edab8cd23747872597081e55145": struct{}{}, - "ff7386f91eb664845af53682fced7c6e0a7e67bf7aaae83e6c79606cd95c317e": struct{}{}, - "6cbcf42824e37b42397f743f852806ce04a9e63119ad00a0c9e5c2b081adc62f": struct{}{}, - "9b550d15e298bd082ff0378694e05688e79f6a710a600ec00b834e8b15d6f6e4": struct{}{}, - "a1a34636362a0a0445187a676fb86b7c4c43c444c0a9befc319d07f84686c047": struct{}{}, - "1197a20df1b4c87b10c868648eba70eac619e18ab6a6772e32bc9c701c315e41": struct{}{}, - "c84c8016356014e02b049ff270c079dd03ab5c5d44a120bee60242782b234ddd": struct{}{}, - "3bed2cb3a3acf7b6a8ef408420cc682d5520e26976d354254f528c965612054f": struct{}{}, - "31b399cc89da2c97f5831bab5a2ba04020000ccf0881412f59a14fbdfe7c046a": struct{}{}, - "8e71b24534e9f3fb3a71263359fed2b7ffb008265e0d34383e319f1b6f5c08f2": struct{}{}, - "15dacc3f49e9a056ad96fd6af691de608d41422e5ee041ef71a60d6d942941c5": struct{}{}, - "e7f5725812407063178c78bf4f1468f9e661ebd6c1fd41dd933ea1d126cc189e": struct{}{}, - "87eba76e7f3164534045ba922e7770fb58bbd14ad732bbf5ba6f11cc56989e6e": struct{}{}, - "e84e17ec8862fa1e200c3b1fb4a2c2cb5e475ff19b68abe56ea5ce9f453ab9ee": struct{}{}, - "fc00eb484b582cfb374e43802b91124bac34ebae56308ac5f5d6b7af6b554c85": struct{}{}, - "c5ad410aa325b0fb6909cd571d1ec07ad21325bed3f05807daf506a25bb3757d": struct{}{}, - "aac302f85bb8a18d75cbe8d9f6910cacf4b4e19e8544b1150d9133124739ded1": struct{}{}, - "606fcd59dc3d808666f24516999bd647bf9569ce2e82f8be8ff465f4f06304d4": struct{}{}, - "821cb01247fcddb76a548c89b50ecd5694dbc67c34c8797645410f9134c9a1b7": struct{}{}, - "6c3994dc0f35184da4adbe1cd42ae36b6e92fd01dd14bcf17c192826cc747ee2": struct{}{}, - "882c9f7efaf6bab44e1769ebd52fe4fc1782092efaa4a747618087cc60a81768": struct{}{}, - "c92920a719be641752ddb083d8ceb6e91bcf5e97ef732daa3a57b367a668fe9e": struct{}{}, - "5bc5f7d10c706393b828db4a427c034cf2befc2662c02275cbfbbdf20a6fe6d9": struct{}{}, - "eb1ea9d72fe0c79269a8dc2b047ddfb531b004ed19afb45eaef9e9a38cccf351": struct{}{}, - "bcf12e565a7d41bfe81f6d5b8100805387219b0b9888a90e1ccca04c418c746a": struct{}{}, - "5e0cf7bd1dfa3831788b0cf6dedcdd228fba6f34dc238d371e746567e80bc7b6": struct{}{}, - "9d3c4061c106b76e02627d5ba59dec85e4f7f21b6d34d9dc5ac64e04cebaad23": struct{}{}, - "0a19533d8eae0719d0e75b3cfb2d80808111b7612756418145cc7103e621f352": struct{}{}, - "0f06b5214c9afb9882c4eddd230e456f29fe083d0e11442ccf376f973f600d72": struct{}{}, - "0a003df1a1689aa8c6d38071839ffcc1bacfda313cf4c5489beb4d521c69df7f": struct{}{}, - "b7b79f32a0c4d45565f6893b8504fd9c8df10c026517055dbe6564efc4102cde": struct{}{}, - "901be86d450c504e8555ffeeeab1e06b926c8785fd99ef382c1310b7c66bc167": struct{}{}, - "ed70e57a9869a689a8367b4a7383cd0a5e0da698c2ee05428a9d8fb852ad6c76": struct{}{}, - "db26ce04fc0e235ae037a334d7e939ea6dedc4ff234fc5e5578fda274d578550": struct{}{}, - "6b7d902199e960ef699051a48fef1214319517d6be498de768f1dc8a9a31ea38": struct{}{}, - "65b969ebf928fcf27536afa28d79743d4b99a30af4b2f33a019019cab344705a": struct{}{}, - "1f1c5b2fad778434024f1537986346927917f4755a6e7d3fd91f22653b7c3132": struct{}{}, - "6e98b4842553dfcdd1325c2f3fb78d64733a0851a8540ecf3032dfd6cc3563ce": struct{}{}, - "46372791018924b8cbc444334300f85a211d2f29a56f2bb4890780b5983fc201": struct{}{}, - "36ab771eba23f49d7ae43af88c601f3de8fccb201250906a4085444ae765f2db": struct{}{}, - "2346e9020be74f3e6ac6df543f708d8025309dd3b62fe19da27339981662a4d0": struct{}{}, - "d5b403201c2bdae39148293cc87c9434b34953a5b86ff8e731ebf031861b8001": struct{}{}, - "2c96398674dbc4e58b556e6fbf53f85b80be112a3c9113851f431d3a67275208": struct{}{}, - "e5af78f00ddc515a3685c1b0f392c4241b2cb36415a87bdaed229b598ecd74cb": struct{}{}, - "d4e3fc7b0477747da28f05c27af9f1ab8e70cd60f729a2813f410fd39eb466ac": struct{}{}, - "90dbf3e3eff5727d212b7f085c6027801eaaa4f98492e0ca3f4e66654cc2c348": struct{}{}, - "992ac612cbf100702bf5424d7221eff2ffa29abe0a99d611c073012dd9b4c717": struct{}{}, - "23f7bbe7865778a4b534e9335e5eda550feb75de4ef163c56a7a48824abc9b37": struct{}{}, - "f1dc9647b26d25cef5b49911870a44986a5e755724dec32a416cf80c9bd383ed": struct{}{}, - "26429a356b1d25b7d57c0f9a6d5fed8a290cb42374185887dcd2874548df0779": struct{}{}, - "dc4d53ba57bb6bae9dcab955fb62204e794208fa15cb824877a99768085d6c7c": struct{}{}, - "59ade5a471560afadb9766c700fd045b00b75bcbcdb3a8f1eb8183559de6643e": struct{}{}, - "17a79d3c813d15483ac126546f246e3ef7918d9218112039184445473e3f398a": struct{}{}, - "6b8dca09e851a987050463c9c60603e9ad797ba09117056fc2e0c07bcac66e43": struct{}{}, - "1c5f7b0e6a9e11f77d8d6012016792d910a67e7ff8433f4060e4e672ae8727b4": struct{}{}, - "b8a190486652489b92204dddba74c98f05213b534863a6af27de1ad5c954a296": struct{}{}, - "08bd4e6debc570f0f3b985a822a15fffb651988ce72fdbd4e150ba0faa35a726": struct{}{}, - "d0350a6064de840458ae346088ad3082824939283153ff87ded35684f120d1d8": struct{}{}, - "cfd90af8403941bfc0b06e249c2f849d356b36b960452e34cc9db1f1de5bac9e": struct{}{}, - "a0a6df0d44c07c5b474b5aacd168c4c30d7f3f9eeea0cb797c5d3c37c2711291": struct{}{}, - "cf0265f289038bbe1fbf0dabb2190014c4bd05cd5097b135c63a702c102ba9c8": struct{}{}, - "2f57d47a3370b4376ace3b02003f58a8c1f8d3238aec8d9a78a52c7927618ee6": struct{}{}, - "6ca1153d981d4631c2f728726030a958b16659643ec5f0aecf84a3331c561256": struct{}{}, - "7ddf5f4bb23d3e64c9ebfc74ada0bb65e3271f0b52e3dddbc2a626e48ad60e9f": struct{}{}, - "29196e6cc036a16176b5f06ac52dd61310cd93aff283bb74f9367b0ea0030ec2": struct{}{}, - "a54e5638bbc34d80ddd34634760fe0db2bbbb301bc97675112c21df61eefdb63": struct{}{}, - "5eb8c5e422fe096a65be9a969c70d528350fcc98ce3f6d62030ee4b755904806": struct{}{}, - "2686af9f25e1a64f5e9f7290c7e457aa06b616fb31d2b4331ff6fa0857661cd5": struct{}{}, - "2bed7dfed277df864e5843e57f8ae14e38198053648a141dead0b7b1ce7307e5": struct{}{}, - "abc9ac4c60ffb6beb56bbed7a4de6f42f0db431b78030ba6a55f07ef58503d04": struct{}{}, - "8fd0548a8d11659bb97657bc56b3ba2b1655b7e951292b42d1633d9e1b1c2ac2": struct{}{}, - "f8d397a33fcb9725db96501e653bf3cfa4455c5639482b9936c22b221634d659": struct{}{}, - "dec0910bf0ae0b7ec107c02d932c73dccdac804e5c86b6b406f7053085609994": struct{}{}, - "dccc5d01dabcd1c0b9fa89c91e7f4bde603121ee0172b4ff394e6bb30d295e41": struct{}{}, - "dd92623b0a4b255f87cc4aaee7990ee182d91db49189df6229ce65b5e9d960da": struct{}{}, - "8a75cc3509a6b968a7f614bbcc3c78a45352fd090142ec7c65e0276a260ef7bd": struct{}{}, - "c5626fc850ed2e704b0e670a9b51548562a99734c6ffe4158d8e0b457f2ce6f2": struct{}{}, - "e718b2dd3e0ac1f9208efdffe98024e7406e29c51c31bcfa9a6b29d1951b19f4": struct{}{}, - "06430f0d6a4605c6bf1aaf577b6dd9a6eaefc65d93c9875d363284304bac1b55": struct{}{}, - "7c4a5c242b72a23d999bb2801c6544a368f49a3ab869ef578e171079b44a9ad8": struct{}{}, - "cd5f28ce664e7689061bef958d003ecd8f221500d8b1ddce9b8dfda91cb54558": struct{}{}, - "72a803c492b0e18ea5e16cb6df62420b3d9e0a9c739be4df987d7edd706dd780": struct{}{}, - "da9072a441087b4be543c7eeff91998532cc46459bca7c86cda540aaa170fc0f": struct{}{}, - "b958e3f9dcf00628c525e24e97d638756defbcff2a82540604484c131a7bd307": struct{}{}, - "d2fa0e68a5824ca58e1a49314a4f395ba10e7eac10d8481653f6940227b412c4": struct{}{}, - "737640f728031e2dfffbb7e6d2d587cf8d247b2b20fa647aefcd600d26ae0419": struct{}{}, - "ca9f642d37edf0b718c57dd0ee4af62de0bd587959e6bfc73f01d6a3c71d4ca8": struct{}{}, - "e98f6b8fae2a9dc58af8c6022e5e9182bbdcc1870004fec20cad588bacc081ed": struct{}{}, - "8ab4ca2f83725a04752af6fed80c77ff6708f20ebd4ee32e95c9a375bd8fc1ed": struct{}{}, - "ae6c79d10f1fd410650790e63186ec108fa106325b52fbd88de21a43540e6f2c": struct{}{}, - "fa15cfb46dea233177530c9f6f24847ee3dada123c2abe897c80629bf4c28392": struct{}{}, - "d464dd9cf16a82bcf6c65b9621666ada896526456a3d80606063f9b6d29bd0bf": struct{}{}, - "ff617590bb8b98d70b41887b300403ddaef0957ecda120cad0bd4665f1f02a53": struct{}{}, - "d9e36ff2055d23ac1b39509df8e7fa4f34d86b3da922e51fd8a1d539dd10b026": struct{}{}, - "657dfceb960c830b29332633785c9b57f37cb26544f78f9a831b5da3f837e378": struct{}{}, - "641f3495279b93bc8a54d7887e221454aec9ace44c8b4c93622f8c81858556cd": struct{}{}, - "fee9bdfc5e09de5926d00ef09756f14238ce3fc29e235144bf73ff2d00af45a0": struct{}{}, - "c58942057ebb66be4a907657b35d818952ea1a09d7ceafdabb76eab6d910585e": struct{}{}, - "3b40bfdbe4441f63a9670197d32e59d1e6fd440ef07a891f9e469d683a2033d9": struct{}{}, - "71c0ecba99f1ea222a062083b6c53e20dcc355360aa3bc31ee4acf39daec5dd8": struct{}{}, - "3f3b4341e51c371706b7c6e381947f5d1f3897898680cead088bbef81daba875": struct{}{}, - "c9805e841751391ca18d1c3508700975438fe0e86161f2d52e5ade49cbb41776": struct{}{}, - "876af9e78774cabb0426f4f4b2e85c0ac07c474c9a643e946820032bd9b15b90": struct{}{}, - "e14a51fded1fb9b43de815d3832da099053b583f1d2c01d0495a70f118df426c": struct{}{}, - "ce9feb8bb511e2ac293feec68b66f100b1767abeca6ea8ab28fe450403713008": struct{}{}, - "1811bdd29f2cfe95e6e23402e2390fa1012708fc52ef8b8a29ee540b1c481534": struct{}{}, - "9ecdde7c0a6e7c8ae2cb8e30dc7dff21db4613f8b515ab0e1fca957d3bd67ee3": struct{}{}, - "ce7611e7481e75f085f6902050b40918b0e8374634eeb803ad3e340e90267d6a": struct{}{}, - "500751a25650c0eea7e0745fdfd936632cbda412fb8569515923742ec89f0bbf": struct{}{}, - "5c509152b44d4cf07880f731c535511cd465de0e99309ff15b69dc504511c4ed": struct{}{}, - "08c4242b5f585628bb8cef8ede40e9895b8719eb8070aee2d96613583ee3904a": struct{}{}, - "393eb45b79f17427b13a2e15b46520ac96917b7397228ced1276ae78d53315ad": struct{}{}, - "05a39f8fb4e83aa15683d84f4d20886b4de926d8feef253e6951ae627022e3f6": struct{}{}, - "0d7a122d153d990ecf252053e398e6bba8162a5e342d765f376ef50fe4443794": struct{}{}, - "28b07c7a913e77b17b857af9dc78af41779d63cd7973ae6716a657e065cceda3": struct{}{}, - "3fc9b689459d738f8c88a3a48aa9e33542016b7a4052e001aaa536fca74813cb": struct{}{}, - "28a8759ab6a913d16f45c42b00913b175830f3db06796d687b1caa3fe4b279de": struct{}{}, - "81cbf4e1b0daeb7bcfbb88952b2739b9847530f9dcff1a59a3d3a439239b9dcf": struct{}{}, - "54253b26788d7dba8de97f3d08994b4fa360c7fad0653e4f2887119976366fd2": struct{}{}, - "40b6642b27806e3dfc0406f0af10dd9b4242df28b73815adda95740b5d20e0a9": struct{}{}, - "d3bb0ca2779f6c8f5095d798d428a27489de62b7ddf783f3b7c5a1ee9319ee01": struct{}{}, - "5605d80da83130710b405d2cd53245794a088799cc52c9f7d149b65f2468e5b5": struct{}{}, - "f9d35d43770d39092a663e665e82ae1d84a9e0da3d0d10c407acada6a40cd281": struct{}{}, - "1d9043b1fdf9a8e930e7054ef1b3e513425ee43daeaaa46f7774a22a5b77c2b8": struct{}{}, - "e03644633d4bf68cd34089c9bc466370ab8c9a55f7d5a143633a9fb71c98b6fb": struct{}{}, - "6b282e3f6f4f3f2d320388f1f6c8c08c54ab651883af9445d3cef31a9529cd8f": struct{}{}, - "03897489a16c7de3bda9b2fd445b4407327c07f86093120f764d34212f45899f": struct{}{}, - "5605a516a7ebf33bbfc1189be3b201f380d9c721b124098eda2894544e2771d7": struct{}{}, - "3547cb112ac4489af2310c0626cdba6f3097a2ad5a3b42ddd3b59c76c7a079a3": struct{}{}, - "1c44b30147d12c47c354230f6bd99bb17657e3d5bf616ac5408dcbf28e83fdb8": struct{}{}, - "879610ba2b8717c14101e4eecc47ba3a3e44a2017a2ca94b0c1a0c1f659fcefb": struct{}{}, - "e6af2c200c735e292fb4eb7a1eb9f90cc59e4ebfba72d5ef7df18e72ddc60149": struct{}{}, - "5597b2b5d60f8487efbd5c7ddf25753852ac8e637063932fa47df28935ec9808": struct{}{}, - "00f39d7bc6bffb399323c89a64d047ab70320e8ae2d396cc1e694320c5aa9b03": struct{}{}, - "be37e56f6f6a7dfb95952a780b9be2b3b465e468c56307c143a745c3a7c681e0": struct{}{}, - "d1fbf3147d7d14c0fd8bd25ca2109d817c51136f136c008c85f201d0211308f1": struct{}{}, - "1f58b9145b24d108d7ac38887338b3ea3229833b9c1e418250343f907bfd1047": struct{}{}, - "aca5b8e5685f39e41758a05cfec88dd72d8d9ec8b899c233da1ffc33bbc15ba0": struct{}{}, - "135fc7a09da25f03e44f7a2c700efd4a9d0a989af4d4704eabfe9ada71b26590": struct{}{}, - "4af1140514ae771150b7f5339163d22bc4d8b4cfc93c9841c09c4b525cc0085b": struct{}{}, - "c26838ec2908c920da3705379b9e902991f7e1bfab5b20bf85936143e1abb089": struct{}{}, - "fbcb269230d74ec30fd70d2d38a544c200737032180914d00a7431501687f128": struct{}{}, - "35b1cbd615818c4b6cb2caa3ce7451ed9aa131cf5592547fb36523e42ef951ca": struct{}{}, - "7c5c125af948679628565450ef159a4fc502103a68a93c8934a704702330a721": struct{}{}, - "db418e6299b87ea285cf659c1b131726eb93dcaab8b544a0a15d9137c29708d8": struct{}{}, - "c75e95f6be6d5e0c7c42dc5a6bcbef105a3698a68c51f857028931fa0b7806f5": struct{}{}, - "4c935b2d93bad98bd72aa3c480ec9dfbb6c79a71148153b2717dbe89d71c8d83": struct{}{}, - "264ad4c6a1169d92eee663ad42b9ec0e8cc00977b5dc451d66989f628356b3b5": struct{}{}, - "9309ded1ec5952fe031d1e71790f48320ca504766b77785ce00ed0880b71a56c": struct{}{}, - "7d305fd473f327e1003df77ace8f3b706642217773cbba93ceea8ddf4cab3c4e": struct{}{}, - "4d5cf48bfd182c3a0d5d89cb75dcf1fb24c5113218b49a5cdb9b66fb4a81bde4": struct{}{}, - "53bdbc54d42e515746dda7af36e93d61042e9d7a344e11272e49434570c9b8b2": struct{}{}, - "a55b0153fdbfc0fb5605e4ba6cb14642e9f17f008feb86853fbc27ad2b9fdf09": struct{}{}, - "2590c36cff76b98c71773025158680f0abe4025d09bed4043cdab891fb811034": struct{}{}, - "8f3c0aab3718b611dd021d6bf8d97040a896945b044e1efff993b456ecd7a986": struct{}{}, - "7c0bb2ad8c7ef423a7da5ae64a7a6383f59ddaa6416dbe58e4b1d1ab024ddfc1": struct{}{}, - "c7647006fa89ebe136b7b96bd4d4ee2e37e58d267fc615f0d3309c63a5559039": struct{}{}, - "b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259": struct{}{}, - "2a79be6a5deb17eb3973b3e1872623682287731df936d313f7c8b0e4a336e958": struct{}{}, - "7f923c0659f47d327646fa9ab71f18cca4d2e77e5dce2d59c9b13cbf65df1007": struct{}{}, - "ac996ae492f9a987c84f109f2118d3f6632e2259455d30c455a9ecd66c3c4959": struct{}{}, - "f5774877e0057713a8093078982383cea4165382b584e8a82c9a310e247d928c": struct{}{}, - "e12ce8285efc67c6d93d3a122e2589ed95089bcbb775ba5634d94e2b8385db07": struct{}{}, - "60280e12933862e2f1552575bcce9c74a7890c9f3148c031237651eb5d5a1a71": struct{}{}, - "b982a09b35429d8f87552f1af0a512034e382562cbc792529ed12fd0096e4bef": struct{}{}, - "f4714bc14ee3d38786b379bda4a646eb03f478623d76033b3605e1244a5a2773": struct{}{}, - "c25b077abcdf064a1977327ca54679c174d1331a5ee665c87dab6cd1a441e086": struct{}{}, - "12d4e6fc471fbe073df5a0678fcffb9f75b12161e4e3f6d1e1bd81ffb22163bf": struct{}{}, - "f460554ef1e286f4c7dc66b4d0256dc5cff3dbfb14630f847b2d75b5376a2374": struct{}{}, - "05c08a8470f37cfad6b30fda4ed573bb8446fb0405701f8c2e263a6fa63b3b23": struct{}{}, - "04df7707fcfebae50d566116fec01e48db4ca45dc58a9d55e21af65e0b37dab9": struct{}{}, - "aae5570e24fceb160ad89022cc4ecf8f225aefb7cbe196c5758acc46a486b0e2": struct{}{}, - "b667446e6eb2552fbe29d98e8f5c3bb7197cad4ab1ec72cf710c406956e71e55": struct{}{}, - "d40c2dff70173c510ea314132fd2e7938750c692f0a99644b8ffcad5ffc5b8e4": struct{}{}, - "368d90c41d74aedc06641dffbef6ce54be38d20580a4a59264c325105e2695f9": struct{}{}, - "ceaa9749b0471a8bcd157a4b16ad02dbc319a0422d4900245d3ae368bb72e75e": struct{}{}, - "a2f89e6fe92debe338cf2e2ff6f5e24c1183cbc24040caf377dbf85522cb2ce4": struct{}{}, - "8ecb3601fc29d7ef39f25e072687f5e620c6ccdd1fafdfcc9d1583917918ad9b": struct{}{}, - "2d8f65dd1823032650f1686443c0659e646d63907bb25ffd73383ea070bd76ec": struct{}{}, - "e127b3ec8c4e1c29379b4a9dd0d0e825c55aee6d1238afe1afc8852b77bd0999": struct{}{}, - "985604c2b60243122c24d4e18363e6434c535923be05f760204a1aef023aae9b": struct{}{}, - "3603a9780bdecf2bb5bf4e55685c7a32cb38191a6a9003f31e38890553caf5f6": struct{}{}, - "58fc07e995e1e6088dadc87755b86214336d2f7736aba12b345cc3f168a5365f": struct{}{}, - "d64bf06140d19c0e2c3c3733b01b48026bbf1697a55da13034fa4aac8a05d2e5": struct{}{}, - "71f74e53b3b9486bea349f892cc4332a672a20dd450cc83c8ef3110452375843": struct{}{}, - "132106a20b11b49bb6c2d6a39e302b5ea3d4acc34cfca1ad41e102d2ab898c0f": struct{}{}, - "7dbb8b144228e2f24fe4279cb204bedbf97914cff022a18a1e73ddfd49b310c4": struct{}{}, - "8dfe4b3e06719fcb3d457ba75420ae124e9261b8a2ffab9b02b6ed488036561d": struct{}{}, - "c41340e46777f1d527d804234aa17bb29b1304e3fd4a5649528754cb34170ee0": struct{}{}, - "9cf90bef112438f2194b5265ced5b68a45f8ab7fde95c8b65e9072a0e16cc96c": struct{}{}, - "31891a39081c66c2bbf87da4fbc373e36760bdfeb5d516cc6e21f582319bc92a": struct{}{}, - "5313dcd2d76e53df7c383cd56a0089da26f47748c766de627447f510b9a2e81e": struct{}{}, - "77e684c4e975c6227bcff1fa54d689f6338a5aa3eca58bd85a788bf808deb659": struct{}{}, - "9eda53a91d8e641c59f219b47bf0c6dd3f0fee25a1fedfb746c8fd479dcac49e": struct{}{}, - "618dc58379dda348eba010597accf72a98ab4cd357b42acdda4091901b2ff144": struct{}{}, - "54db7aa2038e72eaf503cb42d8917ae3bf14260c2b73c141c2a4d8e85daefb6f": struct{}{}, - "f8ba2827fbeddd6eea2d259991b9b359b85fa4688afa7738a40b0da514487806": struct{}{}, - "d64dd9adc9aa8b146a7955723523215dbffa922518015a03b72154d1ed72c071": struct{}{}, - "856f3787f23bb1f70bda91c94b0d25dadc965b8991d80c8645dcbd565019fa56": struct{}{}, - "7a479cd06ebc80d12071d612c6ed6edb5e54e84df706d667238522821bcf8564": struct{}{}, - "bcf21aa743ba9a5765ea834cafb630926f53a70b62977efd05f7326629a32b83": struct{}{}, - "6fc0f94e9e236188fa08793341af92b1b214780047f67bb0220d304632f43b84": struct{}{}, - "ca0667af548100c9cffd30529b3a3a346d6452f103594ee7cf64e1db0558f516": struct{}{}, - "d97b2a20a6daca8842a9e744663aeda131aad3e78ae7a9774560b2c5d76b1578": struct{}{}, - "290abd3147a14f4d0c3ccfae9aaa921c223ac755e6bbee01d535ec2e808f6146": struct{}{}, - "fef31a40a8e0eec69e0ed37b3a9d087ea4cecf9d9099ac80fcd566058ab454b1": struct{}{}, - "1af60cb6cd7d342d86723a84e196b417247ecc50cb890a90bc797a1849428c86": struct{}{}, - "bb482098181fcb0c95c9bf9f14741a6cd72574f925b09949a8c4ee48bbc6985c": struct{}{}, - "1be9f9ac6005f5715e14e2e7f7e7bbe9df841af74a13ede502c34a215f3d4d6c": struct{}{}, - "2a0070d014bc41cb1fb0e65f98a3c94e00478a2501dca02836d2d5eb3a742a62": struct{}{}, - "fc2e913f4d882548a8b133e4e61a772c26d3f4944c444a973a8bd6500e90f8c4": struct{}{}, - "26087e0854ebce7958f13db1e249b27c3181fcbc83c98f0f86e52f8bb591c7cb": struct{}{}, - "647526ed3a6f0d4201cb12d47b65fea7918fcdbb6ce9bc237fa66bf5cfb7e9af": struct{}{}, - "1c632423f027286d8e90087cb0c11f28d7fd5df7a9e44539dc3a36722eaddbda": struct{}{}, - "28f8258878dcf516807e8c88fd518b3e2a3b463926d9b8bc17d678ad49c184c5": struct{}{}, - "41e292f7f018432127ca6c5b984813991e15213ff82a6ec99713329b3d9d5f0b": struct{}{}, - "66cb86b497f2b457cc6a19fafda4952bfbd831ed9c2d7ba12157ef269ecf3885": struct{}{}, - "cf8350e18ad92b242340346adc7909eaf6a9aacc35b512d2bf60005fa952bd19": struct{}{}, - "9323dd6786ebcbf3ac87357cc78ba1abfda6cf5e55cd01097b90d4a286cac90e": struct{}{}, - "9aadd0e12af687e535c62d49bc0512af431d8a4b3cf89915b133873097926b16": struct{}{}, - "fad900280ce54a04da3b698f1a7783717b466fedd3ded41fe5fc5a3686f1b180": struct{}{}, - "014e3f1f20764cc5dac7ce673852e314fba41cfe7b3d15ee929946ee4ad8ad1f": struct{}{}, - "791ee309d1637cd3a826027d4b3aa7dbd9a86539f04e0a6003fef403d221426e": struct{}{}, - "49a331ab6a4d3efe24e2591b6e1cb3e826d939c632fc8a155bfab52351d4e7f7": struct{}{}, - "6a8a77aac456b0416396586f84649b86dd7b6e64b5573e82afab52f9ff0c2306": struct{}{}, - "492e59ebcf35876cb30265ac81c3b52af88385c1d49de23959b3f01f32080939": struct{}{}, - "6770928359445e82d7785c6372ec6f076b49c88bf84e2bff9cf998fe3b1e32a1": struct{}{}, - "d9780d47478787498c88ad589aa18fe5f2d54ad9cac3bd755834b577c306c04f": struct{}{}, - "541bf3ce2c00becc8012af585a3fa01210c046c10039dab2f7990b5ea84c2312": struct{}{}, - "03c75f679baa2ecbfd4ec3ffc4ffd33c584530a278b3d807a37bcb9aa58d5e67": struct{}{}, - "7c17fb094d43cfd5381525d626c5092e7b5936de161f4873eec6e3bfa034b8a5": struct{}{}, - "6df24c372c044a0fd8d05774ee70449d7a035d99b3bc3b570a0bf5b22556a904": struct{}{}, - "599a4410e2af69d1585f16d82d4b5f0abf3ad09fa42b9d55d7b7a50671ccf8c1": struct{}{}, - "af1b2d025b13d63d480d5ee821e53d476ef1d7b8af70fb8866b4db59e72deb1a": struct{}{}, - "302602fc5accb543cab0e2f8f3e58b0d747fe476746831b329aa28b17c3148b0": struct{}{}, - "2438ec1b751d071979589d45f4f5d9d9697ade8f1fb474ae21898d16f52110d4": struct{}{}, - "b2e78e3a5bb1d60df728818ea576deafd6ba9b428fefe13794e012f226516dad": struct{}{}, - "ab97542e73d2ca454912abff83137d238c44c9eb6f6557763d18c051ff572ea8": struct{}{}, - "65f15ce8fee25b248445eedc63cd9a82094e35965e4ed552e195029a9311d474": struct{}{}, - "1b001706a418bdfca35361355c643b7918572b8f9b7503f3043a6e23b45dce52": struct{}{}, - "65a9609af35cdcca66ef9f36455913de166721a8611f533eac41b940846af46b": struct{}{}, - "4dea4edf8a0900eda95453231b74eab18591d399dd0b0032eb21b9a45634c08d": struct{}{}, - "7fd09250a8d625f850e9981efb0aff6a97385645ce134ac9ed8ec9ee774816b4": struct{}{}, - "61ed2fb775aefd12eb44fe5463ad927cc1e3c3a5c9a34424fb49f13cd0c2ee49": struct{}{}, - "9bc6afa34b2f3ed4a2d84675a2900d1aea63c99b8a5bf3b24003392bbc49bde0": struct{}{}, - "dc0cebabb843ed024cb5b0a6c5a00a9fb19f86efc25f570732252ae20def99c5": struct{}{}, - "5493694a6e340e018bde9ba59cba04c37711d3ec97f47fde8c9968689e598132": struct{}{}, - "aae73247278d8dd1adbac64d167f22ad9fa0553e69b009117125fc0aa9ecc422": struct{}{}, - "153babaa1e9faa387e7699ba1ab86820aecdb3ae87f39f802a0e9efa146e67db": struct{}{}, - "697a821745021fae6b85262115738ca633032d031bf9af041799b427bc3d61f7": struct{}{}, - "b0f9feb19f571f8409421c274970f4bb7ed4e05d90c6edfea4f8cb52c89e5191": struct{}{}, - "0b478d0ea76d2bfcec3df7a50b33c501c8489d95407b17756eafb0d41f4a5192": struct{}{}, - "c877647d1eef4e2a539597841d24963407bb2d32fde8cb5cff82f19d9e973d8f": struct{}{}, - "1c81600cba6b76402998cdb11a19eb8192c08d5305d450d1669f84ac217d513c": struct{}{}, - "a0db3f3822e7cd1a95a3e797e95138a5cb7625bfe5238cdd42a29d3072562c0a": struct{}{}, - "b6f8ff77a4786fdb7ba734b39580c9655e30dad9b10599fb6129940a8355c7f1": struct{}{}, - "fc98ddf5b129cb3d65154b0468d00188a342d24e322de605285e3b1f5b74bf7f": struct{}{}, - "50d07f73929c37d7cb9ebdddfd79e000d0f8278c808a6c1c7a2de039bd3cd71d": struct{}{}, - "de6670bd96ccb7f94cec0bf8a6fead5d4697959f66e7117503c41dda2252cb3c": struct{}{}, - "631ca15fff506df1f01e4c520cceea6a14f43feaa765a25e7eb453501fb411bf": struct{}{}, - "660bc389ac0fabd79575eea8cdfeb0cc6a582f36fcf2a18afc7c225c3d6a27e9": struct{}{}, - "1f2a767d757fff528c8c0e75650992b624e611e7d5589397969db07521d43be9": struct{}{}, - "11239872d178729acc9e5aeaf7fc001a0ffd878c922330be43712b9b3be043a7": struct{}{}, - "b5bea41b6c623f7c09f1bf24dcae58ebab3c0cdd90ad966bc43a45b44867e12b": struct{}{}, - "2974744982f08b2077515f1e0dd62fd94b70d805831c994ad0a55b187ecdd1f9": struct{}{}, - "40cea1fd091bafe9547cd5ba2306eec0276882a41254c2bb5e8070e85438614f": struct{}{}, - "914ec42988d42410e36eb4bbf067d2e57d24c5726bcd64127fa1504bc27ccc0d": struct{}{}, - "54986d595e3e8c4b0258170055aab128b4e51ef86abadf53270221c7d41c19b5": struct{}{}, - "14f8f4bb8c0e79a02670a5fea5682da717a5b3d3dc7b1706f7a4bab9afae18c2": struct{}{}, - "b6b54201f23d00f5b719f532c30bc3362af2da4651d9a33cc0e94714d01c205c": struct{}{}, - "74284d9dcbcc09928ca5d7d6187270a62ac1b58ccdc4a44b81e47257ffa53b9e": struct{}{}, - "63b0490d4736e740f26ea9483d55c254abe032845b70ba84ea463ca6582d106f": struct{}{}, - "eebbf6457e46a7f63acdf9b97390f790ba443d60cfa44b607da7e5c40aa1cc1d": struct{}{}, - "2a1224bb93edd5b6b5363694f1087557ebc2accc3571e7f168135762c8112674": struct{}{}, - "be98c6532c0d6a26d7c9e3e15d148ffa35f558afd9f24a3c643ce9a07b791bba": struct{}{}, - "72a8ceef9553c20ae668c69d57c9dbeb5a09a2712072deee0f6d3007b322bc64": struct{}{}, - "e2f20cd250d5e4a51cebaba4a6614dcfafe33f87a2c54a8893376d3ab54d7a4f": struct{}{}, - "1350f84d6d63e1a3f8727e817f55778ef51ba16fd1243c6f7786270373765729": struct{}{}, - "c353f7dabebea3a68d632fb0faddc010090f1e2fa454a7272e4f8e9e29f2a41d": struct{}{}, - "d049f60d4ab3d7d2927422ca29c5432b0fa6c964d896b8c2ca891f968a65e3a0": struct{}{}, - "fe83b68f059f57d585d94ed8a5ae557f98c47281dec921d101c116193b2d8d61": struct{}{}, - "d9f2babb167353d055843c7d537e6a663023561f921f831369ea1d657aa3051a": struct{}{}, - "6d7621eecebae3a93a63ef0c6cd077552e8da3af459f46c3d4c033b50fdad6f0": struct{}{}, - "474c12b01f4f765680ac3bb3e0b670b7ac817c9f717997577cac3f12f1b5013a": struct{}{}, - "590605ebf7e7e47e221dd8a4d780a3f18f7771fbb862a10bfaa20894aad03cd3": struct{}{}, - "530c70748ecdee7f1fbbadce0461ee389dd00aba1aebdfb9a76a41c9420f0e1b": struct{}{}, - "1c188a6d76a7ed718a468997804ee69fd73e2a5cbb286687cd0739681a47db95": struct{}{}, - "82806627fbe898464e9a5102685fb5ceb2a8d6d220d394fb83e15f9c2d5fe105": struct{}{}, - "6bdb262b0201799aefb0ae4116c6fb88f549425e30cd85a7f75a06be246751cf": struct{}{}, - "8fcc57a2875da285f42bf1e56c7826603c3517faf81193ba3c326137d4f754d6": struct{}{}, - "f6f7797f819c34dc23a36f7e2cfce0d6e032ecb9ae1f999fd768dbbb588970b3": struct{}{}, - "d583197d70b8038013d77882b05403ab115516a3639d81037e70905814a63522": struct{}{}, - "d2c055002a6cdf8dd9edf90c7a666cb5f7f2d25da8519ec206f56777d74e0c7d": struct{}{}, - "dc391a3514df89d3201848b1fc3bc150edf0341c7b7b21bd13b3b85ed8143490": struct{}{}, - "c047caef95b4d3e8d7f74cbe12793cc2a98d07369636a457f2313086e02f2753": struct{}{}, - "751c59fe1fa734e61bec7c142171bfcbca97ff8223171b4bd63d8165a67a4d74": struct{}{}, - "754701756b794d3cf7cebe61b196493995576f427b9f2cc5b904d812afd74977": struct{}{}, - "3d1e557b540ac045b3b327994a351f08a443f9216f9b2b8d3a0f42b58671ac83": struct{}{}, - "20bb5d1fde86ae0a081c35df195cee0c3257e4ad0967d050c3d2c5f925a8a923": struct{}{}, - "98b038ed3103ca30654d099a04c15ed8fc9c03dd629c11747d97d0a0772f1e28": struct{}{}, - "f48902e098fd004b6a4d2bae550415f315274a48da78168747971ea325facfc2": struct{}{}, - "aa415c4e8890cf0fec7826aec962ffbcc04534faefd2b3266c54f690d40d6e82": struct{}{}, - "9d99830e3820bd7dcb81e3599f3cd736a402d49a30fd2b01942ca856c1ee375c": struct{}{}, - "93ca779659f5e42e05b4f35fd8083ac84991afda82045d5749667d2c5cbb4a17": struct{}{}, - "81bb7db1c3e4c805aff0096621f2c5c910f522d8ffd4550e14d03ccbbeb99966": struct{}{}, - "184858a00fd7971f810848266ebcecee5e8b69972c5ffaed622f5ee078671aed": struct{}{}, - "2a7018de792313e71eca3c36ea4cefd2537ef2b61fbbde9e2f3e0d51b4f2f600": struct{}{}, - "49a25f9feefaffecad0fcd30c50dc9331cff8b55ece53def6285c09e17e6f5d7": struct{}{}, - "fc4d6c9e874c1f120aa1e3d2dac53dd7804d2d66abbd82ba41599c1a71925048": struct{}{}, - "ef4530534205bef7ed405438e57fda61ce160ec61893016f72d3aa62b42433fd": struct{}{}, - "de6f0783531f8bc8ef5eef681870529a7402ab726e492f9014c0175d719d26ea": struct{}{}, - "232ed6f9fabf14e3bb55392b18cfe3d0febc94d20cc6327c38a1d075d6ea118c": struct{}{}, - "28182f0a278161989f90f090dabd6cab331663d8509ddbae617bb1e77ff81595": struct{}{}, - "5b2463300d5f351e84534519996277c5789f30b6ca76d4a988c032d1ad2767e0": struct{}{}, - "3abafce5bc548613ff47ae39983fff685a2db66ba1a4fa6f93a1d57286e034ef": struct{}{}, - "642ceb7c9846ef21db6274f92e3860534053d6b1d50004fac7712e28c9609950": struct{}{}, - "59aa9f6655437abe7a29fd1093641131b61d9f5c827141476a42169a64ecc04f": struct{}{}, - "9440d10b31d71d6b84c1f9ee0296133859f38b84372ec8e2a93a5631f8810b1a": struct{}{}, - "f579545f36d024bd2c03533eeab8b0bbad7e2d4f4ab4ee0b63164fcb7ed4b68e": struct{}{}, - "d6b1448e4a65adc3d61506a4e42efb8670922943146b5bde057fef3cc8277b71": struct{}{}, - "081aca6cc6335af598ca1884a874ba5fd45b254463e1efb3cdd9b9109d27e9a6": struct{}{}, - "4502846f2b0708ba711ffbd11548a7d79223c02b9fec3b7863628a72d46ac8b3": struct{}{}, - "be74ff939e5b0726657bb372b4a3b63e844f93cdd444f6d72bb7019c2de7c896": struct{}{}, - "2863568293de0e2409cfb00794bb5cc4eb191a979dbe5262570b2843084f1f99": struct{}{}, - "7bc671151cbfaee7f32cd56e86a87b0be30fde8dc72c7f236d3ab2ce42cddbd5": struct{}{}, - "c9affe7f758723c3eb9fe8b143520c7d3c73fa7cacdbf1a76b34e18624cc6ecf": struct{}{}, - "2f89253abd26c11d6829f60ac57c0aa0016202a464a8689cace5a0825f28c66c": struct{}{}, - "b177d1f587c9a552f167ec9345ff157edb81b9580ea47f362b1bbcb91b8abe71": struct{}{}, - "eae1f76262637deb793eadaa8703c57e1343a0496282c3e4790c67b56b145caa": struct{}{}, - "f4f3c8a1bcc0e5da9836d04f23754baba54f107ef4d2d67eeb5326c5ac74c28d": struct{}{}, - "f4cbd73e7e638b953055c0683f5ecd89668a7164ce766ac7751d69e6eb89d026": struct{}{}, - "f9ce3beb80492facbec3586308e6590ef4b543c570a119d1e9b575f3b12ce197": struct{}{}, - "ab3691d8e45c1f50684b2762fc640afbe61266bf49c34e0a5319044da23af364": struct{}{}, - "b3f487fbc1970da93ec061155c4f98a8831b35abd531ddcc216ea9cf29ed3e1e": struct{}{}, - "69451f744e174b9c18bba231d7d2d6e0785df2fc8cdf7929028e14f891367871": struct{}{}, - "d3e1185155d15bf9deb0e2233cac0dd2fe175cfef6a93137c95994a8f0911ecd": struct{}{}, - "4027f515418b77482d033970fe4e6cc2c9b6dbd8d19201c31afd06d080a07f41": struct{}{}, - "19e8637cbb1f4c04d22526f99261415f0a71f53d18484d39a1e1b289d66e57cd": struct{}{}, - "36d4427ab0ae7d0f50bbf999dab58cbd73d6542a24273a48797fee0e9e01cec0": struct{}{}, - "d9a5223b761c375d1263e6e57ebec42d3e0fe3f6f283488d2eb204fb6ff17ee5": struct{}{}, - "2f1987bf98c09d2f5d2a23a6ae29fa53b9aec8f07ed1330bd439122f5a1a2c2c": struct{}{}, - "f79b8977720743f5e7c8b5b2e3813a8a4cc9aacd97eb20fcf41a7169e66ec1b9": struct{}{}, - "79bfb0e2ba76b9d447606ddbcc494834f05a4c11deb052e74b49ea307a3c5bcd": struct{}{}, - "c9c65334165e89f625fbbfaa262dedb2e160e2d463f1b226347ee51694dc95e1": struct{}{}, - "531afd9663aa142e574de1310f298f9d6363885ea0418f12c558d3b81044fa7c": struct{}{}, - "0d0f9020aa322e57266a6de7278d2f96712dfaf698393711cdc1e3966135a92c": struct{}{}, - "283bb9deef02e6843abfb538efa1eca70801bd8a701c3f98191e123496339247": struct{}{}, - "23bb715d696f960ef02331bff91b74649a08f8f673fd7bb4ccab4e4dcfea9c14": struct{}{}, - "d844b1fd564d74edae444da579035919551b795f70e8410463e91e628072eb48": struct{}{}, - "cf274e8d7b7c4e3cc51bdb946c1cf72b8afa310c9b83c67f764dd96b2d2e4a36": struct{}{}, - "6ed9333f898b23472c64853bbbef7a4234cfa1a4b0f1974271fd13a2b0b8ce97": struct{}{}, - "0bd8b2e19931fe065ab5bc50a5e0880f89f4cc812bb6b31a68c4e723cf7633f7": struct{}{}, - "e2d60c496f0b35feefb73a4cfe2f0fdbac3f90d539dc06cc645398f8f5ed28bf": struct{}{}, - "56046694c3d53309620f1b958ed86146980d37dac0ec0bdfc959f65a3a394fa4": struct{}{}, - "2b66e9f343f40c09cecf823a0c6df7e3c244a74feb220ac8dd50b2d66c757329": struct{}{}, - "e2d0dafd01cb91827f19c11704cb381e0638dd279c7f467b7c9dc040a16cebc3": struct{}{}, - "583fcd22c2c2fe3637d80c196d487165ff951a339399253437c9a9ce74313ed2": struct{}{}, - "37edf9404d8649f6a673e2db40fac905f21e662b28f5849651f21aedfb756774": struct{}{}, - "7800630f3def1ccd382d487204c94ff29e058820a95230fa89f542abad463ed9": struct{}{}, - "ed17888ea92f040b471886b1709637426197327d4c211585af61136918209f42": struct{}{}, - "0d335db1f76237bb2f4850c49dd12841c6b63ed7ba57cb04023a3a75576308bf": struct{}{}, - "21297e6e966afbd06e8f08c4525ae2edcbd3696cc6bc436037e278d4b1e67b4d": struct{}{}, - "154d763b6a255621fa9b2d088724e5e0d7a5858f1bd4a1e3ced278cc70a2d73c": struct{}{}, - "977d2387026eeab3fc4a05e6044fc7159c01bee55d45db487758f14459a332b4": struct{}{}, - "f39d5149a46edeaf43052e04f2020b817556c537c4aeb064cc5d9b6a6b9ac1cc": struct{}{}, - "ad12f62c45fc1344872aa1fecfb6cf3d79fbe425ff0ecd1d7e7b4bc42d64fb30": struct{}{}, - "02f541f5c200117380733cf2af7ead42e0e5b40826591a788a02a8a7940687e1": struct{}{}, - "21f692d2c8a225db2824ec4cf162c742d74ce0d036840817220bf322eee56e70": struct{}{}, - "166cfb0e8bff35f92abb8a9a90df45610ac02af4990d740adae6cb00bc16e2f9": struct{}{}, - "4dfa73cebdea102b2c9b519145bc1e1f65a746ceeb56b6a74d926584a7c85c7e": struct{}{}, - "b842f9ff280b320ea76d8973002dd8922836e2d14ad1f02c0fcae8ed37ebf4ff": struct{}{}, - "4714429b1275bd817b3ff053f416a584e50c9c48e8b1e208d8ea31c9777a9a30": struct{}{}, - "dcc43fc8a425bedac5f8976cf9fdfd3141bf6875315f247b87aa829e7922d048": struct{}{}, - "5f0bf3ff6b72ac09f881221fc8bd88ba9f24a393c2e544716dbf792978a7f313": struct{}{}, - "8984228f3b9ff2df3b627963e07f457e69153a8bcba49e048ee42f4676cc6f71": struct{}{}, - "a48b63d4e2da811d9912670e44e9b6923e4b566dda9c82639f762d95abc22501": struct{}{}, - "8f18ffb371eef78a08bd14bfc11f86b8e2abf7a08903991c94c67f9909e73adc": struct{}{}, - "37e1d3b955938bd146d7f3f6dca681f692db9821639ad54d0c6599d9cf9e6a34": struct{}{}, - "bef32d2c315a289576f2a6828d27edb16bb316a4d85c271f2d794045f3ea668d": struct{}{}, - "d9b5f81655f48086308d3d1e0ca1fc5c8554deba62872d7771c0b51ff84973ae": struct{}{}, - "355066bac2584a25cb89191ccdb6829a7b4356ae345892b27b2fbe5e2a72dc71": struct{}{}, - "14dff4be6469a0c423ee09eeaa3c454553c2f8322162c649e1775fd97445d651": struct{}{}, - "87298cc2f31fba73181ea2a9e6ef10dce21ed95e98bdac9c4e1504ea16f486e4": struct{}{}, - "d926c30a7d873c4b897c90a016f8603e9f2758ff82830e0805e86ac72a49537e": struct{}{}, - "5a33d540c8bb21712ca51158ca82096781037653e7624a4210485bea0e962370": struct{}{}, - "ab27b729d9cc4cb1c00960700446924159e9298d0f952aee3d55408b8e0b1b71": struct{}{}, - "6f281157ab246f0e1f35db82165d3448ee1e39077ab0d720fca7dddf014249d6": struct{}{}, - "12202602cb02931020b31530020ddef3673940c3ab69adedb473e8c766a7dcb7": struct{}{}, - "3f1ed2d9dbe58770f9cef1ad9ee3e5ce3486394e0f0eed1f253633f4cd4aa319": struct{}{}, - "57a4911c9baba00aa0f5e297cccd982e32a136e7f8fbea988a2d26f8e99d02a5": struct{}{}, - "08febc6017e156e837b1e607a055bb4f1463bb570dd38d94ef397f5b63460812": struct{}{}, - "b30b83d324e678e4bf0e3cf3f1700676b77f0b02932af2f6c45e0e4f6aceb8d6": struct{}{}, - "e4beb6aad03a5bec892eef25fbe75a0953a68ffafa5b1ddd87864344ac4a6924": struct{}{}, - "8452d9f61c15f9e79ed9626a5c24ad171198d1a4ca180816b5a852aa18a71371": struct{}{}, - "9f362e1447b99925bb4d0db04e714b8687a17a7f8c842402855446b2cf19a0b2": struct{}{}, - "1865e4f9be9639fc76d7d4da8ee25eaa7421f6837accb67516937e69469ea7e4": struct{}{}, - "38be84dc056b8a8525e1c803f504e2ef71f1c958f4f1ff6b7447684069e98bd4": struct{}{}, - "549d8551ce0b75f91c3d62e4400143c3ae65cb931fef865d8a6ffc34d88f8af9": struct{}{}, - "9a1ef8e9abb02eb32f701f8a8b5e29ec83904b8767285bf788dbeef8be236e3c": struct{}{}, - "f97557ce9b1cf089c8ad22a60ea45c2be754ab1ce73c209d5d9c187003e91c87": struct{}{}, - "baac7171dbceef5285e07ef265911f8d5465da25c0892e6ce37a231981e97510": struct{}{}, - "48c9f42f70c2013da2759b5ce55c7706fd61d6d98192ab8f4d8caf71ebfd62b0": struct{}{}, - "90b4976e79bb58a3700efea1dc942b56b4f93c05459369bd6f83f42e9370f7af": struct{}{}, - "96d47181cad1617bad7edaa4cf410e9901b2c7c6a524cea5b2652877d0f40f8e": struct{}{}, - "a2c0ee127bba12eefa2a1991f8a0b2c1167b7905d9afe661d1841db9b18242ac": struct{}{}, - "137c7b2a050dff7a49ecb0626ec0f948aa24bd9fb365f773d92272aefd528c81": struct{}{}, - "1aa42de957aa4c529a279992df281b7f3b3ca62125481fbfa44bedbda6993e2f": struct{}{}, - "645dfcb4d1d2d7f1fa00f60e45541f33dfafc3a7ce1dc4bb1901bf409e4edd60": struct{}{}, - "48f42260a8448caa7a9ca1af2630a086da42c48fb009755da7e8bac819490836": struct{}{}, - "54f6d9fbe8ee576f82d6eb7e4d1d55691a1f0b7bd956246d3de56ee84bd1d333": struct{}{}, - "93f0c40e39fe5846e22e8e5d1cdd106783fe5c37f8d3a2c66f806b0c10369b3e": struct{}{}, - "2a232d1de821f090569fa24fc41180ddf1434d84141b5cd7d1b1dfdb9e95dde1": struct{}{}, - "7d106deafe004a5d01d4b4d0c7098d337de8f766937b52bc81125c5002b80b05": struct{}{}, - "cda597bb594d52131fa5f4f18bce6cabc841420e518003e4e436cab04da46891": struct{}{}, - "2df54fc582b85d2ba77b26703457d3df5fad3e626c455a9e4966960cd212cc9c": struct{}{}, - "6f8860eb5103a16b917146f9cbf21aa00fe7d2291b1aad7f4fe83a6db71688cf": struct{}{}, - "01db71ab8048f74a4b92c26ba77285ade0687ac192758e8185ad52701f649ef2": struct{}{}, - "72059e4ae63a5e1fd696c8817d6ec88d89f9c9bb05ba8cc90a99a35654516986": struct{}{}, - "3419a744b6efbe446c11abc1f73de07ec4e5e7bc39df17042f25031aefe0478a": struct{}{}, - "25962ef8d4c0160a2cf6204f3832d8c3c971b31001fc819951512ff326f62f41": struct{}{}, - "edc1b708e1eeecb09405cbf16b4b795e32f89256c4054ef5f070d1dacac05521": struct{}{}, - "7aad70754f8b1079b8a2c43808fcdbb3cebaa03d79c27f631ddb5ad916f0c486": struct{}{}, - "a1cd94c4a9c0fa368dd2b682fbb9878f0314719deb3685493eade795d8388db4": struct{}{}, - "8895460ab4ed5ed1d372bf55de84f2b9584629570f6637b590b4dfe2390238c2": struct{}{}, - "c006664e73b7f760403f9cb15692d85b45be9dab510c112913f7aec2acfc1fef": struct{}{}, - "d694873cc6e81f5ffa2745613af5d91ed03b160da64ca0296581133300013796": struct{}{}, - "bb87adf8aa783887f0874a55a7fce06d5fc60f5886a767c88f75fe5a7611074a": struct{}{}, - "cf01f8bc67f205dbf16fe5bd94f140fc5c013075aa8e5f6d80faf0525117c1f2": struct{}{}, - "8c244b370747c1930a4e0967254778ddbb69f6a409e62beebe5f92191a09a3a1": struct{}{}, - "018e745845d933317ff666d0ba0ba51a5204a2682c6d368789a9e6489f53faa3": struct{}{}, - "e6db08f96a362ef9708257be7ee34520e5ce643dcf50d9dc88f04a6f319e9924": struct{}{}, - "467da8087f2f4be164eb711646813ab6d75630d78ab5405dcb6a93621c69e192": struct{}{}, - "90aa2608758d2bc747d65079720aba7eef76ccb1b48166a3605908939aa835cd": struct{}{}, - "d7a9ead0b15cd7c8f3ba0d573530ad05b70052080825dda30744c9c7f44e07dc": struct{}{}, - "378ce56a9d146d5174c89d1c0353d7f8e2244e89bb83935a5b1bdcf5e74b4e7f": struct{}{}, - "09bcdd1e2a6d7313347f4c797fadf8f175c0ad8a5ea064303d1f98d1ccba3b77": struct{}{}, - "e4cb3b7ff5a4b3885230474eeeef7d8ac009169af23faa0f7b373933cb664f46": struct{}{}, - "3bb5d67bace1bd7917ea35e6787cc6cca32e74be40c021ee5df3d424f2000b33": struct{}{}, - "0e1737cc61216df82ad94125c977e70a8fd916c3f67a655a815ee199b79eed20": struct{}{}, - "67814213b688c144f85ac79d62e499f1fcb29de51db696dc9f6292beca48462d": struct{}{}, - "1e7c2c0a2491e1858b2f55e1ab0423976f719f8a40a756d66d0f2d145cf4ace4": struct{}{}, - "7b29e8486ffc41263bcaa64918572b08500414aa4f5479ed840cfd3fb9f160d0": struct{}{}, - "2393128bf26df9c372dbb1709473d92f48d491ccc6baf6540604b5805b02b2ed": struct{}{}, - "eb754f7512ef99ebf6d031b1355b496c0ecec0f08bcd1f6d7a790a5a8264be0c": struct{}{}, - "0f8eb4b72b6e0c9e88b388eb967b49e067ef1004bf07bffc22c3acb13b43580a": struct{}{}, - "9cf86cca578640f2f0a0e50fae479eec01cca0a00330d4f6a7c33fd016b40f11": struct{}{}, - "3674cf73a8c701c80b869ac75239e9a95926e7194b907efe9a8a2b9c44425af4": struct{}{}, - "efad137e1f9224a51687fb9c12ee5a226a5f0cbb140f4d41fd54f37692f2fe9c": struct{}{}, - "d2d2328e3359f3de3515871090d1316cbcdc5383204c204f9390788c3ef8618f": struct{}{}, - "8950e548b5dbabcb0e9a5a7b234e4ab308fbd9156808be013c07e0cbd1d2dee6": struct{}{}, - "bfe5d697162d90298c67c1933908741be8395838aa230408e04710909c576806": struct{}{}, - "886fccb79473329ae6adeeb8196a35d922c039c9e8812745f5259313e1dcc9a6": struct{}{}, - "a583c44a5d50db0bb401704304a443f03dbdb4d5127742a73265c61834eaa824": struct{}{}, - "91f49598ac3a9ff6bdebfee273fa1321bf7cc63f5c85f19ebb659dec95d94cdb": struct{}{}, - "7060b8b7cb86428267288640442ec4f7d5c04679df0d08d2765fda7b0fd3294d": struct{}{}, - "205c3e16c66ff0b845700155d0394219f64e9eb6ed9db9f7758382bb4106eb11": struct{}{}, - "6bb7e067447139b18f6094d2d15bcc264affde89a8b9f5227fe5b38abd8b19d7": struct{}{}, - "266550babbb7bd394ab653e15b80471735127d82e68a76fdc2dd7dadcfe667d8": struct{}{}, - "0067bf87149f1dc491a519c357210b348d336b7b938678edd250a99193e671cb": struct{}{}, - "0a51ea4d84400704b8b7bca0cdb9e5ab7bb7b75b87bebb8a1b9e1b898a5895bb": struct{}{}, - "c974e17b8e7321ce8c12983de3d0ed4a289821f579bbe0925b0181a4bc8e8d80": struct{}{}, - "4bd92c091fa0979398716f79f5e2432f7af31c2b8f2f55ebc9a82705f1662328": struct{}{}, - "31fa3f5c3307b51c67f30cf316e3f56b133f91b049dc6fa86df45ad187e38b72": struct{}{}, - "b1e529831a5e84db16679213514d2ca91747a445fbe1a72fd9f766422af648bf": struct{}{}, - "1dd78bdee75691a9ef0c10a08a7d838612e0480d4ab2fdf0246145671044de82": struct{}{}, - "5189f6f62d30f7b732a4fe19ffbf9aa52b3c61fbb47e8c2938d19891b82543b0": struct{}{}, - "63afc41ad7005a4cd5088d8699fcc3923dbd1afea6751306eca8474fef4233c0": struct{}{}, - "4d7c4c9706dde11af5feb5b8f5088c1e7fe052c5a7568686a79b2a6182774a26": struct{}{}, - "fcd7d5a49b6280d4ff4ca3b288e1f90b2b458519b75cb63047cc112d19bc65a3": struct{}{}, - "f6b6407632158ebdc802ed898b47e55bf89f61895b50bd8241769c9ee9d9a533": struct{}{}, - "430a68d98309dd8306dfb7e44c643f7177ecc262066f711014badea2b3e88dc8": struct{}{}, - "d3f5633200f870d340817a9e123481d960dfab3f82dcd6be13567786bce17093": struct{}{}, - "200434c04a73f1f8f1910ebe2c1e3b198f436203a0af90539759f590ecf4335f": struct{}{}, - "c9baad0c17efd756ca8ffdbef182d6c676b658f1bd55db727dd703c3810164a6": struct{}{}, - "0dae4d85ef22523da65f2cb6a8ccbfa6132730f2c038dbd5b4286c1640399790": struct{}{}, - "06cef54a87dad89ca1b585a118a2a7cd2d5c95e8928f7b6dbc0be4a6c948aa38": struct{}{}, - "51652912b8f311dc4d47b6c53ff3ea13762d45887b82b672fe9aba78e8b0ab5a": struct{}{}, - "680dce633db1189eb1158aad579df2ce0ad72a0571ccfeeaa5f79092ea6cb729": struct{}{}, - "e250e6d6683e61f5378157ca13af6c53003aa9cddb06cac6bae0b2985d70fe7d": struct{}{}, - "80d481ada6c0ab9f39e72ac98d0901e9452f8437e7d9a1d66ecf5e5c773adaeb": struct{}{}, - "d22f4e0f2779f1044467cfda393d04e6fce1111e1520a7fa41a8a9d8baa3c017": struct{}{}, - "84381eb69a68ef1bc723b3403cfd775c8f8026df17b9dfb414307fa9c13b511e": struct{}{}, - "93196ca4a16ce11625d2998c9568fc31c9e6810456b5fbe2c864208da7d4c114": struct{}{}, - "9b7b434a4490652505891ff0ad700d71d731f080cf0ab142063daa77940dd791": struct{}{}, - "9626c7444717aab7a3bbdd509bcafa35a7491e9478d421b38e539a621f695edd": struct{}{}, - "3d9087d26cd8227f7511c68459c70470c75c8d4cc35db0d92ed10789c65e0d6f": struct{}{}, - "fcb758b78074e8274889be3114a8bf507914641f63dd9efc7fa65a414f7c0480": struct{}{}, - "2933e7a6706eeaed86d406aa504375a93defafc713b1cf2e58c9b2cb1f29634e": struct{}{}, - "cc8321d6375c494d043fdd0260f21bc0ec51dacc9f6abb7f909cdcd3041b78bf": struct{}{}, - "c8a2035c91b566a5dffc34d586408a298abf95ca3d706ead1c677b4979a44c57": struct{}{}, - "11d6fe94df911d49e71b6c4aae7b9d4a397662a3c1767cf578b785ed35372ec7": struct{}{}, - "8e9177ca98ef097a826950fce977e0477e592b9f1473d1a4a9fa2a83c441d8f2": struct{}{}, - "24c8750f242a709d5e0a3dd6d217a03f961ec1cf258fb1ecd5383a4e3cdeef89": struct{}{}, - "164c4e46a137c7485f6a0fa1127d9e615dd703f72a5e02f63332085ab0ae6f02": struct{}{}, - "e850a3f8b6e7a6d0d71c4f08d1c909b2be3db3a45d0ac6685a079f02d3f68c4e": struct{}{}, - "34b3f600ab02b95f72bd1acf67501d54a6cc5c112534a5337b9ce205a3e338aa": struct{}{}, - "d0a32ecf89f3c0ce7965aa5cc7d9790b34789bd87da255d4cbef49e0bc58c1b5": struct{}{}, - "5221409b64943f25d1fb90d85377d1f5ba1ea173218cc7d87d12814934f49b6c": struct{}{}, - "67b8ea9ae3c31ecb78013c925ff237dd1a7e72845a7f8c99280be25258c0d105": struct{}{}, - "ba9f6e2cc358097447784531bc824d41b992d18579cc2f9314b788cbb948347f": struct{}{}, - "99d821d82e13a3c8330a31dfb166d64a74b4bd84c95c97a2981e4fd85607593f": struct{}{}, - "7de2ab51a57592b9bcdc403fe333c9fddd66a517c42483a1e5a835ba696e64fa": struct{}{}, - "8866a6cf193c843044c3591678350063d2cbc660e40559995cb9343bb15f36ae": struct{}{}, - "c34e8bafe33b7f1cb1335ff6a07a0b7efca46412ba91bb379a322099a285c442": struct{}{}, - "28faaa675ccec57fe305711dbd97794c0e6c6be0be6f5610b2b6153a67e7b3b9": struct{}{}, - "aec70b6af7d213ba80c2297dfb51df4700d03d33e5846f3aa44f13142980e77f": struct{}{}, - "8031bbbd1844733fe20c945d1a115f675d19e81f58e58f756c059f3eb2667036": struct{}{}, - "02d38d5db021b57a551248d063d183429a4703e57312e7416f12002244a2a25f": struct{}{}, - "5df0db16009b3f830f73c5ffd9bb09bc3948afd37779e9f6c9c96e13029f25d2": struct{}{}, - "800ab3b9f4d7dfd1d24e7f8cff157de2318480a8531e202ce7fd7a543c7e911a": struct{}{}, - "a5d2ae286d0d9e45c0621a6fc7c18119940dd737baa8d794e6af357d2136a7a1": struct{}{}, - "08c461ad70fce6c71a9a8e0f824a2f9f54a69db58679f095ea541b2b2419af91": struct{}{}, - "aebab271c16787051fe3e816237c07dabd04f02a6957f36a74276a990ce2fcb8": struct{}{}, - "e71ddf7131e34b20d8d55779c05cc3cf01d6c7daa0994bdee6733dfbb79b08ed": struct{}{}, - "eb5af8ab99b55cda453f70e6a92c7b327bd8f76f49ff6a81c18ade4c26690057": struct{}{}, - "66a7a5807c3130eb2d0b55bb260a6a001b9d62095c94b753cbb215f3e4f099e1": struct{}{}, - "7afd036ceb16908bf877aec9b5e49fb9b07525910cf7a8d0f1dfd7c9ec8f970b": struct{}{}, - "8af54fca0c73ca2e6fdae03b0b459e6cf621bf7a42b66f76f1f0e42e71131e58": struct{}{}, - "b3bf52aaf3fd3fa4dca244c896aeb865b3306aedf256e26b2b2879e24924014c": struct{}{}, - "d21bb537725d603ee2dbadf9c3d1e291a6b7aed215d36a34e58a1a3b0daa5084": struct{}{}, - "d773a201e5fe42abfb44b69c7801d5d94c3c6127f3cec00e002c23b492f29db5": struct{}{}, - "08af8ee237562ccfe968f7cf89d4fc6246c11fe8f1b29ccbfd9ed9971aa0a3b3": struct{}{}, - "70698446f4d8b8db7a3fc5aeb56a8e8c3fa787b3664adb8730351a074006b2f8": struct{}{}, - "b10c94bd8ae38a2a37365b6d81c4ae1e121cccbcc233c8bdb4238eee7e85e657": struct{}{}, - "3caf2f1daf10d7e93dd5a0192f5b7e5121a89a64b2a30c09ae976fd5846e536d": struct{}{}, - "ccc0e8da6b80e08e80d75a89afe11e8f2d5cd0f29a10f782104ca5f2648e8903": struct{}{}, - "46bdc56266eeb01a426c3c2bedbaaedd8885d7b5081afd15401930e61957cd96": struct{}{}, - "9d1b478efb180d2cfe0d47c821d6123fd6786efedcc1bca4a5f8252ed36ca75a": struct{}{}, - "e7a6fe11e5a61eb5d9ced30aa3023566dd25fa7095250e9a88ed24f2de27b1ad": struct{}{}, - "97a7d8f67415b84621d6dc8d61c77435701eaa93a5589c84e2085f0de21b08d6": struct{}{}, - "921a95e8b614f668981d9eee4d24a3ffdb6821162246bb8036f73f4fd7d20564": struct{}{}, - "bf3107e638d7126bbf4809a5860243f71bf9fb8a6617060d0337376136ce88b2": struct{}{}, - "73b4a96391f0dcf28ef73c4b9840b321e87f17fe5d60af305e89b09846ad2b2a": struct{}{}, - "9166432cbb7c24632d9c091e8a9bbc747858146a0e772eebb226d21e37126bab": struct{}{}, - "cabfd247765caac9e7ab8f915981752e6ad57b05c555f72c2cd87586b9be2f9e": struct{}{}, - "f5f445528f4a0134354c891fbd95fd0734d25cb2c9206c4684545ae49b14d29b": struct{}{}, - "55e6b83841f3d0e3d684fc9e915b4196db88629ee6d9fdf253184be8373bdbda": struct{}{}, - "8379dd41f813a59b970bee0cae909fecc38cfb170515ff04a27c19620147ab5a": struct{}{}, - "5003f9b63ba86a823794c26e0b6ba90be1340968091fa40b5e0ff1c729ed4637": struct{}{}, - "e4b8d11548bc9d5ab6f8755394cf574c225048cf8233a8ef92e59720c2f452f7": struct{}{}, - "0467255695084cc12ffe0c55105907f74a9c189314b9fe87b878eb6fee787b99": struct{}{}, - "130c699460d1eb3040e7af7bbf9ac74667c5c9fef45a0edd61c79fdedc05ab32": struct{}{}, - "2cac39c223798f8ee1f1c49173d00d71fec741ce520806e25b358d3199041283": struct{}{}, - "47121e67ee82a486d4b31c2115d70435db0941fe8c213abafa33bdc3a8f9439b": struct{}{}, - "df63fcdf296407efc1a102fe0b233721a96935d5a901b5b7e9029c0bc028fd8e": struct{}{}, - "83156d4b945f0f09711ee1a6f33868517331318e80879a25c79b1d8d63cabb35": struct{}{}, - "51b504953d4d26efffce6ccecbd7c2d9f9efb7e4b0f804dbad986ee4f8224059": struct{}{}, - "a330395cc0a53ad1207736546afff4735940937564bbf75ce1edad40780d9139": struct{}{}, - "b300e708b661ea96c29d49466e8f0ec5351c0d8cd2f8b64d25b18642f5123860": struct{}{}, - "2b2a0ff8f9def3f17d58512ab10d7b1b40e5ae06dabbddfd4d9278a428a3700c": struct{}{}, - "a2fa4cfad054c30de64ae9fd9807b2bb562eb12619b66d0bcf9629cd8c969fe0": struct{}{}, - "0c7936466c23376e7ed7425cab010fbfd37d79c86279e9810d044a380514ec41": struct{}{}, - "b04ff1e245d1f1248c35ad57876142373d602839077afaae45fc9a0bb09b9899": struct{}{}, - "0bbb14c1ce241a895f856c8e85ff27490920f494dd4fb3a6b5a19445f5434fa4": struct{}{}, - "63c5d687c64d48947a75bf25daa7a128d0c9a8fe1205c7ae3728224015ee7c0e": struct{}{}, - "cb0d9c4bcc02337e899345933073f5c24b90ee5f2adcefd68d58b011d520525e": struct{}{}, - "15197cf7214b58e67cae565e573ffd9aa44bcb81f8ee5d7185dfe8da0a16ef43": struct{}{}, - "1ae1522db9452eb11cc84ed16cc8e8098064e8f602317c7ab7946a7d6b53c732": struct{}{}, - "2322d03565a46ece1e31e5ef0d5f79f5a0ee0dc96ff666e52948e52e0298850e": struct{}{}, - "7764735c5d4d88ae3ef1c0d6c0a5769e4187c341895db19a82ba7d4e17b8c914": struct{}{}, - "f43c7b93298a00534c9b15dc1101f01c55ade91a9a6618aada658e2daa8c28a7": struct{}{}, - "5070e08a1859c3ee2b4501d92c7e401914fc906651d9b1377fe994f017b2948e": struct{}{}, - "dbb3b50e59c7326b2ff7d6619430257b9188cf3c0b8d778f7275ffd3bf4998a2": struct{}{}, - "ded16756dabf91cf2300f1a615be0407943422fe8edc95bf8c792e8712e9ae6e": struct{}{}, - "e2e733ddd9dd5bd8a16b3bfb6029ec3dbb5db1cad48487e19de81fe49bd672c5": struct{}{}, - "78f5a50aaa5a8d874723439d4665eb6f705384340395e23d1d29a623ecbe32ed": struct{}{}, - "573018e4d8bf6e21a2d40e1b602f5a44309c4b918c02185bcd42f1e08f0041c8": struct{}{}, - "7ee3819bf62f7e4563a2a9476df6e18a6cd17cceb30b92f00a24a6c8175e3740": struct{}{}, - "6ec8c8b3213672d206355ff4be658827cca2c7d7af78067911ea4568c931711b": struct{}{}, - "8bdbbbd8f743857507fd7234296400fd07c8be3fa62b7b17a94ee464dfc85f9d": struct{}{}, - "b2d9f671884cd5ba60a4575299064c7fea5b17a10c2b0f4a32ce6d12ab8b7e2c": struct{}{}, - "ac800699bdf654a62fad9ab3dc16ba2fe68fb40cf54b27d11e96ed395f754bf7": struct{}{}, - "9e48bad6e31c128258257bd997eacca666f0dae2049027b545ea48a0cf43ed71": struct{}{}, - "3e84fad05bbb080fbeff83c729ae69fba93abb460273c395e2a76c9c2c130f69": struct{}{}, - "d6e0ff2b7a534a9750d13545971476557164f72cb92ad74be7f145491ec4acf4": struct{}{}, - "9fe5e0a43712f05785002103cfac2add80771a19d76f2c562633f65375ea5581": struct{}{}, - "ddc9e669194254cef019a29d3619a2c16592e5d52e1a81e98b01bd52319149a3": struct{}{}, - "8d3647c068ec71984fe1a44afd4a52174afed4a6fe23bd3d8cfa422578032c25": struct{}{}, - "1e8abffb973c275e041eddbe542c020d1e4f4098509018ed4e27f7879d97cbe0": struct{}{}, - "1ff3a3c1830a1531cde661cc90ff6f2b7ec3c68c017fe051dc4500dc5fec99c4": struct{}{}, - "e5cd1243e0e97f6d7b30cd7a6e17be3e63aa4135ced9b80b0a551d99913612ab": struct{}{}, - "ae481ee5d9238dc18d93dfec8b9813eff88d7d603e8c3c28c552048cf1314d69": struct{}{}, - "65087d38db99a7ed894f59de7a57092cabae5c03cc2a82b9530c1bb9cd5df377": struct{}{}, - "8c4ebab40a0da973729e401a5bd3e3da23d4e585e9b7144756ab933ba06b6f07": struct{}{}, - "d63d71f407ad92113aa5b858936038d68e0fde0a7ed8b8a03a4fea2c2eab80d5": struct{}{}, - "6899cafd8fe080dd152799167960e7645d317720617dbd1f8078f7099d390918": struct{}{}, - "3fddef973bf7de791e13a30ac3a9e33f51623022d22a37118cad51bcff2d32ef": struct{}{}, - "afd5b25a47161576fb2c0e9c1fad341a76a5ea35e81ee1d89de024b47c6518b0": struct{}{}, - "8b876e57eb73367d6b042548f7ec0c47f189b97e32af815b4cff1a0471d40c8b": struct{}{}, - "7669b5bbc0177b54e6a4a1518f88776400395f79127bdac94ffb88fb5d2e25d2": struct{}{}, - "a6085329f6f0c132404ffaf953070f3b6e0de40bb783c86a977fccd5161b7d1b": struct{}{}, - "1a106b6bf1d17ef7ba488c6d3bc3bd9fee23cba9a953d0b7ac2a4bec465e90db": struct{}{}, - "b8746e3fc5eb4f3db7a73acbcb68c250d73d998ac6a12933365aff9bcb53028d": struct{}{}, - "01d4a0fcebc9e5a1a3a8006720bfade838ea6b0af49f5dc1ae0b3147e5ff797a": struct{}{}, - "69d850edf86d730de69bf0d74d7b1a539dfd8939df92a4fb0f948d6dc29baa85": struct{}{}, - "b03df5988382ffb51a295783dd8fd7b11ea658e0569828a9422ebb3e7d4cd610": struct{}{}, - "50def4b1d9accabb19790b9b8c28af74a1d7ac61be0358975729df63a03506cf": struct{}{}, - "05b44f4aa0e1b1de75c4ed641fb9c3c6b42b9186c59c78411bb1e2d34f26977e": struct{}{}, - "d2b850dfc8088aa8a74a378bb687ed6d813c2950f76bba26b1666b58fcec62ea": struct{}{}, - "5668e7edcd8a3ef87b00fb57c11eaaa9f8a9067cff7fd9ff95c5a6ef7dee0cfb": struct{}{}, - "ea8a9b83e071c30a46e954fb5f796bb5feb1657be0af52939bbb9199bbf56113": struct{}{}, - "d0c83ca2755928df4703b8927e3bc62905ed7056a186f3e01d6306e32507b2cc": struct{}{}, - "63c95cad473893078dc4985abbbd6613008345c78687c23f90a930d6e7d7857a": struct{}{}, - "0d45f5fd462b8c70bffb10021ac1bcff3f58f29b1faf7568595095427d42812c": struct{}{}, - "0a2e07846fb8c883bceaa0b0f8486ec093b93a6b149c3db5de241a14037575c9": struct{}{}, - "6840f7f8d3bb6dc15f68ff20e3506488cbdcc47ec93e026add82908c9f27a079": struct{}{}, - "16b8bf238bac841d7eeb81d742c1573e354862fcbcda6c5c7ae4a0321f46fe6d": struct{}{}, - "5b6a4e19978c346b8abbda7f587eab190f3bc4116ba5982d154883bd7e319070": struct{}{}, - "b740d031521c59652486548d84bfcc1bb66b8dd2b0d5f1c392f476bca11f9e0a": struct{}{}, - "f400a2bf1f5aaf6a34e92b2eed8b2abdfde20e6e863ab2f235337a21e62738b5": struct{}{}, - "8e321a4f4fcdcbe581abd4a400c61da2ca45a44519689ae9b9458673b24409e0": struct{}{}, - "0b6becdbb9f7a4bca780ab0de371895a16c66b02c3a106789b4f994e44dced25": struct{}{}, - "74c36c4a08ea059991b69cacd127de6fdd7e13025d69b170031ba34e2d8f5c91": struct{}{}, - "77cc4342ad52d6391dea397e8299185075cc7d4be1d4f3fc40bad35971744753": struct{}{}, - "9fee8f88b93ae8b399f50073fe60d9c137216b0cd1176defafd6580328fa5ec9": struct{}{}, - "fc31ac92775f34f0410ac88080e2ff95b779ddd842e46c34bad18a7e9de9dd76": struct{}{}, - "be4bd56772770471b639574d919a92de16c0e522ea5601e2892461691bdf9545": struct{}{}, - "34357b18abddea1874221b1f062f80fe3e11b6abd11fe80b171738893003b24c": struct{}{}, - "94d7567d89f137264d4d487a6b583c07831c3689f8021c742f8f2b9844bb0f9f": struct{}{}, - "c159965f2b9649e5dc6e70d007a2f0f6f03c9da3b1e8da6c7583e87f9f297f9c": struct{}{}, - "04d3708506f46795ae247f391cb22dd01fcede0307acb25658dc051a8896d6ef": struct{}{}, - "9651b33118f3c9513c7758414424947b789ed74e121b2c2134d34bacb9e9ca31": struct{}{}, - "6270ffd8f738cc2667a7ee235cb5a9ff3c1210185e4d94f853af55162101cb3e": struct{}{}, - "75c4013ee12efc4038716f8f78dcac777588a5e02f2cbbffb31ce181c5410c11": struct{}{}, - "06612c2832341c9e69c890d3dea465f8baede4895cad1e5ab22c7f513ab5758d": struct{}{}, - "e5c2ed9c375f3ba8609bef4d646d6eafed55c5693d310ae515cda391e85ef9ab": struct{}{}, - "a20a2b7bb0842d5cf8a0c06c626421fd51ec103925c1819a51271f2779afa730": struct{}{}, - "bcac371b54f59945a14aa49e2e408e5d6e4dbc59387f5d8cfc6b015d40d5bb02": struct{}{}, - "496aca80e4d8f29fb8e8cd816c3afb48d3f103970b3a2ee1600c08ca67326dee": struct{}{}, - "3be9b62181bd5269a20f454f6b5574d43f38e824c40c602ea9622a0ba96f76b7": struct{}{}, - "a0dd93a5673bfb13eeeaeed97bb2d26f87479928c5b4b32f5a63f95d2cdeba9f": struct{}{}, - "b5fcda1c97715a581d1eadb7565822d5f18b215b23e338e56e26039e7b2b21e4": struct{}{}, - "4fd1483821e134f7e04f16a88484f7b9d89b584eedb2af6121c8e316014d3d3b": struct{}{}, - "ac83a1c781d58e8487d755edde2735564d435f94f83855dd948dc34d3963c1eb": struct{}{}, - "6991d3c0d1315e062b3bf9e501ab5bba5e54fd6a6ac707b7b2e8792e2d7470aa": struct{}{}, - "bdc9fc73f65aece77761e33bcb3bc5571a2d06d8185d5f403f0442dffb0c3cf3": struct{}{}, - "59012b44adb8c2f5e94b7a197e260fa945cc23ddd4d8737195d9391396d0ec5c": struct{}{}, - "aec04bf8cf83291ceed3d47e8601e1989dc29ce76a6399264132de2731e69e55": struct{}{}, - "06c648eab10b533b8b1f9a32a3b239ec41d427118597dc52d4e7c790b784255e": struct{}{}, - "a2da0a8e3927bc5321d63953fccef1b3c08f1b9172c4be51a6409c71b1ae4f61": struct{}{}, - "6a7b516ec81884590594a8de9f46b8c575172beed6660e3373f65c39b3856c73": struct{}{}, - "a8d2cc25b929dbfd87d2da9009f8e0d524506a2ec9bd395ccba12e5b5cd6d035": struct{}{}, - "5bcb80d0681d6b5e42674b1b224318829266df06c58a0447d51ac6311fafae70": struct{}{}, - "ab6ee2a6415b2f4027457cfd2bb41bb2a82de4baa06bf7dfd982ce5f8f1165b3": struct{}{}, - "395d4c382804699fa1063dd293af2e8d1aa17f3a40be4d5bf830d7110e3cb85f": struct{}{}, - "7685b8c4b466e590f93135928d0c28ae0aa6a6cd58a1d9ec9f3419d2f76605c3": struct{}{}, - "47a9eeb8760e40bc0ad71667b4eba5210cf609b47a197c892aaaf2f5dec57094": struct{}{}, - "37a475c9818ca80faea2b17fce682bb2d70b0f21e826df367fbbce74ba4a0288": struct{}{}, - "cce66316b4c1c59df94a35afb80cecd82d1a8d91b554022557e115f5c275f515": struct{}{}, - "dc2fc19d8fce376c47641cf15f823a03ad10f2dc7da3f43230551f50706914f5": struct{}{}, - "a60ec1cf58f49cd2fab6d9e760ff1365f6a25dca50c09e9cfc553ce737a2af60": struct{}{}, - "d58e0182f5252d4222b40858bfc1bd45e0bc5bce8e4b161b52f0d8144997ccaf": struct{}{}, - "af9d7f5c94154c51766656842b4ca4ca1024bf010a5d61350766af33ec179a82": struct{}{}, - "554e79e652b29f3a01dcf98e494b9928a1dc12cc83b83e730574c9b37ca6359f": struct{}{}, - "1d3a9a464f3836a0bdb2a0a01266c832a6d236be4d284a30ac6c1e20c4b74454": struct{}{}, - "bf74e4a280affafbdf6692bc6a9f3d66b03094fbfb4a91589bc7fd6b32664fdb": struct{}{}, - "005195352c13e18429414c8b36a4047fdcfab8bb3a1aa1aa19d5ff26010c754d": struct{}{}, - "17840323ea59b5e1c1383d90f5e688fe7441b1de61ff8679d37f16591f261569": struct{}{}, - "d5f29c56ba0bd9695cfa44ce6e31d3d064ac66ca225d18b3d9ec2657b838ebf7": struct{}{}, - "74ba7e49e963635164033eb502fb4d47ad7b0ee662a7e0370a0ea0c66659ef88": struct{}{}, - "af4417c67db4242eba59efa05eee07571b2f1abe6658b5994f3b02d2ebe7a98c": struct{}{}, - "f805bcb3efc982ec60744b3cc23f11ed1a4a0c4b783f345c1dc64ebab616da91": struct{}{}, - "a17312e77236a137f5b028e1c3dacdf18f89a4a62459fdfa564639f0e1ceaea2": struct{}{}, - "1be625d6d7bb3980d83a235dbe0c72859e14bae5db6fb351c19d48f206870749": struct{}{}, - "d36a564e704857ab9e1c3efb225465e0d76eae8ec03fabff71752b6b972e7c4d": struct{}{}, - "98905a60ef1b495d4ee2340773d99d166b2e0991105e0fdaaa07ba3a100c843e": struct{}{}, - "7b0838c2af7e6b1f3fe5a49c32dd459d997a931cee349ca6869f3c17cc838394": struct{}{}, - "6dfb97632210ac38a071667cf8be7df83a16178e12f1248e45b2a3d24b3b2bd1": struct{}{}, - "ef32cc5c2b7c62093d3ec4844b36a02b08bb83eee2efa6b6b5e85ad605790192": struct{}{}, - "15320214c486f28c9772cb4ed8a5237a0eed6d9dc687f446ae99ff3ca3c70329": struct{}{}, - "5fdcf3cf9df24d061dd59d4e6e395a735912d4c2782f73d5e037a921bb0a9e34": struct{}{}, - "128ffa54c31e6c2c3640ad702cc616198801f578b7046560ce7c524530c48bf4": struct{}{}, - "414f824665dee430cdf036e7a8fc4bcff23ec7d309599b18cef6ceb25920bd78": struct{}{}, - "26dcbaddde6eeb068c28f506befdc84bc54ea380f8a82c3eb5690c1a487a4000": struct{}{}, - "14c413de9cb986e04286daeba6656751b9ed5389aa1694e75cd6d9eb5ae0a1f6": struct{}{}, - "1dde21d9dfbe48a35ea7f9e129b264ae705104669bbadcf8046feed750ba77c2": struct{}{}, - "a34c7305af5b1b2cea20ff1591696acf02402faf4f4f38400ab36c421b11fe81": struct{}{}, - "eb1b94cfd6971df4a73991580e1664cfbd8d830c5bd784e92ead3d7de9a9c874": struct{}{}, - "e1492128072fc9caaec9291663776e5447041308eecddd290e8a3989f2fcfc7a": struct{}{}, - "af16847054594066cb5dcf501ed219d6ec043688bd7a1d1beef984b09de0b134": struct{}{}, - "62124b5d1b520d11e8512c8f8050498341204cf8e9ab04587251a526f80b9801": struct{}{}, - "55c18504525ee4bf6afc8ab5563c45399c95c910d8c628a3c60fbc7adc12afc5": struct{}{}, - "31e06f7d89feb99a0e6c0affe198748c3bb5bef5e3cc92d95cb9e996197d3fc3": struct{}{}, - "889393fb69a5b305188405f66dd58ca1fefad6cef46cfbf85236146e633f2a66": struct{}{}, - "5905d5a39ceb9ac11887f5db934e9a6c2539f771e67e8f741895aa443525a994": struct{}{}, - "ac8190a5e78efd2357dbd69e0024ae2c0632e57d5db3d6ec1f484b3c6b5ef1f5": struct{}{}, - "4ded336ad7d3aac1c6b70a1b3d2c819196a5a81b967af448071f8d6f33fa0bb5": struct{}{}, - "8eb9fc953d5d7e10670e32663ce02aacdf3e7d26f0a40fcc11638a43e55eb250": struct{}{}, - "c7b9bdd40758eaa827b7abc1fc31408901ac99cef4f3891c1fb3598f531bb9e6": struct{}{}, - "0de3f80d7bf4a5322b756a295cefe6714b12523ffaba9adbe53668ed82913857": struct{}{}, - "841bffec9644ab01e0048b1b5b724250cb6e13f9cd1632b571e446ae6363b334": struct{}{}, - "c43fb05de4d8a9a0a442824f677d9753397cdd29cd123404e434bc457256b6d2": struct{}{}, - "3c6cd65dcba0c2651744cce06f85a668a554b8eed505da993f44418148c260ec": struct{}{}, - "bc813e5e2990643a3b572810b1fdbf0eea280ab977a48622a8c90ab3f7f2ee27": struct{}{}, - "a4c3ed04a95a3da14a9d235c83d868bed7c0f45cf7f3faa751ee8f50598d2211": struct{}{}, - "19a638eab3874d0ec38ae0a66bde9504610d9531dba39869a45e811142fd231c": struct{}{}, - "878c240fd717f39d8cec9f7a5cf936873ffcc0757beef703ad2c5f9ed1890344": struct{}{}, - "e4b3e9a637676538af5c7ed376aa957f7ca8ee17f3454a4ec0cbbc8264d2fbc1": struct{}{}, - "da1df7402302e2d0a7a6ec6dff769f0e030ba1f4aec142f7d8a2a1875ea61e0e": struct{}{}, - "62062954305db700a9804e930e2ec006e385a229237c5b6a08289aa5b3ded8b6": struct{}{}, - "11e9420772d2c61294766d5a4549880480786d1af1a8712adbeaa6a81159fa83": struct{}{}, - "542fca62348fc051f701a25cce242a0324c365fbd7e56a72264cecc80a2f4c08": struct{}{}, - "16483ee69d8740609947417601985451fce674f30777c24b2227b35ea3cbeb34": struct{}{}, - "ae5eebff12dd75af2fc959fa5ef8bf7c23fb4fde38a0176be14c991d8ff2c440": struct{}{}, - "94af67700f696247cb304807e7115b26fe1587f09b855c34a3801d99ecbc8b9b": struct{}{}, - "82bd81ea4b0302554165ae91628c0965b68b976a62ecf107c2e79688833f7599": struct{}{}, - "ca1d3d4d877cb32cda87cbd7dbce34823a5bc29554055b2d7ab8f3e67556144b": struct{}{}, - "3f9ae4d29f2cc6639cba8a65cb128ce99d16a673300dbebe8accef5dde08c0a4": struct{}{}, - "4473e0e443e2fb0fc10ac07a1b2607dc991f6bfbf04eb557073e1e3083ebea43": struct{}{}, - "31b9bf35d5485fb00fa935d7883c750b094324bfa55a729932c6bc3469df9ab9": struct{}{}, - "fb5374706bb4bb0bd6f291c93f66c364aaff4d8729df34870e7c7c023c71e1eb": struct{}{}, - "d782fbdb1c4cc093df74c833b8ac7baeba7c01369c7309c21ae0bb6947d73ca2": struct{}{}, - "8ae144199b5dfffd1167dafa3026a7454af4c7afe376fbe05a93d3fd9fc15d82": struct{}{}, - "2e11f2c2a8b086bc3e48c5d9d7232fe2af0836dbeb4494c69c9cb4e6bb53718c": struct{}{}, - "8fe1e9c2870c5ff63a105c3dcfa609e191b1c9432525630b11cfc410f5a6fdf9": struct{}{}, - "cac77afa6447276c543eb76ebefbc3ad54d5115f42df0ec4db4e3d29de3715da": struct{}{}, - "75c5a05709569963de506ea20cfe93583fc80470e0e432dac2d8a779ef8fd3f8": struct{}{}, - "98c0f87ec38b0c86817cfa9dc9d894a3468b611048f45060729509505d4543b5": struct{}{}, - "d2461b40f544076f938a2e96c3c929235e1fb2a5ff651d42016d118e2a3358f8": struct{}{}, - "3c4ef966f728d7c0542941c369fbcdd089f8d7f7837b6d42dc2cbd6adaf68158": struct{}{}, - "5846d60018319b9e79700f11a3e826117b1bc6d3fd974c7348b38e9d671816c0": struct{}{}, - "2df52081cbb98be50b296601957414d44f4427d2e1dc6852a60efc485fef81da": struct{}{}, - "3bdc1e86f2b0c2845470ac022f4cc229558f58f80bbc59ef9316b5f34aa4b0d7": struct{}{}, - "60386cf403ca121ea3cba22c04b1258b971da61e9de5f24790005a4324c25be4": struct{}{}, - "213835e536fe62a3510c24cdb0796226e66c6cb8abafb0ff5e1cfdf1110f3753": struct{}{}, - "4f763f54e682a3a341167d70d7caeb99c5c1e8ed9832d33be09763bb4c1c1eef": struct{}{}, - "7e3cc8c07ff40118b4687a6760abe03239fd19eaf4e6ddf2775180ed8b034812": struct{}{}, - "e8e1d31153ed13398722ad3764117711baada729a2ae51ef105da814536b48ed": struct{}{}, - "f513a0aa4f8f39744c6fddf2b5eb18cc1eac55ca866a1b243d835362a023f243": struct{}{}, - "644c5c756b4bae7c0f74f1f6345f8da6eb7ed0bd870da9b6088976dbabf98c3d": struct{}{}, - "c01fc6146d091ca5d15c37cd36dd12d743f6083b94d76158b062f07e31529fa4": struct{}{}, - "f6a83a317945485635d98fd336604cafd82938fc46a28a008f12b57a5491dca9": struct{}{}, - "6e1ae81777ed77e5f185aa4314717628f3ff3b4e6f5109b6a180075fffc7ccd5": struct{}{}, - "62684d63d9411c28f3d439292764f2b719d2a3c1a2ceea6ac327de07bd01a8e9": struct{}{}, - "ceb1868b724aeb1681e2bacf52cc299df0eb2cfce5669ce43c7c540a5f6a9497": struct{}{}, - "4fc50a24efef2d4c2200a039e6686944beaedde83e1a31fb1f80531e6df8af5a": struct{}{}, - "7d99bd5a7f9be42a353bc22177cc906c0dfbef2a24f04b5d3570f6c970be86bf": struct{}{}, - "47a89f61e6f203163204d2684599071a0d2d79528ae81e078cebf59808863aa0": struct{}{}, - "d93fc3c4ade1aa371c83c1393fe522a13a7522e3233a089d900d3eff2c5108e1": struct{}{}, - "b9496b78de9917a6b216f92a7d03419d93269dc26280a72173c5a7f93cf0da1b": struct{}{}, - "07334386287751ba02a4588c1a0875dbd074a61bd9e6ab7c48d244eacd0c99e0": struct{}{}, - "da90114c55191a389a1e36c3ed931c007c953b60ae50a6a87c6ae815270ccdf4": struct{}{}, - "7a85eb336ddfa01b5d923d6e187335c7d7386433eb34cd74e16ae84a3a8ef73f": struct{}{}, - "7a01ac37408614bcf58069bb6b6a543f6c473cdded552c491de4eb36aacce235": struct{}{}, - "d493f8869139bac710b992c1126f61b8bef4bbf2b00b4b3c047f243cbdd0555e": struct{}{}, - "2b50aa6ff81012c3421a08d43467aa8001a95ec30460d9fc0cf3dfe650fdbc75": struct{}{}, - "db2e7f1bd5ab9968ae76199b7cc74795ca7404d5a08d78567715ce532f9d2669": struct{}{}, - "66c240d3ce0d0a66ebc97cb3a3b3ac895c7b279812b2c6a06dcf61be599c6e74": struct{}{}, - "ad7a5714b614323c7efeb890f08c18a33474441c88efaf694a411e07a206e18d": struct{}{}, - "67913ee9be1346dce37cd88df1bb92bcce9125bc4490ad05d5555f9182ec1f6f": struct{}{}, - "b81d6dc25832a85433d9e08c4b12eba7a9664accdea29af03f6c8a6ee48cf82e": struct{}{}, - "fc9c6940a9162937a02380419b64fd7d38c9de6a6502baa49d16571408ec01a2": struct{}{}, - "28ee793c0f72d1d49c61351ca6c4ee4cc18937554fc3711530a04cdfb9cf6aa2": struct{}{}, - "81df63580cc9ff1550c90d918ec234f1834013b800b5321201e88d96e785277c": struct{}{}, - "15b7b9d79e95e443f15f9fb27964b4f1b1dbbbb3eddeadb3611a6ca163660d17": struct{}{}, - "ed0a55664d2cd391905f0a7ab1bd929256abe1e450c47a434a5ca3f129cef053": struct{}{}, - "aca5fb59db69e5dffeda36ff0bd65205f16720b7fa1893c4557e9216dfc276d5": struct{}{}, - "bb37067afeb4ee16d668eef073ca6eea4f3b4a1fc6c68e3c0b1fd01a5fb7f5ad": struct{}{}, - "504db5aeeabd937a18a36b62d6e948d236b889b33c84e9e0561d7fda1c145bce": struct{}{}, - "cc2fc13a49e7f0a2a4d70d95ff9a8a32a32850f361e5091a7d0827afa76391e5": struct{}{}, - "13cc358c604102b8449c47603475881c635e9cb112c6e4e6cf1c0e867c29a1ee": struct{}{}, - "40903c59d19feef1d67c455499304c194ebdec82df78790c3ceaac92bd1d84be": struct{}{}, - "7a119b0b733c0b3305e37cebca90d99de29913c88ef1e34fa2bf880f83c6bcb9": struct{}{}, - "8662213633238aa6ce7b53c7aad50edbb7eaf9ddbe7538f39ce079566f884339": struct{}{}, - "f60fa4f29c038974dad36276ca743681b7c86745d663c3234ef17829713b8fbe": struct{}{}, - "a073d5f14e2a17d0e1af614a665f17be3053c3cfed6eb89a6a41cfa01836ab5c": struct{}{}, - "586697d3fad8a161397bdd601a98e495f75c94af7f3e399ef2f6b2e83134c667": struct{}{}, - "d6c3d18a40585ca1abbd3eca843e4ea6f405fabf35a7818b8673f140bc812438": struct{}{}, - "dbd085b383b2796c78cd4e49181397ee88505965addf653bc1362cf6faf155ec": struct{}{}, - "97c580f5459cdf4af9107a45af02a9dc45c0cd90d4df3bf4478d87be57226295": struct{}{}, - "c38e8334242ffdf00ec3402b43f3010e5ad2d1550e41805a6c158de1b621370d": struct{}{}, - "8544318395464f2154fee858b97fcdd5da6d6b850fbdfaf908703a8ee7ab3cc6": struct{}{}, - "af53f8538d061374803e05584bbf8ae888af1aa88952c1da2579fee63a39ff30": struct{}{}, - "39b233fd83b244c35642311f8eebad8855949a70736447cd547a25a08f03c0d7": struct{}{}, - "82c74ecd82b7df0249a9755cc8a105fbd3e382067afa16995ac2f58d32960c9d": struct{}{}, - "a32df4cf1279c8c1532ef759fbfd1ec7878dd7de07247c8c916f39ea32ec749c": struct{}{}, - "0554a5df02ee12f1ae36a51caaef34a31deb9458a48b629da554a2b322466f4a": struct{}{}, - "7d824ad37e366f330ef3d3bafb8dc8b18a5b07622e2830eac5966339d98a94b0": struct{}{}, - "cdad86ca9450d1c143675a8436131cabaf55905c114fa4524bf6a9ec5662cad7": struct{}{}, - "75992a5ac67ff644d3063976c2effd10bdd93fcc109798e3d5c1acf2e530d01a": struct{}{}, - "829f00a11ddfdebb51b67a913981eddb7937c3f8f01f4140415a24ff1cc29609": struct{}{}, - "8123f97456ea9aef201544cf0a0a403402152e9c826df700a381b1c7ff9b2213": struct{}{}, - "936679e1a5f0e430a0cf8d3b86ae1618f9dddac5ca715826f7ef00e00f163797": struct{}{}, - "7f293243bd679844b135bf683088048ea2f03a21195ceb8e35bed97c7d221428": struct{}{}, - "8d6a0f2c415037eb8827b32135a9447fe91a66c1303fe86e6ebd3a637b9351d9": struct{}{}, - "8f3c9713f8dde0960b3375edbc3aebda35ebc7bc721085a115d14852520578df": struct{}{}, - "2a2487917723f91a42b734ad2ec6a302e93b025aa6e2f8f40814ffa35c2b668c": struct{}{}, - "aa300fb004a5d73d7fa7da3f2452867d4698693e7deb0486f11f72755d8e8ea9": struct{}{}, - "d01d05388d5a24f1bd362a588e221a1c44b15bd63b660ff8f57a3bb6f3836d13": struct{}{}, - "00e7684c0e1559e4078b7197ee6e9c81c6a4497c7f4cc5c5783ef7e7cb7b0403": struct{}{}, - "c585f1decb986f7ff19b8d03deba346ab8a0494cc1e4d69ad9b8acb0dfbeab6f": struct{}{}, - "15b45021ccf8ed60039e5a2f7d345c30d2dc6356aebbc43982c8aa05f64d79d7": struct{}{}, - "6d2b89d12876e29b3fe3ae8ba8bf7a2352b26528aec2f79fba99ffb7d4d592fc": struct{}{}, - "eb953fd6e066c352c95c5d21ddc8610406836e6caa464803d2e695fd4e4eaabb": struct{}{}, - "4a51ffc4e8b75b6a51c3d2b1a241526d9c471e5a25affdc7c4a0f0d02dd2a6ac": struct{}{}, - "a47a8b85e5e57c9c831e3c8027a5869677db87a0b538face828806a1fd50ccda": struct{}{}, - "e118aae104b51265f96035a9ca50e52ef5588211b18f16845cc7e38e72c978f8": struct{}{}, - "f86ee4d5ea0a27e6d7633dbc542bf94c97b810e45216899fb67ee0fac90ddfd0": struct{}{}, - "eea070086c3a22b679609b0ed3ed67783d8cf5c1934de68fb9bb8934f9c3b5fc": struct{}{}, - "a66030818bd442a63e25e6851bfcfe639b9d2e81355cf49acc665c4d70e61afb": struct{}{}, - "d8f01c5929d95169b68e80d7997a6339195e77304b2059819daf93a894708c75": struct{}{}, - "1afda89737a745f15d42807d54f67c803727d75ce443b0f3a659531b38ae660f": struct{}{}, - "bbfcd4160a1e8674dac62292ae48be4785262ad7078f9ec11b74a254ce70fa06": struct{}{}, - "016ad5c3f5c9694a3e754eae34d1d27af7e53de1d0b004244039209ec9cb56df": struct{}{}, - "b84707239ea1484db0110700aa7f4298d7212d69618f17167409790efa290b08": struct{}{}, - "2fe70f8fab887c96dd0abb620572088ea2a5435ef9fe82d635b553af88e55896": struct{}{}, - "bab1120a609d654e08f0d67b99f5890f8511f130db75ed72dd7d7b6167baffab": struct{}{}, - "c51ba39d42518294120c8b705c80ce58836895c547191474132e1ca283583a56": struct{}{}, - "92811b045c373851391092a029753a3a6ca79a1ad624fa34672002f717aa83b4": struct{}{}, - "3a2e8954befdbd6e7eac2f10d4301a2923cd65a5f38bf80914019b55a03f78c4": struct{}{}, - "a018ae7f594651fa2fd9ebb2be6a655c28436dfe8984d71aa1e819d92ff83ae8": struct{}{}, - "ed11c910d7187a411ead45e0ebba2717a640992e5dc0526aeddb76b4829572fb": struct{}{}, - "31501a4d8bdd58ea81883c105c51c116357d7f98cb6d68467400e287b2210eaa": struct{}{}, - "7e5d4325a44714fc86a9b6989e41e966a7297bfe7e913c15fb9ab19588e33d61": struct{}{}, - "31f6aa95ce7a87d0f882d66f8fdca3835ec4f9b9d0cd5a9de7f5a60e46074253": struct{}{}, - "aa9c84558dc50974d03dde6bbe65cc77e548134f582c57882086c417d9501fbd": struct{}{}, - "b470c1dbde00b5326907f23bf7e67c08fbc899c442091e2247822405293e0983": struct{}{}, - "855a2b6a271b67d78bc98fa9ebf7789d9ad6a26f3dce41154c1e54508c979b0c": struct{}{}, - "0dc948d2352203b08328484ed691ee8e288dae0d56b75a76d1409da765e0af4c": struct{}{}, - "45f711f11a5a24416a48f0f92a7e4556a8ad03abee7940cbb48a61f429bcf1ee": struct{}{}, - "d810e2523920525379158a2817711b8cd181fb3d9f9f3a8c2b9f3effbb9caf4d": struct{}{}, - "817935053b2a99bc015fa1f86513c3692ad8c1cad8a38bc94eccad8e87895e93": struct{}{}, - "2f0a9f65dc73e13a4e0090bfb520a5785156e7ec34cc6ad891e2dded28677ef9": struct{}{}, - "40546fd00daba342d94ff470f31dabcb9966e78352bd160167585cd23b4ec509": struct{}{}, - "4237d4264639477726365b04695fd2e4087548f8045c9581b78b58ab0ab140d6": struct{}{}, - "2bf1095a27128e73bdad0cdee09b61be799a5d7cc0e73a91687afc0c377953d0": struct{}{}, - "193f42805ae7bf28aadb7d2cb29fdae02aea40f179d15c17ebfb947ccee6ca01": struct{}{}, - "ee2ab19505f4326c2fd246e46b686b9e498dc536577d25db0f94451eb9127ce5": struct{}{}, - "06d032cac55362db45f0ba3244ad4b5179541468f8c3feddc176772c149ef776": struct{}{}, - "cbfa35ac38b0b6961d0096b94195cf6b4a7e4aa328e8567a03d48b47b27cc21e": struct{}{}, - "03fefb8d091a480a00e01405c5c81f29c75a4395c0c8329cf6eba9bb3d588979": struct{}{}, - "f562267f2b6d890a7c0cb8c0e57ddbc0365082e28a7e3dd02068b3b7d91712d2": struct{}{}, - "62e9a0a5fd248415e7fe895cf5b896ae65efaafd64518a0c2c01bb2c8dc13d71": struct{}{}, - "72325a29ea0a19fb9923b23ca2f1155cbef28e4dcd2f5f0ac8e050508c17840f": struct{}{}, - "6b0dd3e234e3e1a7d87dcefe255e74cc568fdafbd3333a44d79a1c60e2fd165e": struct{}{}, - "48800d0081f2655da75ff2fb5f57495eeedb62d3e0d8ff9f9ed900fbb13e8464": struct{}{}, - "d201c69090662089a5021829f539ec74ae13766765bf4324e46623c657b479e5": struct{}{}, - "bf112a8e8499a55670d6bb13f03809ac4f7708d3f2b35e1dbf8c65b9d9689793": struct{}{}, - "65e936f6102cd016a8038699f61094515ef2b9072682531772185dc18424b581": struct{}{}, - "3376fa54dd0cd33765a10f929a0ac2cca88c49942045be43d77871386ca2be7a": struct{}{}, - "f12e74fc76c944f765099c9446d88f47955933a7776f0e4040b945264875c10c": struct{}{}, - "3859afaa2db59e4e8da79350c0c6d1a32f86dc78ade2ec55fda0b496ec7e221f": struct{}{}, - "a73c5ba166f5bf597c422354bfafcc04e6d339de2fc96da71400ba7945119e61": struct{}{}, - "0acd3d5be39ac1139ebb8d12c982f07190a8300bf7150a5f3534aa4b900c2d1a": struct{}{}, - "f42ff50167e9c6cc7e45a597368484d622fcd95aae12d3704b647023648bec9e": struct{}{}, - "30cce014727386344ef954b48eaef95010f6a1b65fbb4758890fb272c9470b68": struct{}{}, - "18aa9d9b0e65626a31cf9937bb1f12ffecfeac0f00bf35a7b506ec8a4a98ddba": struct{}{}, - "7e4cde4698a304896b964710627b237045a9c907942213a5de7af492caa39930": struct{}{}, - "9543098f1704584b8edd35e00c15228cea23a7f726ab1c22054ed051675b5f09": struct{}{}, - "0648298b48be031996277ae472115a46e7964d2ac3882e61b84351f3c3f8a547": struct{}{}, - "358d1c0943eeb36b145ffd396b01c3323e300e22db73ad1f98faffc5d6f3cd31": struct{}{}, - "3c1843d8b9c81ae5a00861b1792c4e61c26c6181840ef727cfc550491368cef1": struct{}{}, - "4894d50eed4fc7c94384e200023c171eb75f58cbed9a2507d180f420de6dbad1": struct{}{}, - "37b49cae1e43dc58a02e68923d5d13b090b30bb288c5106e48ae36f54f964479": struct{}{}, - "6a30f630de10757c2efcde448c91d09746a44b6be1688992634996f76955a21f": struct{}{}, - "afa06d194fbcc82f37c70bcae5a393d5529705cc5baa2c8c8b8cd9f58f92a018": struct{}{}, - "6d78dbf1b717024c17b9d100c2dbb3e01a486547d17ab714641fb317aafa0036": struct{}{}, - "3a46f87902b31b5295c60e8d1ba6c6fdd6de4a1fcf97fc0282c1d0fda8498076": struct{}{}, - "575e500ddb529cc2e5b14dd6e7feb389a8b6e0d7c2b162b6bf31831c64d23592": struct{}{}, - "70de0f95a234f5685f545c895552bd517ec3907bba387b1c21bd495fce692c45": struct{}{}, - "48ad81490af41eee63d59c9b6cbd7f3975adc6be26ececb9a7f3904e6aa1e31b": struct{}{}, - "a5e2aa01d2db2c350f26c5a67dd400fbbf2427919a9aa62426943b3e6ab97933": struct{}{}, - "33ff332e064b9d30dbfe290608575d62f71e069a78e0f0976808d80744f97f2d": struct{}{}, - "969f83a5b3ab642209585e98213e7e6c60a0a7b3cd5ebfc37fb15d6f137bee27": struct{}{}, - "611af5bd77e314ca5df610a252c298626ed56ce19580150ef813b2ab28816102": struct{}{}, - "d6c5201664349757dfd1fec7624a71d50bb4299f993ff4d9e284e8cdad8e563f": struct{}{}, - "5c417c6c8127d2a31c8d9573fe8903a17a4b0b395132a94a2ff9da0759a8f3a5": struct{}{}, - "446acc36bce9a33c2e19f0205c0a94b8a35955814b560532b0ecefbf90a6d055": struct{}{}, - "e27d7aefd6efb8dc7aee2aef3d2a3e87832e654316e8894d44435723f0ba7033": struct{}{}, - "8009a2ded63115ae0f0905d1db9ea45327ed42b3dfe90993e908ef7d77b4706b": struct{}{}, - "7613c2917d708cd431491e56b34088928013b5a0678c63efb25ffefdb1ebdbd0": struct{}{}, - "8d01416611a03b7c979d6fdee3b16006da68e29b5bd5cbc8785bc0e10205b7e8": struct{}{}, - "9f2bc4f35e1980c7031b6ead641799daddc762a583bab52eb13657991034a424": struct{}{}, - "94a41c6278eb3eb7de2c6f554d3ce12740762e8f63019bf7ecb6ce77d56dec09": struct{}{}, - "924592b9b103f14f833faafb67f480691f01988aa457c0061769f58cd47311bc": struct{}{}, - "7be4264421b825721e046f06e6d58d735e3fb4a17b1efb21ab07131ca34496d6": struct{}{}, - "811dae26155c1f96fa4be679ba416b5b956c4eb73dc831d69f61692507e30595": struct{}{}, - "403a6c556b97d5635c84fb3e5d6e0ba12bc1e22712ed198b0714b11c5142c394": struct{}{}, - "e5cb6d6492935107cbcae4737548573b682bc7d36ef8d373965495f7d70fae47": struct{}{}, - "0c62f876ef1dea830de9f32c2f4b46dd6d74d50d15896e09ef5a2fcd4ac7e1d7": struct{}{}, - "3da4f41c1387f2aa4f4a3701e47efaeb2a8859d9011039d2f66594fd831ef14a": struct{}{}, - "621dd544bc999b6c68072fa40ad677ac1d6d7f72fed5e9c43a1cc2e3d99bf176": struct{}{}, - "d3f676dc91057d63cee04722473eb39ac45842c7b20425e15c110be766120cf8": struct{}{}, - "3ebab9f5eb7ed43854534417c61d5cdc9734b9944b8c2718818a4c78af4a6e25": struct{}{}, - "cd96b5def4ed2b502ab6cc6ce3c04ddd56e194c70949f750b0ab29f3e5ce3dda": struct{}{}, - "b0606c08f5a97ce86eb1110ba37ef1ea2f5ea2b819b05b03efc1b7888b418903": struct{}{}, - "3c89c64cd577ad4297f8f29a1fb0ec2cae0095a0ccf88243ee3b7b9c5c355101": struct{}{}, - "c05951e8ce2dfc7d11804ff619184eb9123197685f178d9325fc9b3bd08124b8": struct{}{}, - "8cde0d2561817e9b8d659213b79bb3ceb17d7be5f2d8553e0f626c6aaf70545c": struct{}{}, - "05b9115df05a2a467841772eccc969822d884c9e71841050fe9e0893cce7d11b": struct{}{}, - "5260fde80bef9d8d17d4ddfd20a9cc1825f3ea87f03d8b611b8d77a6fdebcd50": struct{}{}, - "35bbd1b03f4149554fc1c18ef932c31cc3521d301208f6f7268133502803abf7": struct{}{}, - "96874feedc3c209d13d1c2a9f49ce85cff4ce89c8e057c066bbb729be525eeb0": struct{}{}, - "c81532d2356ad39e88cad7c107cb6dbc5820b51d31a3af9eb7c7020830505006": struct{}{}, - "ba7852257d59e85ffb7fd494439b77919dad72d73006a02bed5fc26e9480e7d6": struct{}{}, - "f7981a82c9e039639fdba8db477c1b95e1423272f5399f0f07415a9163423486": struct{}{}, - "04b2b4ea2f66b8e32f47597cf3384b7e81d9b84b28c0fbbaf5587f24b68dbf5e": struct{}{}, - "99a069a89f576c4d85360a55b20b03feaeacecd867423f1d51c7a4e896e8e602": struct{}{}, - "b7367edaa7bb675764606b5a9343e09ee90242ce419eabfca14c7c7325e550f6": struct{}{}, - "9567d4378a886feea6e0e94f7fd3021b79c629bfdc65c5adb3f3ddd2dba55fb3": struct{}{}, - "9dc184d076c1e6e550514b4364e21dc30609f6f1eca29358b02ba563c55fb992": struct{}{}, - "43163cef22e5d2d736b0a4d1d2f0ce10be1311a8d1094a982c69fb05eca2d68b": struct{}{}, - "367728b04ad7c2ff9151c2ea9eecd94d2aa37486eaaa62f5167e0e2cb120439f": struct{}{}, - "4e605ba19bb9781a2af0f2b04256b7af1b2d9923a3d9f91837f33afee13b24e8": struct{}{}, - "dbc2a415b4683b2e1a223d11a3ebf3e1f42e33d4a00c9224036b480e13911200": struct{}{}, - "0bc851d8e8861b20da271d23994b9bc2604785313d3369acd3a2f8121b97be61": struct{}{}, - "c3dc1dc42f8a829953b40cc4642a245b5c9d9c00c262c22f61371723475c4b6d": struct{}{}, - "9588c4b2e8c1865b86df6fc410ec658e7e5930086451d4c894ffaf4fdab23617": struct{}{}, - "0c3e015009e5155c2c7c815f314916adf370dce9f827818decb4531b453e1237": struct{}{}, - "2b36364b92fdfed9c63879bf2b415b1beb8ed2b9d08e8efb265d1aba48deca96": struct{}{}, - "3b47492744946d5a188be73c702fb2e3cd1b635433f841381ae8d2e0ed67b45f": struct{}{}, - "0af7d7158becc6d02dc41536107090e77195cf90c556cadb37866528cc94e8a9": struct{}{}, - "16a3e9e922b41fde13be3ae377b18e08d44fb136594e7f9819ff2ac75bab5516": struct{}{}, - "71af0fdd2e7f62c1c351432a69fdf8f6579e9dce0c12c2b3af90f964540bdb83": struct{}{}, - "42c10858be90723d8df6572991a031e6904076a9789b64b5324087b9e62a17cd": struct{}{}, - "11e5e16df8d1b25fad6121c5fe3fa34a5dbb7eea25cc9abf1c348fee88f7d34c": struct{}{}, - "148de804a39393d57c7e056806c96aad2a83532ca3d431c34741b0ca728148ec": struct{}{}, - "d261aa3454b76eb8e8b2fd179bd1239532c00c1478983ac51dd351696e42e14a": struct{}{}, - "4a577ac3c017756a7cf2c8689ea5876f168c690497d05ef8e22da2dd13a22c11": struct{}{}, - "136db4c2a7edc5cdaf733d7c4ea04b4a6e1b7e9fed92bc7adb74f8df93e2bd3c": struct{}{}, - "cb8db7f3aeeb5b2730b5ef71d18c03914b5241c6ce08309e1c0418549ff622fe": struct{}{}, - "0a46317e5182088c77ab65b5356baefbdcfa1cfa135684e61cd49d27c068ccbd": struct{}{}, - "b8007d6156d5532d09d94c6df06e96100bab09704b5ecae538f39de12f72d9c4": struct{}{}, - "9209526aaa61b0709dbb838e14686a26c4a03b53e8eedf34c7e5f6f606110d8c": struct{}{}, - "a6bf7916d0cd013a745d29557ba70a660d978808494b433b51ba9bb10ce797be": struct{}{}, - "bdfe2d4930782f7cf3940f984052d9605000de48031d4e1b5508307d548fe032": struct{}{}, - "dca13388cd38b5da22c8ac48d374a045af2c9a10d78bceba787d92a521cc1ccf": struct{}{}, - "9d2c31a0c0af365285cbd705adc7bab607694d55fa67bd465757bb905832ab0b": struct{}{}, - "651b8ff3460bbfcc12bc9adef9671d754ecf92e07d0eac6a0be45ac0a5d441cf": struct{}{}, - "2c7b71d16185a74e590efcb74e5659a20d0d5d8d95037706dbdeab53b092cf2a": struct{}{}, - "3445ca3ead20cec97a285cc0c3b00a931248da7173f27efbfa4de27a9ae1c544": struct{}{}, - "42074e261c99db6a870a7978735bd15dc5effdc69d174d0c1e7fa8f75443b1a1": struct{}{}, - "3461b71b4c9f0b6b1ccae7af3fe3f0db028c11bb01b390d19d6376e806f7bb94": struct{}{}, - "936f75b65669d11251a7d240d32cb334e2983f3c9aed61110f8ec152301b62bf": struct{}{}, - "c5944ac2995295b1964cca2c23e7fedb95b418246f93a7c4cf26aeb482da78e7": struct{}{}, - "924815daa16613a194bc0affdc940fbf1ca1d1449726f767c8394ddb0496e129": struct{}{}, - "d1fedacd123d970fe92b965786e25503fe5a1f3f5c6938889d16847c106d7666": struct{}{}, - "690e51152d5cdf94d5d0e38251a0cf3413e747a30241ab73ae672eec89d0cacd": struct{}{}, - "38be71d04872eb3b3925d2170ee2c052a10643d9fe463b8fec86cc71d96c0f92": struct{}{}, - "1c5c03fdf151565f6d2e34d341be3ec200d0f278ad011077993c6ae3e6423422": struct{}{}, - "df4ef4d384501728aec057e7a71f57cfb4754de9f623146762ffe152a32c86e2": struct{}{}, - "3b066804f6d1d077173cfe4d06002e6a61e6f21c2b2e648417962115f1afcd8e": struct{}{}, - "c44bb2fd516909dab78ae0bfedcd5672b131b996c4b7a4328ee55fba170da776": struct{}{}, - "9cae07a2e9ceea5b88602d99f1bb35f228663649628b7a3281ac9c8203e6043a": struct{}{}, - "7f39c02109ee0083beee61e5fed6f78bd338305725f930b71811da73f1f743fe": struct{}{}, - "8f16aaa0cb1b7008eb790800b873c2e65f6d97b17ea897b6ccc8ea04df0e4195": struct{}{}, - "b8b63f477f28fd2fc7d3935cd08f95473cbf965e259222689fa5ef120d3cfd9e": struct{}{}, - "039d233adad1f50b10dc4ad4adf2b496fdbcb9dcbadee368c15270bdc910e88a": struct{}{}, - "387e108ca4420bb294d40f65616ff6191e82a9dfd57157cdf6c1d0ce7cba87a4": struct{}{}, - "5bc0b7c26186a97429d6414d971288956b7f1e33e73f37a3193b20a1d1a86842": struct{}{}, - "308b666bfd216587c69de1a2b90c5ed164a22ca51866e6624a3ac9f0c7bfe560": struct{}{}, - "cf3a3bbe331c3950d16a8e9917c5bb8340e7c0ef917da25d4a96f92d074bce05": struct{}{}, - "af42fb2c72f09227b06bf6bad9f7074e60f2c20982fa734cf5146577d4833f38": struct{}{}, - "cbdbbf13daa108f92e105133ac24fce67e563884b5c10a18f5853232a06efe88": struct{}{}, - "e5df92fa2f21976bd001e941c22bce0cf6ebaff40fea086a79cd83834537cf6f": struct{}{}, - "1d3b5c9556adfa66edd4966946b240c7b2d116d40ecac5962836bde23ff0ab93": struct{}{}, - "36ce0670f7795e5879f1e23fa4c627e858aa7bf11bb1536a49886c8919618399": struct{}{}, - "3845e52ac6dc3862b39ca58ebdfca8a801d8be3ddcded5bc058ed81b6df3af47": struct{}{}, - "6e317bcd6839e8877395411b47b2b89d2bae7ccb05f78cee32bcdf76b5294265": struct{}{}, - "41d12df274ffaef654ea947446dd0211e338d2651d95805632e5353798f189e4": struct{}{}, - "63304a4087574928bb887012970fe81f48d86c952fcfe8155ef3479f3761ebaa": struct{}{}, - "2ba4a840a14f832a538980eb9be5ea61962e78118da673c00624f5db67cb4973": struct{}{}, - "b0252dc49ad20cb14ab968bd27c020504d9d657499e8c959996095b5e75fb1e2": struct{}{}, - "877cc977e7b033e10d6e0b0d666da1f463bc51b1de48869250a0347ec1b2b8b3": struct{}{}, - "feddc4839a10fed5b886d0034984af57b08abc46693e2e45c485f7ac55cee48d": struct{}{}, - "3fb312f8c4f8ecac0a0586a9b443ed140697af9256f3208625a370c77d47c92d": struct{}{}, - "95feb74db50a889c1010b56ea18755a5fc1af0ce558d41518aa67e1a0edf8a21": struct{}{}, - "90300752de7525528b368d0995a226fe6613004cc41e31bd664a7437f10599a8": struct{}{}, - "ddd36ae5e6900effb7890da9d4e7f2aa0f2be210329ed8ace1c2a4afe1779d6c": struct{}{}, - "f0ebb4be95517e820235c4f20fdb5a5c511093083f9a0b63c7fdf89481f88d8f": struct{}{}, - "2b4ae02970e44beaab2f39261446f3c86768187c15a9b70a886094b9471bb653": struct{}{}, - "62d708fc049b421900638765c93aed8005aec108e56e56fb920a2111549b217d": struct{}{}, - "59a7bfcd042ed03e7d11336897ff6089a7bd52fba7fa5b77edcd7624afdcac84": struct{}{}, - "35f82c04d6d4e4e6deef00720a7b1b7a896c8c767c8aa6d931e2b0df23c7f48b": struct{}{}, - "06271baf49532c879aa3c58b48671884bcc858f09197412d682750496c33e1e1": struct{}{}, - "04e9c0db87e7eebbf8123dae64d169c85540d588a38b4f9f3fbc255c7c6c65a9": struct{}{}, - "35cac28cc9c8e45c843b8695ae7e187321155bb8f75eef3cf437b41f1da35118": struct{}{}, - "763ab14ae951751a626757693dd97b3a626d01d42d11739b6a5488e9656776d6": struct{}{}, - "90912a9126a0bc99a68379212d96981cd808e65e758a11988d61085653483520": struct{}{}, - "8ad69f2165edb74765d0c7f5bb94a6c001d9e5bf4484081de8c4a09be9426674": struct{}{}, - "afc687473207d9c19a337a3ad7d0cb2f767b03bbf975c8ebe9a5f11afbd30f8d": struct{}{}, - "a652518c696b409ef9810c3f35303dbce374294933ce8a1789160ffd7e94e3aa": struct{}{}, - "cb3167835a0a33485798af52e07e534e71b2a8ab91e64f65c3b87c55f7ab7e2f": struct{}{}, - "1ecf96698d130394a5a0a0667dc5f7404ecffd165a6035e1bc6c57945e36ae05": struct{}{}, - "0832c0adf05418144b7b5f01e14600782ba7a302a606b49e3bf26214b765c176": struct{}{}, - "3763e72d74b46e26eb9842869de3bfbf3cf1df9ce174bd7078210796fcdf81ab": struct{}{}, - "3ff74a580f8b39f039822455e92c2ef25658229622f16dc0f9918222c0be4900": struct{}{}, - "72c15cf71bdde66bebc80da4f59ae73ceaddac43e1e0afba13ee40390d974780": struct{}{}, - "d03466088881d2595b204faca58673aa59a0e3cf4647c8454a37ffaffa205258": struct{}{}, - "45f1426eb5631c5022c853f531e99b280d0f58bc022dfcfbb42119f0b5fb24c5": struct{}{}, - "c75d5bcecc24be8a0e29ae9b4b0a3ace4ef361027651e157fa12aa764919cd31": struct{}{}, - "47e28173fdacf9adb1a6c9cf03844961f7cf6abb5e2592462acda5036455dcc1": struct{}{}, - "e6c4df91a3b4064bcaea006b82d7a505cadd284e1864bbe5336b168cf612e4f9": struct{}{}, - "3a68f4c361c79bae39e8ad1ebc0a1de17c5e8cf12c83a47b72e5624bae42af01": struct{}{}, - "cf2351e92bc39fb3de9c14a0acfad546f13cf40edb176382d3896c41c8a876c1": struct{}{}, - "c8cdf720db5562a039be5d81c51a07c5120eaf0bf142b2144f1a1eb7a95678d3": struct{}{}, - "029755415adc0b1761294b545eefadb3dbe023648ba196d5a128c75743bf7a5b": struct{}{}, - "31d6ac82b29e0a40294f3210eb73eb5f87af39d92ae26b2271699a38b5e3db72": struct{}{}, - "9a733d49c5df58f3b82810033d2418f67dc49705fb022e1e9f4c5d20937d83d3": struct{}{}, - "c711071e36189f3bca6ab8e09980c6d1476d493eec08d4b3d215afe63ba68a03": struct{}{}, - "beefd71acc9832c5bf93270f1964c44b1f4a578bd4f1858e2f04d49eb2c843a7": struct{}{}, - "c5df50f999eb649abc0cd48c40907fe29c5ba71e4534a5cc63ae6e3d26234f1d": struct{}{}, - "16103dd07c46aa4cd1d7d633593e7fca039d180870460ccd5cf1c256b374db6a": struct{}{}, - "88328d3ea3b80fec7811162a3cc220efbece527139157de27966a7f68a7cc580": struct{}{}, - "61c27e8f0113e099eec188d1891f84dbf43c216ed1441796a4ca14efa0c05106": struct{}{}, - "e23011c058a38a17532c4877e3ade496b183879d9717343dd2247f99f2a50592": struct{}{}, - "1ce10b381ee4798b8c630429d0320641e950374cb76cba6c8abec071a42c5571": struct{}{}, - "ab530a13e45914982b79f9b7e3fba994cfd1f3fb22f71cea1afbf02b460c6d1d": struct{}{}, - "61058a8bd4669d8f15e24ef04976f5d2ab95cd22f302e0d95f3308981f050b66": struct{}{}, - "fe301eaaac49b4652b8dfd9fb0e913683ac5600f59370a6261824ab608b4fad7": struct{}{}, - "888df6d1572d02bc1a80b3f720916b5e349ca49f4104fc6378f0678b8816ed30": struct{}{}, - "b35b2338732458022fed25a0941c3410a4a5d1143b481b87effded5118ae40aa": struct{}{}, - "e7712396dd9fd72018710e518a012b03c2bf99b02dc4ffca6bae6b09de811b76": struct{}{}, - "941335ed259fe43a7230260595e790f5f2828ee2f9b9e4a285904f0f1228406f": struct{}{}, - "8fcfccf675b1f0bfd39dcbb13015052fe82bc51c003c165a410945c913dbb2be": struct{}{}, - "add8bed82dd53765b77488ed99387ebea5183fec64f1a0d0d2680f1746638932": struct{}{}, - "f5efb6e05e55f7eda6e88bec2eec69e6e2919cce997d7da9e6723e71b3021a87": struct{}{}, - "582c0168ba17eac49642bc85ae623204069e8d6ea06cf45af11e7de46ea31d18": struct{}{}, - "431cd8c8d5abe5cb5944b0889b32482d85772fbb98987b10fbb7f17110757350": struct{}{}, - "9553627933b214db60798fe40d2b4f8497781d024f53d62dc1b12469b7d53784": struct{}{}, - "f0d588a225e6e6ba0501a3f787230abf579f6db2dd55be0fa3450f8acd54e6f3": struct{}{}, - "f55121c29d0ca6ef7af7d7b03bc4670d559fd04325ede332f152619b57899c95": struct{}{}, - "6884882d0a907cf96527457a9ccea91a55b6d41ff3ca0d9626ead6218233eedd": struct{}{}, - "7112733729f24775a6e82d0a6ad7c8106643ad438fef97e33e069f23a2167266": struct{}{}, - "a7bc3e26240a37ccd98155dffd2e27caf96726a2a87cb9ef302680a01e246b15": struct{}{}, - "c3fc7e2879b241d1cb7b567ea7b74df857d4d7a2dc573d7041733e4683be290f": struct{}{}, - "0e92d69f9b951b18299f34b2e53dbba7e9ad0b070883c040c0fcc1f74e1985ef": struct{}{}, - "80f563ecc98a3145fc14237415b486396ec6be5e9826ccd0d6ff1686ef2ffc50": struct{}{}, - "ccefcd91405c728f09cfdfd196e7f74c79a7b3eb193bdaec780c4e61274b0ee4": struct{}{}, - "50b2ff5044f280413913144b24029d8415a5414c0bca9bcc4643af1b842779e3": struct{}{}, - "97488fbab3282166738a47c2f619037228568494475d4ac107c46c02678cb728": struct{}{}, - "1b21af3ce5e635feff37d6d55e2cff24bf340ed2b1dc7385307b122290213dcd": struct{}{}, - "222f930b87524636da58b1987eabd52f1285cb0ef7dad72f28fcd026fbb3f73f": struct{}{}, - "09b632db946f355428a42d42e9fa8964b5937a049ecc3374a0fd12d7aab20961": struct{}{}, - "b7fe9a51d855ca3a673ee2c1d3b36d2408c765042b302d289783ba88b1da48cb": struct{}{}, - "9149a4a2c2c78eacd782a5735821aa0a19708bb305c49530c474b37dd9aa6a46": struct{}{}, - "ef768af6a3b7d9f2a8cf36bab7c6e8a5b2783039b31641cc16bc0bb1a5d6350a": struct{}{}, - "5afb1fa015d65a839bbefa17687ef62bd5aced975b57f062ffe7da874b9f61e5": struct{}{}, - "bee3848c6a3668fe343e946ac051a1f59ef0add00da696416f14eb397d3fca8e": struct{}{}, - "0b26a3f52b8acd1ae7ddae8571a361890cd89d8cc2e9f71d85174aadef2bb116": struct{}{}, - "1ebb068bded764aa39bf2ff3c18260b506535cf6c165e33282b7b8c85edeaf9e": struct{}{}, - "3dcedf90013a36685c8864d9feea619122bb95d1a5f45fca8993c535a98932b3": struct{}{}, - "1567cae8f653c80bf19af557f28253cf3c7000b78aff7d3bcbdc9fd6fb52f2a7": struct{}{}, - "0ec951062ed265c94ac991bb8c305a37a7f2d7faa93d1c6c6a16d9c513ebc7e3": struct{}{}, - "303183b7184214314bef74a53082f39692250b710a9c08822f62212432c1850f": struct{}{}, - "68566dc699f6bb9ea6330371b5dd1c402ae6f717dd30ef15a88d1a7a6afaf8c7": struct{}{}, - "f08689f14e3421bb12d709dfb885e386df1f35e4c3a4bebc71643386661a496a": struct{}{}, - "ed82d253ca962be824257065af6c11907c37536674e1190c558ebdd4f13b49a0": struct{}{}, - "1e299e1540a7c67837a3bac1c78904064dc4ee9893ff42361525f49e703ed12c": struct{}{}, - "d7dc0db32babc58084d8c726d122bd52e039cacbf04993b49f5693ace5a2adc6": struct{}{}, - "5663ba394d068fd76c8f3569763119ce1ed6955cc65f7d5272633cca73c8876d": struct{}{}, - "c7941b6920e2ed43e6bb1a2b450a801a9e3e4e7ab3c018b128b104c9ce24df6a": struct{}{}, - "8478d1b4be57733e327832eaf8a965d26e2d157560456dcaf2521ecb3f674ced": struct{}{}, - "a0c3259330b49b6782dd48d80d7527410faa265458608f19b6afd746151aa4f3": struct{}{}, - "69c058e417e69c7355ed14427e430833ac18468aa4e0c9e834445cb1a9d56fe1": struct{}{}, - "64699baae0c448d4dda46063111277a86fe96b2d5f36105a2549225f6570277f": struct{}{}, - "6227328ed265fc5922635087f207af3d743344cf16b552afaa04cb584b8eb9b9": struct{}{}, - "45989895066fdbdca64aa0e2e605c856b25379ef78f679ba6e9a1c965e0b4505": struct{}{}, - "d77aa671f83f9d139f66c2d7f0ed574894bbbb041fa065dc94dca63f7bf9747d": struct{}{}, - "6463a1a8d8e713a6982a007ccb351288f4bca48dd1f69a722e4b9d58f42f4085": struct{}{}, - "e40dfff1a71eb7baf65ea49495977ddd33d81611a9c59c5573fc42205baa7e85": struct{}{}, - "923361a07cc6fffe687fce5d5caaa092103442c5b69c2541d8168a67badd1301": struct{}{}, - "799f56123d10ca0cb0c90641591cede64bce3182aeb82442c1438cecd539a77d": struct{}{}, - "71e0532ebfec3fcc7839be02cdf79e545789d06a242aafdf4ab0b1b9694d434e": struct{}{}, - "9032419a7faf99da0e9b5ab78b3b6a43075ca4d028c1a616e3cfacd10aabaa9e": struct{}{}, - "5a2a558c78d3717db731600c4f354fa1d9c84b556f108091a891f444f1bdec40": struct{}{}, - "653e9207abc3904621e40feec9aa00b188b00163de73f3b52030037c8eebbf2f": struct{}{}, - "ca3704aa0b06f5954c79ee837faa152d84d6b2d42838f0637a15eda8337dbdce": struct{}{}, - "4f1a55de40bce9010e814d9bea3d3f20b0c9d12b48dd61bcaf7e0b50f39792b9": struct{}{}, - "f8f5f5bae43b2058c9d710b915c6400e3fb61bf76e9b6b0e4e3bbd26386d9607": struct{}{}, - "5167e9677e1fbc500ecb1751eec3b2a6fc24cd378b934b14b2544dfcd1228be1": struct{}{}, - "4681a3a86dfcca71269b095f9832cca24bd83895667c82d95f137bc1bb31ef1f": struct{}{}, - "8bab5adcecfeea3eab8794adfd472d5ca1570b454d54bd6aa37eac36c998fbde": struct{}{}, - "100f19433664a7e332bb1084eba295cf6b1813cd84a184363d7930342d4c22fe": struct{}{}, - "4ceb86317d0d4dac6853663589ef02ccb67134cee75bb886a4410b7aedd0e109": struct{}{}, - "f7b4ccaa2c70afd3fa74a980d9df3db4fbacf146ce6c6e70c0ab74b70f26f43e": struct{}{}, - "38049c3213a81a528c2a27d198f8e012841fb7d5cbb622227f999e30729286ce": struct{}{}, - "104aecd611f970b35d4954aad758cb4029fa93e98302d7943181b052e21b4aad": struct{}{}, - "da4295c03f74ed42ec3543719c191fae1b01a3b299052e8141f4e2e46d2a92ad": struct{}{}, - "e30b92ce7fe9aa549afaf8b5ac050a8d4409da57e7d5ce170ab23e829003a8e6": struct{}{}, - "137b58a5a3c1aa8fb7373808338a45c3ae6556a85ff00d8644e9b6f393fcb4a5": struct{}{}, - "1601f6b3d4d0fe787ae38e770547d0de86392510f79a528adbd58863b8ca08f5": struct{}{}, - "b9505f3c64cdb4c7a685a2861f53e967a42984cc6391c77af0bdae8f36a3310a": struct{}{}, - "817671e191f0b3998517e8c9bca087765df019d30ddd5949f7230c39978e1ff4": struct{}{}, - "d35ca5051b82ffc326a3b0b6574a9a3161dee16b9478a199ee39cd803ce5b799": struct{}{}, - "7a2551f897f10a3a5a041243af9e0cf5b37bbd82c3c0b24cb3513b21f88b042c": struct{}{}, - "128afc1f78949b70dd2554901ee633571e9896778122f9eb25a1b8cb45dc60ca": struct{}{}, - "aeb8f1b26fc6d5e7befbb627272ecad35c19c1eaf450db0464c839024122c3e2": struct{}{}, - "e43ba08557cbb4f19ed7684fe3cdbf5426fa2c145b8baced5e4fcec410c4e46c": struct{}{}, - "a7be8e1fe282a37cd666e0632b17d933fa13f21addf4798fc0455bc166e2488c": struct{}{}, - "77081fc6d6f152623855e0c83a8f511c729c5e246193a48babf8c856964eae65": struct{}{}, - "1d719eb981c5061f48a94dd5e5c6dcf12b8cbe670104862ee50471cf98be5d14": struct{}{}, - "3bc5495c132e4fe6c465a50de80371882101e782a1699286c5f4e793c19cdaab": struct{}{}, - "7c2d9e77f7d81443c8427e250a59c05257eaf6070e6e060489a0787592476daf": struct{}{}, - "79aa93c94eb078dfca1495a07123b8d9a5ce3d71ea5d11f78dc46faef91d0cfa": struct{}{}, - "b5e5dd909a2330cdff273c344a438825c22da1acc18a3a1880f5b86a7bda97c2": struct{}{}, - "7e07fdaf0474b7ca0359bef88542d528adfd19d856ad4e44ebc09cc98437480c": struct{}{}, - "9cafe03be4fb99a640097a3b5e0db6a77c5bda1527db9556ef325b16e40127c4": struct{}{}, - "9544f5b20118944aa2c1e1b9c0430c2915196bb623813f98cc003baa744084a3": struct{}{}, - "a2b47966a9439d2252d3fd7a42561f81387461f4096d85a1c11967732c3ea861": struct{}{}, - "1c15790b3d150a4c466fe4d2fc9b71e03ddae6a50382035db0d0475d451ee70b": struct{}{}, - "4bdb545f6b040652b5892794bd5ac447e6133a08d50673ec88b892f8467788ed": struct{}{}, - "5c7d4ca8541273a2d6793b5d75f4cbb8c07cf2385088e102427515e70a18169d": struct{}{}, - "0d4fc4a78d3706edccafb665a8b2fdd9309e82c78625bb0f2b8e7bb9e1c4d21c": struct{}{}, - "32948fbafc29b36f18c4a96e5d1bd2789d2232f3f56a2abc1287c5f770fccfa1": struct{}{}, - "d041924c15885af6d06530a425c6dbffc80520150c4dd264f40b4364b12421a8": struct{}{}, - "12f7dea6aa0e6d90e4b8191a30145ec12252da6687204b075f57e50cfd10349e": struct{}{}, - "ea7bf4bb723041e7d63fe6c74dc46c048c617eb49a454a1cd6edbf34d05a418e": struct{}{}, - "eba6fd0e1aec9c8bda8e4b8f7e4e540bf0bdd03a1e95217a2f096ac66a7fa1ae": struct{}{}, - "7407f53c44f5c095bf8f723d59ef3d76f69fc73bd782ea52884cb910cdfd00a3": struct{}{}, - "8369bcb9a320ad2d1bebd6aa1b3dba1842f7cbf0bf0d2b6bcd5f9f7ecd775c83": struct{}{}, - "28aaf815a9d2cb8a6f79d1c73c015d1e7fab50fb5f26fdd4768ab7b950aa809e": struct{}{}, - "66ebf4cc3b2f37b4475f680383021bc8320d70da63ad3b6a11b82d636fea23e7": struct{}{}, - "1d48f84aa257b116c0a7407b64135d007bcb898799e99331ec9a9349002fca4e": struct{}{}, - "1c9f8eec5eeca58237f9ebc37654bdc2d8530af53f1ebf6e057fe81e6968259f": struct{}{}, - "fa0a2297c9b9ff42203c19738b58bfa8b67f065737de03dbeb06ccb8284f1600": struct{}{}, - "8ec755d21c36bc157eef94a235af6a58b0c78b0791965958c8fb757d2b8c410f": struct{}{}, - "db44daf2e965f230f9693880ee41395b74b3761914509d494aca20e78e9bce3b": struct{}{}, - "9c3b89ebaf0017dfc8fc721ea71c7af09b55e3bc35019a3cc3a3a5bf3313b0b5": struct{}{}, - "7f2fc2efefc354db55637ace5a9a4edbc64a14120f11c368e6cafc56b118142a": struct{}{}, - "a98ec5c5044800c88e862f007b98d89815fc40ca155d6ce7909530d792e909ce": struct{}{}, - "0547b3628ea7b842a0b560c658bfec8cea1b8526e128acb5a4959ad639578fd6": struct{}{}, - "3dbfc980dc71faf9ce9bb51a91bacd5f7b19a7c2669c9ba9411f6772cf8d9515": struct{}{}, - "ad3376a1ccf2734671060f0531edd7d44b4ca0bf39eef3b9b7dde3a1571e0993": struct{}{}, - "d5b14b7ac0b9e7492932c6e1e1ae750e9d392212d4bf16383c319b0ddd7e0b10": struct{}{}, - "46aa56a0389bd8306e8339195ebf890ed04f074e50ce8916beebe5225a6beca9": struct{}{}, - "244274ae3023e15528b185666b29f78b09951ed7543083762ca26ebd5fa147a2": struct{}{}, - "244f8c18cd3dd9039d61594f1ab385c9cafbd716816684d9a585821e3659de9e": struct{}{}, - "d947b5d05dc6d0ef6c685748d52f215deab1acabedc801c60ed177fb4399911c": struct{}{}, - "15acf55556d07c65dc50d6fad4da5564c48104eb91632cd33b6178b13e9ab07d": struct{}{}, - "aa342a6b087d9fe25e96457873ca611b35158223281c1985a11cf9ad26f89ec0": struct{}{}, - "83b4326ffdb265455f85b8526aa593c1c0ea2d32a3df77521c30d5548019f4d6": struct{}{}, - "277375b99e186c72ac38ac47b03199038342fe0389be8765476fa2be0c5b5649": struct{}{}, - "883f2691c51017948bbd7ace52e847216c9a6bea2c9a3fdc36680a40d8016fd0": struct{}{}, - "2c4dca7c5a34feb03d70447f026e9abb304cf2571e0d98a60cf937e5ff5f1512": struct{}{}, - "c7c7eb1087c4e24d4c121b65abe24f4e54b06a997aa9eac86f3215271f635672": struct{}{}, - "240cdd868415131c9f949b7858e371d15a7c5c02f36b0ebd8cdb9d655fa2ca2f": struct{}{}, - "36d83ed43625d95e7abe02b0b5cffb2be2830af54e6c4100eec0818ef76a5915": struct{}{}, - "943705b79015ad01169baa0fbc7eb07c213e535c6b3d0fb2aad0252814c6bacc": struct{}{}, - "2016478d9492c6ea60a144f25e5df8d21b89c6ad1707846d716ee59343770997": struct{}{}, - "60e6cdbd75f364ae71846ae3131e5597c157e6ff25b4fb218e3e373ef5fc59b7": struct{}{}, - "94deb1a1beb102a11862f24c200be40440debcd9934a659c4b7e32b5a1df68e5": struct{}{}, - "6da9efdb1329c320f21090254ea8ee4609440be758a86cff9df16c350921f120": struct{}{}, - "c10991c8d957adb93c570700499dea19f4dd25651d05e87af0ed466119c3236b": struct{}{}, - "ecb7f7428d8ddd857bf31062fead13981cc70af43b1d2e27206acb696f937966": struct{}{}, - "6b43967d3f2e6d60cedb3bd5d092aadf2fef1790b24634c71b9bc8c47914776e": struct{}{}, - "6dfc3c0cd13ee749f6d759e5bae384bdcbadb4fc4975b6600f9f0f6c39763c36": struct{}{}, - "68665c03361a74341c1bf9793c558d2cba0f9eba4994b3b28cca181dbc076881": struct{}{}, - "797cd5c4ea8bd7cda96736a798bb701f43aadeb70fcc940e088bc3db1fccd710": struct{}{}, - "243801106c4d7b8f43fb87ea881d0e8e079435d199132d411bc612ffb56f9e6e": struct{}{}, - "4150fad235c3eefc712708aea8f37f25724c12ff072774a9e5336f32f740d864": struct{}{}, - "ff22899918336daaa5ca4b0ea074c18f496c5fa2b9feb698e6754374e7cc5661": struct{}{}, - "cab6b959431f0cf4e844d2d5c54f4cf4b568c335e19427fafcb5a9efda1b55cb": struct{}{}, - "8e232a78a47b90e37a158cb931da1d7d77e8ad37822c3d245b8b8a1f61a4f612": struct{}{}, - "5b91025f08d5396afee5c5d71222862a7682b141cd85de610e778108e601860c": struct{}{}, - "9d0b319ae9922e86441ad0a26245729df5803f154a38179eb558615295e0c0e1": struct{}{}, - "e7ed78d2f0f5cb0152627fad975dd34e828ce3d5ded67d7574fbb4098183d660": struct{}{}, - "a7d765dd1fbf9ee2af86801948bbc4a4435985566c4528460b10cfc0d085a466": struct{}{}, - "0f8897661f150233e7e27edda4e9754f6d6085f440055468118b41838e529fbd": struct{}{}, - "c42c0e10d4bc71284f959e506f3e9b2dfa6efd67bd0bbc1e33e1b2da3ade9936": struct{}{}, - "c2ba348d1ffd70736f32541d3e1e9e1f253287cd4668a5659ab22aac9d474abe": struct{}{}, - "d90ee9ccf6bea1d2942a7b21319338198dec2a746f8a0d0771621f00da2e0864": struct{}{}, - "dc0796eb59f3bdbea66516e6c47a2eeb14f7bba262d89306e6dccaab69f0ee17": struct{}{}, - "9abb6eb5b6a6da8e3603d9e8977521d387b10bc9d802c12fb1264d05c8f11dfe": struct{}{}, - "2554d1918fbc9ddaa7ae62ce0a420004851f6dc58fef14c5d478fcc34b592c29": struct{}{}, - "a190ecb5299b4ab0f2acce29dff600d63cdd3aba6320387b1e51f00c988a84cd": struct{}{}, - "58e2a9c5afb82e3ae41dffdd0141229c3035af5215e1b34d85aa03ff046482a3": struct{}{}, - "80d41c54a8ce6d26ae0bdd509db6b187140cae39b4b771269a0d006b0620e2d2": struct{}{}, - "376be72e38bba9182802afedc01055b389d210a3cb9081bf85927b89273b8767": struct{}{}, - "c512ca0c5c1e71be19f3356821e56732b5fb3ac894b0dbc5846d0b6920106fb3": struct{}{}, - "fdb0ecfb6bb87898f143f5ae710209f2ffa48b60c337b478526967e755885ee4": struct{}{}, - "d767748a603799c85319dba02a583b3fdbe28ae5f82b2e872d6bb66cf104e62c": struct{}{}, - "3107ea0301a27894a212be51c76b7c5b94a8700b6ace0024fc023cf1e685558d": struct{}{}, - "ef93b6033efaefd4f90ee9eee469f15996b7eae2ffd91880a8a8a921172eaf4d": struct{}{}, - "45d227c4b297a0ba4ce42738ad3d448e19628a5ecfd509ea861b24a23009d08e": struct{}{}, - "0faa4447645b7488a06221d4ffc02dbd6045678f23f23b79dac1dd0fbb4732b1": struct{}{}, - "740d9472155d310672c71c284d4b91e751b93f8f0a5a3ed9ee01b30f204bf0a8": struct{}{}, - "0dcfafab5f8035e779d7bea495f8177b9b7c5b892b81d7a687df96410a0ba304": struct{}{}, - "c6520d7c46b53b2f4567cb3b29b93b19c775b601d5a0e9e06315ca9e79751e7c": struct{}{}, - "66fda4dea38bb4d3ef9da16bd0eab484b87c2d960523a03939f9c6b7d626b357": struct{}{}, - "f406796405fa4bfb78407aa379c85c0d8a9dc3dc2f280ae3ac6fd45139b41449": struct{}{}, - "5a2956325fbb7b9e03d2759b037457915f9f9aeb3886b3a070f4665697a4a2b5": struct{}{}, - "235e285376cb0d3b63dac947db04303875c7ee0677601ec4acabb9787c5ce16d": struct{}{}, - "6672b930d5d08ec09c0fb99c7e5be01266ca6b80f2da4df5c3bf0c3cfb39fc9b": struct{}{}, - "09f04951af4cd503877815fd30f98e3af8d3d50a0972e83a4aaa42f4fba59761": struct{}{}, - "10b0e83900536562e4cdabe920a50c03db70680c70a9f62ff694cbdabd741965": struct{}{}, - "b5bced848634723f6f58fc76e0b5011a8bdc4c72de717bfb37a5fbae6729e19a": struct{}{}, - "1390f4a0a02345d64870060256ac5119ea23892a6772673cef80137e708caa03": struct{}{}, - "448f711197dfe46be4c95ac3154fd81cdc7ac9a0f02c629cbcc0474dfa58ca0c": struct{}{}, - "b79624284645b7cba19d76a417e493fe9e767745d4313fdfc35107c92a5db909": struct{}{}, - "5c1e134d2d3437b5dcc521bfc24e9db3f2d8250349cb18aa38b0e83506eff4b0": struct{}{}, - "e5ff8d2b6809d8105d3c75b2f8c4a77d4c73d39e11fddc02605720a13168de48": struct{}{}, - "9d65e1579416319bd60d9faeb18671c586077efa8ad19e1010704ef8254181a1": struct{}{}, - "abb32701117d50a93f8ffb60e801097d8a18d0a75ba7f7a327d50103cb50278e": struct{}{}, - "b5b159009e72a357fd89fa82862ee2e854d7489154d36501fa9d0243dc7acd03": struct{}{}, - "eca1dbf552edfe3038ad272d4d7f331c2a01a48eecc0e555b63f2aad9061a929": struct{}{}, - "e7761fd72f017c0beb1ee03917fbdb43722269c5b67508b29246f0a515242e85": struct{}{}, - "2ad1249f8c142d703617276c685c35379a51883a31604ade3bad084c967eb239": struct{}{}, - "e636e1085ecb9872c5c027570a04b6a790100a77f12a7b7a05f3a2843d0c5d04": struct{}{}, - "ac87917328254c54dd7397fd8ecceeb3e8834acdbb3b472a794e67a5d2270651": struct{}{}, - "e3172bdd56faec80e2592bf8ec4026de5100719f7e2d87ce135ae1dacea1271b": struct{}{}, - "c68fef1abac63a8353831df2718519fee1c4c16d63ff13bc473a65be02fb60bf": struct{}{}, - "cfcd650bffa27ddb6d65197835191a5bdf3601921ca8cf5884ec1721b23f35f2": struct{}{}, - "2bcb43cbc8f6b7ef66331532881143fcbae60a879db3a8fb853f645bb24c2b3c": struct{}{}, - "bcdf047254b88e288e58d863774881f9db007f39066bb7254ca050f1709bb677": struct{}{}, - "9f6646bd8c323f20de7538195709835dea9ab11108ea88326bdfaeb32cb2d9a4": struct{}{}, - "6182492599d5e3cebb1cda1701efbedd3c6c4c9b2c16364b22a22b1aea39375e": struct{}{}, - "288fa5fc4a3b311a79c33edbc8ac0a96e7a4a58235a17216067f31dfe6d52a36": struct{}{}, - "b1b716da14a18f616a507fcf9298ae89faaa2113038cd7b996d9c163d0576e22": struct{}{}, - "64709cc189b289e0ba45c8fc29de8a61eb305139fcbeae4321cd10cea3f04620": struct{}{}, - "7054006c40c7f28dd0a84fc38ee0685b2dc85bd159779298542abcb07e91e47a": struct{}{}, - "c8cd1d534e667fcfa09bd6a5fa52a7e7406904f45c36c086620c14b324450231": struct{}{}, - "1c1352602e995e440e1bd200d71f3c47d90febcc18fe3207fb57cc3920c647ec": struct{}{}, - "d4accb4f288246fa34f6db5720a1a50bd051529fd92ebe1f8450a9b26dbb268a": struct{}{}, - "7618f66753db7ec069c83ed8c197708e1402396774f60961065addd678933871": struct{}{}, - "058f5bd8c6feda87f6dade82ad702b51eedc22814a3bd85e2431eb7f3aeed154": struct{}{}, - "1b7e43557e8d302661ccceef751716a7bf0ace165ad070fd8b756558ca7a57af": struct{}{}, - "38075642922ae0a7900a96b6a597fca0f945a8d643fb253b58139af9d7c21d6f": struct{}{}, - "ffe3715dd3d56af9d2a0e6aaa56fc546aeeb31923024c56e6363c7d42cecae16": struct{}{}, - "938521e0c82d69844e9024d3b71a59ecc7b4313160dea1e75287bf0b892f8446": struct{}{}, - "deb2676eeef1cf8fc35f1e58cd860a000ac231550a915589dc66159806fc4a4a": struct{}{}, - "6b789b1a4397905f393eecf92033ce02d665f20edc97b432bf948532b915aecb": struct{}{}, - "ce79c5df0deab841b775033770c530cbdf7163db4bbce4a91b5bd23f8db0774d": struct{}{}, - "2096483682ac4d770ec99f5fe07417b867d348c0d597fccbc78b40d13c1ad4b3": struct{}{}, - "6d7cbfe8a1ceaf7d2c362691053a0700d36713afeba5138b9870375fa4005561": struct{}{}, - "9d486670cffb9cfa6c7592c87d4b6c53a8dc3a85e3b5187fe5c619aae9b8f33a": struct{}{}, - "939d59d1285efa522f3964451c00cbfea1e22d19a6769b9aa00317fecd681e93": struct{}{}, - "8f0bea4b31b0d95988da20387fda4020c0a223c840b7772d0b9c65ef5d6c9eeb": struct{}{}, - "00c970c8fda3cb85b24a7a9c964a6ca6f94c72fde6abafe8d687d2783abd66ca": struct{}{}, - "253ce1ecbbb7d012f998dd6bec0aef7770da374fc8e77883d9f8239e0d7baddc": struct{}{}, - "8d719a09a04b3e69ba97b76081fb2b9d3d5078c44291e183ab5fb208f231fb18": struct{}{}, - "518828138b9a67ae7ba681e28924f9a98098730d89bc4bc3b43cc8c9c321be73": struct{}{}, - "5f1b7b6ae02dea8e28ae304a5d866f7d5fd213a4fcfee9c80782ba287ec8962a": struct{}{}, - "fb4ce1d093a752169ca12eda5f05d6548fafca0013163ae3ab6f853fb7e6e3d0": struct{}{}, - "94a4860109321e262a96149c27cc14b39239377bd1f408ff2f37a90f1df310bd": struct{}{}, - "cd1591a63d28e6ea8a57aec8d4d5d57494990f5a49fb29d34f15405c5d7f9eb9": struct{}{}, - "57b379df03af99183605b1a0069c51dc6db345c3139c476c219c3ddcfcd6e7c2": struct{}{}, - "4781fe4aea4fda38274267556e4738729d6fcd2a5b5dcb4834fdff731b0a44d0": struct{}{}, - "1bfc8dc431c0b469be926f6a7e6ed263ada851805ec61bfe19ec154f98f8aa2e": struct{}{}, - "d35c416a85b807e9b5384915d6ebb4a9f7352713efd89857b45a242f473728a9": struct{}{}, - "5f137bb23a94762b8f2964186d3e8e9af94612568675b95d08afac582938fb13": struct{}{}, - "09e9c4b3279d3815aada2ec5383c32cf4b3eb0c436995e7a196c6da274168960": struct{}{}, - "243ab035e2cbcdbfac8045deb730a0142eaf664babcc53e355f85220706b4ec9": struct{}{}, - "adf4a84426c4bf916745766e713396ba2ea0b36e571264dc0e547dab9b74ce0a": struct{}{}, - "7054ec2534264a3ed32525d7beff3d46cba7f20e13d0bf65754fcb662ed09a7c": struct{}{}, - "9d094e89bc38ac9891b0fa518906457b0e826b322138b5634f4a462eea718b12": struct{}{}, - "c2fde7373fefbb1d9a8415c89aeca1fc6c01c9aa83904ae5a3336448a8bc3157": struct{}{}, - "d8252e5d78f9b929a157d4cd7e19ccf4a8adff7c4a7aa442fb84a3ecf8138ac9": struct{}{}, - "c5420b43786b20f6cd116002a483b128e9d24020852c5e1c53731e25f40217f0": struct{}{}, - "f3707279b45397cc1e03f744941b2d9638c88726203cdec188f2829368b0e84e": struct{}{}, - "7d3b5c83009fadf734c06eeecd7fbe256c69f71c8ba0429e4d7ad5f54b2e4097": struct{}{}, - "824d422d755b9c7b36ac85b680c13d278d36bbe4444f46eaf98bfd2fe2dec267": struct{}{}, - "b7e307660e1611cb42bcb28e4bb4a6465ccb5ec2e028ca4be8b84e8787929a38": struct{}{}, - "595499f88bb033d0e0da3e20b6e9756b7458ab6c22c40fbc394464ecf9610f26": struct{}{}, - "7ba7d31bfa1ed86327ecfa9deb2dd8a44488fba943ca78c86c1e21f2d1be0a10": struct{}{}, - "19975015249cac6433a41989d873ccbab91fb1a748b72dd880ec75f83fcc3e91": struct{}{}, - "6ee5bf9dcedfc760a17cc2b212b64b863e9e919000f25c05371778fa0485b120": struct{}{}, - "766a5ad0a3494a7f2089692e368ac32f974ab0995a32ba605c64148de06f6a05": struct{}{}, - "88670bf6f7563a74404a67a9f22b0c09d6fe5e458e0a7568aa6efa73baf8825e": struct{}{}, - "40405cea63a5d1dab41687864dbd8dfc4daf4f00d08d8921c597afbfe664b4db": struct{}{}, - "75eb6224d3db03d60e086ff50a2466ede784a8e7075d38f8d9fd81ad4e23bccd": struct{}{}, - "083354f64c19aaf064f902704265178aca70548367b13f1a9b75dde022052571": struct{}{}, - "cc61fe05677bc36d7166ff0052dc264a31581bf69c5f18b2c6688085f383751a": struct{}{}, - "579651b0d574971040b531b66efbc5e607b677aa0d2633ec9eca376020ad8b3d": struct{}{}, - "405e119a66bbf24e68962945664e6c46866e70e8e37a3b3f473ca220ce1c777e": struct{}{}, - "b9a334f65d636ad4169fc0469e1cbeea2233d675925b788d387d6df46c84ef7b": struct{}{}, - "67daae98ed0c612857a716202f463356ffcf1a018ce140ab4a4bebc8eb274e6d": struct{}{}, - "7aa203f4642ac2602e27cf16ebf15669301a46cef0e430c2fdb33e85eb5f8afa": struct{}{}, - "24729202e166512e14575b39d492046877087485c1233e247c11716f0c792c51": struct{}{}, - "094af38f344a731a739721374e57f35eb14331709af5216f6fe18dab75d7af9d": struct{}{}, - "29624e2e4c4ccee26ed8f3e0ca1012ea57a8f2191be6149f632250f7036119cc": struct{}{}, - "d85647d35d468ffef8ea2279d18ead97788dd7cbe3dadd3222f220f6b5c63bb6": struct{}{}, - "6c8b6f93864ca3153d9dc6fd7a1ece62b547be3c4251474b77ee7c96bee39b6b": struct{}{}, - "b6e81b71ac210e45b216fccb3302f1ebc798947ba9ddca6b83ed1ddc63b2ff70": struct{}{}, - "74b0c5234400b1cfd7deb0ebdc341f1b7a87d53ee3acf609bd9f7aabc109db0e": struct{}{}, - "1887ba32a3e78540206c3302c8ac29851839378e795a7621bf4733f27cfef0ef": struct{}{}, - "74fe23b09abc9c6fa465e02c64e75dbd7879674d971ee801a013e682aac15269": struct{}{}, - "c0d79744b81cc41858b40d3c1397961954a8a336c8b850e70cf36ff4a45fadce": struct{}{}, - "903c3d456f43dead1d568f717e9943dc1d646001122cffd2c9fe76282b682851": struct{}{}, - "5c0f585af3ad04a5e6f8a5c5c873a6e1f346829f4de51031090ce9cddcb01226": struct{}{}, - "ed4807a3384b31bbf3e3a36549947063a37d52501d4e7fd668a42cf5750980d9": struct{}{}, - "dfdd5a0c3e701c3353bc78e427f04fcb2c40ba0b7b546716e38f2ce8e929cbb3": struct{}{}, - "f442dcc803a56ce7ff31bbc86a3b15f7a1ad13fab44df8b9dae1f3162bcc1abe": struct{}{}, - "428b0cdba4a55e9c3aa71f07390dcc9a72b5e12b49e630a64b19920954601a9f": struct{}{}, - "a3ce0b6106cdb3982d7b2bd5c7be5e1e74d973c4a3018b03beb01fc5dc3ec6f5": struct{}{}, - "eba759e904741b9a2814ae2aaabaef633366ba1a808dabcffdd68b9ea9d9a458": struct{}{}, - "8019d97d49534181e4518e5eca077e9a39c354c00cd2e1342617dd0c9637721d": struct{}{}, - "e546cabfbd6682c1c1af2af956300910a1a20a2afcde2467807f0099ed290583": struct{}{}, - "a72274ae6fcc3a269d52e3debfd99ab96f92b03f16e9fcef197fb089b6d33950": struct{}{}, - "41024084d2e27b7db74265a6252083ac949cdb22496e9c91a1154715f4baed64": struct{}{}, - "4a817a1f20d1f633a3f4252af4dece9b6f1b6be4e5b7ae412460911afb687eb7": struct{}{}, - "8202e09e8194bd424d2192903b70fa5cb2f1d0b47996c6787b1bc1108d87eafc": struct{}{}, - "ce53c678303fa3e5678b3f614d1daeffc31e32137844b42b46546183bc8bb3d8": struct{}{}, - "4950abf9d70f734e001983519a90a43fb1be4ba3a54e03717e540b0fd5b3f507": struct{}{}, - "4497b6d642dd361c23a9a7ec2f10a0e5248dcff52799481ff963ee014db40a49": struct{}{}, - "85f4550918236688517358ad2e2b09f4ad6de4d31705e7556a78cb1591a43e8f": struct{}{}, - "0c63c7464bf325e1057f4f6ab17b4adbfae86dba53d6d37fcaf24d01e7dd4ad3": struct{}{}, - "842f1868ffb5aa8030568df4def87559b2dbcf19f0ca76c3e9451472456d2d73": struct{}{}, - "dfbe820316c99a3bc8c12ac1afe92ee9b1304c39d7a715ee7b41622494855822": struct{}{}, - "254637f72efcddb6a545bccbd0c3bb84e6393647deb5fd344de6584ccc1e743c": struct{}{}, - "326d2500c67372e6ebc9c25f9cb89e087e56a0a22c20782cf40adac68748347e": struct{}{}, - "52698ab8f76cef21fd1c56339814857de43532683064f88ca982bed86b110f16": struct{}{}, - "b81848b9e4857c5ed8da601fa6ba92d9c2ee6c6aceabcf5e09813b427dab7bfc": struct{}{}, - "cac681320df7abae92ea840bb119da3911bee96d43d6e12e0079ad5c6a854915": struct{}{}, - "582d7c6cba707128ef85ba1b1f0320bb5a339a1dd680ae85d7f0a993e59aea83": struct{}{}, - "3756b5fab42243e42c7b73e1948df6716eb2463fabe958667a0174b13d5ee77b": struct{}{}, - "05623b5ed74e472a5997a1bf38f4ca4b9e4f6df5a800d002dd02b3df124048ca": struct{}{}, - "8036c3cf4bd7ea550769c97a020d714a21166f1d52c11cd9b888d394b78bc43a": struct{}{}, - "40542f7e6b9ec1650870123e1481805167519c1ccab767d0a7d4eba76eab509b": struct{}{}, - "79f27f55c6608ec567f8303802272008997f15d980d1eaeabb63bceeb345568d": struct{}{}, - "8a662a1010d2ee7b530ae8155aab20eeefacb480421cdded2b3986caa1df3a1d": struct{}{}, - "aae3ba6bd925f6fa90f778c254346436559dd9cf970f71602ce99cdbdeea5adc": struct{}{}, - "72b33a1cb0bfc9cdd3db0102962414c7a0d85aad94eba64cd8c33265242f7f9f": struct{}{}, - "c20d2927db5a1da39ec2cbb3f9da33291efad8c20d0706d4ba8c8fcd798f51f4": struct{}{}, - "8ba3d9699b867e018cda255f4992f94c6b2d52df0310a5c3820ab1a5dccad3d6": struct{}{}, - "bd034fa8bb69c3ecc914be82b5945eb91dea0e8d5d27702081d1b0424a8079eb": struct{}{}, - "a7cd62b5948d9355c4d2c792a8b5dfbc1b2e3baff78d7624d027a647cd84ab62": struct{}{}, - "ddd9ab3e81e06b4c405d4cc401d6651de4ec99513fe314ab7f69d66db026a1b6": struct{}{}, - "db10a7906121f4eaf1ea2e91a39ecd792fcc271635f8d1fb813c1a4bcee48f70": struct{}{}, - "37a8eec1ce19687d132fe29051dca629d164e2c4958ba141d5f4133a33f0688f": struct{}{}, - "d138e4e2606d9c58caab4709a9a5e295aef1efdbc7dacfe64b462ac238691bfc": struct{}{}, - "6ac605bc57cc5dd7be2967d5479ad80bcbae3ca60026c769d98a4d8a9457b3ee": struct{}{}, - "394753e378e09e70cb217fa8b647834abea0a459ed1e4d760a230c16a6907cbf": struct{}{}, - "c3504fffe8c1bbc7c7eca190e7b51f7e70b153764bfcb9c4833fe75d66ee168c": struct{}{}, - "008be6875298f5a65763851b274e87055f3101a9c0477e583813c187cba187e6": struct{}{}, - "03b2677f8739618eb30d9a2f2f44e1388b32fb234af814492c70aa99fc0fb7c8": struct{}{}, - "5dbf63f37933802838f364464cbd68bcd3a97d930c61851ccebefb78cab4d699": struct{}{}, - "d3b01dff6631adc50fa99d9fd29bb14f1e7de81ef1d4143a190fce02a5578538": struct{}{}, - "c762830741c126f19e44d65d9d98666fc2255c36796775224a84f859b0840099": struct{}{}, - "de244faa787d6097bf104c15060630ce105ccc010ba0abcc756f4f384f738a1d": struct{}{}, - "4f18b075b430429762f19d880df444390a1168662086c74d825bf7f2240a58fc": struct{}{}, - "859169b38185780daa5497983ff20d2994390058d8a71f2847ac7846f970971e": struct{}{}, - "f38c764c8aa00b6578f4254a4dc6d9b50f88fa926e270ea7859bd1b707cd8662": struct{}{}, - "367a1c592458c1b0ab948e37f993feeda943b0a03b70ee6633c51beb78de8ed8": struct{}{}, - "e25b33b483a8681e91d355b56b1dc253b6a9b9598c27d33c6eafb64e71f14654": struct{}{}, - "c00050686b547437d05d9ab78b5527380b029be800bbcd2b21820b3805de541d": struct{}{}, - "7dce0d8644b3bcdd527e49fae5a975017f2d010eeef32bfebbce2a280c46e7f1": struct{}{}, - "ca6e0dc328e4570d5a249d57980b2a03b24da6877b698b953201d6eb906fe09d": struct{}{}, - "3dac2672d71c1a13d9c8d07b6c7dc96fcb289f787877587ab31ad840c59c29b0": struct{}{}, - "a959828bd4bfad50a50d05192c292105f9da939b207d3f10c041f1f6d3f43026": struct{}{}, - "d46aee08cc49f6d1eb41800c1d6bab4506c960c700cff0efffe490d7cb1de5e3": struct{}{}, - "adc6ce7343a9ce8765560da4b4a217a3d206bfce5adf1b6896f1ca7a1399de0e": struct{}{}, - "f5c5fff03cdf79ee5cadc7f7e9ae910241a8a407dfe88b69ce0ae7fb4d356f9e": struct{}{}, - "3c810bb1cc9a38287593f807c8f8b7381b72649641048873253044a134b9ee5c": struct{}{}, - "20b713d0eb801e8cc969e3419f6eafe90e124f403b10bbb2ae1c86c377685289": struct{}{}, - "ba10ba52c6e6fbd0df8690d5793d58b6cf4e3b0bc7b61ed300b47f3e452306d1": struct{}{}, - "ba1c464e18573b1f54a78563d8c3a4eeadb817db5cd7fb8d626456783369f0e2": struct{}{}, - "8da1ebf71cdc2d5d27e6294d2ce875f613ca6c984559e3816851b566f5ab8777": struct{}{}, - "be4af0fd71e2d89e298833f6ecdeb81872ea6c27f226a073e16a33c62ef9f31e": struct{}{}, - "a96a5b946ce5cda6948aff7295af74f1eccda91a79777d555305fe5d4f68bd4e": struct{}{}, - "a8f48fd6fe5f040aabd5d8cd6cb98c405a145f4217cf0c2c06c2a48a3dc3b604": struct{}{}, - "4b353d8b7a3a294348709f202d7bb2d60fc495da8ee331f5f3d3a44870c997b3": struct{}{}, - "6105d6cc76af400325e94d588ce511be5bfdbb73b437dc51eca43917d7a43e3d": struct{}{}, - "3d33b636dec1fadfccfd2aff43a02dbd9a7dc5f4757f61de8e93b746b934e534": struct{}{}, - "682ac033fc24711b9277384ef97895d8b13ec5196c40bd98a54b34a920981560": struct{}{}, - "079b8011434c40b91c5901dcf1a9bb486fc407c46ec98d307a0e8f164c068b9c": struct{}{}, - "e5b84c89f624a40cdfdc0c6da3c9f0b47ce587edae0117b8c8b7c0a9523db5c4": struct{}{}, - "baa8c9420fd1ac6769b26093f3c69f6213307eb93d0a17881042c35893f1559d": struct{}{}, - "1aee478a6578ffe3a55deb2bc9ba1bb04c732224229c63bb68402358e8ee1b1e": struct{}{}, - "3bc175e9ce56d1fc2d86ab6ea2ec56eb21930540b56a5b6e3840dfb64287acab": struct{}{}, - "7e4181fde07e7a559c06b2a1b06877172d2692758628178d22f452835a190696": struct{}{}, - "d8d88df666cb595ea6447762c2560bc06130c07d1d33ecca299351c34ad37d9c": struct{}{}, - "fe7ea00f30a4c22ceb1e43a303f2cb5ef2c5f77ec71fd71225f4cce887b29bbc": struct{}{}, - "4e7d92dde35af0f2d66e10faa48990e8f08b35ad7afbf46cabfac80c7b4e726a": struct{}{}, - "29bc978c87043faf56cb3c75aac6e0459df0d7e3cb712b81592a7c197944790b": struct{}{}, - "888b19a43b151683c87895f6211d9f8640f97bdc8ef32f03dbe057c8f5e56d32": struct{}{}, - "82445f246ff12b2956a045bba8d330e78b819d41ccc57ca7e63c4dfb317625d9": struct{}{}, - "811f2a14b9850c9d9c7562f29228754bfabbd03407fb41f00994180bd9de98f2": struct{}{}, - "473433b64c6eba0accdb7dcd383f756e9ff3da69e6de8e63ca67f3cdc5142206": struct{}{}, - "77f20014765a08667f69e3cba9b1f149d4bc66fa2267c1d5344b0751d1b89732": struct{}{}, - "81930c3cbc39872ee45f69ac62cdf80aa5f6578950a379a55c4beb2d437e7372": struct{}{}, - "a176eeb31e601c3877c87c2843a2f584968975269e369d5c86788b4c2f92d2a2": struct{}{}, - "fe4c15a1c296cf03e926d1c28421e132b104bb7ce9292cbef0f99ab4b42af174": struct{}{}, - "17bc3a6bbef92128462417a00f738e7492ec3d429ae21353f9ba1205ad8b58ba": struct{}{}, - "fb5acfb33cc96a3b0df50739d04510915fae661689a4c96d151b33f88e5a15aa": struct{}{}, - "021624ecb7e008142371f6a5e8ba464675cef73e906528fd20314900f980bba7": struct{}{}, - "f2c819b6790de306847fb22664c37ae432177d8ef72721ed23c91aefd11ccfdf": struct{}{}, - "b74445054813c7207e76c2480a29d120371eab0f91ea4f62d2d349897916d42f": struct{}{}, - "50dd76a1e34d45aaeae5ea51f0f9abef8f502b6d133291aa53f9b9a267ee5379": struct{}{}, - "f8f8922da592b7f3ace2777f428e6c5c0eca681aad18a54fc876dd2432431872": struct{}{}, - "adbbf042db07e37d0c1e1a3f7d2ba51595088035f1f72b0055cc0402ca2a5750": struct{}{}, - "c570e0e3fb51259da717a6c34a61714f1a6106cf9c67cddcd0b54c309713be9d": struct{}{}, - "669b763083cd7b813cdc35d79c6e900bb3febaa96ad074328f171bd178fcb61b": struct{}{}, - "ddb2055f10c5d100ce57d815bad2b14e289d5d98dc4df6cd2558a006bdab7ca1": struct{}{}, - "65c8c0cc5c5d983b2f66034cd864c41c355b066bbd594944ad613f605a6d9413": struct{}{}, - "383ef36928344f56ffe8fe23ceed2ad8c0db8ec222c5f56c47163747dc738a0e": struct{}{}, - "b8192353a3982b4fe7ac9a1f2b1a47cf6fc9f24a8ac0c0bf3ea23a735347186d": struct{}{}, - "ca57045b3b3647832e6cb142a007b69de8c180b655c343418cc7bb9117b931d4": struct{}{}, - "9270c8c38be2579a7e73a7f2eb3584a363824074311d4f5c26ab8ceb96901d5a": struct{}{}, - "6ee16429668c2d8207707407c8c3ee4285a9e26eef5bd0c17ff00efe090808e2": struct{}{}, - "623ca03b773e7f29942956c14e0496c98169fc6293a68094f1a9cce47786b402": struct{}{}, - "28879593fbc3f47c2ea6337dbc31828d1d49f4122903e88bcbfad65fbfdecd05": struct{}{}, - "d38829559dde066f46faf8a97512439f2cd694152eb553f12f8e02c399f1e5f7": struct{}{}, - "7e5ff122fffefa2070cdcb1e71015834b6789b59ffb3e0380cc95131755bdf7d": struct{}{}, - "155fa66aeb0451115e7c9328801457ec002d6a792b10f74930a4882a9fe999fa": struct{}{}, - "2f817509287dfd5f694a29c7c4e2361956d09f80699ddca9262e6a4a11f9e536": struct{}{}, - "5b8907177535b08c4cb2acd4b868b983899e7333f6b17dfe69f570f0bcbaa057": struct{}{}, - "779a613b3e0a0956fa5f75d1cc9f93a57e1c76831767293f4e90fc35a94a4ad8": struct{}{}, - "2f5bbecf7ebc1d87e9b1b172ca61609a6f9f3ceeba5253aa22790f56646edbcf": struct{}{}, - "9525c464f53104d2fa0a3b3ed4b9769a1d1cb61c3f320588846feb2df7af2318": struct{}{}, - "4229b4ba010986336443d3c0141dd1caeedf0fb307fe744e5cfc7a8d27023b38": struct{}{}, - "a4d451ec23463726f72c43d64c710968f6b602cd653b4de8adee1b556240a829": struct{}{}, - "95f8a74bf066fa7336034caae6ce5dd8afa2926b572db00019844fc041f24859": struct{}{}, - "5c6e2938d6342967df9322722c5921e3091b7b94a710bf2709dd71bc2c5732da": struct{}{}, - "8a65a23f3bd1b1aa2ffcd61fcc7bfe0e5f26916363a4d40b6cb1fb436a5d438b": struct{}{}, - "edcc6464b70ace88bd24423181b50c5852408333d44d99e13bcd6543583a7fa4": struct{}{}, - "a1a14ff4aab4f1d3efbe2f3fe8e32ec686289ba95e5b2fc3e1f38052d64da522": struct{}{}, - "419a636ccc2aa55c7347c79971a738c3103b34254bd79c1a3d767df62a788b85": struct{}{}, - "fd68fcf88c30b4df82ec214bb6b559ede1a5d47f3b1f751524177b998bb6bbdd": struct{}{}, - "19461b43bbba8a3a4da70703bda96dbc6dcdc2ee78507e34d2bf0f281932fd1f": struct{}{}, - "e9d40c6f0f6c7eac9b714480e274f0535ad299c4822dfdeb5fb4df6b1bc0e7d1": struct{}{}, - "918f112d100d00dec359dcf58c5be7052076c0f47bc5752bae153c0e92eafda0": struct{}{}, - "c80194cefba53b4a38d06e32f555e1c745038550992744a876ffbe495f4f93e1": struct{}{}, - "b7a16b5f34ead16da593716a4ad7c7a182724734948aee0ab43b2bb4f75ccc97": struct{}{}, - "e27e9b8cdb6d2e03828c182a726c1e6204a35ae8756902a4af826c0dc08eda78": struct{}{}, - "94df486edd7595e40848535c4975c4066e76d9a4059ff58c8343c6a0f55d7f45": struct{}{}, - "9ec0375ea56387e5c8f07dda07b90ab1e8b7ab762ad992e8b9c674fbfc5993f4": struct{}{}, - "268dc4252473d67647f95ffc16182d0cced1b9c5b99b82619dab6c878b6c6ca4": struct{}{}, - "8b4e9e2757674ce5fe1e0b0604639b72d3391c7e8c7bdb4f66c3d665ee5bc0fd": struct{}{}, - "499bc7df9d8873c1c38e6898177c343b2a34d2eb43178a9eb4efcb993366c8cd": struct{}{}, - "e4d50ba0983ea3037da1acd029519cf677b2e426d57aaa95beebb6fda29e5eb0": struct{}{}, - "c0e687a2d1fc85ee043de90c4bfafc300f927d5ee5439bd195ab9be96cacfae8": struct{}{}, - "435d917993db623957c049ed4e9c5cbad0f7eae8a6bc07be7b83b326d99ff831": struct{}{}, - "7863a379f7190c022f37a148cc915807b93dd5b3148ae9802e20bb44a0819897": struct{}{}, - "815135dfdf4e0a6782167cc55f625f0ce71f6f7001d73130edea967c67349b85": struct{}{}, - "349cffd6623246e155a03228d76fa4a5b31199d1b3d8a3a1c01690dd5ffe1b7d": struct{}{}, - "f0acbdf2dbd1cf5f26dce4c87d778f82f9b69ffbf3d5703505f2e1afa993d081": struct{}{}, - "18679f10e50678804a44f8cddbc0ed937b3ed234e95fe28357f2703a259c47d4": struct{}{}, - "e904c9ccfa425ff0b055d2c533462314d35a529b055e8abe41d49bb46d827427": struct{}{}, - "17eebc0598244f4c0acc8fe96757a85eeabab8d50abfb4ca2c634f8ee3ea4d99": struct{}{}, - "6db13bf32f596a5ede78cd96b750ed9aa5a98b48bc96f99a4e513d7287a285a0": struct{}{}, - "2ac025b0e97449a98fd0990de01604eac36abfad0e9c71e4088bfee812be6ddc": struct{}{}, - "9ba18c99cc2995e39112d21304f1e971c82ccedd99df3c46b6f7da2ebfadac71": struct{}{}, - "83b06543f3694e85a5c193193919ac0a96bfdb17e4e6f57555aa4855b214810a": struct{}{}, - "3f3e59a8bcf10a45f2402efcdda43ba8ad2f4320256508e6b70a738fd8ff0d46": struct{}{}, - "abaae85b0c8621400a91b0e88988ede50bb37ed04b3b174b54ad9f026f7f7bb6": struct{}{}, - "664749cbdb67e342a6115cdc08fcafc0bcf50ab5b8300b683a6a385db42375a1": struct{}{}, - "7c523b543e5054ef4028005a0e8336d8a24eb132a6bd0b10a20743ff384c66e3": struct{}{}, - "20771313696b7db52c1da375007154709d62809921510467cc0328d009a6d852": struct{}{}, - "8977913fcc5dcbc16c9d59dbc2917137ce3ea02d0505e569777b3bd65ff39583": struct{}{}, - "52f3141f5cb06025c96c6794e35f5611a578919d0deb4d934136acaba06d4b82": struct{}{}, - "1b8c1d0a373fec4578dfeb86de2f76ce55e5f46a377899b7bc76d84a8e29670b": struct{}{}, - "4088d0d5b8ec83c218dd523468f266a159e6418074bf4a17e5ee0e37975b7320": struct{}{}, - "a3a360036989b5af69f0e5e1c26124652e9eadf3cdf659b58d2308a7574cdc18": struct{}{}, - "07ca1b132652ec4d9163c8a2522581235e3320a9216f7f1f38c5d9c8a2bd26dd": struct{}{}, - "2754fd408806aa784efa2aeb07440e67cd31ef6e2951f5a57a83cf5231721061": struct{}{}, - "45522a0a452fec2520b77c1d348800bb343230f5432c08b307a45e09919342ec": struct{}{}, - "3cb5b70cebac15f567e17d4ed0686244b28fd7046f25c5a9a1dc5514daac41aa": struct{}{}, - "686d2646fc69446f2a3aa3c7dc58808d08723cd2ca64b12a4aea85608a189c98": struct{}{}, - "1310ca2c8932dbd118668bd97442558a3e1f546b5e15c69699dfbd6024f548d8": struct{}{}, - "7dfb6bffe65c2803edf3de1fc808317b83fc99382033e0ac30e26a747cffb465": struct{}{}, - "f0956726750ca787d943bb3ca8ba098bdbff341512524ebb1c887ca1f62dffc1": struct{}{}, - "3e7b3a19fb12a84b581b0d9e7326d6767bba8200c9833f047ac67822d3c118c2": struct{}{}, - "e47f8185e5075519cf13377edb152290654c7b018bf06960c1036531033642da": struct{}{}, - "ec5acefa44cf8383f4ebaff94b6bfa200a9fdbe92aaf11481907c040c4a11b54": struct{}{}, - "564e353dc296c0631b2ac84741cf3a68a5a04e1e13a67c1684f02c564c4834c6": struct{}{}, - "23c56e3a76b7eed484337fb2148023b90ea7cbdfce21e4f68d5b32a4c14f7e1c": struct{}{}, - "15f2408e36d52028d399edd2dc3c0e3a665428ed55f4676d1482304196a2f2a0": struct{}{}, - "e9143a29033d9145afc1854ad835b976814bf560b245685a801450f52c288aec": struct{}{}, - "dbaf86dd80e3bcd5fe4bdb15daa4bc47b66e00bddc2c8e5fa988a183d1ce1899": struct{}{}, - "fb3325e5c8b1e9e5cbcd2357856fd8e69517e82af76d02be51cd1ccd5ffb2239": struct{}{}, - "71a757d6bb11958576704bc8acb05706e21786031bd59123a9cdc58559711a4d": struct{}{}, - "d6137fa79a10acbd15bcdd5ee20b4b180b5c32dfdde2d9870f2d9ba2f4bdb206": struct{}{}, - "82014fa9770ab9cc318fdb9d1759cf0bdc25518ad58154279652c784ea1e5746": struct{}{}, - "04d8d5118a0c68a5a9d004552ccd3a49a4997665714b53a5b30aabe8ba286472": struct{}{}, - "9a9842d1ddc92dee984e2e653d0fe66379ab6de5068e95152c11be1b573695bc": struct{}{}, - "3677138cf1ead5ddbfcc172bef4331fe776c2cd12dbcbbe022c7c44a21df3360": struct{}{}, - "4a4526e21ca8db9f318f158116b7e2b56b0b816e556c0ac7f798dbc157c5e589": struct{}{}, - "f974a31a77efa86f153fb215548672056346439111cd2968af5bd2cdbf880c74": struct{}{}, - "423cb2dd7078b1dac9d67c89b5bb7292f263041abf60455a443664095b47f7b4": struct{}{}, - "09e28e9c5875ef3b2b7463e1c9adc3cefbd35af73283f9f9281dc9b8c48f9524": struct{}{}, - "9eb5c59d70089e53cb3142a8847db8b3033cb52aada9bd051f3c95d8c3b4423a": struct{}{}, - "cb107cfed77186e80250109acee98512ab6776565dcd9094388ac872e27b2575": struct{}{}, - "c27dad1790c97376d4da3f6095063f97a6aeffcce005e1f0be3a44d71b0e0580": struct{}{}, - "d73afd21f46dd34015a4aeb67eb36c3dddb85d1ec619c7f47818bb3dda56651a": struct{}{}, - "51cbf4579cbe638dd8ee27c1a9f19e18cff3ea23dd045072792d8411788f9db7": struct{}{}, - "0b7cfe2b24f6bf14ee9e3e5241adb2f1f84f0c60e5e44adc3d36847ca4ba9a57": struct{}{}, - "a7de009fca3185e342e4a81bdfce39f6faacae6a7e14a9432a8bb5f8976202e8": struct{}{}, - "eee659b327e8b74d81223fd3a52ba7c4543e64c5bf3890ff051e4bafa485f894": struct{}{}, - "73c1e28fcd99258242f23cc1ec951e059a0d4e2502dcec52ed96c8af008680fa": struct{}{}, - "f3877ebccf0112080a593dffbc42bf752cebe8ae91b0a271990773e55ff36902": struct{}{}, - "6923dd1bc0460082c5d55a831908c24a282860b7f1cd6c2b79cf1bc8857c639c": struct{}{}, - "1d64b29c05be0a4d24ee98a7860a53046a6f5123bc73fd181b0a0f2b9823ddb0": struct{}{}, - "0f39683853223d9490af271b145344ab055213e7f4f22a1eeabccf12f4a0e2a0": struct{}{}, - "c93f5d4cbd07f22743545c573b7d88184d5b4609b654201fc2d90a36d1c0bdf2": struct{}{}, - "eec343feef881e850edfd2d907a48343b40b0acc97641dc13cbbf9cb6c75fe94": struct{}{}, - "9f32a6796682f1c784d46ca60d38aab39a4862523b4043814c2793c7b9382520": struct{}{}, - "6211401819b4fccbc2fd59e51228a393b6eede0b11008f34c97c61e1b9e6b54c": struct{}{}, - "40badc66889cd2b74037c3731ecbc21073b924a8f1678a02ea59f0766dcd73d2": struct{}{}, - "34c54662c4b3ddd4e547da3e8b987fc6fe527b8607c2e5b8426b347bb2b5ccdf": struct{}{}, - "93ceaf0de78f8594d83df634406365649dfb16638041cc762dfa23093dd9c4fc": struct{}{}, - "adc85e62c1de9524d68458f6a7a82bd8d621266d194df0564b67653543b76093": struct{}{}, - "26e59d6a3c92de8027609f4f2bff399c3cdb20c68cfb2be587f6452c0377cdb8": struct{}{}, - "b7b8ef0267c884d0b8e7905febff8335c30a43c13cb0aece498037646b468c77": struct{}{}, - "56eff0923675867f255173936ee40e6f7ada31df62cb00f60fb28d54ee428553": struct{}{}, - "9c5997d6875022204f9bb6627b1038563a2e73791e22e5fe0989312af7f1f951": struct{}{}, - "c9526390a7cd420fdcec2988b4f3626fe9c5b51e2959f685e8f4d170d1a9bd96": struct{}{}, - "b409fccdd7355405bd67abe9cd1e1f2181e7e72289945b8895dc023a091d80ac": struct{}{}, - "90ada512bbf0cbbffa37dbe29e42a0045ac41180b74029e8c54df12861a03d1e": struct{}{}, - "2c15e9f4f88b4bb18292e7ff0c4e26120aa3c8e9e18f8c833f9e352423b1c84f": struct{}{}, - "60f80eae09b53d12575103051a118b9bc27504bbe3addf379e523860d4622ddd": struct{}{}, - "03afc7ffe18437f9edcfa0218d65f7c349dc491d45fb6c30e760432784666191": struct{}{}, - "17df2204e77e4d4096a026e05fcc059c015275916f6e83b833c372f47ebb7b0e": struct{}{}, - "3cff53a50707e03ca53543cc3abb5f5a24b6d5facb046b2f8f8afcaa9d1119ae": struct{}{}, - "2a1c17234dad1fc3e56ce17e2b8bea4ddc8c579964f9a37a5a2bb02a44912f21": struct{}{}, - "89378a62ac186ad1c8e50d4efa861e526020097ac892244a1dae22ea4ce7fbbf": struct{}{}, - "427592eea4042e158487ad49c1123b3f88bb0dcb109365cbe77cfb9f07e3ea16": struct{}{}, - "88d853a24be1469211e5c60d7aed7e4baaabc592fcd71e76ffa921af1bd5e73a": struct{}{}, - "66dd06d9ffa9bf4afc735ad41bb3e79d33f40561d43173ff4495c30325983ebb": struct{}{}, - "8d60c4098c232f61f52a34350032b2f3e689d04794249168374573b02c95f590": struct{}{}, - "2b922222376d1240ec48e7227607f5cebdc820be8f91d1e87fdc9527bf8e2d3f": struct{}{}, - "db285d6c0fd5399a70e72159ed04b4b93d9a28a8e545f456fe5403733a457335": struct{}{}, - "1d533b46fb7dad80255a70a3cb5d82718b90554c5e1daefe5ce8b545995465ca": struct{}{}, - "1f75f6cdf94b26bab55edb7b60f3211d6b8c374728c73347f57cfff43e4bfa7b": struct{}{}, - "898fa2c627354c20bf3c7a81460fc5be1ec10a085ce90ca61077782c8e908be9": struct{}{}, - "41667f6e2629360aecaf00b20f8732e3310417ebd54b24981ad43a6456e164bc": struct{}{}, - "0b94cd5e95afb17f118c0ed090fa2621bae55c692e2f7b13948c7d805985974e": struct{}{}, - "d282c8a31606e750d5e70fdaabe3f17b61eb3df4beec5fc808ba3d854a6c9165": struct{}{}, - "003287499bd368abd568a1bccddf894bdeeb6de7a0fd31c437e901004f40cbd7": struct{}{}, - "efda1c925291a74c51bb958e56bf3589677a4a3880ddf40b8323b0235b3ced09": struct{}{}, - "8649a1038da70ad891a526140aff2a25387faf01633a585b59ac190524100bdf": struct{}{}, - "04a6acaa551834f213cddd1a7a83f3fb5640eaf4a72163420593d596af9a3dbd": struct{}{}, - "7fd0fee595c7deed2f1ca450ded860d911952caca6f0c3f27ea1c9d3ac070150": struct{}{}, - "4c799454ccf37279d378848bf41ad9ee12cc4f3d2a4740a54b42412c36af60c0": struct{}{}, - "1570eb5695612a47b30776c45f36d3d1b0cf0c33e63ec7c1fec3e90f3705651e": struct{}{}, - "9014fac8184d7ba9e1dcd80b8c1e1aa2906af8a01fc1e6e9092e575eabf7b80d": struct{}{}, - "7923bfa1a14ef2d9dac4e2e701c3b2fb029396fd08e4a7c752da4918cac4f7df": struct{}{}, - "0249b1b3020d8be5798d3cb56cca3c1c2f819585d5ce1edb4718cbba276cea75": struct{}{}, - "3582246f913c0a8437d0a3b0f5dfe6b440e17864eef7d6282c3e80ab771ccef5": struct{}{}, - "c2b2e5ddebda4dc3cfa79a51077bf86a1db4b6113760643153f6eeaf768f5864": struct{}{}, - "e89a10d4ca9fadf6fb97bd114feafb3a55ef1da7b1dfac118f3ca0c41f07547c": struct{}{}, - "f868ce518d195af53d5ff032c5fc3b17de256d453fa78fec3e000180f21997a7": struct{}{}, - "e878b5219508b0e1c9fe35b958585514a93d41bbcf2fc597fef356d7a65b254a": struct{}{}, - "ff2188e7af137b40587d92a719378df0e000a36e7e0e2374efc2b5bc0dd0c822": struct{}{}, - "9c1431eeb94d267d98b1b11898232ef7095e72b4ea3b269ad458e5a317c81ae8": struct{}{}, - "824cffa0504f12b913cd1ddf4f621a1caad6722f6d01e2ce83a7d9568be72344": struct{}{}, - "afe04579004049ccce1843a40bdf9eb85f3a4839390375e35d0018a7f9aa70c8": struct{}{}, - "4af009dcd561b8060a9e45590019328ade14d11cb111ce932c5fddeeafbd1d0e": struct{}{}, - "12e5cd979ea8c28245158c34a97b04acba64ac008e780742b507420464a1b966": struct{}{}, - "0b691f0b0e8a40343e94834ff9c0134eea97d7b209ed9485ef005ca66ca097a4": struct{}{}, - "05a498efd197c47e0cfbd12c6454277f346f9c8a95bc14088f3b7c2042fc3e74": struct{}{}, - "02cd7d275969f8c46c863994059b9c9335c8028dd6cf99b3df03ff310a89de0f": struct{}{}, - "caf90169eefa5f807d577486b9f795ab86ae2983c5c20806cff959117e90af18": struct{}{}, - "e8aaa8720c56cca66e2aa416067c89567c0f969c4b11332dc5961972cdd54a06": struct{}{}, - "40b30b4e8f0d137056ac497e859ea198c1a00db4267d1ade9c458d04024e2981": struct{}{}, - "4f198eea16e7a596bbe88b0f518453f67ff3b6d2706d93381b6ab5455e67147a": struct{}{}, - "3832cc438b5caa5c73387cc32fb8a899f3e5871192e9a3835323c70be9bd2814": struct{}{}, - "f5f7b379c2c5d793b69701ca7bf0517497d1ece0727ff057d463c4d364e16954": struct{}{}, - "d46dd0dee4e823ffe7fcfec9cab354258b6fe1aeaf2c2619044362c535e193df": struct{}{}, - "2ed6b4c1ae9f8249477df9193d20e75474c152748e0f2a2cc224ead2e1c8769d": struct{}{}, - "12b637dd6a40e81118a123a7dde7d54c584569c273c31ed71ef33fcaa373112a": struct{}{}, - "41966e55400c17d811391ad507c6af707e42e5528f54cb9734571def64c0e97c": struct{}{}, - "9aaf689fbcdfe9f64a071f9cbe28ae44193fa218e72af24456f44bed64583b4d": struct{}{}, - "924b68a85ca137100ecf3c6d45eb9fb87943f4e63893c009b326fb5699a995d0": struct{}{}, - "0fe9115a2cbe5a5ba013c9518173f7b9d486f8a66cb2f7f5b7535ad166db67bc": struct{}{}, - "f001722cdd7f9371daedf315af63a5ffed19ea84a3788bbe7e7069c3ae11f4d0": struct{}{}, - "d83fae7c205203cf00f0f9c4eaaaa511d70eaf19114b012346b5c0d2ee6dab32": struct{}{}, - "caffc69ec268648ed4419625f5c61d76248e0cfddff50e9362f1313579e31d34": struct{}{}, - "24db8654347d2e3e5023f65a396d291cda6e1f3b65de9ff128470d5a0ad888b3": struct{}{}, - "b450d837332a4c69393462acbae42552865738a1b639c33370bf47d7f12855b2": struct{}{}, - "0f7de4dac57f2b29d137644d4d8a2c33fd3d9867618eaadac90289e6908ea9e6": struct{}{}, - "b5679ea7383b47df1553f6c89e6d73d26aab70e5ea2b4344fed5c66553ef4a67": struct{}{}, - "e22633b9c529b6e08fc41592f9e4e6b30b7a7edb47dee17e936c44757c3f5a71": struct{}{}, - "4deff8afe88a75b53e9bf736f1d5f2931fe8d05e260e833f047956881f9ee009": struct{}{}, - "981dbae489010d10bae569ea9e785d2dedbd16b7229c924d05e8cb81807b886e": struct{}{}, - "5d81730c174ab8ceefc2187c6a748648cc69303b043354de4fee81e91ca23979": struct{}{}, - "991a3defd73e481618e9cd44694d9181b8cebc5b3842b28fafec24f89ea63a18": struct{}{}, - "3bd9fa371342f9d53b917a59430208dca20f2f508e1337dbb4969543508d2c0f": struct{}{}, - "cb00b922aa7f7951e24303c615a795e88746cc01fa5e823e8793c216970d1435": struct{}{}, - "f8762d4f84ee44c64b4722b25cdc1b8b9354056e2b3f310a7a0267bcae88b7c5": struct{}{}, - "30ca4f2a75646c34c2ddd984b0337a3e04bece372ec49521367d3bd140e7781a": struct{}{}, - "f801a3d7c5bac677256b1b6c2b5f4ab41f0a0e618feef477ec307d783d24b30e": struct{}{}, - "d77c6924e5aba06de76c72bc5393f78f528f9a1f82a3fdbc253be2ebc1312d58": struct{}{}, - "f6d8642e9a50700442b045508864c5ec462ef8f3b501273d82cc26a07fc0a3fe": struct{}{}, - "0b15ccb694ad02fd5ecf5cae299068aaca83f7a1b7cd782c21138a05a03523a9": struct{}{}, - "813a0c3c8e05421428b2e4cc7513a7d29e3e02a2567375c178f77780cf3a56ed": struct{}{}, - "df56baa28c7d61d8ffde6b990924e80d366a62515765424d1716fbebeebfeb33": struct{}{}, - "5672d0cd2f97f0233e7481df7ad1d4b6e8a7fbeba71fcc1caa632e1a47bd74bb": struct{}{}, - "fa8eee94d3360e9a694b6adb96a902e51a96a65877e598d6fe4f15e5201d83b9": struct{}{}, - "1e44a1c45388c81c1a4bba7c14aa8d54cf11d442c99efc62a68d103cfdd308b8": struct{}{}, - "cc1659c78e3497e8bf299fd294d875d6779424baaf73647adcb110cec1d192f2": struct{}{}, - "d14a58bae804a2b80b5b76a010239c88ffca1fc7951a90f8e9131beda1e23c1b": struct{}{}, - "cd55f397e81d3c4560613511a11d6fd5119f0ebfa919f3cddae5e2eb7277a8ac": struct{}{}, - "793d50cdd5d9b7748855fe7c96c7bd33af5edbf04fbf248462d44892503922e9": struct{}{}, - "7a3b3573cac24bf3922c7294de56c9f17013da092bfcdb33fc663de934e9546e": struct{}{}, - "375082520142eb43c477caf1f04e8a7e296d97d07e74fa43eab935321dae87c0": struct{}{}, - "377d648d65f2dbfe7e3e5a0d33238ea0ad7772fe1af3a1c6011c138b295ef430": struct{}{}, - "6adca4497f8bb65ea42b5f54f4b988ddc770ee6773fa8239a94172b4f6237b2b": struct{}{}, - "cc50df82bcf43a7f75ae2169bd255f125d8c36fe6fe420852b4ee9bfb0d34286": struct{}{}, - "8546756ce25a464bbbe6c76df08dc63ebb5b89b9a029c0f23cca77afd793236b": struct{}{}, - "033634443e996c3034f2accd8e341f75d4e6feedf82cb56af2b6b91c2764b2a2": struct{}{}, - "0600e638817732856eb9d76788a38729e5e334435655589af38606b013db37fb": struct{}{}, - "99b379607fccae8ede1ce5eafb80fbb18fc7c7c73e92249ba16eea825a543909": struct{}{}, - "1dbce02ce96fb7fc3b3d6f0167551a3e4311ffca9bd6d0d11daecdc3f20111d8": struct{}{}, - "73f94c7b122a59697fe25e03ea70f3e02b7353d5501593dbb39ff2dcfefc0ca6": struct{}{}, - "105fee33444c47caac1e0bbfe3ddda89433a80c79fdb3fc0a6ade50c60a9a123": struct{}{}, - "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33": struct{}{}, - "6d9c237bc01c299494d946829226c373d506854c3186077782e740e106d79730": struct{}{}, - "ba6a65698d97b04861de63a9d0a2e5a562e0a572dad90fd523ae1f147ba21ab2": struct{}{}, - "55cb573b4a15cb7463f32a2bcbddaab15012b53c9533092bfa43d280b11c616f": struct{}{}, - "3de7716f4e2849165e9c4b8459b9e2b9f48ac6b44cf59b71cee9bfb66229e5e4": struct{}{}, - "2bb2b13475de1407455a7bd20d7ae32078e7fb8bd8d3a2de871a752c625bb3e1": struct{}{}, - "33b98ff5d4f01e2df65dcecffc9a6920f6b8bdcaee52e33bec1accd98a144f09": struct{}{}, - "469dfc0b18c73eb263c4a1476545e7b064a7f862ef53827c22026c97268b2ebf": struct{}{}, - "ade5daf7e4816a9e6ec38b65465ed49edcaa482d9c63ff606a496cb38610f107": struct{}{}, - "1f5087db919ced5c123c7f507d3fcce818cb0cf6e77c2f95a8a35e951e03fdb9": struct{}{}, - "8e49ca2c15b32d904cf3bda906d33b7e61b04a05967c23200faad2e51f234e88": struct{}{}, - "b1c9f29f128fb2d3cba0e49f044295b900c25bcf9c1cf4be0e94a50e8d1b51c5": struct{}{}, - "161587d996c804e9ea5855ab2408de21ed26d185ef3f3c6fe995aa8c47282b84": struct{}{}, - "952729c9c541f53dfd31df59148be509c5fd2a411338f0edcadc54af4dc5aa17": struct{}{}, - "d0d0ff9462aa863cbfdc8d94f218e6546f8d16f7fba89e4b6489367782f56e98": struct{}{}, - "fbfe382dd94a53b35aa728acd7db4554270974426a35557e22930398fb862182": struct{}{}, - "ddc46cb3bb0e210edf2deda4a11a0abae33aa9f502dfb1edbe3db80c67c7f045": struct{}{}, - "d10149eecff30f56d9478bcd96178e53280121c06ebab70feed4d375be4bd72a": struct{}{}, - "e3f07fb7ffa76db024342eb4b9d237bd1395d2ee51717d33d42e3a08ec01ad19": struct{}{}, - "bbb59da3af939f7af5f360f2ceb80a496e3bae1cd87dde426db0ae40677e1c2c": struct{}{}, - "3db051c8db8ab33407bcf080ea1784800f8783c680f347eefc68a6f9cbf64d29": struct{}{}, - "1529783191a84fbca450e25862b6b4cd51f01cfeca4d0c0ea414aca11394dc72": struct{}{}, - "c20eb2d1dad61c2a2e7797603c5994e727d07b15b77066394511375f442d297b": struct{}{}, - "205556f14222038ad8ac7aa357ab7f709676345a41aa253dee087a0b7d3d4bba": struct{}{}, - "95c87de475ebf595ee5b4a2dc017043a306f646576a710cac5db5ca2e63e0078": struct{}{}, - "2e1025a6f0aa4c8ef105d6e89a8fd408c34ef2297f182f51549e498810be3c61": struct{}{}, - "6a8a24866b40d40fa27cc70747d37633b8af81c61347ef16890e6af97816b199": struct{}{}, - "814a3e8f5949f9b0a95782cfc9a7c214458d08b8c65d03388f7e5c50653af88e": struct{}{}, - "c9d22bd28f57026a1648aa12686fc28e5d5f0c39d37a26eee32d3cb005a8584a": struct{}{}, - "62d6c2330036f64bcf71b95791743d6c77c38e0d7c8cbedcbc525c77c57cf0ee": struct{}{}, - "52304ff6e5b03aa677e51de6cc7669bcb9942b95f9d825ee694a2e5e717693e4": struct{}{}, - "f799286cc7706142645e38a18263eeb305378945dc32d9ccd5e0ecce1c2a9e4e": struct{}{}, - "85e5e9136f2bf1ba5940edc6b62126f7f8a0146fcef3985594e8561aeb9bdcd9": struct{}{}, - "82a3537ff0dbce7eec35d69edc3a189ee6f17d82f353a553f9aa96cb0be3ce89": struct{}{}, - "458ebac126b8e6540e6663d1c39d6a1841394f2af1f99ade793357751b37b56a": struct{}{}, - "2afbb13c6c8ed0c7652ea59befd99074c4ce4d1bc3d94bb90364235c0aea0b1a": struct{}{}, - "4d304dfed178d8ac90f63964a01da0c7f1dc729ad932586ea19709ce10adae04": struct{}{}, - "e68a89e0528334d91cf8ad4ef49a9161c06b4f32e167abaa6388066f47ac01bf": struct{}{}, - "8d53a3e3672946bd802cd2037f1d5da8a61081910cb4054a882b905a51550125": struct{}{}, - "a7aeb9eeaa0f48245856dd3378adba4ee062cbe34ff4c4db740e1da5342b7b05": struct{}{}, - "10344fbb33cf720fe810d0735a7761a89c044b1e828504ae15d8750122edc2c5": struct{}{}, - "3c5ddd0948295663886511e0fd7a3aa89a86837d620648b702cc698c836f0166": struct{}{}, - "43a9f00d001478f4fae7f437eb13a41a1788925ac20d117571810bbd8e90b5ec": struct{}{}, - "2084969e077d4ac3d7a31eff585ffc0e9bfc0bde6da92b6dad4cb6db21eacf73": struct{}{}, - "e1f8a1b71813dcfd91092cdca35e3061f830a5bfba55af3bd4d05800d8c9abf8": struct{}{}, - "fec5c2b64d32b85eea76125bf948f297a41d91a49934068926f90e2f04dd91a0": struct{}{}, - "749497f43bef0d9343af29ce644b8f422a17b1fe534d6a2fa774f19f455157b7": struct{}{}, - "9d52a3c8e4d6e27de4ad74ec621583de1630551e875f6cb6abfd8157517e7b72": struct{}{}, - "d86eec85d7c897f15efadfc6b142ba940371cf866d242198c72fe6e3fadc4b84": struct{}{}, - "2cb4c2ac5779606a1fc2ff523ddacb4fd3a31294935a676b44c8e5a966586c5a": struct{}{}, - "4c95538022f8a2eaa5cf3a4ac8919e873fa40725a8978953104959f37bc796f6": struct{}{}, - "d1f1775ac1327fc68a4e0a3d4eef9e450429cbfc8ab3fa9a1a39dc8dc67749a7": struct{}{}, - "ac1964eb089654e01f7bfb4871e0cd31ea4d2aa6e6e48774b6b9917b1341dbf6": struct{}{}, - "f91cbc085cd05e8166c9c3b7be552e756479f744ffc9387bd8fbcd71b59d5f1b": struct{}{}, - "97731396809a3cfce651e2702ac93872c0b94645ab7ae064b6ed79f7543587db": struct{}{}, - "90e1a41d7e1e0ef26ddbb71cda32720c5aca837fd7d52f9d4569b834c9ae7415": struct{}{}, - "2baaceefd9fdd5fbc766503a8ff39810053f79ac1e687150c8fe4d824d62b8f6": struct{}{}, - "77a68b1362c51e736b2533f0da06ae11593eef54f2429f11b9a7d93a30057531": struct{}{}, - "f84079f9221686c965c10c04449fd5ef94fbea6b05ffd2b19cd862db850c06b9": struct{}{}, - "4fe6a52ff8a36bc18ddfaf3f75c47be3a685d9f2674c75f531abb8086f848c71": struct{}{}, - "9257970bc4c4163c673336f01cb5ceb1180214012ddb1b438ec5696948fd9121": struct{}{}, - "32804f0b137b144d2c8a3213b0343a510e3265632041aafa4444bf4f2516f7d9": struct{}{}, - "5bc032b2ed859e87d05a06283b1045156a0142c0703dcb67b494348be5aa5cd2": struct{}{}, - "f6440bc1b0e308a2d780709b50ca3f02a1ab50cf528e7794492c06fccd1d7653": struct{}{}, - "2da977e917bcbc5ef3b51217c7882607dd49d8afdc04a2f90790f562ef287393": struct{}{}, - "242870a148442709821ee4748e29d5c7bed9626e8cb4fd5a1c39d4bd42c42237": struct{}{}, - "7c23cb88a2b95c142532ebbe3acdbe41728d6821462832fb10b9c37c13a034ed": struct{}{}, - "913e4a09b69b9d5441f793f1946bad96d76875e3c2334747b9c0a4c4c4d67445": struct{}{}, - "a97ce4636a73bea92d2b48c2b39327a2b0b15586082f9d533c2edab4fd2fad25": struct{}{}, - "9d57ffe83bbfec711ec1fc370546efe00da513d78cf88a953d4d22583fad4cc4": struct{}{}, - "48e6f958531e543731746fd0a4fcba173e2ae226d60eb19a5d021be3c29f7a3e": struct{}{}, - "9b4bccf5bb65855dab64a26d75b8d7eaa025cc8a590fa8f88e7b2aff81095530": struct{}{}, - "d8736e2a7f8dce1c8e776c4ba70e1e32fce6948490be8cb2d890f92583cee09b": struct{}{}, - "8056f69ebe364b67175762414ad2df8be2db1c5aa60d6be32177632a38ae2e0e": struct{}{}, - "ffcdaa9ee96565fc48e6cd3d23ca9fc6b4d38f7ff7365acb20cc7bcbe928c30f": struct{}{}, - "271e56d94dd8ad2fe2e6e294ded2e629745822b5176d237685d5e9c754dd44f4": struct{}{}, - "edbb1dd16301e6e0b79e95f3a4164d2627358659721772692638ef64124e51f9": struct{}{}, - "04a5b69cbaa94cc721946882dec4df018b0df0ef2114e3c9031f79fd3b9d0788": struct{}{}, - "546f04bb7ac096c418d0b3d7b933d20e46dc7608024ff2b0cdf86f0f206fd6f9": struct{}{}, - "e14da3bc16df326023093f15417e0b9f72920e51bd4d576fa8e9a2634d1cadf2": struct{}{}, - "dd368cc7bbfb278c3b17f162afa0d1bc6bc5afd233b1d4e6e327ff790e8ad205": struct{}{}, - "8e6a0ab2937027144d3c821bfbc92cc153ab0d161908dffe54149df13044096a": struct{}{}, - "49400da1e2c892bdcb2cf806ffe2ef2ec944a3d5b07ef898e653e1befa136742": struct{}{}, - "de3f1c07390945552bf762f7cc7ab871055647a9b5c5372d8a46cc64b2c97e32": struct{}{}, - "a48fcb713243b3d460ca7f05e4e4e5ef1cd4d8ce4981ce20cdd3e98a39d4fd38": struct{}{}, - "4fd76659c9306a403dbaac17bf927d3380ed4ef5895514c603351606e208b842": struct{}{}, - "9c9915e30ac3bcaf175307308338d1b76a88bcee229f6cb0448d82ed199b6cd3": struct{}{}, - "04ade51f12e0b5473c9e839fef07a1119c7bbb32c30139c4de381eef35ac6476": struct{}{}, - "cb156688c36485269b3d2950100e33177bd0c4881010c7309fb2daf4e8609603": struct{}{}, - "360f84035942243c6a36537ae2f8673485e6c04455a0a85a0db19690f2541480": struct{}{}, - "60e5f258a91a88dd4a57ec0f990064febb5a84a584de775288645ad1de5c08cd": struct{}{}, - "66f0d436b0469c570b3b8d7e11a681881d9a7bcd8b12d5c2db426015d3ddfd1c": struct{}{}, - "90e770d5b5adf68d9e2fc484ee680fa7a69fb40540bcffe37be6c6921dcd6a42": struct{}{}, - "f236409092ecf7f18c82b81b605fa9dd6ae1d292d8ecb88765d5afa9b3a71e36": struct{}{}, - "7200ba2827444d683cb383358e00df51cd4d54113f272aa3899ae6bc18f44052": struct{}{}, - "7ad8dbe5dbed0dcda5e2fa713de5ddb5b6db23d8b7f4fc0ed2650b5b071107c7": struct{}{}, - "8af9261a250380d81613fc567d2044e6c3e0d6d9465093b4f631b8bb323c4c1d": struct{}{}, - "d5488e8d71931b504adee440663436866ba8c6effb0835c2efe6e207019aafdc": struct{}{}, - "c1d4a27a23459b5169e9b17b287cfc9ad37041f82d0ff2f5188ae80a37aff9fe": struct{}{}, - "6a1108ca4ad45d50787ad0a43b6e3ece2ba3815a5cb82c2bc912f4477cf88f3b": struct{}{}, - "dbde6e431edd7f4672f039680c58d4a0b59bff2dacfa25d63a228ba2ce392bd1": struct{}{}, - "87780fa5de684e87cb92b279f0bc07b14f572851e73b8943a097c1770a5f38e6": struct{}{}, - "17cf7cde520fe3aa0ee80e105e3305d2ea3b29fb4eba411b62b056e5d8a2745f": struct{}{}, - "7377a71607a8dabc029ab10e7a6a895b92e87762538b10ce8b10c4c9ddc74448": struct{}{}, - "eb9512d8149684a621bf8bd30933bd8c657c6dd9e307874b81e6dec93b17453f": struct{}{}, - "79b03373b8d6bc02a2ee3e33a4ddc45b684e37ec6d95d7ad1ecc046ed49c81c3": struct{}{}, - "a29bb351ab7025926eb34a77f0485a0f8ab9dc993009f990cbd8eabbf0d947e3": struct{}{}, - "1d533c672bfa5b0caa450a8a4b29849b7f7a523aa9ef4f21b9403bf251d4b208": struct{}{}, - "f6cfe289bbfa10e1fa917b9d1a8ef547f3373e0b8e23b16446500d7c157bb0ed": struct{}{}, - "8bec0a29bc921c8223675433f9c0c7c1398e1246ae1f8621523033f71a3105e5": struct{}{}, - "48a270f5f6ed652d763ed20e1a39cb55d679d53bd008268660442fa04d93ff4f": struct{}{}, - "3c30b30005a1335a77f266f586d4939dc73c4dad6e9ff252212350227732d205": struct{}{}, - "22ddbdf97daf432c4a847cc8b6c3cdce0b2d2a46f499c23f8935635292fe0e42": struct{}{}, - "e5ec62818408b5534d92fa91ab48ebce0876c49227ccf794e0d14d6eccf328e4": struct{}{}, - "e9031f60a3acebde332e6283e7532038f9b28c6fc7bf763fac57de46167c4cd4": struct{}{}, - "2fa4263535b3aef02196a13efe80a5aa5de71dd403bb3dc6a5804d445cf4bb40": struct{}{}, - "454647b7273072ab8bd5d0c89bf13f2b990b25a5b17122b333e1eedb05ee9520": struct{}{}, - "ea7c003b3b0a0c4e0d48d0f1ce8a8850a0fe2f62230e467f4c20b928328aecc0": struct{}{}, - "30f44a23710dbff7c9ea341526641ee578d3de2bcabc26ade4a7f7b64ddeb947": struct{}{}, - "0352f2530616cc221471b4d4a41dcb415712c52c5654ed68a765178da708ced6": struct{}{}, - "91e58dcb509f745680c619477d811449ba9fe9a55ee7a65cacbc685288fbd555": struct{}{}, - "6691980f7ccd1531986f1706bf8c034c59892f739bb5c9c2ecf04ac2660f94a2": struct{}{}, - "19af2f572ff37300bd412ae116aa7d19047ff1c5ac22fc715544e4e5cb2a1bf7": struct{}{}, - "880e042d271f08cd3c456f28704702a6b0ad1c7b442f257bf40578112c8e6ffb": struct{}{}, - "0e1637f032b1ffc7b9a4fd33fcbb1d5134c1fd4eaef74fe896a1ed9217c48a8f": struct{}{}, - "d8f1c991ce99e1b87aaf0a2ecfdce4d0408fefbb84e60002dadaaf79191ca794": struct{}{}, - "2fef3b384ccebf2060acb244a6a9116b04cb83a048f4a90f0188fb1d0f91ed0b": struct{}{}, - "73680de257c4f9e1c2b3e2a132b6f3b5c5b2abd4e1485559c2ddae19b5aafd42": struct{}{}, - "955c4f276b2fc94ca26fe246d37a20453f84d7ade2795f811f57c86a8d0d75c4": struct{}{}, - "fb7a34b8b49889f5fa64a3d7ca7560cc814d1a13f9b2f2a66076a949ac4e840c": struct{}{}, - "a11eb7eb290ee7fda2c261beaf7d982df75bc826f750ff0a5947bb66d5a6d7af": struct{}{}, - "61a97d076f59579a849cf41f53c2e0f9f0f68aa9ab91486f46328625cb730807": struct{}{}, - "020bde06c15049d7651772c8cfae88dc513ad631a87de8cde41e9c368b17511e": struct{}{}, - "f9f9c859e5faf2dcaad937b84e48230733fbc20c140761ea31722f2a2dd5f0ad": struct{}{}, - "0304523efff53f243d76dc81b7c271f922292543cead846ee714f066c3331e5f": struct{}{}, - "8eb67b93b072e9786862c65f91687ecf004ec46e7c7588384a45777386bddb4a": struct{}{}, - "a18b869b2e81c0c529552a3c4fa5c92ed08b98a4e146aed778d71d27517f83ac": struct{}{}, - "c50a11249e1b221eed96f66d58576919856045cc32c3cd2dfc64816c988f1213": struct{}{}, - "3d414ea5928a08a48aba82dec1ba0dcf5a4b57026828e3fbb1b1abfcae03012d": struct{}{}, - "8d511ae541682769f3715f356298a1537e0b7d2b2faed4a34a79f07c6d18b284": struct{}{}, - "822f39a2cb6507321ac9c490ded0cce0d2f646e1a72026caac480a815843ed98": struct{}{}, - "b69009cf47eb9fbd7234d3be35cc5ff1da93efddcf902392273f217e94c083a9": struct{}{}, - "ef00f0458ea0a988b5af283e10f7c53cbcf0a0980d99862ea97e18fbed558ade": struct{}{}, - "722f45c486be18392d04c9bea0231301194f141de759394dc1ba94a033a75d6c": struct{}{}, - "60d441f5d7a589e3d87b223c7babcdba854c6a4e389e870125cd356b2fe2f4ad": struct{}{}, - "2b3240970c71346b98e00c5c83e8070484e9a1ac3d04d4488dedc85f454e6180": struct{}{}, - "ce99cde4e01dd1043fb18c380b2e351777af417e67aebb09e3bf6040fb12cf9b": struct{}{}, - "5189c31a520c5d00140d0d2c13fd3933ade8931f9390b7045a8e87e470bed42d": struct{}{}, - "4e1aa13c8cd953a8250dec66d4efe675e491d3defa2ee59087ee71de6cc0e9fd": struct{}{}, - "331615b44eee84d281d9298056a1a10abca4c9fb56dc5104f78bd6d2658fce15": struct{}{}, - "38ab2e3a40b730b492fcedfd02d1058ee318b1d252593956aa4d40371bb633cb": struct{}{}, - "358a3cd454c38404d1162e39500b2fca02ee644c45bc5a886af173579c1e9549": struct{}{}, - "a14cb8f0f0f266e0812a6be9ed769bfc4bf59d8ee4b25a4b45f7524b8a113a55": struct{}{}, - "489a845537412def5431c0b0022bac0022dd00dadcb971bf375c862e5b1f0085": struct{}{}, - "25fe36108392295ccc168f7c3572ee59b65d673db877db8ecae595e7735145d6": struct{}{}, - "38937336ce970baa8a7b8efe4430d75e93239b82de682ec781780e97fc1590a0": struct{}{}, - "41495f7db6f373db23a2b7f7f0ddff5b2832029620cb4c26605b16d732f58c75": struct{}{}, - "ef777c749fb1d42f90386a0cd6b92bb659bc79c9dbf0a8b39e6ba22ca25322ff": struct{}{}, - "6a6b46521e000c1084dc8f9c1f32876a965302fd0008a1cf54cd75ec15b7cb93": struct{}{}, - "b58a656688f1fe41ce5062e77ce256c8e9f141f86e7a212198cde49ea9ad0f90": struct{}{}, - "4da5b36a9884c4297798dc6bd0bc680a209f4ef47531351640a7bb22d02d4a92": struct{}{}, - "9a4ac2962d517239b1cf85743b47e54fc33bb5128a8568b3ceb413754f2e441b": struct{}{}, - "c97c184615c9da4c9e9050402474c4e13a700a52ff7eca065977bba0e461a65c": struct{}{}, - "e5c7ffac26fed654fe62045898f55b551a0dc120badf3d116bcd364418f3ec16": struct{}{}, - "53f2f9a008cb8cb5a7dfca78c8b067941977338e9553a613bb203f47fe46483f": struct{}{}, - "6c1ff7eccfdbfaec611433e77916f6e16a408e7f85084d5ad7e14b8cdc619344": struct{}{}, - "3d00136c270456965838ca85da9d22b97227149be3dba6f4f35c415b4a0a2196": struct{}{}, - "96f6549c6b97a641e4d1099e39ebf966e649de64f5bf8274d27afad33541e702": struct{}{}, - "76f486f47ef88d7c4f7476143db4d59a0d25a7a3315a8d4d6ef3bf61eb3dd246": struct{}{}, - "5694d08a2e53ffcae0c3103e5ad6f6076abd960eb1f8a56577040bc1028f702b": struct{}{}, - "e36f711114f22a42354b9fe9eebd3f60670e4721f0e7f70343590e76189ce2e1": struct{}{}, - "036a629b33cc7d00033777cf2a376cd5a49cd3d2422c82f4a90714e09ae6558a": struct{}{}, - "47c7ef39cfa6b7bd1286d9c83424f322741549e849ad1af19a8416e861434da5": struct{}{}, - "06f8382071a4109d1a1037036a2636cad2a42fe43c64ffe1c51025768403aeb3": struct{}{}, - "884bd8be17bd7407af59b469be43571cab49cff3e7123f741e5c8569438d29dc": struct{}{}, - "49762afb5c3e8e4b6762519bcf03b6fa9ac9b593ec36b1dfb5fbad3925e647cd": struct{}{}, - "19ee35f9fd8585add60fa88c2e1ec81d093b8e17650bbb42380f377fc33f67df": struct{}{}, - "17550e2bb9ff2c26dcce8ed178e326202cc9c67f16b79470767f01839a062249": struct{}{}, - "bb527b1ed178e754edcf9d9f6ec669b6d9f0a9e8133c97fd03ce76d69a05d6f3": struct{}{}, - "bc8457496346b26ba016ec2a30818c0cf5abfbf074df4118f3a527fcce65469b": struct{}{}, - "b85baaef536e67019d6247f640e024d30de0a09f6e06e0b5f46fa888b2b0d9f8": struct{}{}, - "1630036277d45c5e12c8e17915141b25c603240211e148f3c79ad3415238fa35": struct{}{}, - "fedb24c98f783a5d22564ae15c2ed2edcf6cc7e4e90f398414744e683f5d2629": struct{}{}, - "00f9847a38e0fd0576beb6061c65e52a2c137e24acae646588a86f3e3c62d21a": struct{}{}, - "48cd93fa64d5debc368f62d9fe8e220c1c319fdea929313ec3516b7db51974a3": struct{}{}, - "1e542b06e9b67c8c66db82f616598fa106d36f78c023c2a072e8513bf9edcd8a": struct{}{}, - "63897b154a980bf1ae713b1a6cf2c617d55e2854ef57c34d9832e19b5cab528e": struct{}{}, - "7131674d476137843f95f19d0ec59af40f0707fe560aec47af05645bbf322622": struct{}{}, - "22d5925ed64680ab5f3f6de713645a3d72cae7a9506b8a55a0d4fe950737711b": struct{}{}, - "9e96274abcf3df0dfcd14617d6a14931c97fca364083918e38f8cfd20d136f0d": struct{}{}, - "de20ee549c59b74257d8258702db5c1b0f864d4c730856180f876115dff279ba": struct{}{}, - "2a8fa5cfebbfef8a7083cd7a19ffe7228b3ea8f86bf150e002956a205cb7e9b5": struct{}{}, - "c750328d81321862a3d149465a4cd5b3c0602ea274151770d1497af292268145": struct{}{}, - "6f6465f9dd0685853e76cabda89c082c86afa789ed2a72ca17537e83038d7376": struct{}{}, - "2be423ffd088d0b159a30cf7fcdeb9ea8acddbcd0219a9bf3c3236c80897b5fc": struct{}{}, - "1971c568c7ec11db7e0f0ead1e2dadf93e2cfe1a1ddfc9d1ebbf4e6f8973ae6b": struct{}{}, - "5b164ed27f9c728a5e21452471da1f2fb441d425625d35940df7587baf84c81a": struct{}{}, - "8c02e605bc198d20d102cffbab8c18b6a599274fb6d5ab2378b4733d884c2c6b": struct{}{}, - "bc3a71752b926ec680c4e5ed69141adaf26790b083b3c284031c9b981e798e41": struct{}{}, - "d661cffad8295989b61c0b7f0243deb82065b48ffb24ff216aa76430232bb1d4": struct{}{}, - "00479cd598180fcdb67c97d4f8faf11d8205cb461d7335d16683f1c48697e50e": struct{}{}, - "f71254dcbbb2f3fbe8d09a207e5a14477ef96a0346035d727ef199ede5fc46bc": struct{}{}, - "4c5af973f604c186dcbe090a746d4d0248ae40fed496b48510104bb4b732ea51": struct{}{}, - "d082d09643c00e1404094040e8e6e39b655b73a248975953e305101b65d5b45d": struct{}{}, - "b2c118aae4dae259bbe58ccebd8e142c2fd8ec9c1eac5afd9c4fad1bcad632ae": struct{}{}, - "80cfa3e7f28dde4df64436b652230aff28d7779116d1369c21ef2bbf37261d71": struct{}{}, - "ab108206f962adf93f4bff4284486552f81ed02eef29833bf7f324ce65912af6": struct{}{}, - "6ab84705e5d695efd532f462bc41ffc7a05f3a097b877dd2b95d168cd9f3b93d": struct{}{}, - "259016fa30c3d639203c48f7df818087f4fb01e4fb59cba3eb1b59b8dc6a6541": struct{}{}, - "520beba3852ed62da01b486676e9d96c8eb124ee7a8c731d5b2a51bb9aed3677": struct{}{}, - "78ea68e3f85a40382638129b9357006af9b1614f45a208cb476f4138c1ef1ef8": struct{}{}, - "2dadabdfcfc35f0e9d9ba59821ea27155f7177f8be9d9d78d5cacd04fd621cd8": struct{}{}, - "86f8e0325467c6abad211caf101193ff3e595c93478157e8947c91fccae1ea68": struct{}{}, - "dd35941837693c6267f2eb2485cd967210a14f1c99b4e5da22a8a95d4b4de07e": struct{}{}, - "ad0f90790992eb264e19cc2b38adc610e898cf2cef1c8cfd09dacb8e410ce4c6": struct{}{}, - "4b249a431acbe448101d76bfb7ba0b066ab93daad3dfbb03c93d90e817300ce3": struct{}{}, - "69bf0bc46f51b33377c4f3d92caf876714f6bbbe99e7544487327920873f9820": struct{}{}, - "26c46bf94fc804f97eb397e5a1e8860e5a8155b8990be718aae4c4d59de6e762": struct{}{}, - "9a638b8d29c96ea87f206bf020ea1b7077f37fce235336c175f0b684c3d0caf9": struct{}{}, - "c478361e6869af25970682a2c53967adbc8a46e9429efdc64b96351cfd52e13f": struct{}{}, - "0cf6855c1a3c323bfb9d788f7a55f0ce6cc765b2922c247c28ea21babf00c919": struct{}{}, - "ad8f9c8490c04b0133b7365b47ab6d9bd81a875f86e8dfb2c909733595fff6a2": struct{}{}, - "54e056de7e2b488840c152c53185c985d7b1f720679cbcc404d4e48cb8dea4d1": struct{}{}, - "15511a5c8b65b54a8fad497cb0e26c96b09feaa1678fb368ef02e042a6e9b07a": struct{}{}, - "5fa11cca071cc29e49906cd26485defd279b5d0a872ee60a02dfcbee84f71294": struct{}{}, - "a5768a8c19b4bc08dc93ae29fdf543655e0af15bad86c51a425160b6b70035d5": struct{}{}, - "2d140b32b0f822faa25cb3521a0dbb5b2067ddcc2adfbfc81693c6ad5cb59a7e": struct{}{}, - "bd9f38aee15de11f3948bad2ebe00fd965f738e338629ad53a0cb24f085767b4": struct{}{}, - "923b4b90ea64343e4a1cc2386c52fb2c0d197b0673cffbf1197169c27c8e4eb9": struct{}{}, - "c6d312a8ab6bbcabb650f11480f314bb9b168172f6cda60baf16e2c329e5267a": struct{}{}, - "f5b3a833c1347579b6d5e712b5fb676a77ea50e8ecb8b293154adde7073ce2d1": struct{}{}, - "efe335d482607688ab22b4af024d31fc7bf3a0ae432c59845b4f19d6be4003c0": struct{}{}, - "ecc7dcf5bc2d51ebc4d9a9c9108443e3e2b035c0e167594a63ff2fde9a9cea98": struct{}{}, - "348bc100876653d346a172ffce7c0574954bf5b8d09acfe5ff16eaaf694dd76e": struct{}{}, - "c84c15919aa78185e53a822e589d11f6bd7bb0e18902709af9d0a021e43b89b7": struct{}{}, - "fbdf8d52bda2c5fc797725d6e1c25fce8660bab62671ce7cd84d31a5599f9458": struct{}{}, - "eee202edf6d1fcdd71f39d3284f79b8f4ca2c4fcbd4b1a5896f92445d717a580": struct{}{}, - "8b6bfe00f00345950da703a9a970d891d328b08f378c186dec232133ef62af19": struct{}{}, - "24b456adaa466fff7b504b302879be8e491dd3097617ca4c2a4376f441206efa": struct{}{}, - "3d7f3e5ab2116abbc94dfc718939026b5069afd7bda32d57164ac47cb91e5454": struct{}{}, - "bfb299e96fd90505c8d083adb9447c606d1257ef1d98828b2d677d5539dbc310": struct{}{}, - "a293d22b9d67dadfbafd58dc19c32964cc60e31c377c6ae3200048c1163cb0a7": struct{}{}, - "9f97b71bbbb848323f91335d2e8dbc635ed2c503d05b9342a8b1e93a894b783e": struct{}{}, - "0fb06e5f9ace1417f2404cedca3efac29349116faa448f6a6662078679b86e0c": struct{}{}, - "e3a5ade78362097e9db32a956768e5a8d34980c92e34b3c144fe71c269e53cd0": struct{}{}, - "a5ac1f967df633ef08906d2d95b022c36aad8a61d7256edb7b2b294c8407d8a5": struct{}{}, - "58aebe5d7e1cb455fbe7a7672030d146ea3c12fbb0937bd6e0f284fdd1928476": struct{}{}, - "79607a4b014eb52130ed9b713ae827b0f62d451309380578371f73f1757345ed": struct{}{}, - "14a6f1b8801c32921baf66ae566f7563b3b4b3b846fb056f1b71ed2c8802cc91": struct{}{}, - "6ade381b3aa4b14afeee38dc48ebbf4e8b1871c0357b27cfdb273f60dd432f26": struct{}{}, - "3b3f815837590e8b9cc15aa771a222b214331ebb14126ccef157c7a46f11a1c1": struct{}{}, - "4e048fd2a62f1307866086e803e9be43f78a702d5df10831fbf434e7663ae0e7": struct{}{}, - "9e786cce0d95f0f608958afef7a476a21f6c6fb3dce981d2ac054b5f1e9cb921": struct{}{}, - "b3d4d26ff7e5079e2ae6c61f2e47a4e0530264afc5e9a8e045b8952b4386edd2": struct{}{}, - "422e84833a7bd506e2e27ff2648d161f9227b750ea82092f17e4985769fb7fa0": struct{}{}, - "6a61eb7383e868f226d30d31c95348371760d993de5a098747a36923bdcaf25c": struct{}{}, - "ea6a344e27604dcb67a09c548bcf44b13628c54debf48bcf37139db8f9f9dd86": struct{}{}, - "ab9c385e80791bca43787f9e53c029a713458265fc21c3a02be63dd545f2dbd8": struct{}{}, - "5817ae4948371f9b6b7d94615c0704e6a13ba5a773938351ebd832d0fcdbdf2f": struct{}{}, - "87fd8b5046c69bf206af6be0d08f7faa2d44a242a9968ae0bd33a53bcaf23150": struct{}{}, - "adbe8cc4eea5b6757de7bd1678c84ad1c28d1aa0f5a867de5a5b6a7a014b45d4": struct{}{}, - "695b2ea90ecaa863bc5e54974543734eb65eb8aac29bbf14871df3b13cec21fc": struct{}{}, - "9849945b3811abd61501e1c452c7b884b98405260cfe2fd9542f6f238398f0e6": struct{}{}, - "a6f91dfd2c7321f316bc30c2f5439d2071cf90eafb5c53ed41887f4f74e7e0ad": struct{}{}, - "ccebc82d3d16e316c2ccb8e5324bc968f618886cff9067ed6bde65748d89cdf2": struct{}{}, - "f6eafacb6bcd76ff644d2f027d06a021bedff024c6d500ea89340ed049410973": struct{}{}, - "e9080cc5840be244b8d52aab9df23a124117331b5b9d063d57eb1422a6e74ce1": struct{}{}, - "b0b5af04396d82cafbd0966d4602afc796c685ea8e7d2fcbfb91b473b2b7b7e5": struct{}{}, - "be15e51ceece959cc7eaf3454a31a45d95b2a2c524eb7fa6b0a01a2c2105ff9c": struct{}{}, - "086e018bd6e9a5d37525165316b61ce87fbc9eb0771c48521d8c092ad40f38fa": struct{}{}, - "4761ceef5d8fdb159978f6705a7380972f75351dce8ec20d59c161cc25a14e31": struct{}{}, - "6a3912eee4a1a4c9153ec4e9038c4192aaa0e2fad927ac2826d1a18d7e2ce870": struct{}{}, - "32cdb619196200050ab0af581a10fb83cfc63b1a20f58d4bafb6313d55a3f0e9": struct{}{}, - "5430bc4acbcff0e0e247529964e0145e7dc24647bcb6275d94dade6173f71df3": struct{}{}, - "6a99d72bae5bf51cfc3fa9642fdb484009493d782ce4dc89ba560301b9b115ae": struct{}{}, - "b8d77fa261d3498b5d503ecce56538b3fb480ac919ff5d49e060f44bb7124481": struct{}{}, - "f611beab46437c769c2c4a665d0ab8fbe29827c1ec42cb173838b66863e0771c": struct{}{}, - "e9d9d0f618e05c97b1b5d121c57a45825461f2079ff2feee93f787d835a701d0": struct{}{}, - "a4676031b3575984777edccf14c1b405817e976a2200905d047009c1d34046e5": struct{}{}, - "ae55246d966237276fdc09d88ecc9fbd113d3c6e948b8a5c15af806d1fb7e4cb": struct{}{}, - "b0b23803279f82a28b0db4118f688ddcee308e0cb689f892f2a47c0074c36f27": struct{}{}, - "ee3b2b0bd449adb75d34ff6342aa196a9918aa6b0d3fbaf247ec4f34c564ea84": struct{}{}, - "046a6c56a27182b119ae2e99c034d41da311f0e22986c411967d0be39fe2d2d5": struct{}{}, - "7ba20791f9fa8434067a35a59fbe0d5071057edfff2f9f727be9e152b58bb3ab": struct{}{}, - "997bc249599b74191aa0ee6851d0945e291fd2c543898427d59c279438edd40c": struct{}{}, - "9627491f7590fbacb4ccc71a3981757d5de954cd8c2bf313c5cefe5e9f97acc9": struct{}{}, - "8eab4480b19cc53dd635cabb623186babb68d302d82383a529b21c38575b80a2": struct{}{}, - "bec178298ae0fe56c72cf9ffa1db71767b43ce887bbecb1d51f6bd3e94353b72": struct{}{}, - "a0cd295c29676bdf2422b4329a9715b89355e9f2767e55d317102bd1e8a8ac3c": struct{}{}, - "4a81c5a29eb07527ca9af5fc60db077938a3645234d618713fb99d734a140d51": struct{}{}, - "9b3c753a9404eb79ef4239b22dbcae266535c0a0db403aa51d3dd2836987c82c": struct{}{}, - "3b3927e40c6e2d6dcd4ae074e706611c76b920cd6cfbd0031e70c13029a0c7d1": struct{}{}, - "c5bd1aa8a3e22730bd60e7dc273d84c06ce0d9c479ed8e4f18b38d21ae6ad099": struct{}{}, - "cd26b35b473b475ed553e7b512e9a29f51f25db903c9d365decb9e2785f2782f": struct{}{}, - "5bee6bcf8d8bf2de9af329178e3b076911752899f3b6fcaf5056639c631a72a5": struct{}{}, - "80ac697a6617b37617c05853e58a0d3d16e6eda4f21eca04586b2eeabc856ce9": struct{}{}, - "5cf30a5e7cd99d761134a012be9e28671beece6b04d4bbbcf753500c55a4c441": struct{}{}, - "dcbe40a649ac98df17a0781c70ff4e66e3e9e55fe7b2dd6924259c017b8c452d": struct{}{}, - "781e5116a1e14a34eada50159d589e690c81ec4c5063115ea1f10b99441d5b94": struct{}{}, - "bf3cb6d51dcbbb6d7141321560b7339e8f98c2fbaea3267837547c08cdf138ca": struct{}{}, - "4601c46d355856bb6a42bd47ce94455da93bd688be342b0fde314381b9533948": struct{}{}, - "8e25e1be43a25fbd61bbdbc92d59f962c206a57c300e978e189ceef57bd60dbe": struct{}{}, - "1bd8f760226625d12831f60d96094a09c0e38ea31de7eb30e2ff3a33ad3a9636": struct{}{}, - "134d57a6861ae2143afcfa7deafeb72987c4edd4e32f45294255e523ec479cb4": struct{}{}, - "47d9bb497099c29a05f74dc83894e6bfa64dacf3977ee777bd37f60d15059539": struct{}{}, - "e5736c9c147a34cb64c15abbf9ea5d7a32a09e4e89962161233ad4f90ada59b6": struct{}{}, - "29f0b14dd8cfb81c5030ff0935d36c7aa33baf37d56b9e64e2a11aa77230cd4c": struct{}{}, - "17f9c216e807b74750d4497625bc49c24e3b3fc33b5d112187fbb1058b17236d": struct{}{}, - "01a9f91f2791ea9afa16649018663cd01d430225c4cb772b9562d3e660ba7a29": struct{}{}, - "9dca8f255155123f66c4ab15351db67f5b9379c23c3c37443e8d7a5c5d2e391a": struct{}{}, - "facbd27829f29f1522cd4613c7411adcc2a80834da1dfb7dc880df07d457860a": struct{}{}, - "c0d50e4ee9ac1e92bfc52b0a1d0228965d13fbbb914a168842a1a7786b264c86": struct{}{}, - "c2b03401662296c383829e382cccf6b07bf748cfd1ec05bb8c498dfdf2d12487": struct{}{}, - "9c19636ddea25f0a13357d01c0aef27a4eb4c7f8cbaf2be35f272273572ce2e5": struct{}{}, - "2b08516d769747a9b52fbb8cc68b8940383fb06d46d9ca6a657df4eda6d07fce": struct{}{}, - "23f532066ea3e43d73de273383637c722e284969d3a22e30db76c71416f21f63": struct{}{}, - "0c98d7436fbbd6d3d13e2da1cff1b65598889f28c3d9b506d8bed4af2ed1fbae": struct{}{}, - "fad1db38143931e98bed397baa87e763ebfe0ca9b84fba053fe858865cc4f5e6": struct{}{}, - "eb17be4b2f8e36731b2012714034b407c6ee9bc8391bd6111fbb8963fdd06e14": struct{}{}, - "f994502faae57cee6d59878eb8347be60b0dc9601db1aeb6d2932ba9f106f49b": struct{}{}, - "014b5f09a295b8c2500a4c872d91fbf07ca10c41abb67caa5c65a8514e8493d3": struct{}{}, - "29e1a26d668d836e1a6b75951e65644d7221fb9504d7295cdf5b5550245e21e3": struct{}{}, - "f8af7be43b7f9cad5c60ce963cf6efa520a073e2bbc2e45429e0b91077946767": struct{}{}, - "a79bdba48355cb630ea810f09d265c73e824ea336ba228059dc6054a3727b38c": struct{}{}, - "470b2830637b690d3455246fbfd5c81fa61c41a047c2768f2aea4aaa823bc20f": struct{}{}, - "a4f4e73f0dba43ae3b2af24ae0f7fb05d0f66cd1420432517c362e2038747203": struct{}{}, - "9e02d06dd535a7663c0eb74ffe3c634122a35fb93707b31702cc33a5ef0b7b31": struct{}{}, - "1a486ca7fa306410cabcaa48b724cebaefb2b82045decb42f0c590d51d2fef77": struct{}{}, - "bdd152743777454199b07ea0c88e7cfd820778a38616aaf57d96ce03d828b1cb": struct{}{}, - "ef0c6254a5c81778e740859dd9c5cf80318b07407e7ab19542b9230388755e19": struct{}{}, - "d457c53250236b9ef897bfa4b4f8c7a03babb34c23f30849fe7e50b1e9afb993": struct{}{}, - "31a08718b0dc4ced61fcbdb09fe5ca88665ac87eb2cdba8338591b213be27328": struct{}{}, - "adb7d21bc107626a127b615230f0a941a319f57f6faeaf9db08a2b501ea17e02": struct{}{}, - "1046f316f427461a3751ee6a04db3ed3ae631fbe75bcf27aceab2639e8e60197": struct{}{}, - "8cc1e101ac1aaca232d169c9ffc319401ec9f35c60b0de3c53caa10731b789ff": struct{}{}, - "4c503ca67761e5c4aaecfe996244c25d8c0b40902d1085c85b4468bd567548c6": struct{}{}, - "184865a146c18d18fe95ab450e33ddb65a9e3d7da60ed7c7f00f57a47d1bfd00": struct{}{}, - "d97d93a8b39d005d0cab8acb6cc3c3e71cd1c20f23c086ba4a0c5007e4aa9b55": struct{}{}, - "d99399edd64b3d1eb0c784b90262ae483c176a9952ffbf5e8b4acb04470ebd16": struct{}{}, - "f5f19851b78e1330e28f7885ef47f08228a6269ad9adc3747dfddb3f74509eea": struct{}{}, - "bc13b3dc6e5fd06b9680bff85c16948779e71b3763487d5e493a5b6a9a53d9cb": struct{}{}, - "93506ae407f600d195fa5f231ac902fc4dd8f6a46ee0a347fb758938b4f91ef3": struct{}{}, - "b36708c7beec495a110a7b4191b7c59b57c2c97e09a3e495b398018cc31d2c9d": struct{}{}, - "6bdce79768a0305a36780ed71cb9a30231fe1c4ac98397ce4a8e661be0dbb0f7": struct{}{}, - "9fe6cbb9e933ad0b8b4fa94066474e091ee8be696c224b1c1678fcec5a1885cb": struct{}{}, - "000c5fcd977f6287071da73d505b60889221e4f851fcd0eeea8028fdb274560e": struct{}{}, - "1b86a7b376b02f28c5e122e456129ccd49e37f466137d83e7b787c29b87aa3b3": struct{}{}, - "9c79aeede8b1a2bf329f147daa6a2785a7234f2ec37fb131aa7d7dcfbe5ae019": struct{}{}, - "704b7acbacf9cd963218680c4709da2bff5a4020a234f835bb84f454275b1376": struct{}{}, - "1f578606eb0a178927deb51f7348e82acf8d711d369f710c7ac202c00d5ce9b8": struct{}{}, - "06ddd2e54a3ad4cbc96b0cd5a034e18b85d0f9be64e87dcb7a4c4a0470a6d722": struct{}{}, - "80a51911e171a132994c8ba34588a48a79f5db932e90f1fde0a1511824175948": struct{}{}, - "a406e8e720f54b25ac6c9625348aa8224e55fe1b8933e54182286bbef31e1fc7": struct{}{}, - "6f0e1ebdc02b8dcad339081aa5ca81d6dfeb28b1478dd973a58906ad906efcc3": struct{}{}, - "a2d7cd5aba4611fd646db71593d23e0d9758b24a82cc759a2ae0f6ef2c8e1724": struct{}{}, - "9f9d79ece6f5c956063beff2130631da40a37d050cb3813190b58abb17fec3db": struct{}{}, - "9dceb0fe987e68228937343a1fcd356df507e1b0d3ae79b15d76df028295bda3": struct{}{}, - "8acb4e2e50ca96d7130e8a2c878335315700c216cf4fa9d431558fd76fbb149f": struct{}{}, - "c341564d037fa65938bb59c0c696cd10c26c4b4d3af9d39ac0b6f1b309989cfc": struct{}{}, - "5c63149677f996191ec5857f4b452ee3d31c2190628d4252e7e428666d670450": struct{}{}, - "0aaa294068d312a150ceebca866ec3fdbc4cee49b9f17b9172cd7d016df6571f": struct{}{}, - "f0375a24653bb23fe0020a43d4766b0c742b347e245d853a0b3d31062161215a": struct{}{}, - "53c974608562d8deb326125d91feebb438be270d642b42cf3f937ca9e990338f": struct{}{}, - "4032ee4038775060258f8e68061309aed9bfdea5b574f24324ab7f065202d052": struct{}{}, - "7132d541ba31027e1ebc2b5d4460e01b42467d01cc7f938149e998357d3610a1": struct{}{}, - "72554bdc310ec07245bf1db1ac9870d9833830ef3f6425175c0e9b81689bc7bc": struct{}{}, - "79fbe4ba398c29cb7ceff4dbf63c3658e7589386d4be8e587686fbd738d038ba": struct{}{}, - "91f3b3c4b5bc4ac587e0838fa27b5637a65116629065d98a519226a60a200e8e": struct{}{}, - "e5d5b971139eefeb36d6edb9938fa246740c90da2003626487eb2d5d9646aec6": struct{}{}, - "7dcaf293b8d648d70ec6fc385678b25f96eb096fb8cdc194c11562797f0fe2f8": struct{}{}, - "bf1c9cd84337e5de5ebed1ca642997d7998cc5acc4a678f3c1d73873c9180ee9": struct{}{}, - "fd67586ce17773c31d8b68711e11fdc2c1c9b7e183e702b039f52498742b665e": struct{}{}, - "7fb52da155620b9187d31044726383122aff8e777bd885db3cd6fa44cec3a647": struct{}{}, - "072a626ed2d6cc7d5da0fcc388f3067b49c93bcd23b5109888ceefc4296f53b6": struct{}{}, - "960c7cf08d103197b9336164e50b5cf82dc6d638d7bea0eec7cc675f945f970b": struct{}{}, - "3f7963e4f7a8a5ab12bcbe633762d9b27dab50930d0a3ad53aabb9ff67cd1a34": struct{}{}, - "75e060d763398519f63463655ecc92ee645c8699da3cc6201b6093b9e81264a6": struct{}{}, - "1c45f6c7abc8cb3d379ed1cad8344a40aa4a5e8a2f615a480580160b33d0fd33": struct{}{}, - "4008294739162e8f0119134dd262e35165d4a3bda08f59f97b6fdb9c5b4e655d": struct{}{}, - "f1f45273c90c6fbb7b2e0d1367d23196c2375ed1be329081fc0c2af22e0970d4": struct{}{}, - "1776e45bf96c145a8e35a977e629c84e9f9b22cba982dd577234a091a83d0dde": struct{}{}, - "a4b785c6d774e9f97a38771b84bb40d127010870cef9ec6e76e7cdcc53931b6b": struct{}{}, - "f20ad607cd9100659ca52f69863347994e129fed0f061e4fb57418e9c1b5c7e5": struct{}{}, - "fc90a69f33f798f936f48d5ae70d470b85ab750a33379316a690dfd5a5141268": struct{}{}, - "0650df55259438e25f1665c0998dfa7fc55811ec5dd26a794489d9ed8415f632": struct{}{}, - "d2d6ed71c9b0769339a0388afa656c5c42c20a80f767c4bb43427ad0688d3112": struct{}{}, - "b04a508509a4b3cd0aa7a5a5824c3bce43e8512572cbc35506a255f6a56da2c5": struct{}{}, - "b4b597c714a8f49103da4dab0266af0ee0ae4f8575250a84855c3d76941cd422": struct{}{}, - "4532b70c3828bd648378ed5879c804b9af508bc64de0a290fb2bdef8b6f07c1c": struct{}{}, - "87afeaec6cb1e23ad5e6ff51f919ef108db191154f9a05738134685cd58652cc": struct{}{}, - "ab4ec62c3da85a92a6d9cf6bfd8434710cbffa5be132be3864747b568e7a8927": struct{}{}, - "9814cda0536031e8b6409a9f28cdbbcbf4d438358445854a60f9f8cbc817b63b": struct{}{}, - "33250e89df5d51c92a81a1178b61955b0831164863c763dd859680fe3b3d38bf": struct{}{}, - "16b33ba5785338bc9a64a69a3f51bd16eab6ff4d47b83794b20504138ecf2ed1": struct{}{}, - "d70a02385efd5aed9a06921924c23f4467da827d03513530f2ab1daf40cd69dd": struct{}{}, - "528d37b4bba6498667c77f56a6c4fcc5715b54d01a82da78f959b56baffea9d6": struct{}{}, - "83e4e82cfe202f1809f92b897b56c532990aa14f492a24cc6883a8cc27028fda": struct{}{}, - "8a851d40516708a2666093fa1b20149f57709a9997ed50e0cba3989799fadab1": struct{}{}, - "acf454ad706b1800746b6aa575a9cfa3c1917481f7f30f7fcc9705ff5cc6bae4": struct{}{}, - "741d912c0513334af1bc53cbf21b14a3d7c1e5febc7e605fbec1b7913a1aaa1c": struct{}{}, - "b54677b9e2604fe4886b89613ddfefeceb200e1cfaa0c547044c73ccb049ac23": struct{}{}, - "e1d9ce80c3abd825263ac199b82e1e29431d2b681312842a35cf8ee744911eaa": struct{}{}, - "df4865fca1f159162557359ef967f9502087f57527b0e030e139933e54f3061e": struct{}{}, - "2cec8cf0e321c284fa0c2ebef804aac18bf1cbb85546f89e7e3d0b6aa8b9d2cf": struct{}{}, - "a4374fffc0712f41606bb92bf1b5df72e2c27c4eb58baf8b0861c97758351c43": struct{}{}, - "d2ef30c919600e92ec815a92d0f7ab42a74891198ee3ee0fca61241e444bcd05": struct{}{}, - "0048a973df331dcc6fa69755734c1956f94f9183922e577dcfb808f7a7d496a9": struct{}{}, - "d0711972e2a8e2e4550b66b8ec91e385b250718783e97a33790ec674cebf551f": struct{}{}, - "dded9baf7ebfa56013c09afea41fd31f1e1ebd600f67eea824b3f7d00f7f6ab6": struct{}{}, - "df905058dd67a409fef81480e81e4736572b0edb4d7fb987c695cd07e260ec81": struct{}{}, - "105260ea5c192101f27a8cba63dac7434b6a0482215cf237c2d488ef1dedbd1f": struct{}{}, - "420af36a0af0280ab4ab182ac695cfa12a0303ab824d96755faf4ea31e90e808": struct{}{}, - "70e5a4e2d329e9812b7370bbbb9a044257f5f69f51b642c53f1097ca7d3fbd4b": struct{}{}, - "dd6b079f455bea52b8550b78cb5ae6c78651d3d1db5039b337354f1b53cbc8a0": struct{}{}, - "b2ff406480bc69534cb013d7710bfb8e44bfd3a5d355a23ada73f669a8edfd99": struct{}{}, - "bf6e6e94c661b9618140e624e55f9037a8ffe1cca3727ed0619a4cff871efb9a": struct{}{}, - "df5c4a68be30b0e3e79d1be2bb5c4fbd7984d163f93775f949eb783433389581": struct{}{}, - "4aeb7ad6d5d37a041c4c5ce6562bf9e3caf05a42d931cef4d9e2a60ca623194d": struct{}{}, - "c34ae04b892e736bd3b54da5d1a469184d2114e115c7ed9f3c9373204ca69b5a": struct{}{}, - "236e5fcfd21603c33b82ddd89bab7c428ed18e3830bf04af0ec7f80ea63124a7": struct{}{}, - "822a8e0aad6a68a99ee9db27651f1f6115414b7a772984b3f6609246ffbe3ef5": struct{}{}, - "17c8dfdcc4504c913242d151bf6289e2ded75d6ce9bf2b472b737479b7f24960": struct{}{}, - "9fbdd7f454308ee1ec0d3629d79df78789cbdda31a3c0ce592ea48869e9231a5": struct{}{}, - "efae85d0aa7cb7be142be8f280eb733e3cad2849d71bac4251db3ec0a4e39d60": struct{}{}, - "8683b93db7ba79c25f497a670da580bc58f188781cfa2dd21c915b422ef71ff3": struct{}{}, - "95abaaf7ecfc5ac57b71d405616047bf9565269135ee62e916b08dee79c3a617": struct{}{}, - "746bdbe39121f931644cbb18090722c25484be3f28d361c3a257c1a0057d5a79": struct{}{}, - "22d900fadd3b293e5de7baea5b2bed56c517ac6d59912a01ce3517ab5e9e333e": struct{}{}, - "175f3c4fdddbf9f116f88ae7575f80d2d941051d4b323b66237a5e05da1228e9": struct{}{}, - "0c6cc0a3c66e6456f18ed247c0c036bcb03ce4dda7a10453ef448fc30bf17e9d": struct{}{}, - "f2ed3fbe9d4f951d3305df37576a534ff9ea5f346d9970f1a23e952a658488df": struct{}{}, - "604265a8277aec2e3de6d0aa92bb1961c74634e861eafbf3f6fc5dad1821dbe7": struct{}{}, - "478b8c85b246bc2517856ae854cfda957d86dc36936eb3df4b49b1c39248317e": struct{}{}, - "ce2bf1f4ddd4bda4885b922f7d159ff58542940005df491ab2e1b028f4a674e6": struct{}{}, - "ed8557cd99d666728c7f7e53d38eada3da4336b42fb2ed14270507469e4bd4ba": struct{}{}, - "1bad01e6e3c41ea72e1ba8f263a1615862fc6d914ca595a44a7efe14d0b80310": struct{}{}, - "e16a9d4d1cf9ead81f53e096a680d278908d60574a8d036eda70baede61287b4": struct{}{}, - "a34467af68507b8a79501653055ddcea960689bba444e339acd557eea4ca2538": struct{}{}, - "ba08dad18ac84822bc25630acb53b7ef93888d4b7cc8edb9245c866f02bb4074": struct{}{}, - "a880c383e7df61eee2dc02bb6058fb117f8d8503e0ba955fb8fa77b0af9a9b0e": struct{}{}, - "6a0b6ae6a8339d8dcdc2c6617747e7113489be38bc71b02fb1f698da42159377": struct{}{}, - "16c515ebd61f45e56ec8e42c9938fc484e924214e4bd4a19454c26db631a8b13": struct{}{}, - "439003d0d54d022f61a705da700bff916414fdf8308f0cae6a5b9e5903e86fdf": struct{}{}, - "0b42376fdd72f80c7ad92fcc4e2d3183f32f259fa8e44403be844729601d0864": struct{}{}, - "8f40d29c6591983ace8fd1bf3b517cbe7290e2fc26d2cab47739510b40b75ea7": struct{}{}, - "79897f92c7b84a5c0b058faf5f3602382c554a1735a37de00ffa3d498436dcb8": struct{}{}, - "4157b6c6aa8e2991d76b830e3eb6fb972864748e82108ebadf66cdeeb0d4ae2d": struct{}{}, - "c37913e00294e5a49e7377a9b3dbeb55fd9095ac68ab541c48aa5d6e68364004": struct{}{}, - "0eb6d72419984d2d08e42e0fc033d90214e2d1d6ffcb3e1fa123a2dec7015f28": struct{}{}, - "a0d6969fa2e8571b0dfc300dac6a08968cee17521498f4cb3a4d0e1338a66463": struct{}{}, - "8d11df6037800a0d6a232cde50cd6420c0091d2eda433a684348ad0be911dc8c": struct{}{}, - "37fe96d90c22e0b3e2b9cefb547a11776fb3196e8a0208b455decab76443122c": struct{}{}, - "4a7e0eba622867c0f1253399557136754a1482dc27a7c93bb8ec57acb4df7cb9": struct{}{}, - "b04a8a40063584477f690200cb4262563f36068984ab4f53a51f26bf8e120215": struct{}{}, - "04532923faacec0d1b6f36a3524e9287eaed696d25f3fa1fa35edb0cf57973d2": struct{}{}, - "c80844f9479094972192f28803cb6683ffe29b27ffe9631723ef68b0e3cceed9": struct{}{}, - "3b8b36292dd155eaa5fe5920a982eca87ec9a431a9830b587ff8af06ae2035cc": struct{}{}, - "471b4fbbfbcb2cb8763008110c49f8b1b3175166096a4a2eff8074c068b2c746": struct{}{}, - "7cd51585e407b4ec83651916c6978c63e4f437b880f6bc63729717652285cd1a": struct{}{}, - "e65822f83a00fc74b79bd06086601d24edb07617abf1d89c14daf92c766a0994": struct{}{}, - "d722febbcc725bffc43891e31c190a62cafaab7c84d8b8818d173fd9cfa4adaa": struct{}{}, - "2ce109e9d0faf820b2434e166297934e6177b65ab9951dbc3e204cad4689b39c": struct{}{}, - "54862b4c3c6ae5018dcd68e2ffacbad035fd22b3fb26f7e02465848c03d43e3e": struct{}{}, - "cf1d5e4e94097839f701588ced090880a1a3fffb7369cdf96bcbaa9989046877": struct{}{}, - "4ad259ee020949994159041e596f7ba4d77162bb436facb2a6f624b080d57fe4": struct{}{}, - "355543f790a3b210ecf36c82a2738896d9574c17e963a63633febac1f2386737": struct{}{}, - "01385a34f62563c68d121a6969aa7b413d97b8786f3d708a5b42330e32756a67": struct{}{}, - "5e9562c786d9ca040985788bfdd906e5cdaa64560afe6ec2d5660449944972f9": struct{}{}, - "e54f34dc1c75ac3a1d4652f3bc7ff57fdd82403ccdae1e3d715ea320e846c7c8": struct{}{}, - "fca7a6920bbd62055954e3c0149b65c4d8b36a9eb5d354e29ddbb0252e760366": struct{}{}, - "8e230e4596c98e6986c496c9af39b58b1ff11f1d05aa4c0a22a914cf7f904895": struct{}{}, - "da154afd8c4e2d0d3d9a92aa3caf9489bc93de679cd1aa08abe7e08eaf658916": struct{}{}, - "bdd612cb3f85adad8992f9d9ff153f6f5f9ba6aa869ba613dfd37626157a4431": struct{}{}, - "249c47aa90f20e526222ff3caaf75174f594d7ac186d133bd12a078fd61c1c9e": struct{}{}, - "9e2d486d90ead863d5882154c8cf19b97507abc52c163aaaa4de05629c7557f2": struct{}{}, - "cfec4e4b0ca712823e121457740ccc5caa1cf0a80a5897af6e9983667fc0394a": struct{}{}, - "53f68b8687943513a70fcc6038b3347c8c34fd01d48672680ba0b06a8b0d3393": struct{}{}, - "8626f06455db65f23de82ed13b74a3ee4c925b2b0de000bbd15533369cc31fe7": struct{}{}, - "09881f6ed93360a2f6ad81f435a8ca51ca4575d0f954f197ff8f7d16c6565562": struct{}{}, - "38876cf8d4b27b6bd67373d5377b6aa025f8d650203d1f87ef1f03321fe7cd94": struct{}{}, - "8a2b58992ae609eb58c2813e65bf9142bcd25da31a2f51fde505e65a429e7f7b": struct{}{}, - "ad0d5e9ea49c64d351dab8126a7e55afc01db831017713a7b3f522cdd46a6431": struct{}{}, - "cde4e100efd26f5b02fb484e30db927732ca192f1cd9975186510adec82b5c61": struct{}{}, - "220654c76b257c0747f9020d9a62c7f978c8575a1caf65f357c9e7cf3cb2e89f": struct{}{}, - "372a40a5ed8f4c57e833a3743088d13adc2ba27809044ff365fbe03f2220813f": struct{}{}, - "b5fe0227c382783a6823086c43880d7e97ec32f5134ff0e6bffe920311e5df01": struct{}{}, - "e623c6146858700a971bed7bcc5962a6bd4387b579afc7613f0933b929040f50": struct{}{}, - "300cb2d7a5270ddfccd779f46c7bc9839db6f6cf6f08200be93407622d159954": struct{}{}, - "084251b7f3d192527be56ea95d916608125a106c8888bfa930d831074bb1d930": struct{}{}, - "3eea83de7dcd6473f4ed37e095f245abf6387c40afbf9dd08eb1dc6eb7466526": struct{}{}, - "3dfbf1fbdbf9d2eb39e14955dcfd073792ebed8c9b97995210e70c4059be13c9": struct{}{}, - "733a7b927ad349e830d11463123b7858885ce548413d64374566029ea6f9fe4f": struct{}{}, - "9634ef8b15110748c62e13a99747488b543d24f26ba66d7d40d8e28b588645d2": struct{}{}, - "3ea1e155ad46c87ddf3700ef2d3cbba2a4c6d2dd050ed3531835020302289a19": struct{}{}, - "89d47c1440229f0ad7d55d080ece40b004d238c5e39c497b75baf3857372de7d": struct{}{}, - "def031c1b53a07302f879f6fbfb38816ad8631fd3dab5a0b49d6a9533c81eb32": struct{}{}, - "6236954eb0980f3addf797382ba4867ef67e4dda12596b280a2053b69da72664": struct{}{}, - "720b0a44b5d8e9d48e7757e27bd33ab66af01c34ebc5c77f49ef68ae21de264f": struct{}{}, - "02dc62e9ad44870aee96d17b197e41ee506afbe47889a2f52b86bfeefc6af46d": struct{}{}, - "6f6afcc3b3b050e439968daca1baa9adad77d9557d9885932d6d4fafec999164": struct{}{}, - "dfa8a05dd092861b0375eac5022812e9e1fe9ec5688d766ed024e96639b2afe6": struct{}{}, - "3534411a803e97743196163fe81367d57b0c15b12d0f847662589d0dbf7a49b7": struct{}{}, - "333758f925f076566f033ffec9eaa84bfb1597334226ac5a135f856c15eb8522": struct{}{}, - "37ff20583960759de8bb0200d21522ae79d687f6abe680e82b486bdfe490e98a": struct{}{}, - "19bda4aa00eaf9a162fb92bafdc63160190c0c8387687bb8b23027e7bb886ff6": struct{}{}, - "3805bbdbd829ab1c7b18118e977aeb38b690c22976dffce0267996900ac6e94e": struct{}{}, - "82fc045e7c1be0cb0180655bfa96ffa8da30b9586e43972bad4c04e487141c93": struct{}{}, - "02e46f2e08a53c35a76363c7b4e2790d2f12cdc230b22c3a93815a0c624f810a": struct{}{}, - "a00e0b76e5293d5dac8490b977476eccbe7d32799c46e483477507e6133bc44f": struct{}{}, - "598bcf4b1504cecd237dfcc76bd7ea427d50f016d961456162be608402f88e7e": struct{}{}, - "fbb5a0ea9135737942534281418105e440b23376baa51e59e375f3a1a5c61963": struct{}{}, - "9a561869ec7bf881eca31767225e23bc6d52ded0b6b11d38f30632740a77937a": struct{}{}, - "3aefd1df36248d1f079ab8905cd125a59690b5743cd970ab48e901c59ed1a071": struct{}{}, - "6d0494137e3681eaa923f9909c98aa042a03e413bee025e9c0a85e1c4f09e168": struct{}{}, - "7d12ba56e9f8b3dc64f77c87318c4f37bc12cfbf1a37573cdf3e4fa683f20155": struct{}{}, - "ce7222df54996c3793f7833706274a96f3e9e1af5110e8e46112f2e85e3b3190": struct{}{}, - "5a80e4ba8e6d04259aafa4166f6bc6b1875ffd8396a632997a03e6dabfae12ad": struct{}{}, - "302ec21b9de9e115775702ac26b119fca39375cbdf57d9190768f20440a2b57f": struct{}{}, - "dc01f18e8f3296f4e02741339bdb9f01bfad5edd4ef8481908b2ddb7f705ccc5": struct{}{}, - "b1309af53c887ebad1147de5aa52daf667b4459d70477719e888041c2d654162": struct{}{}, - "4d040d3f6466018c568aadf69a666d4257e5bb350a8e06f7198aedb6b12386fe": struct{}{}, - "1c9e1296be56c12152303b3de7d5f622336b5f5208567b79b02beaea9e91415c": struct{}{}, - "1a46e6a68c37e8f969bab5d6fe6ce7b4deda2a17d80e1a7142e9c005b108f608": struct{}{}, - "1b82d42eb6c169c1ca5adb406bc4deb2fb8253e5a3c3630871e6620a6f9fec3e": struct{}{}, - "d182cfabaf2db480a38316eab984df61840f34cb0a054d468252c3db8d200cc5": struct{}{}, - "f2f2ee7a6f2519c9b403839bf29ce417dc249591184ae01523c4df3b01cd9673": struct{}{}, - "27a2069a8d5b864ff3d4b99309166a6808bb8457de3266d326602c3aa094acde": struct{}{}, - "e76bcfac6eec1a5520e055af046ba56b867ee08c736c2da70fcf913c8d0bfd34": struct{}{}, - "484b5c88097ec6a6a01908261b531b84e5e6030363ceef9cc069c364021d0a88": struct{}{}, - "ef94d492bb48fd3193d3baf6b86f5950f8040254f2dda7088ff42570584b02c3": struct{}{}, - "f021014960c5f61b68f18f5ec06e3d02982b069f2230cc120b6ca3061868d6e2": struct{}{}, - "3e7c3d4cf5be91eca62500b41e292c3474ecc0329156d11f682b609c42828df7": struct{}{}, - "d30f062eab20ace79ee14158eeb0303d545c61db4c77b0d0769198ca516524a0": struct{}{}, - "1f8b6d5a89242a0984816601b1a527ed239e16e213182481301d2706f4396703": struct{}{}, - "1b5d7ab3c89bf90770c3cd436dc0c5562168c17b4b62aa243d70f959a20bff09": struct{}{}, - "2bfbc118abcafe8c53ee6a46b79bed6638ae9eefda8dfd2d7948d02f40481739": struct{}{}, - "7059e7f5ec968283c3add6bcbdffc47aaa363938bae15a7a12c4c546aa49f9f1": struct{}{}, - "66c8708ff6af9d87b85d9f58dad086004606739e687190c8ee4eff525a4fd67f": struct{}{}, - "53423ff995a3fb0c40181ea5932b3642f28f36468c82cbdb25ca80565b8d6247": struct{}{}, - "8d67e924fc4d24e350b9c09b946f56c3b28a3d41608bb566bf661f2e472909e8": struct{}{}, - "fac44f8a6afc8e1ea4d2e951b5ff5b422eb929ea32090795647f67534a86b9ec": struct{}{}, - "f9eb325d0d132de4d5489c595bc5834e7f7b12cd6700bf6abbaeeaba315fb70d": struct{}{}, - "501d5acb4acc06d85d1fe1979a5d36b68f2becc069756164f763fc8bd275d0f0": struct{}{}, - "602fc5df23bb38a35dc5a2dcb6d2a450b3f8266ffd3d43778fec3aae7a977aff": struct{}{}, - "a7d3722ecddf7307bb4831b5ac6679d4b2fe7352b86432661b7f0fc27f9c4f39": struct{}{}, - "c54a061dfcc125cb160421b2680feaf6d65d938d756d887fe4d8d1d046eb626e": struct{}{}, - "879cf9d9196225f49cc11b6afbfe946304918fb2f378dba60419ada1d31ae074": struct{}{}, - "b829beb00d8b10587b3db42244fbfb9016a82083d555bc01527b857fa2ecb0af": struct{}{}, - "6bc6a8553bbe7c8d3f48eddeffc6c128f2e57b1404bc68371d2622ac003035b1": struct{}{}, - "4d8c605c3efb43ffdc55b9b5c9e140d336c1dec0a5ecb951c551eaa55b1fbe87": struct{}{}, - "48dd261bdff177e2c1d7540b1378118a65dbc9ea972caf2ccb523fb3c33cc716": struct{}{}, - "0b7c29a39a18672c71c000e4d3164ac653c70fb234f870f078786a5262821730": struct{}{}, - "d118e41faea4e000917a87021e4945508af18168985cb7d9dce1bba3ba390e43": struct{}{}, - "06f8337de015e7fe1e5fb7c555c693d72a16547d27741ccffd3cca1991f83157": struct{}{}, - "4d4b8c275cead943531cdfa7964e02e0357616549e70022e2fe8dedfe4325f19": struct{}{}, - "5515d70e7615d6ed0a92f74d18035368baf97bd1c95e0f0b22de64bb1f432153": struct{}{}, - "593b4deb436b8500f1f282a65ece860e49b98bd338d932d12e9c310553e14799": struct{}{}, - "a95db5b0ac159e4384ff55ef91c94a98dc563d66a88e7b027fcd5190c0f5bed5": struct{}{}, - "8b07ef6a0bcd1abbde5c5250fe87f261743a7f574c99afd24fd8592770122907": struct{}{}, - "ba88005eb3ce97ac75012f71e63624ce0641428498a941da706b9a969352f79c": struct{}{}, - "cd13584b1a785d6fd10020c6034be115001aea121baee06e6101a7c3e71cf8f8": struct{}{}, - "b759803bc6037a05e6564b6447a755b7f3862ba4d0d746785dbe133dcb6c8f4d": struct{}{}, - "53f4041ba0cc8ed13c5a119242af44e60105ea5e486e4e8e3153e770c09261bb": struct{}{}, - "095bd725ce5687fb57da31700bd214f1165170468a996a7a21071df91e5f9cfa": struct{}{}, - "4654d793972c3b6a1d48fb0ab58d9cb0de46c3d33d605f9222c283dfaa12d420": struct{}{}, - "a22bb54bf49789864c745eb8677fce9033a82301e5e89bc67be55b2644cff589": struct{}{}, - "391b43fc7d09f44b4bdada1cd085fb254b84551cb16f1029db025c271a708c50": struct{}{}, - "c200371d9be402177ef1cf8c135fe9c62ae76af9eb440da362fd99af8642cc5e": struct{}{}, - "890cabe271136403326f8252c59cfdd47160fa63fe7a37801d3bffc1dbbf03f3": struct{}{}, - "9eb6835cc95b053818b660bd9fc9f092a0b30cef89c1f0f17f469d5caa0ae617": struct{}{}, - "2618be5da8aefa55ea5834d506110cf6fab41a09236ffaa6798f8a1a83125a9c": struct{}{}, - "62643f85a5332af5464d07d122165e73a92259751b1faf1d205477707155ebfc": struct{}{}, - "e47dc0ee0db07c908ce040c3f91bac56f0a5a724bbc7483830a57155be3387d7": struct{}{}, - "4b97cf6789a5696221296aceca92331f69c2dafc9770a58e5041acf30320cde0": struct{}{}, - "87809f3076aeabbe06849a63adf31261844467c14cfa9fadb5bd42ab1d0000cd": struct{}{}, - "b9a16025518347233133c662283cb6c4bb33a0f9b8bf65e660429cbd168761bb": struct{}{}, - "e32126ec73e3a9592f405d731e6066810e2cdb87d6603b597e119d878d5bfdd7": struct{}{}, - "f1c072414387df13649fe3151d9b6c96ac1de1ea9dcf16e1c7b112d6daa9c8c1": struct{}{}, - "e476c3b751c1b313eba0a64ed18e4098749f4156e6ea836493bc9799b89c2066": struct{}{}, - "ed0318fbe31d7a0576fdff97fdbd70bb29761502bf0866f271bcb5847911935f": struct{}{}, - "ad3908bfb4def5185a26ef9d66b31ca86c02b570dd95b339678133970e32b32a": struct{}{}, - "7479396390a7de70f8ee7b6b218fdc0c87dda468ca555444fe594cc15655391c": struct{}{}, - "6ec999b52812240df2dba267693fbb86bbfb6d2103225bbdc9263718c8bbb59a": struct{}{}, - "bf038251c59b434b02c7279047808368f1ee0a6ee2ab98799158edb05b2eb4d6": struct{}{}, - "594cd78daf32565f1f17d3aaa6455f0a19a110496e8050655b4b23c4931bc23f": struct{}{}, - "3cd704ad37efedfea303623718cfb5387e433ad828f027eacef48a74125aa6fb": struct{}{}, - "d30bd6806eb58dd402979a127cea6cd49850b6c1fd7f883edfb080b8dcb4f758": struct{}{}, - "126e967da894c65737c8ebedd41940f9da7747f5a53578548248c6987937798d": struct{}{}, - "4d3402f531af953de08baeb7e24eda2cd597ccdee901081e390b41219f176927": struct{}{}, - "cd7e4d47cec828c888ca296704725b15ca700cb70db877fedcc60aff4bd9c14c": struct{}{}, - "5152790e278eb89039f8bfaa354b944ec1b44c5d3fc144edc5720c3edc045c73": struct{}{}, - "6a14b20875e59e35cae0cba72e8ca9ea937789970ce7ba2e3acbc5250af98b4b": struct{}{}, - "434df3759ffa3b595b4709676f9dcda43377195762c200bd57a3ab2db6923c7a": struct{}{}, - "7bbc5e0e115d0f3f62bc8d66db6b343583552d0099311496efcf09441691d138": struct{}{}, - "35d19710631a3c4016c183dab6b8f676daa3ae52acd2cc51d15101e1d7ed3c61": struct{}{}, - "29f006c8fea00ee58303e1d660279900967e7c86c31ae51f8f7c148f32f7f8f1": struct{}{}, - "6916cc8cca322a9f40e9d4d87e57cec1c37b9c26fdfa744f0e9706c21f13ce92": struct{}{}, - "771795917a4700741d27b968cf422b0985ba45e1d45937413135f579cb6aff02": struct{}{}, - "0e376c13c26b9a46256c487ea1f73a0cf71fa0e3f837692f1086c2971abf69b1": struct{}{}, - "6576d2bcdb889677c9cb93eb44c964898c5df5ecf5d00c63c05a864473721fca": struct{}{}, - "842996b63d5454ac176182a302bd2273cc7ebf04ee487b53fa7cea8079bdfdfd": struct{}{}, - "859def85c0471453032a6b401112c0f76a0fe391104388b385c309cddbbf8b34": struct{}{}, - "d461b9670cc0ee509c9e6f4de9e82da222d8dc370325276c464746c2b9af8aa2": struct{}{}, - "549ce58984289d8f03917ff58fa5b197809b7c4a6422d2095ad177175e8b3a04": struct{}{}, - "13779e0ed781a8124bb391b2ef13015ce2bbd98427a245527ba135038e94a101": struct{}{}, - "d7a727460641d8be3a3eed5312684ea2e2e9d59263c9a6d88f85a45666fd63fb": struct{}{}, - "15e6bb0c505b6a854416926300f95196aac51e168ee714039468d31e072488bc": struct{}{}, - "07fbe85f30e31cc7c1467cd2ff5d5f0e8097f96aff89ed66bbc83daecefaa5f8": struct{}{}, - "d2ce238bf4709ddee431fd65689be30c87ae0a0900929f7b3d4f46e67cabca48": struct{}{}, - "cd3b6010f4c837658f754bab36552f05b10a9074c997378e87f8364d49366cf6": struct{}{}, - "ace3e5f024e340557466fe329cfca47b5f534d1cdda1543ba7ca9a4616ea8d50": struct{}{}, - "1047d43777b00cca1165388e90d1b7a092b058fa3aaa4de6fc5ee44f7155d6a1": struct{}{}, - "39b61df651219c00f9a12461b8f552f3f8db979b7fc35d0fa075e7b6eaafa15e": struct{}{}, - "76ef0c9eb114389b78889a7f2a53ddc9f6cccb6373143d36610ffbb0fa47f1f8": struct{}{}, - "7a7801334db4cb75baa54b9a4d7c136212d3abe882abcb88de94e5615d71cb48": struct{}{}, - "dc1f68437c6fc4ab54da855326a832845672015ec2e2cc4e19b30870266fb6ab": struct{}{}, - "d523d77885ea1a720f6b9b36693378baaa903a4fe8bb015fef2d0beb4e372a8f": struct{}{}, - "d46bcc63d0618b41ece43421af6002b3d4e303a7201fc8a9b7d66c6cd252eb77": struct{}{}, - "d15779dd244a6d9837557682c1c21ab7383bb6c4af36fd4073d037edb667bca6": struct{}{}, - "9c03576aa3860ee73fd5a1653d99c76e54f3738f5ff313e9f676a59ca8fb70f1": struct{}{}, - "62b81e10f049a0b757e4249a2f8a6cf9139e9f7a5e031d4fdc78a829d0943b42": struct{}{}, - "62f2df82eae9f9b96d42bcda9ed2db7c58a27a6a37ff7d78446f33043c5a0775": struct{}{}, - "cddd23d79f3da07592e6b669cd8f58b1e21b6aef0ffd9441a024466065b83507": struct{}{}, - "cd07cf85660f7e8a028d4dfe1577c0d75d40d901b35667ab05cd685b1794b63f": struct{}{}, - "b3088b26d6a71356a247d00fa08d6764bc80961e293f7e117c48d2b885936501": struct{}{}, - "3003f08dc05324f05738950208877d908426c5ce068e9d1642058f4a3b9edb44": struct{}{}, - "6acd74e14a9309f388c4ef3d70aebd3c81c933699a2099551731ec052ea5a091": struct{}{}, - "d1990f97a1f4fe7b8e86da8c03c9dd3f7d536c14aa4a6f9eedab07f34cd961b0": struct{}{}, - "6996335ed9c15c2b2761b766d7a56c51982613d639a085561d236ba1a65a8680": struct{}{}, - "387668e22f8946c1a1dfa26267805821b7ff8d66efee9347c9a2a872f2863748": struct{}{}, - "7d2857159e3091222d89dc3a870f741fa522997ee31f6557fc29a7cd4387ddac": struct{}{}, - "6141c87ba58b61e73558cf7637efa0e3bd89f9762145ac22ab9170f147975be3": struct{}{}, - "65ba393b4c2ad1835847a9ac142de67c07225b01dc879e1c3d9912613fd4966f": struct{}{}, - "0ffe5a53f1b326a4dc497431046ed0f90586b1c44e61e7c02a01fa6461cc8aa9": struct{}{}, - "df63a1d9219fa26462f55d2ed51883c6a045cf95d4abceb37d91228e3279deea": struct{}{}, - "80da22bf26232eb13d5845b48307195e000292e7c2385cabda437e4e971bf1a5": struct{}{}, - "0ba3219b17e081b450176b09943a27477ec75b9bcec960b5327d115ffabc9929": struct{}{}, - "127c2817bd9e778721d5ec9c5ab29ce6aa4a7d04aedf57a7b65ec361903c910b": struct{}{}, - "fa6f8d1b63afb3283abc2d53ef6568a03bd05eea37dc5135f35ad739a8590c3f": struct{}{}, - "2c6ac23e4ffdf95f08f369eca6488b585bca0def0ddfe69f525e40d4aa2509d3": struct{}{}, - "3cba2caf9059740777762ede4576c49bc72ad3edcedac2a866bec7d279d6d106": struct{}{}, - "a74e16f3f75cdb0f01fc890e24549ea96dc3ac0d23d4fbc4eb0e7eab7cb6b7b2": struct{}{}, - "4c91feb4c123a608138e9bb7eb35f712b862e8a06fb0b3aa9fef4ba7cc5114b1": struct{}{}, - "f96c94f287038f69433b5a9a759654085e09564156ec90b1c8c754c8c001dc18": struct{}{}, - "d654b6cb576a4203a3d0b8dfda75b6a8cc292adc6685c3dcdc20fa683e2b74b2": struct{}{}, - "31480d16b65c31fc794b7ea6b9b7e510a641f598b28960cd4f4b31001961767b": struct{}{}, - "68fdc39e00b1be9ff160630b9cc2cbf7fab0a6996da6af1fd0a7b7d0944a223c": struct{}{}, - "9978c463676c2ea29e57c861171ee5548cc2f63d4fc30055132cf7a49ef5061c": struct{}{}, - "d0895176221f18f48dee01bc8b689cf5315ef411682c1db73f6379adda57fbbd": struct{}{}, - "011d5bf5158db0137554ca7c15bce8618f0075352878b60bfe7c69f7a7eac907": struct{}{}, - "882099af2b09373f44dfc974cacee3830f1ab7fd8954e732f4f07ed15cf754d9": struct{}{}, - "29d7a9e04047a19a8ed3a23095c12efede8252cf697408a06dd5ac82b6ed8cc8": struct{}{}, - "96fdc3a6acd274dd1607f4b1a172d3420056ee617617d867bc77c16559e34fa6": struct{}{}, - "365d8bbd01dfe481ce2ab473bcc959294dfbd5a3607489545839bb35c2b25f44": struct{}{}, - "0e2af86f10346d582ae8f9532f3136f0d6cb5e259832759b8c2960d85cc366b2": struct{}{}, - "4868170c03e259c20045146225dc04c9a31fe49cb76fc3cdc856c889cbacdd62": struct{}{}, - "5822e4c7671b82c1b740a65106b2427664005ba81f6e29a96fe87d01ea0af250": struct{}{}, - "24c5f5564184a4fb93ecb21ffe9b2a0eb54daf7a180f2d03db4e32ea69d2e45c": struct{}{}, - "14bfaa0fedc8ad5514998dfe9ce7c58567a2dae3953cce35dba3da72d5275fd6": struct{}{}, - "9ef22ea4c9318be2577360541dc844c0966f8c413b34db91eb9a18e7a07d94bf": struct{}{}, - "0deac0a26c0d846d7869c23130c6346c4040904a4134bb2f40a8c206a03748ed": struct{}{}, - "daf7c2f52f94c599fd6c432c258280a36daf3d21738b678285e9ab3bb9b40b0c": struct{}{}, - "37e70aca61dd778ec54223be4e8bf4baaa6fc45083482b3c91ecd591dcd2ae23": struct{}{}, - "fddc9a897e5d1c690f4ebe2fe8ae9cba24927aa3fbcb91946941c5ffb13ff815": struct{}{}, - "203e303a50269f3d533db9f46338da7d8e8d5457a0da2c74ef2c9ccf4784979a": struct{}{}, - "7f2023944f6f86291aec164a2e6c31ede520ad6440783cd6fd3cfbaf76f4f655": struct{}{}, - "77d52dc217c29a9a0091112c06950264255c9092c914198e93e8951c4f58217a": struct{}{}, - "945a233c73c8dd78e1ad65d0490956bd38254d46c5db4b2950d510d218e962bb": struct{}{}, - "b2f9f0bc0d02aa14fc9ad4c1578aae4d6d17bd600de847213589aea3711dda63": struct{}{}, - "766f75cf3c97e4bb4a1176217ca551c4943e5dbb8b517a478059a551e40ad638": struct{}{}, - "f251d1380602511b5c857af5053cc310c96e8108950c809a835eedef16301695": struct{}{}, - "7a116c5596c32da3b2f371f287e75722b7a4671b5ef623ddde7c6d805f1fbc2b": struct{}{}, - "c35fa625d91f9622f025ea38c925070eeff69f0b972700698fd6ad7d30d96628": struct{}{}, - "586a171ba04e69f17d8a9d609b9aa5ca11156aeac2787c46374c36419f4e4e70": struct{}{}, - "884c615372ab52e497889f28f3245185f454ae5ba127d289bdc0d2b385a0cba3": struct{}{}, - "5b16cf221b5799f5f536f493d03593fffddf2563473521ef257388383c7ad085": struct{}{}, - "b3aa5a34064756df6b629dc1a9071c84e55565efb09629d2986fcd0c418569ab": struct{}{}, - "c31a690b528b81ac710a80d6137285dbc96d2472ce04d31f444717ac72d38183": struct{}{}, - "74744f0df5ee17034e3a391aa6bc1dc4d0db08f108a178d0198b177255ddb0c3": struct{}{}, - "80d3cef335b26b1792fc3cc6cc6a7f567b33f6376fac53ac354dce0df66e4119": struct{}{}, - "c1622564bd30ed7585aa1f28320f92569308c7f1f44bb0e493f166abd59f89b9": struct{}{}, - "6ef068853dda8626a667a607f7187d71c254338d95aaaf564caa5516ec585bd8": struct{}{}, - "5da39305bad64a39f6e2b6c2f618d91a2df468bc8c60718065731509c024ca1c": struct{}{}, - "b46cff8f89b531aa0f4f711a3670b7be12861d9689c0ad888cbde95e3dd887eb": struct{}{}, - "45bd8195f239ab8b6ee3479dba6e90f21a4c1aea8a55952db0917d485f7a16b6": struct{}{}, - "2d2c85f6894ba760a68acd1a6169112aec29e2845bbf533fe3d513ad80c86bc6": struct{}{}, - "04e730296169e6947b9b20a48a37127b557bcc32f448bd101cedef7034abe567": struct{}{}, - "068471b85459adbc7085ea86e90db9a64b1d1316a726ce2f2f80e4238c3e362e": struct{}{}, - "e86421cb8ca428710814b0043f32a46d18d7caecc1a04620c34dce3057091dc7": struct{}{}, - "087d9bb8e9b83be5579431b1893ac163741cae631572ffdf8186dbf70d36e4fc": struct{}{}, - "f1bf9843dd12ff9c18f4b3963db35fe4463a1002808c5264cf86e42e02c1340f": struct{}{}, - "b5d7f583fe24ed18083a12136f2059abfba94dc89b44b7653c8a039a1c41f0b9": struct{}{}, - "dd32831e9d5f3da30c303bff83974931551409ef690c5233c3923923162c95eb": struct{}{}, - "4c2660d3131a49335b9d110395965f497c8d7b7a6b0fc94f2a59252e199ad939": struct{}{}, - "1c871ff0282e4dd946a6ba2fbb5b3893689b410f2fed8e5de774f6a3d3ada675": struct{}{}, - "5aca6f08b6780157a0da46aaaef9d86d2fe55919395b0e2aba71e52a377db90d": struct{}{}, - "7fe3f1b963d181e3634204ca1a631a344abadde3151f6465647049e2217347c7": struct{}{}, - "d9a5edfc653305b10b4d8a8d9b4d3b0dad070063342957748e9bb8f606ff5b06": struct{}{}, - "352a4f750446467c7aa84dab0f0d6aa496e3f3e27a970b3011fc0027a41ffc1b": struct{}{}, - "b6997d713f3e3a9c41be6b6e634d9f0ab9a088dbeef298af43049e6197e0676d": struct{}{}, - "3aca18d549f545fd9158ac7128e1ee5ef34658a7d01ca3b70c05808122aa5217": struct{}{}, - "7bf23fe6b0f253d0456c91fa266f2cd3389b585ecc74469d786ed5a6ba437597": struct{}{}, - "aa30aeff0721e2b7302511e164187cc3090d626062a4784934ad580d63e87e39": struct{}{}, - "d5e7d945ccf8829e9b18eb3ed3b720ca2bb6ff1931a58aaa9ce8253913ea4139": struct{}{}, - "fb07cc2f0cbb0723e6e6e5e4cc9027589923229c6ba6f01c05a563000f4b615e": struct{}{}, - "02843cf995bf500cd5ce0dcf03893eff90e2628fce86c83a595eae8f7349534c": struct{}{}, - "79765a0db68b17039b49fefc928648f96def636ae2fc6951754cbf6c2d609415": struct{}{}, - "04262c5bb693c65337ef8d6804cb6041fbbd0f08f7eb715f7f9de68b0ea82117": struct{}{}, - "ee7b09bf90561011bc18153f91694a77052af285adf22a3e02dacb6935ba2351": struct{}{}, - "945c600ba539c283edd4f1fe206a4479f1ea312b6a9a4e93c7199353fcf1aa72": struct{}{}, - "aab0f704daa6b3f4a7b9d1b2babb659fa15d314a76a103f115645263fab275a1": struct{}{}, - "aa3d15ad139267e56a8c94cd5b0af3b3b78ad92b6538d564d7a89136cfc5aebb": struct{}{}, - "8207918b59ab02edfb8cf3c26faabab11c81465cff5715201dbb28ea3a99bb42": struct{}{}, - "f6997950a4917e1897c32de78870b16512f77ebc9fc5b36d9a2093b13c4cf212": struct{}{}, - "4011393f356a69582a25dfd73603b974b88e7df6fec0b75793c6155c365e691c": struct{}{}, - "b041e6c71ab95953bea36d117da795d9de6cf162b9355404ce3cb3007d3fbb19": struct{}{}, - "35c442275925a58f5cae1182a264423bc54cb5f4f79d57a4e46027234ae8f1a4": struct{}{}, - "ad4ccd1cce20ff71773bffa3034858f8aff1033e4fd7e5660a0fa4d32fe29a44": struct{}{}, - "ad5495d23fc943a760c74d14a2cc65542029f18edd76292e4e71d43b4ef47416": struct{}{}, - "1181aeaa381b733560db33c8eb9b3196a78d75810b5668cf8ed651db39184283": struct{}{}, - "66b490dd60054469bdfe5352b0d28cf166ea4a2114cdd72241c02e1422c346ee": struct{}{}, - "fac2d7ca3187f4b7d90fe3c9d1f57f990921c8f65a7bbe19ea475ca733ff300c": struct{}{}, - "233c0ee4290408f134284baabcdec32a2ecd764237d24aefa4f6ed62ce3f9c56": struct{}{}, - "14f7dd77647b3e30f1d3076e45eb383d1c63f5bde98ef3e55be43ba1c42a2f30": struct{}{}, - "226399c852b659baca9341d5af78f22c29bef6b5a5e056c36c480f55a34458aa": struct{}{}, - "0f58b78bc54e0c3e55568ec522fb67be295f3bf726a0d53cef872b7a4272196e": struct{}{}, - "92f473809e5979a7da2b7f52771b3a9e3d7105dcb0f24ae333c5bf279b863d73": struct{}{}, - "94f38a5c5247cbb26c30157b97869aee13af44710c54e868d66b4313322b7bb7": struct{}{}, - "d32f293e971b0859eed0c77a0a8557fe21b78b39b0c4ea271d71383e2ea96228": struct{}{}, - "857689a99bb12c6277e299404e2c6c7c95fe4b14c9786b2fed30d7e27123f910": struct{}{}, - "863ad02631c2064bf317894d94c372e7ee574a935d5f87f5679f4e5e84fbc925": struct{}{}, - "581a73a3676a4e05fe3bbe3c2f215c4c2b8e0947866bba5b22a0b527748a7401": struct{}{}, - "16bb34d78e758137fb9eadd3bd87a6fd2cb462a0342cd0714370db80cf07c4f7": struct{}{}, - "65b45c559e27780c2cb5070b979b45f08b5c39c55b5de24fc5e09d7354071412": struct{}{}, - "4b12674966bead1e2d533eec73f6b864efa0824b46696370cae00266f499a205": struct{}{}, - "5cf4e1f8113db1bbf56a630869f4644f4be99d0be0d9f48ac73dad51ae4c3d02": struct{}{}, - "3bcc367a3488e113dca68b67e5fa262fe4fd2df48b1b72fd3292b30358911aab": struct{}{}, - "904749a4869bcaeb8d84609010015a24e9ea63d9ff3d059468b4974afd9ef50e": struct{}{}, - "8191850cc246f7cfa3a7486ce1a0580907e2079b5482f8008da677cfd70101a8": struct{}{}, - "95a8f6568fa7ad6aba1bdcd6620a471a0aca7d4c447f508a94ae4aab2ed86bd3": struct{}{}, - "b22521f307937e078bcd187b056c2f49aeffbc359023fa04aabaea9c3fa3b560": struct{}{}, - "6296200405a6b7ff9abc0eb20122db58967e5c6c66ba53061dc0b3beff6972cc": struct{}{}, - "2f8815810098c2ff75da3fca5322280f0f46a1091b4f80929fb92484f8c30e9c": struct{}{}, - "57a8282cd627547852954eab2c72c9978e3c052dd070ffcc940a2a7e3b969b0c": struct{}{}, - "64ac911ac6e21ab27ddbb57640353378e528c06f888971d0a9b56d725956afd1": struct{}{}, - "ee932b02fc14995ab1085f9c686b916d3dd5d21361ca4c20923965257334991d": struct{}{}, - "cb69775effd93fc34ef38dfbfcdc4c593b1a3d8e7ab70c0f05d627dbc5cbd298": struct{}{}, - "a13df1211cf4c38cdb7a165c67f8e5ab6fded2982c2cb1f55d4cefef880183d5": struct{}{}, - "6ad4a6b1e5ea5569795e516d71909e0ce4809d9dc983d2c219144f684f816e12": struct{}{}, - "04919c842a1399b0b77e55664598ac1b7016e687124ffd6a5b7dc728594ec635": struct{}{}, - "39c8b1026a897d90d560a6e139ed5ef01cfd29775bab176572b1c14df0efd361": struct{}{}, - "5877fb857ff71a23e6a9d4ac2de251491aa085ba6df067f9ac05ec16da7e0b27": struct{}{}, - "626e57b7df39b16edfd6ddb9db745c67df54230b6ed20dc60a0332739797db2a": struct{}{}, - "a8bd5dd6e487b5b676af753cbd7efe9251d2a1200ed4b9787e9a797449cc99ff": struct{}{}, - "f18356842c82eb30da98e5dffeada3598a51024579b651d765aa019cca52ff1b": struct{}{}, - "0c88794ffa8dbb1a87b1cf138fc7bfed530a79630e08842cc0a078e97b886611": struct{}{}, - "99a5511a354c096ee7c7873842c3cc4e809c6ed2724cbdb6e43b9aeed62eebff": struct{}{}, - "b361633c86bf58ea1fe4aee23ddaca2fdb7c41a5516e20a9bc55688a7d685423": struct{}{}, - "ff2a6eed1cca0e89975c16f80278045853ba39918ab588ac9306d31fc48b8a58": struct{}{}, - "13e4a576d4c4db42c71f3d6a30fb6f58c6d08cc54bc72e37c67db34721d8ab75": struct{}{}, - "c5268c1c7de095a00048869af2cc1a5a583ab558f8e5c63df890cccbbbd63623": struct{}{}, - "de7aafd5ba5fae52b12a53964137330a3479aca805c4c7dcf6bd2d2d724236e2": struct{}{}, - "2eb20bb48051599f3c40c841e224bfe8f69bc11ccfaeee28e8808c1d132cf351": struct{}{}, - "d0b4ba2311b3e84977dc4bb320fe72cd85426f5cf14b487a4c5352ae3ad8cb37": struct{}{}, - "c63e8734779f013f9cbbb5552b7e78e5198b325e77e32591bd538f5dd4406005": struct{}{}, - "6bad216401ccd90f20509c5c233c4927c031ac0e6d94d1bde4d04fd1f356a083": struct{}{}, - "c051acee1dd566308c9000b5b4e4b241cd9c94b61634b2c9824f44b2c9b8f906": struct{}{}, - "29cd981177edeb52bca13153bbbba53bb9bddb7789e1d31429a1c1a443f8aca8": struct{}{}, - "c6de3c105315372cbbc427cb3a96544cb9edc0f91b557deccfe10442fad08854": struct{}{}, - "42681006e6894ee6de06cd027ef8e34d357ac0b2225b8849499d541253628e7e": struct{}{}, - "9c35b425f251667dba79f189e990538644b9132e6da3d003979391e3c0f64c7e": struct{}{}, - "783b79b03e2e22042c3fdd617550a37ba194122d4352a906ce564a439a46b848": struct{}{}, - "92e094ab2d7d0020ac70b5d2ae4953b0280d85ccfe41f0401ad42379f56110b0": struct{}{}, - "f36b8e41e48be33a501793dc5549116da77091909457c18714eab3c685262f78": struct{}{}, - "1ee6638d8d4b85febf5ba9aff8894039ad741e5177bfaee3824b1bb4cef6a752": struct{}{}, - "2f429fc846ccaba33a2d7e5a94e97a7eb9acbc7fcc47637afc4502da7c1c01e9": struct{}{}, - "7aee179642b240979f771d24d4ee7d66e768824c8032839aac6be2eaa5279687": struct{}{}, - "24b1d70291ed8a638da8bb303d24b8c069ceb507c2d699466fcb8afbb1942f04": struct{}{}, - "2fab4f6d0be8b4a21cc4592a4011921d60e68a1f284f18f1857e91dd04cbb2f8": struct{}{}, - "ab92ccaa06277a70ea8518b41de1fcd26dee38d36cc455e68beedb4cb4dcde5d": struct{}{}, - "b63e27a4ecc6cc4c3c7e0257ea68f96d61321cdee4dfc3f86225644c0feeda89": struct{}{}, - "f84cf1c5a4e5436b88c50f7ae39ebae74b8ba9b8283ca5971fb1d74b667cc944": struct{}{}, - "e3ce914bbf6e838bff857279ca532a5992439ac8cfc8684e376a847fc640e171": struct{}{}, - "062d5d619f2aebe204c136a6898de0ab08205cc32178bd2de2ed93cc89ede399": struct{}{}, - "5beac27ce6911e4d3db76fcd1cd73a231af9d3bc3009c82f61929a2b7fdd5acd": struct{}{}, - "09b44a4a583dd2d18eafbcd6f526886f36cbd10a226661ffa4a5c90ef5c6df10": struct{}{}, - "8eac6f0fb584f3d6f364e312290216d530cd068c62497099b80bb34386d3f658": struct{}{}, - "81ea24e895ec1e9ec08a8763a773b4253e1bc682bea01cb2a4e99b81e866c97d": struct{}{}, - "9e756deb4a93cc81cc295ca40fd4945a7b89d0504820fbbc2cd18fa1cb14a807": struct{}{}, - "6af308d5ad701d4b9fe0b567c27867550d2bff29e140d82c82ed1ec48c66acfa": struct{}{}, - "68693d02ab4fbb2331b8cc39915322e48e61f06d4d1b31e7d19913202857bc8a": struct{}{}, - "f905b19542ed08c9a9c26543cca32e5711d207dcffb81b4cdb44ce0b989431c9": struct{}{}, - "2fd707138ef055a60b30e96cc341cc9aa8e73d9fe94648d1f3629e799945708f": struct{}{}, - "914cb64a1bfac4c6f9507c9d26b81ed91757fa051e3f8508140053834d1dda81": struct{}{}, - "ae4863bdae026314d5994730ad24730434f256c274d8431c53ed537393992be6": struct{}{}, - "8d0133e8023a8d3145d605e046ce8760016df729e0e57e4d538241886c259276": struct{}{}, - "484f4c1577130fdb27d8c586d3033e777695750da8bbd3d4f9c592e60152a426": struct{}{}, - "861d9cb74842e51ce466d4159e6a23e1ee70b06bde528f781ea356d8f9efea43": struct{}{}, - "73ca0765d048f0b00a775f8097331a2e1eb9056d0555ea91016375a1be637acf": struct{}{}, - "3a43dba9f151c9a3236f7bb1d21ad2de07fd4f659ffff36bb17e939c412b91a9": struct{}{}, - "94785951085bb6fe59e2f3a9a9a3864ecca14caf9b5429413d7edba3a5461ea6": struct{}{}, - "a8792157cb4f27fb949c035f45518c61e884bb86e6f420204379c2baa8beb66e": struct{}{}, - "b6f8865ed219a3370fb6b651873838f79592ec135beee4a8d22515630d5ff355": struct{}{}, - "5e6a032bb3962a94614cf1e5dd7cd954cc33a06627019db3a476a788854c1d02": struct{}{}, - "09aabcd859e4a72f92230e07e6fa14a1848af2f98d4d0e85f18ea4a697115621": struct{}{}, - "2b82366f689ee4740df6cc63e605e87555e190339e689369be0a85279bb3b5f7": struct{}{}, - "14ce8818f774964073bbcea21792d21256eae6fba95470263323b0777c88b70e": struct{}{}, - "f8478b4a74d8808426508ebc6d2acd027400db8b482f88ee408a8f74785fd42c": struct{}{}, - "5c1f24b0bfe2b3cfb056f1450fde6c0e13d9d541dd7d38c16e0493ffe5001716": struct{}{}, - "fd3aaa3b66ef9ee1013c6a368843653b322b8257c43149d979e44b82acd09810": struct{}{}, - "00167fb910e707b50b3efd003043396a77d13bde463502caba444c9d04d26840": struct{}{}, - "12246ef4801bc0ca82cb07c2f0cddf15cb991cb5c61ba7d56c869a9fc2f55fa7": struct{}{}, - "b8206037e8e711b6f1e2a31b8d02377b1574e3c9781eb24b012e761740153c5e": struct{}{}, - "e22115b5d76640e2389bcac25c46a2df03b2df657c5b3ff32cbaed141f8e8ef0": struct{}{}, - "4ab1335c8c8c583f5c8fe43c327c4d9af0f3e39712e4192cc6086588210afe96": struct{}{}, - "70de46cdfd4c62183a5698b5f97b47f40b139d9a9ccaea8dc1f651898af35a62": struct{}{}, - "983487d9c4b7451b0e7d282114470d3a0ad50dc5e554971a4d1cda04acde670b": struct{}{}, - "0e895c323cc786e17547125a4548680990d22ee9459561804dfc6f413d30618a": struct{}{}, - "beb3c8c57f1e6122b3655510dd2962df081413007e88bb617115c58fd9ddfeee": struct{}{}, - "1505e1f8fa878cdc44dec9aded681d7891b63a28249dccd2180a96359a92658d": struct{}{}, - "fa8847b0c33183273f5945508b31c3208a9e4ece58ca47233a05628d8dba3799": struct{}{}, - "30e38f0fd88485dac55095876e548d629538552af32bbd56dfdb6c7a16f8bcfd": struct{}{}, - "f7786e1f40e8a5784dede1af2f6fa0ff0853cab9eb837d1ebbb5f59462a6c5fa": struct{}{}, - "5712da3f7f5663a945102176aa91c490e783edd67722c86cbf8232c9559eaa7b": struct{}{}, - "264a3096926e9f39dc52cd384895351324a02dbca5fe8894c927c1e94e9911ca": struct{}{}, - "86d0917177d9cf5fb859b81005f650eb9a3be0d5b1a66df60aea9aee416bc03d": struct{}{}, - "7a62c1187b4217e70a6661e2182c744a5d8cd555f5f5d5d66ff5fdcadeeda0d7": struct{}{}, - "4ed28f82ca96c3342a5c591668270f78c26dae22fd953772a6c14e52f46e1b22": struct{}{}, - "b53bcf919f24746d72094b8b86c0325cf40723d3d63240705db51a27b261bc96": struct{}{}, - "97502919b941765dbd0bea4c668f0882c9a9253f5c0b5c5f6fe3ace185bb45ca": struct{}{}, - "a50dd9147d6ef0e564adf29f08c81ebea61892eb04c720585defef8e95ff0e69": struct{}{}, - "d5956544db0a3bd21e54798f49204e5fd56f95412cb598054ae15800daf45353": struct{}{}, - "992845fdb405f259e18a0a7f97caa8a6d2b78d697879351bf37968aa6a3f8729": struct{}{}, - "2659a0bc8b4d9fb2d9fcf70ebfad910fc5636be1d09e763539f34135cbc9cfd8": struct{}{}, - "dae16a865a6af1a8ae5f5d2f95399725f0f93236213b60adcb6d9907c4f147b4": struct{}{}, - "6015993a383253de605d98bf0a1b8e3482eea7e83a81873a303f181340df12f4": struct{}{}, - "a5483c4d42217a371f569700dc603ac7ab58a3db8c4235058e19a13e098ebfb5": struct{}{}, - "8172f9d467171ec8fe95401df3ed1800bc1aa0e89428b5b3a390477893d08a96": struct{}{}, - "1a27e59cd68d6a964b3c48f6a6beaaaaf42d19472020e691ee9c224cdd51e72a": struct{}{}, - "4db9abc324c6a4834cab4ec86047449ea8a81b9d2030ad7561229816b778b8bf": struct{}{}, - "66c97afa55b869ec20d3a751ef4fe4eca34abf78c6f2e80538ec187962b87af2": struct{}{}, - "1b7ad79da423256102b941783e9f5191556ffbcea13e47b14e4112cb584194c6": struct{}{}, - "a926ca156b77ada4a2c4bba622a36599d678eb2571a3fdab5443134683cfbe18": struct{}{}, - "91a65fd09f915ed6f054aedd78902fa1a1090b9ea4f39e6411927cf78cc9db7f": struct{}{}, - "566f0bda0c3de29ca551ff9bf30fdaf0a1c94608c4b6d959113bf64d5be0f309": struct{}{}, - "092c79e8f80e559e404bcf660c48f3522b67aba9ff1484b0367e1a4ddef7431d": struct{}{}, - "e35fbb1a86bcd6f6736a4e44e9ebe408465f0d28fba23421191a9ea64168fac5": struct{}{}, - "6bc535ba1cf236cbfd409902b0d2837630f2740fb2b610f1d059493e0a207440": struct{}{}, - "e2bf78cd0e91483b8dc38581b3f14c486e1d2041d08bbac27b49a2eb16a09a05": struct{}{}, - "4e04d109a3a7a0460ad2dfd95a4f0faa145f3249bee9f371f8204d16c01d4921": struct{}{}, - "1c48f6310f1c849b4ac338d340ee667bf3b6d59b589348eaa2b8a0698ff73481": struct{}{}, - "b3c61473a4feac3d9f8d6f5719c864652ef891efb71eeb38c56be3f586f0c611": struct{}{}, - "7cbd75a33e3f9ceba58eab97dd18cd7866d398f3d56542d64e80a369a36dadab": struct{}{}, - "13f7eac6f16ca900cd528d082ccc876022b4759a7c4482430c1b498ebb9dea83": struct{}{}, - "ae76b97e0e534be303bff3f2dc85f30c34f1926e8583e0d7d964ec071f45de38": struct{}{}, - "ca9066873bf335f3c1d9f0f6986266ee50689a816763851d82791d3dbc31cbf5": struct{}{}, - "554f4d3dafc80844871aa1654e0a7af8c15ff07f5b6a53b5a7de79324747c6dc": struct{}{}, - "1ee4e10c0c7e1bd5ff9cd70d1f3c03521abf21ea7ee45aa92deab16f87af4dbb": struct{}{}, - "0e0637a040574ef3d4ac1a30e6f76140d8de69ff0525d12bebd0ce771a72602c": struct{}{}, - "7379ca163bd6115241326379ee41fdb173f5fe307f013efa8ae0cc1f3b351d81": struct{}{}, - "9411801c10b8294bcb0b7f801333d0ce666774fae7f76c548b28db87d358fde3": struct{}{}, - "587e2210f9e7b36291503cf0b151a9bd183a095c4bddeed26bad9cd24a9a53ed": struct{}{}, - "fd46b683b2ed0c334f7f54d6907874936b995645cbeec90f3ed5303cb6ff4800": struct{}{}, - "311ef643e9da51eee96d86ae86d99fa3bcf0df9dcecea43c615c3a1f275f5b9c": struct{}{}, - "239be79e62f83882850ae97c7233dcaf6ea6779ff9f1559789fabbc299e2295c": struct{}{}, - "aed3af5d61a4a9c52d13ed0b49c72b6e261060fede11dc75b19d7b7d96dc18e4": struct{}{}, - "a7d7c16d185a6aea76c54d9167c535e8de83b5304f6348958f4ba3b8ca969676": struct{}{}, - "03b3dfea2ba0a85375cd507cb1a4df2bbf5cf671ede2bebaec7a3da44ec25bb9": struct{}{}, - "2c4cc05a6f4ddb509a8ca7b3a0376064e3dbffecc22723cce69d591dc7fe3d6b": struct{}{}, - "0a5d59d1e826a9fec485bfdd01186c9ed93b8b2fcaf898ec6c826a44bcbe6ff2": struct{}{}, - "2efb1047074f7a387fa60c82d2b05bc742cfaf8163ccbb2012cb61108f87fa4f": struct{}{}, - "b15adaf1e514e85af9c4586191f89f6eef4d45e0555d96be7e46b9d059d4227d": struct{}{}, - "71a83a978d54fcb62729849269b92aa5c8dd0fe6f9c8938f7fa8ff37592bd2c9": struct{}{}, - "4e5db9c493f96c85ca253df5709db48a4cf06b1b7b927b7b07078d29e6822602": struct{}{}, - "ab353b59945598934370f3345012c65f6c9b5d627bc75199e2b47b35abc37531": struct{}{}, - "6adf050cef6ab76be338a271a2caaeb9ea46573b72d5a6244439c114a940e28d": struct{}{}, - "3d3115511689431e8a6f19fe9742255c6a2d03ac19dd55155af73d765776f97b": struct{}{}, - "8d51addd113608ec37781c46eb2aad4d81cc6ccab48fd047410f098fe45e97f0": struct{}{}, - "889bc9c21f29dadf1ec66def76715e84329ec7d6387429b7b410e17c76770662": struct{}{}, - "4b5bf9adc2208877caecd323b2a514a67e05227c8bce1ecada4b589558aafefb": struct{}{}, - "f651106d90008d408b43c15c526ecf28061dbb08fd47397c8e7f3fb527df99be": struct{}{}, - "baab7b208fc2485a90a49196cee142f8265099ff8e6b409de96ac6cd4464f1fe": struct{}{}, - "2527294051a3b952be88c93b719d45f6c1dbd27a7200ddd85ddbd76142cf3eef": struct{}{}, - "8854758490549dc797466edf9f640aeba9fc8d7676b77cab2cfe65d6068a7f42": struct{}{}, - "2979b98a1040270fde88dbc7c499454b8f76d7a70fcf28745725d2eaef84fe9e": struct{}{}, - "b751bfb654535a5e776df812b14d17f54452e368ad61685b6ebfede2e5b3a879": struct{}{}, - "4a973ffe49f9e346895238eb85fa3801bbdf76d7ff0bab1d765d20caa094d162": struct{}{}, - "e2e48a7c679247306eeb2dd8f92248b4c9ee2957e7db903b1841754f86358fbf": struct{}{}, - "a4c18ee0ada59e343691ef4ddc0e502b86679f2eaa6a5576a0ec3c9ea0658e36": struct{}{}, - "ceff8a27cfac23e71a74dea15acc027a84a0475774df45aafe5128ec953f4a7b": struct{}{}, - "4310884a61489f48543a982d7658577afed8543ecc21cc095ff954b101ec206c": struct{}{}, - "1ecc006a666a184a5b7240cfe7d0f309d7af5207d759360d52a6b12a8922d187": struct{}{}, - "19071d7a72c9c0f2ea8b04937a7a926ba5031883a6337c8e7fd50743542fef62": struct{}{}, - "38fd40b6cef5fe2cc9819d9678860c20bf624831e11a9f3e4eb2d2d0db37d684": struct{}{}, - "a6fcba40fce82b7dba0297dd8692cdc9b7a13be29a23966f24d1720a7f7fd540": struct{}{}, - "397d9cc0208433d899e10901947422b8777ef742ce220b4f9d87be61127f8f99": struct{}{}, - "65e525f5dddf6b8c6445d7bd0583a219594c0a092ba59a6e00bdacb18ef8e5db": struct{}{}, - "50b697a069acc6c4ee4efac5c27d0207c6f5a5948388ed4a2e96fdca0d267181": struct{}{}, - "d3252eb6eb5a8274ed56226e27e16b3b4e37423ced2c8784f59d87383f36428c": struct{}{}, - "dcd6a27e6bb63457737a32a4a82d2d316812a98c47beb779add852e1c0efcf49": struct{}{}, - "af46ed271e08e379224e938e28c63d38d33253d9725d22f571332202d1a78684": struct{}{}, - "01de2db49f11a27e581260faf7b3f47b74059bac1fd68953415026f0c24327f5": struct{}{}, - "e14d95898d63930a5897a698e41ae883b45e2c1f15c1b63e0f9da3971b26ab62": struct{}{}, - "6e84cef3835a51f334a6d4f637f22766c1474a884869304a23929bd74dbf153e": struct{}{}, - "94c47d91f1bebd7b85fb5b4210cb24a8125beaf843e6e4a7a80698817c79ae64": struct{}{}, - "eaf2067e34d6876930d2b304db688ab6b9d5064b7e682f7831297d39fbe01c92": struct{}{}, - "d74fe97872d8a425b5263add13d51a1066bf0b2cdd5e368d316dfe31048b2104": struct{}{}, - "1c053d3970411ca6bf88c28c07c635079acad2c969baf0ecccf3f53918320eb9": struct{}{}, - "1937c2d0cf823834b2688f2f9e0e987dbb418f50e50ade55a18ad5fdfe8ee4a5": struct{}{}, - "0baf38f5da148c7a98b362afba5da1eb781a443f521ed25b049fe34d4ce5edd2": struct{}{}, - "e58608fecbb970c81042632378b37238ce1865e6d1fe81652d11f663c319a5e0": struct{}{}, - "cc2d6b5ca25bb33218e9b6d05bc3a9fbc4f0be442b2eeaaca2cd7e44e6eecc2d": struct{}{}, - "fbb7b5bf4ca348eeeebf8cc3660712859a5a1fbf18e83217a57ce203fb51d5c2": struct{}{}, - "5ac694d1ff431b912bbf30fb672e34edd090c43e4e5a27a6b423bf12a562b1bd": struct{}{}, - "71fce443333775896c1d0c48d181ad4fe99221433c0a2210dea227c48315b33b": struct{}{}, - "0b88f58ef3bc5bf9c1db2901d15838cb183e7c93ceb0fb3d976c292301088dbc": struct{}{}, - "35b9592f532369ad6106a9f75cc76b0afb34050dc2e18cf6748d23e500aafc77": struct{}{}, - "931f22c9283e090b805bb80726169a730cf80971d087fdc22d35a962fa40aa45": struct{}{}, - "298bab1136dcde8c0157190fa5374cbf36c33f79b13a7597da8027c5afe8dc31": struct{}{}, - "a9d4034da07d8ef31db1cd4119b6a4552fdfbd19787e2848e71c8ee3b47703a7": struct{}{}, - "35ba2145dbc31f2201241e1b7d1c951d24abac8acc864775a75288f53bbf2500": struct{}{}, - "01b7fa936df05d8c9a21d643be12e12d553870d2c16f811207a54ea6c2eb4f5e": struct{}{}, - "0f681085d42bb0faac1d0a9444abc8dd07b9358926275a727c4c63b991a1f63c": struct{}{}, - "8def555f97191696039c6a926696c5c6648a1a32021bb8deeac2ded24b1685b6": struct{}{}, - "5f8039ae49e42ae0f136ef26852b8a6c9e99bd29aa49f96763a04f0eea4efc00": struct{}{}, - "652f55016243bf1b9f1bbea46d5749ef892dbe394e46de9d66ab1aacf0b4af57": struct{}{}, - "7bd860cb96d64e8d5d6d13f19ea7abec4d7fa442cb708064767191ac27bb9c1b": struct{}{}, - "d0607f7ad2628b2af9158dfba06ce87166e66b15bf68f8f358f9aa27ccb7c321": struct{}{}, - "653fc7600244b6904e1e8c5dcf0b22f427de58a4836603865341efe25d04a708": struct{}{}, - "8ec98ed17b2fba4b6065111ab34427993f210e878414df557b31b443aacaa327": struct{}{}, - "724ad23ddd2d9ca20c29210acec877c95b607a5e9a5c9ffb10a46d3060506227": struct{}{}, - "8346e21b8f4c409693853b632ac30c016fd7231bf27fcffc6d112a2fd39f3178": struct{}{}, - "68de2e79d41518e4393c7e7eb0084f8a5a5f77ccd78ddda3c6775f9c1dbbb992": struct{}{}, - "c7f26212fd5bef5fe7143b6a0db123372781df6d840d836943a15e73cd4906c6": struct{}{}, - "90f0528c6435cc197997a13bf5b8e5f8ba0b8db20f9a55cfbc9172d959fdf616": struct{}{}, - "3023afa24c006eccc5319e2733eddda2dc4ba0e9c74bdef9be6f261d121c62fd": struct{}{}, - "8d0c33219b8b45328a37d1fc82fdc2656a88499fecb407804791613d230efbbd": struct{}{}, - "360a977a55dc6c8423eb34be96caf526e19efcd1b361b02aa1494abec2686fce": struct{}{}, - "1cd4b732b315669e9d562b8651b223e2ad113143d2ec736c34b6840ca6630492": struct{}{}, - "3b764e721ed80969368487a6e4f054aaa5f5ffd39a7346671cbda1dab65a041f": struct{}{}, - "6a9a2088a8458670fbd59e05541e0180979bd15389826d7d856652421c55a5e4": struct{}{}, - "01b70648e24445f4dd61e6eb3ffbc60605d7ae42cfb24011509dd2e8a1194377": struct{}{}, - "e462aa653afd945987dc9d7bba49277c1c54113de918051ae988af3cd08239cd": struct{}{}, - "f0f668bf610e5cf9f11d2aa50f924ada10797100e8f05de085842ddb6370a867": struct{}{}, - "65bdce16bf48c947dac5146cc1255e1df90fa88c3526a1f07ea9fac3dd847613": struct{}{}, - "35cba23f55109c547e1f253a2129d1ac60aa21f17649cc064070e72641fb7c7a": struct{}{}, - "426bd089d92ad0b1a7f80e0a36ab5c4e66ccb74fa7690292797dadd0509c1f88": struct{}{}, - "1d866a12269c7a76d5f599f677faa1438b9b39ce2e4a40578ffe98574e610dee": struct{}{}, - "1416d15d77ecee65f8ba83dd4c4f2e5f6acc0cb46059c166c8d06ce5b0052a06": struct{}{}, - "a644175a6128381a2d021b8a83db6b6ec29b90d2663257c02857335d0328435d": struct{}{}, - "41e302daf09f8f00dd97ee9a413056788d27c45f044aa364cd2bf2253c1123bc": struct{}{}, - "2f63371bea3c61d9fdba4469984bd22f2cc2381d23e031634f0387bdd97bd28f": struct{}{}, - "5633c9b8af6d089859afcbec42fdc03f8c407aaba9668218b433bd4959911465": struct{}{}, - "33c594e4e36529842cb1344043ec59e9f4d026466fd7ba0112a635fbe30baf3e": struct{}{}, - "7e050c6a300ab8c91c7cb2c6d355b3d7f7d81de7a88aaa66cf841dd5da570fb8": struct{}{}, - "12e27fe0a0b3646f447040307dc3c2bba16e9afc449f48e59d534ff3b88eccaa": struct{}{}, - "64195825354ce73eb386137b5405b16c12367fa017ab710e2673e49cfb8b4804": struct{}{}, - "45c4f606718232fb3c5733108c7dd589a94af7f27e9b7736a6ded127d361e006": struct{}{}, - "d5b7b23e6461293a8a2b4e12c8dfbfa394e39f091f567b11f66b9baa3fc8e338": struct{}{}, - "0b203c464bb32a27e8621a5cfcfae0f86753a7d8881006de7d64090ad5785214": struct{}{}, - "2742c40394c0e21777b4fc4c7940c9c4671695bda35d6b67708488e4fbeef926": struct{}{}, - "adba6c0ec8a8d89efb03de642427a09302fa4f7474989ecf33fd545a30d2ff5b": struct{}{}, - "b2d9925a7e65f161ef369c46b7280750a84c3e77f1c01b59e9b124f0cea9b27f": struct{}{}, - "167036d038074e518e71bd6f582dc7585dbb7eb5d3b7dfab645f12438b281455": struct{}{}, - "c71758a30a821e220d7f441eec57f6d7347183186cacb7f41f55a33a522dc64e": struct{}{}, - "b39419ef79491b8efa0ae699a656fd976430fc16a7917f5bfcc9ce8fc6a8eff2": struct{}{}, - "77de6f01e5a77eefcd79e6bc4a636c996bfeebc1539dd65a16662ff34e90fccc": struct{}{}, - "f62b604cb677322aa6b7768b05fb8a443954eebb95e2e2c6f9d6b348fc922cbd": struct{}{}, - "d4f0bc5a29de06b510f9aa428f1eedba926012b591fef7a518e776a7c9bd1824": struct{}{}, - "9d59f7867e4f56fdc3a5707d60024313defc932bb4b95c4af4683baa5f0b347e": struct{}{}, - "c8422042462561d60191e23fe21e3e62d0074fc05948cb2bc4aa960ba02c9528": struct{}{}, - "8f025eb4b67d0a27e5a158947202ddd9cb518f09ff64994c6d1b1911743d8352": struct{}{}, - "89fc0f6aa4921aac940a46b4653de04bb4aeae84578070b219ebfabd3aa360db": struct{}{}, - "1516ec95e5b45bc0526bc9bb50c670ca80232765fe469a06ab6a7bb54c510eac": struct{}{}, - "4900c08512b6aee2dcfd5fc5fde1cc55eb669618ffb3a339128402e9f6b134f4": struct{}{}, - "0889113e04d3203f0c401c17c0fd8b352b740dc607433779d3edcaa13320b001": struct{}{}, - "3c3efbfb807eec9ebb731d0dd5d0195e70e689c59ba3cb14c22f8f22897d7ea4": struct{}{}, - "b231bc6d8b4089a7ea66c19203be6cb9570323ebdcfd418d71c8bf5dff54edd4": struct{}{}, - "ede213c6d103bdf9c778df6a8514aa840e9ee664cea931bfe805a6e2abf13ce6": struct{}{}, - "0f1f7a94d198c2075c088cfe7fb3f24c8704b8f4820ab2e122841050f710aa9b": struct{}{}, - "a918d55b72e08bc7aed82aaf27bbbf0676c6a2b3b085e39b8321f30c9ccce062": struct{}{}, - "69d16a57a55d00116c61c1b48be4bd995a0da33dc228a8e5a42e52ba442b088f": struct{}{}, - "059e8127089a55a0039e95ce551f5636648932d6c7ce398eec0858e48a9fe641": struct{}{}, - "3e2d7ad3c4d9fb93a8e5c0041875e8cd84ce94fbeae31c59e4544945a9ee564e": struct{}{}, - "05899cf82081e6531debbb2d4c716077a679d58c9d0f9419e9d61041258eda69": struct{}{}, - "5bee984c7b1fbddf0dad30d28df802c045b810672e3f9550454a88125d2e579b": struct{}{}, - "05b390a5c4c0d937aed659a9c26923684620e6b29ff15ca3d7f0b4841a5bf10b": struct{}{}, - "e2a077230779f8c7bdbfe592bd6544d424cb274a05245d607cbdca42e8da42ea": struct{}{}, - "0ab27ddea9e06413985ecb80f6717cebedf4aa192f4984abdb9cd4e76e9a6124": struct{}{}, - "b4788e9005b1150780694cdaea5b4f345858a24397a74dda6f94a6c200fb2e38": struct{}{}, - "048b41bf9c3f842538a368a55490a5806dcbec8d5c58f25de090e05f6ff60d80": struct{}{}, - "a66f131b1dd54dfe54a980f29a578685534392c5918324571f21925e714e438d": struct{}{}, - "ff907b30acdf6340d169ded457c0c238056a613202420e3764b6677c30cf4271": struct{}{}, - "ab4cc9dd4d4a914db3e91e971b1ad0ba7b0a86654f50b8eabcd6b82ac5c1a29c": struct{}{}, - "910c93c2ca3501fd7eaa2c74da4c1d5bbaec6a2aa3b3c242f93484d1f3dcb4d4": struct{}{}, - "4d4ea86ae0f5a5f827952d7f07308283a9545f4c22f002d8b692e8471051315e": struct{}{}, - "1553cc62ff246044c683a61e203e65541990e7fcd4af9443d22b9557ecc9ac54": struct{}{}, - "ba1ad7e3ea8888db6d9d2a2e8038a5ff3d6543676f91a32ccac546adc16e278a": struct{}{}, - "73c76d56fd4042001a19cd735382344bc7ecb1adf080796e9096cf26fcc6cdde": struct{}{}, - "fb2cb45b6b443241e38145b6445a6e0ebee0410d19e71d9fd0adf5fc382d49e5": struct{}{}, - "2aa962118140de40b837140ed2ec0d7e6863742f2fb090c6f4b293e1c031c82f": struct{}{}, - "6a3be761036549131b35f41209a76f395f0e49370ca37e2f27b32770c6c30d8e": struct{}{}, - "1df1fcc3cb5ad1d75993f7ff973be9023b7066ff4191ca561e20edb29e0da0d9": struct{}{}, - "5d4e1bb37d6b97d8e31f4eb6c8d133f1a0c06dd29879618b3edc988121806afa": struct{}{}, - "f0dce5845962dc3700fff65ad30fbbe4241016fc70ef9e3970c53b3c975f084d": struct{}{}, - "d4d2a85a6a4fab5cf711262e96dffea7acdb2b87438a214fbd8a36f6e4ea4ff2": struct{}{}, - "bb88725e8bffeb4697be90a6253f0289ef4da8eed44bd32d5c59966f98402684": struct{}{}, - "a22925b9c393ad6cfb3dec3997000e06e38a0ede0bad351603daa6922713dfa5": struct{}{}, - "d87f0916b5492438c7b78729776a2c6748285a0d399b3f36a801ddfd1fa365eb": struct{}{}, - "2e7af2bf0c17e5a9f2bd41355ab11c01cc792c83d5213aafd28c2391dd402f79": struct{}{}, - "481fc35257da200b266695e419ab73edfbfe340294d90e4ad8e276dde3b1f7c8": struct{}{}, - "774fc5d077f1bc9cf634b380266575f961435a7e50f368938bdb9213ec076824": struct{}{}, - "cc38db5dbacc0e11aed4979f116318a7f7d4559f8fdbacfef812806df97783a6": struct{}{}, - "ba59926159d2aa256eb8739b8da7e2b574b960e1202c6d624cbe981cef996c91": struct{}{}, - "0aeb35f6743e9174846b2e20761168d0a192ccc92fc562ae55bf97e501b53205": struct{}{}, - "b77c4e8ab2fb5ce536bb35289abd92d810f235a97032fa7f4701073d8610ae78": struct{}{}, - "3ea77fc339263dd319e3dab93c288d0afcbd8ba8f96c20cfd6b42b5e8f3cf458": struct{}{}, - "69bb1f5a28cca06ee4247dc66b3797194f07c08af515a3a565260e54ac949e12": struct{}{}, - "8698df0ec492e5026b61ae25e429f82dea81eb962c5fbfa8ed3fd2ac72a968b2": struct{}{}, - "c61da0d3a589adb7491a551c4e6ffd8b4f3efc13735aeeb4170094658f674751": struct{}{}, - "c785cbb20695628546be13bb93beb6f7c576fe2c946c48a0e3ad2bf46829276e": struct{}{}, - "7d12124a8d5256a49e00527b11c72fdf11e2a05da206eeb068d0989052c81f78": struct{}{}, - "2773eb740452e239e42e1ef8bf516a63135b84044450d73b1da6ee349fce8a62": struct{}{}, - "5ccb82d34ba05a15202abe6cd791612ab929982d5119dadfeedc54e002391205": struct{}{}, - "28dfbd591a9bd6ef809643cb583a59e64c9cb2a0bc188fe301e821c08830baa7": struct{}{}, - "bba00b1891afe83daa29a9639a61da57c02c7bd4e381830c7ac765b79bf69542": struct{}{}, - "2c0b4ee2a03c434e1856404d03d57b82c8ef500237be8ae118b1147a1051d5b6": struct{}{}, - "38f4b0788294de6122c3f934486ca92b4a755840654c8903ca82a6d737452823": struct{}{}, - "a33603bf79f74b056172d43b78358a9d6a51ae59d2720741fbb33ce78e3ad607": struct{}{}, - "11c9cba22b8f08028b2a42e5e50fcf105663c6a5650315cd1880138e886d2bc8": struct{}{}, - "5a66028a0b1d61679264c7e6b5fe9f5cbf93edfe48edf3e986e663e969e3d1f6": struct{}{}, - "b4bb198d7494ada355f8a8259ae295fa6c2008aac2546b7bda130cc520244e48": struct{}{}, - "70dd8638e0feb1b38fd3edb7b9e9dba8056ef98ab7b6d060d37be48aa8f4e259": struct{}{}, - "2241cf5d53e7677f8e6e44a5cb5002329651c73082e70bb2a531848697bbc1dc": struct{}{}, - "d1d4372473583f59df3f7daacdcb6dfb2be6b53a282293920e9170085911d831": struct{}{}, - "259864d70a8a989c35fe37e69021389fd7d55b996f0419e721bbbf35f1e6faeb": struct{}{}, - "3ca67c82e5110231b59944a4deade7f8322c55bac240099388cf5c227e4f09c3": struct{}{}, - "f96954f05a42c2a257d3e1cffcf585015c3568b581c6723a9eb4fc1702c16228": struct{}{}, - "3e6e04dd57c98bcf3f1b4d8a38cd91f612b5ae76bf8d3744ef0810688248d606": struct{}{}, - "91184a0dd41538c632dabb7326beb5f4e1cf5d35664a8651bcb8935f2876426d": struct{}{}, - "e726c5854df855807d5e64710d70d17fbe0494e6505c19143e187fe7a097eb72": struct{}{}, - "8fecd005bac9b889118c9f5f1fb6a6afdfa5183b5dcf7f96c8babae6f781695a": struct{}{}, - "073c1634c496cdb649d1afe0a312bbb4b7e1741b271542e4a436c3b8824b1761": struct{}{}, - "c8170612416bf3d9df8a4cd1dd08ef94efd2ed3fb370afa25ded1e9a2cc0d4dc": struct{}{}, - "2cdbdd82910725464b02099ee9baf90cf29520d623b110d972c222d34f6425eb": struct{}{}, - "3f285e5927ff9bfff830575b71e7c4a226b98fa7ac6d5a2aa0f35dbd01541b9f": struct{}{}, - "5e5fe409577f2f74456fdb47d3e7438c2d132f3362dd97fbbde85e0a3bda8eca": struct{}{}, - "7703e28877859f8a4bbcb67695f37bdf2457b032fc938aa88a4aaf0c3b593403": struct{}{}, - "c3af57a9727050ec956728eaec053e732a672e6541e73139fd0cbf4013e837e6": struct{}{}, - "e010963a4422ead3525b4c5f99ac5136cfa5883f284b959dce67b39160979819": struct{}{}, - "46f9504a7a71e4b5b3466c964f19d07062cf70c3b966b0b32201a056bd73d9e2": struct{}{}, - "06e39f9dd2c69f08fb3c9a56e1dfe05a13cb18226901e92f679f790f3dfb1ea1": struct{}{}, - "1cf8531a22348eeb2abe9b891c939707aa5abfab759f1f6be7abc4a763968ebc": struct{}{}, - "12244779e26c406200edbfe03a1284b0c3de5126ccad9a4c147a0771ffcbe923": struct{}{}, - "35c7b0af50746c3522437603fb29688b3f404e0a6a966a02b5d0d55722fdf8c4": struct{}{}, - "1e3bf343407267f9152a0f2904c03ea0caa16e4b15bcb74d264557b4bb4519ca": struct{}{}, - "5696e4bfd9848940c328d4d7b28877d70b84a61a9ba875fee8e541851b6f67ae": struct{}{}, - "60c29f13c20eb81cdbea067e6b578dc22950ef064b8d2ce21d7d7dd276a82868": struct{}{}, - "f33642c5656c724563e1f0904417ed6dd03d1385464ab3eeca460829ed84d17a": struct{}{}, - "c2bc6b554910816a0680590e943fa69cd8db07d99ea9e6ea173c2be8073dea58": struct{}{}, - "81fd67d02f679b818a4df6a50139958aa857eddc4d8f3561630dfb905e6d3c24": struct{}{}, - "375258c5ce72a0a6a41667d89bea42e3e891a70fd16294db9a2ffce87be35eca": struct{}{}, - "d2dae6d1b4625413eade8cafcb06d6d000fdb57d963fc3c5c497084d42288319": struct{}{}, - "ba76b520c293ff379055ed2d3b5c8f5ed31f7711603de8408d8790b12b671de2": struct{}{}, - "cd99760aa9d343044f0b1ff81585d7167e1e7e76d3dccbdfbe21d7acc1edf7d4": struct{}{}, - "a410b5bf29327087f83e5a424c32316c8da82978be253494f15cb4be2aa76484": struct{}{}, - "10e32272c279c6cb994f6f56b312896b3f1f8cb3650c5459292bfc166705127e": struct{}{}, - "279a4f0535b859679415d4bffdd651fe0c1dd593277f5660a70ef9078ff8dba7": struct{}{}, - "8fb611669b2d66648890ae0f7b008920167c856ea86f9cf847caebed4497ec88": struct{}{}, - "55e9abdcf73242fcceffdf48589611de05448cfe0b19e9fbcfe06d490f6ce60a": struct{}{}, - "7a0550bccce738e90be22f4f686e14e088200863504ddddc97484270a14a51d0": struct{}{}, - "23f49fcc0de8265d335010d6cb6f348fa9a4eb7add1163f1c9a67d28d1f8dbf1": struct{}{}, - "e0c146e3d9648bd10bf0ea6b5590d92bd3b8bd0687f6ba67defc2f4f7b8763f9": struct{}{}, - "86cb35a822329fe1de40eb82a1791be1f66f8bd327446686bdd859a89e436853": struct{}{}, - "78c0781a0697b8212d21711580c2c60616f80ab66ec717ecdbb8200be03407f8": struct{}{}, - "ebd72b510911af3e254a030cd891cb804e1902189eee7a0f6199472eb5e4dba2": struct{}{}, - "518fe5f5c1eaf3141c0096c046a52af11de1fb713b0ad719700406969e651198": struct{}{}, - "6cc6eefb674286f7d1a0e4adaa840670dafa7870f77c7d6d73d5924c44ce3bff": struct{}{}, - "769ff5f20e8cbca5ccafd27cd513596503508e44a0674ece9d8a9f65e96a0d0f": struct{}{}, - "23eb127b3caf920d3dd72f834b35b73e5b8bda870b0d7147838d182b5a9dd789": struct{}{}, - "58f4abdca6bcb7c8c47c42c2d33d8679cb82d75713e00b70984a902a1b447d66": struct{}{}, - "7f3e6d27c565867abdc9f1167a6040ac406b39afff45758cc04949deaf6c9145": struct{}{}, - "b8a9dc73f1e1af5b1c0871e2b8695f56b83861190ed1c2dc90c32c65318dfa05": struct{}{}, - "282f328b6f223c8957305424be027e7024687023ec5ac71d2ef3a76f63412dd8": struct{}{}, - "b82a3c19931d689d122865b63515d7624e98bf7aae8c6f97fb706d1879d59601": struct{}{}, - "5561ba03cac2c86f883ddbe7966f05c12375f0ce18fe8a0938733d170887fe76": struct{}{}, - "2e9f768344ff39ac7c1e41218b0a488a0fb666992777f7bfe03d09450df4f5b1": struct{}{}, - "ea9c0339aaf9bae833ec65d1db24d4caf328b1527a13434e3a6e149270013651": struct{}{}, - "f1ebd231a348712a3576c18aea70214cd9fd7d94c69f14b4e18cc9259426db89": struct{}{}, - "6ca780542647c34c89a20810a7c45179d6b2d71863c9801be62c0b36b792f511": struct{}{}, - "a21df3a7265d6a722920e2706f0f5f5024db7900f69accd6fc6f598062361cde": struct{}{}, - "0cda0c5406a7a1422b5772f1a6b270c9be725669ddd8cfe4f77dbacdecf742d9": struct{}{}, - "909f6eacc7d0d73f1aac6dd46f541611580747cd62d0f74dfc3a2092cd005ecc": struct{}{}, - "ac7daf28fd6bfc7a5c3e4b83c7fc9fd51f92ddff10bdc848f99417eca6fafc7c": struct{}{}, - "df16f28b335baeb63626082c66b6b6787f5f949f563c028fa9675fe7fc2640b6": struct{}{}, - "8d0d982cd48156d5503ea2dbbc3cfcaa86b4f479547ccc34f7e8e57c5fc3fb53": struct{}{}, - "53dfa2d257dbd44e372672167abe67889822438faadc9e23ea34302cafa11580": struct{}{}, - "f10a4b9b2f9d6f2f6d3acc66fcdbbe880667377da162da995269a1d7a9c58505": struct{}{}, - "6a95bbab63d587b596398c4bd7e91a132f24032d2007d107e5ea71967724b092": struct{}{}, - "c0034605ea413370d5ad022b8d2f7fe33461bf6d7e5f4ac78f02c27b793673c9": struct{}{}, - "a680a9e3050cf4fa7fd7f709892a45311f485a871724c980553046ca40a6d2b3": struct{}{}, - "2560766fc02b427a01dd0323ffafbaff45476a727ef3cc6a3a024e7784c277a3": struct{}{}, - "e605c134c4b21b2427698d4fe4d1b557e511e949c4e7d453a4a44212632718bc": struct{}{}, - "68bdee640a5fcec7c15bec3b3a24e76834450b19fccfe1eab395c83a771532da": struct{}{}, - "1b1dd3ba6605abc1479d6969d13ac741bf3f5b47d625af4404d46601ca057935": struct{}{}, - "4dc1a9d59e8308d32f9bd039999318800c7f6f15a0a62f9c14d7f35da44d0f3f": struct{}{}, - "93b56b6bb8cb74786a9df8e2c1b98fde497db1e2adeb65bd989707db8ae7c834": struct{}{}, - "2c0826a6f9096b2f2408dc8434969e8c62f42e09818c35b6c6fc1e9077c67fa7": struct{}{}, - "2443630b4620165c8b173e7265e17526fe2787ae594364dd6d839ad58f2fc007": struct{}{}, - "517ea38eca5eca385e6bb43e99c9b3474f6f40855f7abe42d2ec60fa560aa4f9": struct{}{}, - "8eaad39881a87cce7a9ef7dc3d449482ea52a09f16bdb72013257f86a940e7ab": struct{}{}, - "bfad276bf3bff6278f12aa9072cd828bbbf0289e02fbd49837ead90af203e1f6": struct{}{}, - "83590c3cf71d066107de6e090d20bd50400a6c3d179a332eb44fe7ffa89580ee": struct{}{}, - "9d14a500285c1512d983978789e2e21e31358a974fad7e83833d1147d5e8335a": struct{}{}, - "c1b2a4ae788f9e7f01553b2a91983f73815ac806cd89324c51b80c7411cd3c64": struct{}{}, - "35955ba908ff10a57694d819c887e99c887623c1a810ed9e01e8031689a9bf46": struct{}{}, - "2415f50d4aa44c1aeefdee019793fce717601a184ab1f140c0af1a03c2e4b8ac": struct{}{}, - "0967115f2813a3541eaef77de9d9d5773f1c0c04314b0bbfe4ff3b3b1c55b5d5": struct{}{}, - "861827ebacf9b14665c81ba76aff3cd02da17a52c55118ca112821770b56279d": struct{}{}, - "6a381428f4ae6c981484585a8fbef3963f9de2dfbc06c0ffd072a9f8a5d7e9e1": struct{}{}, - "9d79b70850bc6385a32f2e17c3872131a042faa607554530e07c2fbae465304f": struct{}{}, - "9b937d68e751a87b348d9a9497bee6e1a98a23baebc07a9b8b29184d9dcc478a": struct{}{}, - "8ca83b2f5cae02855d347a16444bf1264ffda0f2f84f84a018f3a5aa671017e7": struct{}{}, - "d417a32def5666de310c785e770ded20cca348fa403b3e2c49b187d2e79cf19d": struct{}{}, - "0729b81afda020e9ee173c3e86d94e5225a34976884776d5bfb27f606abec08d": struct{}{}, - "2725a1e33ec7b7701a41f7ba429a9f06b8f723e2238ec0b04fe572b0748daf18": struct{}{}, - "5ad0000a66aac644685638066d6c1beabd796dc33a5a827ce7121a94aa78552d": struct{}{}, - "b27ecc3e9e957490bc1556b3a79332c8dd4c6edf67bc005469a2787ed361af72": struct{}{}, - "cde527543cf33eabed066390759803143747f36f163cc2b8945e4bc664829fe4": struct{}{}, - "74234e98afe7498fb5daf1f36ac2d78acc339464f950703b8c019892f982b90b": struct{}{}, - "67d3e22ffdf32bc7b36655eb7f6141684e72affb304306eccb8a34f677626db4": struct{}{}, - "f3047409c89567e15033c71eea34da792ff0ac019aaa98d2c0ab11ec2efc9f6e": struct{}{}, - "a822611bab950c156ebec25b5c342998dfc5f32c73abf9da4bc98c4baa8e8ca0": struct{}{}, - "c334a19ef6a358e314eddab7d66011781791915b72968dbbb4d00f3f876f615c": struct{}{}, - "1e67037bc5e90f888a47cbeff1e4634bc6fc541b9e36a45c0cc59d67f0d3df3c": struct{}{}, - "94911c5176be2e43a09fd03a55732fef0f56a61fcf56d6486d7b21dfae5cdb01": struct{}{}, - "9948083bad6d1913b350c6ed3e33ad133ac7c3eb18397a8d1847d072bbfe4677": struct{}{}, - "448b9cc1a5bbb952b2bd0ba1f6affbe11a1539f5681b6e17c762c0b4751077ad": struct{}{}, - "7a47d46e30a5efe74919bfb557c6347176328e832c400f60adcdbc18f68e86a8": struct{}{}, - "3918f8ab77dac078d6739de3827b8c9ac922a7aaa32b5fd2732a802359a6fc06": struct{}{}, - "d4fba09b856cc0aa0f2b643f7d9ca20d309dc89b5e562f1229809c51f1e20dcc": struct{}{}, - "abd0f5e52c64e4e3c40c44dc463308ead692aae0c7fbdb4ae2d3e0bd513e54f2": struct{}{}, - "777d9186b4b480868e4ec766297d044fa6c7e28d793a911e47b19820aaf20619": struct{}{}, - "9cbd99990d550249ccccf2f48a6a537d3b55980634ba43c27657a565594e7465": struct{}{}, - "3acb2a5315aaf68bfee1dc4df2f79dfabbaff5ce4b9ae63af2bf90893c5ed11c": struct{}{}, - "bb4a528f0a469cc1e95647d1d634dfa22b027f61d9123f35568efd2e2c9f8b5d": struct{}{}, - "dee151dfd43cfee1336647d5de7853f390ea6939cbb85d3fcad5e2415c0d77cf": struct{}{}, - "80910374ca91ed92c61643d514585b5ee28e23f480be994e55c85947ec8f00f1": struct{}{}, - "adad45b6fb98fac6b980e66324997d3be9ea2122146bb3d35a3b55173f14c72f": struct{}{}, - "a997481847bc5fbb2e2e626051dfc07eb8bb3e192e5b4a03a24572ffb5673a25": struct{}{}, - "e59ae67897757d1a138a46c1f501ce94321e96aa7ec4445e0e97e94f2ec6c8e1": struct{}{}, - "f3f76a8f9192b4144815eec61bfd78d17ecc54499835a6797ca32513a99e9f29": struct{}{}, - "c83d7ea7784680d50e121b1b6be789c2ae5bbceb285f6b249801276467d2fe59": struct{}{}, - "3e6d4b43d473c69d919c7550b0468718a2e66daf934e0428b401e230c5d34d85": struct{}{}, - "9e4658d1cf944709f9d97fd112ca58855ff6f9b86b5979bf0c96d72c8f82fdf7": struct{}{}, - "61896435f9797b8c5a3cc7d176d414c82387f11f22f6d6ec219208070db6d42c": struct{}{}, - "6afe40611ff94485d186f84f9433631893b90737bc4fa518a510f7b65c874bfe": struct{}{}, - "bfccfeb7726160d74f8a18407853846aab2ebd57db1dc32409acd6aefc7c4b33": struct{}{}, - "336dbe0ba9fbec63f54101c30ca981cedbb4fd3e42864a9398780a15f74bfc0f": struct{}{}, - "bac2aff1d7c0ef450e95a6e4a1a241d73798d64a258c07164367ae91ed87fa21": struct{}{}, - "9a7b416aa4edea520092d8c8fcbe82cd10c58b7df8f4ed414bcc251c1ae52669": struct{}{}, - "fe825b3e68b218eb4915b7af51741d62b6cbc0bfa34c52fa2d1231c0111a1a97": struct{}{}, - "4ab2fb51704e5350f2be5d9d3bd9f78e4e9ac2222cfd13093f3bc39f696d4e12": struct{}{}, - "1f73a018e9a6e20badb6778ec572a846ac81ec2bbce4217b3f88060ea60edb0b": struct{}{}, - "3a973d2fc3110defffcb103944b107ffc6e302f4cb22ef0fa6aea205f2917477": struct{}{}, - "595507cbf03b16451d582749f7d65f3dd4d90e8fc8c894b948e432c6e73d8811": struct{}{}, - "4eeaa32dde86bae21e1cca55c7de2de8afe3760f8073e2b5fb1600d0fc8194f2": struct{}{}, - "e7c1671b097d295b12a7ef28fb0410ebe796015079ed5a3fc2c29af07c056bb5": struct{}{}, - "f131d724ea76232e703450f2cb7824a237463ee80379bef4447f4f3962459533": struct{}{}, - "4a9cc623227ab906bcdc597d4c964274ec1ade63634a719ff93f033078687753": struct{}{}, - "aac15bb4bb97acce1e9751c74556745467b3a367e508a2ef37467b7b5abdf345": struct{}{}, - "14b1171147b88d1d7459d4af601f82ebc3dc8a271dba5b8d4e7d0d998af22795": struct{}{}, - "ec91ef5b87e3fa6f726a89dc1f6f2753fb8440c789d4ada43be10c540071ceb0": struct{}{}, - "5ae1b46bce91b626720727f9d8d1eb8998e5b6586b339b97c2288595fe25116a": struct{}{}, - "8db393caa3ab5c7e1d1c5535f8708e717ac3419c52cf5d0e9faf257a68accd1b": struct{}{}, - "064a5d42d2585fc17c774c2bcf7d6e35b371529344cfbccd4ffe7ea352824d2d": struct{}{}, - "aa08bfc47450e91817588bf4a972243be1f3ec32279b2d02bbe521ea1c1cf1a5": struct{}{}, - "7bcafd09a3b5a96635ddfb9a90c75a897143fbb7207d4be29d79b724401f8253": struct{}{}, - "25f33387f767cfa593d573a029ad4ad6f3ec737dcade7c49ef7aae787fe2ddae": struct{}{}, - "004687553429516cefacdedeaf3a24ce9324d0eb0739c192e21d3ecb883aef5b": struct{}{}, - "343ccdf39358b3f908310e2fb9bb714793aa13a6a5fb08fdd829dfa30c2abecb": struct{}{}, - "920510199770f4d65cb8aaa2cd12bdb2b8c37f5b3907c50a734a0e3409da2823": struct{}{}, - "d3599879b3f320fd7fa6a716b50cfd729e9dd2cf207c80d31a1ce294e34b920d": struct{}{}, - "d09f8c72ccc8296febf9af42d6acdc61a8b1990ff095a944476cd85ec4ab8471": struct{}{}, - "b5b81b348f7f084a4d4f1945c25a955e07a6d87a0ef94117db2a9374104bcdbb": struct{}{}, - "a5c7d1719e284f2c9485405d44f62d152cde9e6ede83e1a79a2442b65f6a8735": struct{}{}, - "29d529c6b6f09803de66f85825f87d028b89451f49f31d65d03b79931433c6ee": struct{}{}, - "824d5798e04f17f83d426843a6506e1edb8194e65469f1bbd740da0eac859082": struct{}{}, - "fc5e206e113dee16db7cf50e36c0f2f816db56fda5bb3d4deb11d3543ba79038": struct{}{}, - "7b9ddd16106a7d1edd84020f704ae907b8c60e473007c95ce5c0e6d738ca3371": struct{}{}, - "859ff5df2f1b59b8af736506814ba7357c0f20cde6965d12080f6e4dcf8b9704": struct{}{}, - "a21138179b88a9980752ce4cf4c8f2c98fb51084c083b022e0e8604295df27ee": struct{}{}, - "c04bd42637d1bbb6ed53de0eb2e049adffaffb3a1568312446efa656a15299b0": struct{}{}, - "aee1629ef5f7a074da10e5f5e6bd3cbf21f6c1f8c022a95d0d6ed519af67d47d": struct{}{}, - "9b1ee2186a15aa9aa077afbd99fbc752b296522938bebc07ef9493173a028ac2": struct{}{}, - "2bc8cd948c447e59e4ad298d513ec7af0ce3dc9968e0856155e10deca616683b": struct{}{}, - "209f45e39a1b77bc9036d14529d981be8a70a9117fbfcc8ef69315f560d422b0": struct{}{}, - "c5387dacba066bd909d47fbd059910be4a3290b451d9c8047939d77081a23f05": struct{}{}, - "01783e74f00c38dd47edf51feef6c11369a2ef0ec3801369e387efa7898e5a74": struct{}{}, - "359168947bfb48edfd82917584f675d23d44f3a61ee51352ad25df40e26e50d0": struct{}{}, - "24048638d9fd0361c3d8d49d61db8955299b074795f72070ab9bffd49351cb46": struct{}{}, - "c71fb71bab928dc7aaf0e756dd131ef8ca6df0a49ee214150b047513e9cb6430": struct{}{}, - "fb4a379d44cab4220bc5596ffa1f8eb9b169ba098d2c98b20d110881c92b9299": struct{}{}, - "e115a8987e8c187fff3c3ce2fabcf31d49c30cf6a40f046dc348f4dd7250d40b": struct{}{}, - "9714afc6794f6e444bde35e2217805aa548ebecbc460d149b7c0cb06b3497ae7": struct{}{}, - "28f0116ef42bf718324946f13d787a1d41274a08335d52ee833d5b577f02a32a": struct{}{}, - "5a0b83e19c5750eed6d8d46cb858d15c956a657093c08afa53133c0fbe5f04fb": struct{}{}, - "899b9a314067a23557b5db9b87d3e599e2f3200bd69b6cdc6dcf08f8b8e40e2e": struct{}{}, - "ff71cf74abb3ccb005b8b64371725db15edc42c1ad33413bbe561b2da3c85ef9": struct{}{}, - "64dd76fffbcf94b942ff71d6fbca74b3d0bc06941575e9e8382a080399057a81": struct{}{}, - "1c1d50c7b0490390c9b7dbba0518b1256a60b8b4f90f5a5c5a714abf90c7a9b6": struct{}{}, - "b6c903f9dde09de7effc485a41c9a513cbb3da9a92d6b0cdabf3e6021aa6bb8c": struct{}{}, - "63b690cc7ae34917f4e56ca0a1cc0ecc23bc216f92f6d85d66ccef87213e425b": struct{}{}, - "db52d849b2c4ff1d1f11df9502b9a5d80b2965cee44b45aa32348a310c27a5c9": struct{}{}, - "dfa8aa75052fd4d3013b19df8cf02b7d16f1ec6c77de812c658fdf725bf50129": struct{}{}, - "886745ae493cce2f57155c7d9be09150e251bf2fff5e17711ceea39202abcd40": struct{}{}, - "050c094b86f25db8a8da1c288707a0aad2b57bd90e840ba6e5f81c55d221ad80": struct{}{}, - "092d5e20bf79e2bae70717a6a1a3da1254bc53f446d57a5345c4d774bd5391df": struct{}{}, - "edcf8c014e480077ccfdd90bb1a7f60ec836b7bf33913083b5d6603005d1fbaf": struct{}{}, - "ea047fa1f36a6537a954fbdc483da63160defabba8adcc1c95b15e148be01218": struct{}{}, - "3fbf0de21a976c03ddee04f7df487059eb2327ea13f2f10df05e607c889f3e27": struct{}{}, - "93d19a4552564e66fb73d75849bc33b335a835b8d7a069e59ef9538d62f8e10d": struct{}{}, - "012ab3fc2f5b91a02a5c993e5ae8cb58b622237035d8c39128500b24e874ec32": struct{}{}, - "e11779b2f01a5175e175f83de6c786553646cff665f5074bce5d75409040a5df": struct{}{}, - "38c8dec10f77441f6846064ba6e5ff18a47e55e79f913df21fc800040fe9cf1c": struct{}{}, - "abf9eb9facd09aafefb1e027a819da4d95fdad98b246494096033c7f099608d3": struct{}{}, - "a6b1880837d5e928dfd222acd61aaee3c9f9d2047776eb2609bb4b059d23ed57": struct{}{}, - "6793b93aadfd1f4be130739c4639706a7751495481e24e71a63349296b8465ce": struct{}{}, - "d9c8852b2805055544de9ca3107c9ea8a36ef9dcdae6ed80b61004d94e0f9a0e": struct{}{}, - "ade14c8b78549e39901782d752408795ac316d828eeed6fb00413bf3e6a895b0": struct{}{}, - "8461f6395144799e3bbe4739c55d4dd394155cc1603e6082a82fe63d41ef15e2": struct{}{}, - "fa728715ccea3724678e0433e7bb9d15dcd256718327c2a3deb2b850a186fa85": struct{}{}, - "ec7d7524292090dc29cb603c4142bee642f001979a1e836aba1bc1df9bee2a69": struct{}{}, - "16a0eeb0791b6c92451fd284dd9f599e0a7dbe7f6ebea6e2d2d06c7f74aec112": struct{}{}, - "aacb6c7a077201bb77ff83fee366ace9d2212960318788556f1ec919fbe0478f": struct{}{}, - "2e7af7e9a405b4115fb7e280d55c4f31d60ba9475ba943e0339c46639ed0d3d4": struct{}{}, - "1562206543da764123c21bd524674f0a8aaf49c8a89744c97352fe677f7e4006": struct{}{}, - "89405f34849adc70cd38c56a28b040e690ed98fdb37ef7dd4f0a09d09fc1d315": struct{}{}, - "0bba9b5822d1dcec44274509b72a980ba12ac8d4bb1de654b16ecaeaadae4f2e": struct{}{}, - "08ad350111876155e06a0c094eedaad7c688c60873cac2da2ba2acc40488fd2b": struct{}{}, - "54c0bcab0e7334379ea4e9df9706f845ebcd39924b2d291fb78ebc82f1b4317b": struct{}{}, - "f0bb816907f29ab480b24306edad1fdd43273e68df0c29283f5eab9e6d0d365d": struct{}{}, - "4581c732b43146127264733a81a886ab8516f4956f8a212ff939e06ddad31523": struct{}{}, - "8ce94fc981c83ca58616cd882f29259628365d079a285a5a68a648c5e61c54f6": struct{}{}, - "2081cc0de301a35dba481d3327d2c3a9dd1686e3865f4f96a9077c4dcbacbcd8": struct{}{}, - "c3b69ef12a15f3b6811c1ffd29d6422d79ae2786d45eb08cb0f0f39898454d11": struct{}{}, - "f249e8e4349fc97fa68c5cceebd3fda1afa5c73cd5f4c128d68f2e716f4b7e5b": struct{}{}, - "76db18ef0f2f33d6ec0dd325aa1c5477b8a8af9e009d936090b8cc30e7e632e0": struct{}{}, - "7c6571b1966745d52e1d62e82748be91d1812248f411788d2343fd875b88c3e2": struct{}{}, - "bf8798163a21261d287ecac8ad0a4d87ceb194579e4d0bd25f9ee280ee7253ab": struct{}{}, - "6c056bb7cd293b305ec49a83a96c59362cadbd00c61578944b3f05f67eb4edc6": struct{}{}, - "3a430170d33be1e3396dbec9a0929590b3b689e8536d7e4e99cb082cbdb4a9c1": struct{}{}, - "cae666e06cc23964aec0e8478129e6683075a0b897dea6afb4708208f1a4eb62": struct{}{}, - "3f901602db02e2515a55dcecb094a522d71ed5e27d1565d8967cfbf64b44156f": struct{}{}, - "3cbb175ab2c5c8b61f790d6091506de8f7e9437a027a51f910e60ecbb82d8050": struct{}{}, - "361c9d7256b1f7732d22a869c3b60ae95b11e4fbc162791d067141b8e7dfdc52": struct{}{}, - "76c467c97c151dc4921aa2006d6b309be455c2ee384005c1c790f796b58b29f9": struct{}{}, - "e92d264be53e910471e2d463ad8de7b25d556022dcdaa619e61c9f6d14f9f83e": struct{}{}, - "c2c22ee550de8768eb71a5077371849ae19648a09557df2d233be8a0d44f3087": struct{}{}, - "c78725c73c3d515cee3f98098aa71e3b757372d951f8962287e8c7a211b6cc8d": struct{}{}, - "afb62513af779a12f1bec105f3f4c441552a219dd42dc184bc51e30f74c74d4b": struct{}{}, - "da26b77becf8bcc17aa4e59a0205d8910373e5a25e0d7993fd5b2856c21ecdbb": struct{}{}, - "a61c5cdf78281955d9ab0ef81d8d6d84d6aa758d9d2da6d2ba1e0a84920ccb15": struct{}{}, - "7a7ce758f6473b68cfeaad779b0ceca7d9dbe0cf3fc9f7f5fc5f9ffd0d23d7b7": struct{}{}, - "9d4d27feeabb7eac83af70f8bc1735925fc44f84e783023f268f5beb6049c9f9": struct{}{}, - "898305ec9fdc72c8b0fffeded5da7acc747d4fc1a40af3f409fc8358299ce421": struct{}{}, - "fcd8dd299dfef508d98fb8d4d16d3669f18d40f457a69cb060b48ea786e827c4": struct{}{}, - "f5565bc1845ad8b67cd08934003525512794a8a23a745318eef98cfefb8c092e": struct{}{}, - "82ed22d2125d6bcc44d7dda539b8664cdc9ea3dc10ccff16bfa146a29f5fcf64": struct{}{}, - "781b544867cf4867cc30daa894b6f42ed94c1f4cead652cddc1bafd263b63d56": struct{}{}, - "affc875700667e07a53bea72fdd1d946561a523a0bee0170daedc79151ad0526": struct{}{}, - "d44c791ad03269401542e123d1cbc8e6f4cb65714c1c177f5d8bb0a4afba8157": struct{}{}, - "b1ed1a3b8963ff7f226809e50871e2dfeef30a730768f19f0839849a9b9c5e4a": struct{}{}, - "a88074f2628aefe04aa62e41efb115eeec8f9a1facbb42d37cd9b678d6048713": struct{}{}, - "b16840b100d3ad75b48a6335b12daf977f043b5be746c2ed86a02994d74521cd": struct{}{}, - "9488703aea8f00c9ecffb5cf72c069f7dde8c0eb899cf75cc9669ab082af2c7d": struct{}{}, - "16f0e9cf6f3ee1fc887945c62302f04478ac13b458cb159b116b8f8fe2e7bf2d": struct{}{}, - "36a2beaf3fefff1ae809c4788b674d98c96f66ca962f9b10d26b9c2839865fbb": struct{}{}, - "e299891bb09ae909a89b69476b98926e5b616004e676d0a1a1875ad25e2a5453": struct{}{}, - "5c2b1962db96e51a0c4fd59e0bbf13f55f71e8db63d1a39da857c95626f6126c": struct{}{}, - "6f45605dfaf2f3797242cb5955cc9210848b8f23afdfac963b53e5802a03726d": struct{}{}, - "716870c4d245ea1c2f40b75c62f0dfb38ef24e55a9b40433820310dbced8da60": struct{}{}, - "91ed00d8f12aae1bb39c23ce2c351d5da546fcad58cc2738e6f0ef5a7a963395": struct{}{}, - "1605347b8e049e53ce0c9191b608460dcf9c3137c7e8d517f291b311df36562b": struct{}{}, - "273b65629ee4b65dee3cd4f928adf9a8933c06b246bd199f611133160df299ed": struct{}{}, - "77b8f4d1dca2d16da69b89d4d8f2af01d36b35d50caa58f803adc14de5de7989": struct{}{}, - "b39667cf64cd5bc6cd7adbfc711cd8446036f9144c1cceb604897b0e824a027d": struct{}{}, - "9934db2d907501987ad20277aeeedfaaa96146e777ff321b6695c71f7b8cc92f": struct{}{}, - "708730ab8dc65c819f68bf5f31798dee15aa3147b2d3048ca96f944e0b78b9a4": struct{}{}, - "9dff50df08c635815f4b19da10f756605a34a79a48d4ba48712782502975a70e": struct{}{}, - "3b21278178ed3463f3bde3a35f088caf6d99ff1b2f647d9a0c2b8de599ec3dc6": struct{}{}, - "d20705e05c999cd5b7d174ddcb0c723133f87ebfa45a0b2b38f28b51673ca175": struct{}{}, - "0b24c35b4dbaa028f65693ac433616201233c6f5b060fddd23384f52c07d26b1": struct{}{}, - "65f3dd9abc1845a987d7d13983e62243ca9cdfe02ffe75601019b48bbf05fe49": struct{}{}, - "95d7df5e8b3bf7957dcc019797e53b655420df99f9a6c33dbbab62b8af8c1040": struct{}{}, - "d2212367fcc58979570f56271d49b0830099709971963117e97b27c4b2e4bc99": struct{}{}, - "bc0e899aa43b91209604dfdee7e1bfc74ffaba0e808d722d7eea66a1a7bda7db": struct{}{}, - "23056287db46843a7a9ba189e4e27affda3c64373af660cfb6b54d458745ea18": struct{}{}, - "ab8e1efe40afa48721fe45280e5992d5676836efb4cc79d272e5dd613324b47d": struct{}{}, - "a97b0931730e83fbf9284ff00a4c5f7a4eef2189dddb89319a6dfa5871e28923": struct{}{}, - "845c11e25e5ba13459893641215580edecbeb49d8d38d6a17aa82ca43339a46a": struct{}{}, - "6bf2f9505451ffc3ef54cc5999d8cbf5fba341b207a1eabd2ad92f3f886ae9a2": struct{}{}, - "aa0b9461f91b9b7e6174f24902603628726bf9bebbd7a22be05c2c83aaca217c": struct{}{}, - "2709fb8fd1bc693ba40b6862234713b397fb4199130e82bc2e152a2aabfa566c": struct{}{}, - "4719655b2f724296728541b12f808c470167b58ca86abfff3c29aaa562a349c8": struct{}{}, - "92aefa2fcdd5fb65e099fa9ad4487a4d9630ffa2891bc4970a9b5b45590ef868": struct{}{}, - "eb0c4198c51c4736d278a838b57e98259f5f0e7160b9b23845a54dc34e3e4a75": struct{}{}, - "6a1d0eea83986cc0964b33642ec6bf1853561a7d18dba149f8e1d18fa702182b": struct{}{}, - "6e285a1a52a6e4b222d9ffc026bce02dbbd30e767d31456b02826f088cba0d98": struct{}{}, - "8ee5940779cbff7b9bf9df932ac5bdf1af98e4e1893fbfe30eea3db1bb616882": struct{}{}, - "9951c099a399bf5cc4d0e5081079dacd083fc0bd0c7ebf42c63e7d9053bc93a0": struct{}{}, - "3152927b75057b3ccfdd410180c4c291a5a345e31133b5a1832fef264b5ff94a": struct{}{}, - "3abb5cf3ca3e3d6e5022147bda6eff97f89f12d03b4efae71f826f47b314d749": struct{}{}, - "f6d3c706a8b404d06fc7dec2eaf0c8567ec2a422f651c72d617d2e358fb18d02": struct{}{}, - "1df74d8a91df56181709a35ae500a54621d7e0e899d04d891475d4764baed01b": struct{}{}, - "16739a79c1aa7f19aee35177589262e6e55bc93b74c03d2d82486302f59884d7": struct{}{}, - "46c759e871935f8cd63cac9a269ad2b045ed518ac00f19f79471c8bde5523d91": struct{}{}, - "324300dcb4c5810243243fb36a91568300cb54138ab1e4c97d302304be5ff4c0": struct{}{}, - "96ae2324392eb694a517ee2b0d84e5704079332f4c49d22ffc066e54b9a03663": struct{}{}, - "a01fa8710becdaffd2cee5f416d655d9816e00b7be7d63d6c0b44579d68eff0b": struct{}{}, - "e67ec24fd181d169dd05c3b07ce2dc7daad3e565ebea29c2d6a5a22bcba9463c": struct{}{}, - "fa75c0d91317a0239557ecd1197929ef6fa4cb23ae722acbeb56ab5a4103d29c": struct{}{}, - "a4cc6bc01a927e2a78fd3bec51e865ac0d85e4daab6f988d5d33d056e125b1c3": struct{}{}, - "6d022eb93cfb20c4775923cef3ed6b290e1e1c93e59e15ee7df72b0beacf069b": struct{}{}, - "e3e3164b2b8c149e04a0f7443da3500dd5a4c475b2b22c6c1812c47928871e7e": struct{}{}, - "e873f989e608260be3af77cce1d52b015c0c4ad9867e0799e8099927c85d8245": struct{}{}, - "852693f7cc2b36dc304ed6d8fc2c8cd5f782465c48b853a4baaa7b0a9c1dbe82": struct{}{}, - "4389ec33a36fa41084720f84e4e7432adb3126a8f890e16bc3e43e1da5134178": struct{}{}, - "f9731b912bc9b99d8224acde468c547b0347b3667d1dd4e633e0d46a389e658f": struct{}{}, - "bfbbed451fcbf5d454f21848085a4ccbccb4960df3ce9f4705be636e907d64ba": struct{}{}, - "80c8a5498a7ed3774350fc393b5ef4bf08307d457044b98ae927619a231d2eef": struct{}{}, - "46eaa26621e4955c1675b55d446c6d03325f458b59a465f898d42924010e7286": struct{}{}, - "dc5eef3ac890efef968787c7fb6bae9ebc1ad7a51cbd915fbb6af3eee93d614f": struct{}{}, - "65101546b7d66afa6bc8b7a944f9272f77f4b780fd4f685ae7a1e9a8b8f50b6a": struct{}{}, - "6fbcd4640397d61ca1bbd70b3740c64b961c559fc7fbdc35e00b9aec2753384a": struct{}{}, - "2dae946ece4887f35fb73062be6e8ba4eb9e61f4b3b358d5a42936277ef4a2bb": struct{}{}, - "5c59d0c160a320ec01794f14cfe4002412d87c53753320412e3fd20c77e04cbc": struct{}{}, - "13d94b1efef8497f01a3b8e3453673fdf760ba5ae88b538ab417943f86613531": struct{}{}, - "af7f04cff61fc48b570f1e1f5dd0dd0d080cc32b2179f3468b337cb85a338e78": struct{}{}, - "cb74171872f55fd924a811addcb432c7172050b17e94cebfa051af759acd3247": struct{}{}, - "da5e11efa36720a4211ac89acf1479952e99b35636f006a70bcede07495289d6": struct{}{}, - "df7da510a6fe1ab64a57e49687b9b1d239e647850ecb7ba49b542df06a591d11": struct{}{}, - "e9b238d5f1d355c4e42eac18a754da18651b6d65dcac72fff8eb4102e76d1ea9": struct{}{}, - "8cf405e6b30842c7138cb39b663923f3fbdd0566d626e515f03786a1f7945fe5": struct{}{}, - "11fd18f1d59604063e75d015c7738e650938a170f34aeb1f39c56f17e2d7bd75": struct{}{}, - "ab51004e9d71a485f160f655fb9e72bcdef8f5ca4178b26938b49471456fd11c": struct{}{}, - "1142c377338da4d56affbc95526b4506daa8e8a4f8959c53a93e8caa2c39668c": struct{}{}, - "7c1ff1f695d3a6704f75e9e6ad06ebc03dee3842e11b5ae4e0fa98c25d348004": struct{}{}, - "d77e22123e64d3d87f1f95d9cff7a0b6af6c32b9a81552cb90e991eb55cf63d4": struct{}{}, - "731f46507cc5394513e3bfa31f2766b4babae57a96c1ee3a0911374c483273f3": struct{}{}, - "03517d909ef9c6186fdd65dd47d04bfb7fe21484ee14a8b18d9b1c7c0f4f5b31": struct{}{}, - "f207795b5f1af74e45efcaa36d62ecd5cc4fdfd5d500fcd3689cb76ccb81c1e5": struct{}{}, - "9331f09c96d5e52ecbef1725cf9491b57258666146f92e2abc8a36137f73080e": struct{}{}, - "2e63b89e6f05a354a9503905b7f77d65f519f037fa95445e60571e5f3d7ef478": struct{}{}, - "b8910cd86d69cc32c5d8b65318b401ced0e8aa94c95735a73fd5119b529aff74": struct{}{}, - "4509beb0ab401d71fa4a5cd94a55c9a74f13332776ae4019c5bfc4c2005157ff": struct{}{}, - "5f54df2bcfdeaf92104529aae53ea829a4c05197449aa785e71f81a6c45501bc": struct{}{}, - "281d92624188d421909d128b9d22ffff0046d856950896a24a433b6ebf5c731a": struct{}{}, - "b6517d70cfb75be0caac0f48405c00c2a97de3ac9a7b1ebe2e7c46980d5794c4": struct{}{}, - "9cf4c6123623538ff10023af0948efe638c6925cabfea1fe7a80297e7e83684e": struct{}{}, - "493a13b75abb7a42041db8ff4b43b63516d44c1126cdca488eac1fb0d41225ec": struct{}{}, - "a472b0a58fc4e47a6cd19c041e204d33aa69eb4a62f6e90595f56466c7ce6dd8": struct{}{}, - "a8da12d401ea27dd9b50533a13081763e1dfacb5ca6616202b9be8bffbdfe20d": struct{}{}, - "a28c7aafb1d0e46d61087673ad86369fff7e1fb00cd4c2f70ba85117b48f166e": struct{}{}, - "8de2c0cbc62bcc213514f8d25466a7dbedfe049deb6d6c1f4b327815b1f290d3": struct{}{}, - "3d4d101cd7ea9e5effe950710492bda210dde6cfaec5efdfaccd18dbc9e5241c": struct{}{}, - "59596d4880fc1c2b52ffe84d56b5cfe291859fde3efe1ed2abc70f5c10912f01": struct{}{}, - "28cdd20eaf134a05f18369414cb2df5e36c6a4e4f551b8c9cff8548029c41d6c": struct{}{}, - "774cdf08f6a80fc9dded9eea9e0937f9a1a89e34f448ab28d8930cc17fef803c": struct{}{}, - "2f5f6ce5ae30b54aa5d7ced1ba566982bab34ba2814a51ce1865d2c2d8815cd4": struct{}{}, - "587eb5a7b92beed5c67f5ef71af908038061e672134863fde45640349d2e62dc": struct{}{}, - "2cc34347ceb9b1caf9c9af56e79ea93935f4f3d52736b635393931c942d67187": struct{}{}, - "0182753a60537bb2f2194fdc1500738f156b72e97b484040a2b1b4e3786c8b02": struct{}{}, - "ec1fa1bb38038c75c60adbfb08e508b9843aa2ec9927b8e928b6bee884960ef0": struct{}{}, - "96b8f576c788b75e59186bb3948b3755ec8a75670b7a168820ffb4ab8e95347a": struct{}{}, - "a9d511f2a765f563cb8cc1b28f4f037b9b9c6f0ccffe8ed30e17cea6ab185050": struct{}{}, - "14a6481e0374aefc0dd570e874717a5aaa77839def6d3e80d74f1ee913b0e966": struct{}{}, - "b5048af978744ada641a17af076e34a92195fa6cfd24828bc3d787b2b43635ab": struct{}{}, - "9a46fa6824ff345bc43d5bd3f0da24be067809234124bfa900a1bdf63ba65293": struct{}{}, - "747a8f398395dde8e524d9f983784bd8441c5cfe4307b5a079be5412ee65c314": struct{}{}, - "344c96b185c38071db179979b6c64e47f264aa6ebfed84753957b9711da9cb3f": struct{}{}, - "1f65077658b494717549a7da90d69d2b68e6b1cff1c18be24b459edf3cfdd85e": struct{}{}, - "9279f4a7c1c145d5ae930fda23ef386168f6720b4e0f0d3dee383c5ad8535737": struct{}{}, - "90ffb9636e2dea612a86d0f5a150bdfb3268d5b30d52274d0fcfc43f7654b243": struct{}{}, - "dc4bc886825c446e6ae02d4d0c6a8787af0395079effcc3afc0f8bdc40cbd161": struct{}{}, - "3c924e3f790fb8273475e9a1ba2d0fa8cd2407b1ead947d773136f47957f575e": struct{}{}, - "e41d64db5703c6440b5c714d57251a845bc0bd241480b41a4e7fd3e052f85a82": struct{}{}, - "f3bcbbd20d3f81a559e744c4f7364031ef9b2e43b7b63d22d774e66fcec23eb3": struct{}{}, - "a610d3ea3202b91208b925b017afdf2a69122cf7f3d65a07af5248308ca751a7": struct{}{}, - "dafff407d7450f62b0dd0c413f9f0745d70071b8ba4d731d093804be0502184e": struct{}{}, - "464c7a646393b68d1a42076c010b5aae418d8d322f233ca0b8cd8e2c6bcd9676": struct{}{}, - "3006508c7cd78b46c20ebcb92630b43d7d1d816493e8bac9c51415c95abc93b0": struct{}{}, - "72109425aedd7916861694a71c9a8b73da6ff614690400fc5e095718596d6516": struct{}{}, - "ff478050ed29446c702df522c74e784720d34b33a774adede6f854ec5363982b": struct{}{}, - "daf4da7f7c7a2d1cbf171a57ab9a1876c6ca8cbad3a239361b66e47a4ab6ee0d": struct{}{}, - "df124b0b45f013bd71beed2ef2b7f16d8a2b44af625606cba527e17696f9614c": struct{}{}, - "01aa028bb4212190c27b6f8bb167652ac030734f599585cead95fff2df04d196": struct{}{}, - "0a8a32b6556a26b05469336a0d4980c4ed3b8a93ac1785e3916eaf672519be28": struct{}{}, - "4629541303ae7532a11a37ba6b8d21a48ffeefe01ee83db9385e0aad5bd6cd3c": struct{}{}, - "1bd2b125028fb7943029941733884997ab22fc0ff37d846b894c106bd3642d55": struct{}{}, - "2a37a188cb321009d29e92a14569d5fb3ed3f001b43ac60284715556f515d72b": struct{}{}, - "79019b70b31570e096016baa8f7cc640cb8102ed98c8aab44d3e1bd0a546b2cc": struct{}{}, - "dac858bd52e7cec443f5e6219def273d0fa0441413508ca4b1ff0f8ecdb447de": struct{}{}, - "e5438aff0aafb218346e13838b56c9212f0850fdcd34cb27df373473cf909ae8": struct{}{}, - "cef695f5a0d416f966cdc058bf2faf73209532806313ec06217b8f194cc2efbf": struct{}{}, - "52d1d87c3b2027f3f2660015ddf6463e97430b4e60099217143ac75a45646aa1": struct{}{}, - "6d0cefe4c0ded1cd8ff2da4e69475dc5b83c7defea8f182200c5a3c18f2a0d46": struct{}{}, - "e348d8d0564992652cc3e8e5ae7dbcbed9ecdea4db96c573d8b2087643ee2569": struct{}{}, - "35c653f0996ec42178f1754d3e4c3d3d4c9903d5dcbd4d2b90535d237a00ed1a": struct{}{}, - "f1d822f7e289af4978f9c6a70a80fa792b8436050db8212be90a22f74bfe10a3": struct{}{}, - "a9db351c23922b73f1c112ede5840baf3d251070366784dd6147489129d50cba": struct{}{}, - "bd9b8d8e9aa8e2aa24c07daa04d04cf4058f7f3084faa0dd346e75f783d80e97": struct{}{}, - "625b15292f8e7426be94a4f30a976167e350ddcaaf153ae2c0e9a2e1ab0d2235": struct{}{}, - "f8da44d4b15e75153a949659ea8e99ae14b29e4613b93841943c43e02637445c": struct{}{}, - "f2ce341fdaa67727bbdc88e3f7f19dc539943e346f6c2bac497fa1c89c0ca128": struct{}{}, - "394a5cef5a6114815ff5035caae0cf247415e9f515d81b0200adde997a8f5ae3": struct{}{}, - "4e6750b2ca08feb9581dd5f41711eb8c279965ca5a2332c398e6988b16798f56": struct{}{}, - "09e0dd9e17eadbd4857e5f80dea8fc2f4a5879ef42bb9df6d08b68cc6ae4a7fc": struct{}{}, - "f2888365f4d16bfd7d20de09f20e2c28d821777b2f9d94dd8c0fb305d02abd45": struct{}{}, - "4d50c52025f8bc9d06fe9f5ca6470e9433baa8509e127b059b6b286f8b4dc71d": struct{}{}, - "7fed5d2eb677f1763acb5676edce4b9add6a8e405373386470a423141c8872da": struct{}{}, - "665ddfc8f8320d28aaaa69f358ad64b02c76537e96766602527fc4a12d2f5645": struct{}{}, - "0c96cb4bef1f85910a8c1a21c1d5ab8dd988355b5df1c19a8bc1678d6663cadc": struct{}{}, - "9f73cd7c814a8bd49a0e65c7aabb168faf0a60eab60a5aadbec520490f0c0f49": struct{}{}, - "e89615782c18608d91e649ba5ebd1fe903e4aa2ca42f9fe1addb6661c17530b3": struct{}{}, - "7ef4bc34a49745153c69ebaa002fd521f8ca0a639cbc936b0a9a5e73b45bfc76": struct{}{}, - "8ce33c6c86e991f7b4993479d63cd73039fc874227fd64a5e1f2b4fdcd31d7a9": struct{}{}, - "50ef23e2b7fb15749ff505a82d8fff32f5576ffb891794587adfa234238863f2": struct{}{}, - "d2922372131239317837d760004d37d0b2ca5f803a6c9c4060c565d63c216609": struct{}{}, - "705591cd6c2250a56a642c0138308c9b47fa1ecf5934ce8102f14d284e0e0814": struct{}{}, - "9722d4a4b59ce537542d9abe02c5c6b363102390a5df88bc490c19b76d55c30b": struct{}{}, - "04ed7b7defc2d736f5c1a816ab8cd4afb486745abd7526c4cb48d0187133817b": struct{}{}, - "33cc8f904ff6c1a9f766d0a335cbfbb7cda759924022263ecbc6418e6d018806": struct{}{}, - "616d9affbe4a861b9f01ef1fb16cfa2e5f0eaf47d6f12994f4033de96fbc5730": struct{}{}, - "452f3256632626f5d9d105dbb25f66f831ec987ec39efd027a38ca9faa428f1b": struct{}{}, - "aa4c231348ed81024de144fdc13020a000d718eec0e7deb86ceb2158ce60bbb0": struct{}{}, - "39a39d1bfab5e142a4f765360ebdcd1a96bb912a7d7a13d916cb6b12f1bd2646": struct{}{}, - "56b7dcbb87ddefd96b03aa091fab23289e6d5e04ec753a346722226b43923e7a": struct{}{}, - "192d3300a96ef08b4a68dba7b3df3fa53310b375482f210f1c6d4520490aad83": struct{}{}, - "7a77d3b52b34ff79251f9b73891ad7efeca63254b82cbf15a05e5578460ce992": struct{}{}, - "10e86c6514d40f2a3e861b31847340ee8c8ed181029a17b042f137121d28863e": struct{}{}, - "d1007b74b9b7d106e30675ae1fbf9c91a1a07093421b3b2865f5c95c09d57168": struct{}{}, - "647d177cca8601046a3cb39e12f55bec5790bfcbc42199dd5fcf063200fac1d0": struct{}{}, - "a35bbf3a3240f9fe9a399e3fd31be94d312f25faba5cdedb860ba6fbbafbfd76": struct{}{}, - "57523f642f63eabb2a4f6804ad9efe4131c56c0784de25ef63ed7e6ee2ba10a3": struct{}{}, - "4f3fb37b1fbd45a2253bef575164de1709c8718cda2100330f946b7df66acabb": struct{}{}, - "0fd924362117d03662f0e86e580ab01c70d3fa88dbffce4224017e86e5fbad52": struct{}{}, - "fffacee1653cf049fafbc6d38c97f623b388846034702625c67f0a508710ce7e": struct{}{}, - "bbb0b5b15842af2a3f42072a418c908e8a63caab2120d15cfea68e433cfe68dd": struct{}{}, - "2d5f24e66f8b9071ff133d419e8f926fca38e3de63330c21d1497f4462b1a3f2": struct{}{}, - "812e9ddb9a6a74627d4359f7c28b210dc648ae569e9081ec617d0d8b58c053d7": struct{}{}, - "8f22cae214d21c08a2e2dfc29e77e1dbb1110c0147f2cc72baa37550c30d5666": struct{}{}, - "92bf8ea876da2855facd9e176e481364a9e1d709c9bbfe6e2bd8109bcf7ed20a": struct{}{}, - "084e990a49ba65e622926853785e0ed05c63fc7fbf798a26e8242b5ee3d7ce9f": struct{}{}, - "a75a17819d684e8c24344b1b9494acfcf70f132955fff6dbaa79b8414d2d3a99": struct{}{}, - "c2ff9dfd0ba7aaf9cdf86f054d18440116e70699fd039ed37f8227b551b0e8fa": struct{}{}, - "b9eef96afd8ae5f261340925d6687d95d4e60fac1129977990248f766c27f794": struct{}{}, - "60cb1bb7cac6a85c0b3019fd82b39ec9e9bdd564861f8024c7045433bc3e6631": struct{}{}, - "90dbca35cc682d5b4c2e53bd171e6cfba19168539c6f0799df31613fd5638a0f": struct{}{}, - "61760ab27e09dd222dcfb3a84138566e856e0ae7adf194fdddf0e3803bdbeafb": struct{}{}, - "61ab550f4c85a975cd46f8606ede483d8ee720a76f627773de1bc1dd8ce5fb35": struct{}{}, - "772b17933a787611eadcdaeb067fe83715003961c08293dbfc8244e20d3c4646": struct{}{}, - "490d5e8225e3acd5cf7b5408a1631176b3ff2bcaaa13d49012f57152ed27222f": struct{}{}, - "8b0e5b427b0d399ac19d13f3bff90310150f826bc0db33af93f5118de351cd37": struct{}{}, - "4787fe9ed64ff7d199c12cd98de7cd26d7e9a0ae54f1298634f9cabfddeb6e98": struct{}{}, - "93883c48a44d57deed9e46cd5a63d16bb6606deaaeff6c004a4319338ce7b35d": struct{}{}, - "dc930d31d2ac87e5395f225548fb267662bd209b5d974cc8fac3458d7c8fc143": struct{}{}, - "003ee1cf1c413d89d5683653481975663a4cbe794800c553d03325f00ec7095a": struct{}{}, - "b5e99acb5a143f5e50c1c53516a375a9e855c78efb0276bcfae8c118c7f09931": struct{}{}, - "fb1d2d7b10245525cb347e3e38ebc07f27335972d20555a737847640c10c74a6": struct{}{}, - "30cf1c20022cf1959929016d444bf9ff202382c26b01631ec8783c9034bcde98": struct{}{}, - "714050a0a8fe4f030de23a6deed18c03e6f5e28a4aca97ef27fd33ac4b5f1bd0": struct{}{}, - "a1148fc4bfb61d6712bc36746656414ce19c87c03b6aa53a781670c5343ca26a": struct{}{}, - "92ae5b42b4708693ad9e731c5e35e93b0900ca41ecff524dd1290fad266a8302": struct{}{}, - "09d4b7e22fb0eb802c6dd2ae260a723d65481f33e97e62b4d87d822d1cfbd7a2": struct{}{}, - "accceffe127e0f027a72ee4a3517fcadb8ec4b60c21a3418b5b9c5d1d53c2138": struct{}{}, - "6adad17f65062813efef0f543b426cf38c403d558518b2a7b7cc8af69bcdefa8": struct{}{}, - "2b69fefec90e2528f84816008b8d58fa9a64eec1e967684611944a4f736dbaef": struct{}{}, - "c21c1a4d4f1e71a2f371d4431b92639129dedb0d4674c6c9ef97605bd321040c": struct{}{}, - "d1e915d03c28fd015ed589f5e26e913cd46f217746805729b3e3c569e7e82e20": struct{}{}, - "80df1254bc008f5685d572cbe74d950e747c6db7ca9d929f9965b3a57eee905c": struct{}{}, - "9dcce57c946f3fcd90c75710d1aeba7c7e6bd3e2dab68136cff840d2c4eaf247": struct{}{}, - "24645e9941912927a7471cdd9d35f4aa9ad45aa0161f563fc2f8e238c0d2d915": struct{}{}, - "3eba8e850b9b668e39ec508770fc8dbf5f53703c854dfcabae34794c3c6d651e": struct{}{}, - "a6e5da04f0ce13e7d641811ea2a7d887120e54639741127c94fa2cb695cd7a56": struct{}{}, - "d553700c2d8d6a59f2a8f71b12b25a43cb56ee3b313d6cb8fe0c575aa76e4f28": struct{}{}, - "99f78cb242dc2c852a75f6100f18fb8bab4bff24661c95a003e8fc5f0655bb07": struct{}{}, - "58d54e7c7553ce1fd758e8710a6a249e059578aead259fdf21a3fbd755be8129": struct{}{}, - "9143699b5f21f8fac25c77e9e3130c045ca313f91c4b118a5e8bce5e9c0ed808": struct{}{}, - "593bbd508494feaa9339fe728d8b5cca3890543639c10d88b5f34ba97cefd14a": struct{}{}, - "1eabb241235ad42337c73d976edce749ea3f275842398177105d0c82be8f9dce": struct{}{}, - "4e0488a8dfd46b1c42586dc7f7db1e6a094d49af3355562a97426156adfb02fb": struct{}{}, - "38769b4a2318be3deecdabb823147dba1c7c3c7301b243b37f4b3727de66b6ab": struct{}{}, - "7cc3860a712a2c87f567bfadfb8359d4ebae1cd2f58c5473cc2a2869cd8050cf": struct{}{}, - "c79e27276d22c4aacd2fed8c673f280831d2c9d4d89dc9cd130250c45a748d96": struct{}{}, - "32b2b95d31362e427236836e70115400702c837561fc07953ce615b43ece2e81": struct{}{}, - "ea10e5d86ff99f54a9a5ea8c29c9e9d03449b39380902347bcf53f509251cc18": struct{}{}, - "b27743b3deb68d5fc690079978ddbba3f4049b7167cf0c4ec0e2863ec4a4016b": struct{}{}, - "59fcab4e67b50786fde355d31eed5b16e680210418739a6aab7a2216435d7f85": struct{}{}, - "374c15111bb73600236262e5614d1219fc4c409ea798efc9d3fddb91e89c2e86": struct{}{}, - "07f2778edcb1066d0f0ab2ac457ea05b9d91cfeff90e45d7f6fe5d6ea1fa072a": struct{}{}, - "704f9d0993c9dc1175856032fba034e1a228eb4e5f4a8725269b99ce9e6cf62e": struct{}{}, - "e9c5162436ad1a16360979104c442393933da0fec5af3b3962bbea4dba3288bb": struct{}{}, - "47719a621c8ab2d58c487cff6c2c86af0e29eb9c96ac540d9a2b250fe71e4d93": struct{}{}, - "990b23e81455c9812dfd3afe18aa149cc142b3bc5567e0acd08287e51dc2cca4": struct{}{}, - "4c66eaa71ad574b5069c990c181d9e2e1e365397dd9acaa1fdfb22ab18c7cf80": struct{}{}, - "cfacfd3ec33b9608b59a343d05da204c18abf0eebac9aee076fe9d97a3acee87": struct{}{}, - "41ffd8b76afc92a65758fd9a080ae80421c196f251263b87cbfb6e567dda0879": struct{}{}, - "44c3e574dac5455e6749eefb5373a809552b1c947108270c8be760d03c02c4be": struct{}{}, - "ce21965214439e3139e103545894cc70bac788310ff78e25b1e5693b818c59ba": struct{}{}, - "8f1919fc6105ffbd022b2374ff13704e6f5354b1cb9a6b2d56a1c5d968aa4def": struct{}{}, - "e24bcaa6f1ddf021c700aab641ce06567bb96eeaa839f1f907a3da5d6b2e08cd": struct{}{}, - "662411b21a6e3e3025354954a29579614f158ede4c67638460d637febb5ee4a2": struct{}{}, - "c28943f9be78493eb39e7bc6fbe35016d69a8415f6c65d9f0f9ceefe1900427e": struct{}{}, - "86409473c22483a8c5abd076a037f101a6609127dba715905d1cc7d3debe130d": struct{}{}, - "80d4f92371f0c24be2388ec2d928c94ac6640185a63fb752268fde9f62676d0f": struct{}{}, - "4ef3e3cbab91de8726beaea3b1f0a61b0d4e6e3c4228ca3e8c51b1467def8dce": struct{}{}, - "51365d01e295763226b90cadb302b717b086f77b8fab3384996038163c2baf7d": struct{}{}, - "051b5e0d800398182c06c43925639f39d8c1d2f96f13ec0ce587e3beb13b59a5": struct{}{}, - "5fd03a37b4c780aedff838b860946a00680662b37c5834a89fb41b9c5f26d25f": struct{}{}, - "a34530c30dec412cd09ad5747602353de1d122dffb0a1b332c70dd8a54c0b54a": struct{}{}, - "2754226cc976fe6f3240f8cddc9c789485ea366495d7890f7444a1835c5be5f3": struct{}{}, - "91f4be5bca5af627647ead95d1bec85396b1b6bfee792e7d1e90ebb8707a87a4": struct{}{}, - "c934e4e2c5c4c84287caed79fb2cc5c0327d91ea287e2864fe78eb51aeec1612": struct{}{}, - "1b5f82125c8e908adde35729b861a9da3a9a86fbcc3f033e56b4ca9c02e3e2c3": struct{}{}, - "94a35fdc30df7d129a447217f9ffade7e4917bdcc90460d7db2f0460c08c0e06": struct{}{}, - "a1c2d05656b9041ce76a61d23aef096635931217a5cf8b8dd4249cc067ee3ac7": struct{}{}, - "1006ae573cb5314f4071bd0d07ca969e8e6889cc10b98d50072453e5a7eae928": struct{}{}, - "852fff143bc92629be9e686fe066ba378d8c7c9f09fef32f4dc3c8a6ed541606": struct{}{}, - "fb86c3312c8a149b7c123e76edc22a1315e2d300d048f4e92427ce53dcd3c319": struct{}{}, - "4f9684d4b7a1cdbe8ec15ec1d7e6c5ec4528bfc72866476298aea9ce6f54a69a": struct{}{}, - "7f254086775a14a848449fd32ef1affc9b7b0170330bf82deacd6a9f3a13b3ff": struct{}{}, - "d80c9bf910f144738ef983724bc04bd6bd3f17c5c83ed57bedee1b1b9278e811": struct{}{}, - "3957ccb4da0a2bb53ee14a5a14d7c61b4b2dcabf290668052c01cf18928f585f": struct{}{}, - "cccaba9e1f1d63df796b7b62c5b8acc8eba1011a554eba4b3a084ee42fbc2091": struct{}{}, - "2f9503517a96e3001c99a7993c2019a4a322d2f3025dedc6a18fdaabae67f1af": struct{}{}, - "bbfc6a4ab1fa616e2cb4217ef64dd22007027346297919ba915b6057bba15041": struct{}{}, - "81824c90f8a5276bc87ecd88f9a5903277a636589a38f6bbc34a2b991fb2a095": struct{}{}, - "0a223bf7646e99094cd09a4df4d76ee43233d17a9b671d1dc996a3ca2a4a9e87": struct{}{}, - "b03136e9801c99d70cb80a2c5a654f572dfd9a8fb6e6dde9fcbdcd16e2bc61a6": struct{}{}, - "7bfc97cd629ce18317c78a3698d0fab4f69319ea97ceb46421beacdb3e298cdd": struct{}{}, - "8930d399db295e02aa5922fa3c4e6cf1afcb11edea9dbdf42f75c1f2afb88b8b": struct{}{}, - "413e4403c43dcdc2c9e50801fc1aaad7e1cf74b787610d647f9ea89b4f043abc": struct{}{}, - "fee145f96480ff6e24cab20477197874673cf01c57559b552cefb352124bf60b": struct{}{}, - "9d4d5e8719cbc95daf30159e9e19973b45f93009c3cde7e7620baa604dce3e01": struct{}{}, - "061aa6caf59afbc89acd0dcdd5361b1f49002276b24b57f98215db0b004fab7b": struct{}{}, - "a5e69ec95b8badf43bf7293c97453c12a9ac571b23e364b08f5f10c833f0e7cb": struct{}{}, - "528c291e315592e6c54795d834f5cd08a45f46249fae5eb5ce2ee976485e9edf": struct{}{}, - "6bbdb7554ca9f1e446f671472ba8405a770c7a92533886bf37cd241bad4ef82c": struct{}{}, - "cbda153692ccc5e473b2ea4919301e46c970c048470f26657199c8b3f04ffe4c": struct{}{}, - "1b878c4a713f460c7d1c9c965c02f07b79e82dd95e992c3ab2f0e84021f6276d": struct{}{}, - "348f24c4fb95ebf669f66e8c59ab64266b55e67c35c2d91b39a39272081e88b0": struct{}{}, - "1ea689aa861d70251fc268df5571dd6c9a1f3f6504332561422a8166da4be25d": struct{}{}, - "3151fd35a5b3b649c3ad21d2d19be8dce252aabbaef810011c835ef888f10e78": struct{}{}, - "5f135a6aac944d20b7d7b57e17e53d96955c042437c4345243d7b0f4d371703c": struct{}{}, - "a23a64313fb38332884451a346d6be1ce36b4334acd64d102ff1aa6d159a3aba": struct{}{}, - "e0fe95d82207c2f08f2ce91538dae7fed46f9c678dab63a7029c22ab9de8d948": struct{}{}, - "885f94e35ab18d9dbe262d9a3c07c50fdff6a3583530477070f62ec5a4e5c192": struct{}{}, - "14b2ce63ca9bff5dd028e657617c8e492e979c777a0899d090cca1204b44aedc": struct{}{}, - "4b1d12cc03236971b6cd1d83b4afa56b3875363f58a7a4598ca8d0347b9d3e5a": struct{}{}, - "5696a451c4b28c503c3d1c9c6c542d8b908512d3fcf13c673d77d0e865b4f639": struct{}{}, - "99a5ec8dee37605d9718ee4d9bcee84fcf1af8b5ead8353e8163229a6087fec9": struct{}{}, - "bc1450c6854a5f66a6f5879b3cd8d128d550dd355afe22a170ea09f8927762d9": struct{}{}, - "70a4ce3720ccf427ec45994753b13302a538b793f073082f3a764e8e0d5d941c": struct{}{}, - "36e7b68ecbd24cd82409be3892ea2c32e6f817df9f6d63a637fbf71910767787": struct{}{}, - "a771302bdc19552080ce7d2ba755b3316e38c88604254943b239a038d3bca9c8": struct{}{}, - "5186646859c5b07676015f15c581e2514b2b778309f9422150a6032f826f388e": struct{}{}, - "0dbc3407a7080e3d39871b66208636c038ed9ffbd082421b6ad6b80b22f447f1": struct{}{}, - "db94814334e87ae3095dd41f153944532ec1c503c9fc9648074971e734265b06": struct{}{}, - "e9236ffd48e1b70240a01d9a5216d7843ac02583fc5603fbafe3dabedb365640": struct{}{}, - "d81db2b010e35d46344fce04c4112cd25da6178907cf362ea728e7a1ea80c152": struct{}{}, - "990547227e9b8f55660f8b46c2005ade8c6fb1c855bf05e16a30f14e156416e2": struct{}{}, -} - -func IsPasswordSecure(hash string) bool { - _, ok := passwords10000[hash] - return !ok -} From 84e6d23fcf3ecf08f9a3f9a5de4c66ce8bcfa625 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 19 May 2015 00:33:18 +0200 Subject: [PATCH 53/67] Updated Godeps --- Godeps/Godeps.json | 36 +- .../p/goprotobuf/proto/clone.go | 11 +- .../p/goprotobuf/proto/clone_test.go | 16 + .../p/goprotobuf/proto/decode.go | 2 +- .../p/goprotobuf/proto/encode.go | 28 +- .../p/goprotobuf/proto/equal.go | 2 +- .../p/goprotobuf/proto/extensions.go | 4 +- .../p/goprotobuf/proto/extensions_test.go | 34 + .../p/goprotobuf/proto/message_set.go | 64 +- .../p/goprotobuf/proto/properties.go | 6 +- .../p/goprotobuf/proto/testdata/test.pb.go | 6 + .../p/goprotobuf/proto/text.go | 12 +- .../p/goprotobuf/proto/text_parser.go | 55 +- .../p/goprotobuf/proto/text_parser_test.go | 10 +- .../p/snappy-go/snappy/decode.go | 172 +++- .../p/snappy-go/snappy/encode.go | 84 ++ .../p/snappy-go/snappy/snappy.go | 30 + .../p/snappy-go/snappy/snappy_test.go | 201 +++-- .../src/github.com/bitly/go-nsq/.travis.yml | 19 +- .../src/github.com/bitly/go-nsq/ChangeLog.md | 38 + .../src/github.com/bitly/go-nsq/README.md | 4 +- .../github.com/bitly/go-nsq/command_test.go | 5 - .../src/github.com/bitly/go-nsq/config.go | 182 ++++- .../github.com/bitly/go-nsq/config_test.go | 73 +- .../src/github.com/bitly/go-nsq/conn.go | 48 +- .../src/github.com/bitly/go-nsq/consumer.go | 289 ++++--- .../github.com/bitly/go-nsq/consumer_test.go | 9 + .../src/github.com/bitly/go-nsq/delegates.go | 34 +- .../src/github.com/bitly/go-nsq/errors.go | 3 + .../src/github.com/bitly/go-nsq/message.go | 8 +- .../src/github.com/bitly/go-nsq/mock_test.go | 259 +++++- .../src/github.com/bitly/go-nsq/producer.go | 77 +- .../github.com/bitly/go-nsq/producer_test.go | 135 ++- .../src/github.com/bitly/go-nsq/test.sh | 2 +- .../src/github.com/bitly/go-nsq/version.go | 2 +- .../bitly/go-simplejson/simplejson.go | 25 + .../bitly/go-simplejson/simplejson_test.go | 13 + .../github.com/coreos/go-etcd/etcd/client.go | 59 +- .../coreos/go-etcd/etcd/client_test.go | 6 +- .../github.com/coreos/go-etcd/etcd/cluster.go | 33 +- .../github.com/coreos/go-etcd/etcd/error.go | 3 +- .../src/github.com/coreos/go-etcd/etcd/get.go | 5 + .../github.com/coreos/go-etcd/etcd/options.go | 10 +- .../coreos/go-etcd/etcd/requests.go | 95 +-- .../coreos/go-etcd/etcd/set_curl_chan_test.go | 6 +- .../src/github.com/dchest/uniuri/.travis.yml | 14 +- .../src/github.com/dchest/uniuri/LICENSE | 19 - .../src/github.com/dchest/uniuri/README.md | 180 ++-- .../src/github.com/dchest/uniuri/uniuri.go | 36 +- .../github.com/dchest/uniuri/uniuri_test.go | 22 +- .../src/github.com/pzduniak/glogrus/LICENSE | 20 - .../src/github.com/pzduniak/glogrus/README.md | 36 - .../github.com/pzduniak/glogrus/glogrus.go | 80 -- .../pzduniak/glogrus/writer_proxy.go | 70 -- .../src/github.com/willf/bitset/.gitignore | 22 + .../src/github.com/willf/bitset/CHANGELOG | 10 + .../src/github.com/willf/bitset/LICENSE.txt | 27 + .../src/github.com/willf/bitset/README.md | 44 + .../src/github.com/willf/bitset/bitset.go | 614 ++++++++++++++ .../github.com/willf/bitset/bitset_test.go | 767 ++++++++++++++++++ .../src/github.com/willf/bitset/popcnt.go | 53 ++ .../github.com/willf/bitset/popcnt_amd64.s | 103 +++ .../src/github.com/willf/bitset/popcnt_asm.go | 64 ++ .../github.com/willf/bitset/popcnt_generic.go | 23 + .../src/github.com/willf/bloom/CHANGELOG | 8 + .../src/github.com/willf/bloom/LICENSE.txt | 24 + .../src/github.com/willf/bloom/README.md | 52 ++ .../src/github.com/willf/bloom/bloom.go | 297 +++++++ .../src/github.com/willf/bloom/bloom_test.go | 323 ++++++++ 69 files changed, 4279 insertions(+), 844 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE delete mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md delete mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go delete mode 100644 Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/.gitignore create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/CHANGELOG create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/LICENSE.txt create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/README.md create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/bitset.go create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/bitset_test.go create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/popcnt.go create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/popcnt_amd64.s create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/popcnt_asm.go create mode 100644 Godeps/_workspace/src/github.com/willf/bitset/popcnt_generic.go create mode 100644 Godeps/_workspace/src/github.com/willf/bloom/CHANGELOG create mode 100644 Godeps/_workspace/src/github.com/willf/bloom/LICENSE.txt create mode 100644 Godeps/_workspace/src/github.com/willf/bloom/README.md create mode 100644 Godeps/_workspace/src/github.com/willf/bloom/bloom.go create mode 100644 Godeps/_workspace/src/github.com/willf/bloom/bloom_test.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index c5ce0d4..a69c35b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -14,8 +14,8 @@ }, { "ImportPath": "code.google.com/p/goprotobuf/proto", - "Comment": "go.r60-152", - "Rev": "36be16571e14f67e114bb0af619e5de2c1591679" + "Comment": "go.r60-163", + "Rev": "9352842ae63ee1d7e74e074ce7bb10370c4b6b9e" }, { "ImportPath": "code.google.com/p/rsc/gf256", @@ -29,8 +29,8 @@ }, { "ImportPath": "code.google.com/p/snappy-go/snappy", - "Comment": "null-15", - "Rev": "12e4b4183793ac4b061921e7980845e750679fd0" + "Comment": "null-20", + "Rev": "8850bd446ad6f18a271a08aae26bdf85bdf87c7f" }, { "ImportPath": "github.com/GeertJohan/yubigo", @@ -48,13 +48,13 @@ }, { "ImportPath": "github.com/bitly/go-nsq", - "Comment": "v1.0.1-8-gba766e5", - "Rev": "ba766e5ff96fe184689976d5424d5b266bec4742" + "Comment": "v1.0.4-2-gd76cbe0", + "Rev": "d76cbe0746446bc6ff540042dde4d074a962dfde" }, { "ImportPath": "github.com/bitly/go-simplejson", - "Comment": "v0.4.3-28-g3378bdc", - "Rev": "3378bdcb5cebedcbf8b5750edee28010f128fe24" + "Comment": "v0.4.3-30-g18db6e6", + "Rev": "18db6e68d8fd9cbf2e8ebe4c81a78b96fd9bf05a" }, { "ImportPath": "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes", @@ -68,8 +68,8 @@ }, { "ImportPath": "github.com/coreos/go-etcd/etcd", - "Comment": "v0.2.0-rc1-130-g6aa2da5", - "Rev": "6aa2da5a7a905609c93036b9307185a04a5a84a5" + "Comment": "v0.4.6-10-g2af7a95", + "Rev": "2af7a9525b09fb715b215a1fb852dd4814ef52b5" }, { "ImportPath": "github.com/cupcake/sigil/gen", @@ -82,7 +82,7 @@ }, { "ImportPath": "github.com/dchest/uniuri", - "Rev": "9eb2cf09193567a3329b188db6e6f924a8e3347a" + "Rev": "396b73a566d0993675357e6648bb98587a6162b4" }, { "ImportPath": "github.com/garyburd/redigo/internal", @@ -126,10 +126,6 @@ "Comment": "v0.2.2-8-ga5260a3", "Rev": "a5260a307b3e7dd583283c1e2717445244d506c7" }, - { - "ImportPath": "github.com/pzduniak/glogrus", - "Rev": "7a43c02bc2d246e35cf2c88564ddb2ed3ac741d3" - }, { "ImportPath": "github.com/segmentio/go-loggly", "Comment": "v0.4.3-4-ge78f697", @@ -140,6 +136,16 @@ "Comment": "v2.0.0", "Rev": "ff4a55a20a86994118644bbddc6a216da193cc13" }, + { + "ImportPath": "github.com/willf/bitset", + "Comment": "v1.0.0-17-g4b22041", + "Rev": "4b220417a489359f934045d0509d941a7a2a1038" + }, + { + "ImportPath": "github.com/willf/bloom", + "Comment": "v1.0.0-38-g270f902", + "Rev": "270f902f15edc2c6ae171c30f1de3ce3ae59d30c" + }, { "ImportPath": "github.com/zenazn/goji/graceful", "Comment": "v0.8.3", diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go index 8b94d9e..bc988d2 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone.go @@ -125,6 +125,14 @@ func mergeAny(out, in reflect.Value) { if in.IsNil() { return } + if in.Type().Elem().Kind() == reflect.Uint8 { + // []byte is a scalar bytes field, not a repeated field. + // Make a deep copy. + // Append to []byte{} instead of []byte(nil) so that we never end up + // with a nil result. + out.SetBytes(append([]byte{}, in.Bytes()...)) + return + } n := in.Len() if out.IsNil() { out.Set(reflect.MakeSlice(in.Type(), 0, n)) @@ -133,9 +141,6 @@ func mergeAny(out, in reflect.Value) { case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64, reflect.String, reflect.Uint32, reflect.Uint64: out.Set(reflect.AppendSlice(out, in)) - case reflect.Uint8: - // []byte is a scalar bytes field. - out.Set(in) default: for i := 0; i < n; i++ { x := reflect.Indirect(reflect.New(in.Type().Elem())) diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go index 39aaaf7..522d40e 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/clone_test.go @@ -79,6 +79,22 @@ func TestClone(t *testing.T) { if proto.Equal(m, cloneTestMessage) { t.Error("Mutating clone changed the original") } + // Byte fields and repeated fields should be copied. + if &m.Pet[0] == &cloneTestMessage.Pet[0] { + t.Error("Pet: repeated field not copied") + } + if &m.Others[0] == &cloneTestMessage.Others[0] { + t.Error("Others: repeated field not copied") + } + if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] { + t.Error("Others[0].Value: bytes field not copied") + } + if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] { + t.Error("RepBytes: repeated field not copied") + } + if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] { + t.Error("RepBytes[0]: bytes field not copied") + } } func TestCloneNil(t *testing.T) { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go index 49a487a..cadee03 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/decode.go @@ -362,7 +362,7 @@ func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group } tag := int(u >> 3) if tag <= 0 { - return fmt.Errorf("proto: %s: illegal tag %d", st, tag) + return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire) } fieldnum, ok := prop.decoderTags.get(tag) if !ok { diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go index 35e5ad5..6fd29b2 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/encode.go @@ -247,7 +247,7 @@ func (p *Buffer) Marshal(pb Message) error { return ErrNil } if err == nil { - err = p.enc_struct(t.Elem(), GetProperties(t.Elem()), base) + err = p.enc_struct(GetProperties(t.Elem()), base) } if collectStats { @@ -271,7 +271,7 @@ func Size(pb Message) (n int) { return 0 } if err == nil { - n = size_struct(t.Elem(), GetProperties(t.Elem()), base) + n = size_struct(GetProperties(t.Elem()), base) } if collectStats { @@ -429,7 +429,7 @@ func (o *Buffer) enc_struct_message(p *Properties, base structPointer) error { } o.buf = append(o.buf, p.tagcode...) - return o.enc_len_struct(p.stype, p.sprop, structp, &state) + return o.enc_len_struct(p.sprop, structp, &state) } func size_struct_message(p *Properties, base structPointer) int { @@ -448,7 +448,7 @@ func size_struct_message(p *Properties, base structPointer) int { } n0 := len(p.tagcode) - n1 := size_struct(p.stype, p.sprop, structp) + n1 := size_struct(p.sprop, structp) n2 := sizeVarint(uint64(n1)) // size of encoded length return n0 + n1 + n2 } @@ -462,7 +462,7 @@ func (o *Buffer) enc_struct_group(p *Properties, base structPointer) error { } o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) - err := o.enc_struct(p.stype, p.sprop, b) + err := o.enc_struct(p.sprop, b) if err != nil && !state.shouldContinue(err, nil) { return err } @@ -477,7 +477,7 @@ func size_struct_group(p *Properties, base structPointer) (n int) { } n += sizeVarint(uint64((p.Tag << 3) | WireStartGroup)) - n += size_struct(p.stype, p.sprop, b) + n += size_struct(p.sprop, b) n += sizeVarint(uint64((p.Tag << 3) | WireEndGroup)) return } @@ -831,7 +831,7 @@ func (o *Buffer) enc_slice_struct_message(p *Properties, base structPointer) err } o.buf = append(o.buf, p.tagcode...) - err := o.enc_len_struct(p.stype, p.sprop, structp, &state) + err := o.enc_len_struct(p.sprop, structp, &state) if err != nil && !state.shouldContinue(err, nil) { if err == ErrNil { return ErrRepeatedHasNil @@ -861,7 +861,7 @@ func size_slice_struct_message(p *Properties, base structPointer) (n int) { continue } - n0 := size_struct(p.stype, p.sprop, structp) + n0 := size_struct(p.sprop, structp) n1 := sizeVarint(uint64(n0)) // size of encoded length n += n0 + n1 } @@ -882,7 +882,7 @@ func (o *Buffer) enc_slice_struct_group(p *Properties, base structPointer) error o.EncodeVarint(uint64((p.Tag << 3) | WireStartGroup)) - err := o.enc_struct(p.stype, p.sprop, b) + err := o.enc_struct(p.sprop, b) if err != nil && !state.shouldContinue(err, nil) { if err == ErrNil { @@ -908,7 +908,7 @@ func size_slice_struct_group(p *Properties, base structPointer) (n int) { return // return size up to this point } - n += size_struct(p.stype, p.sprop, b) + n += size_struct(p.sprop, b) } return } @@ -946,7 +946,7 @@ func size_map(p *Properties, base structPointer) int { } // Encode a struct. -func (o *Buffer) enc_struct(t reflect.Type, prop *StructProperties, base structPointer) error { +func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error { var state errorState // Encode fields in tag order so that decoders may use optimizations // that depend on the ordering. @@ -978,7 +978,7 @@ func (o *Buffer) enc_struct(t reflect.Type, prop *StructProperties, base structP return state.err } -func size_struct(t reflect.Type, prop *StructProperties, base structPointer) (n int) { +func size_struct(prop *StructProperties, base structPointer) (n int) { for _, i := range prop.order { p := prop.Prop[i] if p.size != nil { @@ -998,11 +998,11 @@ func size_struct(t reflect.Type, prop *StructProperties, base structPointer) (n var zeroes [20]byte // longer than any conceivable sizeVarint // Encode a struct, preceded by its encoded length (as a varint). -func (o *Buffer) enc_len_struct(t reflect.Type, prop *StructProperties, base structPointer, state *errorState) error { +func (o *Buffer) enc_len_struct(prop *StructProperties, base structPointer, state *errorState) error { iLen := len(o.buf) o.buf = append(o.buf, 0, 0, 0, 0) // reserve four bytes for length iMsg := len(o.buf) - err := o.enc_struct(t, prop, base) + err := o.enc_struct(prop, base) if err != nil && !state.shouldContinue(err, nil) { return err } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go index 42542e6..60dda9f 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/equal.go @@ -57,7 +57,7 @@ Equality is defined in this way: although represented by []byte, is not a repeated field) - Two unset fields are equal. - Two unknown field sets are equal if their current - encoded state is equal. (TODO) + encoded state is equal. - Two extension sets are equal iff they have corresponding elements that are pairwise equal. - Every other combination of things are not equal. diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go index e592053..fca7bde 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions.go @@ -227,7 +227,8 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er return nil, err } - e, ok := pb.ExtensionMap()[extension.Field] + emap := pb.ExtensionMap() + e, ok := emap[extension.Field] if !ok { return nil, ErrMissingExtension } @@ -252,6 +253,7 @@ func GetExtension(pb extendableProto, extension *ExtensionDesc) (interface{}, er e.value = v e.desc = extension e.enc = nil + emap[extension.Field] = e return e.value, nil } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go index 03f756b..f8ed150 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/extensions_test.go @@ -58,3 +58,37 @@ func TestGetExtensionsWithMissingExtensions(t *testing.T) { t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1]) } } + +func TestGetExtensionStability(t *testing.T) { + check := func(m *pb.MyMessage) bool { + ext1, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + ext2, err := proto.GetExtension(m, pb.E_Ext_More) + if err != nil { + t.Fatalf("GetExtension() failed: %s", err) + } + return ext1 == ext2 + } + msg := &pb.MyMessage{Count: proto.Int32(4)} + ext0 := &pb.Ext{} + if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil { + t.Fatalf("Could not set ext1: %s", ext0) + } + if !check(msg) { + t.Errorf("GetExtension() not stable before marshaling") + } + bb, err := proto.Marshal(msg) + if err != nil { + t.Fatalf("Marshal() failed: %s", err) + } + msg1 := &pb.MyMessage{} + err = proto.Unmarshal(bb, msg1) + if err != nil { + t.Fatalf("Unmarshal() failed: %s", err) + } + if !check(msg1) { + t.Errorf("GetExtension() not stable after unmarshaling") + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go index 1a17809..5504855 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/message_set.go @@ -36,7 +36,10 @@ package proto */ import ( + "bytes" + "encoding/json" "errors" + "fmt" "reflect" "sort" ) @@ -211,6 +214,61 @@ func UnmarshalMessageSet(buf []byte, m map[int32]Extension) error { return nil } +// MarshalMessageSetJSON encodes the extension map represented by m in JSON format. +// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option. +func MarshalMessageSetJSON(m map[int32]Extension) ([]byte, error) { + var b bytes.Buffer + b.WriteByte('{') + + // Process the map in key order for deterministic output. + ids := make([]int32, 0, len(m)) + for id := range m { + ids = append(ids, id) + } + sort.Sort(int32Slice(ids)) // int32Slice defined in text.go + + for i, id := range ids { + ext := m[id] + if i > 0 { + b.WriteByte(',') + } + + msd, ok := messageSetMap[id] + if !ok { + // Unknown type; we can't render it, so skip it. + continue + } + fmt.Fprintf(&b, `"[%s]":`, msd.name) + + x := ext.value + if x == nil { + x = reflect.New(msd.t.Elem()).Interface() + if err := Unmarshal(ext.enc, x.(Message)); err != nil { + return nil, err + } + } + d, err := json.Marshal(x) + if err != nil { + return nil, err + } + b.Write(d) + } + b.WriteByte('}') + return b.Bytes(), nil +} + +// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format. +// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option. +func UnmarshalMessageSetJSON(buf []byte, m map[int32]Extension) error { + // Common-case fast path. + if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) { + return nil + } + + // This is fairly tricky, and it's not clear that it is needed. + return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented") +} + // A global registry of types that can be used in a MessageSet. var messageSetMap = make(map[int32]messageSetDesc) @@ -221,9 +279,9 @@ type messageSetDesc struct { } // RegisterMessageSetType is called from the generated code. -func RegisterMessageSetType(i messageTypeIder, name string) { - messageSetMap[i.MessageTypeId()] = messageSetDesc{ - t: reflect.TypeOf(i), +func RegisterMessageSetType(m Message, fieldNum int32, name string) { + messageSetMap[fieldNum] = messageSetDesc{ + t: reflect.TypeOf(m), name: name, } } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go index f2c1e86..0ed1497 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/properties.go @@ -300,7 +300,7 @@ func (p *Properties) setEncAndDec(typ reflect.Type, lockGetProp bool) { switch t1 := typ; t1.Kind() { default: - fmt.Fprintf(os.Stderr, "proto: no coders for %T\n", t1) + fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1) case reflect.Ptr: switch t2 := t1.Elem(); t2.Kind() { @@ -538,7 +538,11 @@ var ( ) // GetProperties returns the list of properties for the type represented by t. +// t must represent a generated struct type of a protocol message. func GetProperties(t reflect.Type) *StructProperties { + if t.Kind() != reflect.Struct { + panic("proto: type must have kind struct") + } mutex.Lock() sprop := getPropertiesLocked(t) mutex.Unlock() diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go index 5a9a603..f3ece60 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/testdata/test.pb.go @@ -1416,6 +1416,12 @@ func (m *MyMessageSet) Marshal() ([]byte, error) { func (m *MyMessageSet) Unmarshal(buf []byte) error { return proto.UnmarshalMessageSet(buf, m.ExtensionMap()) } +func (m *MyMessageSet) MarshalJSON() ([]byte, error) { + return proto.MarshalMessageSetJSON(m.XXX_extensions) +} +func (m *MyMessageSet) UnmarshalJSON(buf []byte) error { + return proto.UnmarshalMessageSetJSON(buf, m.XXX_extensions) +} // ensure MyMessageSet satisfies proto.Marshaler and proto.Unmarshaler var _ proto.Marshaler = (*MyMessageSet)(nil) diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go index 5063009..a6fd69c 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text.go @@ -36,6 +36,7 @@ package proto import ( "bufio" "bytes" + "encoding" "fmt" "io" "log" @@ -74,13 +75,6 @@ type textWriter struct { w writer } -// textMarshaler is implemented by Messages that can marshal themsleves. -// It is identical to encoding.TextMarshaler, introduced in go 1.2, -// which will eventually replace it. -type textMarshaler interface { - MarshalText() (text []byte, err error) -} - func (w *textWriter) WriteString(s string) (n int, err error) { if !strings.Contains(s, "\n") { if !w.compact && w.complete { @@ -358,7 +352,7 @@ func writeAny(w *textWriter, v reflect.Value, props *Properties) error { } } w.indent() - if tm, ok := v.Interface().(textMarshaler); ok { + if tm, ok := v.Interface().(encoding.TextMarshaler); ok { text, err := tm.MarshalText() if err != nil { return err @@ -653,7 +647,7 @@ func marshalText(w io.Writer, pb Message, compact bool) error { compact: compact, } - if tm, ok := pb.(textMarshaler); ok { + if tm, ok := pb.(encoding.TextMarshaler); ok { text, err := tm.MarshalText() if err != nil { return err diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go index d235ca9..dc477c9 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser.go @@ -35,6 +35,7 @@ package proto // TODO: message sets. import ( + "encoding" "errors" "fmt" "reflect" @@ -43,13 +44,6 @@ import ( "unicode/utf8" ) -// textUnmarshaler is implemented by Messages that can unmarshal themsleves. -// It is identical to encoding.TextUnmarshaler, introduced in go 1.2, -// which will eventually replace it. -type textUnmarshaler interface { - UnmarshalText(text []byte) error -} - type ParseError struct { Message string Line int // 1-based line number @@ -361,8 +355,8 @@ func (p *textParser) next() *token { return &p.cur } -// Return an error indicating which required field was not set. -func (p *textParser) missingRequiredFieldError(sv reflect.Value) *ParseError { +// Return a RequiredNotSetError indicating which required field was not set. +func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError { st := sv.Type() sprops := GetProperties(st) for i := 0; i < st.NumField(); i++ { @@ -372,10 +366,10 @@ func (p *textParser) missingRequiredFieldError(sv reflect.Value) *ParseError { props := sprops.Prop[i] if props.Required { - return p.errorf("message %v missing required field %q", st, props.OrigName) + return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)} } } - return p.errorf("message %v missing required field", st) // should not happen + return &RequiredNotSetError{fmt.Sprintf("%v.", st)} // should not happen } // Returns the index in the struct for the named field, as well as the parsed tag properties. @@ -426,9 +420,11 @@ func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseEr return nil } -func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError { +func (p *textParser) readStruct(sv reflect.Value, terminator string) error { st := sv.Type() reqCount := GetProperties(st).reqCount + var reqFieldErr error + fieldSet := make(map[string]bool) // A struct is a sequence of "name: value", terminated by one of // '>' or '}', or the end of the input. A name may also be // "[extension]". @@ -489,7 +485,10 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError ext = reflect.New(typ.Elem()).Elem() } if err := p.readAny(ext, props); err != nil { - return err + if _, ok := err.(*RequiredNotSetError); !ok { + return err + } + reqFieldErr = err } ep := sv.Addr().Interface().(extendableProto) if !rep { @@ -507,17 +506,17 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError } } else { // This is a normal, non-extension field. - fi, props, ok := structFieldByName(st, tok.value) + name := tok.value + fi, props, ok := structFieldByName(st, name) if !ok { - return p.errorf("unknown field name %q in %v", tok.value, st) + return p.errorf("unknown field name %q in %v", name, st) } dst := sv.Field(fi) - isDstNil := isNil(dst) // Check that it's not already set if it's not a repeated field. - if !props.Repeated && !isDstNil { - return p.errorf("non-repeated field %q was repeated", tok.value) + if !props.Repeated && fieldSet[name] { + return p.errorf("non-repeated field %q was repeated", name) } if err := p.checkForColon(props, st.Field(fi).Type); err != nil { @@ -525,11 +524,13 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError } // Parse into the field. + fieldSet[name] = true if err := p.readAny(dst, props); err != nil { - return err - } - - if props.Required { + if _, ok := err.(*RequiredNotSetError); !ok { + return err + } + reqFieldErr = err + } else if props.Required { reqCount-- } } @@ -547,10 +548,10 @@ func (p *textParser) readStruct(sv reflect.Value, terminator string) *ParseError if reqCount > 0 { return p.missingRequiredFieldError(sv) } - return nil + return reqFieldErr } -func (p *textParser) readAny(v reflect.Value, props *Properties) *ParseError { +func (p *textParser) readAny(v reflect.Value, props *Properties) error { tok := p.next() if tok.err != nil { return tok.err @@ -652,7 +653,7 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) *ParseError { default: return p.errorf("expected '{' or '<', found %q", tok.value) } - // TODO: Handle nested messages which implement textUnmarshaler. + // TODO: Handle nested messages which implement encoding.TextUnmarshaler. return p.readStruct(fv, terminator) case reflect.Uint32: if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil { @@ -670,8 +671,10 @@ func (p *textParser) readAny(v reflect.Value, props *Properties) *ParseError { // UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb // before starting to unmarshal, so any existing data in pb is always removed. +// If a required field is not set and no other error occurs, +// UnmarshalText returns *RequiredNotSetError. func UnmarshalText(s string, pb Message) error { - if um, ok := pb.(textUnmarshaler); ok { + if um, ok := pb.(encoding.TextUnmarshaler); ok { err := um.UnmarshalText([]byte(s)) return err } diff --git a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go index d043769..7cdd579 100644 --- a/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go +++ b/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto/text_parser_test.go @@ -294,8 +294,11 @@ var unMarshalTextTests = []UnmarshalTextTest{ // Missing required field { - in: ``, - err: `line 1.0: message testdata.MyMessage missing required field "count"`, + in: `name: "Pawel"`, + err: `proto: required field "testdata.MyMessage.count" not set`, + out: &MyMessage{ + Name: String("Pawel"), + }, }, // Repeated non-repeated field @@ -408,6 +411,9 @@ func TestUnmarshalText(t *testing.T) { } else if err.Error() != test.err { t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v", i, err.Error(), test.err) + } else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) { + t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v", + i, pb, test.out) } } } diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go index d93c1b9..552a17b 100644 --- a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/decode.go @@ -7,10 +7,15 @@ package snappy import ( "encoding/binary" "errors" + "io" ) -// ErrCorrupt reports that the input is invalid. -var ErrCorrupt = errors.New("snappy: corrupt input") +var ( + // ErrCorrupt reports that the input is invalid. + ErrCorrupt = errors.New("snappy: corrupt input") + // ErrUnsupported reports that the input isn't supported. + ErrUnsupported = errors.New("snappy: unsupported input") +) // DecodedLen returns the length of the decoded block. func DecodedLen(src []byte) (int, error) { @@ -122,3 +127,166 @@ func Decode(dst, src []byte) ([]byte, error) { } return dst[:d], nil } + +// NewReader returns a new Reader that decompresses from r, using the framing +// format described at +// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt +func NewReader(r io.Reader) *Reader { + return &Reader{ + r: r, + decoded: make([]byte, maxUncompressedChunkLen), + buf: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)+checksumSize), + } +} + +// Reader is an io.Reader than can read Snappy-compressed bytes. +type Reader struct { + r io.Reader + err error + decoded []byte + buf []byte + // decoded[i:j] contains decoded bytes that have not yet been passed on. + i, j int + readHeader bool +} + +// Reset discards any buffered data, resets all state, and switches the Snappy +// reader to read from r. This permits reusing a Reader rather than allocating +// a new one. +func (r *Reader) Reset(reader io.Reader) { + r.r = reader + r.err = nil + r.i = 0 + r.j = 0 + r.readHeader = false +} + +func (r *Reader) readFull(p []byte) (ok bool) { + if _, r.err = io.ReadFull(r.r, p); r.err != nil { + if r.err == io.ErrUnexpectedEOF { + r.err = ErrCorrupt + } + return false + } + return true +} + +// Read satisfies the io.Reader interface. +func (r *Reader) Read(p []byte) (int, error) { + if r.err != nil { + return 0, r.err + } + for { + if r.i < r.j { + n := copy(p, r.decoded[r.i:r.j]) + r.i += n + return n, nil + } + if !r.readFull(r.buf[:4]) { + return 0, r.err + } + chunkType := r.buf[0] + if !r.readHeader { + if chunkType != chunkTypeStreamIdentifier { + r.err = ErrCorrupt + return 0, r.err + } + r.readHeader = true + } + chunkLen := int(r.buf[1]) | int(r.buf[2])<<8 | int(r.buf[3])<<16 + if chunkLen > len(r.buf) { + r.err = ErrUnsupported + return 0, r.err + } + + // The chunk types are specified at + // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt + switch chunkType { + case chunkTypeCompressedData: + // Section 4.2. Compressed data (chunk type 0x00). + if chunkLen < checksumSize { + r.err = ErrCorrupt + return 0, r.err + } + buf := r.buf[:chunkLen] + if !r.readFull(buf) { + return 0, r.err + } + checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 + buf = buf[checksumSize:] + + n, err := DecodedLen(buf) + if err != nil { + r.err = err + return 0, r.err + } + if n > len(r.decoded) { + r.err = ErrCorrupt + return 0, r.err + } + if _, err := Decode(r.decoded, buf); err != nil { + r.err = err + return 0, r.err + } + if crc(r.decoded[:n]) != checksum { + r.err = ErrCorrupt + return 0, r.err + } + r.i, r.j = 0, n + continue + + case chunkTypeUncompressedData: + // Section 4.3. Uncompressed data (chunk type 0x01). + if chunkLen < checksumSize { + r.err = ErrCorrupt + return 0, r.err + } + buf := r.buf[:checksumSize] + if !r.readFull(buf) { + return 0, r.err + } + checksum := uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24 + // Read directly into r.decoded instead of via r.buf. + n := chunkLen - checksumSize + if !r.readFull(r.decoded[:n]) { + return 0, r.err + } + if crc(r.decoded[:n]) != checksum { + r.err = ErrCorrupt + return 0, r.err + } + r.i, r.j = 0, n + continue + + case chunkTypeStreamIdentifier: + // Section 4.1. Stream identifier (chunk type 0xff). + if chunkLen != len(magicBody) { + r.err = ErrCorrupt + return 0, r.err + } + if !r.readFull(r.buf[:len(magicBody)]) { + return 0, r.err + } + for i := 0; i < len(magicBody); i++ { + if r.buf[i] != magicBody[i] { + r.err = ErrCorrupt + return 0, r.err + } + } + continue + } + + if chunkType <= 0x7f { + // Section 4.5. Reserved unskippable chunks (chunk types 0x02-0x7f). + r.err = ErrUnsupported + return 0, r.err + + } else { + // Section 4.4 Padding (chunk type 0xfe). + // Section 4.6. Reserved skippable chunks (chunk types 0x80-0xfd). + if !r.readFull(r.buf[:chunkLen]) { + return 0, r.err + } + } + } +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go index b2371db..dda3724 100644 --- a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/encode.go @@ -6,6 +6,7 @@ package snappy import ( "encoding/binary" + "io" ) // We limit how far copy back-references can go, the same as the C++ code. @@ -172,3 +173,86 @@ func MaxEncodedLen(srcLen int) int { // This last factor dominates the blowup, so the final estimate is: return 32 + srcLen + srcLen/6 } + +// NewWriter returns a new Writer that compresses to w, using the framing +// format described at +// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt +func NewWriter(w io.Writer) *Writer { + return &Writer{ + w: w, + enc: make([]byte, MaxEncodedLen(maxUncompressedChunkLen)), + } +} + +// Writer is an io.Writer than can write Snappy-compressed bytes. +type Writer struct { + w io.Writer + err error + enc []byte + buf [checksumSize + chunkHeaderSize]byte + wroteHeader bool +} + +// Reset discards the writer's state and switches the Snappy writer to write to +// w. This permits reusing a Writer rather than allocating a new one. +func (w *Writer) Reset(writer io.Writer) { + w.w = writer + w.err = nil + w.wroteHeader = false +} + +// Write satisfies the io.Writer interface. +func (w *Writer) Write(p []byte) (n int, errRet error) { + if w.err != nil { + return 0, w.err + } + if !w.wroteHeader { + copy(w.enc, magicChunk) + if _, err := w.w.Write(w.enc[:len(magicChunk)]); err != nil { + w.err = err + return n, err + } + w.wroteHeader = true + } + for len(p) > 0 { + var uncompressed []byte + if len(p) > maxUncompressedChunkLen { + uncompressed, p = p[:maxUncompressedChunkLen], p[maxUncompressedChunkLen:] + } else { + uncompressed, p = p, nil + } + checksum := crc(uncompressed) + + // Compress the buffer, discarding the result if the improvement + // isn't at least 12.5%. + chunkType := uint8(chunkTypeCompressedData) + chunkBody, err := Encode(w.enc, uncompressed) + if err != nil { + w.err = err + return n, err + } + if len(chunkBody) >= len(uncompressed)-len(uncompressed)/8 { + chunkType, chunkBody = chunkTypeUncompressedData, uncompressed + } + + chunkLen := 4 + len(chunkBody) + w.buf[0] = chunkType + w.buf[1] = uint8(chunkLen >> 0) + w.buf[2] = uint8(chunkLen >> 8) + w.buf[3] = uint8(chunkLen >> 16) + w.buf[4] = uint8(checksum >> 0) + w.buf[5] = uint8(checksum >> 8) + w.buf[6] = uint8(checksum >> 16) + w.buf[7] = uint8(checksum >> 24) + if _, err = w.w.Write(w.buf[:]); err != nil { + w.err = err + return n, err + } + if _, err = w.w.Write(chunkBody); err != nil { + w.err = err + return n, err + } + n += len(uncompressed) + } + return n, nil +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go index 2f1b790..043bf3d 100644 --- a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy.go @@ -8,6 +8,10 @@ // The C++ snappy implementation is at http://code.google.com/p/snappy/ package snappy +import ( + "hash/crc32" +) + /* Each encoded block begins with the varint-encoded length of the decoded data, followed by a sequence of chunks. Chunks begin and end on byte boundaries. The @@ -36,3 +40,29 @@ const ( tagCopy2 = 0x02 tagCopy4 = 0x03 ) + +const ( + checksumSize = 4 + chunkHeaderSize = 4 + magicChunk = "\xff\x06\x00\x00" + magicBody + magicBody = "sNaPpY" + // https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt says + // that "the uncompressed data in a chunk must be no longer than 65536 bytes". + maxUncompressedChunkLen = 65536 +) + +const ( + chunkTypeCompressedData = 0x00 + chunkTypeUncompressedData = 0x01 + chunkTypePadding = 0xfe + chunkTypeStreamIdentifier = 0xff +) + +var crcTable = crc32.MakeTable(crc32.Castagnoli) + +// crc implements the checksum specified in section 3 of +// https://code.google.com/p/snappy/source/browse/trunk/framing_format.txt +func crc(b []byte) uint32 { + c := crc32.Update(0, crcTable, b) + return uint32(c>>15|c<<17) + 0xa282ead8 +} diff --git a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go index 7ba8392..0623385 100644 --- a/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go +++ b/Godeps/_workspace/src/code.google.com/p/snappy-go/snappy/snappy_test.go @@ -18,7 +18,10 @@ import ( "testing" ) -var download = flag.Bool("download", false, "If true, download any missing files before running benchmarks") +var ( + download = flag.Bool("download", false, "If true, download any missing files before running benchmarks") + testdata = flag.String("testdata", "testdata", "Directory containing the test data") +) func roundtrip(b, ebuf, dbuf []byte) error { e, err := Encode(ebuf, b) @@ -55,11 +58,11 @@ func TestSmallCopy(t *testing.T) { } func TestSmallRand(t *testing.T) { - rand.Seed(27354294) + rng := rand.New(rand.NewSource(27354294)) for n := 1; n < 20000; n += 23 { b := make([]byte, n) - for i, _ := range b { - b[i] = uint8(rand.Uint32()) + for i := range b { + b[i] = uint8(rng.Uint32()) } if err := roundtrip(b, nil, nil); err != nil { t.Fatal(err) @@ -70,7 +73,7 @@ func TestSmallRand(t *testing.T) { func TestSmallRegular(t *testing.T) { for n := 1; n < 20000; n += 23 { b := make([]byte, n) - for i, _ := range b { + for i := range b { b[i] = uint8(i%10 + 'a') } if err := roundtrip(b, nil, nil); err != nil { @@ -79,6 +82,120 @@ func TestSmallRegular(t *testing.T) { } } +func cmp(a, b []byte) error { + if len(a) != len(b) { + return fmt.Errorf("got %d bytes, want %d", len(a), len(b)) + } + for i := range a { + if a[i] != b[i] { + return fmt.Errorf("byte #%d: got 0x%02x, want 0x%02x", i, a[i], b[i]) + } + } + return nil +} + +func TestFramingFormat(t *testing.T) { + // src is comprised of alternating 1e5-sized sequences of random + // (incompressible) bytes and repeated (compressible) bytes. 1e5 was chosen + // because it is larger than maxUncompressedChunkLen (64k). + src := make([]byte, 1e6) + rng := rand.New(rand.NewSource(1)) + for i := 0; i < 10; i++ { + if i%2 == 0 { + for j := 0; j < 1e5; j++ { + src[1e5*i+j] = uint8(rng.Intn(256)) + } + } else { + for j := 0; j < 1e5; j++ { + src[1e5*i+j] = uint8(i) + } + } + } + + buf := new(bytes.Buffer) + if _, err := NewWriter(buf).Write(src); err != nil { + t.Fatalf("Write: encoding: %v", err) + } + dst, err := ioutil.ReadAll(NewReader(buf)) + if err != nil { + t.Fatalf("ReadAll: decoding: %v", err) + } + if err := cmp(dst, src); err != nil { + t.Fatal(err) + } +} + +func TestReaderReset(t *testing.T) { + gold := bytes.Repeat([]byte("All that is gold does not glitter,\n"), 10000) + buf := new(bytes.Buffer) + if _, err := NewWriter(buf).Write(gold); err != nil { + t.Fatalf("Write: %v", err) + } + encoded, invalid, partial := buf.String(), "invalid", "partial" + r := NewReader(nil) + for i, s := range []string{encoded, invalid, partial, encoded, partial, invalid, encoded, encoded} { + if s == partial { + r.Reset(strings.NewReader(encoded)) + if _, err := r.Read(make([]byte, 101)); err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + continue + } + r.Reset(strings.NewReader(s)) + got, err := ioutil.ReadAll(r) + switch s { + case encoded: + if err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + if err := cmp(got, gold); err != nil { + t.Errorf("#%d: %v", i, err) + continue + } + case invalid: + if err == nil { + t.Errorf("#%d: got nil error, want non-nil", i) + continue + } + } + } +} + +func TestWriterReset(t *testing.T) { + gold := bytes.Repeat([]byte("Not all those who wander are lost;\n"), 10000) + var gots, wants [][]byte + const n = 20 + w, failed := NewWriter(nil), false + for i := 0; i <= n; i++ { + buf := new(bytes.Buffer) + w.Reset(buf) + want := gold[:len(gold)*i/n] + if _, err := w.Write(want); err != nil { + t.Errorf("#%d: Write: %v", i, err) + failed = true + continue + } + got, err := ioutil.ReadAll(NewReader(buf)) + if err != nil { + t.Errorf("#%d: ReadAll: %v", i, err) + failed = true + continue + } + gots = append(gots, got) + wants = append(wants, want) + } + if failed { + return + } + for i := range gots { + if err := cmp(gots[i], wants[i]); err != nil { + t.Errorf("#%d: %v", i, err) + } + } +} + func benchDecode(b *testing.B, src []byte) { encoded, err := Encode(nil, src) if err != nil { @@ -102,7 +219,7 @@ func benchEncode(b *testing.B, src []byte) { } } -func readFile(b *testing.B, filename string) []byte { +func readFile(b testing.TB, filename string) []byte { src, err := ioutil.ReadFile(filename) if err != nil { b.Fatalf("failed reading %s: %s", filename, err) @@ -144,7 +261,7 @@ func BenchmarkWordsEncode1e5(b *testing.B) { benchWords(b, 1e5, false) } func BenchmarkWordsEncode1e6(b *testing.B) { benchWords(b, 1e6, false) } // testFiles' values are copied directly from -// https://code.google.com/p/snappy/source/browse/trunk/snappy_unittest.cc. +// https://raw.githubusercontent.com/google/snappy/master/snappy_unittest.cc // The label field is unused in snappy-go. var testFiles = []struct { label string @@ -152,29 +269,36 @@ var testFiles = []struct { }{ {"html", "html"}, {"urls", "urls.10K"}, - {"jpg", "house.jpg"}, - {"pdf", "mapreduce-osdi-1.pdf"}, + {"jpg", "fireworks.jpeg"}, + {"jpg_200", "fireworks.jpeg"}, + {"pdf", "paper-100k.pdf"}, {"html4", "html_x_4"}, - {"cp", "cp.html"}, - {"c", "fields.c"}, - {"lsp", "grammar.lsp"}, - {"xls", "kennedy.xls"}, {"txt1", "alice29.txt"}, {"txt2", "asyoulik.txt"}, {"txt3", "lcet10.txt"}, {"txt4", "plrabn12.txt"}, - {"bin", "ptt5"}, - {"sum", "sum"}, - {"man", "xargs.1"}, {"pb", "geo.protodata"}, {"gaviota", "kppkn.gtb"}, } // The test data files are present at this canonical URL. -const baseURL = "https://snappy.googlecode.com/svn/trunk/testdata/" +const baseURL = "https://raw.githubusercontent.com/google/snappy/master/testdata/" func downloadTestdata(basename string) (errRet error) { - filename := filepath.Join("testdata", basename) + filename := filepath.Join(*testdata, basename) + if stat, err := os.Stat(filename); err == nil && stat.Size() != 0 { + return nil + } + + if !*download { + return fmt.Errorf("test data not found; skipping benchmark without the -download flag") + } + // Download the official snappy C++ implementation reference test data + // files for benchmarking. + if err := os.Mkdir(*testdata, 0777); err != nil && !os.IsExist(err) { + return fmt.Errorf("failed to create testdata: %s", err) + } + f, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create %s: %s", filename, err) @@ -185,36 +309,27 @@ func downloadTestdata(basename string) (errRet error) { os.Remove(filename) } }() - resp, err := http.Get(baseURL + basename) + url := baseURL + basename + resp, err := http.Get(url) if err != nil { - return fmt.Errorf("failed to download %s: %s", baseURL+basename, err) + return fmt.Errorf("failed to download %s: %s", url, err) } defer resp.Body.Close() + if s := resp.StatusCode; s != http.StatusOK { + return fmt.Errorf("downloading %s: HTTP status code %d (%s)", url, s, http.StatusText(s)) + } _, err = io.Copy(f, resp.Body) if err != nil { - return fmt.Errorf("failed to write %s: %s", filename, err) + return fmt.Errorf("failed to download %s to %s: %s", url, filename, err) } return nil } func benchFile(b *testing.B, n int, decode bool) { - filename := filepath.Join("testdata", testFiles[n].filename) - if stat, err := os.Stat(filename); err != nil || stat.Size() == 0 { - if !*download { - b.Fatal("test data not found; skipping benchmark without the -download flag") - } - // Download the official snappy C++ implementation reference test data - // files for benchmarking. - if err := os.Mkdir("testdata", 0777); err != nil && !os.IsExist(err) { - b.Fatalf("failed to create testdata: %s", err) - } - for _, tf := range testFiles { - if err := downloadTestdata(tf.filename); err != nil { - b.Fatalf("failed to download testdata: %s", err) - } - } + if err := downloadTestdata(testFiles[n].filename); err != nil { + b.Fatalf("failed to download testdata: %s", err) } - data := readFile(b, filename) + data := readFile(b, filepath.Join(*testdata, testFiles[n].filename)) if decode { benchDecode(b, data) } else { @@ -235,12 +350,6 @@ func Benchmark_UFlat8(b *testing.B) { benchFile(b, 8, true) } func Benchmark_UFlat9(b *testing.B) { benchFile(b, 9, true) } func Benchmark_UFlat10(b *testing.B) { benchFile(b, 10, true) } func Benchmark_UFlat11(b *testing.B) { benchFile(b, 11, true) } -func Benchmark_UFlat12(b *testing.B) { benchFile(b, 12, true) } -func Benchmark_UFlat13(b *testing.B) { benchFile(b, 13, true) } -func Benchmark_UFlat14(b *testing.B) { benchFile(b, 14, true) } -func Benchmark_UFlat15(b *testing.B) { benchFile(b, 15, true) } -func Benchmark_UFlat16(b *testing.B) { benchFile(b, 16, true) } -func Benchmark_UFlat17(b *testing.B) { benchFile(b, 17, true) } func Benchmark_ZFlat0(b *testing.B) { benchFile(b, 0, false) } func Benchmark_ZFlat1(b *testing.B) { benchFile(b, 1, false) } func Benchmark_ZFlat2(b *testing.B) { benchFile(b, 2, false) } @@ -253,9 +362,3 @@ func Benchmark_ZFlat8(b *testing.B) { benchFile(b, 8, false) } func Benchmark_ZFlat9(b *testing.B) { benchFile(b, 9, false) } func Benchmark_ZFlat10(b *testing.B) { benchFile(b, 10, false) } func Benchmark_ZFlat11(b *testing.B) { benchFile(b, 11, false) } -func Benchmark_ZFlat12(b *testing.B) { benchFile(b, 12, false) } -func Benchmark_ZFlat13(b *testing.B) { benchFile(b, 13, false) } -func Benchmark_ZFlat14(b *testing.B) { benchFile(b, 14, false) } -func Benchmark_ZFlat15(b *testing.B) { benchFile(b, 15, false) } -func Benchmark_ZFlat16(b *testing.B) { benchFile(b, 16, false) } -func Benchmark_ZFlat17(b *testing.B) { benchFile(b, 17, false) } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml b/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml index 7ecdd6d..d732750 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/.travis.yml @@ -1,27 +1,28 @@ language: go go: - - 1.2.2 - - 1.3.1 + - 1.4.2 env: - - NSQ_DOWNLOAD=nsq-0.2.24.linux-amd64.go1.2 GOARCH=amd64 - - NSQ_DOWNLOAD=nsq-0.2.24.linux-amd64.go1.2 GOARCH=386 - - NSQ_DOWNLOAD=nsq-0.2.27.linux-amd64.go1.2 GOARCH=amd64 - - NSQ_DOWNLOAD=nsq-0.2.27.linux-amd64.go1.2 GOARCH=386 - - NSQ_DOWNLOAD=nsq-0.2.28.linux-amd64.go1.2.1 GOARCH=amd64 - - NSQ_DOWNLOAD=nsq-0.2.28.linux-amd64.go1.2.1 GOARCH=386 - NSQ_DOWNLOAD=nsq-0.2.30.linux-amd64.go1.3 GOARCH=amd64 - NSQ_DOWNLOAD=nsq-0.2.30.linux-amd64.go1.3 GOARCH=386 - NSQ_DOWNLOAD=nsq-0.2.31.linux-amd64.go1.3.1 GOARCH=amd64 - NSQ_DOWNLOAD=nsq-0.2.31.linux-amd64.go1.3.1 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.3.0.linux-amd64.go1.3.3 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.3.0.linux-amd64.go1.3.3 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.3.1.linux-amd64.go1.4.1 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.3.1.linux-amd64.go1.4.1 GOARCH=386 + - NSQ_DOWNLOAD=nsq-0.3.2.linux-amd64.go1.4.1 GOARCH=amd64 + - NSQ_DOWNLOAD=nsq-0.3.2.linux-amd64.go1.4.1 GOARCH=386 install: - go get github.com/bitly/go-simplejson - go get github.com/mreiferson/go-snappystream script: - wget http://bitly-downloads.s3.amazonaws.com/nsq/$NSQ_DOWNLOAD.tar.gz - tar zxvf $NSQ_DOWNLOAD.tar.gz - - sudo cp $NSQ_DOWNLOAD/bin/nsqd $NSQ_DOWNLOAD/bin/nsqlookupd /usr/local/bin + - export PATH=$NSQ_DOWNLOAD/bin:$PATH - pushd $TRAVIS_BUILD_DIR - ./test.sh - popd notifications: email: false + +sudo: false diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md b/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md index b48f791..8c45770 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/ChangeLog.md @@ -1,5 +1,43 @@ ## go-nsq Change Log +### 1.0.4 - 2015-04-07 + +**Upgrading from 1.0.3**: There are no backward incompatible changes. + + * #133 - fix `ErrNotConnected` race during `Producer` connection (thanks @jeddenlea) + * #132 - fix `RDY` redistribution after backoff with no connections + * #128 - fix backoff stall when using `RequeueWithoutBackoff` + * #127 - fix handling of connection closing when resuming after backoff (thanks @jnewmano) + * #126 - allow `BackoffStrategy` to be set via flag (thanks @twmb) + * #125 - add pluggable consumer `BackoffStrategy`; add full-jitter strategy (thanks @hden) + * #124 - add `DialTimeout` and `LocalAddr` config (thanks @yashkin) + * #119 - add `Producer.Ping()` method (thanks @zulily) + * #122 - refactor log level string handling + * #120 - fix `Message` data races on `responded` + * #114 - fix lookupd jitter having no effect (thanks @judwhite) + +### 1.0.3 - 2015-02-07 + +**Upgrading from 1.0.2**: There are no backward incompatible changes. + + * #104 - fix reconnect address bug (thanks @ryanslade) + * #106 - fix backoff reconnect deadlock (thanks @ryanslade) + * #107 - fix out-of-bounds error when removing nsqlookupd addresses (thanks @andreas) + * #108 - fix potential logger race conditions (thanks @judwhite) + * #111 - fix resolved address error in reconnect loop (thanks @twmb) + +### 1.0.2 - 2015-01-21 + +**Upgrading from 1.0.1**: There are no backward incompatible changes. + + * #102 - TLS min/max config defaults (thanks @twmb) + * #99 - fix `Consumer.Stop()` race and `Producer.Stop()` deadlock (thanks @tylertreat) + * #92 - expose `Message.NSQDAddress` + * #95 - cleanup panic during `Consumer.Stop()` if handlers are deadlocked + * #98 - add `tls-min-version` option (thanks @twmb) + * #93 - expose a way to get `Consumer` runtime stats (thanks @dcarney) + * #94 - allow `#ephemeral` topic names (thanks @jamesgroat) + ### 1.0.1 - 2014-11-09 **Upgrading from 1.0.0**: There are no backward incompatible changes functionally, however this diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md b/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md index a107b11..aa1ca9b 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/README.md @@ -4,7 +4,7 @@ The official Go package for [NSQ][nsq]. [![Build Status](https://secure.travis-ci.org/bitly/go-nsq.png?branch=master)][travis] [![GoDoc](https://godoc.org/github.com/bitly/go-nsq?status.svg)](https://godoc.org/github.com/bitly/go-nsq) -The latest stable release is **[1.0.1][latest_tag]**. +The latest stable release is **[1.0.4][latest_tag]**. NOTE: The public API has been refactored as of `v1.0.0` and is not backwards compatible with previous releases. **[0.3.7][legacy]** is the last stable release compatible with the legacy API. @@ -25,4 +25,4 @@ See the [main repo apps][apps] directory for examples of clients built using thi [pr30]: https://github.com/bitly/go-nsq/pull/30 [legacy]: https://github.com/bitly/go-nsq/releases/tag/v0.3.7 [travis]: http://travis-ci.org/bitly/go-nsq -[latest_tag]: https://github.com/bitly/go-nsq/releases/tag/v1.0.1 +[latest_tag]: https://github.com/bitly/go-nsq/releases/tag/v1.0.4 diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go index 0abb264..5c784d4 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/command_test.go @@ -2,16 +2,11 @@ package nsq import ( "bytes" - "io/ioutil" - "log" - "os" "testing" ) func BenchmarkCommand(b *testing.B) { b.StopTimer() - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) data := make([]byte, 2048) cmd := Publish("test", data) var buf bytes.Buffer diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go index 9a5563c..a88378e 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/config.go @@ -7,10 +7,14 @@ import ( "fmt" "io/ioutil" "log" + "math" + "math/rand" + "net" "os" "reflect" "strconv" "strings" + "sync" "time" "unsafe" ) @@ -26,41 +30,114 @@ type defaultsHandler interface { SetDefaults(c *Config) error } +// BackoffStrategy defines a strategy for calculating the duration of time +// a consumer should backoff for a given attempt +type BackoffStrategy interface { + Calculate(attempt int) time.Duration +} + +// ExponentialStrategy implements an exponential backoff strategy (default) +type ExponentialStrategy struct { + cfg *Config +} + +// Calculate returns a duration of time: 2 ^ attempt +func (s *ExponentialStrategy) Calculate(attempt int) time.Duration { + backoffDuration := s.cfg.BackoffMultiplier * + time.Duration(math.Pow(2, float64(attempt))) + if backoffDuration > s.cfg.MaxBackoffDuration { + backoffDuration = s.cfg.MaxBackoffDuration + } + return backoffDuration +} + +func (s *ExponentialStrategy) setConfig(cfg *Config) { + s.cfg = cfg +} + +// FullJitterStrategy implements http://www.awsarchitectureblog.com/2015/03/backoff.html +type FullJitterStrategy struct { + cfg *Config + + rngOnce sync.Once + rng *rand.Rand +} + +// Calculate returns a random duration of time [0, 2 ^ attempt] +func (s *FullJitterStrategy) Calculate(attempt int) time.Duration { + // lazily initialize the RNG + s.rngOnce.Do(func() { + if s.rng != nil { + return + } + s.rng = rand.New(rand.NewSource(time.Now().UnixNano())) + }) + + backoffDuration := s.cfg.BackoffMultiplier * + time.Duration(math.Pow(2, float64(attempt))) + if backoffDuration > s.cfg.MaxBackoffDuration { + backoffDuration = s.cfg.MaxBackoffDuration + } + return time.Duration(s.rng.Intn(int(backoffDuration))) +} + +func (s *FullJitterStrategy) setConfig(cfg *Config) { + s.cfg = cfg +} + // Config is a struct of NSQ options // // The only valid way to create a Config is via NewConfig, using a struct literal will panic. // After Config is passed into a high-level type (like Consumer, Producer, etc.) the values are no // longer mutable (they are copied). // -// Use Set(key string, value interface{}) as an alternate way to set parameters +// Use Set(option string, value interface{}) as an alternate way to set parameters type Config struct { initialized bool // used to Initialize, Validate configHandlers []configHandler + DialTimeout time.Duration `opt:"dial_timeout" default:"1s"` + // Deadlines for network reads and writes ReadTimeout time.Duration `opt:"read_timeout" min:"100ms" max:"5m" default:"60s"` WriteTimeout time.Duration `opt:"write_timeout" min:"100ms" max:"5m" default:"1s"` + // LocalAddr is the local address to use when dialing an nsqd. + // If empty, a local address is automatically chosen. + LocalAddr net.Addr `opt:"local_addr"` + // Duration between polling lookupd for new producers, and fractional jitter to add to // the lookupd pool loop. this helps evenly distribute requests even if multiple consumers // restart at the same time - LookupdPollInterval time.Duration `opt:"lookupd_poll_interval" min:"5s" max:"5m" default:"60s"` + // + // NOTE: when not using nsqlookupd, LookupdPollInterval represents the duration of time between + // reconnection attempts + LookupdPollInterval time.Duration `opt:"lookupd_poll_interval" min:"10ms" max:"5m" default:"60s"` LookupdPollJitter float64 `opt:"lookupd_poll_jitter" min:"0" max:"1" default:"0.3"` // Maximum duration when REQueueing (for doubling of deferred requeue) MaxRequeueDelay time.Duration `opt:"max_requeue_delay" min:"0" max:"60m" default:"15m"` DefaultRequeueDelay time.Duration `opt:"default_requeue_delay" min:"0" max:"60m" default:"90s"` + + // Backoff strategy, defaults to exponential backoff. Overwrite this to define alternative backoff algrithms. + BackoffStrategy BackoffStrategy `opt:"backoff_strategy" default:"exponential"` + // Maximum amount of time to backoff when processing fails 0 == no backoff + MaxBackoffDuration time.Duration `opt:"max_backoff_duration" min:"0" max:"60m" default:"2m"` // Unit of time for calculating consumer backoff BackoffMultiplier time.Duration `opt:"backoff_multiplier" min:"0" max:"60m" default:"1s"` // Maximum number of times this consumer will attempt to process a message before giving up MaxAttempts uint16 `opt:"max_attempts" min:"0" max:"65535" default:"5"` - // Amount of time in seconds to wait for a message from a producer when in a state where RDY + + // Duration to wait for a message from a producer when in a state where RDY // counts are re-distributed (ie. max_in_flight < num_producers) LowRdyIdleTimeout time.Duration `opt:"low_rdy_idle_timeout" min:"1s" max:"5m" default:"10s"` + // Duration between redistributing max-in-flight to connections + RDYRedistributeInterval time.Duration `opt:"rdy_redistribute_interval" min:"1ms" max:"5s" default:"5s"` + // Identifiers sent to nsqd representing this client // UserAgent is in the spirit of HTTP (default: "/") ClientID string `opt:"client_id"` // (defaults: short hostname) @@ -79,7 +156,7 @@ type Config struct { // tls_insecure_skip_verify - Bool indicates whether this client should verify server certificates // tls_cert - String path to file containing public key for certificate // tls_key - String path to file containing private key for certificate - // tls_min_version - String indicating the minimum version of tls acceptable ('ssl30', 'tls10', 'tls11', 'tls12') + // tls_min_version - String indicating the minimum version of tls acceptable ('ssl3.0', 'tls1.0', 'tls1.1', 'tls1.2') // TlsV1 bool `opt:"tls_v1"` TlsConfig *tls.Config `opt:"tls_config"` @@ -101,9 +178,6 @@ type Config struct { // Maximum number of messages to allow in flight (concurrency knob) MaxInFlight int `opt:"max_in_flight" min:"0" default:"1"` - // Maximum amount of time to backoff when processing fails 0 == no backoff - MaxBackoffDuration time.Duration `opt:"max_backoff_duration" min:"0" max:"60m" default:"2m"` - // The server-side message timeout for messages delivered to this client MsgTimeout time.Duration `opt:"msg_timeout" min:"0"` @@ -115,9 +189,10 @@ type Config struct { // // This must be used to initialize Config structs. Values can be set directly, or through Config.Set() func NewConfig() *Config { - c := &Config{} - c.configHandlers = append(c.configHandlers, &structTagsConfig{}, &tlsConfig{}) - c.initialized = true + c := &Config{ + configHandlers: []configHandler{&structTagsConfig{}, &tlsConfig{}}, + initialized: true, + } if err := c.setDefaults(); err != nil { panic(err.Error()) } @@ -163,7 +238,6 @@ func (c *Config) assertInitialized() { // Validate checks that all values are within specified min/max ranges func (c *Config) Validate() error { c.assertInitialized() - for _, h := range c.configHandlers { if err := h.Validate(c); err != nil { return err @@ -181,7 +255,6 @@ func (c *Config) setDefaults() error { } } } - return nil } @@ -237,6 +310,14 @@ func (h *structTagsConfig) Set(c *Config, option string, value interface{}) erro option, coercedVal.Interface(), coercedMaxVal.Interface()) } } + if coercedVal.Type().String() == "nsq.BackoffStrategy" { + v := coercedVal.Interface().(BackoffStrategy) + if v, ok := v.(interface { + setConfig(*Config) + }); ok { + v.setConfig(c) + } + } dest.Set(coercedVal) return nil } @@ -304,6 +385,7 @@ func (h *structTagsConfig) Validate(c *Config) error { if c.HeartbeatInterval > c.ReadTimeout { return fmt.Errorf("HeartbeatInterval %v must be less than ReadTimeout %v", c.HeartbeatInterval, c.ReadTimeout) } + return nil } @@ -323,7 +405,10 @@ func (t *tlsConfig) HandlesOption(c *Config, option string) bool { func (t *tlsConfig) Set(c *Config, option string, value interface{}) error { if c.TlsConfig == nil { - c.TlsConfig = &tls.Config{} + c.TlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS10, + MaxVersion: tls.VersionTLS12, // enable TLS_FALLBACK_SCSV prior to Go 1.5: https://go-review.googlesource.com/#/c/1776/ + } } val := reflect.ValueOf(c.TlsConfig).Elem() @@ -373,13 +458,13 @@ func (t *tlsConfig) Set(c *Config, option string, value interface{}) error { return fmt.Errorf("ERROR: %v is not a string", value) } switch version { - case "ssl30": + case "ssl3.0": c.TlsConfig.MinVersion = tls.VersionSSL30 - case "tls10": + case "tls1.0": c.TlsConfig.MinVersion = tls.VersionTLS10 - case "tls11": + case "tls1.1": c.TlsConfig.MinVersion = tls.VersionTLS11 - case "tls12": + case "tls1.2": c.TlsConfig.MinVersion = tls.VersionTLS12 default: return fmt.Errorf("ERROR: %v is not a tls version", value) @@ -453,6 +538,10 @@ func coerce(v interface{}, typ reflect.Type) (reflect.Value, error) { v, err = coerceBool(v) case "time.Duration": v, err = coerceDuration(v) + case "net.Addr": + v, err = coerceAddr(v) + case "nsq.BackoffStrategy": + v, err = coerceBackoffStrategy(v) default: v = nil err = errors.New(fmt.Sprintf("invalid type %s", typ.String())) @@ -473,14 +562,16 @@ func valueTypeCoerce(v interface{}, typ reflect.Type) reflect.Value { tval.SetUint(val.Uint()) case "float32", "float64": tval.SetFloat(val.Float()) + default: + tval.Set(val) } return tval } func coerceString(v interface{}) (string, error) { - switch v.(type) { + switch v := v.(type) { case string: - return v.(string), nil + return v, nil case int, int16, int32, int64, uint, uint16, uint32, uint64: return fmt.Sprintf("%d", v), nil case float32, float64: @@ -492,9 +583,9 @@ func coerceString(v interface{}) (string, error) { } func coerceDuration(v interface{}) (time.Duration, error) { - switch v.(type) { + switch v := v.(type) { case string: - return time.ParseDuration(v.(string)) + return time.ParseDuration(v) case int, int16, int32, int64: // treat like ms return time.Duration(reflect.ValueOf(v).Int()) * time.Millisecond, nil @@ -502,17 +593,42 @@ func coerceDuration(v interface{}) (time.Duration, error) { // treat like ms return time.Duration(reflect.ValueOf(v).Uint()) * time.Millisecond, nil case time.Duration: - return v.(time.Duration), nil + return v, nil } return 0, errors.New("invalid value type") } +func coerceAddr(v interface{}) (net.Addr, error) { + switch v := v.(type) { + case string: + return net.ResolveTCPAddr("tcp", v) + case net.Addr: + return v, nil + } + return nil, errors.New("invalid value type") +} + +func coerceBackoffStrategy(v interface{}) (BackoffStrategy, error) { + switch v := v.(type) { + case string: + switch v { + case "", "exponential": + return &ExponentialStrategy{}, nil + case "full_jitter": + return &FullJitterStrategy{}, nil + } + case BackoffStrategy: + return v, nil + } + return nil, errors.New("invalid value type") +} + func coerceBool(v interface{}) (bool, error) { - switch v.(type) { + switch v := v.(type) { case bool: - return v.(bool), nil + return v, nil case string: - return strconv.ParseBool(v.(string)) + return strconv.ParseBool(v) case int, int16, int32, int64: return reflect.ValueOf(v).Int() != 0, nil case uint, uint16, uint32, uint64: @@ -522,25 +638,25 @@ func coerceBool(v interface{}) (bool, error) { } func coerceFloat64(v interface{}) (float64, error) { - switch v.(type) { + switch v := v.(type) { case string: - return strconv.ParseFloat(v.(string), 64) + return strconv.ParseFloat(v, 64) case int, int16, int32, int64: return float64(reflect.ValueOf(v).Int()), nil case uint, uint16, uint32, uint64: return float64(reflect.ValueOf(v).Uint()), nil case float32: - return float64(v.(float32)), nil + return float64(v), nil case float64: - return v.(float64), nil + return v, nil } return 0, errors.New("invalid value type") } func coerceInt64(v interface{}) (int64, error) { - switch v.(type) { + switch v := v.(type) { case string: - return strconv.ParseInt(v.(string), 10, 64) + return strconv.ParseInt(v, 10, 64) case int, int16, int32, int64: return reflect.ValueOf(v).Int(), nil case uint, uint16, uint32, uint64: @@ -550,9 +666,9 @@ func coerceInt64(v interface{}) (int64, error) { } func coerceUint64(v interface{}) (uint64, error) { - switch v.(type) { + switch v := v.(type) { case string: - return strconv.ParseUint(v.(string), 10, 64) + return strconv.ParseUint(v, 10, 64) case int, int16, int32, int64: return uint64(reflect.ValueOf(v).Int()), nil case uint, uint16, uint32, uint64: diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go index f06ea26..060b073 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/config_test.go @@ -1,6 +1,12 @@ package nsq -import "testing" +import ( + "math/rand" + "net" + "reflect" + "testing" + "time" +) func TestConfigSet(t *testing.T) { c := NewConfig() @@ -11,7 +17,7 @@ func TestConfigSet(t *testing.T) { t.Error("No error when setting `tls_v1` to an invalid value") } if err := c.Set("tls_v1", true); err != nil { - t.Errorf("Error setting `tls_v1` config. %v", err) + t.Errorf("Error setting `tls_v1` config. %s", err) } if err := c.Set("tls-insecure-skip-verify", true); err != nil { @@ -20,12 +26,33 @@ func TestConfigSet(t *testing.T) { if c.TlsConfig.InsecureSkipVerify != true { t.Errorf("Error setting `tls-insecure-skip-verify` config: %v", c.TlsConfig) } - if err := c.Set("tls-min-version", "tls12"); err != nil { - t.Errorf("Error setting `tls-min-version` config: %v", err) + if err := c.Set("tls-min-version", "tls1.2"); err != nil { + t.Errorf("Error setting `tls-min-version` config: %s", err) } - if err := c.Set("tls-min-version", "tls13"); err == nil { + if err := c.Set("tls-min-version", "tls1.3"); err == nil { t.Error("No error when setting `tls-min-version` to an invalid value") } + if err := c.Set("local_addr", &net.TCPAddr{}); err != nil { + t.Errorf("Error setting `local_addr` config: %s", err) + } + if err := c.Set("local_addr", "1.2.3.4:27015"); err != nil { + t.Errorf("Error setting `local_addr` config: %s", err) + } + if err := c.Set("dial_timeout", "5s"); err != nil { + t.Errorf("Error setting `dial_timeout` config: %s", err) + } + if c.LocalAddr.String() != "1.2.3.4:27015" { + t.Error("Failed to assign `local_addr` config") + } + if reflect.ValueOf(c.BackoffStrategy).Type().String() != "*nsq.ExponentialStrategy" { + t.Error("Failed to set default `exponential` backoff strategy") + } + if err := c.Set("backoff_strategy", "full_jitter"); err != nil { + t.Errorf("Failed to assign `backoff_strategy` config: %v", err) + } + if reflect.ValueOf(c.BackoffStrategy).Type().String() != "*nsq.FullJitterStrategy" { + t.Error("Failed to set `full_jitter` backoff strategy") + } } func TestConfigValidate(t *testing.T) { @@ -37,5 +64,41 @@ func TestConfigValidate(t *testing.T) { if err := c.Validate(); err == nil { t.Error("no error set for invalid value") } +} +func TestExponentialBackoff(t *testing.T) { + expected := []time.Duration{ + 1 * time.Second, + 2 * time.Second, + 8 * time.Second, + 32 * time.Second, + } + backoffTest(t, expected, func(c *Config) BackoffStrategy { + return &ExponentialStrategy{cfg: c} + }) +} + +func TestFullJitterBackoff(t *testing.T) { + expected := []time.Duration{ + 566028617 * time.Nanosecond, + 1365407263 * time.Nanosecond, + 5232470547 * time.Nanosecond, + 21467499218 * time.Nanosecond, + } + backoffTest(t, expected, func(c *Config) BackoffStrategy { + return &FullJitterStrategy{cfg: c, rng: rand.New(rand.NewSource(99))} + }) +} + +func backoffTest(t *testing.T, expected []time.Duration, cb func(c *Config) BackoffStrategy) { + config := NewConfig() + attempts := []int{0, 1, 3, 5} + s := cb(config) + for i := range attempts { + result := s.Calculate(attempts[i]) + if result != expected[i] { + t.Fatalf("wrong backoff duration %v for attempt %d (should be %v)", + result, attempts[i], expected[i]) + } + } } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go index ad31ef0..3d7ea7f 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/conn.go @@ -65,16 +65,14 @@ type Conn struct { delegate ConnDelegate - logger logger - logLvl LogLevel - logFmt string + logger logger + logLvl LogLevel + logFmt string + logGuard sync.RWMutex r io.Reader w io.Writer - backoffCounter int32 - rdyRetryTimer *time.Timer - cmdChan chan *Command msgResponseChan chan *msgResponse exitChan chan int @@ -121,6 +119,9 @@ func NewConn(addr string, config *Config, delegate ConnDelegate) *Conn { // Output(calldepth int, s string) // func (c *Conn) SetLogger(l logger, lvl LogLevel, format string) { + c.logGuard.Lock() + defer c.logGuard.Unlock() + c.logger = l c.logLvl = lvl c.logFmt = format @@ -129,10 +130,22 @@ func (c *Conn) SetLogger(l logger, lvl LogLevel, format string) { } } +func (c *Conn) getLogger() (logger, LogLevel, string) { + c.logGuard.RLock() + defer c.logGuard.RUnlock() + + return c.logger, c.logLvl, c.logFmt +} + // Connect dials and bootstraps the nsqd connection // (including IDENTIFY) and returns the IdentifyResponse func (c *Conn) Connect() (*IdentifyResponse, error) { - conn, err := net.DialTimeout("tcp", c.addr, time.Second) + dialer := &net.Dialer{ + LocalAddr: c.config.LocalAddr, + Timeout: c.config.DialTimeout, + } + + conn, err := dialer.Dial("tcp", c.addr) if err != nil { return nil, err } @@ -496,6 +509,7 @@ func (c *Conn) readLoop() { goto exit } msg.Delegate = delegate + msg.NSQDAddress = c.String() atomic.AddInt64(&c.rdyCount, -1) atomic.AddInt64(&c.messagesInFlight, 1) @@ -549,14 +563,14 @@ func (c *Conn) writeLoop() { if resp.success { c.log(LogLevelDebug, "FIN %s", resp.msg.ID) c.delegate.OnMessageFinished(c, resp.msg) - if resp.backoff { - c.delegate.OnResume(c) - } + c.delegate.OnResume(c) } else { c.log(LogLevelDebug, "REQ %s", resp.msg.ID) c.delegate.OnMessageRequeued(c, resp.msg) if resp.backoff { c.delegate.OnBackoff(c) + } else { + c.delegate.OnContinue(c) } } @@ -669,7 +683,7 @@ func (c *Conn) waitForCleanup() { } func (c *Conn) onMessageFinish(m *Message) { - c.msgResponseChan <- &msgResponse{m, Finish(m.ID), true, true} + c.msgResponseChan <- &msgResponse{msg: m, cmd: Finish(m.ID), success: true} } func (c *Conn) onMessageRequeue(m *Message, delay time.Duration, backoff bool) { @@ -681,7 +695,7 @@ func (c *Conn) onMessageRequeue(m *Message, delay time.Duration, backoff bool) { delay = c.config.MaxRequeueDelay } } - c.msgResponseChan <- &msgResponse{m, Requeue(m.ID, delay), false, backoff} + c.msgResponseChan <- &msgResponse{msg: m, cmd: Requeue(m.ID, delay), success: false, backoff: backoff} } func (c *Conn) onMessageTouch(m *Message) { @@ -692,15 +706,17 @@ func (c *Conn) onMessageTouch(m *Message) { } func (c *Conn) log(lvl LogLevel, line string, args ...interface{}) { - if c.logger == nil { + logger, logLvl, logFmt := c.getLogger() + + if logger == nil { return } - if c.logLvl > lvl { + if logLvl > lvl { return } - c.logger.Output(2, fmt.Sprintf("%-4s %s %s", logPrefix(lvl), - fmt.Sprintf(c.logFmt, c.String()), + logger.Output(2, fmt.Sprintf("%-4s %s %s", lvl, + fmt.Sprintf(logFmt, c.String()), fmt.Sprintf(line, args...))) } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go index b77b6a4..6a4d75c 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer.go @@ -66,6 +66,14 @@ type ConsumerStats struct { var instCount int64 +type backoffSignal int + +const ( + backoffFlag backoffSignal = iota + continueFlag + resumeFlag +) + // Consumer is a high-level type to consume from NSQ. // // A Consumer instance is supplied a Handler that will be executed @@ -82,12 +90,14 @@ type Consumer struct { messagesRequeued uint64 totalRdyCount int64 backoffDuration int64 + backoffCounter int32 maxInFlight int32 mtx sync.RWMutex - logger logger - logLvl LogLevel + logger logger + logLvl LogLevel + logGuard sync.RWMutex behaviorDelegate interface{} @@ -100,8 +110,7 @@ type Consumer struct { needRDYRedistributed int32 - backoffMtx sync.RWMutex - backoffCounter int32 + backoffMtx sync.RWMutex incomingMessages chan *Message @@ -123,6 +132,7 @@ type Consumer struct { stopFlag int32 connectedFlag int32 stopHandler sync.Once + exitHandler sync.Once // read from this channel to block until consumer is cleanly stopped StopChan chan int @@ -205,10 +215,20 @@ func (r *Consumer) conns() []*Conn { // Output(calldepth int, s string) // func (r *Consumer) SetLogger(l logger, lvl LogLevel) { + r.logGuard.Lock() + defer r.logGuard.Unlock() + r.logger = l r.logLvl = lvl } +func (r *Consumer) getLogger() (logger, LogLevel) { + r.logGuard.RLock() + defer r.logGuard.RUnlock() + + return r.logger, r.logLvl +} + // SetBehaviorDelegate takes a type implementing one or more // of the following interfaces that modify the behavior // of the `Consumer`: @@ -351,7 +371,7 @@ func (r *Consumer) lookupdLoop() { // when restarted at the same time, dont all connect at once. jitter := time.Duration(int64(r.rng.Float64() * r.config.LookupdPollJitter * float64(r.config.LookupdPollInterval))) - ticker := time.NewTicker(r.config.LookupdPollInterval) + var ticker *time.Ticker select { case <-time.After(jitter): @@ -359,6 +379,8 @@ func (r *Consumer) lookupdLoop() { goto exit } + ticker = time.NewTicker(r.config.LookupdPollInterval) + for { select { case <-ticker.C: @@ -371,7 +393,9 @@ func (r *Consumer) lookupdLoop() { } exit: - ticker.Stop() + if ticker != nil { + ticker.Stop() + } r.log(LogLevelInfo, "exiting lookupdLoop") r.wg.Done() } @@ -380,6 +404,9 @@ exit: // keeping track of which one was last used func (r *Consumer) nextLookupdEndpoint() string { r.mtx.RLock() + if r.lookupdQueryIndex >= len(r.lookupdHTTPAddrs) { + r.lookupdQueryIndex = 0 + } addr := r.lookupdHTTPAddrs[r.lookupdQueryIndex] num := len(r.lookupdHTTPAddrs) r.mtx.RUnlock() @@ -481,8 +508,10 @@ func (r *Consumer) ConnectToNSQD(addr string) error { atomic.StoreInt32(&r.connectedFlag, 1) + logger, logLvl := r.getLogger() + conn := NewConn(addr, &r.config, &consumerConnDelegate{r}) - conn.SetLogger(r.logger, r.logLvl, + conn.SetLogger(logger, logLvl, fmt.Sprintf("%3d [%s/%s] (%%s)", r.id, r.topic, r.channel)) r.mtx.Lock() @@ -492,10 +521,10 @@ func (r *Consumer) ConnectToNSQD(addr string) error { r.mtx.Unlock() return ErrAlreadyConnected } - if !pendingOk { - r.pendingConnections[addr] = conn + r.pendingConnections[addr] = conn + if idx := indexOf(addr, r.nsqdTCPAddrs); idx == -1 { + r.nsqdTCPAddrs = append(r.nsqdTCPAddrs, addr) } - r.nsqdTCPAddrs = append(r.nsqdTCPAddrs, addr) r.mtx.Unlock() r.log(LogLevelInfo, "(%s) connecting to nsqd", addr) @@ -613,85 +642,15 @@ func (r *Consumer) onConnMessageRequeued(c *Conn, msg *Message) { } func (r *Consumer) onConnBackoff(c *Conn) { - r.startStopContinueBackoff(c, false) + r.startStopContinueBackoff(c, backoffFlag) } -func (r *Consumer) onConnResume(c *Conn) { - r.startStopContinueBackoff(c, true) +func (r *Consumer) onConnContinue(c *Conn) { + r.startStopContinueBackoff(c, continueFlag) } -func (r *Consumer) startStopContinueBackoff(conn *Conn, success bool) { - // prevent many async failures/successes from immediately resulting in - // max backoff/normal rate (by ensuring that we dont continually incr/decr - // the counter during a backoff period) - if r.inBackoffBlock() { - return - } - - // update backoff state - r.backoffMtx.Lock() - backoffUpdated := false - if success { - if r.backoffCounter > 0 { - r.backoffCounter-- - backoffUpdated = true - } - } else { - maxBackoffCount := int32(math.Max(1, math.Ceil( - math.Log2(r.config.MaxBackoffDuration.Seconds())))) - if r.backoffCounter < maxBackoffCount { - r.backoffCounter++ - backoffUpdated = true - } - } - r.backoffMtx.Unlock() - - if r.backoffCounter == 0 && backoffUpdated { - // exit backoff - count := r.perConnMaxInFlight() - r.log(LogLevelWarning, "exiting backoff, returning all to RDY %d", count) - for _, c := range r.conns() { - r.updateRDY(c, count) - } - } else if r.backoffCounter > 0 { - // start or continue backoff - backoffDuration := r.backoffDurationForCount(r.backoffCounter) - atomic.StoreInt64(&r.backoffDuration, backoffDuration.Nanoseconds()) - time.AfterFunc(backoffDuration, func() { - var choice *Conn - - atomic.StoreInt64(&r.backoffDuration, 0) - - // pick a random connection to test the waters - var i int - conns := r.conns() - if len(conns) == 0 { - return - } - idx := r.rng.Intn(len(conns)) - for _, c := range conns { - if i == idx { - choice = c - break - } - i++ - } - - r.log(LogLevelWarning, - "(%s) backoff timeout expired, sending RDY 1", - choice.String()) - // while in backoff only ever let 1 message at a time through - r.updateRDY(choice, 1) - }) - - r.log(LogLevelWarning, "backing off for %.04f seconds (backoff level %d), setting all to RDY 0", - backoffDuration.Seconds(), r.backoffCounter) - - // send RDY 0 immediately (to *all* connections) - for _, c := range r.conns() { - r.updateRDY(c, 0) - } - } +func (r *Consumer) onConnResume(c *Conn) { + r.startStopContinueBackoff(c, resumeFlag) } func (r *Consumer) onConnResponse(c *Conn, data []byte) { @@ -769,8 +728,8 @@ func (r *Consumer) onConnClose(c *Conn) { // try to reconnect after a bit go func(addr string) { for { - r.log(LogLevelInfo, "(%s) re-connecting in 15 seconds...", addr) - time.Sleep(15 * time.Second) + r.log(LogLevelInfo, "(%s) re-connecting in %.04f seconds...", addr, r.config.LookupdPollInterval) + time.Sleep(r.config.LookupdPollInterval) if atomic.LoadInt32(&r.stopFlag) == 1 { break } @@ -788,32 +747,114 @@ func (r *Consumer) onConnClose(c *Conn) { } break } - }(c.RemoteAddr().String()) + }(c.String()) } } -func (r *Consumer) backoffDurationForCount(count int32) time.Duration { - backoffDuration := r.config.BackoffMultiplier * - time.Duration(math.Pow(2, float64(count))) - if backoffDuration > r.config.MaxBackoffDuration { - backoffDuration = r.config.MaxBackoffDuration +func (r *Consumer) startStopContinueBackoff(conn *Conn, signal backoffSignal) { + // prevent many async failures/successes from immediately resulting in + // max backoff/normal rate (by ensuring that we dont continually incr/decr + // the counter during a backoff period) + r.backoffMtx.Lock() + if r.inBackoffTimeout() { + r.backoffMtx.Unlock() + return + } + defer r.backoffMtx.Unlock() + + // update backoff state + backoffUpdated := false + backoffCounter := atomic.LoadInt32(&r.backoffCounter) + switch signal { + case resumeFlag: + if backoffCounter > 0 { + backoffCounter-- + backoffUpdated = true + } + case backoffFlag: + nextBackoff := r.config.BackoffStrategy.Calculate(int(backoffCounter) + 1) + if nextBackoff <= r.config.MaxBackoffDuration { + backoffCounter++ + backoffUpdated = true + } } - return backoffDuration + atomic.StoreInt32(&r.backoffCounter, backoffCounter) + + if r.backoffCounter == 0 && backoffUpdated { + // exit backoff + count := r.perConnMaxInFlight() + r.log(LogLevelWarning, "exiting backoff, returning all to RDY %d", count) + for _, c := range r.conns() { + r.updateRDY(c, count) + } + } else if r.backoffCounter > 0 { + // start or continue backoff + backoffDuration := r.config.BackoffStrategy.Calculate(int(backoffCounter)) + + r.log(LogLevelWarning, "backing off for %.04f seconds (backoff level %d), setting all to RDY 0", + backoffDuration.Seconds(), backoffCounter) + + // send RDY 0 immediately (to *all* connections) + for _, c := range r.conns() { + r.updateRDY(c, 0) + } + + r.backoff(backoffDuration) + } +} + +func (r *Consumer) backoff(d time.Duration) { + atomic.StoreInt64(&r.backoffDuration, d.Nanoseconds()) + time.AfterFunc(d, r.resume) +} + +func (r *Consumer) resume() { + if atomic.LoadInt32(&r.stopFlag) == 1 { + atomic.StoreInt64(&r.backoffDuration, 0) + return + } + + // pick a random connection to test the waters + conns := r.conns() + if len(conns) == 0 { + r.log(LogLevelWarning, "no connection available to resume") + r.log(LogLevelWarning, "backing off for %.04f seconds", 1) + r.backoff(time.Second) + return + } + idx := r.rng.Intn(len(conns)) + choice := conns[idx] + + r.log(LogLevelWarning, + "(%s) backoff timeout expired, sending RDY 1", + choice.String()) + + // while in backoff only ever let 1 message at a time through + err := r.updateRDY(choice, 1) + if err != nil { + r.log(LogLevelWarning, "(%s) error resuming RDY 1 - %s", choice.String(), err) + r.log(LogLevelWarning, "backing off for %.04f seconds", 1) + r.backoff(time.Second) + return + } + + atomic.StoreInt64(&r.backoffDuration, 0) } func (r *Consumer) inBackoff() bool { - r.backoffMtx.RLock() - backoffCounter := r.backoffCounter - r.backoffMtx.RUnlock() - return backoffCounter > 0 + return atomic.LoadInt32(&r.backoffCounter) > 0 } -func (r *Consumer) inBackoffBlock() bool { +func (r *Consumer) inBackoffTimeout() bool { return atomic.LoadInt64(&r.backoffDuration) > 0 } func (r *Consumer) maybeUpdateRDY(conn *Conn) { - if r.inBackoff() || r.inBackoffBlock() { + inBackoff := r.inBackoff() + inBackoffTimeout := r.inBackoffTimeout() + if inBackoff || inBackoffTimeout { + r.log(LogLevelDebug, "(%s) skip sending RDY inBackoff:%v || inBackoffTimeout:%v", + conn, inBackoff, inBackoffTimeout) return } @@ -833,7 +874,7 @@ func (r *Consumer) maybeUpdateRDY(conn *Conn) { } func (r *Consumer) rdyLoop() { - redistributeTicker := time.NewTicker(5 * time.Second) + redistributeTicker := time.NewTicker(r.config.RDYRedistributeInterval) for { select { @@ -852,7 +893,7 @@ exit: func (r *Consumer) updateRDY(c *Conn, count int64) error { if c.IsClosing() { - return nil + return ErrClosing } // never exceed the nsqd's configured max RDY count @@ -910,20 +951,26 @@ func (r *Consumer) sendRDY(c *Conn, count int64) error { } func (r *Consumer) redistributeRDY() { - if r.inBackoffBlock() { + if r.inBackoffTimeout() { + return + } + + // if an external heuristic set needRDYRedistributed we want to wait + // until we can actually redistribute to proceed + conns := r.conns() + if len(conns) == 0 { return } - numConns := int32(len(r.conns())) maxInFlight := r.getMaxInFlight() - if numConns > maxInFlight { + if len(conns) > int(maxInFlight) { r.log(LogLevelDebug, "redistributing RDY state (%d conns > %d max_in_flight)", - numConns, maxInFlight) + len(conns), maxInFlight) atomic.StoreInt32(&r.needRDYRedistributed, 1) } - if r.inBackoff() && numConns > 1 { - r.log(LogLevelDebug, "redistributing RDY state (in backoff and %d conns > 1)", numConns) + if r.inBackoff() && len(conns) > 1 { + r.log(LogLevelDebug, "redistributing RDY state (in backoff and %d conns > 1)", len(conns)) atomic.StoreInt32(&r.needRDYRedistributed, 1) } @@ -931,7 +978,6 @@ func (r *Consumer) redistributeRDY() { return } - conns := r.conns() possibleConns := make([]*Conn, 0, len(conns)) for _, c := range conns { lastMsgDuration := time.Now().Sub(c.LastMessageTime()) @@ -982,7 +1028,12 @@ func (r *Consumer) Stop() { } time.AfterFunc(time.Second*30, func() { - r.stopHandlers() + // if we've waited this long handlers are blocked on processing messages + // so we can't just stopHandlers (if any adtl. messages were pending processing + // we would cause a panic on channel close) + // + // instead, we just bypass handler closing and skip to the final exit + r.exit() }) } } @@ -1074,21 +1125,25 @@ func (r *Consumer) shouldFailMessage(message *Message, handler interface{}) bool } func (r *Consumer) exit() { - close(r.exitChan) - r.wg.Wait() - close(r.StopChan) + r.exitHandler.Do(func() { + close(r.exitChan) + r.wg.Wait() + close(r.StopChan) + }) } func (r *Consumer) log(lvl LogLevel, line string, args ...interface{}) { - if r.logger == nil { + logger, logLvl := r.getLogger() + + if logger == nil { return } - if r.logLvl > lvl { + if logLvl > lvl { return } - r.logger.Output(2, fmt.Sprintf("%-4s %3d [%s/%s] %s", - logPrefix(lvl), r.id, r.topic, r.channel, + logger.Output(2, fmt.Sprintf("%-4s %3d [%s/%s] %s", + lvl, r.id, r.topic, r.channel, fmt.Sprintf(line, args...))) } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go index 3febc12..c944dd3 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/consumer_test.go @@ -7,6 +7,7 @@ import ( "fmt" "io/ioutil" "log" + "net" "net/http" "os" "strconv" @@ -140,6 +141,9 @@ func TestConsumerTLSClientCertViaSet(t *testing.T) { func consumerTest(t *testing.T, cb func(c *Config)) { config := NewConfig() + laddr := "127.0.0.1" + // so that the test can simulate binding consumer to specified address + config.LocalAddr, _ = net.ResolveTCPAddr("tcp", laddr+":0") // so that the test can simulate reaching max requeues and a call to LogFailedMessage config.DefaultRequeueDelay = 0 // so that the test wont timeout from backing off @@ -187,6 +191,11 @@ func consumerTest(t *testing.T, cb func(c *Config)) { t.Fatal("should not be able to connect to the same NSQ twice") } + conn := q.conns()[0] + if !strings.HasPrefix(conn.conn.LocalAddr().String(), laddr) { + t.Fatal("connection should be bound to the specified address:", conn.conn.LocalAddr()) + } + err = q.DisconnectFromNSQD("1.2.3.4:4150") if err == nil { t.Fatal("should not be able to disconnect from an unknown nsqd") diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go index 4bef7aa..1122578 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/delegates.go @@ -2,42 +2,31 @@ package nsq import "time" -// LogLevel specifies the severity of a given log message -type LogLevel int - type logger interface { Output(calldepth int, s string) error } -// logging constants +// LogLevel specifies the severity of a given log message +type LogLevel int + const ( LogLevelDebug LogLevel = iota LogLevelInfo LogLevelWarning LogLevelError - - LogLevelDebugPrefix = "DBG" - LogLevelInfoPrefix = "INF" - LogLevelWarningPrefix = "WRN" - LogLevelErrorPrefix = "ERR" ) -// LogPrefix Resolution -func logPrefix(lvl LogLevel) string { - var prefix string - +// String returns the string form for a given LogLevel +func (lvl LogLevel) String() string { switch lvl { - case LogLevelDebug: - prefix = LogLevelDebugPrefix case LogLevelInfo: - prefix = LogLevelInfoPrefix + return "INF" case LogLevelWarning: - prefix = LogLevelWarningPrefix + return "WRN" case LogLevelError: - prefix = LogLevelErrorPrefix + return "ERR" } - - return prefix + return "DBG" } // MessageDelegate is an interface of methods that are used as @@ -92,6 +81,9 @@ type ConnDelegate interface { // OnBackoff is called when the connection triggers a backoff state OnBackoff(*Conn) + // OnContinue is called when the connection finishes a message without adjusting backoff state + OnContinue(*Conn) + // OnResume is called when the connection triggers a resume state OnResume(*Conn) @@ -120,6 +112,7 @@ func (d *consumerConnDelegate) OnMessage(c *Conn, m *Message) { d.r.onCo func (d *consumerConnDelegate) OnMessageFinished(c *Conn, m *Message) { d.r.onConnMessageFinished(c, m) } func (d *consumerConnDelegate) OnMessageRequeued(c *Conn, m *Message) { d.r.onConnMessageRequeued(c, m) } func (d *consumerConnDelegate) OnBackoff(c *Conn) { d.r.onConnBackoff(c) } +func (d *consumerConnDelegate) OnContinue(c *Conn) { d.r.onConnContinue(c) } func (d *consumerConnDelegate) OnResume(c *Conn) { d.r.onConnResume(c) } func (d *consumerConnDelegate) OnIOError(c *Conn, err error) { d.r.onConnIOError(c, err) } func (d *consumerConnDelegate) OnHeartbeat(c *Conn) { d.r.onConnHeartbeat(c) } @@ -137,6 +130,7 @@ func (d *producerConnDelegate) OnMessage(c *Conn, m *Message) {} func (d *producerConnDelegate) OnMessageFinished(c *Conn, m *Message) {} func (d *producerConnDelegate) OnMessageRequeued(c *Conn, m *Message) {} func (d *producerConnDelegate) OnBackoff(c *Conn) {} +func (d *producerConnDelegate) OnContinue(c *Conn) {} func (d *producerConnDelegate) OnResume(c *Conn) {} func (d *producerConnDelegate) OnIOError(c *Conn, err error) { d.w.onConnIOError(c, err) } func (d *producerConnDelegate) OnHeartbeat(c *Conn) { d.w.onConnHeartbeat(c) } diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go index 7e1cd63..2f228d1 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/errors.go @@ -13,6 +13,9 @@ var ErrNotConnected = errors.New("not connected") // made against a Producer that has been stopped var ErrStopped = errors.New("stopped") +// ErrClosing is returned when a connection is closing +var ErrClosing = errors.New("closing") + // ErrAlreadyConnected is returned from ConnectToNSQD when already connected var ErrAlreadyConnected = errors.New("already connected") diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go index f77191a..a5a7fa4 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/message.go @@ -23,6 +23,8 @@ type Message struct { Timestamp int64 Attempts uint16 + NSQDAddress string + Delegate MessageDelegate autoResponseDisabled int32 @@ -63,11 +65,10 @@ func (m *Message) HasResponded() bool { // Finish sends a FIN command to the nsqd which // sent this message func (m *Message) Finish() { - if m.HasResponded() { + if !atomic.CompareAndSwapInt32(&m.responded, 0, 1) { return } m.Delegate.OnFinish(m) - atomic.StoreInt32(&m.responded, 1) } // Touch sends a TOUCH command to the nsqd which @@ -99,11 +100,10 @@ func (m *Message) RequeueWithoutBackoff(delay time.Duration) { } func (m *Message) doRequeue(delay time.Duration, backoff bool) { - if m.HasResponded() { + if !atomic.CompareAndSwapInt32(&m.responded, 0, 1) { return } m.Delegate.OnRequeue(m, delay, backoff) - atomic.StoreInt32(&m.responded, 1) } // WriteTo implements the WriterTo interface and serializes diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go index 9f62a00..62b9fc0 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/mock_test.go @@ -7,15 +7,30 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "net" - "os" "strconv" "testing" "time" ) +type tbLog interface { + Log(...interface{}) +} + +type testLogger struct { + tbLog +} + +func (tl *testLogger) Output(maxdepth int, s string) error { + tl.Log(s) + return nil +} + +func newTestLogger(tbl tbLog) logger { + return &testLogger{tbl} +} + type instruction struct { delay time.Duration frameType int32 @@ -30,14 +45,13 @@ type mockNSQD struct { exitChan chan int } -func newMockNSQD(script []instruction) *mockNSQD { +func newMockNSQD(script []instruction, addr string) *mockNSQD { n := &mockNSQD{ script: script, exitChan: make(chan int), } - addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") - tcpListener, err := net.Listen("tcp", addr.String()) + tcpListener, err := net.Listen("tcp", addr) if err != nil { log.Fatalf("FATAL: listen (%s) failed - %s", n.tcpAddr.String(), err) } @@ -148,6 +162,7 @@ func (n *mockNSQD) handle(conn net.Conn) { exit: n.tcpListener.Close() + conn.Close() } func framedResponse(frameType int32, data []byte) []byte { @@ -175,53 +190,60 @@ func framedResponse(frameType int32, data []byte) []byte { type testHandler struct{} func (h *testHandler) HandleMessage(message *Message) error { - if bytes.Equal(message.Body, []byte("bad")) { + switch string(message.Body) { + case "requeue": + message.Requeue(-1) + return nil + case "requeue_no_backoff_1": + if message.Attempts > 1 { + return nil + } + message.RequeueWithoutBackoff(-1) + return nil + case "bad": return errors.New("bad") } return nil } -func TestConsumerBackoff(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - - logger := log.New(ioutil.Discard, "", log.LstdFlags) +func frameMessage(m *Message) []byte { + var b bytes.Buffer + m.WriteTo(&b) + return b.Bytes() +} - var mgood bytes.Buffer +func TestConsumerBackoff(t *testing.T) { msgIDGood := MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} msgGood := NewMessage(msgIDGood, []byte("good")) - msgGood.WriteTo(&mgood) - msgBytesGood := mgood.Bytes() - var mbad bytes.Buffer msgIDBad := MessageID{'z', 'x', 'c', 'v', 'b', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} msgBad := NewMessage(msgIDBad, []byte("bad")) - msgBad.WriteTo(&mbad) - msgBytesBad := mbad.Bytes() script := []instruction{ // SUB instruction{0, FrameTypeResponse, []byte("OK")}, // IDENTIFY instruction{0, FrameTypeResponse, []byte("OK")}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesBad}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesBad}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, - instruction{20 * time.Millisecond, FrameTypeMessage, msgBytesGood}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgBad)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgBad)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, // needed to exit test instruction{200 * time.Millisecond, -1, []byte("exit")}, } - n := newMockNSQD(script) + + addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + n := newMockNSQD(script, addr.String()) topicName := "test_consumer_commands" + strconv.Itoa(int(time.Now().Unix())) config := NewConfig() config.MaxInFlight = 5 config.BackoffMultiplier = 10 * time.Millisecond q, _ := NewConsumer(topicName, "ch", config) - q.SetLogger(logger, LogLevelDebug) + q.SetLogger(newTestLogger(t), LogLevelDebug) q.AddHandler(&testHandler{}) err := q.ConnectToNSQD(n.tcpAddr.String()) if err != nil { @@ -263,3 +285,188 @@ func TestConsumerBackoff(t *testing.T) { } } } + +func TestConsumerRequeueNoBackoff(t *testing.T) { + msgIDGood := MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + msgIDRequeue := MessageID{'r', 'e', 'q', 'v', 'b', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + msgIDRequeueNoBackoff := MessageID{'r', 'e', 'q', 'n', 'b', 'a', 'c', 'k', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + + msgGood := NewMessage(msgIDGood, []byte("good")) + msgRequeue := NewMessage(msgIDRequeue, []byte("requeue")) + msgRequeueNoBackoff := NewMessage(msgIDRequeueNoBackoff, []byte("requeue_no_backoff_1")) + + script := []instruction{ + // SUB + instruction{0, FrameTypeResponse, []byte("OK")}, + // IDENTIFY + instruction{0, FrameTypeResponse, []byte("OK")}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgRequeue)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgRequeueNoBackoff)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + // needed to exit test + instruction{100 * time.Millisecond, -1, []byte("exit")}, + } + + addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + n := newMockNSQD(script, addr.String()) + + topicName := "test_requeue" + strconv.Itoa(int(time.Now().Unix())) + config := NewConfig() + config.MaxInFlight = 1 + config.BackoffMultiplier = 10 * time.Millisecond + q, _ := NewConsumer(topicName, "ch", config) + q.SetLogger(newTestLogger(t), LogLevelDebug) + q.AddHandler(&testHandler{}) + err := q.ConnectToNSQD(n.tcpAddr.String()) + if err != nil { + t.Fatalf(err.Error()) + } + + select { + case <-n.exitChan: + log.Printf("clean exit") + case <-time.After(500 * time.Millisecond): + log.Printf("timeout") + } + + for i, r := range n.got { + log.Printf("%d: %s", i, r) + } + + expected := []string{ + "IDENTIFY", + "SUB " + topicName + " ch", + "RDY 1", + "RDY 1", + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDRequeue), + "RDY 1", + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDRequeueNoBackoff), + "RDY 1", + "RDY 1", + fmt.Sprintf("FIN %s", msgIDGood), + } + if len(n.got) != len(expected) { + t.Fatalf("we got %d commands != %d expected", len(n.got), len(expected)) + } + for i, r := range n.got { + if string(r) != expected[i] { + t.Fatalf("cmd %d bad %s != %s", i, r, expected[i]) + } + } +} + +func TestConsumerBackoffDisconnect(t *testing.T) { + msgIDGood := MessageID{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + msgIDRequeue := MessageID{'r', 'e', 'q', 'v', 'b', '6', '7', '8', '9', '0', 'a', 's', 'd', 'f', 'g', 'h'} + + msgGood := NewMessage(msgIDGood, []byte("good")) + msgRequeue := NewMessage(msgIDRequeue, []byte("requeue")) + + script := []instruction{ + // SUB + instruction{0, FrameTypeResponse, []byte("OK")}, + // IDENTIFY + instruction{0, FrameTypeResponse, []byte("OK")}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgRequeue)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgRequeue)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + // needed to exit test + instruction{100 * time.Millisecond, -1, []byte("exit")}, + } + + addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + n := newMockNSQD(script, addr.String()) + + topicName := "test_requeue" + strconv.Itoa(int(time.Now().Unix())) + config := NewConfig() + config.MaxInFlight = 5 + config.BackoffMultiplier = 10 * time.Millisecond + config.LookupdPollInterval = 10 * time.Millisecond + config.RDYRedistributeInterval = 10 * time.Millisecond + q, _ := NewConsumer(topicName, "ch", config) + q.SetLogger(newTestLogger(t), LogLevelDebug) + q.AddHandler(&testHandler{}) + err := q.ConnectToNSQD(n.tcpAddr.String()) + if err != nil { + t.Fatalf(err.Error()) + } + + select { + case <-n.exitChan: + log.Printf("clean exit") + case <-time.After(500 * time.Millisecond): + log.Printf("timeout") + } + + for i, r := range n.got { + log.Printf("%d: %s", i, r) + } + + expected := []string{ + "IDENTIFY", + "SUB " + topicName + " ch", + "RDY 5", + fmt.Sprintf("FIN %s", msgIDGood), + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDRequeue), + "RDY 1", + "RDY 0", + fmt.Sprintf("REQ %s 0", msgIDRequeue), + "RDY 1", + "RDY 0", + fmt.Sprintf("FIN %s", msgIDGood), + "RDY 1", + } + if len(n.got) != len(expected) { + t.Fatalf("we got %d commands != %d expected", len(n.got), len(expected)) + } + for i, r := range n.got { + if string(r) != expected[i] { + t.Fatalf("cmd %d bad %s != %s", i, r, expected[i]) + } + } + + script = []instruction{ + // SUB + instruction{0, FrameTypeResponse, []byte("OK")}, + // IDENTIFY + instruction{0, FrameTypeResponse, []byte("OK")}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + instruction{20 * time.Millisecond, FrameTypeMessage, frameMessage(msgGood)}, + // needed to exit test + instruction{100 * time.Millisecond, -1, []byte("exit")}, + } + + n = newMockNSQD(script, n.tcpAddr.String()) + + select { + case <-n.exitChan: + log.Printf("clean exit") + case <-time.After(500 * time.Millisecond): + log.Printf("timeout") + } + + for i, r := range n.got { + log.Printf("%d: %s", i, r) + } + + expected = []string{ + "IDENTIFY", + "SUB " + topicName + " ch", + "RDY 1", + "RDY 5", + fmt.Sprintf("FIN %s", msgIDGood), + fmt.Sprintf("FIN %s", msgIDGood), + } + if len(n.got) != len(expected) { + t.Fatalf("we got %d commands != %d expected", len(n.got), len(expected)) + } + for i, r := range n.got { + if string(r) != expected[i] { + t.Fatalf("cmd %d bad %s != %s", i, r, expected[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go index be0755d..517c14e 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer.go @@ -9,6 +9,14 @@ import ( "time" ) +type producerConn interface { + String() string + SetLogger(logger, LogLevel, string) + Connect() (*IdentifyResponse, error) + Close() error + WriteCommand(*Command) error +} + // Producer is a high-level type to publish to NSQ. // // A Producer instance is 1:1 with a destination `nsqd` @@ -17,11 +25,12 @@ import ( type Producer struct { id int64 addr string - conn *Conn + conn producerConn config Config - logger logger - logLvl LogLevel + logger logger + logLvl LogLevel + logGuard sync.RWMutex responseChan chan []byte errorChan chan []byte @@ -78,11 +87,27 @@ func NewProducer(addr string, config *Config) (*Producer, error) { exitChan: make(chan int), responseChan: make(chan []byte), errorChan: make(chan []byte), - closeChan: make(chan int), } return p, nil } +// Ping causes the Producer to connect to it's configured nsqd (if not already +// connected) and send a `Nop` command, returning any error that might occur. +// +// This method can be used to verify that a newly-created Producer instance is +// configured correctly, rather than relying on the lazy "connect on Publish" +// behavior of a Producer. +func (w *Producer) Ping() error { + if atomic.LoadInt32(&w.state) != StateConnected { + err := w.connect() + if err != nil { + return err + } + } + + return w.conn.WriteCommand(Nop()) +} + // SetLogger assigns the logger to use as well as a level // // The logger parameter is an interface that requires the following @@ -91,10 +116,20 @@ func NewProducer(addr string, config *Config) (*Producer, error) { // Output(calldepth int, s string) // func (w *Producer) SetLogger(l logger, lvl LogLevel) { + w.logGuard.Lock() + defer w.logGuard.Unlock() + w.logger = l w.logLvl = lvl } +func (w *Producer) getLogger() (logger, LogLevel) { + w.logGuard.RLock() + defer w.logGuard.RUnlock() + + return w.logger, w.logLvl +} + // String returns the address of the Producer func (w *Producer) String() string { return w.addr @@ -145,13 +180,13 @@ func (w *Producer) MultiPublishAsync(topic string, body [][]byte, doneChan chan } // Publish synchronously publishes a message body to the specified topic, returning -// the an error if publish failed +// an error if publish failed func (w *Producer) Publish(topic string, body []byte) error { return w.sendCommand(Publish(topic, body)) } // MultiPublish synchronously publishes a slice of message bodies to the specified topic, returning -// the an error if publish failed +// an error if publish failed func (w *Producer) MultiPublish(topic string, body [][]byte) error { cmd, err := MultiPublish(topic, body) if err != nil { @@ -208,22 +243,29 @@ func (w *Producer) connect() error { return ErrStopped } - if !atomic.CompareAndSwapInt32(&w.state, StateInit, StateConnected) { + switch state := atomic.LoadInt32(&w.state); state { + case StateInit: + case StateConnected: + return nil + default: return ErrNotConnected } w.log(LogLevelInfo, "(%s) connecting to nsqd", w.addr) + logger, logLvl := w.getLogger() + w.conn = NewConn(w.addr, &w.config, &producerConnDelegate{w}) - w.conn.SetLogger(w.logger, w.logLvl, fmt.Sprintf("%3d (%%s)", w.id)) + w.conn.SetLogger(logger, logLvl, fmt.Sprintf("%3d (%%s)", w.id)) + _, err := w.conn.Connect() if err != nil { w.conn.Close() w.log(LogLevelError, "(%s) error connecting to nsqd - %s", w.addr, err) - atomic.StoreInt32(&w.state, StateInit) return err } - + atomic.StoreInt32(&w.state, StateConnected) + w.closeChan = make(chan int) w.wg.Add(1) go w.router() @@ -302,25 +344,30 @@ func (w *Producer) transactionCleanup() { } // give the runtime a chance to schedule other racing goroutines time.Sleep(5 * time.Millisecond) - continue } } } func (w *Producer) log(lvl LogLevel, line string, args ...interface{}) { - if w.logger == nil { + logger, logLvl := w.getLogger() + + if logger == nil { return } - if w.logLvl > lvl { + if logLvl > lvl { return } - w.logger.Output(2, fmt.Sprintf("%-4s %3d %s", logPrefix(lvl), w.id, fmt.Sprintf(line, args...))) + logger.Output(2, fmt.Sprintf("%-4s %3d %s", lvl, w.id, fmt.Sprintf(line, args...))) } func (w *Producer) onConnResponse(c *Conn, data []byte) { w.responseChan <- data } func (w *Producer) onConnError(c *Conn, data []byte) { w.errorChan <- data } func (w *Producer) onConnHeartbeat(c *Conn) {} func (w *Producer) onConnIOError(c *Conn, err error) { w.close() } -func (w *Producer) onConnClose(c *Conn) { w.closeChan <- 1 } +func (w *Producer) onConnClose(c *Conn) { + w.guard.Lock() + defer w.guard.Unlock() + close(w.closeChan) +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go index c4733ac..820ce96 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/producer_test.go @@ -1,11 +1,17 @@ package nsq import ( + "bytes" "errors" "io/ioutil" "log" + "net" "os" + "runtime" "strconv" + "strings" + "sync" + "sync/atomic" "testing" "time" ) @@ -35,16 +41,22 @@ func (h *ConsumerHandler) HandleMessage(message *Message) error { } func TestProducerConnection(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - config := NewConfig() + laddr := "127.0.0.2" + + config.LocalAddr, _ = net.ResolveTCPAddr("tcp", laddr+":0") + w, _ := NewProducer("127.0.0.1:4150", config) w.SetLogger(nullLogger, LogLevelInfo) err := w.Publish("write_test", []byte("test")) if err != nil { - t.Fatalf("should lazily connect") + t.Fatalf("should lazily connect - %s", err) + } + + conn := w.conn.(*Conn) + if !strings.HasPrefix(conn.conn.LocalAddr().String(), laddr) { + t.Fatal("producer connection should be bound to specified address:", conn.conn.LocalAddr()) } w.Stop() @@ -55,10 +67,29 @@ func TestProducerConnection(t *testing.T) { } } -func TestProducerPublish(t *testing.T) { +func TestProducerPing(t *testing.T) { log.SetOutput(ioutil.Discard) defer log.SetOutput(os.Stdout) + config := NewConfig() + w, _ := NewProducer("127.0.0.1:4150", config) + w.SetLogger(nullLogger, LogLevelInfo) + + err := w.Ping() + + if err != nil { + t.Fatalf("should connect on ping") + } + + w.Stop() + + err = w.Ping() + if err != ErrStopped { + t.Fatalf("should not be able to ping after Stop()") + } +} + +func TestProducerPublish(t *testing.T) { topicName := "publish" + strconv.Itoa(int(time.Now().Unix())) msgCount := 10 @@ -83,9 +114,6 @@ func TestProducerPublish(t *testing.T) { } func TestProducerMultiPublish(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - topicName := "multi_publish" + strconv.Itoa(int(time.Now().Unix())) msgCount := 10 @@ -113,9 +141,6 @@ func TestProducerMultiPublish(t *testing.T) { } func TestProducerPublishAsync(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - topicName := "async_publish" + strconv.Itoa(int(time.Now().Unix())) msgCount := 10 @@ -151,9 +176,6 @@ func TestProducerPublishAsync(t *testing.T) { } func TestProducerMultiPublishAsync(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - topicName := "multi_publish" + strconv.Itoa(int(time.Now().Unix())) msgCount := 10 @@ -193,9 +215,6 @@ func TestProducerMultiPublishAsync(t *testing.T) { } func TestProducerHeartbeat(t *testing.T) { - log.SetOutput(ioutil.Discard) - defer log.SetOutput(os.Stdout) - topicName := "heartbeat" + strconv.Itoa(int(time.Now().Unix())) config := NewConfig() @@ -269,3 +288,85 @@ func readMessages(topicName string, t *testing.T, msgCount int) { t.Fatal("failed message not done") } } + +type mockProducerConn struct { + delegate ConnDelegate + closeCh chan struct{} + pubCh chan struct{} +} + +func newMockProducerConn(delegate ConnDelegate) producerConn { + m := &mockProducerConn{ + delegate: delegate, + closeCh: make(chan struct{}), + pubCh: make(chan struct{}, 4), + } + go m.router() + return m +} + +func (m *mockProducerConn) String() string { + return "127.0.0.1:0" +} + +func (m *mockProducerConn) SetLogger(logger logger, level LogLevel, prefix string) {} + +func (m *mockProducerConn) Connect() (*IdentifyResponse, error) { + return &IdentifyResponse{}, nil +} + +func (m *mockProducerConn) Close() error { + close(m.closeCh) + return nil +} + +func (m *mockProducerConn) WriteCommand(cmd *Command) error { + if bytes.Equal(cmd.Name, []byte("PUB")) { + m.pubCh <- struct{}{} + } + return nil +} + +func (m *mockProducerConn) router() { + for { + select { + case <-m.closeCh: + goto exit + case <-m.pubCh: + m.delegate.OnResponse(nil, framedResponse(FrameTypeResponse, []byte("OK"))) + } + } +exit: +} + +func BenchmarkProducer(b *testing.B) { + b.StopTimer() + body := make([]byte, 512) + + config := NewConfig() + p, _ := NewProducer("127.0.0.1:0", config) + + p.conn = newMockProducerConn(&producerConnDelegate{p}) + atomic.StoreInt32(&p.state, StateConnected) + p.closeChan = make(chan int) + go p.router() + + startCh := make(chan struct{}) + var wg sync.WaitGroup + parallel := runtime.GOMAXPROCS(0) + + for j := 0; j < parallel; j++ { + wg.Add(1) + go func() { + <-startCh + for i := 0; i < b.N/parallel; i++ { + p.Publish("test", body) + } + wg.Done() + }() + } + + b.StartTimer() + close(startCh) + wg.Wait() +} diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh b/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh index da16138..351135a 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/test.sh @@ -40,4 +40,4 @@ cleanup() { } trap cleanup INT TERM EXIT -go test -v -timeout 15s +go test -v -timeout 60s diff --git a/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go b/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go index b744228..45907fc 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go +++ b/Godeps/_workspace/src/github.com/bitly/go-nsq/version.go @@ -5,4 +5,4 @@ package nsq // VERSION -const VERSION = "1.0.1" +const VERSION = "1.0.5-alpha" diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go index 516fe76..8031e99 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson.go @@ -305,6 +305,31 @@ func (j *Json) MustString(args ...string) string { return def } +// MustStringArray guarantees the return of a `[]string` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, s := range js.Get("results").MustStringArray() { +// fmt.Println(i, s) +// } +func (j *Json) MustStringArray(args ...[]string) []string { + var def []string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustStringArray() received too many arguments %d", len(args)) + } + + a, err := j.StringArray() + if err == nil { + return a + } + + return def +} + // MustInt guarantees the return of an `int` (with optional default) // // useful when you explicitly want an `int` in a single value return context: diff --git a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go index 5a11156..b46ffff 100644 --- a/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go +++ b/Godeps/_workspace/src/github.com/bitly/go-simplejson/simplejson_test.go @@ -72,6 +72,19 @@ func TestSimplejson(t *testing.T) { ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"}) assert.Equal(t, ma2, []interface{}{"1", 2, "3"}) + msa := js.Get("test").Get("string_array").MustStringArray() + assert.Equal(t, msa[0], "asdf") + assert.Equal(t, msa[1], "ghjk") + assert.Equal(t, msa[2], "zxcv") + + msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"}) + assert.Equal(t, msa2[0], "asdf") + assert.Equal(t, msa2[1], "ghjk") + assert.Equal(t, msa2[2], "zxcv") + + msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"}) + assert.Equal(t, msa3, []string{"1", "2", "3"}) + mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false}) assert.Equal(t, mm2, map[string]interface{}{"found": false}) diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go index f6ae548..8ecb50e 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client.go @@ -7,12 +7,16 @@ import ( "errors" "io" "io/ioutil" + "math/rand" "net" "net/http" "net/url" "os" "path" + "strings" "time" + + "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes" ) // See SetConsistency for how to use these constants. @@ -28,6 +32,10 @@ const ( defaultBufferSize = 10 ) +func init() { + rand.Seed(int64(time.Now().Nanosecond())) +} + type Config struct { CertFile string `json:"certFile"` KeyFile string `json:"keyFile"` @@ -64,8 +72,7 @@ func NewClient(machines []string) *Client { config := Config{ // default timeout is one second DialTimeout: time.Second, - // default consistency level is STRONG - Consistency: STRONG_CONSISTENCY, + Consistency: WEAK_CONSISTENCY, } client := &Client{ @@ -89,8 +96,7 @@ func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) config := Config{ // default timeout is one second DialTimeout: time.Second, - // default consistency level is STRONG - Consistency: STRONG_CONSISTENCY, + Consistency: WEAK_CONSISTENCY, CertFile: cert, KeyFile: key, CaCertFile: make([]string, 0), @@ -292,30 +298,37 @@ func (c *Client) SyncCluster() bool { // internalSyncCluster syncs cluster information using the given machine list. func (c *Client) internalSyncCluster(machines []string) bool { for _, machine := range machines { - httpPath := c.createHttpPath(machine, path.Join(version, "machines")) + httpPath := c.createHttpPath(machine, path.Join(version, "members")) resp, err := c.httpClient.Get(httpPath) if err != nil { // try another machine in the cluster continue - } else { - b, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - // try another machine in the cluster - continue - } - - // update Machines List - c.cluster.updateFromStr(string(b)) - - // update leader - // the first one in the machine list is the leader - c.cluster.switchLeader(0) - - logger.Debug("sync.machines ", c.cluster.Machines) - c.saveConfig() - return true } + + b, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + // try another machine in the cluster + continue + } + + var mCollection httptypes.MemberCollection + if err := json.Unmarshal(b, &mCollection); err != nil { + // try another machine + continue + } + + urls := make([]string, 0) + for _, m := range mCollection { + urls = append(urls, m.ClientURLs...) + } + + // update Machines List + c.cluster.updateFromStr(strings.Join(urls, ",")) + + logger.Debug("sync.machines ", c.cluster.Machines) + c.saveConfig() + return true } return false } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go index c245e47..66d79d7 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/client_test.go @@ -10,7 +10,7 @@ import ( ) // To pass this test, we need to create a cluster of 3 machines -// The server should be listening on 127.0.0.1:4001, 4002, 4003 +// The server should be listening on localhost:4001, 4002, 4003 func TestSync(t *testing.T) { fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003") @@ -36,8 +36,8 @@ func TestSync(t *testing.T) { if err != nil { t.Fatal(err) } - if host != "127.0.0.1" { - t.Fatal("Host must be 127.0.0.1") + if host != "localhost" { + t.Fatal("Host must be localhost") } } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go index aaa2054..787cf75 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/cluster.go @@ -1,13 +1,14 @@ package etcd import ( - "net/url" + "math/rand" "strings" ) type Cluster struct { Leader string `json:"leader"` Machines []string `json:"machines"` + picked int } func NewCluster(machines []string) *Cluster { @@ -18,34 +19,16 @@ func NewCluster(machines []string) *Cluster { // default leader and machines return &Cluster{ - Leader: machines[0], + Leader: "", Machines: machines, + picked: rand.Intn(len(machines)), } } -// switchLeader switch the current leader to machines[num] -func (cl *Cluster) switchLeader(num int) { - logger.Debugf("switch.leader[from %v to %v]", - cl.Leader, cl.Machines[num]) - - cl.Leader = cl.Machines[num] -} +func (cl *Cluster) failure() { cl.picked = rand.Intn(len(cl.Machines)) } +func (cl *Cluster) pick() string { return cl.Machines[cl.picked] } func (cl *Cluster) updateFromStr(machines string) { - cl.Machines = strings.Split(machines, ", ") -} - -func (cl *Cluster) updateLeader(leader string) { - logger.Debugf("update.leader[%s,%s]", cl.Leader, leader) - cl.Leader = leader -} - -func (cl *Cluster) updateLeaderFromURL(u *url.URL) { - var leader string - if u.Scheme == "" { - leader = "http://" + u.Host - } else { - leader = u.Scheme + "://" + u.Host - } - cl.updateLeader(leader) + cl.Machines = strings.Split(machines, ",") + cl.picked = rand.Intn(len(cl.Machines)) } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go index 7e69287..66dca54 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/error.go @@ -6,7 +6,8 @@ import ( ) const ( - ErrCodeEtcdNotReachable = 501 + ErrCodeEtcdNotReachable = 501 + ErrCodeUnhandledHTTPStatus = 502 ) var ( diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go index 976bf07..09fe641 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/get.go @@ -18,9 +18,14 @@ func (c *Client) Get(key string, sort, recursive bool) (*Response, error) { } func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) { + var q bool + if c.config.Consistency == STRONG_CONSISTENCY { + q = true + } ops := Options{ "recursive": recursive, "sorted": sort, + "quorum": q, } return c.get(key, ops) diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go index 701c9b3..d21c96f 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/options.go @@ -17,11 +17,11 @@ type validOptions map[string]reflect.Kind // values are meant to be used as constants. var ( VALID_GET_OPTIONS = validOptions{ - "recursive": reflect.Bool, - "consistent": reflect.Bool, - "sorted": reflect.Bool, - "wait": reflect.Bool, - "waitIndex": reflect.Uint64, + "recursive": reflect.Bool, + "quorum": reflect.Bool, + "sorted": reflect.Bool, + "wait": reflect.Bool, + "waitIndex": reflect.Uint64, } VALID_PUT_OPTIONS = validOptions{ diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go index 2741cd0..70d9db2 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/requests.go @@ -5,7 +5,7 @@ import ( "fmt" "io" "io/ioutil" - "math/rand" + "net" "net/http" "net/url" "path" @@ -39,15 +39,9 @@ func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan // getCancelable issues a cancelable GET request func (c *Client) getCancelable(key string, options Options, cancel <-chan bool) (*RawResponse, error) { - logger.Debugf("get %s [%s]", key, c.cluster.Leader) + logger.Debugf("get %s [%s]", key, c.cluster.pick()) p := keyToPath(key) - // If consistency level is set to STRONG, append - // the `consistent` query string. - if c.config.Consistency == STRONG_CONSISTENCY { - options["consistent"] = true - } - str, err := options.toParameters(VALID_GET_OPTIONS) if err != nil { return nil, err @@ -73,7 +67,7 @@ func (c *Client) get(key string, options Options) (*RawResponse, error) { func (c *Client) put(key string, value string, ttl uint64, options Options) (*RawResponse, error) { - logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) p := keyToPath(key) str, err := options.toParameters(VALID_PUT_OPTIONS) @@ -94,7 +88,7 @@ func (c *Client) put(key string, value string, ttl uint64, // post issues a POST request func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) { - logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader) + logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.pick()) p := keyToPath(key) req := NewRawRequest("POST", p, buildValues(value, ttl), nil) @@ -109,7 +103,7 @@ func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error // delete issues a DELETE request func (c *Client) delete(key string, options Options) (*RawResponse, error) { - logger.Debugf("delete %s [%s]", key, c.cluster.Leader) + logger.Debugf("delete %s [%s]", key, c.cluster.pick()) p := keyToPath(key) str, err := options.toParameters(VALID_DELETE_OPTIONS) @@ -130,7 +124,6 @@ func (c *Client) delete(key string, options Options) (*RawResponse, error) { // SendRequest sends a HTTP request and returns a Response as defined by etcd func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { - var req *http.Request var resp *http.Response var httpPath string @@ -196,14 +189,7 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { logger.Debug("Connecting to etcd: attempt ", attempt+1, " for ", rr.RelativePath) - if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY { - // If it's a GET and consistency level is set to WEAK, - // then use a random machine. - httpPath = c.getHttpPath(true, rr.RelativePath) - } else { - // Else use the leader. - httpPath = c.getHttpPath(false, rr.RelativePath) - } + httpPath = c.getHttpPath(rr.RelativePath) // Return a cURL command if curlChan is set if c.cURLch != nil { @@ -264,7 +250,7 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { return nil, checkErr } - c.cluster.switchLeader(attempt % len(c.cluster.Machines)) + c.cluster.failure() continue } @@ -295,22 +281,6 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { } } - // if resp is TemporaryRedirect, set the new leader and retry - if resp.StatusCode == http.StatusTemporaryRedirect { - u, err := resp.Location() - - if err != nil { - logger.Warning(err) - } else { - // Update cluster leader based on redirect location - // because it should point to the leader address - c.cluster.updateLeaderFromURL(u) - logger.Debug("recv.response.relocate ", u.String()) - } - resp.Body.Close() - continue - } - if checkErr := checkRetry(c.cluster, numReqs, *resp, errors.New("Unexpected HTTP status code")); checkErr != nil { return nil, checkErr @@ -333,34 +303,53 @@ func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) { func DefaultCheckRetry(cluster *Cluster, numReqs int, lastResp http.Response, err error) error { - if numReqs >= 2*len(cluster.Machines) { - return newError(ErrCodeEtcdNotReachable, - "Tried to connect to each peer twice and failed", 0) + if isEmptyResponse(lastResp) { + if !isConnectionError(err) { + return err + } + } else if !shouldRetry(lastResp) { + body := []byte("nil") + if lastResp.Body != nil { + if b, err := ioutil.ReadAll(lastResp.Body); err == nil { + body = b + } + } + errStr := fmt.Sprintf("unhandled http status [%s] with body [%s]", http.StatusText(lastResp.StatusCode), body) + return newError(ErrCodeUnhandledHTTPStatus, errStr, 0) } - code := lastResp.StatusCode - if code == http.StatusInternalServerError { + if numReqs > 2*len(cluster.Machines) { + errStr := fmt.Sprintf("failed to propose on members %v twice [last error: %v]", cluster.Machines, err) + return newError(ErrCodeEtcdNotReachable, errStr, 0) + } + if shouldRetry(lastResp) { + // sleep some time and expect leader election finish time.Sleep(time.Millisecond * 200) - } - logger.Warning("bad response status code", code) + logger.Warning("bad response status code", lastResp.StatusCode) return nil } -func (c *Client) getHttpPath(random bool, s ...string) string { - var machine string - if random { - machine = c.cluster.Machines[rand.Intn(len(c.cluster.Machines))] - } else { - machine = c.cluster.Leader - } +func isEmptyResponse(r http.Response) bool { return r.StatusCode == 0 } - fullPath := machine + "/" + version +func isConnectionError(err error) bool { + _, ok := err.(*net.OpError) + return ok +} + +// shouldRetry returns whether the reponse deserves retry. +func shouldRetry(r http.Response) bool { + // TODO: only retry when the cluster is in leader election + // We cannot do it exactly because etcd doesn't support it well. + return r.StatusCode == http.StatusInternalServerError +} + +func (c *Client) getHttpPath(s ...string) string { + fullPath := c.cluster.pick() + "/" + version for _, seg := range s { fullPath = fullPath + "/" + seg } - return fullPath } diff --git a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go index 756e317..87c86b8 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go @@ -19,7 +19,7 @@ func TestSetCurlChan(t *testing.T) { } expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5", - c.cluster.Leader) + c.cluster.pick()) actual := c.RecvCURL() if expected != actual { t.Fatalf(`Command "%s" is not equal to expected value "%s"`, @@ -32,8 +32,8 @@ func TestSetCurlChan(t *testing.T) { t.Fatal(err) } - expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false", - c.cluster.Leader) + expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?quorum=true&recursive=false&sorted=false", + c.cluster.pick()) actual = c.RecvCURL() if expected != actual { t.Fatalf(`Command "%s" is not equal to expected value "%s"`, diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml b/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml index 7c38172..ae719be 100644 --- a/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/.travis.yml @@ -1,7 +1,7 @@ -language: go - -go: - - 1.0 - - 1.1 - - 1.2 - - tip +language: go + +go: + - 1.2 + - 1.3 + - 1.4 + - tip diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE b/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE deleted file mode 100644 index c52f931..0000000 --- a/Godeps/_workspace/src/github.com/dchest/uniuri/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011 Dmitry Chestnykh - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/README.md b/Godeps/_workspace/src/github.com/dchest/uniuri/README.md index 1be9671..b321a5f 100644 --- a/Godeps/_workspace/src/github.com/dchest/uniuri/README.md +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/README.md @@ -1,83 +1,97 @@ -Package uniuri -===================== - -[![Build Status](https://travis-ci.org/dchest/uniuri.png)](https://travis-ci.org/dchest/uniuri) - -```go -import "github.com/dchest/uniuri" -``` - -Package uniuri generates random strings good for use in URIs to identify -unique objects. - -Example usage: - -```go -s := uniuri.New() // s is now "apHCJBl7L1OmC57n" -``` - -A standard string created by New() is 16 bytes in length and consists of -Latin upper and lowercase letters, and numbers (from the set of 62 allowed -characters), which means that it has ~95 bits of entropy. To get more -entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving -~119 bits of entropy, or any other desired length. - -Functions read from crypto/rand random source, and panic if they fail to -read from it. - - -Constants ---------- - -```go -const ( - // Standard length of uniuri string to achive ~95 bits of entropy. - StdLen = 16 - // Length of uniurl string to achive ~119 bits of entropy, closest - // to what can be losslessly converted to UUIDv4 (122 bits). - UUIDLen = 20 -) -``` - - - -Variables ---------- - -```go -var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") -``` - -Standard characters allowed in uniuri string. - - -Functions ---------- - -### func New - -```go -func New() string -``` - -New returns a new random string of the standard length, consisting of -standard characters. - -### func NewLen - -```go -func NewLen(length int) string -``` - -NewLen returns a new random string of the provided length, consisting of -standard characters. - -### func NewLenChars - -```go -func NewLenChars(length int, chars []byte) string -``` - -NewLenChars returns a new random string of the provided length, consisting -of the provided byte slice of allowed characters (maximum 256). - +Package uniuri +===================== + +[![Build Status](https://travis-ci.org/dchest/uniuri.svg)](https://travis-ci.org/dchest/uniuri) + +```go +import "github.com/dchest/uniuri" +``` + +Package uniuri generates random strings good for use in URIs to identify +unique objects. + +Example usage: + +```go +s := uniuri.New() // s is now "apHCJBl7L1OmC57n" +``` + +A standard string created by New() is 16 bytes in length and consists of +Latin upper and lowercase letters, and numbers (from the set of 62 allowed +characters), which means that it has ~95 bits of entropy. To get more +entropy, you can use NewLen(UUIDLen), which returns 20-byte string, giving +~119 bits of entropy, or any other desired length. + +Functions read from crypto/rand random source, and panic if they fail to +read from it. + + +Constants +--------- + +```go +const ( + // StdLen is a standard length of uniuri string to achive ~95 bits of entropy. + StdLen = 16 + // UUIDLen is a length of uniuri string to achive ~119 bits of entropy, closest + // to what can be losslessly converted to UUIDv4 (122 bits). + UUIDLen = 20 +) + +``` + + + +Variables +--------- + +```go +var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") +``` + + +StdChars is a set of standard characters allowed in uniuri string. + + +Functions +--------- + +### func New + +```go +func New() string +``` + +New returns a new random string of the standard length, consisting of +standard characters. + +### func NewLen + +```go +func NewLen(length int) string +``` + +NewLen returns a new random string of the provided length, consisting of +standard characters. + +### func NewLenChars + +```go +func NewLenChars(length int, chars []byte) string +``` + +NewLenChars returns a new random string of the provided length, consisting +of the provided byte slice of allowed characters (maximum 256). + + + +Public domain dedication +------------------------ + +Written in 2011-2014 by Dmitry Chestnykh + +The author(s) have dedicated all copyright and related and +neighboring rights to this software to the public domain +worldwide. Distributed without any warranty. +http://creativecommons.org/publicdomain/zero/1.0/ + diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go index c2b8ae1..6393446 100644 --- a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri.go @@ -1,3 +1,10 @@ +// Written in 2011-2014 by Dmitry Chestnykh +// +// The author(s) have dedicated all copyright and related and +// neighboring rights to this software to the public domain +// worldwide. Distributed without any warranty. +// http://creativecommons.org/publicdomain/zero/1.0/ + // Package uniuri generates random strings good for use in URIs to identify // unique objects. // @@ -15,20 +22,17 @@ // read from it. package uniuri -import ( - "crypto/rand" - "io" -) +import "crypto/rand" const ( - // Standard length of uniuri string to achive ~95 bits of entropy. + // StdLen is a standard length of uniuri string to achive ~95 bits of entropy. StdLen = 16 - // Length of uniurl string to achive ~119 bits of entropy, closest + // UUIDLen is a length of uniuri string to achive ~119 bits of entropy, closest // to what can be losslessly converted to UUIDv4 (122 bits). UUIDLen = 20 ) -// Standard characters allowed in uniuri string. +// StdChars is a set of standard characters allowed in uniuri string. var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") // New returns a new random string of the standard length, consisting of @@ -46,17 +50,20 @@ func NewLen(length int) string { // NewLenChars returns a new random string of the provided length, consisting // of the provided byte slice of allowed characters (maximum 256). func NewLenChars(length int, chars []byte) string { - b := make([]byte, length) - r := make([]byte, length+(length/4)) // storage for random bytes. + if length == 0 { + return "" + } clen := len(chars) - if clen > 256 { - panic("uniuri: maximum length of charset for NewLenChars is 256") + if clen < 2 || clen > 256 { + panic("uniuri: wrong charset length for NewLenChars") } - maxrb := 256 - (256 % clen) + maxrb := 255 - (256 % clen) + b := make([]byte, length) + r := make([]byte, length+(length/4)) // storage for random bytes. i := 0 for { - if _, err := io.ReadFull(rand.Reader, r); err != nil { - panic("error reading from random source: " + err.Error()) + if _, err := rand.Read(r); err != nil { + panic("uniuri: error reading random bytes: " + err.Error()) } for _, rb := range r { c := int(rb) @@ -71,5 +78,4 @@ func NewLenChars(length int, chars []byte) string { } } } - panic("unreachable") } diff --git a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go index 17b3ba1..c9c3ffb 100644 --- a/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go +++ b/Godeps/_workspace/src/github.com/dchest/uniuri/uniuri_test.go @@ -1,3 +1,10 @@ +// Written in 2011-2014 by Dmitry Chestnykh +// +// The author(s) have dedicated all copyright and related and +// neighboring rights to this software to the public domain +// worldwide. Distributed without any warranty. +// http://creativecommons.org/publicdomain/zero/1.0/ + package uniuri import "testing" @@ -5,7 +12,7 @@ import "testing" func validateChars(t *testing.T, u string, chars []byte) { for _, c := range u { var present bool - for _, a := range StdChars { + for _, a := range chars { if rune(a) == c { present = true } @@ -27,18 +34,27 @@ func TestNew(t *testing.T) { // Generate 1000 uniuris and check that they are unique uris := make([]string, 1000) - for i, _ := range uris { + for i := range uris { uris[i] = New() } for i, u := range uris { for j, u2 := range uris { if i != j && u == u2 { - t.Fatalf("not unique: %d:%q and %d:%q", i, j, u, u2) + t.Fatalf("not unique: %d:%q and %d:%q", i, u, j, u2) } } } } +func TestNewLen(t *testing.T) { + for i := 0; i < 100; i++ { + u := NewLen(i) + if len(u) != i { + t.Fatalf("request length %d, got %d", i, len(u)) + } + } +} + func TestNewLenChars(t *testing.T) { length := 10 chars := []byte("01234567") diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE b/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE deleted file mode 100644 index fd1000d..0000000 --- a/Godeps/_workspace/src/github.com/pzduniak/glogrus/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2014 Carl Jackson, Sajith Appukuttan - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md b/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md deleted file mode 100644 index 1b41ee4..0000000 --- a/Godeps/_workspace/src/github.com/pzduniak/glogrus/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# goji/glogrus [![GoDoc](https://godoc.org/github.com/goji/glogrus?status.png)](https://godoc.org/github.com/goji/glogrus) - -glogrus provides structured logging via logrus for Goji. - -## Example - - -```go - -package main - -import( - "github.com/zenazn/goji" - "github.com/zenazn/goji/web/middleware" - "github.com/goji/glogrus" - "github.com/Sirupsen/logrus" -) - -func main() { - goji.Abandon(middleware.Logger) - - logr := logrus.New() - logr.Formatter = new(logrus.JSONFormatter) - goji.Use(glogrus.NewGlogrus(logr, "my-app-name")) - - goji.Get("/ping", yourHandler) - goji.Serve() -} - -``` - -- - - - - -#### Looking for hierarchical structured logging? -[slog](https://github.com/zenazn/slog) and [lunk](https://github.com/codahale/lunk) looks interesting. \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go b/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go deleted file mode 100644 index 4a9e03d..0000000 --- a/Godeps/_workspace/src/github.com/pzduniak/glogrus/glogrus.go +++ /dev/null @@ -1,80 +0,0 @@ -package glogrus - -import ( - "fmt" - "net/http" - "time" - - "github.com/Sirupsen/logrus" - "github.com/zenazn/goji/web" - "github.com/zenazn/goji/web/middleware" -) - -// glogrus is a middleware handler that logs the -// request and response in a structured way -type glogrus struct { - h http.Handler - c *web.C - l *logrus.Logger - name string -} - -func (glogr glogrus) ServeHTTP(resp http.ResponseWriter, req *http.Request) { - start := time.Now() - reqID := middleware.GetReqID(*glogr.c) - glogr.l.WithFields(logrus.Fields{ - "req_id": reqID, - "uri": req.RequestURI, - "method": req.Method, - "remote": req.RemoteAddr, - }).Info("New request incoming") - lresp := wrapWriter(resp) - - glogr.h.ServeHTTP(lresp, req) - lresp.maybeWriteHeader() - - latency := float64(time.Since(start)) / float64(time.Millisecond) - - glogr.l.WithFields(logrus.Fields{ - "req_id": reqID, - "status": lresp.status(), - "method": req.Method, - "uri": req.RequestURI, - "remote": req.RemoteAddr, - "latency": fmt.Sprintf("%6.4f ms", latency), - "app": glogr.name, - }).Info("Finished serving the request") -} - -// NewGlogrus allows you to configure a goji middleware that logs all requests and responses -// using the structured logger logrus. It takes the logrus instance and the name of the app -// as the parameters and returns a middleware of type "func(c *web.C, http.Handler) http.Handler" -// -// Example: -// -// package main -// -// import( -// "github.com/zenazn/goji" -// "github.com/zenazn/goji/web/middleware" -// "github.com/goji/glogrus" -// "github.com/Sirupsen/logrus" -// ) -// -// func main() { -// goji.Abandon(middleware.Logger) -// -// logr := logrus.New() -// logr.Formatter = new(logrus.JSONFormatter) -// goji.Use(glogrus.NewGlogrus(logr, "my-app-name")) -// -// goji.Get("/ping", yourHandler) -// goji.Serve() -// } -// -func NewGlogrus(l *logrus.Logger, name string) func(*web.C, http.Handler) http.Handler { - fn := func(c *web.C, h http.Handler) http.Handler { - return glogrus{h: h, c: c, l: l, name: name} - } - return fn -} diff --git a/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go b/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go deleted file mode 100644 index 16a887f..0000000 --- a/Godeps/_workspace/src/github.com/pzduniak/glogrus/writer_proxy.go +++ /dev/null @@ -1,70 +0,0 @@ -package glogrus - -import ( - "bufio" - "net" - "net/http" -) - -// wrapWriter returns a proxy that wraps ResponseWriter -func wrapWriter(w http.ResponseWriter) writerProxy { - bw := basicWriter{ResponseWriter: w} - return &bw -} - -// writerProxy is a proxy that wraps ResponseWriter -type writerProxy interface { - http.ResponseWriter - maybeWriteHeader() - status() int -} - -// basicWriter holds the status code and a -// flag in addition to http.ResponseWriter -type basicWriter struct { - http.ResponseWriter - wroteHeader bool - code int -} - -// WriteHeader stores the status code and writes header -func (b *basicWriter) WriteHeader(code int) { - if !b.wroteHeader { - b.code = code - b.wroteHeader = true - b.ResponseWriter.WriteHeader(code) - } -} - -// Write writes the bytes and calls MaybeWriteHeader -func (b *basicWriter) Write(buf []byte) (int, error) { - b.maybeWriteHeader() - return b.ResponseWriter.Write(buf) -} - -// maybeWriteHeader writes the header if it is not alredy set -func (b *basicWriter) maybeWriteHeader() { - if !b.wroteHeader { - b.WriteHeader(http.StatusOK) - } -} - -// status returns the status -func (b *basicWriter) status() int { - return b.code -} - -// unwrap returns the original http.ResponseWriter -func (b *basicWriter) Unwrap() http.ResponseWriter { - return b.ResponseWriter -} - -// Flush is used for the implementation of http.Flusher -func (b *basicWriter) Flush() { - b.ResponseWriter.(http.Flusher).Flush() -} - -// Hijack is used for the implementation of http.Hijacker -func (b *basicWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return b.ResponseWriter.(http.Hijacker).Hijack() -} diff --git a/Godeps/_workspace/src/github.com/willf/bitset/.gitignore b/Godeps/_workspace/src/github.com/willf/bitset/.gitignore new file mode 100644 index 0000000..6cd1df2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/willf/bitset/CHANGELOG b/Godeps/_workspace/src/github.com/willf/bitset/CHANGELOG new file mode 100644 index 0000000..30410d9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/CHANGELOG @@ -0,0 +1,10 @@ +2014-02-14 Version 1.0.0 💗 + +- First 'released' version +- includes various fixes by: + - Daniel Lemire + - Todd Vierling + - Zellyn Hunter + - Seyi Ogunyemi + - Simon Menke + - Cenk Altı \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/willf/bitset/LICENSE.txt b/Godeps/_workspace/src/github.com/willf/bitset/LICENSE.txt new file mode 100644 index 0000000..59cab8a --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (c) 2014 Will Fitzgerald. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/willf/bitset/README.md b/Godeps/_workspace/src/github.com/willf/bitset/README.md new file mode 100644 index 0000000..bbf1ea5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/README.md @@ -0,0 +1,44 @@ +Package bitset implements bitsets, a mapping +between non-negative integers and boolean values. It should be more +efficient than map[uint] bool. + +It provides methods for setting, clearing, flipping, and testing +individual integers. + +But it also provides set intersection, union, difference, +complement, and symmetric operations, as well as tests to +check whether any, all, or no bits are set, and querying a +bitset's current length and number of postive bits. + +BitSets are expanded to the size of the largest set bit; the +memory allocation is approximately Max bits, where Max is +the largest set bit. BitSets are never shrunk. On creation, +a hint can be given for the number of bits that will be used. + +Many of the methods, including Set, Clear, and Flip, return +a BitSet pointer, which allows for chaining. + +Example use: + + import "bitset" + var b BitSet + b.Set(10).Set(11) + if b.Test(1000) { + b.Clear(1000) + } + for i,e := v.NextSet(0); e; i,e = v.NextSet(i + 1) { + frmt.Println("The following bit is set:",i); + } + if B.Intersection(bitset.New(100).Set(10)).Count() > 1 { + fmt.Println("Intersection works.") + } + +As an alternative to BitSets, one should check out the 'big' package, +which provides a (less set-theoretical) view of bitsets. + +Discussions golang-nuts Google Group: + +* [Revised BitSet](https://groups.google.com/forum/#!topic/golang-nuts/5i3l0CXDiBg) +* [simple bitset?](https://groups.google.com/d/topic/golang-nuts/7n1VkRTlBf4/discussion) + +Godoc documentation is at: https://godoc.org/github.com/willf/bitset diff --git a/Godeps/_workspace/src/github.com/willf/bitset/bitset.go b/Godeps/_workspace/src/github.com/willf/bitset/bitset.go new file mode 100644 index 0000000..9d53932 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/bitset.go @@ -0,0 +1,614 @@ +/* +Package bitset implements bitsets, a mapping +between non-negative integers and boolean values. It should be more +efficient than map[uint] bool. + +It provides methods for setting, clearing, flipping, and testing +individual integers. + +But it also provides set intersection, union, difference, +complement, and symmetric operations, as well as tests to +check whether any, all, or no bits are set, and querying a +bitset's current length and number of postive bits. + +BitSets are expanded to the size of the largest set bit; the +memory allocation is approximately Max bits, where Max is +the largest set bit. BitSets are never shrunk. On creation, +a hint can be given for the number of bits that will be used. + +Many of the methods, including Set,Clear, and Flip, return +a BitSet pointer, which allows for chaining. + +Example use: + + import "bitset" + var b BitSet + b.Set(10).Set(11) + if b.Test(1000) { + b.Clear(1000) + } + if B.Intersection(bitset.New(100).Set(10)).Count() > 1 { + fmt.Println("Intersection works.") + } + +As an alternative to BitSets, one should check out the 'big' package, +which provides a (less set-theoretical) view of bitsets. + +*/ +package bitset + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "io" +) + +// the wordSize of a bit set +const wordSize = uint(64) + +// log2WordSize is lg(wordSize) +const log2WordSize = uint(6) + +// A BitSet is a set of bits. The zero value of a BitSet is an empty set of length 0. +type BitSet struct { + length uint + set []uint64 +} + +// A BitSetError is used to distinguish errors (panics) generated in this package. +type BitSetError string + +// safeSet will fixup b.set to be non-nil and return the field value +func (b *BitSet) safeSet() []uint64 { + if b.set == nil { + b.set = make([]uint64, wordsNeeded(0)) + } + return b.set +} + +// wordsNeeded calculates the number of words needed for i bits +func wordsNeeded(i uint) int { + if i > ((^uint(0)) - wordSize + 1) { + return int((^uint(0)) >> log2WordSize) + } + return int((i + (wordSize - 1)) >> log2WordSize) +} + +// New creates a new BitSet with a hint that length bits will be required +func New(length uint) *BitSet { + return &BitSet{length, make([]uint64, wordsNeeded(length))} +} + +// Cap returns the total possible capicity, or number of bits +func Cap() uint { + return ^uint(0) +} + +// Len returns the length of the BitSet in words +func (b *BitSet) Len() uint { + return b.length +} + +// extendSetMaybe adds additional words to incorporate new bits if needed +func (b *BitSet) extendSetMaybe(i uint) { + if i >= b.length { // if we need more bits, make 'em + nsize := wordsNeeded(i + 1) + if b.set == nil { + b.set = make([]uint64, nsize) + } else if len(b.set) < nsize { + newset := make([]uint64, nsize) + copy(newset, b.set) + b.set = newset + } + b.length = i + 1 + } +} + +// Test whether bit i is set. +func (b *BitSet) Test(i uint) bool { + if i >= b.length { + return false + } + return b.set[i>>log2WordSize]&(1<<(i&(wordSize-1))) != 0 +} + +// Set bit i to 1 +func (b *BitSet) Set(i uint) *BitSet { + b.extendSetMaybe(i) + b.set[i>>log2WordSize] |= 1 << (i & (wordSize - 1)) + return b +} + +// Clear bit i to 0 +func (b *BitSet) Clear(i uint) *BitSet { + if i >= b.length { + return b + } + b.set[i>>log2WordSize] &^= 1 << (i & (wordSize - 1)) + return b +} + +// SetTo sets bit i to value +func (b *BitSet) SetTo(i uint, value bool) *BitSet { + if value { + return b.Set(i) + } + return b.Clear(i) +} + +// Flip bit at i +func (b *BitSet) Flip(i uint) *BitSet { + if i >= b.length { + return b.Set(i) + } + b.set[i>>log2WordSize] ^= 1 << (i & (wordSize - 1)) + return b +} + +// NextSet returns the next bit set from the specified index, +// including possibly the current index +// along with an error code (true = valid, false = no set bit found) +// for i,e := v.NextSet(0); e; i,e = v.NextSet(i + 1) {...} +func (b *BitSet) NextSet(i uint) (uint, bool) { + x := int(i >> log2WordSize) + if x >= len(b.set) { + return 0, false + } + w := b.set[x] + w = w >> (i & (wordSize - 1)) + if w != 0 { + return i + trailingZeroes64(w), true + } + x = x + 1 + for x < len(b.set) { + if b.set[x] != 0 { + return uint(x)*wordSize + trailingZeroes64(b.set[x]), true + } + x = x + 1 + + } + return 0, false +} + +// ClearAll clears the entire BitSet +func (b *BitSet) ClearAll() *BitSet { + if b != nil && b.set != nil { + for i := range b.set { + b.set[i] = 0 + } + } + return b +} + +// wordCount returns the number of words used in a bit set +func (b *BitSet) wordCount() int { + return wordsNeeded(b.length) +} + +// Clone this BitSet +func (b *BitSet) Clone() *BitSet { + c := New(b.length) + if b.set != nil { // Clone should not modify current object + copy(c.set, b.set) + } + return c +} + +// Copy this BitSet into a destination BitSet +// Returning the size of the destination BitSet +// like array copy +func (b *BitSet) Copy(c *BitSet) (count uint) { + if c == nil { + return + } + if b.set != nil { // Copy should not modify current object + copy(c.set, b.set) + } + count = c.length + if b.length < c.length { + count = b.length + } + return +} + +// Count (number of set bits) +func (b *BitSet) Count() uint { + if b != nil && b.set != nil { + return uint(popcntSlice(b.set)) + } + return 0 +} + +var deBruijn = [...]byte{ + 0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4, + 62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5, + 63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11, + 54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6, +} + +func trailingZeroes64(v uint64) uint { + return uint(deBruijn[((v&-v)*0x03f79d71b4ca8b09)>>58]) +} + +// Equal tests the equvalence of two BitSets. +// False if they are of different sizes, otherwise true +// only if all the same bits are set +func (b *BitSet) Equal(c *BitSet) bool { + if c == nil { + return false + } + if b.length != c.length { + return false + } + if b.length == 0 { // if they have both length == 0, then could have nil set + return true + } + // testing for equality shoud not transform the bitset (no call to safeSet) + + for p, v := range b.set { + if c.set[p] != v { + return false + } + } + return true +} + +func panicIfNull(b *BitSet) { + if b == nil { + panic(BitSetError("BitSet must not be null")) + } +} + +// Difference of base set and other set +// This is the BitSet equivalent of &^ (and not) +func (b *BitSet) Difference(compare *BitSet) (result *BitSet) { + panicIfNull(b) + panicIfNull(compare) + result = b.Clone() // clone b (in case b is bigger than compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + for i := 0; i < l; i++ { + result.set[i] = b.set[i] &^ compare.set[i] + } + return +} + +// DifferenceCardinality computes the cardinality of the differnce +func (b *BitSet) DifferenceCardinality(compare *BitSet) uint { + panicIfNull(b) + panicIfNull(compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + cnt := uint64(0) + cnt += popcntMaskSlice(b.set[:l], compare.set[:l]) + cnt += popcntSlice(b.set[l:]) + return uint(cnt) +} + +// InPlaceDifference computes the difference of base set and other set +// This is the BitSet equivalent of &^ (and not) +func (b *BitSet) InPlaceDifference(compare *BitSet) { + panicIfNull(b) + panicIfNull(compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + for i := 0; i < l; i++ { + b.set[i] &^= compare.set[i] + } +} + +// Convenience function: return two bitsets ordered by +// increasing length. Note: neither can be nil +func sortByLength(a *BitSet, b *BitSet) (ap *BitSet, bp *BitSet) { + if a.length <= b.length { + ap, bp = a, b + } else { + ap, bp = b, a + } + return +} + +// Intersection of base set and other set +// This is the BitSet equivalent of & (and) +func (b *BitSet) Intersection(compare *BitSet) (result *BitSet) { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + result = New(b.length) + for i, word := range b.set { + result.set[i] = word & compare.set[i] + } + return +} + +// IntersectionCardinality computes the cardinality of the union +func (b *BitSet) IntersectionCardinality(compare *BitSet) uint { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + cnt := popcntAndSlice(b.set, compare.set) + return uint(cnt) +} + +// InPlaceIntersection destructively computes the intersection of +// base set and the compare set. +// This is the BitSet equivalent of & (and) +func (b *BitSet) InPlaceIntersection(compare *BitSet) { + panicIfNull(b) + panicIfNull(compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + for i := 0; i < l; i++ { + b.set[i] &= compare.set[i] + } + for i := l; i < len(b.set); i++ { + b.set[i] = 0 + } + if compare.length > 0 { + b.extendSetMaybe(compare.length - 1) + } + return +} + +// Union of base set and other set +// This is the BitSet equivalent of | (or) +func (b *BitSet) Union(compare *BitSet) (result *BitSet) { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + result = compare.Clone() + for i, word := range b.set { + result.set[i] = word | compare.set[i] + } + return +} + +// UnionCardinality computes the cardinality of the uniton of the base set +// and the compare set. +func (b *BitSet) UnionCardinality(compare *BitSet) uint { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + cnt := popcntOrSlice(b.set, compare.set) + if len(compare.set) > len(b.set) { + cnt += popcntSlice(compare.set[len(b.set):]) + } + return uint(cnt) +} + +// InPlaceUnion creates the destructive union of base set and compare set. +// This is the BitSet equivalent of | (or). +func (b *BitSet) InPlaceUnion(compare *BitSet) { + panicIfNull(b) + panicIfNull(compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + if compare.length > 0 { + b.extendSetMaybe(compare.length - 1) + } + for i := 0; i < l; i++ { + b.set[i] |= compare.set[i] + } + if len(compare.set) > l { + for i := l; i < len(compare.set); i++ { + b.set[i] = compare.set[i] + } + } +} + +// SymmetricDifference of base set and other set +// This is the BitSet equivalent of ^ (xor) +func (b *BitSet) SymmetricDifference(compare *BitSet) (result *BitSet) { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + // compare is bigger, so clone it + result = compare.Clone() + for i, word := range b.set { + result.set[i] = word ^ compare.set[i] + } + return +} + +// SymmetricDifferenceCardinality computes the cardinality of the symmetric difference +func (b *BitSet) SymmetricDifferenceCardinality(compare *BitSet) uint { + panicIfNull(b) + panicIfNull(compare) + b, compare = sortByLength(b, compare) + cnt := popcntXorSlice(b.set, compare.set) + if len(compare.set) > len(b.set) { + cnt += popcntSlice(compare.set[len(b.set):]) + } + return uint(cnt) +} + +// InPlaceSymmetricDifference creates the destructive SymmetricDifference of base set and other set +// This is the BitSet equivalent of ^ (xor) +func (b *BitSet) InPlaceSymmetricDifference(compare *BitSet) { + panicIfNull(b) + panicIfNull(compare) + l := int(compare.wordCount()) + if l > int(b.wordCount()) { + l = int(b.wordCount()) + } + if compare.length > 0 { + b.extendSetMaybe(compare.length - 1) + } + for i := 0; i < l; i++ { + b.set[i] ^= compare.set[i] + } + if len(compare.set) > l { + for i := l; i < len(compare.set); i++ { + b.set[i] = compare.set[i] + } + } +} + +// Is the length an exact multiple of word sizes? +func (b *BitSet) isEven() bool { + return b.length%wordSize == 0 +} + +// Clean last word by setting unused bits to 0 +func (b *BitSet) cleanLastWord() { + if !b.isEven() { + // Mask for cleaning last word + const allBits uint64 = 0xffffffffffffffff + b.set[wordsNeeded(b.length)-1] &= allBits >> (wordSize - b.length%wordSize) + } +} + +// Complement computes the (local) complement of a biset (up to length bits) +func (b *BitSet) Complement() (result *BitSet) { + panicIfNull(b) + result = New(b.length) + for i, word := range b.set { + result.set[i] = ^word + } + result.cleanLastWord() + return +} + +// All returns true if all bits are set, false otherwise +func (b *BitSet) All() bool { + panicIfNull(b) + return b.Count() == b.length +} + +// None returns true if no bit is set, false otherwise +func (b *BitSet) None() bool { + panicIfNull(b) + if b != nil && b.set != nil { + for _, word := range b.set { + if word > 0 { + return false + } + } + return true + } + return true +} + +// Any returns true if any bit is set, false otherwise +func (b *BitSet) Any() bool { + panicIfNull(b) + return !b.None() +} + +// IsSuperSet returns true if this is a superset of the other set +func (b *BitSet) IsSuperSet(other *BitSet) bool { + for i, e := b.NextSet(0); e; i, e = b.NextSet(i + 1) { + if !other.Test(i) { + return false + } + } + return true +} + +// IsStrictSuperSet returns true if this is a strict superset of the other set +func (b *BitSet) IsStrictSuperSet(other *BitSet) bool { + return b.Count() > other.Count() && b.IsSuperSet(other) +} + +// DumpAsBits dumps a bit set as a string of bits +func (b *BitSet) DumpAsBits() string { + if b.set == nil { + return "." + } + buffer := bytes.NewBufferString("") + i := len(b.set) - 1 + for ; i >= 0; i-- { + fmt.Fprintf(buffer, "%064b.", b.set[i]) + } + return string(buffer.Bytes()) +} + +// BinaryStorageSize returns the binary storage requirements +func (b *BitSet) BinaryStorageSize() int { + return binary.Size(uint64(0)) + binary.Size(b.set) +} + +// WriteTo writes a BitSet to a stream +func (b *BitSet) WriteTo(stream io.Writer) (int64, error) { + length := uint64(b.length) + + // Write length + err := binary.Write(stream, binary.BigEndian, length) + if err != nil { + return 0, err + } + + // Write set + err = binary.Write(stream, binary.BigEndian, b.set) + return int64(b.BinaryStorageSize()), err +} + +// ReadFrom reads a BitSet from a stream written using WriteTo +func (b *BitSet) ReadFrom(stream io.Reader) (int64, error) { + var length uint64 + + // Read length first + err := binary.Read(stream, binary.BigEndian, &length) + if err != nil { + return 0, err + } + newset := New(uint(length)) + + if uint64(newset.length) != length { + return 0, errors.New("Unmarshalling error: type mismatch") + } + + // Read remaining bytes as set + err = binary.Read(stream, binary.BigEndian, newset.set) + if err != nil { + return 0, err + } + + *b = *newset + return int64(b.BinaryStorageSize()), nil +} + +// MarshalJSON marshals a BitSet as a JSON structure +func (b *BitSet) MarshalJSON() ([]byte, error) { + buffer := bytes.NewBuffer(make([]byte, 0, b.BinaryStorageSize())) + _, err := b.WriteTo(buffer) + if err != nil { + return nil, err + } + + // URLEncode all bytes + return json.Marshal(base64.URLEncoding.EncodeToString(buffer.Bytes())) +} + +// UnmarshalJSON unmarshals a BitSet from JSON created using MarshalJSON +func (b *BitSet) UnmarshalJSON(data []byte) error { + // Unmarshal as string + var s string + err := json.Unmarshal(data, &s) + if err != nil { + return err + } + + // URLDecode string + buf, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return err + } + + _, err = b.ReadFrom(bytes.NewReader(buf)) + return err +} diff --git a/Godeps/_workspace/src/github.com/willf/bitset/bitset_test.go b/Godeps/_workspace/src/github.com/willf/bitset/bitset_test.go new file mode 100644 index 0000000..3215ab3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/bitset_test.go @@ -0,0 +1,767 @@ +// Copyright 2014 Will Fitzgerald. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file tests bit sets + +package bitset + +import ( + "encoding/json" + "math" + "math/rand" + "testing" +) + +func TestEmptyBitSet(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error("A zero-length bitset should be fine") + } + }() + b := New(0) + if b.Len() != 0 { + t.Errorf("Empty set should have capacity 0, not %d", b.Len()) + } +} + +func TestZeroValueBitSet(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Error("A zero-length bitset should be fine") + } + }() + var b BitSet + if b.Len() != 0 { + t.Errorf("Empty set should have capacity 0, not %d", b.Len()) + } +} + +func TestBitSetNew(t *testing.T) { + v := New(16) + if v.Test(0) != false { + t.Errorf("Unable to make a bit set and read its 0th value.") + } +} + +func TestBitSetHuge(t *testing.T) { + v := New(uint(math.MaxUint32)) + if v.Test(0) != false { + t.Errorf("Unable to make a huge bit set and read its 0th value.") + } +} + +func TestLen(t *testing.T) { + v := New(1000) + if v.Len() != 1000 { + t.Errorf("Len should be 1000, but is %d.", v.Len()) + } +} + +func TestBitSetIsClear(t *testing.T) { + v := New(1000) + for i := uint(0); i < 1000; i++ { + if v.Test(i) != false { + t.Errorf("Bit %d is set, and it shouldn't be.", i) + } + } +} + +func TestExendOnBoundary(t *testing.T) { + v := New(32) + defer func() { + if r := recover(); r != nil { + t.Error("Border out of index error should not have caused a panic") + } + }() + v.Set(32) +} + +func TestExpand(t *testing.T) { + v := New(0) + defer func() { + if r := recover(); r != nil { + t.Error("Expansion should not have caused a panic") + } + }() + for i := uint(0); i < 1000; i++ { + v.Set(i) + } +} + +func TestBitSetAndGet(t *testing.T) { + v := New(1000) + v.Set(100) + if v.Test(100) != true { + t.Errorf("Bit %d is clear, and it shouldn't be.", 100) + } +} + +func TestIterate(t *testing.T) { + v := New(10000) + v.Set(0) + v.Set(1) + v.Set(2) + data := make([]uint, 3) + c := 0 + for i, e := v.NextSet(0); e; i, e = v.NextSet(i + 1) { + data[c] = i + c++ + } + if data[0] != 0 { + t.Errorf("bug 0") + } + if data[1] != 1 { + t.Errorf("bug 1") + } + if data[2] != 2 { + t.Errorf("bug 2") + } + v.Set(10) + v.Set(2000) + data = make([]uint, 5) + c = 0 + for i, e := v.NextSet(0); e; i, e = v.NextSet(i + 1) { + data[c] = i + c++ + } + if data[0] != 0 { + t.Errorf("bug 0") + } + if data[1] != 1 { + t.Errorf("bug 1") + } + if data[2] != 2 { + t.Errorf("bug 2") + } + if data[3] != 10 { + t.Errorf("bug 3") + } + if data[4] != 2000 { + t.Errorf("bug 4") + } + +} + +func TestSetTo(t *testing.T) { + v := New(1000) + v.SetTo(100, true) + if v.Test(100) != true { + t.Errorf("Bit %d is clear, and it shouldn't be.", 100) + } + v.SetTo(100, false) + if v.Test(100) != false { + t.Errorf("Bit %d is set, and it shouldn't be.", 100) + } +} + +func TestChain(t *testing.T) { + if New(1000).Set(100).Set(99).Clear(99).Test(100) != true { + t.Errorf("Bit %d is clear, and it shouldn't be.", 100) + } +} + +func TestOutOfBoundsLong(t *testing.T) { + v := New(64) + defer func() { + if r := recover(); r != nil { + t.Error("Long distance out of index error should not have caused a panic") + } + }() + v.Set(1000) +} + +func TestOutOfBoundsClose(t *testing.T) { + v := New(65) + defer func() { + if r := recover(); r != nil { + t.Error("Local out of index error should not have caused a panic") + } + }() + v.Set(66) +} + +func TestCount(t *testing.T) { + tot := uint(64*4 + 11) // just some multi unit64 number + v := New(tot) + checkLast := true + for i := uint(0); i < tot; i++ { + sz := uint(v.Count()) + if sz != i { + t.Errorf("Count reported as %d, but it should be %d", sz, i) + checkLast = false + break + } + v.Set(i) + } + if checkLast { + sz := uint(v.Count()) + if sz != tot { + t.Errorf("After all bits set, size reported as %d, but it should be %d", sz, tot) + } + } +} + +// test setting every 3rd bit, just in case something odd is happening +func TestCount2(t *testing.T) { + tot := uint(64*4 + 11) // just some multi unit64 number + v := New(tot) + for i := uint(0); i < tot; i += 3 { + sz := uint(v.Count()) + if sz != i/3 { + t.Errorf("Count reported as %d, but it should be %d", sz, i) + break + } + v.Set(i) + } +} + +// nil tests +func TestNullTest(t *testing.T) { + var v *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Checking bit of null reference should have caused a panic") + } + }() + v.Test(66) +} + +func TestNullSet(t *testing.T) { + var v *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Setting bit of null reference should have caused a panic") + } + }() + v.Set(66) +} + +func TestNullClear(t *testing.T) { + var v *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Clearning bit of null reference should have caused a panic") + } + }() + v.Clear(66) +} + +func TestNullCount(t *testing.T) { + var v *BitSet = nil + defer func() { + if r := recover(); r != nil { + t.Error("Counting null reference should not have caused a panic") + } + }() + cnt := v.Count() + if cnt != 0 { + t.Errorf("Count reported as %d, but it should be 0", cnt) + } +} + +func TestPanicDifferenceBNil(t *testing.T) { + var b *BitSet = nil + var compare = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil First should should have caused a panic") + } + }() + b.Difference(compare) +} + +func TestPanicDifferenceCompareNil(t *testing.T) { + var compare *BitSet = nil + var b = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil Second should should have caused a panic") + } + }() + b.Difference(compare) +} + +func TestPanicUnionBNil(t *testing.T) { + var b *BitSet = nil + var compare = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil First should should have caused a panic") + } + }() + b.Union(compare) +} + +func TestPanicUnionCompareNil(t *testing.T) { + var compare *BitSet = nil + var b = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil Second should should have caused a panic") + } + }() + b.Union(compare) +} + +func TestPanicIntersectionBNil(t *testing.T) { + var b *BitSet = nil + var compare = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil First should should have caused a panic") + } + }() + b.Intersection(compare) +} + +func TestPanicIntersectionCompareNil(t *testing.T) { + var compare *BitSet = nil + var b = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil Second should should have caused a panic") + } + }() + b.Intersection(compare) +} + +func TestPanicSymmetricDifferenceBNil(t *testing.T) { + var b *BitSet = nil + var compare = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil First should should have caused a panic") + } + }() + b.SymmetricDifference(compare) +} + +func TestPanicSymmetricDifferenceCompareNil(t *testing.T) { + var compare *BitSet = nil + var b = New(10) + defer func() { + if r := recover(); r == nil { + t.Error("Nil Second should should have caused a panic") + } + }() + b.SymmetricDifference(compare) +} + +func TestPanicComplementBNil(t *testing.T) { + var b *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Nil should should have caused a panic") + } + }() + b.Complement() +} + +func TestPanicAnytBNil(t *testing.T) { + var b *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Nil should should have caused a panic") + } + }() + b.Any() +} + +func TestPanicNonetBNil(t *testing.T) { + var b *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Nil should should have caused a panic") + } + }() + b.None() +} + +func TestPanicAlltBNil(t *testing.T) { + var b *BitSet = nil + defer func() { + if r := recover(); r == nil { + t.Error("Nil should should have caused a panic") + } + }() + b.All() +} + +func TestEqual(t *testing.T) { + a := New(100) + b := New(99) + c := New(100) + if a.Equal(b) { + t.Error("Sets of different sizes should be not be equal") + } + if !a.Equal(c) { + t.Error("Two empty sets of the same size should be equal") + } + a.Set(99) + c.Set(0) + if a.Equal(c) { + t.Error("Two sets with differences should not be equal") + } + c.Set(99) + a.Set(0) + if !a.Equal(c) { + t.Error("Two sets with the same bits set should be equal") + } +} + +func TestUnion(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + if a.UnionCardinality(b) != 200 { + t.Errorf("Union should have 200 bits set, but had %d", a.UnionCardinality(b)) + } + if a.UnionCardinality(b) != b.UnionCardinality(a) { + t.Errorf("Union should be symmetric") + } + + c := a.Union(b) + d := b.Union(a) + if c.Count() != 200 { + t.Errorf("Union should have 200 bits set, but had %d", c.Count()) + } + if !c.Equal(d) { + t.Errorf("Union should be symmetric") + } +} + +func TestInPlaceUnion(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + c := a.Clone() + c.InPlaceUnion(b) + d := b.Clone() + d.InPlaceUnion(a) + if c.Count() != 200 { + t.Errorf("Union should have 200 bits set, but had %d", c.Count()) + } + if d.Count() != 200 { + t.Errorf("Union should have 200 bits set, but had %d", d.Count()) + } + if !c.Equal(d) { + t.Errorf("Union should be symmetric") + } +} + +func TestIntersection(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1).Set(i) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + if a.IntersectionCardinality(b) != 50 { + t.Errorf("Intersection should have 50 bits set, but had %d", a.IntersectionCardinality(b)) + } + if a.IntersectionCardinality(b) != b.IntersectionCardinality(a) { + t.Errorf("Intersection should be symmetric") + } + c := a.Intersection(b) + d := b.Intersection(a) + if c.Count() != 50 { + t.Errorf("Intersection should have 50 bits set, but had %d", c.Count()) + } + if !c.Equal(d) { + t.Errorf("Intersection should be symmetric") + } +} + +func TestInplaceIntersection(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1).Set(i) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + c := a.Clone() + c.InPlaceIntersection(b) + d := b.Clone() + d.InPlaceIntersection(a) + if c.Count() != 50 { + t.Errorf("Intersection should have 50 bits set, but had %d", c.Count()) + } + if d.Count() != 50 { + t.Errorf("Intersection should have 50 bits set, but had %d", d.Count()) + } + if !c.Equal(d) { + t.Errorf("Intersection should be symmetric") + } +} + +func TestDifference(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + if a.DifferenceCardinality(b) != 50 { + t.Errorf("a-b Difference should have 50 bits set, but had %d", a.DifferenceCardinality(b)) + } + if b.DifferenceCardinality(a) != 150 { + t.Errorf("b-a Difference should have 150 bits set, but had %d", b.DifferenceCardinality(a)) + } + + c := a.Difference(b) + d := b.Difference(a) + if c.Count() != 50 { + t.Errorf("a-b Difference should have 50 bits set, but had %d", c.Count()) + } + if d.Count() != 150 { + t.Errorf("b-a Difference should have 150 bits set, but had %d", d.Count()) + } + if c.Equal(d) { + t.Errorf("Difference, here, should not be symmetric") + } +} + +func TestInPlaceDifference(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) + b.Set(i - 1) + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + c := a.Clone() + c.InPlaceDifference(b) + d := b.Clone() + d.InPlaceDifference(a) + if c.Count() != 50 { + t.Errorf("a-b Difference should have 50 bits set, but had %d", c.Count()) + } + if d.Count() != 150 { + t.Errorf("b-a Difference should have 150 bits set, but had %d", d.Count()) + } + if c.Equal(d) { + t.Errorf("Difference, here, should not be symmetric") + } +} + +func TestSymmetricDifference(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) // 01010101010 ... 0000000 + b.Set(i - 1).Set(i) // 11111111111111111000000 + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + if a.SymmetricDifferenceCardinality(b) != 150 { + t.Errorf("a^b Difference should have 150 bits set, but had %d", a.SymmetricDifferenceCardinality(b)) + } + if b.SymmetricDifferenceCardinality(a) != 150 { + t.Errorf("b^a Difference should have 150 bits set, but had %d", b.SymmetricDifferenceCardinality(a)) + } + + c := a.SymmetricDifference(b) + d := b.SymmetricDifference(a) + if c.Count() != 150 { + t.Errorf("a^b Difference should have 150 bits set, but had %d", c.Count()) + } + if d.Count() != 150 { + t.Errorf("b^a Difference should have 150 bits set, but had %d", d.Count()) + } + if !c.Equal(d) { + t.Errorf("SymmetricDifference should be symmetric") + } +} + +func TestInPlaceSymmetricDifference(t *testing.T) { + a := New(100) + b := New(200) + for i := uint(1); i < 100; i += 2 { + a.Set(i) // 01010101010 ... 0000000 + b.Set(i - 1).Set(i) // 11111111111111111000000 + } + for i := uint(100); i < 200; i++ { + b.Set(i) + } + c := a.Clone() + c.InPlaceSymmetricDifference(b) + d := b.Clone() + d.InPlaceSymmetricDifference(a) + if c.Count() != 150 { + t.Errorf("a^b Difference should have 150 bits set, but had %d", c.Count()) + } + if d.Count() != 150 { + t.Errorf("b^a Difference should have 150 bits set, but had %d", d.Count()) + } + if !c.Equal(d) { + t.Errorf("SymmetricDifference should be symmetric") + } +} + +func TestComplement(t *testing.T) { + a := New(50) + b := a.Complement() + if b.Count() != 50 { + t.Errorf("Complement failed, size should be 50, but was %d", b.Count()) + } + a = New(50) + a.Set(10).Set(20).Set(42) + b = a.Complement() + if b.Count() != 47 { + t.Errorf("Complement failed, size should be 47, but was %d", b.Count()) + } +} + +func TestIsSuperSet(t *testing.T) { + a := New(500) + b := New(300) + for i := uint(0); i < 200; i++ { + a.Set(i) + b.Set(i) + } + if a.IsSuperSet(b) != true { + t.Errorf("IsSuperSet fails") + } + if a.IsStrictSuperSet(b) != false { + t.Errorf("IsStrictSuperSet fails") + } +} + +func TestDumpAsBits(t *testing.T) { + a := New(10).Set(10) + astr := "0000000000000000000000000000000000000000000000000000010000000000." + if a.DumpAsBits() != astr { + t.Errorf("DumpAsBits failed, output should be \"%s\" but was \"%s\"", astr, a.DumpAsBits()) + } + var b BitSet // zero value (b.set == nil) + bstr := "." + if b.DumpAsBits() != bstr { + t.Errorf("DumpAsBits failed, output should be \"%s\" but was \"%s\"", bstr, b.DumpAsBits()) + } +} + +func TestMarshalUnmarshalJSON(t *testing.T) { + a := New(1010).Set(10).Set(1001) + data, err := json.Marshal(a) + if err != nil { + t.Errorf(err.Error()) + return + } + + b := new(BitSet) + err = json.Unmarshal(data, b) + if err != nil { + t.Errorf(err.Error()) + return + } + + // Bitsets must be equal after marshalling and unmarshalling + if !a.Equal(b) { + t.Error("Bitsets are not equal:\n\t", a.DumpAsBits(), "\n\t", b.DumpAsBits()) + return + } +} + +// BENCHMARKS + +func BenchmarkSet(b *testing.B) { + b.StopTimer() + r := rand.New(rand.NewSource(0)) + sz := 100000 + s := New(uint(sz)) + b.StartTimer() + for i := 0; i < b.N; i++ { + s.Set(uint(r.Int31n(int32(sz)))) + } +} + +func BenchmarkGetTest(b *testing.B) { + b.StopTimer() + r := rand.New(rand.NewSource(0)) + sz := 100000 + s := New(uint(sz)) + b.StartTimer() + for i := 0; i < b.N; i++ { + s.Test(uint(r.Int31n(int32(sz)))) + } +} + +func BenchmarkSetExpand(b *testing.B) { + b.StopTimer() + sz := uint(100000) + b.StartTimer() + for i := 0; i < b.N; i++ { + var s BitSet + s.Set(sz) + } +} + +// go test -bench=Count +func BenchmarkCount(b *testing.B) { + b.StopTimer() + s := New(100000) + for i := 0; i < 100000; i += 100 { + s.Set(uint(i)) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + s.Count() + } +} + +// go test -bench=Iterate +func BenchmarkIterate(b *testing.B) { + b.StopTimer() + s := New(10000) + for i := 0; i < 10000; i += 3 { + s.Set(uint(i)) + } + b.StartTimer() + for j := 0; j < b.N; j++ { + c := uint(0) + for i, e := s.NextSet(0); e; i, e = s.NextSet(i + 1) { + c++ + } + } +} + +// go test -bench=SparseIterate +func BenchmarkSparseIterate(b *testing.B) { + b.StopTimer() + s := New(100000) + for i := 0; i < 100000; i += 30 { + s.Set(uint(i)) + } + b.StartTimer() + for j := 0; j < b.N; j++ { + c := uint(0) + for i, e := s.NextSet(0); e; i, e = s.NextSet(i + 1) { + c++ + } + } +} diff --git a/Godeps/_workspace/src/github.com/willf/bitset/popcnt.go b/Godeps/_workspace/src/github.com/willf/bitset/popcnt.go new file mode 100644 index 0000000..76577a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/popcnt.go @@ -0,0 +1,53 @@ +package bitset + +// bit population count, take from +// https://code.google.com/p/go/issues/detail?id=4988#c11 +// credit: https://code.google.com/u/arnehormann/ +func popcount(x uint64) (n uint64) { + x -= (x >> 1) & 0x5555555555555555 + x = (x>>2)&0x3333333333333333 + x&0x3333333333333333 + x += x >> 4 + x &= 0x0f0f0f0f0f0f0f0f + x *= 0x0101010101010101 + return x >> 56 +} + +func popcntSliceGo(s []uint64) uint64 { + cnt := uint64(0) + for _, x := range s { + cnt += popcount(x) + } + return cnt +} + +func popcntMaskSliceGo(s, m []uint64) uint64 { + cnt := uint64(0) + for i := range s { + cnt += popcount(s[i] &^ m[i]) + } + return cnt +} + +func popcntAndSliceGo(s, m []uint64) uint64 { + cnt := uint64(0) + for i := range s { + cnt += popcount(s[i] & m[i]) + } + return cnt +} + +func popcntOrSliceGo(s, m []uint64) uint64 { + cnt := uint64(0) + for i := range s { + cnt += popcount(s[i] | m[i]) + } + return cnt +} + +func popcntXorSliceGo(s, m []uint64) uint64 { + cnt := uint64(0) + for i := range s { + cnt += popcount(s[i] ^ m[i]) + } + return cnt +} diff --git a/Godeps/_workspace/src/github.com/willf/bitset/popcnt_amd64.s b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_amd64.s new file mode 100644 index 0000000..ca106f9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_amd64.s @@ -0,0 +1,103 @@ +// +build !appengine +TEXT ·hasAsm(SB),4,$0 +MOVQ $1, AX +CPUID +SHRQ $23, CX +ANDQ $1, CX +MOVB CX, ret+0(FP) +RET + + +#define POPCNTQ_DX_DX BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0xd2 + +TEXT ·popcntSliceAsm(SB),4,$0-32 +XORQ AX, AX +MOVQ s+0(FP), SI +MOVQ s+8(FP), CX +TESTQ CX, CX +JZ popcntSliceEnd +popcntSliceLoop: +BYTE $0xf3; BYTE $0x48; BYTE $0x0f; BYTE $0xb8; BYTE $0x16 // POPCNTQ (SI), DX +ADDQ DX, AX +ADDQ $8, SI +LOOP popcntSliceLoop +popcntSliceEnd: +MOVQ AX, ret+24(FP) +RET + +TEXT ·popcntMaskSliceAsm(SB),4,$0-56 +XORQ AX, AX +MOVQ s+0(FP), SI +MOVQ s+8(FP), CX +TESTQ CX, CX +JZ popcntMaskSliceEnd +MOVQ m+24(FP), DI +popcntMaskSliceLoop: +MOVQ (DI), DX +NOTQ DX +ANDQ (SI), DX +POPCNTQ_DX_DX +ADDQ DX, AX +ADDQ $8, SI +ADDQ $8, DI +LOOP popcntMaskSliceLoop +popcntMaskSliceEnd: +MOVQ AX, ret+48(FP) +RET + +TEXT ·popcntAndSliceAsm(SB),4,$0-56 +XORQ AX, AX +MOVQ s+0(FP), SI +MOVQ s+8(FP), CX +TESTQ CX, CX +JZ popcntAndSliceEnd +MOVQ m+24(FP), DI +popcntAndSliceLoop: +MOVQ (DI), DX +ANDQ (SI), DX +POPCNTQ_DX_DX +ADDQ DX, AX +ADDQ $8, SI +ADDQ $8, DI +LOOP popcntAndSliceLoop +popcntAndSliceEnd: +MOVQ AX, ret+48(FP) +RET + +TEXT ·popcntOrSliceAsm(SB),4,$0-56 +XORQ AX, AX +MOVQ s+0(FP), SI +MOVQ s+8(FP), CX +TESTQ CX, CX +JZ popcntOrSliceEnd +MOVQ m+24(FP), DI +popcntOrSliceLoop: +MOVQ (DI), DX +ORQ (SI), DX +POPCNTQ_DX_DX +ADDQ DX, AX +ADDQ $8, SI +ADDQ $8, DI +LOOP popcntOrSliceLoop +popcntOrSliceEnd: +MOVQ AX, ret+48(FP) +RET + +TEXT ·popcntXorSliceAsm(SB),4,$0-56 +XORQ AX, AX +MOVQ s+0(FP), SI +MOVQ s+8(FP), CX +TESTQ CX, CX +JZ popcntXorSliceEnd +MOVQ m+24(FP), DI +popcntXorSliceLoop: +MOVQ (DI), DX +XORQ (SI), DX +POPCNTQ_DX_DX +ADDQ DX, AX +ADDQ $8, SI +ADDQ $8, DI +LOOP popcntXorSliceLoop +popcntXorSliceEnd: +MOVQ AX, ret+48(FP) +RET diff --git a/Godeps/_workspace/src/github.com/willf/bitset/popcnt_asm.go b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_asm.go new file mode 100644 index 0000000..9fe38ca --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_asm.go @@ -0,0 +1,64 @@ +// +build amd64,!appengine + +package bitset + +//go:noescape + +func hasAsm() bool + +var useAsm = hasAsm() + +//go:noescape + +func popcntSliceAsm(s []uint64) uint64 + +//go:noescape + +func popcntMaskSliceAsm(s, m []uint64) uint64 + +//go:noescape + +func popcntAndSliceAsm(s, m []uint64) uint64 + +//go:noescape + +func popcntOrSliceAsm(s, m []uint64) uint64 + +//go:noescape + +func popcntXorSliceAsm(s, m []uint64) uint64 + +func popcntSlice(s []uint64) uint64 { + if useAsm { + return popcntSliceAsm(s) + } + return popcntSliceGo(s) +} + +func popcntMaskSlice(s, m []uint64) uint64 { + if useAsm { + return popcntMaskSliceAsm(s, m) + } + return popcntMaskSliceGo(s, m) +} + +func popcntAndSlice(s, m []uint64) uint64 { + if useAsm { + return popcntAndSliceAsm(s, m) + } + return popcntAndSliceGo(s, m) +} + +func popcntOrSlice(s, m []uint64) uint64 { + if useAsm { + return popcntOrSliceAsm(s, m) + } + return popcntOrSliceGo(s, m) +} + +func popcntXorSlice(s, m []uint64) uint64 { + if useAsm { + return popcntXorSliceAsm(s, m) + } + return popcntXorSliceGo(s, m) +} diff --git a/Godeps/_workspace/src/github.com/willf/bitset/popcnt_generic.go b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_generic.go new file mode 100644 index 0000000..6b21cb7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bitset/popcnt_generic.go @@ -0,0 +1,23 @@ +// +build !amd64 appengine + +package bitset + +func popcntSlice(s []uint64) uint64 { + return popcntSliceGo(s) +} + +func popcntMaskSlice(s, m []uint64) uint64 { + return popcntMaskSliceGo(s, m) +} + +func popcntAndSlice(s, m []uint64) uint64 { + return popcntAndSliceGo(s, m) +} + +func popcntOrSlice(s, m []uint64) uint64 { + return popcntOrSliceGo(s, m) +} + +func popcntXorSlice(s, m []uint64) uint64 { + return popcntXorSliceGo(s, m) +} diff --git a/Godeps/_workspace/src/github.com/willf/bloom/CHANGELOG b/Godeps/_workspace/src/github.com/willf/bloom/CHANGELOG new file mode 100644 index 0000000..2f70ffd --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bloom/CHANGELOG @@ -0,0 +1,8 @@ +2014-02-14 Version 1.0.0 💗 + +- First released version + - Contains contributions from: + - Will Fitzgerald + - "chkno" + - Cenk Altı + - Simon Menke diff --git a/Godeps/_workspace/src/github.com/willf/bloom/LICENSE.txt b/Godeps/_workspace/src/github.com/willf/bloom/LICENSE.txt new file mode 100644 index 0000000..3b9d36a --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bloom/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2014 Will Fitzgerald. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/willf/bloom/README.md b/Godeps/_workspace/src/github.com/willf/bloom/README.md new file mode 100644 index 0000000..0bb6458 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bloom/README.md @@ -0,0 +1,52 @@ +Bloom filters +------------- + +A Bloom filter is a representation of a set of _n_ items, where the main +requirement is to make membership queries; _i.e._, whether an item is a +member of a set. + +A Bloom filter has two parameters: _m_, a maximum size (typically a reasonably large +multiple of the cardinality of the set to represent) and _k_, the number of hashing +functions on elements of the set. (The actual hashing functions are important, too, +but this is not a parameter for this implementation). A Bloom filter is backed by +a BitSet; a key is represented in the filter by setting the bits at each value of the +hashing functions (modulo _m_). Set membership is done by _testing_ whether the +bits at each value of the hashing functions (again, modulo _m_) are set. If so, +the item is in the set. If the item is actually in the set, a Bloom filter will +never fail (the true positive rate is 1.0); but it is susceptible to false +positives. The art is to choose _k_ and _m_ correctly. + +In this implementation, the hashing functions used is a local version of FNV, +a non-cryptographic hashing function, seeded with the index number of +the kth hashing function. + +This implementation accepts keys for setting as testing as []byte. Thus, to +add a string item, "Love": + + uint n = 1000 + filter := bloom.New(20*n, 5) // load of 20, 5 keys + filter.Add([]byte("Love")) + +Similarly, to test if "Love" is in bloom: + + if filter.Test([]byte("Love")) + +For numeric data, I recommend that you look into the binary/encoding library. But, +for example, to add a uint32 to the filter: + + i := uint32(100) + n1 := make([]byte,4) + binary.BigEndian.PutUint32(n1,i) + f.Add(n1) + +Finally, there is a method to estimate the false positive rate of a particular +bloom filter for a set of size _n_: + + if filter.EstimateFalsePositiveRate(1000) > 0.001 + +Given the particular hashing scheme, it's best to be empirical about this. Note +that estimating the FP rate will clear the Bloom filter. + +Discussion here: [Bloom filter](https://groups.google.com/d/topic/golang-nuts/6MktecKi1bE/discussion) + +Godoc documentation at https://godoc.org/github.com/willf/bloom diff --git a/Godeps/_workspace/src/github.com/willf/bloom/bloom.go b/Godeps/_workspace/src/github.com/willf/bloom/bloom.go new file mode 100644 index 0000000..142d1b0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bloom/bloom.go @@ -0,0 +1,297 @@ +/* +Package bloom provides data structures and methods for creating Bloom filters. + +A Bloom filter is a representation of a set of _n_ items, where the main +requirement is to make membership queries; _i.e._, whether an item is a +member of a set. + +A Bloom filter has two parameters: _m_, a maximum size (typically a reasonably large +multiple of the cardinality of the set to represent) and _k_, the number of hashing +functions on elements of the set. (The actual hashing functions are important, too, +but this is not a parameter for this implementation). A Bloom filter is backed by +a BitSet; a key is represented in the filter by setting the bits at each value of the +hashing functions (modulo _m_). Set membership is done by _testing_ whether the +bits at each value of the hashing functions (again, modulo _m_) are set. If so, +the item is in the set. If the item is actually in the set, a Bloom filter will +never fail (the true positive rate is 1.0); but it is susceptible to false +positives. The art is to choose _k_ and _m_ correctly. + +In this implementation, the hashing functions used is a local version of FNV, +a non-cryptographic hashing function, seeded with the index number of +the kth hashing function. + +This implementation accepts keys for setting as testing as []byte. Thus, to +add a string item, "Love": + + uint n = 1000 + filter := bloom.New(20*n, 5) // load of 20, 5 keys + filter.Add([]byte("Love")) + +Similarly, to test if "Love" is in bloom: + + if filter.Test([]byte("Love")) + +For numeric data, I recommend that you look into the binary/encoding library. But, +for example, to add a uint32 to the filter: + + i := uint32(100) + n1 := make([]byte,4) + binary.BigEndian.PutUint32(n1,i) + f.Add(n1) + +Finally, there is a method to estimate the false positive rate of a particular +bloom filter for a set of size _n_: + + if filter.EstimateFalsePositiveRate(1000) > 0.001 + +Given the particular hashing scheme, it's best to be empirical about this. Note +that estimating the FP rate will clear the Bloom filter. +*/ +package bloom + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "io" + "math" + + "github.com/willf/bitset" +) + +// A BloomFilter is a representation of a set of _n_ items, where the main +// requirement is to make membership queries; _i.e._, whether an item is a +// member of a set. +type BloomFilter struct { + m uint + k uint + b *bitset.BitSet +} + +// New creates a new Bloom filter with _m_ bits and _k_ hashing functions +func New(m uint, k uint) *BloomFilter { + return &BloomFilter{m, k, bitset.New(m)} +} + +func fnv64Hash(index uint, data []byte) uint64 { + hash := uint64(index) + 14695981039346656037 + for _, c := range data { + hash ^= uint64(c) + hash *= 1099511628211 + } + return hash +} + +// baseHashes returns the four hash values of data that are used to create k +// hashes +func baseHashes(data []byte) []uint64 { + return []uint64{ + fnv64Hash(0, data), + fnv64Hash(1, data), + fnv64Hash(2, data), + fnv64Hash(3, data), + } +} + +// location returns the ith hashed location using the four base hash values +func (f *BloomFilter) location(h []uint64, i uint) (location uint) { + ii := uint64(i) + location = uint((h[ii%2] + ii*h[2+(((ii+(ii%2))%4)/2)]) % uint64(f.m)) + return +} + +// EstimateParameters estimates requirements for m and k. +// Based on https://bitbucket.org/ww/bloom/src/829aa19d01d9/bloom.go +// used with permission. +func EstimateParameters(n uint, p float64) (m uint, k uint) { + m = uint(math.Ceil(-1 * float64(n) * math.Log(p) / math.Pow(math.Log(2), 2))) + k = uint(math.Ceil(math.Log(2) * float64(m) / float64(n))) + return +} + +// NewWithEstimates creates a new Bloom filter for about n items with fp +// false positive rate +func NewWithEstimates(n uint, fp float64) *BloomFilter { + m, k := EstimateParameters(n, fp) + return New(m, k) +} + +// Cap returns the capacity, _m_, of a Bloom filter +func (f *BloomFilter) Cap() uint { + return f.m +} + +// K returns the number of hash functions used in the BloomFilter +func (f *BloomFilter) K() uint { + return f.k +} + +// Add data to the Bloom Filter. Returns the filter (allows chaining) +func (f *BloomFilter) Add(data []byte) *BloomFilter { + h := baseHashes(data) + for i := uint(0); i < f.k; i++ { + f.b.Set(f.location(h, i)) + } + return f +} + +// AddString to the Bloom Filter. Returns the filter (allows chaining) +func (f *BloomFilter) AddString(data string) *BloomFilter { + return f.Add([]byte(data)) +} + +// Test returns true if the data is in the BloomFilter, false otherwise. +// If true, the result might be a false positive. If false, the data +// is definitely not in the set. +func (f *BloomFilter) Test(data []byte) bool { + h := baseHashes(data) + for i := uint(0); i < f.k; i++ { + if !f.b.Test(f.location(h, i)) { + return false + } + } + return true +} + +// TestString returns true if the string is in the BloomFilter, false otherwise. +// If true, the result might be a false positive. If false, the data +// is definitely not in the set. +func (f *BloomFilter) TestString(data string) bool { + return f.Test([]byte(data)) +} + +// TestAndAdd is the equivalent to calling Test(data) then Add(data). +// Returns the result of Test. +func (f *BloomFilter) TestAndAdd(data []byte) bool { + present := true + h := baseHashes(data) + for i := uint(0); i < f.k; i++ { + l := f.location(h, i) + if !f.b.Test(l) { + present = false + } + f.b.Set(l) + } + return present +} + +// TestAndAddString is the equivalent to calling Test(string) then Add(string). +// Returns the result of Test. +func (f *BloomFilter) TestAndAddString(data string) bool { + return f.TestAndAdd([]byte(data)) +} + +// ClearAll clears all the data in a Bloom filter, removing all keys +func (f *BloomFilter) ClearAll() *BloomFilter { + f.b.ClearAll() + return f +} + +// EstimateFalsePositiveRate returns, for a BloomFilter with a estimate of m bits +// and k hash functions, what the false positive rate will be +// while storing n entries; runs 100,000 tests. This is an empirical +// test using integers as keys. As a side-effect, it clears the BloomFilter. +func (f *BloomFilter) EstimateFalsePositiveRate(n uint) (fpRate float64) { + rounds := uint32(100000) + f.ClearAll() + n1 := make([]byte, 4) + for i := uint32(0); i < uint32(n); i++ { + binary.BigEndian.PutUint32(n1, i) + f.Add(n1) + } + fp := 0 + // test for number of rounds + for i := uint32(0); i < rounds; i++ { + binary.BigEndian.PutUint32(n1, i+uint32(n)+1) + if f.Test(n1) { + //fmt.Printf("%v failed.\n", i+uint32(n)+1) + fp++ + } + } + fpRate = float64(fp) / (float64(rounds)) + f.ClearAll() + return +} + +// bloomFilterJSON is an unexported type for marshaling/unmarshaling BloomFilter struct. +type bloomFilterJSON struct { + M uint `json:"m"` + K uint `json:"k"` + B *bitset.BitSet `json:"b"` +} + +// MarshalJSON implements json.Marshaler interface. +func (f *BloomFilter) MarshalJSON() ([]byte, error) { + return json.Marshal(bloomFilterJSON{f.m, f.k, f.b}) +} + +// UnmarshalJSON implements json.Unmarshaler interface. +func (f *BloomFilter) UnmarshalJSON(data []byte) error { + var j bloomFilterJSON + err := json.Unmarshal(data, &j) + if err != nil { + return err + } + f.m = j.M + f.k = j.K + f.b = j.B + return nil +} + +// WriteTo writes a binary representation of the BloomFilter to an i/o stream. +// It returns the number of bytes written. +func (f *BloomFilter) WriteTo(stream io.Writer) (int64, error) { + err := binary.Write(stream, binary.BigEndian, uint64(f.m)) + if err != nil { + return 0, err + } + err = binary.Write(stream, binary.BigEndian, uint64(f.k)) + if err != nil { + return 0, err + } + numBytes, err := f.b.WriteTo(stream) + return numBytes + int64(2*binary.Size(uint64(0))), err +} + +// ReadFrom reads a binary representation of the BloomFilter (such as might +// have been written by WriteTo()) from an i/o stream. It returns the number +// of bytes read. +func (f *BloomFilter) ReadFrom(stream io.Reader) (int64, error) { + var m, k uint64 + err := binary.Read(stream, binary.BigEndian, &m) + if err != nil { + return 0, err + } + err = binary.Read(stream, binary.BigEndian, &k) + if err != nil { + return 0, err + } + b := &bitset.BitSet{} + numBytes, err := b.ReadFrom(stream) + if err != nil { + return 0, err + } + f.m = uint(m) + f.k = uint(k) + f.b = b + return numBytes + int64(2*binary.Size(uint64(0))), nil +} + +// GobEncode implements gob.GobEncoder interface. +func (f *BloomFilter) GobEncode() ([]byte, error) { + var buf bytes.Buffer + _, err := f.WriteTo(&buf) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// GobDecode implements gob.GobDecoder interface. +func (f *BloomFilter) GobDecode(data []byte) error { + buf := bytes.NewBuffer(data) + _, err := f.ReadFrom(buf) + + return err +} diff --git a/Godeps/_workspace/src/github.com/willf/bloom/bloom_test.go b/Godeps/_workspace/src/github.com/willf/bloom/bloom_test.go new file mode 100644 index 0000000..9d96263 --- /dev/null +++ b/Godeps/_workspace/src/github.com/willf/bloom/bloom_test.go @@ -0,0 +1,323 @@ +package bloom + +import ( + "bytes" + "encoding/binary" + "encoding/gob" + "encoding/json" + "fmt" + "runtime" + "sync" + "testing" +) + +func TestConcurrent(t *testing.T) { + gmp := runtime.GOMAXPROCS(2) + defer runtime.GOMAXPROCS(gmp) + + f := New(1000, 4) + n1 := []byte("Bess") + n2 := []byte("Jane") + f.Add(n1) + f.Add(n2) + + var wg sync.WaitGroup + const try = 1000 + var err1, err2 error + + wg.Add(1) + go func() { + for i := 0; i < try; i++ { + n1b := f.Test(n1) + if !n1b { + err1 = fmt.Errorf("%v should be in", n1) + break + } + } + wg.Done() + }() + + wg.Add(1) + go func() { + for i := 0; i < try; i++ { + n2b := f.Test(n2) + if !n2b { + err2 = fmt.Errorf("%v should be in", n2) + break + } + } + wg.Done() + }() + + wg.Wait() + + if err1 != nil { + t.Fatal(err1) + } + if err2 != nil { + t.Fatal(err2) + } +} + +func TestBasic(t *testing.T) { + f := New(1000, 4) + n1 := []byte("Bess") + n2 := []byte("Jane") + n3 := []byte("Emma") + f.Add(n1) + n3a := f.TestAndAdd(n3) + n1b := f.Test(n1) + n2b := f.Test(n2) + n3b := f.Test(n3) + if !n1b { + t.Errorf("%v should be in.", n1) + } + if n2b { + t.Errorf("%v should not be in.", n2) + } + if n3a { + t.Errorf("%v should not be in the first time we look.", n3) + } + if !n3b { + t.Errorf("%v should be in the second time we look.", n3) + } +} + +func TestBasicUint32(t *testing.T) { + f := New(1000, 4) + n1 := make([]byte, 4) + n2 := make([]byte, 4) + n3 := make([]byte, 4) + n4 := make([]byte, 4) + binary.BigEndian.PutUint32(n1, 100) + binary.BigEndian.PutUint32(n2, 101) + binary.BigEndian.PutUint32(n3, 102) + binary.BigEndian.PutUint32(n4, 103) + f.Add(n1) + n3a := f.TestAndAdd(n3) + n1b := f.Test(n1) + n2b := f.Test(n2) + n3b := f.Test(n3) + f.Test(n4) + if !n1b { + t.Errorf("%v should be in.", n1) + } + if n2b { + t.Errorf("%v should not be in.", n2) + } + if n3a { + t.Errorf("%v should not be in the first time we look.", n3) + } + if !n3b { + t.Errorf("%v should be in the second time we look.", n3) + } +} + +func TestString(t *testing.T) { + f := NewWithEstimates(1000, 0.001) + n1 := "Love" + n2 := "is" + n3 := "in" + n4 := "bloom" + f.AddString(n1) + n3a := f.TestAndAddString(n3) + n1b := f.TestString(n1) + n2b := f.TestString(n2) + n3b := f.TestString(n3) + f.TestString(n4) + if !n1b { + t.Errorf("%v should be in.", n1) + } + if n2b { + t.Errorf("%v should not be in.", n2) + } + if n3a { + t.Errorf("%v should not be in the first time we look.", n3) + } + if !n3b { + t.Errorf("%v should be in the second time we look.", n3) + } + +} + +func testEstimated(n uint, maxFp float64, t *testing.T) { + m, k := EstimateParameters(n, maxFp) + f := NewWithEstimates(n, maxFp) + fpRate := f.EstimateFalsePositiveRate(n) + if fpRate > 1.10*maxFp { + t.Errorf("False positive rate too high: n: %v; m: %v; k: %v; maxFp: %f; fpRate: %f, fpRate/maxFp: %f", n, m, k, maxFp, fpRate, fpRate/maxFp) + } +} + +func TestEstimated1000_0001(t *testing.T) { testEstimated(1000, 0.000100, t) } +func TestEstimated10000_0001(t *testing.T) { testEstimated(10000, 0.000100, t) } +func TestEstimated100000_0001(t *testing.T) { testEstimated(100000, 0.000100, t) } +func TestEstimated1000000_0001(t *testing.T) { testEstimated(1000000, 0.000100, t) } +func TestEstimated10000000_0001(t *testing.T) { testEstimated(10000000, 0.000100, t) } +func TestEstimated1000_001(t *testing.T) { testEstimated(1000, 0.001000, t) } +func TestEstimated10000_001(t *testing.T) { testEstimated(10000, 0.001000, t) } +func TestEstimated100000_001(t *testing.T) { testEstimated(100000, 0.001000, t) } +func TestEstimated1000000_001(t *testing.T) { testEstimated(1000000, 0.001000, t) } +func TestEstimated10000000_001(t *testing.T) { testEstimated(10000000, 0.001000, t) } +func TestEstimated1000_01(t *testing.T) { testEstimated(1000, 0.010000, t) } +func TestEstimated10000_01(t *testing.T) { testEstimated(10000, 0.010000, t) } +func TestEstimated100000_01(t *testing.T) { testEstimated(100000, 0.010000, t) } +func TestEstimated1000000_01(t *testing.T) { testEstimated(1000000, 0.010000, t) } +func TestEstimated10000000_01(t *testing.T) { testEstimated(10000000, 0.010000, t) } + +func TestMarshalUnmarshalJSON(t *testing.T) { + f := New(1000, 4) + data, err := json.Marshal(f) + if err != nil { + t.Fatal(err.Error()) + } + + var g BloomFilter + err = json.Unmarshal(data, &g) + if err != nil { + t.Fatal(err.Error()) + } + if g.m != f.m { + t.Error("invalid m value") + } + if g.k != f.k { + t.Error("invalid k value") + } + if g.b == nil { + t.Fatal("bitset is nil") + } + if !g.b.Equal(f.b) { + t.Error("bitsets are not equal") + } +} + +func TestWriteToReadFrom(t *testing.T) { + var b bytes.Buffer + f := New(1000, 4) + _, err := f.WriteTo(&b) + if err != nil { + t.Fatal(err) + } + + g := New(1000, 1) + _, err = g.ReadFrom(&b) + if err != nil { + t.Fatal(err) + } + if g.m != f.m { + t.Error("invalid m value") + } + if g.k != f.k { + t.Error("invalid k value") + } + if g.b == nil { + t.Fatal("bitset is nil") + } + if !g.b.Equal(f.b) { + t.Error("bitsets are not equal") + } + + g.Test([]byte("")) +} + +func TestReadWriteBinary(t *testing.T) { + f := New(1000, 4) + var buf bytes.Buffer + bytesWritten, err := f.WriteTo(&buf) + if err != nil { + t.Fatal(err.Error()) + } + if bytesWritten != int64(buf.Len()) { + t.Errorf("incorrect write length %d != %d", bytesWritten, buf.Len()) + } + + var g BloomFilter + bytesRead, err := g.ReadFrom(&buf) + if err != nil { + t.Fatal(err.Error()) + } + if bytesRead != bytesWritten { + t.Errorf("read unexpected number of bytes %d != %d", bytesRead, bytesWritten) + } + if g.m != f.m { + t.Error("invalid m value") + } + if g.k != f.k { + t.Error("invalid k value") + } + if g.b == nil { + t.Fatal("bitset is nil") + } + if !g.b.Equal(f.b) { + t.Error("bitsets are not equal") + } +} + +func TestEncodeDecodeGob(t *testing.T) { + f := New(1000, 4) + f.Add([]byte("one")) + f.Add([]byte("two")) + f.Add([]byte("three")) + var buf bytes.Buffer + err := gob.NewEncoder(&buf).Encode(f) + if err != nil { + t.Fatal(err.Error()) + } + + var g BloomFilter + err = gob.NewDecoder(&buf).Decode(&g) + if err != nil { + t.Fatal(err.Error()) + } + if g.m != f.m { + t.Error("invalid m value") + } + if g.k != f.k { + t.Error("invalid k value") + } + if g.b == nil { + t.Fatal("bitset is nil") + } + if !g.b.Equal(f.b) { + t.Error("bitsets are not equal") + } + if !g.Test([]byte("three")) { + t.Errorf("missing value 'three'") + } + if !g.Test([]byte("two")) { + t.Errorf("missing value 'two'") + } + if !g.Test([]byte("one")) { + t.Errorf("missing value 'one'") + } +} + +func BenchmarkEstimated(b *testing.B) { + for n := uint(100000); n <= 100000; n *= 10 { + for fp := 0.1; fp >= 0.0001; fp /= 10.0 { + f := NewWithEstimates(n, fp) + f.EstimateFalsePositiveRate(n) + } + } +} + +func BenchmarkSeparateTestAndAdd(b *testing.B) { + f := NewWithEstimates(uint(b.N), 0.0001) + key := make([]byte, 100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint32(key, uint32(i)) + f.Test(key) + f.Add(key) + } +} + +func BenchmarkCombinedTestAndAdd(b *testing.B) { + f := NewWithEstimates(uint(b.N), 0.0001) + key := make([]byte, 100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.BigEndian.PutUint32(key, uint32(i)) + f.TestAndAdd(key) + } +} From 499c7a820a72b6b240f1b9253581069fa7d7f1c0 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 19 May 2015 00:42:29 +0200 Subject: [PATCH 54/67] Updated CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be02835..b461103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Moved from traditional `go get`-based flow to dependency vendoring using [godep](https://github.com/tools/godep). - Disabled most of the log output to make it easier to analyze. + - Matching for 10k most used passwords replaced with a bloom filter + containing 17.5m leaked passwords from various hacks. ### Fixed - Cursor leakage all over the `db` package. From 14b740f8add5b904b09c331f360d1554e923d3b8 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 19 May 2015 00:43:53 +0200 Subject: [PATCH 55/67] Bump to 2.0.2 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b461103..9e25caf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased][unreleased] + +## [2.0.2] - 2015-05-19 ### Added - Added a check whether an address mapping is used in the username reservation. @@ -42,5 +44,6 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Initial release of Lavaboom API 2.0 -[unreleased]: https://github.com/lavab/api/compare/2.0.1...HEAD +[unreleased]: https://github.com/lavab/api/compare/2.0.2...HEAD +[2.0.2]: https://github.com/lavab/api/compare/2.0.2...2.0.1 [2.0.1]: https://github.com/lavab/api/compare/2.0.1...0.2.0 \ No newline at end of file From 4864797c3287d37ad1306ee796f577a28736ba91 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Wed, 20 May 2015 00:56:13 +0200 Subject: [PATCH 56/67] Added state restoration to the api client --- client/client.go | 51 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/client/client.go b/client/client.go index fcfe3c6..ed3d8f5 100644 --- a/client/client.go +++ b/client/client.go @@ -22,6 +22,8 @@ type Client struct { Incoming map[string]chan *Response Handlers []func(ev *Event) Timeout time.Duration + + subscription string } func New(address string, timeout time.Duration) (*Client, error) { @@ -49,25 +51,28 @@ func New(address string, timeout time.Duration) (*Client, error) { } func (c *Client) Loop() { - for { - log.Print("STARTING THE LOOP") - - x := []string{} - if err := c.SockJS.ReadMessage(&x); err != nil { - log.Print(err) - break - } + message := make(chan *Response) + + go func() { + for { + x := []string{} + if err := c.SockJS.ReadMessage(&x); err != nil { + log.Print(err) + continue + } - log.Print("GOT THE MESSAGE") + var resp *Response + if err := json.Unmarshal([]byte(x[0]), &resp); err != nil { + log.Print(err) + continue + } - var resp *Response - if err := json.Unmarshal([]byte(x[0]), &resp); err != nil { - log.Print(err) - continue + message <- resp } + }() - log.Printf("%+v", resp) - + select { + case resp := <-message: if resp.Type == "response" { c.RLock() d, ok := c.Incoming[resp.ID] @@ -90,6 +95,20 @@ func (c *Client) Loop() { }(resp) } } + case <-c.SockJS.Reconnected: + data, err := Encode(&Request{ + Type: "subscribe", + Token: c.subscription, + }) + if err != nil { + log.Print(err) + return + } + + if err := c.SockJS.WriteMessage(data); err != nil { + log.Print(err) + return + } } } @@ -165,6 +184,8 @@ func (c *Client) Subscribe(token string, callback func(ev *Event)) error { if err := c.SockJS.WriteMessage(data); err != nil { return err } + + c.subscription = token } return nil From 60a6c1f2f4e4e3b7b243f650b8d9de485c6e4030 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 21 May 2015 16:42:37 +0200 Subject: [PATCH 57/67] Webhook model --- db/setup.go | 4 ++++ models/webhook.go | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 models/webhook.go diff --git a/db/setup.go b/db/setup.go index 7c2a5e8..8383329 100644 --- a/db/setup.go +++ b/db/setup.go @@ -115,6 +115,10 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("tokens").IndexCreate("date_modified").Exec(ss) r.Db(d).Table("tokens").IndexCreate("type").Exec(ss) r.Db(d).Table("tokens").IndexCreate("expiry_date").Exec(ss) + + r.Db(d).TableCreate("webhooks").Exec(ss) + r.Db(d).Table("webhooks").IndexCreate("target").Exec(ss) + r.Db(d).Table("webhooks").IndexCreate("type").Exec(ss) } return ss.Close() diff --git a/models/webhook.go b/models/webhook.go new file mode 100644 index 0000000..7833797 --- /dev/null +++ b/models/webhook.go @@ -0,0 +1,9 @@ +package models + +type Webhook struct { + Resource + + Target string `json:"target" gorethink:"target"` + Type string `json:"type" gorethink:"type"` + Address string `json:"address" gorethink:"address"` +} From 54b46b5c73f0c974f34b007164514bd728ba1599 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 21 May 2015 19:05:27 +0200 Subject: [PATCH 58/67] Database initialization code for the webhooks --- db/setup.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/db/setup.go b/db/setup.go index 8383329..dd3f66f 100644 --- a/db/setup.go +++ b/db/setup.go @@ -119,6 +119,12 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).TableCreate("webhooks").Exec(ss) r.Db(d).Table("webhooks").IndexCreate("target").Exec(ss) r.Db(d).Table("webhooks").IndexCreate("type").Exec(ss) + r.Db(d).Table("webhooks").IndexCreateFunc("targetType", func(row r.Term) interface{} { + return []interface{}{ + row.Field("target"), + row.Field("owner"), + } + }).Exec(ss) } return ss.Close() From 226bd3896044ad343fe09f6181aff332458115be Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Thu, 21 May 2015 19:33:36 +0200 Subject: [PATCH 59/67] Typo in the targetType index definition --- db/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/setup.go b/db/setup.go index dd3f66f..ac2c1ce 100644 --- a/db/setup.go +++ b/db/setup.go @@ -122,7 +122,7 @@ func Setup(opts r.ConnectOpts) error { r.Db(d).Table("webhooks").IndexCreateFunc("targetType", func(row r.Term) interface{} { return []interface{}{ row.Field("target"), - row.Field("owner"), + row.Field("type"), } }).Exec(ss) } From 0855ecf974b62d29c015294120e733b57c509cd8 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Mon, 1 Jun 2015 21:48:19 +0200 Subject: [PATCH 60/67] Preventing mentally challenged users from registering one-character usernames. --- routes/accounts.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/accounts.go b/routes/accounts.go index 1caaa98..c71e732 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -89,7 +89,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { input.Username = utils.NormalizeUsername(input.Username) // Validate the username - if len(input.Username) < 3 || len(input.Username) > 32 { + if len(input.Username) < 3 || utils.RemoveDots(input.Username) < 3 || len(input.Username) > 32 { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid username - it has to be at least 3 and at max 32 characters long", From 6bdbdd84e93bad2bd1537780a8b67599608c6506 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Tue, 2 Jun 2015 21:47:17 +0200 Subject: [PATCH 61/67] Name length fix + headers proxy --- routes/accounts.go | 2 +- setup/setup.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/routes/accounts.go b/routes/accounts.go index c71e732..c1ff734 100644 --- a/routes/accounts.go +++ b/routes/accounts.go @@ -89,7 +89,7 @@ func AccountsCreate(w http.ResponseWriter, r *http.Request) { input.Username = utils.NormalizeUsername(input.Username) // Validate the username - if len(input.Username) < 3 || utils.RemoveDots(input.Username) < 3 || len(input.Username) > 32 { + if len(input.Username) < 3 || len(utils.RemoveDots(input.Username)) < 3 || len(input.Username) > 32 { utils.JSONResponse(w, 400, &AccountsCreateResponse{ Success: false, Message: "Invalid username - it has to be at least 3 and at max 32 characters long", diff --git a/setup/setup.go b/setup/setup.go index cba33c5..f1fcb28 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -27,6 +27,7 @@ import ( "github.com/lavab/api/env" "github.com/lavab/api/factor" "github.com/lavab/api/routes" + "github.com/lavab/api/utils" ) // sessions contains all "subscribing" WebSockets sessions @@ -550,6 +551,11 @@ func PrepareMux(flags *env.Flags) *web.Mux { mux.Get("/keys/:id", routes.KeysGet) auth.Post("/keys/:id/vote", routes.KeysVote) + // Headers proxy + mux.Get("/headers", func(w http.ResponseWriter, r *http.Request) { + utils.JSONResponse(w, 200, r.Header) + }) + mux.Handle("/ws/*", sockjs.NewHandler("/ws", sockjs.DefaultOptions, func(session sockjs.Session) { var subscribed string From 6f6fed8a820b963e9f116a3040d0ec3e01ebc9e1 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 00:23:09 +0200 Subject: [PATCH 62/67] Raven recovery --- env/config.go | 2 ++ env/env.go | 3 ++ main.go | 4 +++ setup/recoverer.go | 41 ++++++++++++++++++++++++++++ setup/setup.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+) create mode 100644 setup/recoverer.go diff --git a/env/config.go b/env/config.go index 48c743a..39f838a 100644 --- a/env/config.go +++ b/env/config.go @@ -34,4 +34,6 @@ type Flags struct { BloomFilter string BloomCount uint + + RavenDSN string } diff --git a/env/env.go b/env/env.go index 9b85ce3..ab67e39 100644 --- a/env/env.go +++ b/env/env.go @@ -4,6 +4,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/bitly/go-nsq" "github.com/dancannon/gorethink" + "github.com/getsentry/raven-go" "github.com/willf/bloom" "github.com/lavab/api/cache" @@ -46,4 +47,6 @@ var ( Producer *nsq.Producer // PasswordBF is the bloom filter used for leaked password matching PasswordBF *bloom.BloomFilter + // Raven is the raven client used for reporting panics to Sentry + Raven *raven.Client ) diff --git a/main.go b/main.go index 5038374..38e94ed 100644 --- a/main.go +++ b/main.go @@ -89,6 +89,8 @@ var ( // Password bloom filter path bloomFilter = flag.String("bloom_filter", "bloom.db", "Bloom filter containing passwords") bloomCount = flag.Uint("bloom_count", 14522336, "Estimated count of passwords in the bloom filter") + // raven dsn + ravenDSN = flag.String("raven_dsn", "", "DSN of the Raven connection") ) func main() { @@ -129,6 +131,8 @@ func main() { BloomFilter: *bloomFilter, BloomCount: *bloomCount, + + RavenDSN: *ravenDSN, } // Generate a mux diff --git a/setup/recoverer.go b/setup/recoverer.go new file mode 100644 index 0000000..8955bfa --- /dev/null +++ b/setup/recoverer.go @@ -0,0 +1,41 @@ +package setup + +import ( + "errors" + "fmt" + "net/http" + "runtime/debug" + + "github.com/getsentry/raven-go" + "github.com/zenazn/goji/web" + "github.com/zenazn/goji/web/middleware" + + "github.com/lavab/api/env" +) + +func recoverer(c *web.C, h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := middleware.GetReqID(*c) + + // Raven recoverer + defer func() { + var packet *raven.Packet + switch rval := recover().(type) { + case nil: + return + case error: + packet = raven.NewPacket(rval.Error(), raven.NewHttp(r), raven.NewException(rval, raven.NewStacktrace(2, 3, nil))) + default: + str := fmt.Sprintf("%+v", rval) + packet = raven.NewPacket(str, raven.NewHttp(r), raven.NewException(errors.New(str), raven.NewStacktrace(2, 3, nil))) + } + + debug.PrintStack() + http.Error(w, http.StatusText(500), 500) + + env.Raven.Capture(packet, map[string]string{ + "request_id": id, + }) + }() + }) +} diff --git a/setup/setup.go b/setup/setup.go index f1fcb28..edbfd60 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -2,6 +2,8 @@ package setup import ( "encoding/json" + "errors" + "fmt" "io" "net/http" "net/http/httptest" @@ -16,6 +18,7 @@ import ( "github.com/dancannon/gorethink" "github.com/johntdyer/slackrus" //"github.com/pzduniak/glogrus" + "github.com/getsentry/raven-go" "github.com/segmentio/go-loggly" "github.com/willf/bloom" "github.com/zenazn/goji/web" @@ -90,6 +93,23 @@ func PrepareMux(flags *env.Flags) *web.Mux { }) } + // Connect to raven + var rc *raven.Client + if flags.RavenDSN != "" { + h, err := os.Hostname() + if err != nil { + log.Fatal(err) + } + + rc, err = raven.NewClient(flags.RavenDSN, map[string]string{ + "hostname": h, + }) + if err != nil { + log.Fatal(err) + } + } + env.Raven = rc + // Pass it to the environment package env.Log = log @@ -274,6 +294,30 @@ func PrepareMux(flags *env.Flags) *web.Mux { //defer deliveryConsumer.Stop() deliveryConsumer.AddConcurrentHandlers(nsq.HandlerFunc(func(m *nsq.Message) error { + // Raven recoverer + defer func() { + rec := recover() + if rec == nil { + return + } + + msg := &raven.Message{ + Message: string(m.Body), + Params: []interface{}{"delivery"}, + } + + var packet *raven.Packet + switch rval := recover().(type) { + case error: + packet = raven.NewPacket(rval.Error(), msg, raven.NewException(rval, raven.NewStacktrace(2, 3, nil))) + default: + str := fmt.Sprintf("%+v", rval) + packet = raven.NewPacket(str, msg, raven.NewException(errors.New(str), raven.NewStacktrace(2, 3, nil))) + } + + rc.Capture(packet, nil) + }() + var msg *struct { ID string `json:"id"` Owner string `json:"owner"` @@ -351,6 +395,30 @@ func PrepareMux(flags *env.Flags) *web.Mux { //defer receiptConsumer.Stop() receiptConsumer.AddConcurrentHandlers(nsq.HandlerFunc(func(m *nsq.Message) error { + // Raven recoverer + defer func() { + rec := recover() + if rec == nil { + return + } + + msg := &raven.Message{ + Message: string(m.Body), + Params: []interface{}{"receipt"}, + } + + var packet *raven.Packet + switch rval := recover().(type) { + case error: + packet = raven.NewPacket(rval.Error(), msg, raven.NewException(rval, raven.NewStacktrace(2, 3, nil))) + default: + str := fmt.Sprintf("%+v", rval) + packet = raven.NewPacket(str, msg, raven.NewException(errors.New(str), raven.NewStacktrace(2, 3, nil))) + } + + rc.Capture(packet, nil) + }() + var msg *struct { ID string `json:"id"` Owner string `json:"owner"` From e90a874c815e2c8b5eeb6f36de9ffb4b6366b1d8 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 00:24:22 +0200 Subject: [PATCH 63/67] Updated Godeps --- Godeps/Godeps.json | 13 +- .../github.com/getsentry/raven-go/.gitignore | 2 + .../github.com/getsentry/raven-go/.travis.yml | 19 + .../src/github.com/getsentry/raven-go/LICENSE | 27 + .../github.com/getsentry/raven-go/README.md | 12 + .../github.com/getsentry/raven-go/client.go | 502 ++++++++++++++++++ .../getsentry/raven-go/client_test.go | 150 ++++++ .../getsentry/raven-go/example/example.go | 42 ++ .../getsentry/raven-go/examples_test.go | 28 + .../getsentry/raven-go/exception.go | 41 ++ .../getsentry/raven-go/exception_test.go | 29 + .../src/github.com/getsentry/raven-go/http.go | 60 +++ .../getsentry/raven-go/http_test.go | 149 ++++++ .../getsentry/raven-go/interfaces.go | 47 ++ .../getsentry/raven-go/stacktrace.go | 195 +++++++ .../getsentry/raven-go/stacktrace_test.go | 133 +++++ .../github.com/getsentry/raven-go/writer.go | 20 + .../src/golang.org/x/crypto/openpgp/keys.go | 12 +- .../golang.org/x/crypto/openpgp/keys_test.go | 164 +++++- .../x/crypto/openpgp/packet/config.go | 18 + .../x/crypto/openpgp/packet/encrypted_key.go | 49 +- .../openpgp/packet/encrypted_key_test.go | 21 + .../x/crypto/openpgp/packet/packet.go | 23 +- .../x/crypto/openpgp/packet/public_key.go | 30 +- .../x/crypto/openpgp/packet/reader.go | 18 +- .../x/crypto/openpgp/packet/signature.go | 42 +- .../openpgp/packet/symmetric_key_encrypted.go | 2 +- .../src/golang.org/x/crypto/openpgp/read.go | 8 +- .../golang.org/x/crypto/openpgp/read_test.go | 47 ++ .../golang.org/x/crypto/openpgp/s2k/s2k.go | 61 ++- .../x/crypto/openpgp/s2k/s2k_test.go | 5 +- 31 files changed, 1918 insertions(+), 51 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/.gitignore create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/LICENSE create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/README.md create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/client.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/client_test.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/example/example.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/examples_test.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/exception.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/exception_test.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/http.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/http_test.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/interfaces.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace_test.go create mode 100644 Godeps/_workspace/src/github.com/getsentry/raven-go/writer.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a69c35b..3c350f5 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -92,6 +92,10 @@ "ImportPath": "github.com/garyburd/redigo/redis", "Rev": "535138d7bcd717d6531c701ef5933d98b1866257" }, + { + "ImportPath": "github.com/getsentry/raven-go", + "Rev": "c8f8fb7c415203f52ca882e2661d21bc6dcb54d7" + }, { "ImportPath": "github.com/gokyle/hotp", "Rev": "cab5c36e945092b6fb703b6c785bb06fd0537a0d" @@ -158,18 +162,15 @@ }, { "ImportPath": "golang.org/x/crypto/cast5", - "Comment": "null-233", - "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + "Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b" }, { "ImportPath": "golang.org/x/crypto/openpgp", - "Comment": "null-233", - "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + "Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b" }, { "ImportPath": "golang.org/x/crypto/ripemd160", - "Comment": "null-233", - "Rev": "8fec09c61d5d66f460d227fd1df3473d7e015bc6" + "Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b" }, { "ImportPath": "gopkg.in/igm/sockjs-go.v2/sockjs", diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/.gitignore b/Godeps/_workspace/src/github.com/getsentry/raven-go/.gitignore new file mode 100644 index 0000000..815a975 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/.gitignore @@ -0,0 +1,2 @@ +*.test +example/example diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/.travis.yml b/Godeps/_workspace/src/github.com/getsentry/raven-go/.travis.yml new file mode 100644 index 0000000..5f46e3a --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/.travis.yml @@ -0,0 +1,19 @@ +sudo: false +language: go +go: + - "1.2" + - "1.3" + - "1.4" + - tip + +before_install: + - go install -race std + - go get golang.org/x/tools/cmd/cover + +script: + - go test -race ./... + - go test -cover ./... + +matrix: + allow_failures: + - go: tip diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/LICENSE b/Godeps/_workspace/src/github.com/getsentry/raven-go/LICENSE new file mode 100644 index 0000000..a74d40e --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 Apollic Software, LLC. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Apollic Software, LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/README.md b/Godeps/_workspace/src/github.com/getsentry/raven-go/README.md new file mode 100644 index 0000000..a5373ce --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/README.md @@ -0,0 +1,12 @@ +# raven [![Build Status](https://travis-ci.org/getsentry/raven-go.png?branch=master)](https://travis-ci.org/getsentry/raven-go) + +raven is a Go client for the [Sentry](https://github.com/getsentry/sentry) +event/error logging system. + +[**Documentation**](http://godoc.org/github.com/getsentry/raven-go). + +## Installation + +```text +go get github.com/getsentry/raven-go +``` diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/client.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/client.go new file mode 100644 index 0000000..515c9bf --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/client.go @@ -0,0 +1,502 @@ +// Package raven implements a client for the Sentry error logging service. +package raven + +import ( + "bytes" + "compress/zlib" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "runtime" + "strings" + "sync" + "time" +) + +const ( + userAgent = "raven-go/1.0" + timestampFormat = `"2006-01-02T15:04:05"` +) + +var ( + ErrPacketDropped = errors.New("raven: packet dropped") + ErrUnableToUnmarshalJSON = errors.New("raven: unable to unmarshal JSON") + ErrMissingUser = errors.New("raven: dns missing public key and/or password") + ErrMissingPrivateKey = errors.New("raven: dsn missing private key") + ErrMissingProjectID = errors.New("raven: dsn missing project id") +) + +type Severity string + +// http://docs.python.org/2/howto/logging.html#logging-levels +const ( + DEBUG = Severity("debug") + INFO = Severity("info") + WARNING = Severity("warning") + ERROR = Severity("error") + FATAL = Severity("fatal") +) + +type Timestamp time.Time + +func (t Timestamp) MarshalJSON() ([]byte, error) { + return []byte(time.Time(t).UTC().Format(timestampFormat)), nil +} + +func (timestamp *Timestamp) UnmarshalJSON(data []byte) error { + t, err := time.Parse(timestampFormat, string(data)) + if err != nil { + return err + } + + *timestamp = Timestamp(t) + return nil +} + +// An Interface is a Sentry interface that will be serialized as JSON. +// It must implement json.Marshaler or use json struct tags. +type Interface interface { + // The Sentry class name. Example: sentry.interfaces.Stacktrace + Class() string +} + +type Culpriter interface { + Culprit() string +} + +type Transport interface { + Send(url, authHeader string, packet *Packet) error +} + +type outgoingPacket struct { + packet *Packet + ch chan error +} + +type Tag struct { + Key string + Value string +} + +type Tags []Tag + +func (tag *Tag) MarshalJSON() ([]byte, error) { + return json.Marshal([2]string{tag.Key, tag.Value}) +} + +func (t *Tag) UnmarshalJSON(data []byte) error { + var tag [2]string + if err := json.Unmarshal(data, &tag); err != nil { + return err + } + *t = Tag{tag[0], tag[1]} + return nil +} + +func (t *Tags) UnmarshalJSON(data []byte) error { + var tags []Tag + + switch data[0] { + case '[': + // Unmarshal into []Tag + if err := json.Unmarshal(data, &tags); err != nil { + return err + } + case '{': + // Unmarshal into map[string]string + tagMap := make(map[string]string) + if err := json.Unmarshal(data, &tagMap); err != nil { + return err + } + + // Convert to []Tag + for k, v := range tagMap { + tags = append(tags, Tag{k, v}) + } + default: + return ErrUnableToUnmarshalJSON + } + + *t = tags + return nil +} + +// http://sentry.readthedocs.org/en/latest/developer/client/index.html#building-the-json-packet +type Packet struct { + // Required + Message string `json:"message"` + + // Required, set automatically by Client.Send/Report via Packet.Init if blank + EventID string `json:"event_id"` + Project string `json:"project"` + Timestamp Timestamp `json:"timestamp"` + Level Severity `json:"level"` + Logger string `json:"logger"` + + // Optional + Platform string `json:"platform,omitempty"` + Culprit string `json:"culprit,omitempty"` + ServerName string `json:"server_name,omitempty"` + Release string `json:"release,omitempty"` + Tags Tags `json:"tags,omitempty"` + Modules []map[string]string `json:"modules,omitempty"` + Extra map[string]interface{} `json:"extra,omitempty"` + + Interfaces []Interface `json:"-"` +} + +// NewPacket constructs a packet with the specified message and interfaces. +func NewPacket(message string, interfaces ...Interface) *Packet { + extra := map[string]interface{}{ + "runtime.Version": runtime.Version(), + "runtime.NumCPU": runtime.NumCPU(), + "runtime.GOMAXPROCS": runtime.GOMAXPROCS(0), // 0 just returns the current value + "runtime.NumGoroutine": runtime.NumGoroutine(), + } + return &Packet{ + Message: message, + Interfaces: interfaces, + Extra: extra, + } +} + +// Init initializes required fields in a packet. It is typically called by +// Client.Send/Report automatically. +func (packet *Packet) Init(project string) error { + if packet.Project == "" { + packet.Project = project + } + if packet.EventID == "" { + var err error + packet.EventID, err = uuid() + if err != nil { + return err + } + } + if time.Time(packet.Timestamp).IsZero() { + packet.Timestamp = Timestamp(time.Now()) + } + if packet.Level == "" { + packet.Level = ERROR + } + if packet.Logger == "" { + packet.Logger = "root" + } + if packet.ServerName == "" { + packet.ServerName = hostname + } + if packet.Platform == "" { + packet.Platform = "go" + } + + if packet.Culprit == "" { + for _, inter := range packet.Interfaces { + if c, ok := inter.(Culpriter); ok { + packet.Culprit = c.Culprit() + if packet.Culprit != "" { + break + } + } + } + } + + return nil +} + +func (packet *Packet) AddTags(tags map[string]string) { + for k, v := range tags { + packet.Tags = append(packet.Tags, Tag{k, v}) + } +} + +func uuid() (string, error) { + id := make([]byte, 16) + _, err := io.ReadFull(rand.Reader, id) + if err != nil { + return "", err + } + id[6] &= 0x0F // clear version + id[6] |= 0x40 // set version to 4 (random uuid) + id[8] &= 0x3F // clear variant + id[8] |= 0x80 // set to IETF variant + return hex.EncodeToString(id), nil +} + +func (packet *Packet) JSON() []byte { + packetJSON, _ := json.Marshal(packet) + + interfaces := make(map[string]Interface, len(packet.Interfaces)) + for _, inter := range packet.Interfaces { + interfaces[inter.Class()] = inter + } + + if len(interfaces) > 0 { + interfaceJSON, _ := json.Marshal(interfaces) + packetJSON[len(packetJSON)-1] = ',' + packetJSON = append(packetJSON, interfaceJSON[1:]...) + } + + return packetJSON +} + +// The maximum number of packets that will be buffered waiting to be delivered. +// Packets will be dropped if the buffer is full. Used by NewClient. +var MaxQueueBuffer = 100 + +// NewClient constructs a Sentry client and spawns a background goroutine to +// handle packets sent by Client.Report. +func NewClient(dsn string, tags map[string]string) (*Client, error) { + client := &Client{Transport: &HTTPTransport{}, Tags: tags, queue: make(chan *outgoingPacket, MaxQueueBuffer)} + go client.worker() + return client, client.SetDSN(dsn) +} + +// Client encapsulates a connection to a Sentry server. It must be initialized +// by calling NewClient. Modification of fields concurrently with Send or after +// calling Report for the first time is not thread-safe. +type Client struct { + Tags map[string]string + + Transport Transport + + // DropHandler is called when a packet is dropped because the buffer is full. + DropHandler func(*Packet) + + mu sync.RWMutex + url string + projectID string + authHeader string + release string + queue chan *outgoingPacket +} + +// SetDSN updates a client with a new DSN. It safe to call after and +// concurrently with calls to Report and Send. +func (client *Client) SetDSN(dsn string) error { + if dsn == "" { + return nil + } + + client.mu.Lock() + defer client.mu.Unlock() + + uri, err := url.Parse(dsn) + if err != nil { + return err + } + + if uri.User == nil { + return ErrMissingUser + } + publicKey := uri.User.Username() + secretKey, ok := uri.User.Password() + if !ok { + return ErrMissingPrivateKey + } + uri.User = nil + + if idx := strings.LastIndex(uri.Path, "/"); idx != -1 { + client.projectID = uri.Path[idx+1:] + uri.Path = uri.Path[:idx+1] + "api/" + client.projectID + "/store/" + } + if client.projectID == "" { + return ErrMissingProjectID + } + + client.url = uri.String() + + client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey) + + return nil +} + +func (client *Client) SetRelease(release string) { + client.mu.Lock() + defer client.mu.Unlock() + client.release = release +} + +func (client *Client) worker() { + for outgoingPacket := range client.queue { + client.mu.RLock() + url, authHeader := client.url, client.authHeader + client.mu.RUnlock() + + outgoingPacket.ch <- client.Transport.Send(url, authHeader, outgoingPacket.packet) + } +} + +// Capture asynchronously delivers a packet to the Sentry server. It is a no-op +// when client is nil. A channel is provided if it is important to check for a +// send's success. +func (client *Client) Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) { + if client == nil { + return + } + + ch = make(chan error, 1) + + // Merge capture tags and client tags + packet.AddTags(captureTags) + packet.AddTags(client.Tags) + + // Initialize any required packet fields + client.mu.RLock() + projectID := client.projectID + release := client.release + client.mu.RUnlock() + + err := packet.Init(projectID) + if err != nil { + ch <- err + return + } + packet.Release = release + + outgoingPacket := &outgoingPacket{packet, ch} + + select { + case client.queue <- outgoingPacket: + default: + // Send would block, drop the packet + if client.DropHandler != nil { + client.DropHandler(packet) + } + ch <- ErrPacketDropped + } + + return packet.EventID, ch +} + +// CaptureMessage formats and delivers a string message to the Sentry server. +func (client *Client) CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string { + if client == nil { + return "" + } + + packet := NewPacket(message, append(interfaces, &Message{message, nil})...) + eventID, _ := client.Capture(packet, tags) + + return eventID +} + +// CaptureErrors formats and delivers an errorto the Sentry server. +// Adds a stacktrace to the packet, excluding the call to this method. +func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string { + if client == nil { + return "" + } + + packet := NewPacket(err.Error(), append(interfaces, NewException(err, NewStacktrace(1, 3, nil)))...) + eventID, _ := client.Capture(packet, tags) + + return eventID +} + +// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs. +func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces ...Interface) { + if client == nil { + return + } + + defer func() { + var packet *Packet + switch rval := recover().(type) { + case nil: + return + case error: + packet = NewPacket(rval.Error(), append(interfaces, NewException(rval, NewStacktrace(2, 3, nil)))...) + default: + rvalStr := fmt.Sprint(rval) + packet = NewPacket(rvalStr, append(interfaces, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)))...) + } + + client.Capture(packet, tags) + }() + + f() +} + +func (client *Client) Close() { + close(client.queue) +} + +func (client *Client) URL() string { + client.mu.RLock() + defer client.mu.RUnlock() + + return client.url +} + +func (client *Client) ProjectID() string { + client.mu.RLock() + defer client.mu.RUnlock() + + return client.projectID +} + +func (client *Client) Release() string { + client.mu.RLock() + defer client.mu.RUnlock() + + return client.release +} + +// HTTPTransport is the default transport, delivering packets to Sentry via the +// HTTP API. +type HTTPTransport struct { + Http http.Client +} + +func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error { + if url == "" { + return nil + } + + body, contentType := serializedPacket(packet) + req, _ := http.NewRequest("POST", url, body) + req.Header.Set("X-Sentry-Auth", authHeader) + req.Header.Set("User-Agent", userAgent) + req.Header.Set("Content-Type", contentType) + res, err := t.Http.Do(req) + if err != nil { + return err + } + io.Copy(ioutil.Discard, res.Body) + res.Body.Close() + if res.StatusCode != 200 { + return fmt.Errorf("raven: got http status %d", res.StatusCode) + } + return nil +} + +func serializedPacket(packet *Packet) (r io.Reader, contentType string) { + packetJSON := packet.JSON() + + // Only deflate/base64 the packet if it is bigger than 1KB, as there is + // overhead. + if len(packetJSON) > 1000 { + buf := &bytes.Buffer{} + b64 := base64.NewEncoder(base64.StdEncoding, buf) + deflate, _ := zlib.NewWriterLevel(b64, zlib.BestCompression) + deflate.Write(packetJSON) + deflate.Close() + b64.Close() + return buf, "application/octet-stream" + } + return bytes.NewReader(packetJSON), "application/json" +} + +var hostname string + +func init() { + hostname, _ = os.Hostname() +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/client_test.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/client_test.go new file mode 100644 index 0000000..d74f05c --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/client_test.go @@ -0,0 +1,150 @@ +package raven + +import ( + "encoding/json" + "reflect" + "testing" + "time" +) + +type testInterface struct{} + +func (t *testInterface) Class() string { return "sentry.interfaces.Test" } +func (t *testInterface) Culprit() string { return "codez" } + +func TestPacketJSON(t *testing.T) { + packet := &Packet{ + Project: "1", + EventID: "2", + Platform: "linux", + Culprit: "caused_by", + ServerName: "host1", + Release: "721e41770371db95eee98ca2707686226b993eda", + Message: "test", + Timestamp: Timestamp(time.Date(2000, 01, 01, 0, 0, 0, 0, time.UTC)), + Level: ERROR, + Logger: "com.getsentry.raven-go.logger-test-packet-json", + Tags: []Tag{Tag{"foo", "bar"}}, + Interfaces: []Interface{&Message{Message: "foo"}}, + } + + packet.AddTags(map[string]string{"foo": "foo"}) + packet.AddTags(map[string]string{"baz": "buzz"}) + + expected := `{"message":"test","event_id":"2","project":"1","timestamp":"2000-01-01T00:00:00","level":"error","logger":"com.getsentry.raven-go.logger-test-packet-json","platform":"linux","culprit":"caused_by","server_name":"host1","release":"721e41770371db95eee98ca2707686226b993eda","tags":[["foo","bar"],["foo","foo"],["baz","buzz"]],"sentry.interfaces.Message":{"message":"foo"}}` + actual := string(packet.JSON()) + + if actual != expected { + t.Errorf("incorrect json; got %s, want %s", actual, expected) + } +} + +func TestPacketInit(t *testing.T) { + packet := &Packet{Message: "a", Interfaces: []Interface{&testInterface{}}} + packet.Init("foo") + + if packet.Project != "foo" { + t.Error("incorrect Project:", packet.Project) + } + if packet.Culprit != "codez" { + t.Error("incorrect Culprit:", packet.Culprit) + } + if packet.ServerName == "" { + t.Errorf("ServerName should not be empty") + } + if packet.Level != ERROR { + t.Errorf("incorrect Level: got %d, want %d", packet.Level, ERROR) + } + if packet.Logger != "root" { + t.Errorf("incorrect Logger: got %s, want %s", packet.Logger, "root") + } + if time.Time(packet.Timestamp).IsZero() { + t.Error("Timestamp is zero") + } + if len(packet.EventID) != 32 { + t.Error("incorrect EventID:", packet.EventID) + } +} + +func TestSetDSN(t *testing.T) { + client := &Client{} + client.SetDSN("https://u:p@example.com/sentry/1") + + if client.url != "https://example.com/sentry/api/1/store/" { + t.Error("incorrect url:", client.url) + } + if client.projectID != "1" { + t.Error("incorrect projectID:", client.projectID) + } + if client.authHeader != "Sentry sentry_version=4, sentry_key=u, sentry_secret=p" { + t.Error("incorrect authHeader:", client.authHeader) + } +} + +func TestUnmarshalTag(t *testing.T) { + actual := new(Tag) + if err := json.Unmarshal([]byte(`["foo","bar"]`), actual); err != nil { + t.Fatal("unable to decode JSON:", err) + } + + expected := &Tag{Key: "foo", Value: "bar"} + if !reflect.DeepEqual(actual, expected) { + t.Errorf("incorrect Tag: wanted '%+v' and got '%+v'", expected, actual) + } +} + +func TestUnmarshalTags(t *testing.T) { + tests := []struct { + Input string + Expected Tags + }{ + { + `{"foo":"bar"}`, + Tags{Tag{Key: "foo", Value: "bar"}}, + }, + { + `[["foo","bar"],["bar","baz"]]`, + Tags{Tag{Key: "foo", Value: "bar"}, Tag{Key: "bar", Value: "baz"}}, + }, + } + + for _, test := range tests { + var actual Tags + if err := json.Unmarshal([]byte(test.Input), &actual); err != nil { + t.Fatal("unable to decode JSON:", err) + } + + if !reflect.DeepEqual(actual, test.Expected) { + t.Errorf("incorrect Tags: wanted '%+v' and got '%+v'", test.Expected, actual) + } + } +} + +func TestMarshalTimestamp(t *testing.T) { + timestamp := Timestamp(time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)) + expected := `"2000-01-02T03:04:05"` + + actual, err := json.Marshal(timestamp) + if err != nil { + t.Error(err) + } + + if string(actual) != expected { + t.Errorf("incorrect string; got %s, want %s", actual, expected) + } +} + +func TestUnmarshalTimestamp(t *testing.T) { + timestamp := `"2000-01-02T03:04:05"` + expected := Timestamp(time.Date(2000, 01, 02, 03, 04, 05, 0, time.UTC)) + + var actual Timestamp + err := json.Unmarshal([]byte(timestamp), &actual) + if err != nil { + t.Error(err) + } + + if actual != expected { + t.Errorf("incorrect string; got %s, want %s", actual, expected) + } +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/example/example.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/example/example.go new file mode 100644 index 0000000..8801e21 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/example/example.go @@ -0,0 +1,42 @@ +package main + +import ( + "errors" + "fmt" + "github.com/getsentry/raven-go" + "log" + "net/http" + "os" +) + +func trace() *raven.Stacktrace { + return raven.NewStacktrace(0, 2, nil) +} + +func main() { + client, err := raven.NewClient(os.Args[1], map[string]string{"foo": "bar"}) + if err != nil { + log.Fatal(err) + } + httpReq, _ := http.NewRequest("GET", "http://example.com/foo?bar=true", nil) + httpReq.RemoteAddr = "127.0.0.1:80" + httpReq.Header = http.Header{"Content-Type": {"text/html"}, "Content-Length": {"42"}} + packet := &raven.Packet{Message: "Test report", Interfaces: []raven.Interface{raven.NewException(errors.New("example"), trace()), raven.NewHttp(httpReq)}} + _, ch := client.Capture(packet, nil) + if err = <-ch; err != nil { + log.Fatal(err) + } + log.Print("sent packet successfully") +} + +// CheckError sends error report to sentry and records event id and error name to the logs +func CheckError(err error, r *http.Request) { + client, err := raven.NewClient(os.Args[1], map[string]string{"foo": "bar"}) + if err != nil { + log.Fatal(err) + } + packet := raven.NewPacket(err.Error(), raven.NewException(err, trace()), raven.NewHttp(r)) + eventID, _ := client.Capture(packet, nil) + message := fmt.Sprintf("Error event with id \"%s\" - %s", eventID, err.Error()) + log.Println(message) +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/examples_test.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/examples_test.go new file mode 100644 index 0000000..c1ab448 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/examples_test.go @@ -0,0 +1,28 @@ +package raven + +import ( + "fmt" + "net/http" + "log" +) + +func Example() { + // ... i.e. raisedErr is incoming error + var raisedErr error + // sentry DSN generated by Sentry server + var sentryDSN string + // r is a request performed when error occured + var r *http.Request + client, err := NewClient(sentryDSN, nil) + if err != nil { + log.Fatal(err) + } + trace := NewStacktrace(0, 2, nil) + packet := NewPacket(raisedErr.Error(), NewException(raisedErr, trace), NewHttp(r)) + eventID, ch := client.Capture(packet, nil) + if err = <-ch; err != nil { + log.Fatal(err) + } + message := fmt.Sprintf("Captured error with id %s: %q", eventID, raisedErr) + log.Println(message) +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/exception.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/exception.go new file mode 100644 index 0000000..cd297cc --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/exception.go @@ -0,0 +1,41 @@ +package raven + +import ( + "reflect" + "regexp" +) + +var errorMsgPattern = regexp.MustCompile(`\A(\w+): (.+)\z`) + +func NewException(err error, stacktrace *Stacktrace) *Exception { + msg := err.Error() + ex := &Exception{ + Stacktrace: stacktrace, + Value: msg, + Type: reflect.TypeOf(err).String(), + } + if m := errorMsgPattern.FindStringSubmatch(msg); m != nil { + ex.Module, ex.Value = m[1], m[2] + } + return ex +} + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Exception +type Exception struct { + // Required + Value string `json:"value"` + + // Optional + Type string `json:"type,omitempty"` + Module string `json:"module,omitempty"` + Stacktrace *Stacktrace `json:"stacktrace,omitempty"` +} + +func (e *Exception) Class() string { return "sentry.interfaces.Exception" } + +func (e *Exception) Culprit() string { + if e.Stacktrace == nil { + return "" + } + return e.Stacktrace.Culprit() +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/exception_test.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/exception_test.go new file mode 100644 index 0000000..f7d3ce4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/exception_test.go @@ -0,0 +1,29 @@ +package raven + +import ( + "errors" + "testing" +) + +var newExceptionTests = []struct { + err error + Exception +}{ + {errors.New("foobar"), Exception{Value: "foobar", Type: "*errors.errorString"}}, + {errors.New("bar: foobar"), Exception{Value: "foobar", Type: "*errors.errorString", Module: "bar"}}, +} + +func TestNewException(t *testing.T) { + for _, test := range newExceptionTests { + actual := NewException(test.err, nil) + if actual.Value != test.Value { + t.Errorf("incorrect Value: got %s, want %s", actual.Value, test.Value) + } + if actual.Type != test.Type { + t.Errorf("incorrect Type: got %s, want %s", actual.Type, test.Type) + } + if actual.Module != test.Module { + t.Errorf("incorrect Module: got %s, want %s", actual.Module, test.Module) + } + } +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/http.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/http.go new file mode 100644 index 0000000..4ea5dba --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/http.go @@ -0,0 +1,60 @@ +package raven + +import ( + "net" + "net/http" + "net/url" + "strings" +) + +func NewHttp(req *http.Request) *Http { + proto := "http" + if req.TLS != nil || req.Header.Get("X-Forwarded-Proto") == "https" { + proto = "https" + } + h := &Http{ + Method: req.Method, + Cookies: req.Header.Get("Cookie"), + Query: sanitizeQuery(req.URL.Query()).Encode(), + URL: proto + "://" + req.Host + req.URL.Path, + Headers: make(map[string]string, len(req.Header)), + } + if addr, port, err := net.SplitHostPort(req.RemoteAddr); err == nil { + h.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port} + } + for k, v := range req.Header { + h.Headers[k] = strings.Join(v, ",") + } + return h +} + +var querySecretFields = []string{"password", "passphrase", "passwd", "secret"} + +func sanitizeQuery(query url.Values) url.Values { + for _, keyword := range querySecretFields { + for field := range query { + if strings.Contains(field, keyword) { + query[field] = []string{"********"} + } + } + } + return query +} + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Http +type Http struct { + // Required + URL string `json:"url"` + Method string `json:"method"` + Query string `json:"query_string,omitempty"` + + // Optional + Cookies string `json:"cookies,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Env map[string]string `json:"env,omitempty"` + + // Must be either a string or map[string]string + Data interface{} `json:"data,omitempty"` +} + +func (h *Http) Class() string { return "sentry.interfaces.Http" } diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/http_test.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/http_test.go new file mode 100644 index 0000000..7d611b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/http_test.go @@ -0,0 +1,149 @@ +package raven + +import ( + "net/http" + "net/url" + "reflect" + "testing" +) + +type testcase struct { + request *http.Request + *Http +} + +func newBaseRequest() *http.Request { + u, _ := url.Parse("http://example.com/") + header := make(http.Header) + header.Add("Foo", "bar") + + req := &http.Request{ + Method: "GET", + URL: u, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: header, + Host: u.Host, + RemoteAddr: "127.0.0.1:8000", + } + return req +} + +func newBaseHttp() *Http { + h := &Http{ + Method: "GET", + Cookies: "", + Query: "", + URL: "http://example.com/", + Headers: map[string]string{"Foo": "bar"}, + Env: map[string]string{"REMOTE_ADDR": "127.0.0.1", "REMOTE_PORT": "8000"}, + } + return h +} + +func NewRequest() testcase { + return testcase{newBaseRequest(), newBaseHttp()} +} + +func NewRequestIPV6() testcase { + req := newBaseRequest() + req.RemoteAddr = "[:1]:8000" + + h := newBaseHttp() + h.Env = map[string]string{"REMOTE_ADDR": ":1", "REMOTE_PORT": "8000"} + return testcase{req, h} +} + +func NewRequestMultipleHeaders() testcase { + req := newBaseRequest() + req.Header.Add("Foo", "baz") + + h := newBaseHttp() + h.Headers["Foo"] = "bar,baz" + return testcase{req, h} +} + +func NewSecureRequest() testcase { + req := newBaseRequest() + req.Header.Add("X-Forwarded-Proto", "https") + + h := newBaseHttp() + h.URL = "https://example.com/" + h.Headers["X-Forwarded-Proto"] = "https" + return testcase{req, h} +} + +func NewCookiesRequest() testcase { + val := "foo=bar; bar=baz" + req := newBaseRequest() + req.Header.Add("Cookie", val) + + h := newBaseHttp() + h.Cookies = val + h.Headers["Cookie"] = val + return testcase{req, h} +} + +var newHttpTests = []testcase{ + NewRequest(), + NewRequestIPV6(), + NewRequestMultipleHeaders(), + NewSecureRequest(), + NewCookiesRequest(), +} + +func TestNewHttp(t *testing.T) { + for _, test := range newHttpTests { + actual := NewHttp(test.request) + if actual.Method != test.Method { + t.Errorf("incorrect Method: got %s, want %s", actual.Method, test.Method) + } + if actual.Cookies != test.Cookies { + t.Errorf("incorrect Cookies: got %s, want %s", actual.Cookies, test.Cookies) + } + if actual.Query != test.Query { + t.Errorf("incorrect Query: got %s, want %s", actual.Query, test.Query) + } + if actual.URL != test.URL { + t.Errorf("incorrect URL: got %s, want %s", actual.URL, test.URL) + } + if !reflect.DeepEqual(actual.Headers, test.Headers) { + t.Errorf("incorrect Headers: got %+v, want %+v", actual.Headers, test.Headers) + } + if !reflect.DeepEqual(actual.Env, test.Env) { + t.Errorf("incorrect Env: got %+v, want %+v", actual.Env, test.Env) + } + if !reflect.DeepEqual(actual.Data, test.Data) { + t.Errorf("incorrect Data: got %+v, want %+v", actual.Data, test.Data) + } + } +} + +var sanitizeQueryTests = []struct { + input, output string +}{ + {"foo=bar", "foo=bar"}, + {"password=foo", "password=********"}, + {"passphrase=foo", "passphrase=********"}, + {"passwd=foo", "passwd=********"}, + {"secret=foo", "secret=********"}, + {"secretstuff=foo", "secretstuff=********"}, + {"foo=bar&secret=foo", "foo=bar&secret=********"}, + {"secret=foo&secret=bar", "secret=********"}, +} + +func parseQuery(q string) url.Values { + r, _ := url.ParseQuery(q) + return r +} + +func TestSanitizeQuery(t *testing.T) { + for _, test := range sanitizeQueryTests { + actual := sanitizeQuery(parseQuery(test.input)) + expected := parseQuery(test.output) + if !reflect.DeepEqual(actual, expected) { + t.Errorf("incorrect sanitization: got %+v, want %+v", actual, expected) + } + } +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/interfaces.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/interfaces.go new file mode 100644 index 0000000..2104d6b --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/interfaces.go @@ -0,0 +1,47 @@ +package raven + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Message +type Message struct { + // Required + Message string `json:"message"` + + // Optional + Params []interface{} `json:"params,omitempty"` +} + +func (m *Message) Class() string { return "sentry.interfaces.Message" } + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Template +type Template struct { + // Required + Filename string `json:"filename"` + Lineno int `json:"lineno"` + ContextLine string `json:"context_line"` + + // Optional + PreContext []string `json:"pre_context,omitempty"` + PostContext []string `json:"post_context,omitempty"` + AbsolutePath string `json:"abs_path,omitempty"` +} + +func (t *Template) Class() string { return "sentry.interfaces.Template" } + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.User +type User struct { + ID string `json:"id"` + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` +} + +func (h *User) Class() string { return "sentry.interfaces.User" } + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Query +type Query struct { + // Required + Query string `json:"query"` + + // Optional + Engine string `json:"engine,omitempty"` +} + +func (q *Query) Class() string { return "sentry.interfaces.Query" } diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace.go new file mode 100644 index 0000000..d272a37 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace.go @@ -0,0 +1,195 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// Some code from the runtime/debug package of the Go standard library. + +package raven + +import ( + "bytes" + "go/build" + "io/ioutil" + "path/filepath" + "runtime" + "strings" + "sync" +) + +// http://sentry.readthedocs.org/en/latest/developer/interfaces/index.html#sentry.interfaces.Stacktrace +type Stacktrace struct { + // Required + Frames []*StacktraceFrame `json:"frames"` +} + +func (s *Stacktrace) Class() string { return "sentry.interfaces.Stacktrace" } + +func (s *Stacktrace) Culprit() string { + for i := len(s.Frames) - 1; i >= 0; i-- { + frame := s.Frames[i] + if frame.InApp == true && frame.Module != "" && frame.Function != "" { + return frame.Module + "." + frame.Function + } + } + return "" +} + +type StacktraceFrame struct { + // At least one required + Filename string `json:"filename,omitempty"` + Function string `json:"function,omitempty"` + Module string `json:"module,omitempty"` + + // Optional + Lineno int `json:"lineno,omitempty"` + Colno int `json:"colno,omitempty"` + AbsolutePath string `json:"abs_path,omitempty"` + ContextLine string `json:"context_line,omitempty"` + PreContext []string `json:"pre_context,omitempty"` + PostContext []string `json:"post_context,omitempty"` + InApp bool `json:"in_app,omitempty"` +} + +// Intialize and populate a new stacktrace, skipping skip frames. +// +// context is the number of surrounding lines that should be included for context. +// Setting context to 3 would try to get seven lines. Setting context to -1 returns +// one line with no surrounding context, and 0 returns no context. +// +// appPackagePrefixes is a list of prefixes used to check whether a package should +// be considered "in app". +func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace { + var frames []*StacktraceFrame + for i := 1 + skip; ; i++ { + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + frames = append(frames, NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)) + } + // Sentry wants the frames with the oldest first, so reverse them + for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 { + frames[i], frames[j] = frames[j], frames[i] + } + return &Stacktrace{frames} +} + +// Build a single frame using data returned from runtime.Caller. +// +// context is the number of surrounding lines that should be included for context. +// Setting context to 3 would try to get seven lines. Setting context to -1 returns +// one line with no surrounding context, and 0 returns no context. +// +// appPackagePrefixes is a list of prefixes used to check whether a package should +// be considered "in app". +func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame { + frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false} + frame.Module, frame.Function = functionName(pc) + if frame.Module == "main" { + frame.InApp = true + } else { + for _, prefix := range appPackagePrefixes { + if strings.HasPrefix(frame.Module, prefix) && !strings.Contains(frame.Module, "vendor") && !strings.Contains(frame.Module, "third_party") { + frame.InApp = true + } + } + } + + if context > 0 { + contextLines, lineIdx := fileContext(file, line, context) + if len(contextLines) > 0 { + for i, line := range contextLines { + switch { + case i < lineIdx: + frame.PreContext = append(frame.PreContext, string(line)) + case i == lineIdx: + frame.ContextLine = string(line) + default: + frame.PostContext = append(frame.PostContext, string(line)) + } + } + } + } else if context == -1 { + contextLine, _ := fileContext(file, line, 0) + if len(contextLine) > 0 { + frame.ContextLine = string(contextLine[0]) + } + } + return frame +} + +// Retrieve the name of the package and function containing the PC. +func functionName(pc uintptr) (pack string, name string) { + fn := runtime.FuncForPC(pc) + if fn == nil { + return + } + name = fn.Name() + // We get this: + // runtime/debug.*T·ptrmethod + // and want this: + // pack = runtime/debug + // name = *T.ptrmethod + if idx := strings.LastIndex(name, "."); idx != -1 { + pack = name[:idx] + name = name[idx+1:] + } + name = strings.Replace(name, "·", ".", -1) + return +} + +var fileCacheLock sync.Mutex +var fileCache = make(map[string][][]byte) + +func fileContext(filename string, line, context int) ([][]byte, int) { + fileCacheLock.Lock() + defer fileCacheLock.Unlock() + lines, ok := fileCache[filename] + if !ok { + data, err := ioutil.ReadFile(filename) + if err != nil { + return nil, 0 + } + lines = bytes.Split(data, []byte{'\n'}) + fileCache[filename] = lines + } + line-- // stack trace lines are 1-indexed + start := line - context + var idx int + if start < 0 { + start = 0 + idx = line + } else { + idx = context + } + end := line + context + 1 + if line >= len(lines) { + return nil, 0 + } + if end > len(lines) { + end = len(lines) + } + return lines[start:end], idx +} + +var trimPaths []string + +// Try to trim the GOROOT or GOPATH prefix off of a filename +func trimPath(filename string) string { + for _, prefix := range trimPaths { + if trimmed := strings.TrimPrefix(filename, prefix); len(trimmed) < len(filename) { + return trimmed + } + } + return filename +} + +func init() { + // Collect all source directories, and make sure they + // end in a trailing "separator" + for _, prefix := range build.Default.SrcDirs() { + if prefix[len(prefix)-1] != filepath.Separator { + prefix += string(filepath.Separator) + } + trimPaths = append(trimPaths, prefix) + } +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace_test.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace_test.go new file mode 100644 index 0000000..b213c81 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/stacktrace_test.go @@ -0,0 +1,133 @@ +package raven + +import ( + "fmt" + "go/build" + "path/filepath" + "runtime" + "strings" + "testing" +) + +type FunctionNameTest struct { + skip int + pack string + name string +} + +var ( + thisFile string + thisPackage string + functionNameTests []FunctionNameTest +) + +func TestFunctionName(t *testing.T) { + for _, test := range functionNameTests { + pc, _, _, _ := runtime.Caller(test.skip) + pack, name := functionName(pc) + + if pack != test.pack { + t.Errorf("incorrect package; got %s, want %s", pack, test.pack) + } + if name != test.name { + t.Errorf("incorrect function; got %s, want %s", name, test.name) + } + } +} + +func TestStacktrace(t *testing.T) { + st := trace() + if st == nil { + t.Error("got nil stacktrace") + } + if len(st.Frames) == 0 { + t.Error("got zero frames") + } + + f := st.Frames[len(st.Frames)-1] + if f.Filename != thisFile { + t.Errorf("incorrect Filename; got %s, want %s", f.Filename, thisFile) + } + if !strings.HasSuffix(f.AbsolutePath, thisFile) { + t.Error("incorrect AbsolutePath:", f.AbsolutePath) + } + if f.Function != "trace" { + t.Error("incorrect Function:", f.Function) + } + if f.Module != thisPackage { + t.Error("incorrect Module:", f.Module) + } + if f.Lineno != 83 { + t.Error("incorrect Lineno:", f.Lineno) + } + if f.ContextLine != "\treturn NewStacktrace(0, 2, []string{thisPackage})" { + t.Errorf("incorrect ContextLine: %#v", f.ContextLine) + } + if len(f.PreContext) != 2 || f.PreContext[0] != "// a" || f.PreContext[1] != "func trace() *Stacktrace {" { + t.Errorf("incorrect PreContext %#v", f.PreContext) + } + if len(f.PostContext) != 2 || f.PostContext[0] != "\t// b" || f.PostContext[1] != "}" { + t.Errorf("incorrect PostContext %#v", f.PostContext) + } + if !f.InApp { + t.Error("expected InApp to be true") + } + + if st.Culprit() != fmt.Sprintf("%s.trace", thisPackage) { + t.Error("incorrect Culprit:", st.Culprit()) + } +} + +// a +func trace() *Stacktrace { + return NewStacktrace(0, 2, []string{thisPackage}) + // b +} + +func derivePackage() (file, pack string) { + // Get file name by seeking caller's file name. + _, callerFile, _, ok := runtime.Caller(1) + if !ok { + return + } + + // Trim file name + file = callerFile + for _, dir := range build.Default.SrcDirs() { + dir := dir + string(filepath.Separator) + if trimmed := strings.TrimPrefix(callerFile, dir); len(trimmed) < len(file) { + file = trimmed + } + } + + // Now derive package name + dir := filepath.Dir(callerFile) + + dirPkg, err := build.ImportDir(dir, build.AllowBinary) + if err != nil { + return + } + + pack = dirPkg.ImportPath + return +} + +func init() { + thisFile, thisPackage = derivePackage() + functionNameTests = []FunctionNameTest{ + {0, thisPackage, "TestFunctionName"}, + {1, "testing", "tRunner"}, + {2, "runtime", "goexit"}, + {100, "", ""}, + } +} + +// TestNewStacktrace_outOfBounds verifies that a context exceeding the number +// of lines in a file does not cause a panic. +func TestNewStacktrace_outOfBounds(t *testing.T) { + st := NewStacktrace(0, 1000000, []string{thisPackage}) + f := st.Frames[len(st.Frames)-1] + if f.ContextLine != "\tst := NewStacktrace(0, 1000000, []string{thisPackage})" { + t.Errorf("incorrect ContextLine: %#v", f.ContextLine) + } +} diff --git a/Godeps/_workspace/src/github.com/getsentry/raven-go/writer.go b/Godeps/_workspace/src/github.com/getsentry/raven-go/writer.go new file mode 100644 index 0000000..61f7a91 --- /dev/null +++ b/Godeps/_workspace/src/github.com/getsentry/raven-go/writer.go @@ -0,0 +1,20 @@ +package raven + +type Writer struct { + Client *Client + Level Severity + Logger string // Logger name reported to Sentry +} + +// Write formats the byte slice p into a string, and sends a message to +// Sentry at the severity level indicated by the Writer w. +func (w *Writer) Write(p []byte) (int, error) { + message := string(p) + + packet := NewPacket(message, &Message{message, nil}) + packet.Level = w.Level + packet.Logger = w.Logger + w.Client.Capture(packet, nil) + + return len(p), nil +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go index c4a6640..fe12cfa 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys.go @@ -6,11 +6,12 @@ package openpgp import ( "crypto/rsa" + "io" + "time" + "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/errors" "golang.org/x/crypto/openpgp/packet" - "io" - "time" ) // PublicKeyType is the armor type for a PGP public key. @@ -90,13 +91,16 @@ func (e *Entity) primaryIdentity() *Identity { func (e *Entity) encryptionKey(now time.Time) (Key, bool) { candidateSubkey := -1 + // Iterate the keys to find the newest key + var maxTime time.Time for i, subkey := range e.Subkeys { if subkey.Sig.FlagsValid && subkey.Sig.FlagEncryptCommunications && subkey.PublicKey.PubKeyAlgo.CanEncrypt() && - !subkey.Sig.KeyExpired(now) { + !subkey.Sig.KeyExpired(now) && + (maxTime.IsZero() || subkey.Sig.CreationTime.After(maxTime)) { candidateSubkey = i - break + maxTime = subkey.Sig.CreationTime } } diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go index e0625cb..d5e2056 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/keys_test.go @@ -1,9 +1,12 @@ package openpgp import ( + "bytes" + "strings" "testing" "time" + "golang.org/x/crypto/openpgp/errors" "golang.org/x/crypto/openpgp/packet" ) @@ -13,15 +16,16 @@ func TestKeyExpiry(t *testing.T) { const timeFormat = "2006-01-02" time1, _ := time.Parse(timeFormat, "2013-07-01") + // The expiringKeyHex key is structured as: // - // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC - // sub 1024R/1ABB25A0 created: 2013-07-01 expires: 2013-07-08 usage: E - // sub 1024R/96A672F5 created: 2013-07-01 expires: 2013-07-31 usage: E + // pub 1024R/5E237D8C created: 2013-07-01 expires: 2013-07-31 usage: SC + // sub 1024R/1ABB25A0 created: 2013-07-01 23:11:07 +0200 CEST expires: 2013-07-08 usage: E + // sub 1024R/96A672F5 created: 2013-07-01 23:11:23 +0200 CEST expires: 2013-07-31 usage: E // - // So this should select the first, non-expired encryption key. + // So this should select the newest, non-expired encryption key. key, _ := entity.encryptionKey(time1) - if id := key.PublicKey.KeyIdShortString(); id != "1ABB25A0" { + if id := key.PublicKey.KeyIdShortString(); id != "96A672F5" { t.Errorf("Expected key 1ABB25A0 at time %s, but got key %s", time1.Format(timeFormat), id) } @@ -40,6 +44,63 @@ func TestKeyExpiry(t *testing.T) { } } +func TestMissingCrossSignature(t *testing.T) { + // This public key has a signing subkey, but the subkey does not + // contain a cross-signature. + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(missingCrossSignatureKey)) + if len(keys) != 0 { + t.Errorf("Accepted key with missing cross signature") + } + if err == nil { + t.Fatal("Failed to detect error in keyring with missing cross signature") + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err) + } + const expectedMsg = "signing subkey is missing cross-signature" + if !strings.Contains(string(structural), expectedMsg) { + t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg) + } +} + +func TestInvalidCrossSignature(t *testing.T) { + // This public key has a signing subkey, and the subkey has an + // embedded cross-signature. However, the cross-signature does + // not correctly validate over the primary and subkey. + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(invalidCrossSignatureKey)) + if len(keys) != 0 { + t.Errorf("Accepted key with invalid cross signature") + } + if err == nil { + t.Fatal("Failed to detect error in keyring with an invalid cross signature") + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T. Wanted StructuralError", err) + } + const expectedMsg = "subkey signature invalid" + if !strings.Contains(string(structural), expectedMsg) { + t.Fatalf("Unexpected error: %q. Expected it to contain %q", err, expectedMsg) + } +} + +func TestGoodCrossSignature(t *testing.T) { + // This public key has a signing subkey, and the subkey has an + // embedded cross-signature which correctly validates over the + // primary and subkey. + keys, err := ReadArmoredKeyRing(bytes.NewBufferString(goodCrossSignatureKey)) + if err != nil { + t.Fatal(err) + } + if len(keys) != 1 { + t.Errorf("Failed to accept key with good cross signature, %d", len(keys)) + } + if len(keys[0].Subkeys) != 1 { + t.Errorf("Failed to accept good subkey, %d", len(keys[0].Subkeys)) + } +} + // TestExternallyRevokableKey attempts to load and parse a key with a third party revocation permission. func TestExternallyRevocableKey(t *testing.T) { kring, _ := ReadKeyRing(readerFromHex(subkeyUsageHex)) @@ -214,3 +275,96 @@ const expiringKeyHex = "988d0451d1ec5d010400ba3385721f2dc3f4ab096b2ee867ab77213f const subkeyUsageHex = "988d04533a52bc010400d26af43085558f65b9e7dbc90cb9238015259aed5e954637adcfa2181548b2d0b60c65f1f42ec5081cbf1bc0a8aa4900acfb77070837c58f26012fbce297d70afe96e759ad63531f0037538e70dbf8e384569b9720d99d8eb39d8d0a2947233ed242436cb6ac7dfe74123354b3d0119b5c235d3dd9c9d6c004f8ffaf67ad8583001101000188b7041f010200210502533b8552170c8001ce094aa433f7040bb2ddf0be3893cb843d0fe70c020700000a0910a42704b92866382aa98404009d63d916a27543da4221c60087c33f1c44bec9998c5438018ed370cca4962876c748e94b73eb39c58eb698063f3fd6346d58dd2a11c0247934c4a9d71f24754f7468f96fb24c3e791dd2392b62f626148ad724189498cbf993db2df7c0cdc2d677c35da0f16cb16c9ce7c33b4de65a4a91b1d21a130ae9cc26067718910ef8e2b417556d627261203c756d627261407379642e65642e61753e88b80413010200220502533a52bc021b03060b090807030206150802090a0b0416020301021e01021780000a0910a42704b92866382a47840400c0c2bd04f5fca586de408b395b3c280a278259c93eaaa8b79a53b97003f8ed502a8a00446dd9947fb462677e4fcac0dac2f0701847d15130aadb6cd9e0705ea0cf5f92f129136c7be21a718d46c8e641eb7f044f2adae573e11ae423a0a9ca51324f03a8a2f34b91fa40c3cc764bee4dccadedb54c768ba0469b683ea53f1c29b88d04533a52bc01040099c92a5d6f8b744224da27bc2369127c35269b58bec179de6bbc038f749344222f85a31933224f26b70243c4e4b2d242f0c4777eaef7b5502f9dad6d8bf3aaeb471210674b74de2d7078af497d55f5cdad97c7bedfbc1b41e8065a97c9c3d344b21fc81d27723af8e374bc595da26ea242dccb6ae497be26eea57e563ed517e90011010001889f0418010200090502533a52bc021b0c000a0910a42704b92866382afa1403ff70284c2de8a043ff51d8d29772602fa98009b7861c540535f874f2c230af8caf5638151a636b21f8255003997ccd29747fdd06777bb24f9593bd7d98a3e887689bf902f999915fcc94625ae487e5d13e6616f89090ebc4fdc7eb5cad8943e4056995bb61c6af37f8043016876a958ec7ebf39c43d20d53b7f546cfa83e8d2604b88d04533b8283010400c0b529316dbdf58b4c54461e7e669dc11c09eb7f73819f178ccd4177b9182b91d138605fcf1e463262fabefa73f94a52b5e15d1904635541c7ea540f07050ce0fb51b73e6f88644cec86e91107c957a114f69554548a85295d2b70bd0b203992f76eb5d493d86d9eabcaa7ef3fc7db7e458438db3fcdb0ca1cc97c638439a9170011010001889f0418010200090502533b8283021b0c000a0910a42704b92866382adc6d0400cfff6258485a21675adb7a811c3e19ebca18851533f75a7ba317950b9997fda8d1a4c8c76505c08c04b6c2cc31dc704d33da36a21273f2b388a1a706f7c3378b66d887197a525936ed9a69acb57fe7f718133da85ec742001c5d1864e9c6c8ea1b94f1c3759cebfd93b18606066c063a63be86085b7e37bdbc65f9a915bf084bb901a204533b85cd110400aed3d2c52af2b38b5b67904b0ef73d6dd7aef86adb770e2b153cd22489654dcc91730892087bb9856ae2d9f7ed1eb48f214243fe86bfe87b349ebd7c30e630e49c07b21fdabf78b7a95c8b7f969e97e3d33f2e074c63552ba64a2ded7badc05ce0ea2be6d53485f6900c7860c7aa76560376ce963d7271b9b54638a4028b573f00a0d8854bfcdb04986141568046202192263b9b67350400aaa1049dbc7943141ef590a70dcb028d730371d92ea4863de715f7f0f16d168bd3dc266c2450457d46dcbbf0b071547e5fbee7700a820c3750b236335d8d5848adb3c0da010e998908dfd93d961480084f3aea20b247034f8988eccb5546efaa35a92d0451df3aaf1aee5aa36a4c4d462c760ecd9cebcabfbe1412b1f21450f203fd126687cd486496e971a87fd9e1a8a765fe654baa219a6871ab97768596ab05c26c1aeea8f1a2c72395a58dbc12ef9640d2b95784e974a4d2d5a9b17c25fedacfe551bda52602de8f6d2e48443f5dd1a2a2a8e6a5e70ecdb88cd6e766ad9745c7ee91d78cc55c3d06536b49c3fee6c3d0b6ff0fb2bf13a314f57c953b8f4d93bf88e70418010200090502533b85cd021b0200520910a42704b92866382a47200419110200060502533b85cd000a091042ce2c64bc0ba99214b2009e26b26852c8b13b10c35768e40e78fbbb48bd084100a0c79d9ea0844fa5853dd3c85ff3ecae6f2c9dd6c557aa04008bbbc964cd65b9b8299d4ebf31f41cc7264b8cf33a00e82c5af022331fac79efc9563a822497ba012953cefe2629f1242fcdcb911dbb2315985bab060bfd58261ace3c654bdbbe2e8ed27a46e836490145c86dc7bae15c011f7e1ffc33730109b9338cd9f483e7cef3d2f396aab5bd80efb6646d7e778270ee99d934d187dd98" const revokedKeyHex = "988d045331ce82010400c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be10011010001889f04200102000905025331d0e3021d03000a0910a401d9f09a34f7c042aa040086631196405b7e6af71026b88e98012eab44aa9849f6ef3fa930c7c9f23deaedba9db1538830f8652fb7648ec3fcade8dbcbf9eaf428e83c6cbcc272201bfe2fbb90d41963397a7c0637a1a9d9448ce695d9790db2dc95433ad7be19eb3de72dacf1d6db82c3644c13eae2a3d072b99bb341debba012c5ce4006a7d34a1f4b94b444526567205265766f6b657220283c52656727732022424d204261726973746122204b657920262530305c303e5c29203c72656740626d626172697374612e636f2e61753e88b704130102002205025331ce82021b03060b090807030206150802090a0b0416020301021e01021780000a0910a401d9f09a34f7c0019c03f75edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56889c04100102000605025331cfb5000a0910fe9645554e8266b64b4303fc084075396674fb6f778d302ac07cef6bc0b5d07b66b2004c44aef711cbac79617ef06d836b4957522d8772dd94bf41a2f4ac8b1ee6d70c57503f837445a74765a076d07b829b8111fc2a918423ddb817ead7ca2a613ef0bfb9c6b3562aec6c3cf3c75ef3031d81d95f6563e4cdcc9960bcb386c5d757b104fcca5fe11fc709df884604101102000605025331cfe7000a09107b15a67f0b3ddc0317f6009e360beea58f29c1d963a22b962b80788c3fa6c84e009d148cfde6b351469b8eae91187eff07ad9d08fcaab88d045331ce820104009f25e20a42b904f3fa555530fe5c46737cf7bd076c35a2a0d22b11f7e0b61a69320b768f4a80fe13980ce380d1cfc4a0cd8fbe2d2e2ef85416668b77208baa65bf973fe8e500e78cc310d7c8705cdb34328bf80e24f0385fce5845c33bc7943cf6b11b02348a23da0bf6428e57c05135f2dc6bd7c1ce325d666d5a5fd2fd5e410011010001889f04180102000905025331ce82021b0c000a0910a401d9f09a34f7c0418003fe34feafcbeaef348a800a0d908a7a6809cc7304017d820f70f0474d5e23cb17e38b67dc6dca282c6ca00961f4ec9edf2738d0f087b1d81e4871ef08e1798010863afb4eac4c44a376cb343be929c5be66a78cfd4456ae9ec6a99d97f4e1c3ff3583351db2147a65c0acef5c003fb544ab3a2e2dc4d43646f58b811a6c3a369d1f" const revokedSubkeyHex = "988d04533121f6010400aefc803a3e4bb1a61c86e8a86d2726c6a43e0079e9f2713f1fa017e9854c83877f4aced8e331d675c67ea83ddab80aacbfa0b9040bb12d96f5a3d6be09455e2a76546cbd21677537db941cab710216b6d24ec277ee0bd65b910f416737ed120f6b93a9d3b306245c8cfd8394606fdb462e5cf43c551438d2864506c63367fc890011010001b41d416c696365203c616c69636540626d626172697374612e636f2e61753e88bb041301020025021b03060b090807030206150802090a0b0416020301021e01021780050253312798021901000a09104ef7e4beccde97f015a803ff5448437780f63263b0df8442a995e7f76c221351a51edd06f2063d8166cf3157aada4923dfc44aa0f2a6a4da5cf83b7fe722ba8ab416c976e77c6b5682e7f1069026673bd0de56ba06fd5d7a9f177607f277d9b55ff940a638c3e68525c67517e2b3d976899b93ca267f705b3e5efad7d61220e96b618a4497eab8d04403d23f8846041011020006050253312910000a09107b15a67f0b3ddc03d96e009f50b6365d86c4be5d5e9d0ea42d5e56f5794c617700a0ab274e19c2827780016d23417ce89e0a2c0d987d889c04100102000605025331cf7a000a0910a401d9f09a34f7c0ee970400aca292f213041c9f3b3fc49148cbda9d84afee6183c8dd6c5ff2600b29482db5fecd4303797be1ee6d544a20a858080fec43412061c9a71fae4039fd58013b4ae341273e6c66ad4c7cdd9e68245bedb260562e7b166f2461a1032f2b38c0e0e5715fb3d1656979e052b55ca827a76f872b78a9fdae64bc298170bfcebedc1271b41a416c696365203c616c696365407379646973702e6f722e61753e88b804130102002205025331278b021b03060b090807030206150802090a0b0416020301021e01021780000a09104ef7e4beccde97f06a7003fa03c3af68d272ebc1fa08aa72a03b02189c26496a2833d90450801c4e42c5b5f51ad96ce2d2c9cef4b7c02a6a2fcf1412d6a2d486098eb762f5010a201819c17fd2888aec8eda20c65a3b75744de7ee5cc8ac7bfc470cbe3cb982720405a27a3c6a8c229cfe36905f881b02ed5680f6a8f05866efb9d6c5844897e631deb949ca8846041011020006050253312910000a09107b15a67f0b3ddc0347bc009f7fa35db59147469eb6f2c5aaf6428accb138b22800a0caa2f5f0874bacc5909c652a57a31beda65eddd5889c04100102000605025331cf7a000a0910a401d9f09a34f7c0316403ff46f2a5c101256627f16384d34a38fb47a6c88ba60506843e532d91614339fccae5f884a5741e7582ffaf292ba38ee10a270a05f139bde3814b6a077e8cd2db0f105ebea2a83af70d385f13b507fac2ad93ff79d84950328bb86f3074745a8b7f9b64990fb142e2a12976e27e8d09a28dc5621f957ac49091116da410ac3cbde1b88d04533121f6010400cbd785b56905e4192e2fb62a720727d43c4fa487821203cf72138b884b78b701093243e1d8c92a0248a6c0203a5a88693da34af357499abacaf4b3309c640797d03093870a323b4b6f37865f6eaa2838148a67df4735d43a90ca87942554cdf1c4a751b1e75f9fd4ce4e97e278d6c1c7ed59d33441df7d084f3f02beb68896c70011010001889f0418010200090502533121f6021b0c000a09104ef7e4beccde97f0b98b03fc0a5ccf6a372995835a2f5da33b282a7d612c0ab2a97f59cf9fff73e9110981aac2858c41399afa29624a7fd8a0add11654e3d882c0fd199e161bdad65e5e2548f7b68a437ea64293db1246e3011cbb94dc1bcdeaf0f2539bd88ff16d95547144d97cead6a8c5927660a91e6db0d16eb36b7b49a3525b54d1644e65599b032b7eb901a204533127a0110400bd3edaa09eff9809c4edc2c2a0ebe52e53c50a19c1e49ab78e6167bf61473bb08f2050d78a5cbbc6ed66aff7b42cd503f16b4a0b99fa1609681fca9b7ce2bbb1a5b3864d6cdda4d7ef7849d156d534dea30fb0efb9e4cf8959a2b2ce623905882d5430b995a15c3b9fe92906086788b891002924f94abe139b42cbbfaaabe42f00a0b65dc1a1ad27d798adbcb5b5ad02d2688c89477b03ff4eebb6f7b15a73b96a96bed201c0e5e4ea27e4c6e2dd1005b94d4b90137a5b1cf5e01c6226c070c4cc999938101578877ee76d296b9aab8246d57049caacf489e80a3f40589cade790a020b1ac146d6f7a6241184b8c7fcde680eae3188f5dcbe846d7f7bdad34f6fcfca08413e19c1d5df83fc7c7c627d493492e009c2f52a80400a2fe82de87136fd2e8845888c4431b032ba29d9a29a804277e31002a8201fb8591a3e55c7a0d0881496caf8b9fb07544a5a4879291d0dc026a0ea9e5bd88eb4aa4947bbd694b25012e208a250d65ddc6f1eea59d3aed3b4ec15fcab85e2afaa23a40ab1ef9ce3e11e1bc1c34a0e758e7aa64deb8739276df0af7d4121f834a9b88e70418010200090502533127a0021b02005209104ef7e4beccde97f047200419110200060502533127a0000a0910dbce4ee19529437fe045009c0b32f5ead48ee8a7e98fac0dea3d3e6c0e2c552500a0ad71fadc5007cfaf842d9b7db3335a8cdad15d3d1a6404009b08e2c68fe8f3b45c1bb72a4b3278cdf3012aa0f229883ad74aa1f6000bb90b18301b2f85372ca5d6b9bf478d235b733b1b197d19ccca48e9daf8e890cb64546b4ce1b178faccfff07003c172a2d4f5ebaba9f57153955f3f61a9b80a4f5cb959908f8b211b03b7026a8a82fc612bfedd3794969bcf458c4ce92be215a1176ab88d045331d144010400a5063000c5aaf34953c1aa3bfc95045b3aab9882b9a8027fecfe2142dc6b47ba8aca667399990244d513dd0504716908c17d92c65e74219e004f7b83fc125e575dd58efec3ab6dd22e3580106998523dea42ec75bf9aa111734c82df54630bebdff20fe981cfc36c76f865eb1c2fb62c9e85bc3a6e5015a361a2eb1c8431578d0011010001889f04280102000905025331d433021d03000a09104ef7e4beccde97f02e5503ff5e0630d1b65291f4882b6d40a29da4616bb5088717d469fbcc3648b8276de04a04988b1f1b9f3e18f52265c1f8b6c85861691c1a6b8a3a25a1809a0b32ad330aec5667cb4262f4450649184e8113849b05e5ad06a316ea80c001e8e71838190339a6e48bbde30647bcf245134b9a97fa875c1d83a9862cae87ffd7e2c4ce3a1b89013d04180102000905025331d144021b0200a809104ef7e4beccde97f09d2004190102000605025331d144000a0910677815e371c2fd23522203fe22ab62b8e7a151383cea3edd3a12995693911426f8ccf125e1f6426388c0010f88d9ca7da2224aee8d1c12135998640c5e1813d55a93df472faae75bef858457248db41b4505827590aeccf6f9eb646da7f980655dd3050c6897feddddaca90676dee856d66db8923477d251712bb9b3186b4d0114daf7d6b59272b53218dd1da94a03ff64006fcbe71211e5daecd9961fba66cdb6de3f914882c58ba5beddeba7dcb950c1156d7fba18c19ea880dccc800eae335deec34e3b84ac75ffa24864f782f87815cda1c0f634b3dd2fa67cea30811d21723d21d9551fa12ccbcfa62b6d3a15d01307b99925707992556d50065505b090aadb8579083a20fe65bd2a270da9b011" +const missingCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Charset: UTF-8 + +mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY +ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG +zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 +QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ +QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo +9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu +Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ +dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R +JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL +ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew +RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW +/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu +yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAJcXQeP+NmuciE99YcJoffxv +2gVLU4ZXBNHEaP0mgaJ1+tmMD089vUQAcyGRvw8jfsNsVZQIOAuRxY94aHQhIRHR +bUzBN28ofo/AJJtfx62C15xt6fDKRV6HXYqAiygrHIpEoRLyiN69iScUsjIJeyFL +C8wa72e8pSL6dkHoaV1N9ZH/xmrJ+k0vsgkQaAh9CzYufncDxcwkoP+aOlGtX1gP +WwWoIbz0JwLEMPHBWvDDXQcQPQTYQyj+LGC9U6f9VZHN25E94subM1MjuT9OhN9Y +MLfWaaIc5WyhLFyQKW2Upofn9wSFi8ubyBnv640Dfd0rVmaWv7LNTZpoZ/GbJAMA +EQEAAYkBHwQYAQIACQUCU5ygeQIbAgAKCRDt1A0FCB6SP0zCB/sEzaVR38vpx+OQ +MMynCBJrakiqDmUZv9xtplY7zsHSQjpd6xGflbU2n+iX99Q+nav0ETQZifNUEd4N +1ljDGQejcTyKD6Pkg6wBL3x9/RJye7Zszazm4+toJXZ8xJ3800+BtaPoI39akYJm ++ijzbskvN0v/j5GOFJwQO0pPRAFtdHqRs9Kf4YanxhedB4dIUblzlIJuKsxFit6N +lgGRblagG3Vv2eBszbxzPbJjHCgVLR3RmrVezKOsZjr/2i7X+xLWIR0uD3IN1qOW +CXQxLBizEEmSNVNxsp7KPGTLnqO3bPtqFirxS9PJLIMPTPLNBY7ZYuPNTMqVIUWF +4artDmrG +=7FfJ +-----END PGP PUBLIC KEY BLOCK-----` + +const invalidCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQENBFMYynYBCACVOZ3/e8Bm2b9KH9QyIlHGo/i1bnkpqsgXj8tpJ2MIUOnXMMAY +ztW7kKFLCmgVdLIC0vSoLA4yhaLcMojznh/2CcUglZeb6Ao8Gtelr//Rd5DRfPpG +zqcfUo+m+eO1co2Orabw0tZDfGpg5p3AYl0hmxhUyYSc/xUq93xL1UJzBFgYXY54 +QsM8dgeQgFseSk/YvdP5SMx1ev+eraUyiiUtWzWrWC1TdyRa5p4UZg6Rkoppf+WJ +QrW6BWrhAtqATHc8ozV7uJjeONjUEq24roRc/OFZdmQQGK6yrzKnnbA6MdHhqpdo +9kWDcXYb7pSE63Lc+OBa5X2GUVvXJLS/3nrtABEBAAG0F2ludmFsaWQtc2lnbmlu +Zy1zdWJrZXlziQEoBBMBAgASBQJTnKB5AhsBAgsHAhUIAh4BAAoJEO3UDQUIHpI/ +dN4H/idX4FQ1LIZCnpHS/oxoWQWfpRgdKAEM0qCqjMgiipJeEwSQbqjTCynuh5/R +JlODDz85ABR06aoF4l5ebGLQWFCYifPnJZ/Yf5OYcMGtb7dIbqxWVFL9iLMO/oDL +ioI3dotjPui5e+2hI9pVH1UHB/bZ/GvMGo6Zg0XxLPolKQODMVjpjLAQ0YJ3spew +RAmOGre6tIvbDsMBnm8qREt7a07cBJ6XK7xjxYaZHQBiHVxyEWDa6gyANONx8duW +/fhQ/zDTnyVM/ik6VO0Ty9BhPpcEYLFwh5c1ilFari1ta3e6qKo6ZGa9YMk/REhu +yBHd9nTkI+0CiQUmbckUiVjDKKe5AQ0EUxjKdgEIAIINDqlj7X6jYKc6DjwrOkjQ +UIRWbQQar0LwmNilehmt70g5DCL1SYm9q4LcgJJ2Nhxj0/5qqsYib50OSWMcKeEe +iRXpXzv1ObpcQtI5ithp0gR53YPXBib80t3bUzomQ5UyZqAAHzMp3BKC54/vUrSK +FeRaxDzNLrCeyI00+LHNUtwghAqHvdNcsIf8VRumK8oTm3RmDh0TyjASWYbrt9c8 +R1Um3zuoACOVy+mEIgIzsfHq0u7dwYwJB5+KeM7ZLx+HGIYdUYzHuUE1sLwVoELh ++SHIGHI1HDicOjzqgajShuIjj5hZTyQySVprrsLKiXS6NEwHAP20+XjayJ/R3tEA +EQEAAYkCPgQYAQIBKAUCU5ygeQIbAsBdIAQZAQIABgUCU5ygeQAKCRCpVlnFZmhO +52RJB/9uD1MSa0wjY6tHOIgquZcP3bHBvHmrHNMw9HR2wRCMO91ZkhrpdS3ZHtgb +u3/55etj0FdvDo1tb8P8FGSVtO5Vcwf5APM8sbbqoi8L951Q3i7qt847lfhu6sMl +w0LWFvPTOLHrliZHItPRjOltS1WAWfr2jUYhsU9ytaDAJmvf9DujxEOsN5G1YJep +54JCKVCkM/y585Zcnn+yxk/XwqoNQ0/iJUT9qRrZWvoeasxhl1PQcwihCwss44A+ +YXaAt3hbk+6LEQuZoYS73yR3WHj+42tfm7YxRGeubXfgCEz/brETEWXMh4pe0vCL +bfWrmfSPq2rDegYcAybxRQz0lF8PAAoJEO3UDQUIHpI/exkH/0vQfdHA8g/N4T6E +i6b1CUVBAkvtdJpCATZjWPhXmShOw62gkDw306vHPilL4SCvEEi4KzG72zkp6VsB +DSRcpxCwT4mHue+duiy53/aRMtSJ+vDfiV1Vhq+3sWAck/yUtfDU9/u4eFaiNok1 +8/Gd7reyuZt5CiJnpdPpjCwelK21l2w7sHAnJF55ITXdOxI8oG3BRKufz0z5lyDY +s2tXYmhhQIggdgelN8LbcMhWs/PBbtUr6uZlNJG2lW1yscD4aI529VjwJlCeo745 +U7pO4eF05VViUJ2mmfoivL3tkhoTUWhx8xs8xCUcCg8DoEoSIhxtOmoTPR22Z9BL +6LCg2mg= +=Dhm4 +-----END PGP PUBLIC KEY BLOCK-----` + +const goodCrossSignatureKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVUqeVwEEAMufHRrMPWK3gyvi0O0tABCs/oON9zV9KDZlr1a1M91ShCSFwCPo +7r80PxdWVWcj0V5h50/CJYtpN3eE/mUIgW2z1uDYQF1OzrQ8ubrksfsJvpAhENom +lTQEppv9mV8qhcM278teb7TX0pgrUHLYF5CfPdp1L957JLLXoQR/lwLVABEBAAG0 +E2dvb2Qtc2lnbmluZy1zdWJrZXmIuAQTAQIAIgUCVUqeVwIbAwYLCQgHAwIGFQgC +CQoLBBYCAwECHgECF4AACgkQNRjL95IRWP69XQQAlH6+eyXJN4DZTLX78KGjHrsw +6FCvxxClEPtPUjcJy/1KCRQmtLAt9PbbA78dvgzjDeZMZqRAwdjyJhjyg/fkU2OH +7wq4ktjUu+dLcOBb+BFMEY+YjKZhf6EJuVfxoTVr5f82XNPbYHfTho9/OABKH6kv +X70PaKZhbwnwij8Nts65AaIEVUqftREEAJ3WxZfqAX0bTDbQPf2CMT2IVMGDfhK7 +GyubOZgDFFjwUJQvHNvsrbeGLZ0xOBumLINyPO1amIfTgJNm1iiWFWfmnHReGcDl +y5mpYG60Mb79Whdcer7CMm3AqYh/dW4g6IB02NwZMKoUHo3PXmFLxMKXnWyJ0clw +R0LI/Qn509yXAKDh1SO20rqrBM+EAP2c5bfI98kyNwQAi3buu94qo3RR1ZbvfxgW +CKXDVm6N99jdZGNK7FbRifXqzJJDLcXZKLnstnC4Sd3uyfyf1uFhmDLIQRryn5m+ +LBYHfDBPN3kdm7bsZDDq9GbTHiFZUfm/tChVKXWxkhpAmHhU/tH6GGzNSMXuIWSO +aOz3Rqq0ED4NXyNKjdF9MiwD/i83S0ZBc0LmJYt4Z10jtH2B6tYdqnAK29uQaadx +yZCX2scE09UIm32/w7pV77CKr1Cp/4OzAXS1tmFzQ+bX7DR+Gl8t4wxr57VeEMvl +BGw4Vjh3X8//m3xynxycQU18Q1zJ6PkiMyPw2owZ/nss3hpSRKFJsxMLhW3fKmKr +Ey2KiOcEGAECAAkFAlVKn7UCGwIAUgkQNRjL95IRWP5HIAQZEQIABgUCVUqftQAK +CRD98VjDN10SqkWrAKDTpEY8D8HC02E/KVC5YUI01B30wgCgurpILm20kXEDCeHp +C5pygfXw1DJrhAP+NyPJ4um/bU1I+rXaHHJYroYJs8YSweiNcwiHDQn0Engh/mVZ +SqLHvbKh2dL/RXymC3+rjPvQf5cup9bPxNMa6WagdYBNAfzWGtkVISeaQW+cTEp/ +MtgVijRGXR/lGLGETPg2X3Afwn9N9bLMBkBprKgbBqU7lpaoPupxT61bL70= +=vtbN +-----END PGP PUBLIC KEY BLOCK-----` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go index 8c4f213..d977cde 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/config.go @@ -32,6 +32,17 @@ type Config struct { DefaultCompressionAlgo CompressionAlgo // CompressionConfig configures the compression settings. CompressionConfig *CompressionConfig + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int } func (c *Config) Random() io.Reader { @@ -68,3 +79,10 @@ func (c *Config) Compression() CompressionAlgo { } return c.DefaultCompressionAlgo } + +func (c *Config) PasswordHashIterations() int { + if c == nil || c.S2KCount == 0 { + return 0 + } + return c.S2KCount +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go index b43522c..266840d 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key.go @@ -7,11 +7,12 @@ package packet import ( "crypto/rsa" "encoding/binary" - "golang.org/x/crypto/openpgp/elgamal" - "golang.org/x/crypto/openpgp/errors" "io" "math/big" "strconv" + + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" ) const encryptedKeyVersion = 3 @@ -24,7 +25,7 @@ type EncryptedKey struct { CipherFunc CipherFunction // only valid after a successful Decrypt Key []byte // only valid after a successful Decrypt - encryptedMPI1, encryptedMPI2 []byte + encryptedMPI1, encryptedMPI2 parsedMPI } func (e *EncryptedKey) parse(r io.Reader) (err error) { @@ -40,13 +41,13 @@ func (e *EncryptedKey) parse(r io.Reader) (err error) { e.Algo = PublicKeyAlgorithm(buf[9]) switch e.Algo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - e.encryptedMPI1, _, err = readMPI(r) + e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r) case PubKeyAlgoElGamal: - e.encryptedMPI1, _, err = readMPI(r) + e.encryptedMPI1.bytes, e.encryptedMPI1.bitLength, err = readMPI(r) if err != nil { return } - e.encryptedMPI2, _, err = readMPI(r) + e.encryptedMPI2.bytes, e.encryptedMPI2.bitLength, err = readMPI(r) } _, err = consumeAll(r) return @@ -71,10 +72,10 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { // padding oracle attacks. switch priv.PubKeyAlgo { case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: - b, err = rsa.DecryptPKCS1v15(config.Random(), priv.PrivateKey.(*rsa.PrivateKey), e.encryptedMPI1) + b, err = rsa.DecryptPKCS1v15(config.Random(), priv.PrivateKey.(*rsa.PrivateKey), e.encryptedMPI1.bytes) case PubKeyAlgoElGamal: - c1 := new(big.Int).SetBytes(e.encryptedMPI1) - c2 := new(big.Int).SetBytes(e.encryptedMPI2) + c1 := new(big.Int).SetBytes(e.encryptedMPI1.bytes) + c2 := new(big.Int).SetBytes(e.encryptedMPI2.bytes) b, err = elgamal.Decrypt(priv.PrivateKey.(*elgamal.PrivateKey), c1, c2) default: err = errors.InvalidArgumentError("cannot decrypted encrypted session key with private key of type " + strconv.Itoa(int(priv.PubKeyAlgo))) @@ -95,6 +96,36 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error { return nil } +// Serialize writes the encrypted key packet, e, to w. +func (e *EncryptedKey) Serialize(w io.Writer) error { + var mpiLen int + switch e.Algo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: + mpiLen = 2 + len(e.encryptedMPI1.bytes) + case PubKeyAlgoElGamal: + mpiLen = 2 + len(e.encryptedMPI1.bytes) + 2 + len(e.encryptedMPI2.bytes) + default: + return errors.InvalidArgumentError("don't know how to serialize encrypted key type " + strconv.Itoa(int(e.Algo))) + } + + serializeHeader(w, packetTypeEncryptedKey, 1 /* version */ +8 /* key id */ +1 /* algo */ +mpiLen) + + w.Write([]byte{encryptedKeyVersion}) + binary.Write(w, binary.BigEndian, e.KeyId) + w.Write([]byte{byte(e.Algo)}) + + switch e.Algo { + case PubKeyAlgoRSA, PubKeyAlgoRSAEncryptOnly: + writeMPIs(w, e.encryptedMPI1) + case PubKeyAlgoElGamal: + writeMPIs(w, e.encryptedMPI1, e.encryptedMPI2) + default: + panic("internal error") + } + + return nil +} + // SerializeEncryptedKey serializes an encrypted key packet to w that contains // key, encrypted to pub. // If config is nil, sensible defaults will be used. diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go index 0a8dcc6..fee14cf 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/encrypted_key_test.go @@ -7,6 +7,7 @@ package packet import ( "bytes" "crypto/rsa" + "encoding/hex" "fmt" "math/big" "testing" @@ -123,3 +124,23 @@ func TestEncryptingEncryptedKey(t *testing.T) { t.Errorf("bad key, got %s want %x", keyHex, expectedKeyHex) } } + +func TestSerializingEncryptedKey(t *testing.T) { + const encryptedKeyHex = "c18c032a67d68660df41c70104005789d0de26b6a50c985a02a13131ca829c413a35d0e6fa8d6842599252162808ac7439c72151c8c6183e76923fe3299301414d0c25a2f06a2257db3839e7df0ec964773f6e4c4ac7ff3b48c444237166dd46ba8ff443a5410dc670cb486672fdbe7c9dfafb75b4fea83af3a204fe2a7dfa86bd20122b4f3d2646cbeecb8f7be8" + + p, err := Read(readerFromHex(encryptedKeyHex)) + if err != nil { + t.Fatalf("error from Read: %s", err) + } + ek, ok := p.(*EncryptedKey) + if !ok { + t.Fatalf("didn't parse an EncryptedKey, got %#v", p) + } + + var buf bytes.Buffer + ek.Serialize(&buf) + + if bufHex := hex.EncodeToString(buf.Bytes()); bufHex != encryptedKeyHex { + t.Fatalf("serialization of encrypted key differed from original. Original was %s, but reserialized as %s", encryptedKeyHex, bufHex) + } +} diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go index b1e7a7f..78a0f5e 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/packet.go @@ -385,16 +385,17 @@ func Read(r io.Reader) (p Packet, err error) { type SignatureType uint8 const ( - SigTypeBinary SignatureType = 0 - SigTypeText = 1 - SigTypeGenericCert = 0x10 - SigTypePersonaCert = 0x11 - SigTypeCasualCert = 0x12 - SigTypePositiveCert = 0x13 - SigTypeSubkeyBinding = 0x18 - SigTypeDirectSignature = 0x1F - SigTypeKeyRevocation = 0x20 - SigTypeSubkeyRevocation = 0x28 + SigTypeBinary SignatureType = 0 + SigTypeText = 1 + SigTypeGenericCert = 0x10 + SigTypePersonaCert = 0x11 + SigTypeCasualCert = 0x12 + SigTypePositiveCert = 0x13 + SigTypeSubkeyBinding = 0x18 + SigTypePrimaryKeyBinding = 0x19 + SigTypeDirectSignature = 0x1F + SigTypeKeyRevocation = 0x20 + SigTypeSubkeyRevocation = 0x28 ) // PublicKeyAlgorithm represents the different public key system specified for @@ -427,7 +428,7 @@ func (pka PublicKeyAlgorithm) CanEncrypt() bool { // sign a message. func (pka PublicKeyAlgorithm) CanSign() bool { switch pka { - case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA: + case PubKeyAlgoRSA, PubKeyAlgoRSASignOnly, PubKeyAlgoDSA, PubKeyAlgoECDSA: return true } return false diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go index 26bd9cc..1a0c21a 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/public_key.go @@ -16,13 +16,14 @@ import ( _ "crypto/sha512" "encoding/binary" "fmt" - "golang.org/x/crypto/openpgp/elgamal" - "golang.org/x/crypto/openpgp/errors" "hash" "io" "math/big" "strconv" "time" + + "golang.org/x/crypto/openpgp/elgamal" + "golang.org/x/crypto/openpgp/errors" ) var ( @@ -564,12 +565,33 @@ func keySignatureHash(pk, signed signingKey, hashFunc crypto.Hash) (h hash.Hash, // VerifyKeySignature returns nil iff sig is a valid signature, made by this // public key, of signed. -func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) (err error) { +func (pk *PublicKey) VerifyKeySignature(signed *PublicKey, sig *Signature) error { h, err := keySignatureHash(pk, signed, sig.Hash) if err != nil { return err } - return pk.VerifySignature(h, sig) + if err = pk.VerifySignature(h, sig); err != nil { + return err + } + + if sig.FlagSign { + // Signing subkeys must be cross-signed. See + // https://www.gnupg.org/faq/subkey-cross-certify.html. + if sig.EmbeddedSignature == nil { + return errors.StructuralError("signing subkey is missing cross-signature") + } + // Verify the cross-signature. This is calculated over the same + // data as the main signature, so we cannot just recursively + // call signed.VerifyKeySignature(...) + if h, err = keySignatureHash(pk, signed, sig.EmbeddedSignature.Hash); err != nil { + return errors.StructuralError("error while hashing for cross-signature: " + err.Error()) + } + if err := signed.VerifySignature(h, sig.EmbeddedSignature); err != nil { + return errors.StructuralError("error while verifying cross-signature: " + err.Error()) + } + } + + return nil } func keyRevocationHash(pk signingKey, hashFunc crypto.Hash) (h hash.Hash, err error) { diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go index 753bf8f..34bc7c6 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/reader.go @@ -16,6 +16,14 @@ type Reader struct { readers []io.Reader } +// New io.Readers are pushed when a compressed or encrypted packet is processed +// and recursively treated as a new source of packets. However, a carefully +// crafted packet can trigger an infinite recursive sequence of packets. See +// http://mumble.net/~campbell/misc/pgp-quine +// https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-4402 +// This constant limits the number of recursive packets that may be pushed. +const maxReaders = 32 + // Next returns the most recently unread Packet, or reads another packet from // the top-most io.Reader. Unknown packet types are skipped. func (r *Reader) Next() (p Packet, err error) { @@ -44,9 +52,15 @@ func (r *Reader) Next() (p Packet, err error) { // Push causes the Reader to start reading from a new io.Reader. When an EOF // error is seen from the new io.Reader, it is popped and the Reader continues -// to read from the next most recent io.Reader. -func (r *Reader) Push(reader io.Reader) { +// to read from the next most recent io.Reader. Push returns a StructuralError +// if pushing the reader would exceed the maximum recursion level, otherwise it +// returns nil. +func (r *Reader) Push(reader io.Reader) (err error) { + if len(r.readers) >= maxReaders { + return errors.StructuralError("too many layers of packets") + } r.readers = append(r.readers, reader) + return nil } // Unread causes the given Packet to be returned from the next call to Next. diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go index 79cf993..1f29d3d 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/signature.go @@ -5,16 +5,18 @@ package packet import ( + "bytes" "crypto" "crypto/dsa" "crypto/rsa" "encoding/binary" - "golang.org/x/crypto/openpgp/errors" - "golang.org/x/crypto/openpgp/s2k" "hash" "io" "strconv" "time" + + "golang.org/x/crypto/openpgp/errors" + "golang.org/x/crypto/openpgp/s2k" ) const ( @@ -63,6 +65,15 @@ type Signature struct { RevocationReason *uint8 RevocationReasonText string + // MDC is set if this signature has a feature packet that indicates + // support for MDC subpackets. + MDC bool + + // EmbeddedSignature, if non-nil, is a signature of the parent key, by + // this key. This prevents an attacker from claiming another's signing + // subkey as their own. + EmbeddedSignature *Signature + outSubpackets []outputSubpacket } @@ -190,6 +201,8 @@ const ( primaryUserIdSubpacket signatureSubpacketType = 25 keyFlagsSubpacket signatureSubpacketType = 27 reasonForRevocationSubpacket signatureSubpacketType = 29 + featuresSubpacket signatureSubpacketType = 30 + embeddedSignatureSubpacket signatureSubpacketType = 32 ) // parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. @@ -343,7 +356,30 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r sig.RevocationReason = new(uint8) *sig.RevocationReason = subpacket[0] sig.RevocationReasonText = string(subpacket[1:]) - + case featuresSubpacket: + // Features subpacket, section 5.2.3.24 specifies a very general + // mechanism for OpenPGP implementations to signal support for new + // features. In practice, the subpacket is used exclusively to + // indicate support for MDC-protected encryption. + sig.MDC = len(subpacket) >= 1 && subpacket[0]&1 == 1 + case embeddedSignatureSubpacket: + // Only usage is in signatures that cross-certify + // signing subkeys. section 5.2.3.26 describes the + // format, with its usage described in section 11.1 + if sig.EmbeddedSignature != nil { + err = errors.StructuralError("Cannot have multiple embedded signatures") + return + } + sig.EmbeddedSignature = new(Signature) + // Embedded signatures are required to be v4 signatures see + // section 12.1. However, we only parse v4 signatures in this + // file anyway. + if err := sig.EmbeddedSignature.parse(bytes.NewBuffer(subpacket)); err != nil { + return nil, err + } + if sigType := sig.EmbeddedSignature.SigType; sigType != SigTypePrimaryKeyBinding { + return nil, errors.StructuralError("cross-signature has unexpected type " + strconv.Itoa(int(sigType))) + } default: if isCritical { err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType))) diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go index 23721bc..7dff655 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/packet/symmetric_key_encrypted.go @@ -120,7 +120,7 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf keyEncryptingKey := make([]byte, keySize) // s2k.Serialize salts and stretches the passphrase, and writes the // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. - err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash()}) + err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.PasswordHashIterations()}) if err != nil { return } diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go index 08129d7..7469792 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read.go @@ -211,7 +211,9 @@ FindKey: } md.decrypted = decrypted - packets.Push(decrypted) + if err := packets.Push(decrypted); err != nil { + return nil, err + } return readSignedMessage(packets, md, keyring) } @@ -235,7 +237,9 @@ FindLiteralData: } switch p := p.(type) { case *packet.Compressed: - packets.Push(p.Body) + if err := packets.Push(p.Body); err != nil { + return nil, err + } case *packet.OnePassSignature: if !p.IsLast { return nil, errors.UnsupportedError("nested signatures") diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go index c2720ae..03bf5d8 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/read_test.go @@ -135,6 +135,25 @@ func TestTextSignedMessage(t *testing.T) { checkSignedMessage(t, signedTextMessageHex, signedTextInput) } +// The reader should detect "compressed quines", which are compressed +// packets that expand into themselves and cause an infinite recursive +// parsing loop. +// The packet in this test case comes from Taylor R. Campbell at +// http://mumble.net/~campbell/misc/pgp-quine/ +func TestCampbellQuine(t *testing.T) { + md, err := ReadMessage(readerFromHex(campbellQuine), nil, nil, nil) + if md != nil { + t.Errorf("Reading a compressed quine should not return any data: %#v", md) + } + structural, ok := err.(errors.StructuralError) + if !ok { + t.Fatalf("Unexpected class of error: %T", err) + } + if !strings.Contains(string(structural), "too many layers of packets") { + t.Fatalf("Unexpected error: %s", err) + } +} + var signedEncryptedMessageTests = []struct { keyRingHex string messageHex string @@ -332,6 +351,16 @@ func TestReadingArmoredPrivateKey(t *testing.T) { } } +func TestReadingArmoredPublicKey(t *testing.T) { + el, err := ReadArmoredKeyRing(bytes.NewBufferString(e2ePublicKey)) + if err != nil { + t.Error(err) + } + if len(el) != 1 { + t.Errorf("didn't get a valid entity") + } +} + func TestNoArmoredData(t *testing.T) { _, err := ReadArmoredKeyRing(bytes.NewBufferString("foo")) if _, ok := err.(errors.InvalidArgumentError); !ok { @@ -409,8 +438,26 @@ VrM0m72/jnpKo04= =zNCn -----END PGP PRIVATE KEY BLOCK-----` +const e2ePublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- +Charset: UTF-8 + +xv8AAABSBAAAAAATCCqGSM49AwEHAgME1LRoXSpOxtHXDUdmuvzchyg6005qIBJ4 +sfaSxX7QgH9RV2ONUhC+WiayCNADq+UMzuR/vunSr4aQffXvuGnR383/AAAAFDxk +Z2lsQHlhaG9vLWluYy5jb20+wv8AAACGBBATCAA4/wAAAAWCVGvAG/8AAAACiwn/ +AAAACZC2VkQCOjdvYf8AAAAFlQgJCgv/AAAAA5YBAv8AAAACngEAAE1BAP0X8veD +24IjmI5/C6ZAfVNXxgZZFhTAACFX75jUA3oD6AEAzoSwKf1aqH6oq62qhCN/pekX ++WAsVMBhNwzLpqtCRjLO/wAAAFYEAAAAABIIKoZIzj0DAQcCAwT50ain7vXiIRv8 +B1DO3x3cE/aattZ5sHNixJzRCXi2vQIA5QmOxZ6b5jjUekNbdHG3SZi1a2Ak5mfX +fRxC/5VGAwEIB8L/AAAAZQQYEwgAGP8AAAAFglRrwBz/AAAACZC2VkQCOjdvYQAA +FJAA9isX3xtGyMLYwp2F3nXm7QEdY5bq5VUcD/RJlj792VwA/1wH0pCzVLl4Q9F9 +ex7En5r7rHR5xwX82Msc+Rq9dSyO +=7MrZ +-----END PGP PUBLIC KEY BLOCK-----` + const dsaKeyWithSHA512 = `9901a2044f04b07f110400db244efecc7316553ee08d179972aab87bb1214de7692593fcf5b6feb1c80fba268722dd464748539b85b81d574cd2d7ad0ca2444de4d849b8756bad7768c486c83a824f9bba4af773d11742bdfb4ac3b89ef8cc9452d4aad31a37e4b630d33927bff68e879284a1672659b8b298222fc68f370f3e24dccacc4a862442b9438b00a0ea444a24088dc23e26df7daf8f43cba3bffc4fe703fe3d6cd7fdca199d54ed8ae501c30e3ec7871ea9cdd4cf63cfe6fc82281d70a5b8bb493f922cd99fba5f088935596af087c8d818d5ec4d0b9afa7f070b3d7c1dd32a84fca08d8280b4890c8da1dde334de8e3cad8450eed2a4a4fcc2db7b8e5528b869a74a7f0189e11ef097ef1253582348de072bb07a9fa8ab838e993cef0ee203ff49298723e2d1f549b00559f886cd417a41692ce58d0ac1307dc71d85a8af21b0cf6eaa14baf2922d3a70389bedf17cc514ba0febbd107675a372fe84b90162a9e88b14d4b1c6be855b96b33fb198c46f058568817780435b6936167ebb3724b680f32bf27382ada2e37a879b3d9de2abe0c3f399350afd1ad438883f4791e2e3b4184453412068617368207472756e636174696f6e207465737488620413110a002205024f04b07f021b03060b090807030206150802090a0b0416020301021e01021780000a0910ef20e0cefca131581318009e2bf3bf047a44d75a9bacd00161ee04d435522397009a03a60d51bd8a568c6c021c8d7cf1be8d990d6417b0020003` const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101` + +const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000` diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go index 9e993ae..5416421 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k.go @@ -23,6 +23,17 @@ type Config struct { // Hash is the default hash function to be used. If // nil, SHA1 is used. Hash crypto.Hash + // S2KCount is only used for symmetric encryption. It + // determines the strength of the passphrase stretching when + // the said passphrase is hashed to produce a key. S2KCount + // should be between 1024 and 65011712, inclusive. If Config + // is nil or S2KCount is 0, the value 65536 used. Not all + // values in the above range can be represented. S2KCount will + // be rounded up to the next representable value if it cannot + // be encoded exactly. When set, it is strongly encrouraged to + // use a value that is at least 65536. See RFC 4880 Section + // 3.7.1.3. + S2KCount int } func (c *Config) hash() crypto.Hash { @@ -34,6 +45,49 @@ func (c *Config) hash() crypto.Hash { return c.Hash } +func (c *Config) encodedCount() uint8 { + if c == nil || c.S2KCount == 0 { + return 96 // The common case. Correspoding to 65536 + } + + i := c.S2KCount + switch { + // Behave like GPG. Should we make 65536 the lowest value used? + case i < 1024: + i = 1024 + case i > 65011712: + i = 65011712 + } + + return encodeCount(i) +} + +// encodeCount converts an iterative "count" in the range 1024 to +// 65011712, inclusive, to an encoded count. The return value is the +// octet that is actually stored in the GPG file. encodeCount panics +// if i is not in the above range (encodedCount above takes care to +// pass i in the correct range). See RFC 4880 Section 3.7.7.1. +func encodeCount(i int) uint8 { + if i < 1024 || i > 65011712 { + panic("count arg i outside the required range") + } + + for encoded := 0; encoded < 256; encoded++ { + count := decodeCount(uint8(encoded)) + if count >= i { + return uint8(encoded) + } + } + + return 255 +} + +// decodeCount returns the s2k mode 3 iterative "count" corresponding to +// the encoded octet c. +func decodeCount(c uint8) int { + return (16 + int(c&15)) << (uint32(c>>4) + 6) +} + // Simple writes to out the result of computing the Simple S2K function (RFC // 4880, section 3.7.1.1) using the given hash and input passphrase. func Simple(out []byte, h hash.Hash, in []byte) { @@ -136,7 +190,7 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) { if err != nil { return } - count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6) + count := decodeCount(buf[8]) f := func(out, in []byte) { Iterated(out, h, in, buf[:8], count) } @@ -158,8 +212,9 @@ func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Co if _, err := io.ReadFull(rand, salt); err != nil { return err } - const count = 65536 // this is the default in gpg - buf[10] = 96 // 65536 iterations + encodedCount := c.encodedCount() + count := decodeCount(encodedCount) + buf[10] = encodedCount if _, err := w.Write(buf[:]); err != nil { return err } diff --git a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go index 2e3d7b6..183d260 100644 --- a/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go +++ b/Godeps/_workspace/src/golang.org/x/crypto/openpgp/s2k/s2k_test.go @@ -104,8 +104,11 @@ func TestParse(t *testing.T) { func TestSerialize(t *testing.T) { hashes := []crypto.Hash{crypto.MD5, crypto.SHA1, crypto.RIPEMD160, crypto.SHA256, crypto.SHA384, crypto.SHA512, crypto.SHA224} + testCounts := []int{-1, 0, 1024, 65536, 4063232, 65011712} for _, h := range hashes { - testSerializeConfig(t, &Config{Hash: h}) + for _, c := range testCounts { + testSerializeConfig(t, &Config{Hash: h, S2KCount: c}) + } } } From fdaf342d2d809ea60a19180119786da2b6a821e1 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 01:09:07 +0200 Subject: [PATCH 64/67] MessageID fix --- routes/emails.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/routes/emails.go b/routes/emails.go index 3ac67a5..89ca0eb 100644 --- a/routes/emails.go +++ b/routes/emails.go @@ -3,8 +3,8 @@ package routes import ( //"bytes" //"io" - //"crypto/sha256" - //"encoding/hex" + "crypto/sha256" + "encoding/hex" "net/http" "net/mail" "regexp" @@ -389,10 +389,14 @@ func EmailsCreate(c web.C, w http.ResponseWriter, r *http.Request) { input.Thread = thread.ID } + // Calculate the message ID + idHash := sha256.Sum256([]byte(resource.ID)) + messageID := hex.EncodeToString(idHash[:]) + "@" + env.Config.EmailDomain + // Create a new email struct email := &models.Email{ Resource: resource, - MessageID: resource.ID + "@lavaboom.com", + MessageID: messageID, Kind: input.Kind, Thread: input.Thread, From ae8d8fcd23afbb149e5fffb25f7a6cacdf130534 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 01:24:24 +0200 Subject: [PATCH 65/67] Enabling the custom recoverer --- setup/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/setup.go b/setup/setup.go index edbfd60..d03b17a 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -547,7 +547,7 @@ func PrepareMux(flags *env.Flags) *web.Mux { }) mux.Use(middleware.RequestID) //mux.Use(glogrus.NewGlogrus(log, "api")) - mux.Use(middleware.Recoverer) + mux.Use(recoverer) mux.Use(middleware.AutomaticOptions) // Set up an auth'd mux From 0f9a331623d84ed6199f9235893769ba537d3914 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 19:13:40 +0200 Subject: [PATCH 66/67] Removed Loggly --- Godeps/Godeps.json | 10 - .../github.com/segmentio/go-loggly/History.md | 71 ---- .../github.com/segmentio/go-loggly/Readme.md | 17 - .../github.com/segmentio/go-loggly/loggly.go | 306 ------------------ .../visionmedia/go-debug/History.md | 21 -- .../github.com/visionmedia/go-debug/Makefile | 8 - .../github.com/visionmedia/go-debug/Readme.md | 75 ----- .../github.com/visionmedia/go-debug/debug.go | 128 -------- .../visionmedia/go-debug/debug_test.go | 152 --------- .../visionmedia/go-debug/example/multiple.go | 25 -- .../visionmedia/go-debug/example/single.go | 16 - README.md | 1 - env/config.go | 2 - main.go | 4 - setup/loggly.go | 24 -- setup/setup.go | 9 +- 16 files changed, 1 insertion(+), 868 deletions(-) delete mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md delete mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md delete mode 100644 Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go delete mode 100644 Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go delete mode 100644 setup/loggly.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 3c350f5..3b4f319 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -130,16 +130,6 @@ "Comment": "v0.2.2-8-ga5260a3", "Rev": "a5260a307b3e7dd583283c1e2717445244d506c7" }, - { - "ImportPath": "github.com/segmentio/go-loggly", - "Comment": "v0.4.3-4-ge78f697", - "Rev": "e78f6971ebca5835614673e2f5f6a47ca5f13501" - }, - { - "ImportPath": "github.com/visionmedia/go-debug", - "Comment": "v2.0.0", - "Rev": "ff4a55a20a86994118644bbddc6a216da193cc13" - }, { "ImportPath": "github.com/willf/bitset", "Comment": "v1.0.0-17-g4b22041", diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md b/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md deleted file mode 100644 index 8564e07..0000000 --- a/Godeps/_workspace/src/github.com/segmentio/go-loggly/History.md +++ /dev/null @@ -1,71 +0,0 @@ - -v0.4.3 / 2014-07-31 -================== - - * fix res.Body.Close() defer - -v0.4.2 / 2014-07-31 -================== - - * remove implicit newline in .Write() - -v0.4.1 / 2014-07-31 -================== - - * fix when no tags are used - -v0.4.0 / 2014-07-25 -================== - - * add tag support to New() - * add varg support to .Tag() - -v0.3.0 / 2014-07-25 -================== - - * add io.Writer interface support - * add tag support. Closes #5 - * change User-Agent sent to loggly to match repo - -0.1.1 / 2014-07-10 -================== - - * fix: close the response body so it doesn't leak file descriptors - -0.3.0 / 2014-06-01 -================== - - * change timestamp to be milliseconds - -0.2.0 / 2014-05-31 -================== - - * add .Stdout option - * add simple example - * fix locking typo - * refactor locking - -0.1.0 / 2014-05-22 -================== - - * replace Debugging option with debug package - -0.0.5 / 2014-05-20 -================== - - * add mutexes - -0.0.4 / 2014-05-20 -================== - - * rename .Log() to .Send() - -0.0.3 / 2014-05-20 -================== - - * unix epoch timestamp - -0.0.2 / 2014-05-20 -================== - - * https diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md b/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md deleted file mode 100644 index ac50e8e..0000000 --- a/Godeps/_workspace/src/github.com/segmentio/go-loggly/Readme.md +++ /dev/null @@ -1,17 +0,0 @@ -# go-loggly - - Loggly client for Go. - - View the [docs](http://godoc.org/github.com/segmentio/go-loggly). - -## Installation - - $ go get github.com/segmentio/go-loggly - -## Debug - - Enable verbose debugging output using the __DEBUG__ environment variable, for exmaple `DEBUG=loggly`. - -## License - - MIT diff --git a/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go b/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go deleted file mode 100644 index 7a4d967..0000000 --- a/Godeps/_workspace/src/github.com/segmentio/go-loggly/loggly.go +++ /dev/null @@ -1,306 +0,0 @@ -package loggly - -import . "github.com/visionmedia/go-debug" -import . "encoding/json" -import "io/ioutil" -import "net/http" -import "strings" -import "bytes" -import "time" -import "sync" -import "fmt" -import "os" -import "io" - -const Version = "0.4.3" - -const api = "https://logs-01.loggly.com/bulk/{token}" - -type Message map[string]interface{} - -var debug = Debug("loggly") - -var nl = []byte{'\n'} - -type Level int - -const ( - DEBUG Level = iota - INFO - NOTICE - WARNING - ERROR - CRITICAL - ALERT - EMERGENCY -) - -// Loggly client. -type Client struct { - // Optionally output logs to the given writer. - Writer io.Writer - - // Log level defaulting to INFO. - Level Level - - // Size of buffer before flushing [100] - BufferSize int - - // Flush interval regardless of size [5s] - FlushInterval time.Duration - - // Loggly end-point. - Endpoint string - - // Token string. - Token string - - // Default properties. - Defaults Message - buffer [][]byte - tags []string - sync.Mutex -} - -// New returns a new loggly client with the given `token`. -// Optionally pass `tags` or set them later with `.Tag()`. -func New(token string, tags ...string) *Client { - host, err := os.Hostname() - defaults := Message{} - - if err == nil { - defaults["hostname"] = host - } - - c := &Client{ - Level: INFO, - BufferSize: 100, - FlushInterval: 5 * time.Second, - Token: token, - Endpoint: strings.Replace(api, "{token}", token, 1), - buffer: make([][]byte, 0), - Defaults: defaults, - } - - c.Tag(tags...) - - go c.start() - - return c -} - -// Send buffers `msg` for async sending. -func (c *Client) Send(msg Message) error { - if _, exists := msg["timestamp"]; !exists { - msg["timestamp"] = time.Now().UnixNano() / int64(time.Millisecond) - } - merge(msg, c.Defaults) - - json, err := Marshal(msg) - if err != nil { - return err - } - - c.Lock() - defer c.Unlock() - - if c.Writer != nil { - fmt.Fprintf(c.Writer, "%s\n", string(json)) - } - - c.buffer = append(c.buffer, json) - - debug("buffer (%d/%d) %v", len(c.buffer), c.BufferSize, msg) - - if len(c.buffer) >= c.BufferSize { - go c.Flush() - } - - return nil -} - -// Write raw data to loggly. -func (c *Client) Write(b []byte) (int, error) { - c.Lock() - defer c.Unlock() - - if c.Writer != nil { - fmt.Fprintf(c.Writer, "%s", b) - } - - c.buffer = append(c.buffer, b) - - debug("buffer (%d/%d) %q", len(c.buffer), c.BufferSize, b) - - if len(c.buffer) >= c.BufferSize { - go c.Flush() - } - - return len(b), nil -} - -// Debug log. -func (c *Client) Debug(t string, props ...Message) error { - if c.Level > DEBUG { - return nil - } - msg := Message{"level": "debug", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Info log. -func (c *Client) Info(t string, props ...Message) error { - if c.Level > INFO { - return nil - } - msg := Message{"level": "info", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Notice log. -func (c *Client) Notice(t string, props ...Message) error { - if c.Level > NOTICE { - return nil - } - msg := Message{"level": "notice", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Warning log. -func (c *Client) Warn(t string, props ...Message) error { - if c.Level > WARNING { - return nil - } - msg := Message{"level": "warning", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Error log. -func (c *Client) Error(t string, props ...Message) error { - if c.Level > ERROR { - return nil - } - msg := Message{"level": "error", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Critical log. -func (c *Client) Critical(t string, props ...Message) error { - if c.Level > CRITICAL { - return nil - } - msg := Message{"level": "critical", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Alert log. -func (c *Client) Alert(t string, props ...Message) error { - if c.Level > ALERT { - return nil - } - msg := Message{"level": "alert", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Emergency log. -func (c *Client) Emergency(t string, props ...Message) error { - if c.Level > EMERGENCY { - return nil - } - msg := Message{"level": "emergency", "type": t} - merge(msg, props...) - return c.Send(msg) -} - -// Flush the buffered messages. -func (c *Client) Flush() error { - c.Lock() - - if len(c.buffer) == 0 { - debug("no messages to flush") - c.Unlock() - return nil - } - - debug("flushing %d messages", len(c.buffer)) - body := bytes.Join(c.buffer, nl) - - c.buffer = nil - c.Unlock() - - client := &http.Client{} - debug("POST %s with %d bytes", c.Endpoint, len(body)) - req, err := http.NewRequest("POST", c.Endpoint, bytes.NewBuffer(body)) - if err != nil { - debug("error: %v", err) - return err - } - - req.Header.Add("User-Agent", "go-loggly (version: "+Version+")") - req.Header.Add("Content-Type", "text/plain") - req.Header.Add("Content-Length", string(len(body))) - - tags := c.tagsList() - if tags != "" { - req.Header.Add("X-Loggly-Tag", tags) - } - - res, err := client.Do(req) - if err != nil { - debug("error: %v", err) - return err - } - - defer res.Body.Close() - - debug("%d response", res.StatusCode) - if res.StatusCode >= 400 { - resp, _ := ioutil.ReadAll(res.Body) - debug("error: %s", string(resp)) - } - - return err -} - -// Tag adds the given `tags` for all logs. -func (c *Client) Tag(tags ...string) { - c.Lock() - defer c.Unlock() - - for _, tag := range tags { - c.tags = append(c.tags, tag) - } -} - -// Return a comma-delimited tag list string. -func (c *Client) tagsList() string { - c.Lock() - defer c.Unlock() - - return strings.Join(c.tags, ",") -} - -// Start flusher. -func (c *Client) start() { - for { - time.Sleep(c.FlushInterval) - debug("interval %v reached", c.FlushInterval) - c.Flush() - } -} - -// Merge others into a. -func merge(a Message, others ...Message) { - for _, msg := range others { - for k, v := range msg { - a[k] = v - } - } -} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md b/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md deleted file mode 100644 index 318ceb4..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/History.md +++ /dev/null @@ -1,21 +0,0 @@ - -v2.0.0 / 2014-10-22 -================== - - * remove live toggling feature. Closes #10 - -1.1.1 / 2014-07-07 -================== - - * fix: dispose socket. Closes #1 - -1.1.0 / 2014-06-29 -================== - - * add unix domain socket live debugging support - * add support for enabling/disabling at runtime - -0.1.0 / 2014-05-24 -================== - - * add global and debug relative deltas diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile deleted file mode 100644 index 16bc6d3..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Makefile +++ /dev/null @@ -1,8 +0,0 @@ - -test: - @go test - -bench: - @go test -bench=. - -.PHONY: bench test \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md b/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md deleted file mode 100644 index 6560af8..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/Readme.md +++ /dev/null @@ -1,75 +0,0 @@ - -# go-debug - - Conditional debug logging for Go libraries. - - View the [docs](http://godoc.org/github.com/tj/go-debug). - -## Installation - -``` -$ go get github.com/tj/go-debug -``` - -## Example - -```go -package main - -import . "github.com/tj/go-debug" -import "time" - -var debug = Debug("single") - -func main() { - for { - debug("sending mail") - debug("send email to %s", "tobi@segment.io") - debug("send email to %s", "loki@segment.io") - debug("send email to %s", "jane@segment.io") - time.Sleep(500 * time.Millisecond) - } -} -``` - -If you run the program with the `DEBUG=*` environment variable you will see: - -``` -15:58:15.115 34us 33us single - sending mail -15:58:15.116 3us 3us single - send email to tobi@segment.io -15:58:15.116 1us 1us single - send email to loki@segment.io -15:58:15.116 1us 1us single - send email to jane@segment.io -15:58:15.620 504ms 504ms single - sending mail -15:58:15.620 6us 6us single - send email to tobi@segment.io -15:58:15.620 4us 4us single - send email to loki@segment.io -15:58:15.620 4us 4us single - send email to jane@segment.io -15:58:16.123 503ms 503ms single - sending mail -15:58:16.123 7us 7us single - send email to tobi@segment.io -15:58:16.123 4us 4us single - send email to loki@segment.io -15:58:16.123 4us 4us single - send email to jane@segment.io -15:58:16.625 501ms 501ms single - sending mail -15:58:16.625 4us 4us single - send email to tobi@segment.io -15:58:16.625 4us 4us single - send email to loki@segment.io -15:58:16.625 5us 5us single - send email to jane@segment.io -``` - -A timestamp and two deltas are displayed. The timestamp consists of hour, minute, second and microseconds. The left-most delta is relative to the previous debug call of any name, followed by a delta specific to that debug function. These may be useful to identify timing issues and potential bottlenecks. - -## The DEBUG environment variable - - Executables often support `--verbose` flags for conditional logging, however - libraries typically either require altering your code to enable logging, - or simply omit logging all together. go-debug allows conditional logging - to be enabled via the __DEBUG__ environment variable, where one or more - patterns may be specified. - - For example suppose your application has several models and you want - to output logs for users only, you might use `DEBUG=models:user`. In contrast - if you wanted to see what all database activity was you might use `DEBUG=models:*`, - or if you're love being swamped with logs: `DEBUG=*`. You may also specify a list of names delimited by a comma, for example `DEBUG=mongo,redis:*`. - - The name given _should_ be the package name, however you can use whatever you like. - -# License - -MIT \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go deleted file mode 100644 index 016ca46..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug.go +++ /dev/null @@ -1,128 +0,0 @@ -package debug - -import ( - "fmt" - "io" - "math/rand" - "os" - "regexp" - "strconv" - "strings" - "sync" - "time" -) - -var ( - writer io.Writer = os.Stderr - reg *regexp.Regexp - m sync.Mutex - enabled = false -) - -// Debugger function. -type DebugFunction func(string, ...interface{}) - -// Terminal colors used at random. -var colors []string = []string{ - "31", - "32", - "33", - "34", - "35", - "36", -} - -// Initialize with DEBUG environment variable. -func init() { - env := os.Getenv("DEBUG") - - if "" != env { - Enable(env) - } -} - -// SetWriter replaces the default of os.Stderr with `w`. -func SetWriter(w io.Writer) { - m.Lock() - defer m.Unlock() - writer = w -} - -// Disable all pattern matching. This function is thread-safe. -func Disable() { - m.Lock() - defer m.Unlock() - enabled = false -} - -// Enable the given debug `pattern`. Patterns take a glob-like form, -// for example if you wanted to enable everything, just use "*", or -// if you had a library named mongodb you could use "mongodb:connection", -// or "mongodb:*". Multiple matches can be made with a comma, for -// example "mongo*,redis*". -// -// This function is thread-safe. -func Enable(pattern string) { - m.Lock() - defer m.Unlock() - pattern = regexp.QuoteMeta(pattern) - pattern = strings.Replace(pattern, "\\*", ".*?", -1) - pattern = strings.Replace(pattern, ",", "|", -1) - pattern = "^(" + pattern + ")$" - reg = regexp.MustCompile(pattern) - enabled = true -} - -// Debug creates a debug function for `name` which you call -// with printf-style arguments in your application or library. -func Debug(name string) DebugFunction { - prevGlobal := time.Now() - color := colors[rand.Intn(len(colors))] - prev := time.Now() - - return func(format string, args ...interface{}) { - if !enabled { - return - } - - if !reg.MatchString(name) { - return - } - - d := deltas(prevGlobal, prev, color) - fmt.Fprintf(writer, d+" \033["+color+"m"+name+"\033[0m - "+format+"\n", args...) - prevGlobal = time.Now() - prev = time.Now() - } -} - -// Return formatting for deltas. -func deltas(prevGlobal, prev time.Time, color string) string { - now := time.Now() - global := now.Sub(prevGlobal).Nanoseconds() - delta := now.Sub(prev).Nanoseconds() - ts := now.UTC().Format("15:04:05.000") - deltas := fmt.Sprintf("%s %-6s \033["+color+"m%-6s", ts, humanizeNano(global), humanizeNano(delta)) - return deltas -} - -// Humanize nanoseconds to a string. -func humanizeNano(n int64) string { - var suffix string - - switch { - case n > 1e9: - n /= 1e9 - suffix = "s" - case n > 1e6: - n /= 1e6 - suffix = "ms" - case n > 1e3: - n /= 1e3 - suffix = "us" - default: - suffix = "ns" - } - - return strconv.Itoa(int(n)) + suffix -} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go deleted file mode 100644 index 7ce2764..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/debug_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package debug - -import "testing" -import "strings" -import "bytes" -import "time" - -func assertContains(t *testing.T, str, substr string) { - if !strings.Contains(str, substr) { - t.Fatalf("expected %q to contain %q", str, substr) - } -} - -func assertNotContains(t *testing.T, str, substr string) { - if strings.Contains(str, substr) { - t.Fatalf("expected %q to not contain %q", str, substr) - } -} - -func TestDefault(t *testing.T) { - var b []byte - buf := bytes.NewBuffer(b) - SetWriter(buf) - - debug := Debug("foo") - debug("something") - debug("here") - debug("whoop") - - if buf.Len() != 0 { - t.Fatalf("buffer should be empty") - } -} - -func TestEnable(t *testing.T) { - var b []byte - buf := bytes.NewBuffer(b) - SetWriter(buf) - - Enable("foo") - - debug := Debug("foo") - debug("something") - debug("here") - debug("whoop") - - if buf.Len() == 0 { - t.Fatalf("buffer should have output") - } - - str := string(buf.Bytes()) - assertContains(t, str, "something") - assertContains(t, str, "here") - assertContains(t, str, "whoop") -} - -func TestMultipleOneEnabled(t *testing.T) { - var b []byte - buf := bytes.NewBuffer(b) - SetWriter(buf) - - Enable("foo") - - foo := Debug("foo") - foo("foo") - - bar := Debug("bar") - bar("bar") - - if buf.Len() == 0 { - t.Fatalf("buffer should have output") - } - - str := string(buf.Bytes()) - assertContains(t, str, "foo") - assertNotContains(t, str, "bar") -} - -func TestMultipleEnabled(t *testing.T) { - var b []byte - buf := bytes.NewBuffer(b) - SetWriter(buf) - - Enable("foo,bar") - - foo := Debug("foo") - foo("foo") - - bar := Debug("bar") - bar("bar") - - if buf.Len() == 0 { - t.Fatalf("buffer should have output") - } - - str := string(buf.Bytes()) - assertContains(t, str, "foo") - assertContains(t, str, "bar") -} - -func TestEnableDisable(t *testing.T) { - var b []byte - buf := bytes.NewBuffer(b) - SetWriter(buf) - - Enable("foo,bar") - Disable() - - foo := Debug("foo") - foo("foo") - - bar := Debug("bar") - bar("bar") - - if buf.Len() != 0 { - t.Fatalf("buffer should not have output") - } -} - -func ExampleEnable() { - Enable("mongo:connection") - Enable("mongo:*") - Enable("foo,bar,baz") - Enable("*") -} - -func ExampleDebug() { - var debug = Debug("single") - - for { - debug("sending mail") - debug("send email to %s", "tobi@segment.io") - debug("send email to %s", "loki@segment.io") - debug("send email to %s", "jane@segment.io") - time.Sleep(500 * time.Millisecond) - } -} - -func BenchmarkDisabled(b *testing.B) { - debug := Debug("something") - for i := 0; i < b.N; i++ { - debug("stuff") - } -} - -func BenchmarkNonMatch(b *testing.B) { - debug := Debug("something") - Enable("nonmatch") - for i := 0; i < b.N; i++ { - debug("stuff") - } -} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go deleted file mode 100644 index 81c3308..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/multiple.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import . "github.com/visionmedia/go-debug" -import "time" - -var a = Debug("multiple:a") -var b = Debug("multiple:b") -var c = Debug("multiple:c") - -func work(debug DebugFunction, delay time.Duration) { - for { - debug("doing stuff") - time.Sleep(delay) - } -} - -func main() { - q := make(chan bool) - - go work(a, 1000*time.Millisecond) - go work(b, 250*time.Millisecond) - go work(c, 100*time.Millisecond) - - <-q -} diff --git a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go b/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go deleted file mode 100644 index fccfe33..0000000 --- a/Godeps/_workspace/src/github.com/visionmedia/go-debug/example/single.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import . "github.com/visionmedia/go-debug" -import "time" - -var debug = Debug("single") - -func main() { - for { - debug("sending mail") - debug("send email to %s", "tobi@segment.io") - debug("send email to %s", "loki@segment.io") - debug("send email to %s", "jane@segment.io") - time.Sleep(500 * time.Millisecond) - } -} diff --git a/README.md b/README.md index 3dc3646..0c24654 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,6 @@ Usage of api: -etcd_path="settings/": Path of the keys -force_colors=false: Force colored prompt? -log="text": Log formatter type. Either "json" or "text" - -loggly_token="": Loggly token -lookupd_address="127.0.0.1:4160": Address of the lookupd server -nsqd_address="127.0.0.1:4150": Address of the nsqd server -redis_address="127.0.0.1:6379": Address of the redis server diff --git a/env/config.go b/env/config.go index 39f838a..038b45d 100644 --- a/env/config.go +++ b/env/config.go @@ -24,8 +24,6 @@ type Flags struct { YubiCloudID string YubiCloudKey string - LogglyToken string - SlackURL string SlackLevels string SlackChannel string diff --git a/main.go b/main.go index 38e94ed..3593d79 100644 --- a/main.go +++ b/main.go @@ -72,8 +72,6 @@ var ( // YubiCloud params yubiCloudID = flag.String("yubicloud_id", "", "YubiCloud API id") yubiCloudKey = flag.String("yubicloud_key", "", "YubiCloud API key") - // loggly - logglyToken = flag.String("loggly_token", "", "Loggly token") // etcd etcdAddress = flag.String("etcd_address", "", "etcd peer addresses split by commas") etcdCAFile = flag.String("etcd_ca_file", "", "etcd path to server cert's ca") @@ -121,8 +119,6 @@ func main() { YubiCloudID: *yubiCloudID, YubiCloudKey: *yubiCloudKey, - LogglyToken: *logglyToken, - SlackURL: *slackURL, SlackLevels: *slackLevels, SlackChannel: *slackChannel, diff --git a/setup/loggly.go b/setup/loggly.go deleted file mode 100644 index 4d7ec83..0000000 --- a/setup/loggly.go +++ /dev/null @@ -1,24 +0,0 @@ -package setup - -import ( - "github.com/Sirupsen/logrus" - "github.com/segmentio/go-loggly" -) - -type logglyHook struct { - Loggly *loggly.Client -} - -func (h *logglyHook) Fire(entry *logrus.Entry) error { - entry.Data["message"] = entry.Message - return h.Loggly.Send(map[string]interface{}(entry.Data)) -} - -func (h *logglyHook) Levels() []logrus.Level { - return []logrus.Level{ - logrus.WarnLevel, - logrus.ErrorLevel, - logrus.FatalLevel, - logrus.PanicLevel, - } -} diff --git a/setup/setup.go b/setup/setup.go index d03b17a..314273a 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -19,7 +19,6 @@ import ( "github.com/johntdyer/slackrus" //"github.com/pzduniak/glogrus" "github.com/getsentry/raven-go" - "github.com/segmentio/go-loggly" "github.com/willf/bloom" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" @@ -59,13 +58,7 @@ func PrepareMux(flags *env.Flags) *web.Mux { log.Formatter = &logrus.JSONFormatter{} } - // Install logrus hooks - if flags.LogglyToken != "" { - log.Hooks.Add(&logglyHook{ - Loggly: loggly.New(flags.LogglyToken), - }) - } - + // Install Logrus hooks if flags.SlackURL != "" { var level []logrus.Level From 37d96a377776e9a3009e6ce2bae986d004a61c6c Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Sat, 6 Jun 2015 19:25:05 +0200 Subject: [PATCH 67/67] Dockerfile restructurized --- Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9fd60c6..6b32eb9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,11 @@ -FROM google/golang +FROM google/golang:1.4 -MAINTAINER "Andrei Simionescu " +MAINTAINER Piotr Zduniak RUN go get github.com/tools/godep RUN mkdir -p /gopath/src/github.com/lavab/api ADD . /gopath/src/github.com/lavab/api -#RUN go get github.com/lavab/api RUN cd /gopath/src/github.com/lavab/api && godep go install - -CMD [] ENTRYPOINT ["/gopath/bin/api"]

      ?kVZoQnYTom;` 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

  • oucFp`F?D==Fyp4)N-8++a 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�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�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