import RocksDB from 'rocksdb-native'
import b4a from 'b4a'
// Mirrors hypercore-storage key encoding
// TL_CORE_BY_DKEY = 1, keys are: varint(1) + fixed32(discoveryKey)
// Values are @corestore/core: { version, corePointer, dataPointer, alias? }
const CORESTORE_CF = 'corestore'
export async function enumerateCores (dbPath, opts = {}) {
const { readOnly = false } = opts
const db = new RocksDB(dbPath, {
readOnly,
columnFamilies: [CORESTORE_CF]
})
await db.ready()
const cs = db.columnFamily(CORESTORE_CF)
const cores = []
// Key prefix 0x01 = TL_CORE_BY_DKEY (varint-encoded 1)
// Each entry maps a discovery key to a @corestore/core record
const start = Buffer.from([0x01])
const end = Buffer.from([0x02])
const iter = cs.iterator({ gte: start, lt: end, limit: 10000 })
await new Promise((resolve, reject) => {
iter.on('data', ({ key, value }) => {
if (!value || key[0] !== 0x01) return
// Discovery key is the 32 bytes after the varint prefix
const discoveryKey = b4a.toString(key.subarray(1, 33), 'hex')
// Decode @corestore/core value using compact-encoding
try {
const core = decodeCoreRecord(value)
cores.push({
discoveryKey,
...core
})
} catch (err) {
cores.push({
discoveryKey,
corePointer: -1,
dataPointer: -1,
alias: null,
error: err.message
})
}
})
iter.on('end', resolve)
iter.on('error', reject)
})
// Read auth records to get public keys and head lengths
// Auth key format: varint(3) + varint(corePointer) + varint(0)
// Auth value: fixed32(key) + fixed32(discoveryKey) + varint(flags) + ...
// Head key format: varint(4) + varint(dataPointer) + varint(0)
// Head value: { fork, length, rootHash, signature }
for (const core of cores) {
// Build auth key
const authKey = encodeKey(3, core.corePointer, 0)
try {
const authIter = cs.iterator({ gte: authKey, lt: incrementKey(authKey), limit: 1 })
await new Promise((resolve, reject) => {
authIter.on('data', ({ value }) => {
if (value && value.length >= 64) {
core.publicKey = b4a.toString(value.subarray(0, 32), 'hex')
}
})
authIter.on('end', resolve)
authIter.on('error', reject)
})
} catch {}
// Build head key to get core length
const headKey = encodeKey(4, core.dataPointer, 0)
try {
const headIter = cs.iterator({ gte: headKey, lt: incrementKey(headKey), limit: 1 })
await new Promise((resolve, reject) => {
headIter.on('data', ({ value }) => {
if (value) {
const head = decodeHeadRecord(value)
core.length = head.length
core.fork = head.fork
}
})
headIter.on('end', resolve)
headIter.on('error', reject)
})
} catch {}
}
// Also try to read the corestore head for allocation counts
let head = null
try {
const headKey = Buffer.from([0x00])
const headIter = cs.iterator({ gte: headKey, lt: Buffer.from([0x01]), limit: 1 })
await new Promise((resolve, reject) => {
headIter.on('data', ({ value }) => {
if (value) head = decodeHead(value)
})
headIter.on('end', resolve)
headIter.on('error', reject)
})
} catch {}
if (head) {
cores._head = head
}
await db.close()
// Sort by corePointer
cores.sort((a, b) => a.corePointer - b.corePointer)
return cores
}
// Encode a RocksDB key from varint parts
function encodeKey (...parts) {
const bufs = parts.map(encodeVarint)
return Buffer.concat(bufs)
}
function encodeVarint (n) {
const bytes = []
while (n >= 0x80) {
bytes.push((n & 0x7f) | 0x80)
n >>>= 7
}
bytes.push(n & 0x7f)
return Buffer.from(bytes)
}
function incrementKey (key) {
const buf = Buffer.from(key)
for (let i = buf.length - 1; i >= 0; i--) {
if (buf[i] < 0xff) {
buf[i]++
return buf
}
buf[i] = 0
}
return Buffer.concat([Buffer.from([0x01]), buf])
}
// Decode @core/head { fork, length, rootHash, signature }
function decodeHeadRecord (buf) {
let pos = 0
const { value: fork, bytesRead: fb } = readVarint(buf, pos)
pos += fb
const { value: length, bytesRead: lb } = readVarint(buf, pos)
pos += lb
return { fork, length }
}
// Minimal varint decoder
function readVarint (buf, offset) {
let result = 0
let shift = 0
let pos = offset
while (pos < buf.length) {
const byte = buf[pos]
result |= (byte & 0x7f) << shift
pos++
if ((byte & 0x80) === 0) break
shift += 7
}
return { value: result, bytesRead: pos - offset }
}
function readFixed32 (buf, offset) {
return {
value: b4a.toString(buf.subarray(offset, offset + 32), 'hex'),
bytesRead: 32
}
}
function readString (buf, offset) {
const { value: len, bytesRead: lenBytes } = readVarint(buf, offset)
const str = b4a.toString(buf.subarray(offset + lenBytes, offset + lenBytes + len), 'utf8')
return { value: str, bytesRead: lenBytes + len }
}
// Decode @corestore/core { version, corePointer, dataPointer, alias? }
function decodeCoreRecord (buf) {
let pos = 0
const { value: version, bytesRead: vb } = readVarint(buf, pos)
pos += vb
const { value: corePointer, bytesRead: cpb } = readVarint(buf, pos)
pos += cpb
const { value: dataPointer, bytesRead: dpb } = readVarint(buf, pos)
pos += dpb
const { value: flags, bytesRead: fb } = readVarint(buf, pos)
pos += fb
let alias = null
if (flags & 1) {
// @corestore/alias { name, namespace }
const { value: name, bytesRead: nb } = readString(buf, pos)
pos += nb
const { value: namespace, bytesRead: nsb } = readFixed32(buf, pos)
pos += nsb
alias = { name, namespace }
}
return { version, corePointer, dataPointer, alias }
}
// Decode @corestore/head { version, flags, allocated?, seed?, defaultDiscoveryKey? }
function decodeHead (buf) {
let pos = 0
const { value: version, bytesRead: vb } = readVarint(buf, pos)
pos += vb
const { value: flags, bytesRead: fb } = readVarint(buf, pos)
pos += fb
let allocated = null
if (flags & 1) {
const { value: cores, bytesRead: cb } = readVarint(buf, pos)
pos += cb
const { value: datas, bytesRead: db } = readVarint(buf, pos)
pos += db
allocated = { cores, datas }
}
let seed = null
if (flags & 2) {
const { value, bytesRead } = readFixed32(buf, pos)
pos += bytesRead
seed = value
}
let defaultDiscoveryKey = null
if (flags & 4) {
const { value, bytesRead } = readFixed32(buf, pos)
pos += bytesRead
defaultDiscoveryKey = value
}
return { version, allocated, seed, defaultDiscoveryKey }
}