feat: record interface (#52)

* feat: record interface

* chore: add readme reference

* chore: address review
This commit is contained in:
Vasco Santos 2020-07-15 12:29:36 +02:00 committed by GitHub
parent 4adedcc4bf
commit 1cc943e1b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 146 additions and 0 deletions

View File

@ -18,6 +18,7 @@
- [Crypto](./src/crypto)
- [Peer Discovery](./src/peer-discovery)
- [Peer Routing](./src/peer-routing)
- [Record](./src/record)
- [Stream Muxer](./src/stream-muxer)
- [Topology](./src/topology)
- [Transport](./src/transport)

75
src/record/README.md Normal file
View File

@ -0,0 +1,75 @@
interface-record
==================
A libp2p node needs to store data in a public location (e.g. a DHT), or rely on potentially untrustworthy intermediaries to relay information. Libp2p provides an all-purpose data container called **envelope**, which includes a signature of the data, so that it its authenticity can be verified.
The record represents the data that will be stored inside the **envelope** when distributing records across the network. The `interface-record` aims to guarantee that any type of record created is compliant with the libp2p **envelope**.
Taking into account that a record might be used in different contexts, an **envelope** signature made for a specific purpose **must not** be considered valid for a different purpose. Accordingly, each record has a short and descriptive string representing the record use case, known as **domain**. The data to be signed will be prepended with the domain string, in order to create a domain signature.
A record can also contain a Buffer codec (ideally registered as a [multicodec](https://github.com/multiformats/multicodec)). This codec will prefix the record data in the **envelope** , so that it can be deserialized deterministically.
## Usage
```js
const tests = require('libp2p-interfaces/src/record/tests')
describe('your record', () => {
tests({
async setup () {
return YourRecord
},
async teardown () {
// cleanup resources created by setup()
}
})
})
```
## Create Record
```js
const multicodec = require('multicodec')
const Record = require('libp2p-interfaces/src/record')
// const Protobuf = require('./record.proto')
const ENVELOPE_DOMAIN_PEER_RECORD = 'libp2p-peer-record'
const ENVELOPE_PAYLOAD_TYPE_PEER_RECORD = Buffer.from('0301', 'hex')
class PeerRecord extends Record {
constructor (peerId, multiaddrs, seqNumber) {
super (ENVELOPE_DOMAIN_PEER_RECORD, ENVELOPE_PAYLOAD_TYPE_PEER_RECORD)
}
marshal () {
// Implement and return using Protobuf
}
equals (other) {
// Verify
}
}
```
## API
### marshal
- `record.marshal()`
Marshal a record to be used in a libp2p envelope.
**Returns**
It returns a `Protobuf` containing the record data.
### equals
- `record.equals(other)`
Verifies if the other Record is identical to this one.
**Parameters**
- other is a `Record` to compare with the current instance.
**Returns**
- `boolean`

35
src/record/index.js Normal file
View File

@ -0,0 +1,35 @@
'use strict'
const errcode = require('err-code')
/**
* Record is the base implementation of a record that can be used as the payload of a libp2p envelope.
*/
class Record {
/**
* @constructor
* @param {String} domain signature domain
* @param {Buffer} codec identifier of the type of record
*/
constructor (domain, codec) {
this.domain = domain
this.codec = codec
}
/**
* Marshal a record to be used in an envelope.
*/
marshal () {
throw errcode(new Error('marshal must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED')
}
/**
* Verifies if the other provided Record is identical to this one.
* @param {Record} other
*/
equals (other) {
throw errcode(new Error('equals must be implemented by the subclass'), 'ERR_NOT_IMPLEMENTED')
}
}
module.exports = Record

35
src/record/tests/index.js Normal file
View File

@ -0,0 +1,35 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
module.exports = (test) => {
describe('record', () => {
let record
beforeEach(async () => {
record = await test.setup()
if (!record) throw new Error('missing record')
})
afterEach(() => test.teardown())
it('has domain and codec', () => {
expect(record.domain).to.exist()
expect(record.codec).to.exist()
})
it('is able to marshal', () => {
const rawData = record.marshal()
expect(Buffer.isBuffer(rawData)).to.eql(true)
})
it('is able to compare two records', () => {
const equals = record.equals(record)
expect(equals).to.eql(true)
})
})
}