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

fix: support uint8arrays in place of node buffers #23

Merged
merged 3 commits into from
Jul 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('/')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Seems like factoring out logic to count / char codes would be make sense here, which also could be then improved to avoid string encoding splitting etc...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this in place as it's just doing what was being done before, but it's a valid point.


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('/')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 This is the second place were / char codes are counted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this in place as it's just doing what was being done before, but it's a valid point.


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