├── README.md ├── test └── cid.js ├── package.json ├── LICENSE ├── .gitignore └── cid.js /README.md: -------------------------------------------------------------------------------- 1 | # caper 2 | Content-Addressed Protocol for Efficient Retrieval 3 | -------------------------------------------------------------------------------- /test/cid.js: -------------------------------------------------------------------------------- 1 | 2 | import { equal } from 'node:assert'; 3 | import { parse, cidContentTypes } from "../cid.js"; 4 | 5 | const wtf = 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi'; 6 | // base32 - cidv1 - dag-pb - (sha2-256 : 256 : C3C4733EC8AFFD06CF9E9FF50FFC6BCD2EC85A6170004BB709669C31DE94391A) 7 | 8 | describe('CID parsing', () => { 9 | it('parses a valid CID', () => { 10 | const { version, contentType, multihash } = parse(wtf); 11 | equal(version, 1, 'version must be 1'); 12 | equal(contentType, cidContentTypes.dagPB, 'content type must be dag-pb'); 13 | // XXX also needs the multihash parsed 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caper", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "description": "Content-Addressed Protocol for Efficient Retrieval", 6 | "author": "Robin Berjon ", 7 | "license": "MIT", 8 | "scripts": { 9 | "test": "mocha" 10 | }, 11 | "bin": {}, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/darobin/caper.git" 15 | }, 16 | "eslintConfig": { 17 | "env": { 18 | "browser": true, 19 | "mocha": true, 20 | "es2021": true 21 | }, 22 | "extends": "eslint:recommended", 23 | "overrides": [], 24 | "parserOptions": { 25 | "ecmaVersion": "latest", 26 | "sourceType": "module" 27 | }, 28 | "rules": {} 29 | }, 30 | "devDependencies": { 31 | "eslint": "^8.26.0" 32 | }, 33 | "dependencies": { 34 | "mocha": "^10.2.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Robin Berjon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | rapide 106 | -------------------------------------------------------------------------------- /cid.js: -------------------------------------------------------------------------------- 1 | 2 | const b32codes = new Map('abcdefghijklmnopqrstuvwxyz234567'.split('').map((k, i) => [k, i])); 3 | console.warn(b32codes); 4 | export const cidContentTypes = { 5 | raw: 0x55, 6 | dagCBOR: 0x71, 7 | dagPB: 0x70, 8 | car: 0x0202, 9 | dagJSON: 0x0129, 10 | }; 11 | const supportedContentTypes = new Set(Object.values(cidContentTypes)); 12 | 13 | const MSB = 0x80; 14 | const REST = 0x7F; 15 | 16 | // Gets a CID as string or Uint8Array. 17 | // Returns a structure with all components. 18 | // Throws if we don't support it. 19 | // (Code heavily inspired by js-multiformats.) 20 | export function parse (cid) { 21 | // If we get a string, parse it into Uint8Array. 22 | let uarr; 23 | if (typeof cid === 'string') { 24 | if (cid.length === 46 && /^Qm/.test(cid)) throw new Error('CIDv0 is not supported.'); 25 | if (cid[0] !== 'b') throw new Error('Only base32 lowercase is supported.'); 26 | cid = cid.substring(1); 27 | const bitsPerChar = 5; 28 | uarr = new Uint8Array((cid.length * bitsPerChar / 8) | 0); 29 | let bits = 0; 30 | let buffer = 0; 31 | let written = 0; 32 | for (let i = 0; i < cid.length; i++) { 33 | if (!b32codes.has(cid[i])) throw new Error(`Invalid character "${cid[i]}"`); 34 | const value = b32codes.get(cid[i]); 35 | buffer = (buffer << bitsPerChar) | value; 36 | bits += bitsPerChar; 37 | if (bits >= 8) { 38 | bits -= 8; 39 | uarr[written++] = 0xff & (buffer >> bits); 40 | } 41 | } 42 | if (bits >= bitsPerChar || 0xff & (buffer << (8 - bits))) { 43 | console.warn(bits, bitsPerChar, 0xff & (buffer << (8 - bits))); 44 | throw new Error('Unexpected end of data'); 45 | } 46 | } 47 | else { 48 | uarr = cid; 49 | } 50 | if (uarr[0] === 18) throw new Error('CIDv0 is not supported.'); 51 | const { value: version, offset } = varint(uarr); 52 | if (version !== 1) throw new Error(`Only version 1 is supported, got ${version}.`); 53 | const { value: contentType, offset: restOffset } = varint(uarr, offset); 54 | if (!supportedContentTypes.has(contentType)) throw new Error(`Unsupported CID content type ${contentType}`); 55 | const multihash = new Uint8Array(uarr, restOffset); 56 | return { version, contentType, multihash }; 57 | } 58 | 59 | function varint (buf, offset = 0) { 60 | let value = 0; 61 | let shift = 0; 62 | let counter = offset; 63 | let b; 64 | 65 | do { 66 | if (counter >= buf.length) throw new Error('Could not decode varint'); 67 | b = buf[counter++]; 68 | value += shift < 28 69 | ? (b & REST) << shift 70 | : (b & REST) * Math.pow(2, shift); 71 | shift += 7; 72 | } while (b >= MSB) 73 | 74 | return { value, offset: counter }; 75 | } 76 | --------------------------------------------------------------------------------