diff --git a/libp2p/cid.nim b/libp2p/cid.nim index 3e397cc14..c1590ffb2 100644 --- a/libp2p/cid.nim +++ b/libp2p/cid.nim @@ -8,13 +8,18 @@ ## those terms. ## This module implementes CID (Content IDentifier). + +{.push raises: [Defect].} + import tables import multibase, multicodec, multihash, vbuffer, varint -import stew/base58 +import stew/[base58, results] + +export results type - CidStatus* {.pure.} = enum - Error, Success, Incorrect, Overrun + CidError* {.pure.} = enum + Error, Incorrect, Unsupported, Overrun CidVersion* = enum CIDvIncorrect, CIDv0, CIDv1, CIDvReserved @@ -25,8 +30,6 @@ type hpos*: int data*: VBuffer - CidError* = object of CatchableError - const ContentIdsList = [ multiCodec("raw"), @@ -66,66 +69,74 @@ proc initCidCodeTable(): Table[int, MultiCodec] {.compileTime.} = const CodeContentIds = initCidCodeTable() -proc decode(data: openarray[byte], cid: var Cid): CidStatus = - if len(data) == 34: - if data[0] == 0x12'u8 and data[1] == 0x20'u8: - cid.cidver = CIDv0 - cid.mcodec = multiCodec("dag-pb") - cid.hpos = 0 - cid.data = initVBuffer(data) - result = CidStatus.Success - if cid.cidver == CIDvIncorrect: +template orError*(exp: untyped, err: untyped): untyped = + (exp.mapErr do (_: auto) -> auto: err) + +proc decode(data: openarray[byte]): Result[Cid, CidError] = + if len(data) == 34 and data[0] == 0x12'u8 and data[1] == 0x20'u8: + ok(Cid( + cidver: CIDv0, + mcodec: multiCodec("dag-pb"), + hpos: 0, + data: initVBuffer(data))) + else: var version, codec: uint64 var res, offset: int var vb = initVBuffer(data) if vb.isEmpty(): - return CidStatus.Incorrect - res = vb.readVarint(version) - if res == -1: - return CidStatus.Incorrect - offset += res - if version != 1'u64: - return CidStatus.Incorrect - res = vb.readVarint(codec) - if res == -1: - return CidStatus.Incorrect - offset += res - var mcodec = CodeContentIds.getOrDefault(cast[int](codec), - InvalidMultiCodec) - if mcodec == InvalidMultiCodec: - return CidStatus.Incorrect - if not MultiHash.validate(vb.buffer.toOpenArray(vb.offset, - vb.buffer.high)): - return CidStatus.Incorrect - vb.finish() - cid.cidver = CIDv1 - cid.mcodec = mcodec - cid.hpos = offset - cid.data = vb - result = CidStatus.Success + err(CidError.Incorrect) + else: + res = vb.readVarint(version) + if res == -1: + err(CidError.Incorrect) + else: + offset += res + if version != 1'u64: + err(CidError.Incorrect) + else: + res = vb.readVarint(codec) + if res == -1: + err(CidError.Incorrect) + else: + offset += res + var mcodec = CodeContentIds.getOrDefault(cast[int](codec), + InvalidMultiCodec) + if mcodec == InvalidMultiCodec: + err(CidError.Incorrect) + else: + if not MultiHash.validate(vb.buffer.toOpenArray(vb.offset, + vb.buffer.high)): + err(CidError.Incorrect) + 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 plen = 0 if len(data) < 2: - return CidStatus.Incorrect + return err(CidError.Incorrect) if len(data) == 46: if data[0] == 'Q' and data[1] == 'm': buffer = newSeq[byte](BTCBase58.decodedLength(len(data))) if BTCBase58.decode(data, buffer, plen) != Base58Status.Success: - return CidStatus.Incorrect + return err(CidError.Incorrect) buffer.setLen(plen) if len(buffer) == 0: let length = MultiBase.decodedLength(data[0], len(data)) if length == -1: - return CidStatus.Incorrect + return err(CidError.Incorrect) buffer = newSeq[byte](length) if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success: - return CidStatus.Incorrect + return err(CidError.Incorrect) buffer.setLen(plen) if buffer[0] == 0x12'u8: - return CidStatus.Incorrect - result = decode(buffer, cid) + return err(CidError.Incorrect) + decode(buffer) proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = ## Returns ``true`` is data has valid binary CID representation. @@ -157,30 +168,30 @@ proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = return false result = true -proc mhash*(cid: Cid): MultiHash = +proc mhash*(cid: Cid): Result[MultiHash, CidError] = ## Returns MultiHash part of CID. if cid.cidver notin {CIDv0, CIDv1}: - raise newException(CidError, "Incorrect CID!") - result = MultiHash.init( - cid.data.buffer.toOpenArray(cid.hpos, cid.data.high)).tryGet() + err(CidError.Incorrect) + else: + 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 if cid.cidver notin {CIDv0, CIDv1}: - raise newException(CidError, "Incorrect CID!") - result = cid.mcodec + err(CidError.Incorrect) + else: + ok(cid.mcodec) proc version*(cid: Cid): CidVersion = ## Returns CID version 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``. - if decode(data, result) != CidStatus.Success: - raise newException(CidError, "Incorrect CID!") + decode(data) 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 ## 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)) ## ## All other encodings and hashes are not supported by CIDv0. - result.cidver = version + + var res: Cid + res.cidver = version if version == CIDv0: if content != multiCodec("dag-pb"): - raise newException(CidError, - "CIDv0 supports only `dag-pb` content type!") - result.data = initVBuffer() + return err(CidError.Unsupported) + res.data = initVBuffer() if hash.mcodec != multiCodec("sha2-256"): - raise newException(CidError, - "CIDv0 supports only `sha2-256` hash digest!") - result.mcodec = content - result.data.write(hash) - result.data.finish() + return err(CidError.Unsupported) + res.mcodec = content + res.data.write(hash) + res.data.finish() + return ok(res) elif version == CIDv1: let mcodec = CodeContentIds.getOrDefault(cast[int](content), InvalidMultiCodec) if mcodec == InvalidMultiCodec: - raise newException(CidError, "Incorrect content type") - result.mcodec = mcodec - result.data = initVBuffer() - result.data.writeVarint(cast[uint64](1)) - result.data.write(mcodec) - result.hpos = len(result.data.buffer) - result.data.write(hash) - result.data.finish() + return err(CidError.Incorrect) + res.mcodec = mcodec + res.data = initVBuffer() + res.data.writeVarint(cast[uint64](1)) + res.data.write(mcodec) + res.hpos = len(res.data.buffer) + res.data.write(hash) + res.data.finish() + return ok(res) else: - raise newException(CidError, "CID version is not supported" & $version) + return err(CidError.Unsupported) proc `==`*(a: Cid, b: Cid): bool = ## 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 = ## Return official string representation of content identifier ``cid``. if cid.cidver == CIDv0: - result = BTCBase58.encode(cid.data.buffer) + BTCBase58.encode(cid.data.buffer) 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: + "" diff --git a/tests/testcid.nim b/tests/testcid.nim index b9df56b7c..2609eee3b 100644 --- a/tests/testcid.nim +++ b/tests/testcid.nim @@ -7,29 +7,24 @@ suite "Content identifier CID test suite": test "CIDv0 test vector": var cid0Text = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" - var cid0 = Cid.init(cid0Text) + var cid0 = Cid.init(cid0Text).tryGet() check: $cid0 == cid0Text cid0.version() == CIDv0 - cid0.contentType() == multiCodec("dag-pb") - cid0.mhash().mcodec == multiCodec("sha2-256") - var res = 0 - try: - discard Cid.init("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII") - except CidError: - res = 1 - check res == 1 + cid0.contentType().tryGet() == multiCodec("dag-pb") + cid0.mhash().tryGet().mcodec == multiCodec("sha2-256") + Cid.init("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII").isErr() test "CIDv1 test vector": var cid1Text = "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" var chex = "015512209D8453505BDC6F269678E16B3E56" & "C2A2948A41F2C792617CC9611ED363C95B63" - var cid1 = Cid.init(cid1Text) + var cid1 = Cid.init(cid1Text).tryGet() check: $cid1 == cid1Text cid1.version() == CIDv1 - cid1.contentType() == multiCodec("raw") - cid1.mhash().mcodec == multiCodec("sha2-256") + cid1.contentType().tryGet() == multiCodec("raw") + cid1.mhash().tryGet().mcodec == multiCodec("sha2-256") hex(cid1) == chex test "Comparison test": @@ -38,17 +33,17 @@ suite "Content identifier CID test suite": var bmsg = cast[seq[byte]](msg) var bmmsg = cast[seq[byte]](mmsg) 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"), - MultiHash.digest("sha2-256", bmsg).get()) + MultiHash.digest("sha2-256", bmsg).get()).tryGet() var cid2 = cid1 var cid3 = cid0 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"), - MultiHash.digest("sha2-256", bmmsg).get()) + MultiHash.digest("sha2-256", bmmsg).get()).tryGet() var cid6 = Cid.init(CIDv1, multiCodec("dag-pb"), - MultiHash.digest("keccak-256", bmsg).get()) + MultiHash.digest("keccak-256", bmsg).get()).tryGet() check: cid0 == cid1 cid1 == cid2