├── testsuite ├── false.dat ├── null.dat ├── true.dat ├── zero.dat ├── empty-dict.dat ├── empty-list.dat ├── null.yaml ├── true.yaml ├── zero.yaml ├── empty-byte-string.dat ├── empty-dict.yaml ├── empty-list.yaml ├── false.yaml ├── natural-number.dat ├── null.repr.json ├── true.repr.json ├── zero.repr.json ├── empty-dict.repr.json ├── empty-list.repr.json ├── empty-unicode-string.dat ├── false.repr.json ├── natural-number.yaml ├── negative-number.dat ├── bigint.dat ├── empty-unicode-string.yaml ├── natural-number.repr.json ├── negative-number.yaml ├── bigint.yaml ├── empty-byte-string.repr.json ├── empty-byte-string.yaml ├── negative-number.repr.json ├── null.json ├── bigint.repr.json ├── bytestring-dict.dat ├── empty-unicode-string.repr.json ├── unicode-dict.dat ├── unicode-dict.yaml ├── list-4sprouts.dat ├── .gitattributes ├── empty-list.json ├── false.json ├── list-4sprouts.yaml ├── true.json ├── zero.json ├── empty-dict.json ├── empty-byte-string.json ├── empty-unicode-string.json ├── mixed-dict.dat ├── natural-number.json ├── negative-number.json ├── bigint.json ├── list.dat ├── bytestring-dict.repr.json ├── bytestring-dict.yaml ├── byte-string.dat ├── unicode-string.dat ├── unicode-dict.repr.json ├── unicode-string.yaml ├── mixed-dict.yaml ├── list.yaml ├── list-4sprouts.repr.json ├── mixed-dict.repr.json ├── byte-string.repr.json ├── list.repr.json ├── byte-string.yaml ├── byte-string.json ├── unicode-string.repr.json ├── unicode-string.json ├── nested-dict.dat ├── list-4sprouts.json ├── nested-dict.yaml ├── unicode-dict.json ├── bytestring-dict.json ├── list-of-dicts.dat ├── nested-dict.repr.json ├── list-of-dicts.yaml ├── list.json ├── mixed-dict.json ├── list-of-dicts.repr.json ├── nested-dict.json └── list-of-dicts.json ├── .github ├── main.workflow └── workflows │ └── check.yaml ├── .mdlintrc ├── LIBRARIES.tsv ├── CHANGES.md ├── utils └── testsuite-schema.json ├── JSON.md └── README.md /testsuite/false.dat: -------------------------------------------------------------------------------- 1 | f -------------------------------------------------------------------------------- /testsuite/null.dat: -------------------------------------------------------------------------------- 1 | n -------------------------------------------------------------------------------- /testsuite/true.dat: -------------------------------------------------------------------------------- 1 | t -------------------------------------------------------------------------------- /testsuite/zero.dat: -------------------------------------------------------------------------------- 1 | i0e -------------------------------------------------------------------------------- /testsuite/empty-dict.dat: -------------------------------------------------------------------------------- 1 | de -------------------------------------------------------------------------------- /testsuite/empty-list.dat: -------------------------------------------------------------------------------- 1 | le -------------------------------------------------------------------------------- /testsuite/null.yaml: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /testsuite/true.yaml: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /testsuite/zero.yaml: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /testsuite/empty-byte-string.dat: -------------------------------------------------------------------------------- 1 | 0: -------------------------------------------------------------------------------- /testsuite/empty-dict.yaml: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /testsuite/empty-list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /testsuite/false.yaml: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /testsuite/natural-number.dat: -------------------------------------------------------------------------------- 1 | i42e -------------------------------------------------------------------------------- /testsuite/null.repr.json: -------------------------------------------------------------------------------- 1 | null 2 | -------------------------------------------------------------------------------- /testsuite/true.repr.json: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /testsuite/zero.repr.json: -------------------------------------------------------------------------------- 1 | "0" 2 | -------------------------------------------------------------------------------- /testsuite/empty-dict.repr.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /testsuite/empty-list.repr.json: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /testsuite/empty-unicode-string.dat: -------------------------------------------------------------------------------- 1 | u0: -------------------------------------------------------------------------------- /testsuite/false.repr.json: -------------------------------------------------------------------------------- 1 | false 2 | -------------------------------------------------------------------------------- /testsuite/natural-number.yaml: -------------------------------------------------------------------------------- 1 | 42 2 | -------------------------------------------------------------------------------- /testsuite/negative-number.dat: -------------------------------------------------------------------------------- 1 | i-123e -------------------------------------------------------------------------------- /testsuite/bigint.dat: -------------------------------------------------------------------------------- 1 | i9223372036854775807e -------------------------------------------------------------------------------- /testsuite/empty-unicode-string.yaml: -------------------------------------------------------------------------------- 1 | "" 2 | -------------------------------------------------------------------------------- /testsuite/natural-number.repr.json: -------------------------------------------------------------------------------- 1 | "42" 2 | -------------------------------------------------------------------------------- /testsuite/negative-number.yaml: -------------------------------------------------------------------------------- 1 | -123 2 | -------------------------------------------------------------------------------- /testsuite/bigint.yaml: -------------------------------------------------------------------------------- 1 | 9223372036854775807 2 | -------------------------------------------------------------------------------- /testsuite/empty-byte-string.repr.json: -------------------------------------------------------------------------------- 1 | "0x" 2 | -------------------------------------------------------------------------------- /testsuite/empty-byte-string.yaml: -------------------------------------------------------------------------------- 1 | !!binary "" 2 | -------------------------------------------------------------------------------- /testsuite/negative-number.repr.json: -------------------------------------------------------------------------------- 1 | "-123" 2 | -------------------------------------------------------------------------------- /testsuite/null.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "null" 3 | } -------------------------------------------------------------------------------- /testsuite/bigint.repr.json: -------------------------------------------------------------------------------- 1 | "9223372036854775807" 2 | -------------------------------------------------------------------------------- /testsuite/bytestring-dict.dat: -------------------------------------------------------------------------------- 1 | d1:ai1e1:bi2e1:ci3ee -------------------------------------------------------------------------------- /testsuite/empty-unicode-string.repr.json: -------------------------------------------------------------------------------- 1 | "\ufeff" 2 | -------------------------------------------------------------------------------- /testsuite/unicode-dict.dat: -------------------------------------------------------------------------------- 1 | du1:ai1eu1:bi2eu1:ci3ee -------------------------------------------------------------------------------- /testsuite/unicode-dict.yaml: -------------------------------------------------------------------------------- 1 | a: 1 2 | b: 2 3 | c: 3 4 | -------------------------------------------------------------------------------- /testsuite/list-4sprouts.dat: -------------------------------------------------------------------------------- 1 | lu12:惻隱之心u12:羞惡之心u12:辭讓之心u12:是非之心e -------------------------------------------------------------------------------- /testsuite/.gitattributes: -------------------------------------------------------------------------------- 1 | *.dat binary 2 | *.json text 3 | *.yaml text 4 | -------------------------------------------------------------------------------- /testsuite/empty-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "values": [] 4 | } -------------------------------------------------------------------------------- /testsuite/false.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "boolean", 3 | "value": false 4 | } -------------------------------------------------------------------------------- /testsuite/list-4sprouts.yaml: -------------------------------------------------------------------------------- 1 | - 惻隱之心 2 | - 羞惡之心 3 | - 辭讓之心 4 | - 是非之心 5 | -------------------------------------------------------------------------------- /testsuite/true.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "boolean", 3 | "value": true 4 | } -------------------------------------------------------------------------------- /testsuite/zero.json: -------------------------------------------------------------------------------- 1 | { 2 | "decimal": "0", 3 | "type": "integer" 4 | } -------------------------------------------------------------------------------- /testsuite/empty-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "pairs": [], 3 | "type": "dictionary" 4 | } -------------------------------------------------------------------------------- /testsuite/empty-byte-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "base64": "", 3 | "type": "binary" 4 | } -------------------------------------------------------------------------------- /testsuite/empty-unicode-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "text", 3 | "value": "" 4 | } -------------------------------------------------------------------------------- /testsuite/mixed-dict.dat: -------------------------------------------------------------------------------- 1 | d1:ai1e1:bi2e1:ci3eu1:ai1eu3:ái2eu1:bi3eu1:ci4eu2:ái5ee -------------------------------------------------------------------------------- /testsuite/natural-number.json: -------------------------------------------------------------------------------- 1 | { 2 | "decimal": "42", 3 | "type": "integer" 4 | } -------------------------------------------------------------------------------- /testsuite/negative-number.json: -------------------------------------------------------------------------------- 1 | { 2 | "decimal": "-123", 3 | "type": "integer" 4 | } -------------------------------------------------------------------------------- /testsuite/bigint.json: -------------------------------------------------------------------------------- 1 | { 2 | "decimal": "9223372036854775807", 3 | "type": "integer" 4 | } -------------------------------------------------------------------------------- /testsuite/list.dat: -------------------------------------------------------------------------------- 1 | lu16:a Unicode string13:a byte stringi123ei-456etfndu1:au4:dictelu1:au4:listee -------------------------------------------------------------------------------- /testsuite/bytestring-dict.repr.json: -------------------------------------------------------------------------------- 1 | { 2 | "0x61": "1", 3 | "0x62": "2", 4 | "0x63": "3" 5 | } 6 | -------------------------------------------------------------------------------- /testsuite/bytestring-dict.yaml: -------------------------------------------------------------------------------- 1 | !!binary "YQ==": 1 2 | !!binary "Yg==": 2 3 | !!binary "Yw==": 3 4 | -------------------------------------------------------------------------------- /testsuite/byte-string.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planetarium/bencodex/HEAD/testsuite/byte-string.dat -------------------------------------------------------------------------------- /testsuite/unicode-string.dat: -------------------------------------------------------------------------------- 1 | u146:秋江에 밤이 드니 물결이 차노매라 2 | 낚시 드리치니 고기 아니 무노매라 3 | 無心한 달빛만 싣고 빈 배 저어 오노라 4 | -------------------------------------------------------------------------------- /testsuite/unicode-dict.repr.json: -------------------------------------------------------------------------------- 1 | { 2 | "\ufeffa": "1", 3 | "\ufeffb": "2", 4 | "\ufeffc": "3" 5 | } 6 | -------------------------------------------------------------------------------- /testsuite/unicode-string.yaml: -------------------------------------------------------------------------------- 1 | | 2 | 秋江에 밤이 드니 물결이 차노매라 3 | 낚시 드리치니 고기 아니 무노매라 4 | 無心한 달빛만 싣고 빈 배 저어 오노라 5 | -------------------------------------------------------------------------------- /testsuite/mixed-dict.yaml: -------------------------------------------------------------------------------- 1 | a: 1 2 | "a\u0301": 2 3 | b: 3 4 | c: 4 5 | "\u00e1": 5 6 | !!binary "YQ==": 1 7 | !!binary "Yg==": 2 8 | !!binary "Yw==": 3 9 | -------------------------------------------------------------------------------- /testsuite/list.yaml: -------------------------------------------------------------------------------- 1 | - a Unicode string 2 | - !!binary "YSBieXRlIHN0cmluZw==" # b"a byte string" 3 | - 123 4 | - -456 5 | - true 6 | - false 7 | - null 8 | - a: dict 9 | - [a, list] 10 | -------------------------------------------------------------------------------- /testsuite/list-4sprouts.repr.json: -------------------------------------------------------------------------------- 1 | [ 2 | "\ufeff\u60fb\u96b1\u4e4b\u5fc3", 3 | "\ufeff\u7f9e\u60e1\u4e4b\u5fc3", 4 | "\ufeff\u8fad\u8b93\u4e4b\u5fc3", 5 | "\ufeff\u662f\u975e\u4e4b\u5fc3" 6 | ] 7 | -------------------------------------------------------------------------------- /testsuite/mixed-dict.repr.json: -------------------------------------------------------------------------------- 1 | { 2 | "\ufeffa": "1", 3 | "\ufeffa\u0301": "2", 4 | "\ufeffb": "3", 5 | "\ufeffc": "4", 6 | "\ufeff\u00e1": "5", 7 | "0x61": "1", 8 | "0x62": "2", 9 | "0x63": "3" 10 | } 11 | -------------------------------------------------------------------------------- /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "on push" { 2 | on = "push" 3 | resolves = ["mdlint"] 4 | } 5 | 6 | action "mdlint" { 7 | uses = "bltavares/actions/mdlint@master" 8 | runs = ["markdownlint", "--config", ".mdlintrc", "."] 9 | } 10 | -------------------------------------------------------------------------------- /testsuite/byte-string.repr.json: -------------------------------------------------------------------------------- 1 | "b64:R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=" 2 | -------------------------------------------------------------------------------- /testsuite/list.repr.json: -------------------------------------------------------------------------------- 1 | [ 2 | "\ufeffa Unicode string", 3 | "0x61206279746520737472696e67", 4 | "123", 5 | "-456", 6 | true, 7 | false, 8 | null, 9 | { 10 | "\ufeffa": "\ufeffdict" 11 | }, 12 | [ 13 | "\ufeffa", 14 | "\ufefflist" 15 | ] 16 | ] 17 | -------------------------------------------------------------------------------- /testsuite/byte-string.yaml: -------------------------------------------------------------------------------- 1 | !!binary | 2 | R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5 3 | OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+ 4 | +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC 5 | AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs= 6 | -------------------------------------------------------------------------------- /.mdlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD003": { "style": "setext_with_atx" }, 4 | "MD006": false, 5 | "MD007": false, 6 | "MD029": { "style": "ordered" }, 7 | "MD030": { "ul_single": 2, "ol_single": 2, "ul_multi": 2, "ol_multi": 2 }, 8 | "no-hard-tabs": true, 9 | "whitespace": false 10 | } 11 | -------------------------------------------------------------------------------- /testsuite/byte-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "base64": "R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLCAgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=", 3 | "type": "binary" 4 | } -------------------------------------------------------------------------------- /testsuite/unicode-string.repr.json: -------------------------------------------------------------------------------- 1 | "\ufeff\u79cb\u6c5f\uc5d0 \ubc24\uc774 \ub4dc\ub2c8 \ubb3c\uacb0\uc774 \ucc28\ub178\ub9e4\ub77c\n\ub09a\uc2dc \ub4dc\ub9ac\uce58\ub2c8 \uace0\uae30 \uc544\ub2c8 \ubb34\ub178\ub9e4\ub77c\n\u7121\u5fc3\ud55c \ub2ec\ube5b\ub9cc \uc2e3\uace0 \ube48 \ubc30 \uc800\uc5b4 \uc624\ub178\ub77c\n" 2 | -------------------------------------------------------------------------------- /testsuite/unicode-string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "text", 3 | "value": "\u79cb\u6c5f\uc5d0 \ubc24\uc774 \ub4dc\ub2c8 \ubb3c\uacb0\uc774 \ucc28\ub178\ub9e4\ub77c\n\ub09a\uc2dc \ub4dc\ub9ac\uce58\ub2c8 \uace0\uae30 \uc544\ub2c8 \ubb34\ub178\ub9e4\ub77c\n\u7121\u5fc3\ud55c \ub2ec\ube5b\ub9cc \uc2e3\uace0 \ube48 \ubc30 \uc800\uc5b4 \uc624\ub178\ub77c\n" 4 | } -------------------------------------------------------------------------------- /testsuite/nested-dict.dat: -------------------------------------------------------------------------------- 1 | du3:EPOdu6:albumslu9:Down Townu7:Goodiesu11:Vitamin EPOu16:Hi-touch Hi-techeu5:debuti1980eu4:nameu13:宮川 榮子eu12:山下達郎du6:albumslu5:Spacyu12:Ride On Timeu7:For Youu8:Melodieseu5:debuti1973eeu6:杏里du6:albumslu8:Timely!!u5:Cooolu4:Waveeu5:debuti1978eu4:nameu13:川嶋 栄子eu12:角松敏生du6:albumslu10:Sea Breezeu17:On The City Shoreu13:After 5 Clasheu5:debuti1981eu4:nameu13:角松 敏生ee -------------------------------------------------------------------------------- /testsuite/list-4sprouts.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "values": [ 4 | { 5 | "type": "text", 6 | "value": "\u60fb\u96b1\u4e4b\u5fc3" 7 | }, 8 | { 9 | "type": "text", 10 | "value": "\u7f9e\u60e1\u4e4b\u5fc3" 11 | }, 12 | { 13 | "type": "text", 14 | "value": "\u8fad\u8b93\u4e4b\u5fc3" 15 | }, 16 | { 17 | "type": "text", 18 | "value": "\u662f\u975e\u4e4b\u5fc3" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /testsuite/nested-dict.yaml: -------------------------------------------------------------------------------- 1 | 山下達郎: 2 | debut: 1973 3 | albums: 4 | - Spacy 5 | - Ride On Time 6 | - For You 7 | - Melodies 8 | 杏里: 9 | name: 川嶋 栄子 10 | debut: 1978 11 | albums: 12 | - Timely!! 13 | - Coool 14 | - Wave 15 | EPO: 16 | name: 宮川 榮子 17 | debut: 1980 18 | albums: 19 | - Down Town 20 | - Goodies 21 | - Vitamin EPO 22 | - Hi-touch Hi-tech 23 | 角松敏生: 24 | name: 角松 敏生 25 | debut: 1981 26 | albums: 27 | - Sea Breeze 28 | - On The City Shore 29 | - After 5 Clash 30 | -------------------------------------------------------------------------------- /testsuite/unicode-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "pairs": [ 3 | { 4 | "key": { 5 | "type": "text", 6 | "value": "a" 7 | }, 8 | "value": { 9 | "decimal": "1", 10 | "type": "integer" 11 | } 12 | }, 13 | { 14 | "key": { 15 | "type": "text", 16 | "value": "b" 17 | }, 18 | "value": { 19 | "decimal": "2", 20 | "type": "integer" 21 | } 22 | }, 23 | { 24 | "key": { 25 | "type": "text", 26 | "value": "c" 27 | }, 28 | "value": { 29 | "decimal": "3", 30 | "type": "integer" 31 | } 32 | } 33 | ], 34 | "type": "dictionary" 35 | } -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | name: check 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | check: 6 | runs-on: ubuntu-20.04 7 | steps: 8 | - uses: actions/checkout@v2 9 | - name: 10 | if: startsWith(github.ref, 'refs/tags/') 11 | run: | 12 | set -evx 13 | VERSION="${GITHUB_REF:10}" 14 | fgrep "**$VERSION**" README.md 15 | fgrep "/$VERSION/" utils/testsuite-schema.json 16 | - run: | 17 | set -e 18 | npm install -g ajv-cli 19 | for j in testsuite/*.json; do 20 | if [[ "$j" != *.repr.json ]]; then 21 | ajv validate -s utils/testsuite-schema.json -d "$j" 22 | fi 23 | done 24 | -------------------------------------------------------------------------------- /testsuite/bytestring-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "pairs": [ 3 | { 4 | "key": { 5 | "base64": "YQ==", 6 | "type": "binary" 7 | }, 8 | "value": { 9 | "decimal": "1", 10 | "type": "integer" 11 | } 12 | }, 13 | { 14 | "key": { 15 | "base64": "Yg==", 16 | "type": "binary" 17 | }, 18 | "value": { 19 | "decimal": "2", 20 | "type": "integer" 21 | } 22 | }, 23 | { 24 | "key": { 25 | "base64": "Yw==", 26 | "type": "binary" 27 | }, 28 | "value": { 29 | "decimal": "3", 30 | "type": "integer" 31 | } 32 | } 33 | ], 34 | "type": "dictionary" 35 | } -------------------------------------------------------------------------------- /testsuite/list-of-dicts.dat: -------------------------------------------------------------------------------- 1 | ldu11:byte-stringtu14:human-readablefu4:nameu7:bencodeu13:normalizationtu6:schemafu12:unicode-textfu9:zero-copyfedu11:byte-stringtu14:human-readablefu4:nameu8:bencodexu13:normalizationtu6:schemafu12:unicode-texttu9:zero-copyfedu11:byte-stringtu14:human-readablefu4:nameu5:capnpu13:normalizationfu6:schematu12:unicode-texttu9:zero-copytedu11:byte-stringfu14:human-readabletu4:nameu4:jsonu13:normalizationfu6:schemafu12:unicode-texttu9:zero-copyfedu11:byte-stringtu14:human-readablefu4:nameu7:msgpacku13:normalizationfu6:schemafu12:unicode-texttu9:zero-copyfedu11:byte-stringtu14:human-readablefu4:nameu6:thriftu13:normalizationfu6:schematu12:unicode-texttu9:zero-copyfedu11:byte-stringtu14:human-readabletu4:nameu4:yamlu13:normalizationfu12:unicode-texttu9:zero-copyfee -------------------------------------------------------------------------------- /LIBRARIES.tsv: -------------------------------------------------------------------------------- 1 | "Project Name" "Platform" "Language" "Spec Version" "Encoder" "Decoder" "JSON Repr" "Test Suite" "Website" 2 | "bencodex-python" "Python" "Python" "1" "Provided" "Provided" "Unimplemented" "1.0" "https://github.com/planetarium/bencodex-python" 3 | "bencodex.net" ".NET" "C#" "1" "Provided" "Provided" "Implemented" "1.0" "https://github.com/planetarium/bencodex.net" 4 | "disjukr/bencodex" "JavaScript" "TypeScript" "1" "Provided" "Provided" "Unimplemented" "1.0" "https://github.com/disjukr/bencodex" 5 | "bencodex.js" "JavaScript" "TypeScript" "1" "Provided" "Provided" "Unimplemented" "1.3" "https://github.com/planetarium/bencodex.js" 6 | "bencodex-haskell" "Haskell" "Haskell" "1" "Provided" "Provided" "Unimplemented" "1.0" "https://github.com/dahlia/bencodex-haskell" 7 | "bencodex-rs" "Rust" "Rust" "1" "Provided" "Provided" "Unimplemented" "1.2" "https://github.com/moreal/bencodex-rs" 8 | "bencodex-php" "PHP" "PHP" "1" "Provided" "Provided" "Unimplemented" "1.2" "https://github.com/dahlia/bencodex-php" 9 | -------------------------------------------------------------------------------- /testsuite/nested-dict.repr.json: -------------------------------------------------------------------------------- 1 | { 2 | "\ufeff\u5c71\u4e0b\u9054\u90ce": { 3 | "\ufeffdebut": "1973", 4 | "\ufeffalbums": [ 5 | "\ufeffSpacy", 6 | "\ufeffRide On Time", 7 | "\ufeffFor You", 8 | "\ufeffMelodies" 9 | ] 10 | }, 11 | "\ufeff\u674f\u91cc": { 12 | "\ufeffname": "\ufeff\u5ddd\u5d8b \u6804\u5b50", 13 | "\ufeffdebut": "1978", 14 | "\ufeffalbums": [ 15 | "\ufeffTimely!!", 16 | "\ufeffCoool", 17 | "\ufeffWave" 18 | ] 19 | }, 20 | "\ufeffEPO": { 21 | "\ufeffname": "\ufeff\u5bae\u5ddd \u69ae\u5b50", 22 | "\ufeffdebut": "1980", 23 | "\ufeffalbums": [ 24 | "\ufeffDown Town", 25 | "\ufeffGoodies", 26 | "\ufeffVitamin EPO", 27 | "\ufeffHi-touch Hi-tech" 28 | ] 29 | }, 30 | "\ufeff\u89d2\u677e\u654f\u751f": { 31 | "\ufeffname": "\ufeff\u89d2\u677e \u654f\u751f", 32 | "\ufeffdebut": "1981", 33 | "\ufeffalbums": [ 34 | "\ufeffSea Breeze", 35 | "\ufeffOn The City Shore", 36 | "\ufeffAfter 5 Clash" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /testsuite/list-of-dicts.yaml: -------------------------------------------------------------------------------- 1 | - name: bencode 2 | human-readable: false 3 | byte-string: true 4 | unicode-text: false 5 | zero-copy: false 6 | schema: false 7 | normalization: true 8 | - name: bencodex 9 | human-readable: false 10 | byte-string: true 11 | unicode-text: true 12 | zero-copy: false 13 | schema: false 14 | normalization: true 15 | - name: capnp 16 | human-readable: false 17 | byte-string: true 18 | unicode-text: true 19 | zero-copy: true 20 | schema: true 21 | normalization: false 22 | - name: json 23 | human-readable: true 24 | byte-string: false 25 | unicode-text: true 26 | zero-copy: false 27 | schema: false 28 | normalization: false 29 | - name: msgpack 30 | human-readable: false 31 | byte-string: true 32 | unicode-text: true 33 | zero-copy: false 34 | schema: false 35 | normalization: false 36 | - name: thrift 37 | human-readable: false 38 | byte-string: true 39 | unicode-text: true 40 | zero-copy: false 41 | schema: true 42 | normalization: false 43 | - name: yaml 44 | human-readable: true 45 | byte-string: true 46 | unicode-text: true 47 | zero-copy: false 48 | normalization: false 49 | -------------------------------------------------------------------------------- /testsuite/list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "values": [ 4 | { 5 | "type": "text", 6 | "value": "a Unicode string" 7 | }, 8 | { 9 | "base64": "YSBieXRlIHN0cmluZw==", 10 | "type": "binary" 11 | }, 12 | { 13 | "decimal": "123", 14 | "type": "integer" 15 | }, 16 | { 17 | "decimal": "-456", 18 | "type": "integer" 19 | }, 20 | { 21 | "type": "boolean", 22 | "value": true 23 | }, 24 | { 25 | "type": "boolean", 26 | "value": false 27 | }, 28 | { 29 | "type": "null" 30 | }, 31 | { 32 | "pairs": [ 33 | { 34 | "key": { 35 | "type": "text", 36 | "value": "a" 37 | }, 38 | "value": { 39 | "type": "text", 40 | "value": "dict" 41 | } 42 | } 43 | ], 44 | "type": "dictionary" 45 | }, 46 | { 47 | "type": "list", 48 | "values": [ 49 | { 50 | "type": "text", 51 | "value": "a" 52 | }, 53 | { 54 | "type": "text", 55 | "value": "list" 56 | } 57 | ] 58 | } 59 | ] 60 | } -------------------------------------------------------------------------------- /testsuite/mixed-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "pairs": [ 3 | { 4 | "key": { 5 | "base64": "YQ==", 6 | "type": "binary" 7 | }, 8 | "value": { 9 | "decimal": "1", 10 | "type": "integer" 11 | } 12 | }, 13 | { 14 | "key": { 15 | "base64": "Yg==", 16 | "type": "binary" 17 | }, 18 | "value": { 19 | "decimal": "2", 20 | "type": "integer" 21 | } 22 | }, 23 | { 24 | "key": { 25 | "base64": "Yw==", 26 | "type": "binary" 27 | }, 28 | "value": { 29 | "decimal": "3", 30 | "type": "integer" 31 | } 32 | }, 33 | { 34 | "key": { 35 | "type": "text", 36 | "value": "a" 37 | }, 38 | "value": { 39 | "decimal": "1", 40 | "type": "integer" 41 | } 42 | }, 43 | { 44 | "key": { 45 | "type": "text", 46 | "value": "a\u0301" 47 | }, 48 | "value": { 49 | "decimal": "2", 50 | "type": "integer" 51 | } 52 | }, 53 | { 54 | "key": { 55 | "type": "text", 56 | "value": "b" 57 | }, 58 | "value": { 59 | "decimal": "3", 60 | "type": "integer" 61 | } 62 | }, 63 | { 64 | "key": { 65 | "type": "text", 66 | "value": "c" 67 | }, 68 | "value": { 69 | "decimal": "4", 70 | "type": "integer" 71 | } 72 | }, 73 | { 74 | "key": { 75 | "type": "text", 76 | "value": "\u00e1" 77 | }, 78 | "value": { 79 | "decimal": "5", 80 | "type": "integer" 81 | } 82 | } 83 | ], 84 | "type": "dictionary" 85 | } -------------------------------------------------------------------------------- /testsuite/list-of-dicts.repr.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "\ufeffname": "\ufeffbencode", 4 | "\ufeffhuman-readable": false, 5 | "\ufeffbyte-string": true, 6 | "\ufeffunicode-text": false, 7 | "\ufeffzero-copy": false, 8 | "\ufeffschema": false, 9 | "\ufeffnormalization": true 10 | }, 11 | { 12 | "\ufeffname": "\ufeffbencodex", 13 | "\ufeffhuman-readable": false, 14 | "\ufeffbyte-string": true, 15 | "\ufeffunicode-text": true, 16 | "\ufeffzero-copy": false, 17 | "\ufeffschema": false, 18 | "\ufeffnormalization": true 19 | }, 20 | { 21 | "\ufeffname": "\ufeffcapnp", 22 | "\ufeffhuman-readable": false, 23 | "\ufeffbyte-string": true, 24 | "\ufeffunicode-text": true, 25 | "\ufeffzero-copy": true, 26 | "\ufeffschema": true, 27 | "\ufeffnormalization": false 28 | }, 29 | { 30 | "\ufeffname": "\ufeffjson", 31 | "\ufeffhuman-readable": true, 32 | "\ufeffbyte-string": false, 33 | "\ufeffunicode-text": true, 34 | "\ufeffzero-copy": false, 35 | "\ufeffschema": false, 36 | "\ufeffnormalization": false 37 | }, 38 | { 39 | "\ufeffname": "\ufeffmsgpack", 40 | "\ufeffhuman-readable": false, 41 | "\ufeffbyte-string": true, 42 | "\ufeffunicode-text": true, 43 | "\ufeffzero-copy": false, 44 | "\ufeffschema": false, 45 | "\ufeffnormalization": false 46 | }, 47 | { 48 | "\ufeffname": "\ufeffthrift", 49 | "\ufeffhuman-readable": false, 50 | "\ufeffbyte-string": true, 51 | "\ufeffunicode-text": true, 52 | "\ufeffzero-copy": false, 53 | "\ufeffschema": true, 54 | "\ufeffnormalization": false 55 | }, 56 | { 57 | "\ufeffname": "\ufeffyaml", 58 | "\ufeffhuman-readable": true, 59 | "\ufeffbyte-string": true, 60 | "\ufeffunicode-text": true, 61 | "\ufeffzero-copy": false, 62 | "\ufeffnormalization": false 63 | } 64 | ] 65 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Bencodex Changelog 2 | ================== 3 | 4 | This repository consists of the specification document and the test suite. 5 | The tests purpose to be confident that an implementation accurately 6 | the specification. Therefore, although a change of the specification implies 7 | a change of the document and test suite, a change of the document or test suite 8 | does not necessarily imply a change of the specification. 9 | 10 | To distinguish these two kinds of changes, this repository follows 11 | the versioning scheme that consists of a *major version* and a *minor version*. 12 | For example, *1.0* represents a major version 1 and a minor version 0. It is 13 | not a real number, but two positive integers, that is, *1.23* is later than 14 | *1.3*. 15 | 16 | If a change breaks compatibility about any cases (even if it's a corner case) 17 | from accurate implementations that follow the specification right before 18 | that change, it increases the major version. 19 | 20 | If a change fixes only some typo on the document or adds more test cases, 21 | it does not increase the major version but only the minor version. 22 | 23 | In order to an implementation indicates the specification version it follows, 24 | it can mention only a major version. 25 | 26 | 27 | Version 1.4 28 | ----------- 29 | 30 | To be released. 31 | 32 | 33 | Version 1.3 34 | ----------- 35 | 36 | Published on September 9, 2022. 37 | 38 | - Introduces [Bencodex JSON Representation](./JSON.md) specification. 39 | Conformance to this specification is optional and it is not a 40 | required part of Bencodex. 41 | 42 | 43 | Version 1.2 44 | ----------- 45 | 46 | Published on March 31, 2019. 47 | 48 | - Added a *.gitattributes* file to *testsuite/* directory so that 49 | *.dat* files are not affected by Git's `core.autocrlf` settings 50 | on Windows. 51 | 52 | 53 | Version 1.1 54 | ----------- 55 | 56 | Published on January 4, 2019. 57 | 58 | - Since YAML is a relatively large and complex specification, only few 59 | implementations can deal with [`!!binary`][yaml-binary] tag which is many 60 | YAML files in the Bencodex testsuite uses. To consider such practical 61 | reality, JSON files that renders a AST of the corresponding Bencodex value 62 | were added as an alternative to YAML files. 63 | 64 | - Since the ordering rule of Unicode keys in a dictionary might be confusing, 65 | the specifcation became to elaborate about this, a test to check this was 66 | also added. [[#1]] 67 | 68 | - It now has a list of implementations. It is placed in 69 | [*LIBRARIES.tsv*](./LIBRARIES.tsv) file. 70 | 71 | [yaml-binary]: http://yaml.org/type/binary.html 72 | [#1]: https://github.com/planetarium/bencodex/issues/1 73 | 74 | 75 | Version 1.0 76 | ----------- 77 | 78 | Published on November 1, 2018. 79 | 80 | This extends the below things on the existing [Bencoding]: 81 | 82 | - null 83 | - Boolean values 84 | - Unicode strings besides byte strings 85 | - Dictionaries with both byte and Unicode string keys 86 | 87 | [Bencoding]: http://www.bittorrent.org/beps/bep_0003.html#bencoding 88 | -------------------------------------------------------------------------------- /utils/testsuite-schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "https://github.com/planetarium/bencodex/tree/1.2/testsuite", 4 | "title": "Bencodex test suite semantics representation", 5 | "description": "This forms .json files in the Bencodex test suite.", 6 | "definitions": { 7 | "value": { 8 | "oneOf": [ 9 | {"$ref": "#/definitions/null"}, 10 | {"$ref": "#/definitions/boolean"}, 11 | {"$ref": "#/definitions/integer"}, 12 | {"$ref": "#/definitions/binary"}, 13 | {"$ref": "#/definitions/text"}, 14 | {"$ref": "#/definitions/list"}, 15 | {"$ref": "#/definitions/dictionary"} 16 | ] 17 | }, 18 | 19 | "null": { 20 | "description": "Represents a Bencodex null value.", 21 | "type": "object", 22 | "properties": { 23 | "type": {"const": "null"} 24 | }, 25 | "required": ["type"] 26 | }, 27 | 28 | "boolean": { 29 | "description": "Represents a Bencodex boolean value.", 30 | "type": "object", 31 | "properties": { 32 | "type": {"const": "boolean"}, 33 | "value": {"type": "boolean"} 34 | }, 35 | "required": ["type", "value"] 36 | }, 37 | 38 | "integer": { 39 | "description": "Represents a Bencodex integer, in a decimal string.", 40 | "type": "object", 41 | "properties": { 42 | "type": {"const": "integer"}, 43 | "decimal": { 44 | "type": "string", 45 | "pattern": "^-?[1-9][0-9]*$|^0$" 46 | } 47 | }, 48 | "required": ["type", "decimal"] 49 | }, 50 | 51 | "binary": { 52 | "description": "Represents a Bencodex byte string (i.e., binary data).", 53 | "type": "object", 54 | "properties": { 55 | "type": {"const": "binary"}, 56 | "base64": { 57 | "description": "Binary data encoded in Base64.", 58 | "type": "string", 59 | "pattern": 60 | "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$" 61 | } 62 | }, 63 | "required": ["type", "base64"] 64 | }, 65 | 66 | "text": { 67 | "description": "Represents a Bencodex Unicode text.", 68 | "type": "object", 69 | "properties": { 70 | "type": {"const": "text"}, 71 | "value": {"type": "string"} 72 | }, 73 | "required": ["type", "value"] 74 | }, 75 | 76 | "list": { 77 | "description": "Represents a Bencodex list.", 78 | "type": "object", 79 | "properties": { 80 | "type": {"const": "list"}, 81 | "values": { 82 | "description": 83 | "Values a list contains. Each value is an also Bencodex value.", 84 | "type": "array", 85 | "items": {"$ref": "#/definitions/value"} 86 | } 87 | }, 88 | "required": ["type", "values"] 89 | }, 90 | 91 | "dictionary": { 92 | "description": "Represents a Bencodex dictionary.", 93 | "type": "object", 94 | "properties": { 95 | "type": {"const": "dictionary"}, 96 | "pairs": { 97 | "description": 98 | "Key-value pairs that a dictionary contains.", 99 | "type": "array", 100 | "items": { 101 | "type": "object", 102 | "properties": { 103 | "key": { 104 | "description": 105 | "A dictionary key is a binary or a Unicode text.", 106 | "oneOf": [ 107 | {"$ref": "#/definitions/binary"}, 108 | {"$ref": "#/definitions/text"} 109 | ] 110 | }, 111 | "value": { 112 | "description": "A dictionary value is an any Bencodex value.", 113 | "$ref": "#/definitions/value" 114 | } 115 | }, 116 | "required": ["key", "value"] 117 | } 118 | } 119 | }, 120 | "required": ["type", "pairs"] 121 | } 122 | }, 123 | "$ref": "#/definitions/value" 124 | } 125 | -------------------------------------------------------------------------------- /JSON.md: -------------------------------------------------------------------------------- 1 | Bencodex JSON Representation 2 | =========================== 3 | 4 | Introduction 5 | ------------ 6 | 7 | Bencodex JSON Representation is a standard way to represent a given Bencodex 8 | value to a valid JSON value to be interchanged. 9 | 10 | This specification defines a standard way to represent a Bencodex value to (a 11 | subset of) JSON and how to decode/encode them. 12 | 13 | Note that this specification is a separate specification from Bencodex itself. 14 | Bencodex implementations are not required to implement this specification. 15 | 16 | ### Conventions Used in This Document 17 | 18 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", 19 | "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this 20 | document are to be interpreted as described in BCP 14[^RFC-2119] when, and only 21 | when, they appear in all capitals, as shown here. 22 | 23 | Prefixes 24 | -------- 25 | 26 | Bencodex JSON Representation defines a set of prefixes that **SHALL** be used 27 | for JSON[^JSON] string value to denote a corresponding Bencodex type. The 28 | defined prefixes are following: 29 | 30 | - `0x` denotes a hexadecimal byte string. The rest of the string **SHOULD** be 31 | parsed as a byte-string encoded in hexadecimal. 32 | - The hexadecimal byte string **SHOULD NOT** contain any upper letters. 33 | - The casing of the value **SHOULD** be ignored. 34 | - `U+FEFF BYTE ORDER MARK` denotes a Unicode string. The rest of the string 35 | **MUST** be parsed as a Unicode string. 36 | - `b64:` denotes a base64-encoded byte string. The rest of the string **MUST** 37 | be parsed as a byte string encoded in base64 as defined in 38 | [RFC 4648](https://www.rfc-editor.org/rfc/rfc4648.html)[^RFC-4648]. 39 | - In case no prefixes mentioned above are found, the value of the string 40 | **MUST** be parsed as an integer. 41 | - Any non-digit character in the string **SHOULD** result in an error, except 42 | minus sign at the start of the string which denotes negative integer. 43 | 44 | If the JSON string value contains escaped Unicode code points (denoted using 45 | `\u`) they **SHOULD** be interpreted before identifying a prefix. 46 | 47 | Encoding to JSON 48 | ---------------- 49 | 50 | When encoding a given value to JSON, the value **MUST** be encoded as specified 51 | in the following table. 52 | 53 | | Bencodex type | JSON type | 54 | | ------------------ | ------------------------- | 55 | | Null (`n`) | Null (`null`) | 56 | | Boolean (`t`, `f`) | Boolean (`true`, `false`) | 57 | | Byte string | See [Prefixes](#prefixes) | 58 | | Unicode string | See [Prefixes](#prefixes) | 59 | | Integer (`i`–`e`) | See [Prefixes](#prefixes) | 60 | | Lists (`l`–`e`) | Array (`[]`) | 61 | | Dictionary | Object (`{}`) | 62 | 63 | When encoding a dictionary value to a JSON object, the resulting object 64 | **SHOULD** have the same key order as Bencodex specifies. 65 | 66 | When encoding a byte string, an implementation **MAY** choose which encoding to 67 | use depending on the length of the value. 68 | 69 | Decoding to Bencodex 70 | -------------------- 71 | 72 | When decoding a given JSON to Bencodex, the duplicate keys of the JSON object 73 | **SHOULD** result in failure of the decoding. In case if this is not desirable, 74 | the implementation **MAY** use the lexically last key-value pair as specified 75 | in [Section 15.12](https://www.ecma-international.org/ecma-262/5.1/ECMA-262.pdf) 76 | ("The JSON Object") of ECMAScript 5.1[^ECMAScript]. 77 | 78 | The ordering of object keys **MUST** be ignored and reordered as Bencodex 79 | specifies. 80 | 81 | [^JSON]: Ecma International, "ECMAScript Language Specification, 82 | 5.1 Edition", ECMA Standard 262, June 2011, 83 | . 84 | [^RFC-2119]: Bradner, S., "Key words for use in RFCs to Indicate 85 | Requirement Levels", BCP 14, RFC 2119, 86 | DOI 10.17487/RFC2119, March 1997, 87 | . 88 | [^RFC-4648]: Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", 89 | RFC 4648, DOI 10.17487/RFC4648, October 2006, 90 | . 91 | [^ECMAScript]: Ecma International, "The JSON Data Interchange Format", 92 | Standard ECMA-404, 93 | . 94 | -------------------------------------------------------------------------------- /testsuite/nested-dict.json: -------------------------------------------------------------------------------- 1 | { 2 | "pairs": [ 3 | { 4 | "key": { 5 | "type": "text", 6 | "value": "EPO" 7 | }, 8 | "value": { 9 | "pairs": [ 10 | { 11 | "key": { 12 | "type": "text", 13 | "value": "albums" 14 | }, 15 | "value": { 16 | "type": "list", 17 | "values": [ 18 | { 19 | "type": "text", 20 | "value": "Down Town" 21 | }, 22 | { 23 | "type": "text", 24 | "value": "Goodies" 25 | }, 26 | { 27 | "type": "text", 28 | "value": "Vitamin EPO" 29 | }, 30 | { 31 | "type": "text", 32 | "value": "Hi-touch Hi-tech" 33 | } 34 | ] 35 | } 36 | }, 37 | { 38 | "key": { 39 | "type": "text", 40 | "value": "debut" 41 | }, 42 | "value": { 43 | "decimal": "1980", 44 | "type": "integer" 45 | } 46 | }, 47 | { 48 | "key": { 49 | "type": "text", 50 | "value": "name" 51 | }, 52 | "value": { 53 | "type": "text", 54 | "value": "\u5bae\u5ddd \u69ae\u5b50" 55 | } 56 | } 57 | ], 58 | "type": "dictionary" 59 | } 60 | }, 61 | { 62 | "key": { 63 | "type": "text", 64 | "value": "\u5c71\u4e0b\u9054\u90ce" 65 | }, 66 | "value": { 67 | "pairs": [ 68 | { 69 | "key": { 70 | "type": "text", 71 | "value": "albums" 72 | }, 73 | "value": { 74 | "type": "list", 75 | "values": [ 76 | { 77 | "type": "text", 78 | "value": "Spacy" 79 | }, 80 | { 81 | "type": "text", 82 | "value": "Ride On Time" 83 | }, 84 | { 85 | "type": "text", 86 | "value": "For You" 87 | }, 88 | { 89 | "type": "text", 90 | "value": "Melodies" 91 | } 92 | ] 93 | } 94 | }, 95 | { 96 | "key": { 97 | "type": "text", 98 | "value": "debut" 99 | }, 100 | "value": { 101 | "decimal": "1973", 102 | "type": "integer" 103 | } 104 | } 105 | ], 106 | "type": "dictionary" 107 | } 108 | }, 109 | { 110 | "key": { 111 | "type": "text", 112 | "value": "\u674f\u91cc" 113 | }, 114 | "value": { 115 | "pairs": [ 116 | { 117 | "key": { 118 | "type": "text", 119 | "value": "albums" 120 | }, 121 | "value": { 122 | "type": "list", 123 | "values": [ 124 | { 125 | "type": "text", 126 | "value": "Timely!!" 127 | }, 128 | { 129 | "type": "text", 130 | "value": "Coool" 131 | }, 132 | { 133 | "type": "text", 134 | "value": "Wave" 135 | } 136 | ] 137 | } 138 | }, 139 | { 140 | "key": { 141 | "type": "text", 142 | "value": "debut" 143 | }, 144 | "value": { 145 | "decimal": "1978", 146 | "type": "integer" 147 | } 148 | }, 149 | { 150 | "key": { 151 | "type": "text", 152 | "value": "name" 153 | }, 154 | "value": { 155 | "type": "text", 156 | "value": "\u5ddd\u5d8b \u6804\u5b50" 157 | } 158 | } 159 | ], 160 | "type": "dictionary" 161 | } 162 | }, 163 | { 164 | "key": { 165 | "type": "text", 166 | "value": "\u89d2\u677e\u654f\u751f" 167 | }, 168 | "value": { 169 | "pairs": [ 170 | { 171 | "key": { 172 | "type": "text", 173 | "value": "albums" 174 | }, 175 | "value": { 176 | "type": "list", 177 | "values": [ 178 | { 179 | "type": "text", 180 | "value": "Sea Breeze" 181 | }, 182 | { 183 | "type": "text", 184 | "value": "On The City Shore" 185 | }, 186 | { 187 | "type": "text", 188 | "value": "After 5 Clash" 189 | } 190 | ] 191 | } 192 | }, 193 | { 194 | "key": { 195 | "type": "text", 196 | "value": "debut" 197 | }, 198 | "value": { 199 | "decimal": "1981", 200 | "type": "integer" 201 | } 202 | }, 203 | { 204 | "key": { 205 | "type": "text", 206 | "value": "name" 207 | }, 208 | "value": { 209 | "type": "text", 210 | "value": "\u89d2\u677e \u654f\u751f" 211 | } 212 | } 213 | ], 214 | "type": "dictionary" 215 | } 216 | } 217 | ], 218 | "type": "dictionary" 219 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bencodex: Bencoding Extended 2 | ============================ 3 | 4 | *The version of this document is **1.3**. See also [changelog].* 5 | 6 | *There is a list of implementations. See also [LIBRARIES.tsv](./LIBRARIES.tsv) 7 | file.* 8 | 9 | Bencodex is a serialization format that extends BitTorrent's [Bencoding]. 10 | Since it is a superset of Bencoding, every valid Bencoding representation is 11 | a valid Bencodex representation of the same meaning (i.e., represents the same 12 | value). Bencodex adds the below data types to Bencoding: 13 | 14 | - null 15 | - Boolean values 16 | - Unicode strings besides byte strings 17 | - Dictionaries with both byte and Unicode string keys 18 | 19 | [Bencoding]: http://www.bittorrent.org/beps/bep_0003.html#bencoding 20 | [changelog]: ./CHANGES.md 21 | 22 | 23 | Why not *[insert your favorite format here]* 24 | -------------------------------------------- 25 | 26 | The unique feature of Bencoding is forced normalization. 27 | According to Wikipedia's [Bencode] page: 28 | 29 | > For each possible (complex) value, there is only a single valid bencoding; 30 | > i.e. there is a [bijection] between values and their encodings. 31 | > This has the advantage that applications may compare bencoded values by 32 | > comparing their encoded forms, eliminating the need to decode the values. 33 | 34 | This makes things really simple when an application needs to determine 35 | if encoded values are the same, in particular, with cryptographic hash or 36 | digital signatures. 37 | 38 | There have been countless improvements in data serialization like 39 | rich data types, human readability, compact binary representation, 40 | zero-copy serialization, and even streaming, but canonical representation 41 | is still not well counted. 42 | 43 | Bencodex actually does not aim high in ambition; it purposes to merely 44 | leverage Bencoding's good things with average-level data types of modern 45 | serialization formats. 46 | 47 | [Bencode]: https://en.wikipedia.org/wiki/Bencode#Features_&_drawbacks 48 | [bijection]: https://en.wikipedia.org/wiki/Bijection 49 | 50 | 51 | Encoding 52 | -------- 53 | 54 | Note that notations for the semantics (i.e., the values that encodings 55 | represent) use Python's literals. 56 | 57 | - Null is represented by `n` (`6e`). 58 | 59 | - Boolean true is represented by `t` (`74`), 60 | and false is represented by `f` (`66`). 61 | 62 | - Byte strings are length-prefixed base 10 followed by a colon and 63 | the byte string. 64 | 65 | For example, `4:spam` (`34 3a 73 70 61 6d`) corresponds to `b"spam"`. 66 | 67 | - Unicode strings are represented by `u` followed by UTF-8 byte length 68 | base 10 and UTF-8 encoding of the Unicode string. 69 | 70 | For example, `u6:단팥` (`75 36 3a eb 8b a8 ed 8c a5`) corresponds to 71 | `u"\ub2e8\ud325"`. 72 | 73 | - Integers are represented by an `i` followed by the number in base 10 74 | followed by an `e`. 75 | 76 | For example, `i3e` (`69 33 65`) corresponds to `3`, 77 | and `i-3e` (`69 2d 33 65`) corresponds to `-3`. 78 | 79 | Integers have no size limitation. 80 | 81 | `i-0e` (`69 2d 30 65`) is invalid. All encodings with a leading zero, 82 | such as `i03e` (`69 30 33 65`), are invalid, other than `i0e` (`69 30 65`), 83 | which of course corresponds to `0`. 84 | 85 | - Lists are encoded as an `l` followed by their elements (also represented in 86 | Bencodex) followed by an `e`. 87 | 88 | For example, `l4:spamu4:eggse` (`6c 34 3a 73 70 61 6d 75 34 3a 65 67 67 73 89 | 65`) corresponds to `[b"spam", u"eggs"]`. 90 | 91 | - Dictionaries are encoded as a `d` followed by a list of alternating keys 92 | and their corresponding values followed by an `e`. 93 | 94 | For example, `d3:cowu3:moou4:spam4:eggse` (`64 33 3a 63 6f 77 75 33 3a 6d 95 | 6f 6f 75 34 3a 73 70 61 6d 34 3a 65 67 67 73 65`) corresponds to 96 | `{b"cow": u"moo", u"spam": b"eggs"}`, and `du4:spaml1:au1:bee` (`64 75 34 97 | 3a 73 70 61 6d 6c 31 3a 61 75 31 3a 62 65 65`) corresponds to 98 | `{u"spam": [b"a", u"b"]}`. 99 | 100 | Keys must be Unicode or byte strings, and appear in the certain order: 101 | 102 | - Unicode strings do not appear earlier than byte strings. 103 | 104 | - Byte strings are sorted as raw strings, not alphanumerics. 105 | 106 | - Unicode strings are sorted as their UTF-8 *byte* representations, 107 | *not* any collation order or chart order listed by Unicode. 108 | 109 | For example, `b` (`62`) should be followed by `á` (`C3 A1`), 110 | because the byte `62` is less than the byte `C3`. 111 | 112 | `du1:k1:v1:k1:ve` (`64 75 31 3a 6b 31 3a 76 31 3a 6b 31 3a 76 65`) is 113 | invalid because `u1:k` appear earlier than `1:k`. 114 | 115 | 116 | Test suite 117 | ---------- 118 | 119 | The *testsuite/* directory contains a set of Bencodex tests. Every test case 120 | is a triple of *.dat* which is an arbitrary Bencodex data, a *.yaml* which 121 | is its corresponding value in YAML, and a *.json* which is an alternative to 122 | YAML and renders an AST of the Bencodex value. 123 | 124 | For example, *list.dat* contains the below Bencodex data: 125 | 126 | ~~~~ bencodex 127 | lu16:a Unicode string13:a byte stringi123ei-456etfndu1:au4:dictelu1:au4:listee 128 | ~~~~ 129 | 130 | which encodes the value corresponding to *list.yaml*, that is: 131 | 132 | ~~~~ yaml 133 | - a Unicode string 134 | - !!binary "YSBieXRlIHN0cmluZw==" # b"a byte string" 135 | - 123 136 | - -456 137 | - true 138 | - false 139 | - null 140 | - a: dict 141 | - [a, list] 142 | ~~~~ 143 | 144 | Or, as an alternative there's *list.json* which renders an AST of the value 145 | structure: 146 | 147 | ~~~~ json 148 | { 149 | "type": "list", 150 | "values": [ 151 | { 152 | "type": "text", 153 | "value": "a Unicode string" 154 | }, 155 | { 156 | "base64": "YSBieXRlIHN0cmluZw==", 157 | "type": "binary" 158 | }, 159 | { 160 | "decimal": "123", 161 | "type": "integer" 162 | }, 163 | { 164 | "decimal": "-456", 165 | "type": "integer" 166 | }, 167 | { 168 | "type": "boolean", 169 | "value": true 170 | }, 171 | { 172 | "type": "boolean", 173 | "value": false 174 | }, 175 | { 176 | "type": "null" 177 | }, 178 | { 179 | "pairs": [ 180 | { 181 | "key": { 182 | "type": "text", 183 | "value": "a" 184 | }, 185 | "value": { 186 | "type": "text", 187 | "value": "dict" 188 | } 189 | } 190 | ], 191 | "type": "dictionary" 192 | }, 193 | { 194 | "type": "list", 195 | "values": [ 196 | { 197 | "type": "text", 198 | "value": "a" 199 | }, 200 | { 201 | "type": "text", 202 | "value": "list" 203 | } 204 | ] 205 | } 206 | ] 207 | } 208 | ~~~~ 209 | 210 | Note that the schema of *.json* files is formally described in [JSON Schema]. 211 | see also [utils/testsuite-schema.json](./utils/testsuite-schema.json). 212 | 213 | An implementation should satisfy the below rules: 214 | 215 | - Bytes that an encoder builds from a YAML/JSON content should be exactly 216 | same to the contents of a *.dat* file that corresponds to 217 | the *.yaml*/*json* file. 218 | 219 | - A content a decoder read from a *.dat* file should be equivalent to 220 | the content of a *.yaml*/*.json* file that corresponds to the *.dat* file. 221 | 222 | [JSON Schema]: https://json-schema.org/ 223 | 224 | 225 | ---- 226 | 227 | This document (*README.md*) and every content in this repository including 228 | the test suite (*testsuite/*) are in the public domain. 229 | -------------------------------------------------------------------------------- /testsuite/list-of-dicts.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "list", 3 | "values": [ 4 | { 5 | "pairs": [ 6 | { 7 | "key": { 8 | "type": "text", 9 | "value": "byte-string" 10 | }, 11 | "value": { 12 | "type": "boolean", 13 | "value": true 14 | } 15 | }, 16 | { 17 | "key": { 18 | "type": "text", 19 | "value": "human-readable" 20 | }, 21 | "value": { 22 | "type": "boolean", 23 | "value": false 24 | } 25 | }, 26 | { 27 | "key": { 28 | "type": "text", 29 | "value": "name" 30 | }, 31 | "value": { 32 | "type": "text", 33 | "value": "bencode" 34 | } 35 | }, 36 | { 37 | "key": { 38 | "type": "text", 39 | "value": "normalization" 40 | }, 41 | "value": { 42 | "type": "boolean", 43 | "value": true 44 | } 45 | }, 46 | { 47 | "key": { 48 | "type": "text", 49 | "value": "schema" 50 | }, 51 | "value": { 52 | "type": "boolean", 53 | "value": false 54 | } 55 | }, 56 | { 57 | "key": { 58 | "type": "text", 59 | "value": "unicode-text" 60 | }, 61 | "value": { 62 | "type": "boolean", 63 | "value": false 64 | } 65 | }, 66 | { 67 | "key": { 68 | "type": "text", 69 | "value": "zero-copy" 70 | }, 71 | "value": { 72 | "type": "boolean", 73 | "value": false 74 | } 75 | } 76 | ], 77 | "type": "dictionary" 78 | }, 79 | { 80 | "pairs": [ 81 | { 82 | "key": { 83 | "type": "text", 84 | "value": "byte-string" 85 | }, 86 | "value": { 87 | "type": "boolean", 88 | "value": true 89 | } 90 | }, 91 | { 92 | "key": { 93 | "type": "text", 94 | "value": "human-readable" 95 | }, 96 | "value": { 97 | "type": "boolean", 98 | "value": false 99 | } 100 | }, 101 | { 102 | "key": { 103 | "type": "text", 104 | "value": "name" 105 | }, 106 | "value": { 107 | "type": "text", 108 | "value": "bencodex" 109 | } 110 | }, 111 | { 112 | "key": { 113 | "type": "text", 114 | "value": "normalization" 115 | }, 116 | "value": { 117 | "type": "boolean", 118 | "value": true 119 | } 120 | }, 121 | { 122 | "key": { 123 | "type": "text", 124 | "value": "schema" 125 | }, 126 | "value": { 127 | "type": "boolean", 128 | "value": false 129 | } 130 | }, 131 | { 132 | "key": { 133 | "type": "text", 134 | "value": "unicode-text" 135 | }, 136 | "value": { 137 | "type": "boolean", 138 | "value": true 139 | } 140 | }, 141 | { 142 | "key": { 143 | "type": "text", 144 | "value": "zero-copy" 145 | }, 146 | "value": { 147 | "type": "boolean", 148 | "value": false 149 | } 150 | } 151 | ], 152 | "type": "dictionary" 153 | }, 154 | { 155 | "pairs": [ 156 | { 157 | "key": { 158 | "type": "text", 159 | "value": "byte-string" 160 | }, 161 | "value": { 162 | "type": "boolean", 163 | "value": true 164 | } 165 | }, 166 | { 167 | "key": { 168 | "type": "text", 169 | "value": "human-readable" 170 | }, 171 | "value": { 172 | "type": "boolean", 173 | "value": false 174 | } 175 | }, 176 | { 177 | "key": { 178 | "type": "text", 179 | "value": "name" 180 | }, 181 | "value": { 182 | "type": "text", 183 | "value": "capnp" 184 | } 185 | }, 186 | { 187 | "key": { 188 | "type": "text", 189 | "value": "normalization" 190 | }, 191 | "value": { 192 | "type": "boolean", 193 | "value": false 194 | } 195 | }, 196 | { 197 | "key": { 198 | "type": "text", 199 | "value": "schema" 200 | }, 201 | "value": { 202 | "type": "boolean", 203 | "value": true 204 | } 205 | }, 206 | { 207 | "key": { 208 | "type": "text", 209 | "value": "unicode-text" 210 | }, 211 | "value": { 212 | "type": "boolean", 213 | "value": true 214 | } 215 | }, 216 | { 217 | "key": { 218 | "type": "text", 219 | "value": "zero-copy" 220 | }, 221 | "value": { 222 | "type": "boolean", 223 | "value": true 224 | } 225 | } 226 | ], 227 | "type": "dictionary" 228 | }, 229 | { 230 | "pairs": [ 231 | { 232 | "key": { 233 | "type": "text", 234 | "value": "byte-string" 235 | }, 236 | "value": { 237 | "type": "boolean", 238 | "value": false 239 | } 240 | }, 241 | { 242 | "key": { 243 | "type": "text", 244 | "value": "human-readable" 245 | }, 246 | "value": { 247 | "type": "boolean", 248 | "value": true 249 | } 250 | }, 251 | { 252 | "key": { 253 | "type": "text", 254 | "value": "name" 255 | }, 256 | "value": { 257 | "type": "text", 258 | "value": "json" 259 | } 260 | }, 261 | { 262 | "key": { 263 | "type": "text", 264 | "value": "normalization" 265 | }, 266 | "value": { 267 | "type": "boolean", 268 | "value": false 269 | } 270 | }, 271 | { 272 | "key": { 273 | "type": "text", 274 | "value": "schema" 275 | }, 276 | "value": { 277 | "type": "boolean", 278 | "value": false 279 | } 280 | }, 281 | { 282 | "key": { 283 | "type": "text", 284 | "value": "unicode-text" 285 | }, 286 | "value": { 287 | "type": "boolean", 288 | "value": true 289 | } 290 | }, 291 | { 292 | "key": { 293 | "type": "text", 294 | "value": "zero-copy" 295 | }, 296 | "value": { 297 | "type": "boolean", 298 | "value": false 299 | } 300 | } 301 | ], 302 | "type": "dictionary" 303 | }, 304 | { 305 | "pairs": [ 306 | { 307 | "key": { 308 | "type": "text", 309 | "value": "byte-string" 310 | }, 311 | "value": { 312 | "type": "boolean", 313 | "value": true 314 | } 315 | }, 316 | { 317 | "key": { 318 | "type": "text", 319 | "value": "human-readable" 320 | }, 321 | "value": { 322 | "type": "boolean", 323 | "value": false 324 | } 325 | }, 326 | { 327 | "key": { 328 | "type": "text", 329 | "value": "name" 330 | }, 331 | "value": { 332 | "type": "text", 333 | "value": "msgpack" 334 | } 335 | }, 336 | { 337 | "key": { 338 | "type": "text", 339 | "value": "normalization" 340 | }, 341 | "value": { 342 | "type": "boolean", 343 | "value": false 344 | } 345 | }, 346 | { 347 | "key": { 348 | "type": "text", 349 | "value": "schema" 350 | }, 351 | "value": { 352 | "type": "boolean", 353 | "value": false 354 | } 355 | }, 356 | { 357 | "key": { 358 | "type": "text", 359 | "value": "unicode-text" 360 | }, 361 | "value": { 362 | "type": "boolean", 363 | "value": true 364 | } 365 | }, 366 | { 367 | "key": { 368 | "type": "text", 369 | "value": "zero-copy" 370 | }, 371 | "value": { 372 | "type": "boolean", 373 | "value": false 374 | } 375 | } 376 | ], 377 | "type": "dictionary" 378 | }, 379 | { 380 | "pairs": [ 381 | { 382 | "key": { 383 | "type": "text", 384 | "value": "byte-string" 385 | }, 386 | "value": { 387 | "type": "boolean", 388 | "value": true 389 | } 390 | }, 391 | { 392 | "key": { 393 | "type": "text", 394 | "value": "human-readable" 395 | }, 396 | "value": { 397 | "type": "boolean", 398 | "value": false 399 | } 400 | }, 401 | { 402 | "key": { 403 | "type": "text", 404 | "value": "name" 405 | }, 406 | "value": { 407 | "type": "text", 408 | "value": "thrift" 409 | } 410 | }, 411 | { 412 | "key": { 413 | "type": "text", 414 | "value": "normalization" 415 | }, 416 | "value": { 417 | "type": "boolean", 418 | "value": false 419 | } 420 | }, 421 | { 422 | "key": { 423 | "type": "text", 424 | "value": "schema" 425 | }, 426 | "value": { 427 | "type": "boolean", 428 | "value": true 429 | } 430 | }, 431 | { 432 | "key": { 433 | "type": "text", 434 | "value": "unicode-text" 435 | }, 436 | "value": { 437 | "type": "boolean", 438 | "value": true 439 | } 440 | }, 441 | { 442 | "key": { 443 | "type": "text", 444 | "value": "zero-copy" 445 | }, 446 | "value": { 447 | "type": "boolean", 448 | "value": false 449 | } 450 | } 451 | ], 452 | "type": "dictionary" 453 | }, 454 | { 455 | "pairs": [ 456 | { 457 | "key": { 458 | "type": "text", 459 | "value": "byte-string" 460 | }, 461 | "value": { 462 | "type": "boolean", 463 | "value": true 464 | } 465 | }, 466 | { 467 | "key": { 468 | "type": "text", 469 | "value": "human-readable" 470 | }, 471 | "value": { 472 | "type": "boolean", 473 | "value": true 474 | } 475 | }, 476 | { 477 | "key": { 478 | "type": "text", 479 | "value": "name" 480 | }, 481 | "value": { 482 | "type": "text", 483 | "value": "yaml" 484 | } 485 | }, 486 | { 487 | "key": { 488 | "type": "text", 489 | "value": "normalization" 490 | }, 491 | "value": { 492 | "type": "boolean", 493 | "value": false 494 | } 495 | }, 496 | { 497 | "key": { 498 | "type": "text", 499 | "value": "unicode-text" 500 | }, 501 | "value": { 502 | "type": "boolean", 503 | "value": true 504 | } 505 | }, 506 | { 507 | "key": { 508 | "type": "text", 509 | "value": "zero-copy" 510 | }, 511 | "value": { 512 | "type": "boolean", 513 | "value": false 514 | } 515 | } 516 | ], 517 | "type": "dictionary" 518 | } 519 | ] 520 | } --------------------------------------------------------------------------------