Skip to content
This repository has been archived by the owner on Jul 21, 2023. It is now read-only.

Commit

Permalink
fix: support uint8arrays in place of node buffers (#23)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: takes Uint8Arrays as well as Node Buffers
  • Loading branch information
achingbrain authored Jul 29, 2020
1 parent a60c685 commit 3b99ee1
Show file tree
Hide file tree
Showing 13 changed files with 108 additions and 75 deletions.
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,19 @@
},
"homepage": "https://github.com/libp2p/js-libp2p-record",
"devDependencies": {
"aegir": "^22.0.0",
"aegir": "^25.0.0",
"chai": "^4.2.0",
"dirty-chai": "^2.0.1",
"ipfs-utils": "^2.3.1",
"libp2p-crypto": "~0.17.0",
"multibase": "^2.0.0",
"peer-id": "~0.13.6"
},
"dependencies": {
"buffer": "^5.6.0",
"err-code": "^2.0.0",
"multihashes": "~0.4.15",
"multihashing-async": "^0.8.0",
"protons": "^1.0.1"
"multihashes": "^1.0.1",
"multihashing-async": "^1.0.0",
"protons": "^1.2.1"
},
"contributors": [
"Vasco Santos <vasco.santos@moxy.studio>",
Expand Down
17 changes: 8 additions & 9 deletions src/record.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
'use strict'

const protons = require('protons')
const { Buffer } = require('buffer')
const pb = protons(require('./record.proto')).Record
const utils = require('./utils')

class Record {
/**
* @param {Buffer} [key]
* @param {Buffer} [value]
* @param {Uint8Array} [key]
* @param {Uint8Array} [value]
* @param {Date} [recvtime]
*/
constructor (key, value, recvtime) {
if (key && !Buffer.isBuffer(key)) {
throw new Error('key must be a Buffer')
if (!(key instanceof Uint8Array)) {
throw new Error('key must be a Uint8Array')
}

if (value && !Buffer.isBuffer(value)) {
throw new Error('value must be a buffer')
if (!(value instanceof Uint8Array)) {
throw new Error('value must be a Uint8Array')
}

this.key = key
Expand All @@ -26,7 +25,7 @@ class Record {
}

/**
* @returns {Buffer}
* @returns {Uint8Array}
*/
serialize () {
return pb.encode(this.prepareSerialize())
Expand All @@ -48,7 +47,7 @@ class Record {
/**
* Decode a protobuf encoded record.
*
* @param {Buffer} raw
* @param {Uint8Array} raw
* @returns {Record}
*/
static deserialize (raw) {
Expand Down
9 changes: 6 additions & 3 deletions src/selection.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use strict'

const errcode = require('err-code')
const { utf8Decoder } = require('./utils')

/**
* Select the best record out of the given records.
*
* @param {Object} selectors
* @param {Buffer} k
* @param {Array<Buffer>} records
* @param {Uint8Array} k
* @param {Array<Uint8Array>} records
* @returns {number} - The index of the best record.
*/
const bestRecord = (selectors, k, records) => {
Expand All @@ -16,7 +18,8 @@ const bestRecord = (selectors, k, records) => {
throw errcode(new Error(errMsg), 'ERR_NO_RECORDS_RECEIVED')
}

const parts = k.toString().split('/')
const kStr = utf8Decoder.decode(k)
const parts = kStr.split('/')

if (parts.length < 3) {
const errMsg = 'Record key does not have a selector function'
Expand Down
4 changes: 2 additions & 2 deletions src/selectors/public-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* Simply returns the first record, as all valid public key
* records are equal.
*
* @param {Buffer} k
* @param {Array<Buffer>} records
* @param {Uint8Array} k
* @param {Array<Uint8Array>} records
* @returns {number}
*/
const publicKeySelector = (k, records) => {
Expand Down
23 changes: 23 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
'use strict'

const TextDecoder = require('ipfs-utils/src/text-decoder')
const utf8Decoder = new TextDecoder('utf8')

module.exports.utf8Decoder = utf8Decoder

/**
* Convert a JavaScript date into an `RFC3339Nano` formatted
* string.
Expand Down Expand Up @@ -52,3 +57,21 @@ module.exports.parseRFC3339 = (time) => {

return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond))
}

module.exports.uint8ArraysEqual = (arr1, arr2) => {
if (arr1 === arr2) {
return true
}

if (arr1.byteLength !== arr2.byteLength) {
return false
}

for (let i = 0; i < arr1.byteLength; i++) {
if (arr1[i] !== arr2[i]) {
return false
}
}

return true
}
4 changes: 3 additions & 1 deletion src/validator.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict'

const errcode = require('err-code')
const { utf8Decoder } = require('./utils')
/**
* Checks a record and ensures it is still valid.
* It runs the needed validators.
Expand All @@ -12,7 +13,8 @@ const errcode = require('err-code')
*/
const verifyRecord = (validators, record) => {
const key = record.key
const parts = key.toString().split('/')
const keyString = utf8Decoder.decode(key)
const parts = keyString.split('/')

if (parts.length < 3) {
// No validator available
Expand Down
19 changes: 10 additions & 9 deletions src/validators/public-key.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@

const multihashing = require('multihashing-async')
const errcode = require('err-code')
const { Buffer } = require('buffer')
const { utf8Decoder, uint8ArraysEqual } = require('../utils')

/**
* Validator for publick key records.
* Validator for public key records.
* Verifies that the passed in record value is the PublicKey
* that matches the passed in key.
* If validation fails the returned Promise will reject with the error.
*
* @param {Buffer} key - A valid key is of the form `'/pk/<keymultihash>'`
* @param {Buffer} publicKey - The public key to validate against (protobuf encoded).
* @param {Uint8Array} key - A valid key is of the form `'/pk/<keymultihash>'`
* @param {Uint8Array} publicKey - The public key to validate against (protobuf encoded).
* @returns {Promise}
*/
const validatePublicKeyRecord = async (key, publicKey) => {
if (!Buffer.isBuffer(key)) {
throw errcode(new Error('"key" must be a Buffer'), 'ERR_INVALID_RECORD_KEY_NOT_BUFFER')
if (!(key instanceof Uint8Array)) {
throw errcode(new Error('"key" must be a Uint8Array'), 'ERR_INVALID_RECORD_KEY_NOT_BUFFER')
}

if (key.length < 5) {
if (key.byteLength < 5) {
throw errcode(new Error('invalid public key record'), 'ERR_INVALID_RECORD_KEY_TOO_SHORT')
}

const prefix = key.slice(0, 4).toString()
const prefix = utf8Decoder.decode(key.subarray(0, 4))

if (prefix !== '/pk/') {
throw errcode(new Error('key was not prefixed with /pk/'), 'ERR_INVALID_RECORD_KEY_BAD_PREFIX')
Expand All @@ -32,7 +33,7 @@ const validatePublicKeyRecord = async (key, publicKey) => {

const publicKeyHash = await multihashing(publicKey, 'sha2-256')

if (!keyhash.equals(publicKeyHash)) {
if (!uint8ArraysEqual(keyhash, publicKeyHash)) {
throw errcode(new Error('public key does not match passed in key'), 'ERR_INVALID_RECORD_HASH_MISMATCH')
}
}
Expand Down
9 changes: 5 additions & 4 deletions test/fixtures/go-key-records.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict'
const { Buffer } = require('buffer')

const multibase = require('multibase')

module.exports = {
publicKey: Buffer.from(
'CAASXjBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDjXAQQMal4SB2tSnX6NJIPmC69/BT8A8jc7/gDUZNkEhdhYHvc7k7S4vntV/c92nJGxNdop9fKJyevuNMuXhhHAgMBAAE=',
'base64'
publicKey: multibase.decode(
'MCAASXjBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDjXAQQMal4SB2tSnX6NJIPmC69/BT8A8jc7/gDUZNkEhdhYHvc7k7S4vntV/c92nJGxNdop9fKJyevuNMuXhhHAgMBAAE='
)
}
12 changes: 5 additions & 7 deletions test/fixtures/go-record.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
'use strict'
const { Buffer } = require('buffer')
const multibase = require('multibase')
// Fixtures generated using gore (https://github.com/motemen/gore)
//
// :import github.com/libp2p/go-libp2p-record
Expand All @@ -18,12 +18,10 @@ const { Buffer } = require('buffer')
// ioutil.WriteFile("js-libp2p-record/test/fixtures/record.bin", enc, 0644)
// ioutil.WriteFile("js-libp2p-record/test/fixtures/record-signed.bin", enc2, 0644)
module.exports = {
serialized: Buffer.from(
'0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116',
'hex'
serialized: multibase.decode(
'f0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116'
),
serializedSigned: Buffer.from(
'0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116228001500fe7505698b8a873ccde6f1d36a2be662d57807490d9a9959540f2645a454bf615215092e10123f6ffc4ed694711bfbb1d5ccb62f3da83cf4528ee577a96b6cf0272eef9a920bd56459993690060353b72c22b8c03ad2a33894522dac338905b201179a85cb5e2fc68ed58be96cf89beec6dc0913887dddc10f202a2a1b117',
'hex'
serializedSigned: multibase.decode(
'f0a0568656c6c6f1205776f726c641a2212201bd5175b1d4123ee29665348c60ea5cf5ac62e2e05215b97a7b9a9b0cf71d116228001500fe7505698b8a873ccde6f1d36a2be662d57807490d9a9959540f2645a454bf615215092e10123f6ffc4ed694711bfbb1d5ccb62f3da83cf4528ee577a96b6cf0272eef9a920bd56459993690060353b72c22b8c03ad2a33894522dac338905b201179a85cb5e2fc68ed58be96cf89beec6dc0913887dddc10f202a2a1b117'
)
}
20 changes: 10 additions & 10 deletions test/record.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const chai = require('chai')
chai.use(require('dirty-chai'))
const expect = chai.expect
const { Buffer } = require('buffer')
const { utf8TextEncoder } = require('./utils')
const libp2pRecord = require('../src')
const Record = libp2pRecord.Record

Expand All @@ -15,28 +15,28 @@ const date = new Date(Date.UTC(2012, 1, 25, 10, 10, 10, 10))
describe('record', () => {
it('new', () => {
const rec = new Record(
Buffer.from('hello'),
Buffer.from('world')
utf8TextEncoder.encode('hello'),
utf8TextEncoder.encode('world')
)

expect(rec).to.have.property('key').eql(Buffer.from('hello'))
expect(rec).to.have.property('value').eql(Buffer.from('world'))
expect(rec).to.have.property('key').eql(utf8TextEncoder.encode('hello'))
expect(rec).to.have.property('value').eql(utf8TextEncoder.encode('world'))
})

it('serialize & deserialize', () => {
const rec = new Record(Buffer.from('hello'), Buffer.from('world'), date)
const rec = new Record(utf8TextEncoder.encode('hello'), utf8TextEncoder.encode('world'), date)
const dec = Record.deserialize(rec.serialize())

expect(dec).to.have.property('key').eql(Buffer.from('hello'))
expect(dec).to.have.property('value').eql(Buffer.from('world'))
expect(dec).to.have.property('key').eql(utf8TextEncoder.encode('hello'))
expect(dec).to.have.property('value').eql(utf8TextEncoder.encode('world'))
expect(dec.timeReceived).to.be.eql(date)
})

describe('go interop', () => {
it('no signature', () => {
const dec = Record.deserialize(fixture.serialized)
expect(dec).to.have.property('key').eql(Buffer.from('hello'))
expect(dec).to.have.property('value').eql(Buffer.from('world'))
expect(dec).to.have.property('key').eql(utf8TextEncoder.encode('hello'))
expect(dec).to.have.property('value').eql(utf8TextEncoder.encode('world'))
})
})
})
16 changes: 8 additions & 8 deletions test/selection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,33 @@
'use strict'

var expect = require('chai').expect
const { Buffer } = require('buffer')
const { utf8TextEncoder } = require('./utils')
const libp2pRecord = require('../src')
const selection = libp2pRecord.selection

const records = [Buffer.alloc(0), Buffer.from('hello')]
const records = [new Uint8Array(), utf8TextEncoder.encode('hello')]

describe('selection', () => {
describe('bestRecord', () => {
it('throws no records given when no records received', () => {
expect(
() => selection.bestRecord({}, Buffer.from('/'), [])
() => selection.bestRecord({}, utf8TextEncoder.encode('/'), [])
).to.throw(
/No records given/
)
})

it('throws on missing selector in the record key', () => {
expect(
() => selection.bestRecord({}, Buffer.from('/'), records)
() => selection.bestRecord({}, utf8TextEncoder.encode('/'), records)
).to.throw(
/Record key does not have a selector function/
)
})

it('throws on unknown key prefix', () => {
expect(
() => selection.bestRecord({ world () {} }, Buffer.from('/hello/'), records)
() => selection.bestRecord({ world () {} }, utf8TextEncoder.encode('/hello/'), records)
).to.throw(
/Unrecognized key prefix: hello/
)
Expand All @@ -38,15 +38,15 @@ describe('selection', () => {
it('returns the index from the matching selector', () => {
const selectors = {
hello (k, recs) {
expect(k).to.be.eql(Buffer.from('/hello/world'))
expect(k).to.be.eql(utf8TextEncoder.encode('/hello/world'))
expect(recs).to.be.eql(records)

return 1
}
}

expect(
selection.bestRecord(selectors, Buffer.from('/hello/world'), records)
selection.bestRecord(selectors, utf8TextEncoder.encode('/hello/world'), records)
).to.equal(
1
)
Expand All @@ -56,7 +56,7 @@ describe('selection', () => {
describe('selectors', () => {
it('public key', () => {
expect(
selection.selectors.pk(Buffer.from('/hello/world'), records)
selection.selectors.pk(utf8TextEncoder.encode('/hello/world'), records)
).to.equal(
0
)
Expand Down
6 changes: 6 additions & 0 deletions test/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use strict'

const TextEncoder = require('ipfs-utils/src/text-encoder')
const utf8TextEncoder = new TextEncoder('utf8')

module.exports.utf8TextEncoder = utf8TextEncoder
Loading

0 comments on commit 3b99ee1

Please sign in to comment.