From 6affcda9378593f96e9d2b75f9d9ac5e6e33c526 Mon Sep 17 00:00:00 2001 From: Giovanni Petrantoni Date: Sun, 31 May 2020 23:22:49 +0900 Subject: [PATCH] Less exceptions more results (#188) * Less exceptions more results * Fix daemonapi and interop tests * Add multibase * wip multiaddress * fix the build, consuming new result types * fix standard setup * Simplify match, rename into MaError, add more exaustive err text * Fix the CI issues * Fix directchat build * daemon api fixes * better err messages formatting Co-authored-by: Zahary Karadjov --- examples/directchat.nim | 8 +- libp2p/cid.nim | 10 +- libp2p/crypto/secp.nim | 6 +- libp2p/daemon/daemonapi.nim | 22 +- libp2p/multiaddress.nim | 504 ++++++++++++++++------------- libp2p/multibase.nim | 42 +-- libp2p/multihash.nim | 6 +- libp2p/protobuf/minprotobuf.nim | 30 +- libp2p/protocols/identify.nim | 4 +- libp2p/standard_setup.nim | 2 +- libp2p/stream/lpstream.nim | 4 +- libp2p/transcoder.nim | 6 +- libp2p/transports/tcptransport.nim | 6 +- libp2p/transports/transport.nim | 2 +- libp2p/varint.nim | 128 ++++---- libp2p/vbuffer.nim | 14 +- libp2p/wire.nim | 30 +- tests/testidentify.nim | 4 +- tests/testinterop.nim | 9 +- tests/testmplex.nim | 16 +- tests/testmultiaddress.nim | 81 +++-- tests/testmultibase.nim | 82 ++--- tests/testmultistream.nim | 8 +- tests/testnoise.nim | 10 +- tests/testswitch.nim | 8 +- tests/testtransport.nim | 12 +- tests/testvarint.nim | 90 +++--- 27 files changed, 605 insertions(+), 539 deletions(-) diff --git a/examples/directchat.nim b/examples/directchat.nim index 472fc07..318820d 100644 --- a/examples/directchat.nim +++ b/examples/directchat.nim @@ -41,11 +41,11 @@ type ChatProto = ref object of LPProtocol proc initAddress(T: type MultiAddress, str: string): T = - let address = MultiAddress.init(str) + let address = MultiAddress.init(str).tryGet() if IPFS.match(address) and matchPartial(multiaddress.TCP, address): result = address else: - raise newException(MultiAddressError, + raise newException(ValueError, "Invalid bootstrap node multi-address") proc dialPeer(p: ChatProto, address: string) {.async.} = @@ -160,10 +160,10 @@ proc processInput(rfd: AsyncFD) {.async.} = let a = await transp.readLine() try: if a.len > 0: - peerInfo.addrs.add(Multiaddress.init(a)) + peerInfo.addrs.add(Multiaddress.init(a).tryGet()) break - peerInfo.addrs.add(Multiaddress.init(localAddress)) + peerInfo.addrs.add(Multiaddress.init(localAddress).tryGet()) break except: echo "invalid address" diff --git a/libp2p/cid.nim b/libp2p/cid.nim index c7b7bd0..3e397cc 100644 --- a/libp2p/cid.nim +++ b/libp2p/cid.nim @@ -130,7 +130,7 @@ proc decode(data: openarray[char], cid: var Cid): CidStatus = proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = ## Returns ``true`` is data has valid binary CID representation. var version, codec: uint64 - var res: VarintStatus + var res: VarintResult[void] if len(data) < 2: return false let last = data.high @@ -140,7 +140,7 @@ proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = var offset = 0 var length = 0 res = LP.getUVarint(data.toOpenArray(offset, last), length, version) - if res != VarintStatus.Success: + if res.isErr(): return false if version != 1'u64: return false @@ -148,7 +148,7 @@ proc validate*(ctype: typedesc[Cid], data: openarray[byte]): bool = if offset >= len(data): return false res = LP.getUVarint(data.toOpenArray(offset, last), length, codec) - if res != VarintStatus.Success: + if res.isErr(): return false var mcodec = CodeContentIds.getOrDefault(cast[int](codec), InvalidMultiCodec) if mcodec == InvalidMultiCodec: @@ -253,11 +253,11 @@ proc encode*(mbtype: typedesc[MultiBase], encoding: string, cid: Cid): string {.inline.} = ## Get MultiBase encoded representation of ``cid`` using encoding ## ``encoding``. - result = MultiBase.encode(encoding, cid.data.buffer) + result = MultiBase.encode(encoding, cid.data.buffer).tryGet() proc `$`*(cid: Cid): string = ## Return official string representation of content identifier ``cid``. if cid.cidver == CIDv0: result = BTCBase58.encode(cid.data.buffer) elif cid.cidver == CIDv1: - result = Multibase.encode("base58btc", cid.data.buffer) + result = Multibase.encode("base58btc", cid.data.buffer).tryGet() diff --git a/libp2p/crypto/secp.nim b/libp2p/crypto/secp.nim index 5e9137f..e7b493a 100644 --- a/libp2p/crypto/secp.nim +++ b/libp2p/crypto/secp.nim @@ -82,7 +82,7 @@ proc init*(sig: var SkSignature, data: string): SkResult[void] = try: buffer = hexToSeqByte(data) except ValueError: - return err("Hex to bytes failed") + return err("secp: Hex to bytes failed") init(sig, buffer) proc init*(t: typedesc[SkPrivateKey], data: openarray[byte]): SkResult[SkPrivateKey] = @@ -145,7 +145,7 @@ proc toBytes*(key: SkPrivateKey, data: var openarray[byte]): SkResult[int] = data[0.. MaxMessageSize: + if res.isErr() or size > MaxMessageSize: buffer.setLen(0) result = buffer return @@ -521,27 +521,27 @@ proc getSocket(pattern: string, var sockname = "" var pid = $getProcessId() sockname = pattern % [pid, $(count[])] - let tmpma = MultiAddress.init(sockname) + let tmpma = MultiAddress.init(sockname).tryGet() if UNIX.match(tmpma): while true: count[] = count[] + 1 sockname = pattern % [pid, $(count[])] - var ma = MultiAddress.init(sockname) + var ma = MultiAddress.init(sockname).tryGet() let res = await socketExists(ma) if not res: result = ma break elif TCP.match(tmpma): sockname = pattern % [pid, "0"] - var ma = MultiAddress.init(sockname) + var ma = MultiAddress.init(sockname).tryGet() var sock = createAsyncSocket(ma) if sock.bindAsyncSocket(ma): # Socket was successfully bound, then its free to use count[] = count[] + 1 var ta = sock.getLocalAddress() sockname = pattern % [pid, $ta.port] - result = MultiAddress.init(sockname) + result = MultiAddress.init(sockname).tryGet() closeSocket(sock) # This is forward declaration needed for newDaemonApi() @@ -649,7 +649,7 @@ proc newDaemonApi*(flags: set[P2PDaemonFlags] = {}, api.flags.excl(NoProcessCtrl) api.address = await getSocket(patternForSocket, addr daemonsCount) else: - api.address = MultiAddress.init(sockpath) + api.address = MultiAddress.init(sockpath).tryGet() api.flags.incl(NoProcessCtrl) let res = await socketExists(api.address) if not res: @@ -830,7 +830,7 @@ proc getPeerInfo(pb: var ProtoBuffer): PeerInfo = while pb.getBytes(2, address) != -1: if len(address) != 0: var copyaddr = address - result.addresses.add(MultiAddress.init(copyaddr)) + result.addresses.add(MultiAddress.init(copyaddr).tryGet()) address.setLen(0) proc identity*(api: DaemonAPI): Future[PeerInfo] {.async.} = @@ -888,7 +888,7 @@ proc openStream*(api: DaemonAPI, peer: PeerID, raise newException(DaemonLocalError, "Missing `peer` field!") if pb.getLengthValue(2, raddress) == -1: raise newException(DaemonLocalError, "Missing `address` field!") - stream.raddress = MultiAddress.init(raddress) + stream.raddress = MultiAddress.init(raddress).tryGet() if pb.getLengthValue(3, stream.protocol) == -1: raise newException(DaemonLocalError, "Missing `proto` field!") stream.flags.incl(Outbound) @@ -909,7 +909,7 @@ proc streamHandler(server: StreamServer, transp: StreamTransport) {.async.} = raise newException(DaemonLocalError, "Missing `peer` field!") if pb.getLengthValue(2, raddress) == -1: raise newException(DaemonLocalError, "Missing `address` field!") - stream.raddress = MultiAddress.init(raddress) + stream.raddress = MultiAddress.init(raddress).tryGet() if pb.getLengthValue(3, stream.protocol) == -1: raise newException(DaemonLocalError, "Missing `proto` field!") stream.flags.incl(Inbound) diff --git a/libp2p/multiaddress.nim b/libp2p/multiaddress.nim index d15f58d..9a309ce 100644 --- a/libp2p/multiaddress.nim +++ b/libp2p/multiaddress.nim @@ -8,14 +8,16 @@ ## those terms. ## This module implements MultiAddress. + +{.push raises: [Defect].} + import nativesockets import tables, strutils, net import chronos import multicodec, multihash, multibase, transcoder, vbuffer -import stew/[base58, base32, endians2] +import stew/[base58, base32, endians2, results] from peer import PeerID - -{.deadCodeElim:on.} +export results type MAKind* = enum @@ -42,7 +44,13 @@ type flag*: bool rem*: seq[MultiCodec] - MultiAddressError* = object of CatchableError + MaResult*[T] = Result[T, string] + +const + # These are needed in order to avoid an ambiguity error stemming from + # some cint constants with the same name defined in the posix modules + IPPROTO_TCP = Protocol.IPPROTO_TCP + IPPROTO_UDP = Protocol.IPPROTO_UDP proc ip4StB(s: string, vb: var VBuffer): bool = ## IPv4 stringToBuffer() implementation. @@ -438,33 +446,35 @@ proc shcopy*(m1: var MultiAddress, m2: MultiAddress) = m1.data.offset = m2.data.offset m1.data.length = m2.data.length -proc protoCode*(ma: MultiAddress): MultiCodec = +proc protoCode*(ma: MultiAddress): MaResult[MultiCodec] = ## Returns MultiAddress ``ma`` protocol code. var header: uint64 var vb: MultiAddress shcopy(vb, ma) if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") - let proto = CodeAddresses.getOrDefault(MultiCodec(header)) - if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") - result = proto.mcodec + err("multiaddress: Malformed binary address!") + else: + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) + if proto.kind == None: + err("multiaddress: Unsupported protocol '" & $header & "'") + else: + ok(proto.mcodec) -proc protoName*(ma: MultiAddress): string = +proc protoName*(ma: MultiAddress): MaResult[string] = ## Returns MultiAddress ``ma`` protocol name. var header: uint64 var vb: MultiAddress shcopy(vb, ma) if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") - let proto = CodeAddresses.getOrDefault(MultiCodec(header)) - if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") - result = $(proto.mcodec) + err("multiaddress: Malformed binary address!") + else: + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) + if proto.kind == None: + err("multiaddress: Unsupported protocol '" & $header & "'") + else: + ok($(proto.mcodec)) -proc protoArgument*(ma: MultiAddress, value: var openarray[byte]): int = +proc protoArgument*(ma: MultiAddress, value: var openarray[byte]): MaResult[int] = ## Returns MultiAddress ``ma`` protocol argument value. ## ## If current MultiAddress do not have argument value, then result will be @@ -474,71 +484,85 @@ proc protoArgument*(ma: MultiAddress, value: var openarray[byte]): int = var buffer: seq[byte] shcopy(vb, ma) if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") - let proto = CodeAddresses.getOrDefault(MultiCodec(header)) - if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") - if proto.kind == Fixed: - result = proto.size - if len(value) >= result: - if vb.data.readArray(value.toOpenArray(0, proto.size - 1)) != proto.size: - raise newException(MultiAddressError, "Decoding protocol error") - elif proto.kind in {Length, Path}: - if vb.data.readSeq(buffer) == -1: - raise newException(MultiAddressError, "Decoding protocol error") - result = len(buffer) - if len(value) >= result: - copyMem(addr value[0], addr buffer[0], result) + err("multiaddress: Malformed binary address!") + else: + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) + if proto.kind == None: + err("multiaddress: Unsupported protocol '" & $header & "'") + else: + var res: int + if proto.kind == Fixed: + res = proto.size + if len(value) >= res and + vb.data.readArray(value.toOpenArray(0, proto.size - 1)) != proto.size: + err("multiaddress: Decoding protocol error") + else: + ok(res) + elif proto.kind in {Length, Path}: + if vb.data.readSeq(buffer) == -1: + err("multiaddress: Decoding protocol error") + else: + res = len(buffer) + if len(value) >= res: + copyMem(addr value[0], addr buffer[0], res) + ok(res) + else: + ok(res) -proc protoAddress*(ma: MultiAddress): seq[byte] = +proc protoAddress*(ma: MultiAddress): MaResult[seq[byte]] = ## Returns MultiAddress ``ma`` protocol address binary blob. ## ## If current MultiAddress do not have argument value, then result array will ## be empty. - result = newSeq[byte](len(ma.data.buffer)) - let res = protoArgument(ma, result) - result.setLen(res) + var buffer = newSeq[byte](len(ma.data.buffer)) + let res = ? protoArgument(ma, buffer) + buffer.setLen(res) + ok(buffer) -proc getPart(ma: MultiAddress, index: int): MultiAddress = +proc getPart(ma: MultiAddress, index: int): MaResult[MultiAddress] = var header: uint64 var data = newSeq[byte]() var offset = 0 var vb = ma - result.data = initVBuffer() + var res: MultiAddress + res.data = initVBuffer() while offset <= index: if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") + return err("multiaddress: Malformed binary address!") + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") + return err("multiaddress: Unsupported protocol '" & $header & "'") + elif proto.kind == Fixed: data.setLen(proto.size) if vb.data.readArray(data) != proto.size: - raise newException(MultiAddressError, "Decoding protocol error") + return err("multiaddress: Decoding protocol error") + if offset == index: - result.data.writeVarint(header) - result.data.writeArray(data) - result.data.finish() + res.data.writeVarint(header) + res.data.writeArray(data) + res.data.finish() elif proto.kind in {Length, Path}: if vb.data.readSeq(data) == -1: - raise newException(MultiAddressError, "Decoding protocol error") + return err("multiaddress: Decoding protocol error") + if offset == index: - result.data.writeVarint(header) - result.data.writeSeq(data) - result.data.finish() + res.data.writeVarint(header) + res.data.writeSeq(data) + res.data.finish() elif proto.kind == Marker: if offset == index: - result.data.writeVarint(header) - result.data.finish() + res.data.writeVarint(header) + res.data.finish() inc(offset) + ok(res) -proc `[]`*(ma: MultiAddress, i: int): MultiAddress {.inline.} = +proc `[]`*(ma: MultiAddress, i: int): MaResult[MultiAddress] {.inline.} = ## Returns part with index ``i`` of MultiAddress ``ma``. - result = ma.getPart(i) + ma.getPart(i) -iterator items*(ma: MultiAddress): MultiAddress = +iterator items*(ma: MultiAddress): MaResult[MultiAddress] = ## Iterates over all addresses inside of MultiAddress ``ma``. var header: uint64 var data = newSeq[byte]() @@ -546,75 +570,70 @@ iterator items*(ma: MultiAddress): MultiAddress = while true: if vb.data.isEmpty(): break + var res = MultiAddress(data: initVBuffer()) if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") + yield err(MaResult[MultiAddress], "Malformed binary address!") + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") + yield err(MaResult[MultiAddress], "Unsupported protocol '" & $header & "'") + elif proto.kind == Fixed: data.setLen(proto.size) if vb.data.readArray(data) != proto.size: - raise newException(MultiAddressError, "Decoding protocol error") + yield err(MaResult[MultiAddress], "Decoding protocol error") + res.data.writeVarint(header) res.data.writeArray(data) elif proto.kind in {Length, Path}: if vb.data.readSeq(data) == -1: - raise newException(MultiAddressError, "Decoding protocol error") + yield err(MaResult[MultiAddress], "Decoding protocol error") + res.data.writeVarint(header) res.data.writeSeq(data) elif proto.kind == Marker: res.data.writeVarint(header) res.data.finish() - yield res + yield ok(MaResult[MultiAddress], res) -proc contains*(ma: MultiAddress, codec: MultiCodec): bool {.inline.} = +proc contains*(ma: MultiAddress, codec: MultiCodec): MaResult[bool] {.inline.} = ## Returns ``true``, if address with MultiCodec ``codec`` present in ## MultiAddress ``ma``. - var res = false for item in ma.items: - if item.protoCode() == codec: - res = true - break - result = res + let code = ?(?item).protoCode() + if code == codec: + return ok(true) + ok(false) -proc `[]`*(ma: MultiAddress, codec: MultiCodec): MultiAddress {.inline.} = +proc `[]`*(ma: MultiAddress, codec: MultiCodec): MaResult[MultiAddress] {.inline.} = ## Returns partial MultiAddress with MultiCodec ``codec`` and present in ## MultiAddress ``ma``. - ## - ## Procedure will raise ``MultiAddressError`` if ``codec`` could not be - ## found inside of ``ma``. - var res = MultiAddress(data: initVBuffer()) for item in ma.items: - if item.protoCode == codec: - res = item - break - if res.data.isEmpty(): - raise newException(MultiAddressError, "Codec is not present in address") - result = res + if ?(?item).protoCode == codec: + return item + err("multiaddress: Codec is not present in address") -proc `$`*(value: MultiAddress): string = +proc toString*(value: MultiAddress): MaResult[string] = ## Return string representation of MultiAddress ``value``. var header: uint64 var vb = value var parts = newSeq[string]() var part: string + var res: string while true: if vb.data.isEmpty(): break if vb.data.readVarint(header) == -1: - raise newException(MultiAddressError, "Malformed binary address!") + return err("multiaddress: Malformed binary address!") let proto = CodeAddresses.getOrDefault(MultiCodec(header)) if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & $header & "'") + return err("multiaddress: Unsupported protocol '" & $header & "'") if proto.kind in {Fixed, Length, Path}: if isNil(proto.coder.bufferToString): - raise newException(MultiAddressError, - "Missing protocol '" & $(proto.mcodec) & "' coder") + return err("multiaddress: Missing protocol '" & $(proto.mcodec) & "' coder") if not proto.coder.bufferToString(vb.data, part): - raise newException(MultiAddressError, "Decoding protocol error") + return err("multiaddress: Decoding protocol error") parts.add($(proto.mcodec)) if proto.kind == Path and part[0] == '/': parts.add(part[1..^1]) @@ -623,17 +642,23 @@ proc `$`*(value: MultiAddress): string = elif proto.kind == Marker: parts.add($(proto.mcodec)) if len(parts) > 0: - result = "/" & parts.join("/") + res = "/" & parts.join("/") + ok(res) -proc protocols*(value: MultiAddress): seq[MultiCodec] = +proc `$`*(value: MultiAddress): string {.raises: [Defect, ResultError[string]].} = + ## Return string representation of MultiAddress ``value``. + value.toString().tryGet() + +proc protocols*(value: MultiAddress): MaResult[seq[MultiCodec]] = ## Returns list of protocol codecs inside of MultiAddress ``value``. - result = newSeq[MultiCodec]() + var res = newSeq[MultiCodec]() for item in value.items(): - result.add(item.protoCode()) + res.add(?(?item).protoCode()) + ok(res) proc hex*(value: MultiAddress): string = ## Return hexadecimal string representation of MultiAddress ``value``. - result = $(value.data) + $(value.data) proc write*(vb: var VBuffer, ma: MultiAddress) {.inline.} = ## Write MultiAddress value ``ma`` to buffer ``vb``. @@ -667,200 +692,239 @@ proc validate*(ma: MultiAddress): bool = discard result = true -proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, - value: openarray[byte]): MultiAddress = +proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, value: openarray[byte]): MaResult[MultiAddress] = ## Initialize MultiAddress object from protocol id ``protocol`` and array ## of bytes ``value``. let proto = CodeAddresses.getOrDefault(protocol) if proto.kind == None: - raise newException(MultiAddressError, "Protocol not found") - result.data = initVBuffer() - result.data.writeVarint(cast[uint64](proto.mcodec)) - if proto.kind in {Fixed, Length, Path}: - if len(value) == 0: - raise newException(MultiAddressError, "Value must not be empty array") - if proto.kind == Fixed: - result.data.writeArray(value) - else: - var data = newSeq[byte](len(value)) - copyMem(addr data[0], unsafeAddr value[0], len(value)) - result.data.writeSeq(data) - result.data.finish() + err("multiaddress: Protocol not found") + else: + var res: MultiAddress + res.data = initVBuffer() + res.data.writeVarint(cast[uint64](proto.mcodec)) + if proto.kind in {Fixed, Length, Path}: + if len(value) == 0: + err("multiaddress: Value must not be empty array") + else: + if proto.kind == Fixed: + res.data.writeArray(value) + else: + var data = newSeq[byte](len(value)) + copyMem(addr data[0], unsafeAddr value[0], len(value)) + res.data.writeSeq(data) + res.data.finish() + ok(res) -proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, - value: PeerID): MultiAddress {.inline.} = +proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, value: PeerID): MaResult[MultiAddress] {.inline.} = ## Initialize MultiAddress object from protocol id ``protocol`` and peer id ## ``value``. init(mtype, protocol, value.data) -proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec): MultiAddress = +proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec): MaResult[MultiAddress] = ## Initialize MultiAddress object from protocol id ``protocol``. let proto = CodeAddresses.getOrDefault(protocol) if proto.kind == None: - raise newException(MultiAddressError, "Protocol not found") - result.data = initVBuffer() - if proto.kind != Marker: - raise newException(MultiAddressError, "Protocol missing value") - result.data.writeVarint(cast[uint64](proto.mcodec)) - result.data.finish() + err("multiaddress: Protocol not found") + else: + var res: MultiAddress + res.data = initVBuffer() + if proto.kind != Marker: + raise newException(MultiAddressError, "Protocol missing value") + res.data.writeVarint(cast[uint64](proto.mcodec)) + res.data.finish() + ok(res) -proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, - value: int): MultiAddress = +proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec, value: int): MaResult[MultiAddress] = ## Initialize MultiAddress object from protocol id ``protocol`` and integer ## ``value``. This procedure can be used to instantiate ``tcp``, ``udp``, ## ``dccp`` and ``sctp`` MultiAddresses. var allowed = [multiCodec("tcp"), multiCodec("udp"), multiCodec("dccp"), multiCodec("sctp")] if protocol notin allowed: - raise newException(MultiAddressError, - "Incorrect protocol for integer value") - let proto = CodeAddresses.getOrDefault(protocol) - result.data = initVBuffer() - result.data.writeVarint(cast[uint64](proto.mcodec)) - if value < 0 or value > 65535: - raise newException(MultiAddressError, "Incorrect integer value") - result.data.writeArray(toBytesBE(cast[uint16](value))) - result.data.finish() + err("multiaddress: Incorrect protocol for integer value") + else: + let proto = CodeAddresses.getOrDefault(protocol) + var res: MultiAddress + res.data = initVBuffer() + res.data.writeVarint(cast[uint64](proto.mcodec)) + if value < 0 or value > 65535: + err("multiaddress: Incorrect integer value") + else: + res.data.writeArray(toBytesBE(cast[uint16](value))) + res.data.finish() + ok(res) proc getProtocol(name: string): MAProtocol {.inline.} = let mc = MultiCodec.codec(name) if mc != InvalidMultiCodec: result = CodeAddresses.getOrDefault(mc) -proc init*(mtype: typedesc[MultiAddress], value: string): MultiAddress = +proc init*(mtype: typedesc[MultiAddress], value: string): MaResult[MultiAddress] = ## Initialize MultiAddress object from string representation ``value``. var parts = value.trimRight('/').split('/') if len(parts[0]) != 0: - raise newException(MultiAddressError, - "Invalid MultiAddress, must start with `/`") - var offset = 1 - result.data = initVBuffer() - while offset < len(parts): - let part = parts[offset] - let proto = getProtocol(part) - if proto.kind == None: - raise newException(MultiAddressError, - "Unsupported protocol '" & part & "'") - if proto.kind in {Fixed, Length, Path}: - if isNil(proto.coder.stringToBuffer): - raise newException(MultiAddressError, - "Missing protocol '" & part & "' transcoder") - if offset + 1 >= len(parts): - raise newException(MultiAddressError, - "Missing protocol '" & part & "' argument") + err("multiaddress: Invalid MultiAddress, must start with `/`") + else: + var offset = 1 + var res: MultiAddress + res.data = initVBuffer() + while offset < len(parts): + let part = parts[offset] + let proto = getProtocol(part) + if proto.kind == None: + return err("multiaddress: Unsupported protocol '" & part & "'") + else: + if proto.kind in {Fixed, Length, Path}: + if isNil(proto.coder.stringToBuffer): + return err("multiaddress: Missing protocol '" & part & "' transcoder") - if proto.kind in {Fixed, Length}: - result.data.write(proto.mcodec) - let res = proto.coder.stringToBuffer(parts[offset + 1], result.data) - if not res: - raise newException(MultiAddressError, - "Error encoding `$1/$2`" % [part, parts[offset + 1]]) - offset += 2 - elif proto.kind == Path: - var path = "/" & (parts[(offset + 1)..^1].join("/")) - result.data.write(proto.mcodec) - if not proto.coder.stringToBuffer(path, result.data): - raise newException(MultiAddressError, - "Error encoding `$1/$2`" % [part, path]) - break - elif proto.kind == Marker: - result.data.write(proto.mcodec) - offset += 1 - result.data.finish() + if offset + 1 >= len(parts): + return err("multiaddress: Missing protocol '" & part & "' argument") -proc init*(mtype: typedesc[MultiAddress], data: openarray[byte]): MultiAddress = + if proto.kind in {Fixed, Length}: + res.data.write(proto.mcodec) + let res = proto.coder.stringToBuffer(parts[offset + 1], res.data) + if not res: + return err("multiaddress: Error encoding `" & part & "/" & parts[offset + 1] & "`") + offset += 2 + + elif proto.kind == Path: + var path = "/" & (parts[(offset + 1)..^1].join("/")) + res.data.write(proto.mcodec) + if not proto.coder.stringToBuffer(path, res.data): + return err("multiaddress: Error encoding `" & part & "/" & path & "`") + + break + elif proto.kind == Marker: + res.data.write(proto.mcodec) + offset += 1 + res.data.finish() + ok(res) + + +proc init*(mtype: typedesc[MultiAddress], data: openarray[byte]): MaResult[MultiAddress] = ## Initialize MultiAddress with array of bytes ``data``. if len(data) == 0: - raise newException(MultiAddressError, "Address could not be empty!") - result.data = initVBuffer() - result.data.buffer.setLen(len(data)) - copyMem(addr result.data.buffer[0], unsafeAddr data[0], len(data)) - if not result.validate(): - raise newException(MultiAddressError, "Incorrect MultiAddress!") + err("multiaddress: Address could not be empty!") + else: + var res: MultiAddress + res.data = initVBuffer() + res.data.buffer.setLen(len(data)) + copyMem(addr res.data.buffer[0], unsafeAddr data[0], len(data)) + if not res.validate(): + err("multiaddress: Incorrect MultiAddress!") + else: + ok(res) proc init*(mtype: typedesc[MultiAddress]): MultiAddress = ## Initialize empty MultiAddress. result.data = initVBuffer() proc init*(mtype: typedesc[MultiAddress], - address: IpAddress, protocol: Protocol, port: Port): MultiAddress = + address: IpAddress, protocol: Protocol, port: Port): MaResult[MultiAddress] = ## Initialize MultiAddress using stdlib's net.IpAddress (IPv4/IPv6) and ## net.Protocol (UDP/TCP) information. - result.data = initVBuffer() + var res: MultiAddress + res.data = initVBuffer() let familyProto = case address.family of IpAddressFamily.IPv4: getProtocol("ip4") of IpAddressFamily.IPv6: getProtocol("ip6") let protoProto = case protocol of IPPROTO_TCP: getProtocol("tcp") of IPPROTO_UDP: getProtocol("udp") - else: raise newException(AssertionError, - "protocol should be either TCP or UDP") - result.data.write(familyProto.mcodec) - if not familyProto.coder.stringToBuffer($address, result.data): - raise newException(MultiAddressError, "Error encoding IPv4/IPv6 address") - result.data.write(protoProto.mcodec) - if not protoProto.coder.stringToBuffer($port, result.data): - raise newException(MultiAddressError, "Error encoding port number") - result.data.finish() + else: return err("multiaddress: protocol should be either TCP or UDP") + + res.data.write(familyProto.mcodec) + if not familyProto.coder.stringToBuffer($address, res.data): + err("multiaddress: Error encoding IPv4/IPv6 address") + else: + res.data.write(protoProto.mcodec) + if not protoProto.coder.stringToBuffer($port, res.data): + err("multiaddress: Error encoding port number") + else: + res.data.finish() + ok(res) proc init*(mtype: typedesc[MultiAddress], address: TransportAddress, - protocol: Protocol = IPPROTO_TCP): MultiAddress = + protocol = IPPROTO_TCP): MaResult[MultiAddress] = ## Initialize MultiAddress using chronos.TransportAddress (IPv4/IPv6/Unix) ## and protocol information (UDP/TCP). - result.data = initVBuffer() + var res: MultiAddress + res.data = initVBuffer() let protoProto = case protocol of IPPROTO_TCP: getProtocol("tcp") of IPPROTO_UDP: getProtocol("udp") - else: raise newException(AssertionError, - "protocol should be either TCP or UDP") + else: return err("multiaddress: protocol should be either TCP or UDP") if address.family == AddressFamily.IPv4: - result.data.write(getProtocol("ip4").mcodec) - result.data.writeArray(address.address_v4) - result.data.write(protoProto.mcodec) - discard protoProto.coder.stringToBuffer($address.port, result.data) + res.data.write(getProtocol("ip4").mcodec) + res.data.writeArray(address.address_v4) + res.data.write(protoProto.mcodec) + discard protoProto.coder.stringToBuffer($address.port, res.data) elif address.family == AddressFamily.IPv6: - result.data.write(getProtocol("ip6").mcodec) - result.data.writeArray(address.address_v6) - result.data.write(protoProto.mcodec) - discard protoProto.coder.stringToBuffer($address.port, result.data) + res.data.write(getProtocol("ip6").mcodec) + res.data.writeArray(address.address_v6) + res.data.write(protoProto.mcodec) + discard protoProto.coder.stringToBuffer($address.port, res.data) elif address.family == AddressFamily.Unix: - result.data.write(getProtocol("unix").mcodec) - result.data.writeSeq(address.address_un) - result.data.finish() + res.data.write(getProtocol("unix").mcodec) + res.data.writeSeq(address.address_un) + res.data.finish() + ok(res) proc isEmpty*(ma: MultiAddress): bool = ## Returns ``true``, if MultiAddress ``ma`` is empty or non initialized. result = len(ma.data) == 0 -proc `&`*(m1, m2: MultiAddress): MultiAddress = +proc concat*(m1, m2: MultiAddress): MaResult[MultiAddress] = + var res: MultiAddress + res.data = initVBuffer() + res.data.buffer = m1.data.buffer & m2.data.buffer + if not res.validate(): + err("multiaddress: Incorrect MultiAddress!") + else: + ok(res) + +proc append*(m1: var MultiAddress, m2: MultiAddress): MaResult[void] = + m1.data.buffer &= m2.data.buffer + if not m1.validate(): + err("multiaddress: Incorrect MultiAddress!") + else: + ok() + +proc `&`*(m1, m2: MultiAddress): MultiAddress {.raises: [Defect, ResultError[string]].} = ## Concatenates two addresses ``m1`` and ``m2``, and returns result. ## ## This procedure performs validation of concatenated result and can raise ## exception on error. - result.data = initVBuffer() - result.data.buffer = m1.data.buffer & m2.data.buffer - if not result.validate(): - raise newException(MultiAddressError, "Incorrect MultiAddress!") + concat(m1, m2).tryGet() -proc `&=`*(m1: var MultiAddress, m2: MultiAddress) = +proc `&=`*(m1: var MultiAddress, m2: MultiAddress) {.raises: [Defect, ResultError[string]].} = ## Concatenates two addresses ``m1`` and ``m2``. ## ## This procedure performs validation of concatenated result and can raise ## exception on error. - m1.data.buffer &= m2.data.buffer - if not m1.validate(): - raise newException(MultiAddressError, "Incorrect MultiAddress!") + ## + m1.append(m2).tryGet() proc isWire*(ma: MultiAddress): bool = ## Returns ``true`` if MultiAddress ``ma`` is one of: ## - {IP4}/{TCP, UDP} ## - {IP6}/{TCP, UDP} ## - {UNIX}/{PATH} - var state = 0 + var + state = 0 try: - for part in ma.items(): + for rpart in ma.items(): + if rpart.isErr(): + return false + let part = rpart.get() + if state == 0: - let code = part.protoCode() + let rcode = part.protoCode() + if rcode.isErr(): + return false + let code = rcode.get() + if code == multiCodec("ip4") or code == multiCodec("ip6"): inc(state) continue @@ -871,8 +935,12 @@ proc isWire*(ma: MultiAddress): bool = result = false break elif state == 1: - if part.protoCode == multiCodec("tcp") or - part.protoCode == multiCodec("udp"): + let rcode = part.protoCode() + if rcode.isErr(): + return false + let code = rcode.get() + + if code == multiCodec("tcp") or code == multiCodec("udp"): inc(state) result = true else: @@ -912,16 +980,20 @@ proc matchPart(pat: MaPattern, protos: seq[MultiCodec]): MaPatResult = proc match*(pat: MaPattern, address: MultiAddress): bool = ## Match full ``address`` using pattern ``pat`` and return ``true`` if ## ``address`` satisfies pattern. - var protos = address.protocols() - let res = matchPart(pat, protos) - result = res.flag and (len(res.rem) == 0) + let protos = address.protocols() + if protos.isErr(): + return false + let res = matchPart(pat, protos.get()) + res.flag and (len(res.rem) == 0) proc matchPartial*(pat: MaPattern, address: MultiAddress): bool = ## Match prefix part of ``address`` using pattern ``pat`` and return ## ``true`` if ``address`` starts with pattern. - var protos = address.protocols() - let res = matchPart(pat, protos) - result = res.flag + let protos = address.protocols() + if protos.isErr(): + return false + let res = matchPart(pat, protos.get()) + res.flag proc `$`*(pat: MaPattern): string = ## Return pattern ``pat`` as string. diff --git a/libp2p/multibase.nim b/libp2p/multibase.nim index 6fc8886..f5f824d 100644 --- a/libp2p/multibase.nim +++ b/libp2p/multibase.nim @@ -11,8 +11,12 @@ ## ## TODO: ## 1. base32z +## + +{.push raises: [Defect].} + import tables -import stew/[base32, base58, base64] +import stew/[base32, base58, base64, results] type MultibaseStatus* {.pure.} = enum @@ -20,22 +24,20 @@ type MultiBase* = object - MBCodeSize = proc(length: int): int {.nimcall.} + MBCodeSize = proc(length: int): int {.nimcall, raises: [Defect].} MBCodec = object code: char name: string encr: proc(inbytes: openarray[byte], outbytes: var openarray[char], - outlen: var int): MultibaseStatus {.nimcall.} + outlen: var int): MultibaseStatus {.nimcall, raises: [Defect].} decr: proc(inbytes: openarray[char], outbytes: var openarray[byte], - outlen: var int): MultibaseStatus {.nimcall.} + outlen: var int): MultibaseStatus {.nimcall, raises: [Defect].} encl: MBCodeSize decl: MBCodeSize - MultiBaseError* = object of CatchableError - proc idd(inbytes: openarray[char], outbytes: var openarray[byte], outlen: var int): MultibaseStatus = let length = len(inbytes) @@ -439,48 +441,50 @@ proc decode*(mbtype: typedesc[MultiBase], inbytes: openarray[char], result = mb.decr(inbytes.toOpenArray(1, length - 1), outbytes, outlen) proc encode*(mbtype: typedesc[MultiBase], encoding: string, - inbytes: openarray[byte]): string = + inbytes: openarray[byte]): Result[string, string] = ## Encode array ``inbytes`` using MultiBase encoding scheme ``encoding`` and ## return encoded string. let length = len(inbytes) let mb = NameMultibases.getOrDefault(encoding) if len(mb.name) == 0: - raise newException(MultiBaseError, "Encoding scheme is incorrect!") + return err("multibase: Encoding scheme is incorrect!") if isNil(mb.encr) or isNil(mb.encl): - raise newException(MultiBaseError, "Encoding scheme is not supported!") + return err("multibase: Encoding scheme is not supported!") var buffer: string if length > 0: buffer = newString(mb.encl(length) + 1) var outlen = 0 let res = mb.encr(inbytes, buffer.toOpenArray(1, buffer.high), outlen) if res != MultiBaseStatus.Success: - raise newException(MultiBaseError, "Encoding error [" & $res & "]") + return err("multibase: Encoding error [" & $res & "]") buffer.setLen(outlen + 1) buffer[0] = mb.code else: buffer = newString(1) buffer[0] = mb.code - result = buffer + ok(buffer) -proc decode*(mbtype: typedesc[MultiBase], inbytes: openarray[char]): seq[byte] = +proc decode*(mbtype: typedesc[MultiBase], inbytes: openarray[char]): Result[seq[byte], string] = ## Decode MultiBase encoded array ``inbytes`` and return decoded sequence of ## bytes. let length = len(inbytes) if length == 0: - raise newException(MultiBaseError, "Could not decode zero-length string") + return err("multibase: Could not decode zero-length string") let mb = CodeMultibases.getOrDefault(inbytes[0]) if len(mb.name) == 0: - raise newException(MultiBaseError, "MultiBase scheme is incorrect!") + return err("multibase: MultiBase scheme is incorrect!") if isNil(mb.decr) or isNil(mb.decl): - raise newException(MultiBaseError, "MultiBase scheme is not supported!") + return err("multibase: MultiBase scheme is not supported!") if length == 1: - result = newSeq[byte]() + let empty: seq[byte] = @[] + ok(empty) # empty else: var buffer = newSeq[byte](mb.decl(length - 1)) var outlen = 0 let res = mb.decr(inbytes.toOpenArray(1, length - 1), buffer, outlen) if res != MultiBaseStatus.Success: - raise newException(MultiBaseError, "Decoding error [" & $res & "]") - result = buffer - result.setLen(outlen) + err("multibase: Decoding error [" & $res & "]") + else: + buffer.setLen(outlen) + ok(buffer) diff --git a/libp2p/multihash.nim b/libp2p/multihash.nim index a0845a6..bc0f928 100644 --- a/libp2p/multihash.nim +++ b/libp2p/multihash.nim @@ -482,20 +482,20 @@ proc decode*(mhtype: typedesc[MultiHash], data: openarray[byte], proc validate*(mhtype: typedesc[MultiHash], data: openarray[byte]): bool = ## Returns ``true`` if array of bytes ``data`` has correct MultiHash inside. var code, size: uint64 - var res: VarintStatus + var res: VarintResult[void] if len(data) < 2: return false let last = data.high var offset = 0 var length = 0 res = LP.getUVarint(data.toOpenArray(offset, last), length, code) - if res != VarintStatus.Success: + if res.isErr(): return false offset += length if offset >= len(data): return false res = LP.getUVarint(data.toOpenArray(offset, last), length, size) - if res != VarintStatus.Success: + if res.isErr(): return false offset += length if size > 0x7FFF_FFFF'u64: diff --git a/libp2p/protobuf/minprotobuf.nim b/libp2p/protobuf/minprotobuf.nim index f1ca7b1..caef22c 100644 --- a/libp2p/protobuf/minprotobuf.nim +++ b/libp2p/protobuf/minprotobuf.nim @@ -8,6 +8,9 @@ ## those terms. ## This module implements minimal Google's ProtoBuf primitives. + +{.push raises: [Defect].} + import ../varint const @@ -142,15 +145,15 @@ proc initProtoBuffer*(options: set[ProtoFlags] = {}): ProtoBuffer = proc write*(pb: var ProtoBuffer, field: ProtoField) = ## Encode protobuf's field ``field`` and store it to protobuf's buffer ``pb``. var length = 0 - var res: VarintStatus + var res: VarintResult[void] pb.buffer.setLen(len(pb.buffer) + vsizeof(field)) res = PB.putUVarint(pb.toOpenArray(), length, protoHeader(field)) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) pb.offset += length case field.kind of ProtoFieldKind.Varint: res = PB.putUVarint(pb.toOpenArray(), length, field.vint) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) pb.offset += length of ProtoFieldKind.Fixed64: doAssert(pb.isEnough(8)) @@ -174,7 +177,7 @@ proc write*(pb: var ProtoBuffer, field: ProtoField) = pb.offset += 4 of ProtoFieldKind.Length: res = PB.putUVarint(pb.toOpenArray(), length, uint(len(field.vbuffer))) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) pb.offset += length doAssert(pb.isEnough(len(field.vbuffer))) if len(field.vbuffer) > 0: @@ -192,7 +195,7 @@ proc finish*(pb: var ProtoBuffer) = let pos = 10 - vsizeof(size) var usedBytes = 0 let res = PB.putUVarint(pb.buffer.toOpenArray(pos, 9), usedBytes, size) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) pb.offset = pos elif WithUint32BeLength in pb.options: let size = uint(len(pb.buffer) - 4) @@ -218,8 +221,7 @@ proc getVarintValue*(data: var ProtoBuffer, field: int, var header = 0'u64 var soffset = data.offset - if not data.isEmpty() and - PB.getUVarint(data.toOpenArray(), length, header) == VarintStatus.Success: + if not data.isEmpty() and PB.getUVarint(data.toOpenArray(), length, header).isOk(): data.offset += length if header == protoHeader(field, Varint): if not data.isEmpty(): @@ -227,7 +229,7 @@ proc getVarintValue*(data: var ProtoBuffer, field: int, let res = getSVarint(data.toOpenArray(), length, value) else: let res = PB.getUVarint(data.toOpenArray(), length, value) - if res == VarintStatus.Success: + if res.isOk(): data.offset += length result = length return @@ -243,12 +245,10 @@ proc getLengthValue*[T: string|seq[byte]](data: var ProtoBuffer, field: int, var soffset = data.offset result = -1 buffer.setLen(0) - if not data.isEmpty() and - PB.getUVarint(data.toOpenArray(), length, header) == VarintStatus.Success: + if not data.isEmpty() and PB.getUVarint(data.toOpenArray(), length, header).isOk(): data.offset += length if header == protoHeader(field, Length): - if not data.isEmpty() and - PB.getUVarint(data.toOpenArray(), length, ssize) == VarintStatus.Success: + if not data.isEmpty() and PB.getUVarint(data.toOpenArray(), length, ssize).isOk(): data.offset += length if ssize <= MaxMessageSize and data.isEnough(int(ssize)): buffer.setLen(ssize) @@ -280,12 +280,10 @@ proc enterSubmessage*(pb: var ProtoBuffer): int = var msize = 0'u64 var soffset = pb.offset - if not pb.isEmpty() and - PB.getUVarint(pb.toOpenArray(), length, header) == VarintStatus.Success: + if not pb.isEmpty() and PB.getUVarint(pb.toOpenArray(), length, header).isOk(): pb.offset += length if (header and 0x07'u64) == cast[uint64](ProtoFieldKind.Length): - if not pb.isEmpty() and - PB.getUVarint(pb.toOpenArray(), length, msize) == VarintStatus.Success: + if not pb.isEmpty() and PB.getUVarint(pb.toOpenArray(), length, msize).isOk(): pb.offset += length if msize <= MaxMessageSize and pb.isEnough(int(msize)): pb.length = int(msize) diff --git a/libp2p/protocols/identify.nim b/libp2p/protocols/identify.nim index 60e1f36..cdf051c 100644 --- a/libp2p/protocols/identify.nim +++ b/libp2p/protocols/identify.nim @@ -78,7 +78,7 @@ proc decodeMsg*(buf: seq[byte]): IdentifyInfo = while pb.getBytes(2, address) > 0: if len(address) != 0: var copyaddr = address - var ma = MultiAddress.init(copyaddr) + var ma = MultiAddress.init(copyaddr).tryGet() result.addrs.add(ma) trace "read address bytes from message", address = ma address.setLen(0) @@ -91,7 +91,7 @@ proc decodeMsg*(buf: seq[byte]): IdentifyInfo = var observableAddr = newSeq[byte]() if pb.getBytes(4, observableAddr) > 0: # attempt to read the observed addr - var ma = MultiAddress.init(observableAddr) + var ma = MultiAddress.init(observableAddr).tryGet() trace "read observedAddr from message", address = ma result.observedAddr = some(ma) diff --git a/libp2p/standard_setup.nim b/libp2p/standard_setup.nim index 2d53b1f..b7312e4 100644 --- a/libp2p/standard_setup.nim +++ b/libp2p/standard_setup.nim @@ -21,7 +21,7 @@ export switch, peer, peerinfo, connection, multiaddress, crypto proc newStandardSwitch*(privKey = none(PrivateKey), - address = MultiAddress.init("/ip4/127.0.0.1/tcp/0"), + address = MultiAddress.init("/ip4/127.0.0.1/tcp/0").tryGet(), triggerSelf = false, gossip = false, verifySignature = libp2p_pubsub_verify, diff --git a/libp2p/stream/lpstream.nim b/libp2p/stream/lpstream.nim index e22aaca..899691c 100644 --- a/libp2p/stream/lpstream.nim +++ b/libp2p/stream/lpstream.nim @@ -120,9 +120,9 @@ proc readVarint*(conn: LPStream): Future[uint64] {.async, gcsafe.} = for i in 0.. 0 + result = address.protocols.tryGet().filterIt( it == multiCodec("tcp") ).len > 0 diff --git a/libp2p/transports/transport.nim b/libp2p/transports/transport.nim index 09eaa18..a80945b 100644 --- a/libp2p/transports/transport.nim +++ b/libp2p/transports/transport.nim @@ -56,7 +56,7 @@ method handles*(t: Transport, address: MultiAddress): bool {.base, gcsafe.} = # by default we skip circuit addresses to avoid # having to repeat the check in every transport - address.protocols.filterIt( it == multiCodec("p2p-circuit") ).len == 0 + address.protocols.tryGet().filterIt( it == multiCodec("p2p-circuit") ).len == 0 method localAddress*(t: Transport): MultiAddress {.base, gcsafe.} = ## get the local address of the transport in case started with 0.0.0.0:0 diff --git a/libp2p/varint.nim b/libp2p/varint.nim index 9cb044b..5ace9fe 100644 --- a/libp2p/varint.nim +++ b/libp2p/varint.nim @@ -15,17 +15,23 @@ ## - LibP2P varint, which is able to encode only 63bits of uint64 number and ## maximum size of encoded value is 9 octets (bytes). ## https://github.com/multiformats/unsigned-varint + +{.push raises: [Defect].} + import bitops, typetraits +import stew/results +export results type - VarintStatus* {.pure.} = enum + VarintError* {.pure.} = enum Error, - Success, Overflow, Incomplete, Overlong, Overrun + VarintResult*[T] = Result[T, VarintError] + PB* = object ## Use this type to specify Google ProtoBuf's varint encoding LP* = object @@ -49,7 +55,6 @@ type LPSomeVarint* = LPSomeUVarint SomeVarint* = PBSomeVarint | LPSomeVarint SomeUVarint* = PBSomeUVarint | LPSomeUVarint - VarintError* = object of CatchableError proc vsizeof*(x: SomeUVarint): int {.inline.} = ## Returns number of bytes required to encode integer ``x`` as varint. @@ -99,7 +104,7 @@ proc vsizeof*(x: PBZigVarint): int {.inline.} = proc getUVarint*[T: PB|LP](vtype: typedesc[T], pbytes: openarray[byte], outlen: var int, - outval: var SomeUVarint): VarintStatus = + outval: var SomeUVarint): VarintResult[void] = ## Decode `unsigned varint` from buffer ``pbytes`` and store it to ``outval``. ## On success ``outlen`` will be set to number of bytes processed while ## decoding `unsigned varint`. @@ -130,38 +135,35 @@ proc getUVarint*[T: PB|LP](vtype: typedesc[T], const MaxBits = byte(sizeof(outval) * 8) var shift = 0'u8 - result = VarintStatus.Incomplete outlen = 0 outval = type(outval)(0) for i in 0..= MaxBits: - result = VarintStatus.Overflow outlen = 0 outval = type(outval)(0) - break + return err(VarintError.Overflow) else: outval = outval or (type(outval)(b and 0x7F'u8) shl shift) shift += 7 inc(outlen) if (b and 0x80'u8) == 0'u8: - result = VarintStatus.Success - break - if result == VarintStatus.Incomplete: - outlen = 0 - outval = type(outval)(0) - - when vtype is LP: - if result == VarintStatus.Success: + # done, success if outlen != vsizeof(outval): outval = type(outval)(0) outlen = 0 - result = VarintStatus.Overlong + return err(VarintError.Overlong) + else: + return ok() + + outlen = 0 + outval = type(outval)(0) + err(VarintError.Incomplete) proc putUVarint*[T: PB|LP](vtype: typedesc[T], pbytes: var openarray[byte], outlen: var int, - outval: SomeUVarint): VarintStatus = + outval: SomeUVarint): VarintResult[void] = ## Encode `unsigned varint` ``outval`` and store it to array ``pbytes``. ## ## On success ``outlen`` will hold number of bytes (octets) used to encode @@ -185,8 +187,7 @@ proc putUVarint*[T: PB|LP](vtype: typedesc[T], when vtype is LP: if sizeof(outval) == 8: if (uint64(outval) and 0x8000_0000_0000_0000'u64) != 0'u64: - result = Overflow - return + return err(VarintError.Overflow) if value <= type(outval)(0x7F): buffer[0] = byte(outval and 0xFF) @@ -201,12 +202,12 @@ proc putUVarint*[T: PB|LP](vtype: typedesc[T], outlen = k if len(pbytes) >= k: copyMem(addr pbytes[0], addr buffer[0], k) - result = VarintStatus.Success + ok() else: - result = VarintStatus.Overrun + err(VarintError.Overrun) proc getSVarint*(pbytes: openarray[byte], outsize: var int, - outval: var PBSomeSVarint): VarintStatus {.inline.} = + outval: var PBSomeSVarint): VarintResult[void] {.inline.} = ## Decode signed integer (``int32`` or ``int64``) from buffer ``pbytes`` ## and store it to ``outval``. ## @@ -230,12 +231,13 @@ proc getSVarint*(pbytes: openarray[byte], outsize: var int, else: var value: uint32 - result = PB.getUVarint(pbytes, outsize, value) - if result == VarintStatus.Success: + let res = PB.getUVarint(pbytes, outsize, value) + if res.isOk(): outval = cast[type(outval)](value) + res proc getSVarint*(pbytes: openarray[byte], outsize: var int, - outval: var PBZigVarint): VarintStatus {.inline.} = + outval: var PBZigVarint): VarintResult[void] {.inline.} = ## Decode Google ProtoBuf's zigzag encoded signed integer (``sint32`` or ## ``sint64`` ) from buffer ``pbytes`` and store it to ``outval``. ## @@ -259,15 +261,16 @@ proc getSVarint*(pbytes: openarray[byte], outsize: var int, else: var value: uint32 - result = PB.getUVarint(pbytes, outsize, value) - if result == VarintStatus.Success: + let res = PB.getUVarint(pbytes, outsize, value) + if res.isOk(): if (value and type(value)(1)) != type(value)(0): outval = cast[type(outval)](not(value shr 1)) else: outval = cast[type(outval)](value shr 1) + res proc putSVarint*(pbytes: var openarray[byte], outsize: var int, - outval: PBZigVarint): VarintStatus {.inline.} = + outval: PBZigVarint): VarintResult[void] {.inline.} = ## Encode signed integer ``outval`` using ProtoBuffer's zigzag encoding ## (``sint32`` or ``sint64``) and store it to array ``pbytes``. ## @@ -292,10 +295,10 @@ proc putSVarint*(pbytes: var openarray[byte], outsize: var int, not(uint32(outval) shl 1) else: uint32(outval) shl 1 - result = PB.putUVarint(pbytes, outsize, value) + PB.putUVarint(pbytes, outsize, value) proc putSVarint*(pbytes: var openarray[byte], outsize: var int, - outval: PBSomeSVarint): VarintStatus {.inline.} = + outval: PBSomeSVarint): VarintResult[void] {.inline.} = ## Encode signed integer ``outval`` (``int32`` or ``int64``) and store it to ## array ``pbytes``. ## @@ -309,84 +312,87 @@ proc putSVarint*(pbytes: var openarray[byte], outsize: var int, ## Maximum encoded length of 64bit integer is 10 octets. ## Maximum encoded length of 32bit integer is 5 octets. when sizeof(outval) == 8: - result = PB.putUVarint(pbytes, outsize, uint64(outval)) + PB.putUVarint(pbytes, outsize, uint64(outval)) else: - result = PB.putUVarint(pbytes, outsize, uint32(outval)) + PB.putUVarint(pbytes, outsize, uint32(outval)) template varintFatal(msg) = const m = msg {.fatal: m.} proc putVarint*[T: PB|LP](vtype: typedesc[T], pbytes: var openarray[byte], - nbytes: var int, value: SomeVarint): VarintStatus {.inline.} = + nbytes: var int, value: SomeVarint): VarintResult[void] {.inline.} = when vtype is PB: when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): - result = putSVarint(pbytes, nbytes, value) + putSVarint(pbytes, nbytes, value) elif (type(value) is PBSomeUVarint): - result = PB.putUVarint(pbytes, nbytes, value) + PB.putUVarint(pbytes, nbytes, value) else: varintFatal("Protobuf's varint do not support type [" & typetraits.name(type(value)) & "]") elif vtype is LP: when (type(value) is LPSomeVarint): - result = LP.putUVarint(pbytes, nbytes, value) + LP.putUVarint(pbytes, nbytes, value) else: varintFatal("LibP2P's varint do not support type [" & typetraits.name(type(value)) & "]") proc getVarint*[T: PB|LP](vtype: typedesc[T], pbytes: openarray[byte], nbytes: var int, - value: var SomeVarint): VarintStatus {.inline.} = + value: var SomeVarint): VarintResult[void] {.inline.} = when vtype is PB: when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): - result = getSVarint(pbytes, nbytes, value) + getSVarint(pbytes, nbytes, value) elif (type(value) is PBSomeUVarint): - result = PB.getUVarint(pbytes, nbytes, value) + PB.getUVarint(pbytes, nbytes, value) else: varintFatal("Protobuf's varint do not support type [" & typetraits.name(type(value)) & "]") elif vtype is LP: when (type(value) is LPSomeVarint): - result = LP.getUVarint(pbytes, nbytes, value) + LP.getUVarint(pbytes, nbytes, value) else: varintFatal("LibP2P's varint do not support type [" & typetraits.name(type(value)) & "]") proc encodeVarint*(vtype: typedesc[PB], - value: PBSomeVarint): seq[byte] {.inline.} = + value: PBSomeVarint): VarintResult[seq[byte]] {.inline.} = ## Encode integer to Google ProtoBuf's `signed/unsigned varint` and returns - ## sequence of bytes as result. + ## sequence of bytes as buffer. var outsize = 0 - result = newSeqOfCap[byte](10) + var buffer = newSeqOfCap[byte](10) when sizeof(value) == 4: - result.setLen(5) + buffer.setLen(5) else: - result.setLen(10) + buffer.setLen(10) when (type(value) is PBSomeSVarint) or (type(value) is PBZigVarint): - let res = putSVarint(result, outsize, value) + let res = putSVarint(buffer, outsize, value) else: - let res = PB.putUVarint(result, outsize, value) - if res == VarintStatus.Success: - result.setLen(outsize) + let res = PB.putUVarint(buffer, outsize, value) + if res.isOk(): + buffer.setLen(outsize) + ok(buffer) else: - raise newException(VarintError, "Error '" & $res & "'") + err(res.error()) + proc encodeVarint*(vtype: typedesc[LP], - value: LPSomeVarint): seq[byte] {.inline.} = + value: LPSomeVarint): VarintResult[seq[byte]] {.inline.} = ## Encode integer to LibP2P `unsigned varint` and returns sequence of bytes - ## as result. + ## as buffer. var outsize = 0 - result = newSeqOfCap[byte](9) + var buffer = newSeqOfCap[byte](9) when sizeof(value) == 1: - result.setLen(2) + buffer.setLen(2) elif sizeof(value) == 2: - result.setLen(3) + buffer.setLen(3) elif sizeof(value) == 4: - result.setLen(5) + buffer.setLen(5) else: - result.setLen(9) - let res = LP.putUVarint(result, outsize, value) - if res == VarintStatus.Success: - result.setLen(outsize) + buffer.setLen(9) + let res = LP.putUVarint(buffer, outsize, value) + if res.isOk(): + buffer.setLen(outsize) + ok(buffer) else: - raise newException(VarintError, "Error '" & $res & "'") + err(res.error) diff --git a/libp2p/vbuffer.nim b/libp2p/vbuffer.nim index 1ee9b52..6f315c7 100644 --- a/libp2p/vbuffer.nim +++ b/libp2p/vbuffer.nim @@ -8,6 +8,9 @@ ## those terms. ## This module implements variable buffer. + +{.push raises: [Defect].} + import varint, strutils type @@ -66,7 +69,7 @@ proc writePBVarint*(vb: var VBuffer, value: PBSomeUVarint) = vb.buffer.setLen(len(vb.buffer) + vsizeof(v)) let res = PB.putUVarint(toOpenArray(vb.buffer, vb.offset, vb.buffer.high), length, v) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) vb.offset += length proc writeLPVarint*(vb: var VBuffer, value: LPSomeUVarint) = @@ -77,7 +80,7 @@ proc writeLPVarint*(vb: var VBuffer, value: LPSomeUVarint) = vb.buffer.setLen(len(vb.buffer) + vsizeof(v)) let res = LP.putUVarint(toOpenArray(vb.buffer, vb.offset, vb.buffer.high), length, v) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) vb.offset += length proc writeVarint*(vb: var VBuffer, value: LPSomeUVarint) = @@ -90,7 +93,7 @@ proc writeSeq*[T: byte|char](vb: var VBuffer, value: openarray[T]) = vb.buffer.setLen(len(vb.buffer) + vsizeof(uint(len(value))) + len(value)) let res = LP.putUVarint(toOpenArray(vb.buffer, vb.offset, vb.buffer.high), length, uint(len(value))) - doAssert(res == VarintStatus.Success) + doAssert(res.isOk()) vb.offset += length if len(value) > 0: copyMem(addr vb.buffer[vb.offset], unsafeAddr value[0], len(value)) @@ -120,7 +123,7 @@ proc peekVarint*(vb: var VBuffer, value: var LPSomeUVarint): int = if not vb.isEmpty(): let res = LP.getUVarint( toOpenArray(vb.buffer, vb.offset, vb.buffer.high), length, value) - if res == VarintStatus.Success: + if res.isOk(): result = length proc peekSeq*[T: string|seq[byte]](vb: var VBuffer, value: var T): int = @@ -135,8 +138,7 @@ proc peekSeq*[T: string|seq[byte]](vb: var VBuffer, value: var T): int = var length = 0 var size = 0'u64 if not vb.isEmpty() and - LP.getUVarint(toOpenArray(vb.buffer, vb.offset, vb.buffer.high), - length, size) == VarintStatus.Success: + LP.getUVarint(toOpenArray(vb.buffer, vb.offset, vb.buffer.high), length, size).isOk(): vb.offset += length result = length if vb.isEnough(int(size)): diff --git a/libp2p/wire.nim b/libp2p/wire.nim index 16d087f..870be29 100644 --- a/libp2p/wire.nim +++ b/libp2p/wire.nim @@ -22,38 +22,40 @@ proc initTAddress*(ma: MultiAddress): TransportAddress = ## MultiAddress must be wire address, e.g. ``{IP4, IP6, UNIX}/{TCP, UDP}``. var state = 0 var pbuf: array[2, byte] - for part in ma.items(): - let code = part.protoCode() + for rpart in ma.items(): + let + part = rpart.tryGet() + rcode = part.protoCode() + code = rcode.tryGet() + if state == 0: if code == multiCodec("ip4"): result = TransportAddress(family: AddressFamily.IPv4) - if part.protoArgument(result.address_v4) == 0: + if part.protoArgument(result.address_v4).tryGet() == 0: raise newException(TransportAddressError, "Incorrect IPv4 address") inc(state) elif code == multiCodec("ip6"): result = TransportAddress(family: AddressFamily.IPv6) - if part.protoArgument(result.address_v6) == 0: + if part.protoArgument(result.address_v6).tryGet() == 0: raise newException(TransportAddressError, "Incorrect IPv6 address") inc(state) elif code == multiCodec("unix"): result = TransportAddress(family: AddressFamily.Unix) - if part.protoArgument(result.address_un) == 0: + if part.protoArgument(result.address_un).tryGet() == 0: raise newException(TransportAddressError, "Incorrect Unix address") result.port = Port(1) break else: - raise newException(TransportAddressError, - "Could not initialize address!") + raise newException(TransportAddressError, "Could not initialize address!") elif state == 1: if code == multiCodec("tcp") or code == multiCodec("udp"): - if part.protoArgument(pbuf) == 0: + if part.protoArgument(pbuf).tryGet() == 0: raise newException(TransportAddressError, "Incorrect port") result.port = Port((cast[uint16](pbuf[0]) shl 8) or cast[uint16](pbuf[1])) break else: - raise newException(TransportAddressError, - "Could not initialize address!") + raise newException(TransportAddressError, "Could not initialize address!") proc connect*(ma: MultiAddress, bufferSize = DefaultStreamBufferSize, child: StreamTransport = nil): Future[StreamTransport] {.async.} = @@ -63,7 +65,7 @@ proc connect*(ma: MultiAddress, bufferSize = DefaultStreamBufferSize, let address = initTAddress(ma) if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}: - if ma[1].protoCode() != multiCodec("tcp"): + if ma[1].tryGet().protoCode().tryGet() != multiCodec("tcp"): raise newException(TransportAddressError, "Incorrect address type!") result = await connect(address, bufferSize, child) @@ -79,7 +81,7 @@ proc createStreamServer*[T](ma: MultiAddress, ## Create new TCP stream server which bounds to ``ma`` address. var address = initTAddress(ma) if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}: - if ma[1].protoCode() != multiCodec("tcp"): + if ma[1].tryGet().protoCode().tryGet() != multiCodec("tcp"): raise newException(TransportAddressError, "Incorrect address type!") result = createStreamServer(address, cbproc, flags, udata, sock, backlog, bufferSize, child, init) @@ -100,10 +102,10 @@ proc createAsyncSocket*(ma: MultiAddress): AsyncFD = return asyncInvalidSocket if address.family in {AddressFamily.IPv4, AddressFamily.IPv6}: - if ma[1].protoCode() == multiCodec("udp"): + if ma[1].tryGet().protoCode().tryGet() == multiCodec("udp"): socktype = SockType.SOCK_DGRAM protocol = Protocol.IPPROTO_UDP - elif ma[1].protoCode() == multiCodec("tcp"): + elif ma[1].tryGet().protoCode().tryGet() == multiCodec("tcp"): socktype = SockType.SOCK_STREAM protocol = Protocol.IPPROTO_TCP elif address.family in {AddressFamily.Unix}: diff --git a/tests/testidentify.nim b/tests/testidentify.nim index 7adb4a3..44f1929 100644 --- a/tests/testidentify.nim +++ b/tests/testidentify.nim @@ -20,7 +20,7 @@ suite "Identify": test "handle identify message": proc testHandle(): Future[bool] {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() let remoteSecKey = PrivateKey.random(ECDSA).get() let remotePeerInfo = PeerInfo.init(remoteSecKey, [ma], @@ -65,7 +65,7 @@ suite "Identify": test "handle failed identify": proc testHandleError() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() var remotePeerInfo = PeerInfo.init(PrivateKey.random(ECDSA).get(), [ma]) let identifyProto1 = newIdentify(remotePeerInfo) let msListen = newMultistream() diff --git a/tests/testinterop.nim b/tests/testinterop.nim index 9f04a1d..015be34 100644 --- a/tests/testinterop.nim +++ b/tests/testinterop.nim @@ -46,16 +46,15 @@ proc readLp*(s: StreamTransport): Future[seq[byte]] {.async, gcsafe.} = var size: uint length: int - res: VarintStatus + res: VarintResult[void] result = newSeq[byte](10) for i in 0.. 0.uint: await s.readExactly(addr result[0], int(size)) @@ -68,7 +67,7 @@ proc createNode*(privKey: Option[PrivateKey] = none(PrivateKey), if privKey.isNone: seckey = some(PrivateKey.random(RSA).get()) - var peerInfo = NativePeerInfo.init(seckey.get(), [Multiaddress.init(address)]) + var peerInfo = NativePeerInfo.init(seckey.get(), [Multiaddress.init(address).tryGet()]) proc createMplex(conn: Connection): Muxer = newMplex(conn) let mplexProvider = newMuxerProvider(createMplex, MplexCodec) let transports = @[Transport(TcpTransport.init())] diff --git a/tests/testmplex.nim b/tests/testmplex.nim index 16e31a2..eea9963 100644 --- a/tests/testmplex.nim +++ b/tests/testmplex.nim @@ -214,7 +214,7 @@ suite "Mplex": test "e2e - read/write receiver": proc testNewStream() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() var done = newFuture[void]() proc connHandler(conn: Connection) {.async, gcsafe.} = @@ -252,7 +252,7 @@ suite "Mplex": test "e2e - read/write receiver lazy": proc testNewStream() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() var done = newFuture[void]() proc connHandler(conn: Connection) {.async, gcsafe.} = @@ -292,7 +292,7 @@ suite "Mplex": test "e2e - write fragmented": proc testNewStream() {.async.} = let - ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() listenJob = newFuture[void]() var bigseq = newSeqOfCap[uint8](MaxMsgSize * 2) @@ -338,7 +338,7 @@ suite "Mplex": test "e2e - read/write initiator": proc testNewStream() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() let done = newFuture[void]() proc connHandler(conn: Connection) {.async, gcsafe.} = @@ -375,7 +375,7 @@ suite "Mplex": test "e2e - multiple streams": proc testNewStream() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() let done = newFuture[void]() proc connHandler(conn: Connection) {.async, gcsafe.} = @@ -417,7 +417,7 @@ suite "Mplex": test "e2e - multiple read/write streams": proc testNewStream() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() let done = newFuture[void]() proc connHandler(conn: Connection) {.async, gcsafe.} = @@ -461,7 +461,7 @@ suite "Mplex": test "jitter - channel should be able to handle erratic read/writes": proc test() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() var complete = newFuture[void]() const MsgSize = 1024 @@ -529,7 +529,7 @@ suite "Mplex": test "jitter - channel should handle 1 byte read/write": proc test() {.async.} = - let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0") + let ma: MultiAddress = Multiaddress.init("/ip4/0.0.0.0/tcp/0").tryGet() var complete = newFuture[void]() const MsgSize = 512 diff --git a/tests/testmultiaddress.nim b/tests/testmultiaddress.nim index df398bc..77042a5 100644 --- a/tests/testmultiaddress.nim +++ b/tests/testmultiaddress.nim @@ -297,23 +297,18 @@ suite "MultiAddress test suite": test "go-multiaddr success test vectors": for item in SuccessVectors: - var a = MultiAddress.init(item) + var a = MultiAddress.init(item).get() check a.isEmpty() == false check a.validate() == true test "go-multiaddr failure test vectors": for item in FailureVectors: - var r = false - try: - discard MultiAddress.init(item) - except: - r = true - check r == true + check MultiAddress.init(item).isErr() test "rust-multiaddr success test vectors": ## Rust test vectors are with changed UDP encoding and without WSS for i in 0..