first pass, use results for Cid module (#480)

* first pass, use results for Cid module

* improvements to decode
This commit is contained in:
Giovanni Petrantoni 2020-12-15 22:19:18 +09:00 committed by GitHub
parent a990fe95a0
commit 5543f6681f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 93 deletions

View File

@ -8,13 +8,18 @@
## those terms. ## those terms.
## This module implementes CID (Content IDentifier). ## This module implementes CID (Content IDentifier).
{.push raises: [Defect].}
import tables import tables
import multibase, multicodec, multihash, vbuffer, varint import multibase, multicodec, multihash, vbuffer, varint
import stew/base58 import stew/[base58, results]
export results
type type
CidStatus* {.pure.} = enum CidError* {.pure.} = enum
Error, Success, Incorrect, Overrun Error, Incorrect, Unsupported, Overrun
CidVersion* = enum CidVersion* = enum
CIDvIncorrect, CIDv0, CIDv1, CIDvReserved CIDvIncorrect, CIDv0, CIDv1, CIDvReserved
@ -25,8 +30,6 @@ type
hpos*: int hpos*: int
data*: VBuffer data*: VBuffer
CidError* = object of CatchableError
const const
ContentIdsList = [ ContentIdsList = [
multiCodec("raw"), multiCodec("raw"),
@ -66,66 +69,74 @@ proc initCidCodeTable(): Table[int, MultiCodec] {.compileTime.} =
const const
CodeContentIds = initCidCodeTable() CodeContentIds = initCidCodeTable()
proc decode(data: openarray[byte], cid: var Cid): CidStatus = template orError*(exp: untyped, err: untyped): untyped =
if len(data) == 34: (exp.mapErr do (_: auto) -> auto: err)
if data[0] == 0x12'u8 and data[1] == 0x20'u8:
cid.cidver = CIDv0 proc decode(data: openarray[byte]): Result[Cid, CidError] =
cid.mcodec = multiCodec("dag-pb") if len(data) == 34 and data[0] == 0x12'u8 and data[1] == 0x20'u8:
cid.hpos = 0 ok(Cid(
cid.data = initVBuffer(data) cidver: CIDv0,
result = CidStatus.Success mcodec: multiCodec("dag-pb"),
if cid.cidver == CIDvIncorrect: hpos: 0,
data: initVBuffer(data)))
else:
var version, codec: uint64 var version, codec: uint64
var res, offset: int var res, offset: int
var vb = initVBuffer(data) var vb = initVBuffer(data)
if vb.isEmpty(): if vb.isEmpty():
return CidStatus.Incorrect err(CidError.Incorrect)
res = vb.readVarint(version) else:
if res == -1: res = vb.readVarint(version)
return CidStatus.Incorrect if res == -1:
offset += res err(CidError.Incorrect)
if version != 1'u64: else:
return CidStatus.Incorrect offset += res
res = vb.readVarint(codec) if version != 1'u64:
if res == -1: err(CidError.Incorrect)
return CidStatus.Incorrect else:
offset += res res = vb.readVarint(codec)
var mcodec = CodeContentIds.getOrDefault(cast[int](codec), if res == -1:
InvalidMultiCodec) err(CidError.Incorrect)
if mcodec == InvalidMultiCodec: else:
return CidStatus.Incorrect offset += res
if not MultiHash.validate(vb.buffer.toOpenArray(vb.offset, var mcodec = CodeContentIds.getOrDefault(cast[int](codec),
vb.buffer.high)): InvalidMultiCodec)
return CidStatus.Incorrect if mcodec == InvalidMultiCodec:
vb.finish() err(CidError.Incorrect)
cid.cidver = CIDv1 else:
cid.mcodec = mcodec if not MultiHash.validate(vb.buffer.toOpenArray(vb.offset,
cid.hpos = offset vb.buffer.high)):
cid.data = vb err(CidError.Incorrect)
result = CidStatus.Success else:
vb.finish()
ok(Cid(
cidver: CIDv1,
mcodec: mcodec,
hpos: offset,
data: vb))
proc decode(data: openarray[char], cid: var Cid): CidStatus = proc decode(data: openarray[char]): Result[Cid, CidError] =
var buffer: seq[byte] var buffer: seq[byte]
var plen = 0 var plen = 0
if len(data) < 2: if len(data) < 2:
return CidStatus.Incorrect return err(CidError.Incorrect)
if len(data) == 46: if len(data) == 46:
if data[0] == 'Q' and data[1] == 'm': if data[0] == 'Q' and data[1] == 'm':
buffer = newSeq[byte](BTCBase58.decodedLength(len(data))) buffer = newSeq[byte](BTCBase58.decodedLength(len(data)))
if BTCBase58.decode(data, buffer, plen) != Base58Status.Success: if BTCBase58.decode(data, buffer, plen) != Base58Status.Success:
return CidStatus.Incorrect return err(CidError.Incorrect)
buffer.setLen(plen) buffer.setLen(plen)
if len(buffer) == 0: if len(buffer) == 0:
let length = MultiBase.decodedLength(data[0], len(data)) let length = MultiBase.decodedLength(data[0], len(data))
if length == -1: if length == -1:
return CidStatus.Incorrect return err(CidError.Incorrect)
buffer = newSeq[byte](length) buffer = newSeq[byte](length)
if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success: if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success:
return CidStatus.Incorrect return err(CidError.Incorrect)
buffer.setLen(plen) buffer.setLen(plen)
if buffer[0] == 0x12'u8: if buffer[0] == 0x12'u8:
return CidStatus.Incorrect return err(CidError.Incorrect)
result = decode(buffer, cid) decode(buffer)
proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool =
## Returns ``true`` is data has valid binary CID representation. ## Returns ``true`` is data has valid binary CID representation.
@ -157,30 +168,30 @@ proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool =
return false return false
result = true result = true
proc mhash*(cid: Cid): MultiHash = proc mhash*(cid: Cid): Result[MultiHash, CidError] =
## Returns MultiHash part of CID. ## Returns MultiHash part of CID.
if cid.cidver notin {CIDv0, CIDv1}: if cid.cidver notin {CIDv0, CIDv1}:
raise newException(CidError, "Incorrect CID!") err(CidError.Incorrect)
result = MultiHash.init( else:
cid.data.buffer.toOpenArray(cid.hpos, cid.data.high)).tryGet() MultiHash.init(cid.data.buffer.toOpenArray(cid.hpos, cid.data.high)).orError(CidError.Incorrect)
proc contentType*(cid: Cid): MultiCodec = proc contentType*(cid: Cid): Result[MultiCodec, CidError] =
## Returns content type part of CID ## Returns content type part of CID
if cid.cidver notin {CIDv0, CIDv1}: if cid.cidver notin {CIDv0, CIDv1}:
raise newException(CidError, "Incorrect CID!") err(CidError.Incorrect)
result = cid.mcodec else:
ok(cid.mcodec)
proc version*(cid: Cid): CidVersion = proc version*(cid: Cid): CidVersion =
## Returns CID version ## Returns CID version
result = cid.cidver result = cid.cidver
proc init*[T: char|byte](ctype: typedesc[Cid], data: openarray[T]): Cid = proc init*[T: char|byte](ctype: typedesc[Cid], data: openarray[T]): Result[Cid, CidError] =
## Create new content identifier using array of bytes or string ``data``. ## Create new content identifier using array of bytes or string ``data``.
if decode(data, result) != CidStatus.Success: decode(data)
raise newException(CidError, "Incorrect CID!")
proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec, proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec,
hash: MultiHash): Cid = hash: MultiHash): Result[Cid, CidError] =
## Create new content identifier using content type ``content`` and ## Create new content identifier using content type ``content`` and
## MultiHash ``hash`` using version ``version``. ## MultiHash ``hash`` using version ``version``.
## ##
@ -188,33 +199,35 @@ proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec,
## Cid.init(CIDv0, multiCodec("dag-pb"), MultiHash.digest("sha2-256", data)) ## Cid.init(CIDv0, multiCodec("dag-pb"), MultiHash.digest("sha2-256", data))
## ##
## All other encodings and hashes are not supported by CIDv0. ## All other encodings and hashes are not supported by CIDv0.
result.cidver = version
var res: Cid
res.cidver = version
if version == CIDv0: if version == CIDv0:
if content != multiCodec("dag-pb"): if content != multiCodec("dag-pb"):
raise newException(CidError, return err(CidError.Unsupported)
"CIDv0 supports only `dag-pb` content type!") res.data = initVBuffer()
result.data = initVBuffer()
if hash.mcodec != multiCodec("sha2-256"): if hash.mcodec != multiCodec("sha2-256"):
raise newException(CidError, return err(CidError.Unsupported)
"CIDv0 supports only `sha2-256` hash digest!") res.mcodec = content
result.mcodec = content res.data.write(hash)
result.data.write(hash) res.data.finish()
result.data.finish() return ok(res)
elif version == CIDv1: elif version == CIDv1:
let mcodec = CodeContentIds.getOrDefault(cast[int](content), let mcodec = CodeContentIds.getOrDefault(cast[int](content),
InvalidMultiCodec) InvalidMultiCodec)
if mcodec == InvalidMultiCodec: if mcodec == InvalidMultiCodec:
raise newException(CidError, "Incorrect content type") return err(CidError.Incorrect)
result.mcodec = mcodec res.mcodec = mcodec
result.data = initVBuffer() res.data = initVBuffer()
result.data.writeVarint(cast[uint64](1)) res.data.writeVarint(cast[uint64](1))
result.data.write(mcodec) res.data.write(mcodec)
result.hpos = len(result.data.buffer) res.hpos = len(res.data.buffer)
result.data.write(hash) res.data.write(hash)
result.data.finish() res.data.finish()
return ok(res)
else: else:
raise newException(CidError, "CID version is not supported" & $version) return err(CidError.Unsupported)
proc `==`*(a: Cid, b: Cid): bool = proc `==`*(a: Cid, b: Cid): bool =
## Compares content identifiers ``a`` and ``b``, returns ``true`` if hashes ## Compares content identifiers ``a`` and ``b``, returns ``true`` if hashes
@ -258,6 +271,12 @@ proc encode*(mbtype: typedesc[MultiBase], encoding: string,
proc `$`*(cid: Cid): string = proc `$`*(cid: Cid): string =
## Return official string representation of content identifier ``cid``. ## Return official string representation of content identifier ``cid``.
if cid.cidver == CIDv0: if cid.cidver == CIDv0:
result = BTCBase58.encode(cid.data.buffer) BTCBase58.encode(cid.data.buffer)
elif cid.cidver == CIDv1: elif cid.cidver == CIDv1:
result = Multibase.encode("base58btc", cid.data.buffer).tryGet() let res = Multibase.encode("base58btc", cid.data.buffer)
if res.isOk():
res.get()
else:
""
else:
""

View File

@ -7,29 +7,24 @@ suite "Content identifier CID test suite":
test "CIDv0 test vector": test "CIDv0 test vector":
var cid0Text = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" var cid0Text = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
var cid0 = Cid.init(cid0Text) var cid0 = Cid.init(cid0Text).tryGet()
check: check:
$cid0 == cid0Text $cid0 == cid0Text
cid0.version() == CIDv0 cid0.version() == CIDv0
cid0.contentType() == multiCodec("dag-pb") cid0.contentType().tryGet() == multiCodec("dag-pb")
cid0.mhash().mcodec == multiCodec("sha2-256") cid0.mhash().tryGet().mcodec == multiCodec("sha2-256")
var res = 0 Cid.init("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII").isErr()
try:
discard Cid.init("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII")
except CidError:
res = 1
check res == 1
test "CIDv1 test vector": test "CIDv1 test vector":
var cid1Text = "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" var cid1Text = "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG"
var chex = "015512209D8453505BDC6F269678E16B3E56" & var chex = "015512209D8453505BDC6F269678E16B3E56" &
"C2A2948A41F2C792617CC9611ED363C95B63" "C2A2948A41F2C792617CC9611ED363C95B63"
var cid1 = Cid.init(cid1Text) var cid1 = Cid.init(cid1Text).tryGet()
check: check:
$cid1 == cid1Text $cid1 == cid1Text
cid1.version() == CIDv1 cid1.version() == CIDv1
cid1.contentType() == multiCodec("raw") cid1.contentType().tryGet() == multiCodec("raw")
cid1.mhash().mcodec == multiCodec("sha2-256") cid1.mhash().tryGet().mcodec == multiCodec("sha2-256")
hex(cid1) == chex hex(cid1) == chex
test "Comparison test": test "Comparison test":
@ -38,17 +33,17 @@ suite "Content identifier CID test suite":
var bmsg = cast[seq[byte]](msg) var bmsg = cast[seq[byte]](msg)
var bmmsg = cast[seq[byte]](mmsg) var bmmsg = cast[seq[byte]](mmsg)
var cid0 = Cid.init(CIDv0, multiCodec("dag-pb"), var cid0 = Cid.init(CIDv0, multiCodec("dag-pb"),
MultiHash.digest("sha2-256", bmsg).get()) MultiHash.digest("sha2-256", bmsg).get()).tryGet()
var cid1 = Cid.init(CIDv1, multiCodec("dag-pb"), var cid1 = Cid.init(CIDv1, multiCodec("dag-pb"),
MultiHash.digest("sha2-256", bmsg).get()) MultiHash.digest("sha2-256", bmsg).get()).tryGet()
var cid2 = cid1 var cid2 = cid1
var cid3 = cid0 var cid3 = cid0
var cid4 = Cid.init(CIDv1, multiCodec("dag-cbor"), var cid4 = Cid.init(CIDv1, multiCodec("dag-cbor"),
MultiHash.digest("sha2-256", bmsg).get()) MultiHash.digest("sha2-256", bmsg).get()).tryGet()
var cid5 = Cid.init(CIDv1, multiCodec("dag-pb"), var cid5 = Cid.init(CIDv1, multiCodec("dag-pb"),
MultiHash.digest("sha2-256", bmmsg).get()) MultiHash.digest("sha2-256", bmmsg).get()).tryGet()
var cid6 = Cid.init(CIDv1, multiCodec("dag-pb"), var cid6 = Cid.init(CIDv1, multiCodec("dag-pb"),
MultiHash.digest("keccak-256", bmsg).get()) MultiHash.digest("keccak-256", bmsg).get()).tryGet()
check: check:
cid0 == cid1 cid0 == cid1
cid1 == cid2 cid1 == cid2