Skip to content

Commit c2b48c9

Browse files
jtsmedleydependabot[bot]semantic-release-botachingbrain
authored
Merge Upstream Changes (#3)
* deps: bump @libp2p/crypto from 3.0.4 to 4.0.0 (ipfs#304) Bumps [@libp2p/crypto](https://github.com/libp2p/js-libp2p) from 3.0.4 to 4.0.0. - [Release notes](https://github.com/libp2p/js-libp2p/releases) - [Changelog](https://github.com/libp2p/js-libp2p/blob/main/.release-please.json) - [Commits](libp2p/js-libp2p@perf-v3.0.4...utils-v4.0.0) --- updated-dependencies: - dependency-name: "@libp2p/crypto" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(release): 8.0.2 [skip ci] ## [8.0.2](ipfs/js-ipns@v8.0.1...v8.0.2) (2024-01-15) ### Dependencies * bump @libp2p/crypto from 3.0.4 to 4.0.0 ([ipfs#304](ipfs#304)) ([ed83244](ipfs@ed83244)) * fix: mark package as side-effect free (ipfs#305) [Tree shaking](https://webpack.js.org/guides/tree-shaking/) results in smaller web bundles by deleting unused code. This module is side-effect free so mark it as such to signal to bundlers that unused exports can be excluded from bundles. * chore(release): 8.0.3 [skip ci] ## [8.0.3](ipfs/js-ipns@v8.0.2...v8.0.3) (2024-01-16) ### Bug Fixes * mark package as side-effect free ([ipfs#305](ipfs#305)) ([a389fe8](ipfs@a389fe8)) * fix: log type as string (ipfs#306) The value field is a string so it needs to be logged as `%s` - logging as `%b` will throw. * chore(release): 8.0.4 [skip ci] ## [8.0.4](ipfs/js-ipns@v8.0.3...v8.0.4) (2024-01-18) ### Bug Fixes * log type as string ([ipfs#306](ipfs#306)) ([de68e4c](ipfs@de68e4c)) * fix!: treat validity as opaque (ipfs#307) Instead of parsing the validity field of an IPNS record as a timestamp during unmarshalling, treat it as an opaque string value. This ensures we can round-trip records supplied by other systems which may have a different interpretation of rfc3339 dates, for example. It also means we can handle different types of validity types, not just EOL. BREAKING CHANGE: the validity field is now a string * chore(release): 9.0.0 [skip ci] ## [9.0.0](ipfs/js-ipns@v8.0.4...v9.0.0) (2024-01-18) ### ⚠ BREAKING CHANGES * the validity field is now a string ### Bug Fixes * treat validity as opaque ([ipfs#307](ipfs#307)) ([461190e](ipfs@461190e)) --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net> Co-authored-by: Alex Potsides <alex@achingbrain.net>
1 parent 01f3f2d commit c2b48c9

10 files changed

+108
-84
lines changed

.aegir.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
/** @type {import('aegir').PartialOptions} */
33
export default {
44
build: {
5-
bundlesizeMax: '143KB'
5+
bundlesizeMax: '60KB'
66
}
77
}

CHANGELOG.md

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
## [9.0.0](https://github.com/ipfs/js-ipns/compare/v8.0.4...v9.0.0) (2024-01-18)
2+
3+
4+
### ⚠ BREAKING CHANGES
5+
6+
* the validity field is now a string
7+
8+
### Bug Fixes
9+
10+
* treat validity as opaque ([#307](https://github.com/ipfs/js-ipns/issues/307)) ([461190e](https://github.com/ipfs/js-ipns/commit/461190e215173e0ac2aad1dca107de5cb65a52ef))
11+
12+
## [8.0.4](https://github.com/ipfs/js-ipns/compare/v8.0.3...v8.0.4) (2024-01-18)
13+
14+
15+
### Bug Fixes
16+
17+
* log type as string ([#306](https://github.com/ipfs/js-ipns/issues/306)) ([de68e4c](https://github.com/ipfs/js-ipns/commit/de68e4c0601702fb5d567a97e305b26f65c34fc2))
18+
19+
## [8.0.3](https://github.com/ipfs/js-ipns/compare/v8.0.2...v8.0.3) (2024-01-16)
20+
21+
22+
### Bug Fixes
23+
24+
* mark package as side-effect free ([#305](https://github.com/ipfs/js-ipns/issues/305)) ([a389fe8](https://github.com/ipfs/js-ipns/commit/a389fe8f0e6dff4867ef22b6ddada43880476754))
25+
26+
## [8.0.2](https://github.com/ipfs/js-ipns/compare/v8.0.1...v8.0.2) (2024-01-15)
27+
28+
29+
### Dependencies
30+
31+
* bump @libp2p/crypto from 3.0.4 to 4.0.0 ([#304](https://github.com/ipfs/js-ipns/issues/304)) ([ed83244](https://github.com/ipfs/js-ipns/commit/ed832448a9c903dc2ea0dd6158cc73211eacded7))
32+
133
## [8.0.1](https://github.com/ipfs/js-ipns/compare/v8.0.0...v8.0.1) (2024-01-12)
234

335

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ipns",
3-
"version": "8.0.1",
3+
"version": "9.0.0",
44
"description": "IPNS record definitions",
55
"author": "Vasco Santos <vasco.santos@moxy.studio>",
66
"license": "Apache-2.0 OR MIT",
@@ -166,7 +166,7 @@
166166
"docs:no-publish": "NODE_OPTIONS=--max_old_space_size=8192 aegir docs --publish false"
167167
},
168168
"dependencies": {
169-
"@libp2p/crypto": "^3.0.3",
169+
"@libp2p/crypto": "^4.0.0",
170170
"@libp2p/interface": "^1.1.0",
171171
"@libp2p/logger": "^4.0.3",
172172
"@libp2p/peer-id": "^4.0.3",
@@ -183,5 +183,6 @@
183183
"@libp2p/peer-id-factory": "^4.0.2",
184184
"aegir": "^42.1.1",
185185
"protons": "^7.3.3"
186-
}
186+
},
187+
"sideEffects": false
187188
}

src/index.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface IPNSRecordV1V2 {
4040
/**
4141
* expiration datetime for the record in RFC3339 format
4242
*/
43-
validity: NanoDate
43+
validity: string
4444

4545
/**
4646
* number representing the version of the record
@@ -85,9 +85,10 @@ export interface IPNSRecordV2 {
8585
validityType: IpnsEntry.ValidityType
8686

8787
/**
88-
* expiration datetime for the record in RFC3339 format
88+
* If the validity type is EOL, this is the expiration datetime for the record
89+
* in RFC3339 format
8990
*/
90-
validity: NanoDate
91+
validity: string
9192

9293
/**
9394
* number representing the version of the record
@@ -171,7 +172,7 @@ export async function create (peerId: PeerId, value: CID | PeerId | string, seq:
171172
const validityType = IpnsEntry.ValidityType.EOL
172173
const lifetimeNs = typeof options.lifetimeNs === "bigint" ? options.lifetimeNs : DEFAULT_TTL
173174

174-
return _create(peerId, value, seq, validityType, expirationDate, lifetimeNs, options)
175+
return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options)
175176
}
176177

177178
/**
@@ -198,12 +199,12 @@ export async function createWithExpiration (peerId: PeerId, value: CID | PeerId
198199
const validityType = IpnsEntry.ValidityType.EOL
199200
const lifetimeNs = typeof options.lifetimeNs === "bigint" ? options.lifetimeNs : DEFAULT_TTL
200201

201-
return _create(peerId, value, seq, validityType, expirationDate, lifetimeNs, options)
202+
return _create(peerId, value, seq, validityType, expirationDate.toString(), lifetimeNs, options)
202203
}
203204

204-
const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, expirationDate: NanoDate, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
205+
const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number | bigint, validityType: IpnsEntry.ValidityType, validity: string, ttl: bigint, options: CreateOptions = defaultCreateOptions): Promise<IPNSRecord> => {
205206
seq = BigInt(seq)
206-
const isoValidity = uint8ArrayFromString(expirationDate.toString())
207+
const isoValidity = uint8ArrayFromString(validity)
207208
const normalizedValue = normalizeValue(value)
208209
const encodedValue = uint8ArrayFromString(normalizedValue)
209210

@@ -212,7 +213,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
212213
}
213214

214215
const privateKey = await unmarshalPrivateKey(peerId.privateKey)
215-
const data = createCborData(encodedValue, isoValidity, validityType, seq, ttl)
216+
const data = createCborData(encodedValue, validityType, isoValidity, seq, ttl)
216217
const sigData = ipnsRecordDataForV2Sig(data)
217218
const signatureV2 = await privateKey.sign(sigData)
218219
let pubKey: Uint8Array | undefined
@@ -233,7 +234,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
233234
const record: IPNSRecord = {
234235
value: normalizedValue,
235236
signatureV1,
236-
validity: expirationDate,
237+
validity,
237238
validityType,
238239
sequence: seq,
239240
ttl,
@@ -249,7 +250,7 @@ const _create = async (peerId: PeerId, value: CID | PeerId | string, seq: number
249250
} else {
250251
const record: IPNSRecordV2 = {
251252
value: normalizedValue,
252-
validity: expirationDate,
253+
validity,
253254
validityType,
254255
sequence: seq,
255256
ttl,

src/selector.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import NanoDate from 'timestamp-nano'
2+
import { IpnsEntry } from './pb/ipns.js'
13
import { unmarshal } from './utils.js'
24

35
export function ipnsSelector (key: Uint8Array, data: Uint8Array[]): number {
@@ -21,16 +23,18 @@ export function ipnsSelector (key: Uint8Array, data: Uint8Array[]): number {
2123
return 1
2224
}
2325

24-
// choose longer lived record if sequence numbers the same
25-
const recordAValidityDate = a.record.validity.toDate()
26-
const recordBValidityDate = b.record.validity.toDate()
26+
if (a.record.validityType === IpnsEntry.ValidityType.EOL && b.record.validityType === IpnsEntry.ValidityType.EOL) {
27+
// choose longer lived record if sequence numbers the same
28+
const recordAValidityDate = NanoDate.fromString(a.record.validity).toDate()
29+
const recordBValidityDate = NanoDate.fromString(b.record.validity).toDate()
2730

28-
if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
29-
return -1
30-
}
31+
if (recordAValidityDate.getTime() > recordBValidityDate.getTime()) {
32+
return -1
33+
}
3134

32-
if (recordAValidityDate.getTime() < recordBValidityDate.getTime()) {
33-
return 1
35+
if (recordAValidityDate.getTime() < recordBValidityDate.getTime()) {
36+
return 1
37+
}
3438
}
3539

3640
return 0

src/utils.ts

+3-58
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import * as cborg from 'cborg'
66
import errCode from 'err-code'
77
import { base36 } from 'multiformats/bases/base36'
88
import { CID } from 'multiformats/cid'
9-
import NanoDate from 'timestamp-nano'
109
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
1110
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
1211
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
@@ -20,53 +19,6 @@ const log = logger('ipns:utils')
2019
const IPNS_PREFIX = uint8ArrayFromString('/ipns/')
2120
const LIBP2P_CID_CODEC = 114
2221

23-
/**
24-
* Convert a JavaScript date into an `RFC3339Nano` formatted
25-
* string
26-
*/
27-
export function toRFC3339 (time: Date): string {
28-
const year = time.getUTCFullYear()
29-
const month = String(time.getUTCMonth() + 1).padStart(2, '0')
30-
const day = String(time.getUTCDate()).padStart(2, '0')
31-
const hour = String(time.getUTCHours()).padStart(2, '0')
32-
const minute = String(time.getUTCMinutes()).padStart(2, '0')
33-
const seconds = String(time.getUTCSeconds()).padStart(2, '0')
34-
const milliseconds = time.getUTCMilliseconds()
35-
const nanoseconds = milliseconds * 1000 * 1000
36-
37-
return `${year}-${month}-${day}T${hour}:${minute}:${seconds}.${nanoseconds}Z`
38-
}
39-
40-
/**
41-
* Parses a date string formatted as `RFC3339Nano` into a
42-
* JavaScript Date object
43-
*/
44-
export function parseRFC3339 (time: string): Date {
45-
const rfc3339Matcher = new RegExp(
46-
// 2006-01-02T
47-
'(\\d{4})-(\\d{2})-(\\d{2})T' +
48-
// 15:04:05
49-
'(\\d{2}):(\\d{2}):(\\d{2})' +
50-
// .999999999Z
51-
'\\.(\\d+)Z'
52-
)
53-
const m = String(time).trim().match(rfc3339Matcher)
54-
55-
if (m == null) {
56-
throw new Error('Invalid format')
57-
}
58-
59-
const year = parseInt(m[1], 10)
60-
const month = parseInt(m[2], 10) - 1
61-
const date = parseInt(m[3], 10)
62-
const hour = parseInt(m[4], 10)
63-
const minute = parseInt(m[5], 10)
64-
const second = parseInt(m[6], 10)
65-
const millisecond = parseInt(m[7].padEnd(6, '0').slice(0, 3), 10)
66-
67-
return new Date(Date.UTC(year, month, date, hour, minute, second, millisecond))
68-
}
69-
7022
/**
7123
* Extracts a public key from the passed PeerId, falling
7224
* back to the pubKey embedded in the ipns record
@@ -129,7 +81,7 @@ export const marshal = (obj: IPNSRecord | IPNSRecordV2): Uint8Array => {
12981
value: uint8ArrayFromString(obj.value),
13082
signatureV1: obj.signatureV1,
13183
validityType: obj.validityType,
132-
validity: uint8ArrayFromString(obj.validity.toString()),
84+
validity: uint8ArrayFromString(obj.validity),
13385
sequence: obj.sequence,
13486
ttl: obj.ttl,
13587
pubKey: obj.pubKey,
@@ -167,14 +119,7 @@ export function unmarshal (buf: Uint8Array): IPNSRecord {
167119

168120
const data = parseCborData(message.data)
169121
const value = normalizeValue(data.Value)
170-
171-
let validity
172-
try {
173-
validity = NanoDate.fromDate(parseRFC3339(uint8ArrayToString(data.Validity)))
174-
} catch (e) {
175-
log.error('unrecognized validity format (not an rfc3339 format)')
176-
throw errCode(new Error('unrecognized validity format (not an rfc3339 format)'), ERRORS.ERR_UNRECOGNIZED_FORMAT)
177-
}
122+
const validity = uint8ArrayToString(data.Validity)
178123

179124
if (message.value != null && message.signatureV1 != null) {
180125
// V1+V2
@@ -219,7 +164,7 @@ export const peerIdFromRoutingKey = (key: Uint8Array): PeerId => {
219164
return peerIdFromBytes(key.slice(IPNS_PREFIX.length))
220165
}
221166

222-
export const createCborData = (value: Uint8Array, validity: Uint8Array, validityType: string, sequence: bigint, ttl: bigint): Uint8Array => {
167+
export const createCborData = (value: Uint8Array, validityType: IpnsEntry.ValidityType, validity: Uint8Array, sequence: bigint, ttl: bigint): Uint8Array => {
223168
let ValidityType
224169

225170
if (validityType === IpnsEntry.ValidityType.EOL) {

src/validator.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { logger } from '@libp2p/logger'
22
import errCode from 'err-code'
3+
import NanoDate from 'timestamp-nano'
34
import * as ERRORS from './errors.js'
45
import { IpnsEntry } from './pb/ipns.js'
56
import { extractPublicKey, ipnsRecordDataForV2Sig, unmarshal, peerIdFromRoutingKey } from './utils.js'
@@ -37,7 +38,7 @@ export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<v
3738

3839
// Validate according to the validity type
3940
if (record.validityType === IpnsEntry.ValidityType.EOL) {
40-
if (record.validity.toDate().getTime() < Date.now()) {
41+
if (NanoDate.fromString(record.validity).toDate().getTime() < Date.now()) {
4142
log.error('record has expired')
4243
throw errCode(new Error('record has expired'), ERRORS.ERR_IPNS_EXPIRED_RECORD)
4344
}
@@ -46,7 +47,7 @@ export const validate = async (publicKey: PublicKey, buf: Uint8Array): Promise<v
4647
throw errCode(new Error('unrecognized validity type'), ERRORS.ERR_UNRECOGNIZED_VALIDITY)
4748
}
4849

49-
log('ipns record for %b is valid', record.value)
50+
log('ipns record for %s is valid', record.value)
5051
}
5152

5253
export async function ipnsValidator (key: Uint8Array, marshalledData: Uint8Array): Promise<void> {

test/conformance.spec.ts

+17
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,21 @@ describe('conformance', function () {
6565

6666
expect(record.value).to.equal('/ipfs/bafkqadtwgiww63tmpeqhezldn5zgi')
6767
})
68+
69+
it('should round trip fixtures', () => {
70+
const fixtures = [
71+
'test/fixtures/k51qzi5uqu5dlkw8pxuw9qmqayfdeh4kfebhmreauqdc6a7c3y7d5i9fi8mk9w_v1-v2.ipns-record',
72+
'test/fixtures/k51qzi5uqu5diamp7qnnvs1p1gzmku3eijkeijs3418j23j077zrkok63xdm8c_v1-v2-broken-signature-v2.ipns-record',
73+
'test/fixtures/k51qzi5uqu5dilgf7gorsh9vcqqq4myo6jd4zmqkuy9pxyxi5fua3uf7axph4y_v1-v2-broken-signature-v1.ipns-record',
74+
'test/fixtures/k51qzi5uqu5dit2ku9mutlfgwyz8u730on38kd10m97m36bjt66my99hb6103f_v2.ipns-record'
75+
]
76+
77+
for (const fixture of fixtures) {
78+
const buf = loadFixture(fixture)
79+
const record = ipns.unmarshal(buf)
80+
const marshalled = ipns.marshal(record)
81+
82+
expect(buf).to.equalBytes(marshalled)
83+
}
84+
})
6885
})

test/fixtures/records.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { peerIdFromString } from '@libp2p/peer-id'
2+
3+
export const kuboRecord = {
4+
bytes: Uint8Array.from([10, 52, 47, 105, 112, 102, 115, 47, 81, 109, 97, 52, 115, 87, 121, 111, 84, 105, 74, 75, 89, 120, 49, 119, 84, 106, 107, 120, 87, 89, 55, 49, 100, 89, 103, 49, 111, 87, 103, 69, 55, 83, 69, 57, 111, 105, 84, 71, 113, 71, 110, 121, 111, 82, 18, 64, 178, 225, 212, 157, 188, 23, 25, 166, 9, 89, 255, 63, 227, 160, 140, 70, 192, 237, 178, 167, 94, 6, 112, 184, 106, 130, 89, 252, 141, 158, 84, 53, 65, 125, 253, 93, 255, 17, 28, 93, 9, 176, 232, 89, 51, 118, 104, 236, 126, 137, 136, 72, 0, 127, 101, 88, 178, 83, 115, 6, 30, 28, 140, 5, 24, 0, 34, 27, 50, 48, 50, 52, 45, 48, 49, 45, 49, 57, 84, 49, 54, 58, 51, 51, 58, 50, 48, 46, 56, 53, 56, 50, 48, 57, 90, 40, 0, 48, 128, 192, 226, 133, 227, 104, 66, 64, 133, 91, 52, 64, 253, 186, 129, 154, 218, 85, 188, 18, 104, 96, 180, 216, 254, 176, 210, 145, 130, 209, 176, 150, 134, 33, 59, 197, 162, 193, 15, 252, 71, 190, 240, 25, 3, 169, 60, 24, 236, 68, 218, 171, 61, 235, 157, 73, 215, 0, 51, 52, 24, 195, 90, 158, 245, 199, 172, 204, 12, 249, 89, 7, 74, 136, 1, 165, 99, 84, 84, 76, 27, 0, 0, 3, 70, 48, 184, 160, 0, 101, 86, 97, 108, 117, 101, 88, 52, 47, 105, 112, 102, 115, 47, 81, 109, 97, 52, 115, 87, 121, 111, 84, 105, 74, 75, 89, 120, 49, 119, 84, 106, 107, 120, 87, 89, 55, 49, 100, 89, 103, 49, 111, 87, 103, 69, 55, 83, 69, 57, 111, 105, 84, 71, 113, 71, 110, 121, 111, 82, 104, 83, 101, 113, 117, 101, 110, 99, 101, 0, 104, 86, 97, 108, 105, 100, 105, 116, 121, 88, 27, 50, 48, 50, 52, 45, 48, 49, 45, 49, 57, 84, 49, 54, 58, 51, 51, 58, 50, 48, 46, 56, 53, 56, 50, 48, 57, 90, 108, 86, 97, 108, 105, 100, 105, 116, 121, 84, 121, 112, 101, 0]),
5+
peerId: peerIdFromString('12D3KooWBT21CjaZgY3MvoFFwRJLBEqgk7zwa294Boh9wdX2RUX2'),
6+
value: '/ipfs/Qma4sWyoTiJKYx1wTjkxWY71dYg1oWgE7SE9oiTGqGnyoR'
7+
}

test/index.spec.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as ipns from '../src/index.js'
1616
import { IpnsEntry } from '../src/pb/ipns.js'
1717
import { extractPublicKey, peerIdToRoutingKey, parseCborData, createCborData, ipnsRecordDataForV2Sig } from '../src/utils.js'
1818
import { ipnsValidator } from '../src/validator.js'
19+
import { kuboRecord } from './fixtures/records.js'
1920
import type { PeerId } from '@libp2p/interface'
2021

2122
describe('ipns', function () {
@@ -186,7 +187,7 @@ describe('ipns', function () {
186187
const record = await ipns.create(peerId, inputValue, 0, 1000000)
187188

188189
const pb = IpnsEntry.decode(ipns.marshal(record))
189-
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validity ?? new Uint8Array(0), pb.validityType ?? '', pb.sequence ?? 0n, pb.ttl ?? 0n)
190+
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validityType ?? IpnsEntry.ValidityType.EOL, pb.validity ?? new Uint8Array(0), pb.sequence ?? 0n, pb.ttl ?? 0n)
190191
pb.value = uint8ArrayFromString(inputValue)
191192

192193
const modifiedRecord = ipns.unmarshal(IpnsEntry.encode(pb))
@@ -199,7 +200,7 @@ describe('ipns', function () {
199200
const record = await ipns.create(peerId, inputValue, 0, 1000000)
200201

201202
const pb = IpnsEntry.decode(ipns.marshal(record))
202-
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validity ?? new Uint8Array(0), pb.validityType ?? '', pb.sequence ?? 0n, pb.ttl ?? 0n)
203+
pb.data = createCborData(uint8ArrayFromString(inputValue), pb.validityType ?? IpnsEntry.ValidityType.EOL, pb.validity ?? new Uint8Array(0), pb.sequence ?? 0n, pb.ttl ?? 0n)
203204
pb.value = uint8ArrayFromString(inputValue)
204205

205206
const modifiedRecord = ipns.unmarshal(IpnsEntry.encode(pb))
@@ -397,4 +398,19 @@ describe('ipns', function () {
397398

398399
expect(record).to.have.property('value', '/ipfs/bafkqae3imvwgy3zamzzg63janjzs22lqnzzqu')
399400
})
401+
402+
it('should round trip kubo records to bytes and back', async () => {
403+
// the IPNS spec gives an example for the Validity field as
404+
// 1970-01-01T00:00:00.000000001Z - e.g. nanosecond precision but Kubo only
405+
// uses microsecond precision. The value is a timestamp as defined by
406+
// rfc3339 which doesn't have a strong opinion on fractions of seconds so
407+
// both are valid but we must be able to round trip them intact.
408+
const unmarshalled = ipns.unmarshal(kuboRecord.bytes)
409+
const remarhshalled = ipns.marshal(unmarshalled)
410+
411+
const reUnmarshalled = ipns.unmarshal(remarhshalled)
412+
413+
expect(unmarshalled).to.deep.equal(reUnmarshalled)
414+
expect(remarhshalled).to.equalBytes(kuboRecord.bytes)
415+
})
400416
})

0 commit comments

Comments
 (0)