diff --git a/examples/chat.nim b/examples/chat.nim new file mode 100644 index 0000000..4eaba4d --- /dev/null +++ b/examples/chat.nim @@ -0,0 +1,129 @@ +import asyncdispatch2, nimcrypto, strutils +import ../libp2p/daemon/daemonapi, ../libp2p/[base58, multiaddress] + +const + ConsoleAddress = "/tmp/console-chat.sock" + ServerAddress = "/tmp/remote-chat.sock" + ServerProtocols = @["/test-chat-stream"] + +type + CustomData = ref object + api: DaemonAPI + remotes: seq[StreamTransport] + +proc threadMain(a: int) {.thread.} = + ## This procedure performs reading from `stdin` and sends data over + ## unix domain socket to main thread. + var transp = waitFor connect(initTAddress(ConsoleAddress)) + + while true: + var line = stdin.readLine() + let res = waitFor transp.write(line & "\r\n") + +proc serveThread(server: StreamServer, + transp: StreamTransport) {.async.} = + ## This procedure perform readin on local unix domain socket and + ## sends data to remote clients. + var udata = getUserData[CustomData](server) + + proc remoteReader(transp: StreamTransport) {.async.} = + while true: + var line = await transp.readLine() + if len(line) == 0: + break + echo ">> ", line + + while true: + try: + var line = await transp.readLine() + if line.startsWith("/connect"): + var parts = line.split(" ") + if len(parts) == 2: + var peerId = Base58.decode(parts[1]) + var address = MultiAddress.init(P_P2PCIRCUIT) + address &= MultiAddress.init(P_P2P, peerId) + echo "= Searching for peer ", parts[1] + var id = await udata.api.dhtFindPeer(peerId) + echo "= Peer " & parts[1] & " found at addresses:" + for item in id.addresses: + echo $item + echo "= Connecting to peer ", $address + await udata.api.connect(peerId, @[address], 30) + echo "= Opening stream to peer chat ", parts[1] + var stream = await udata.api.openStream(peerId, ServerProtocols) + udata.remotes.add(stream.transp) + echo "= Connected to peer chat ", parts[1] + asyncCheck remoteReader(stream.transp) + elif line.startsWith("/search"): + var parts = line.split(" ") + if len(parts) == 2: + var peerId = Base58.decode(parts[1]) + echo "= Searching for peer ", parts[1] + var id = await udata.api.dhtFindPeer(peerId) + echo "= Peer " & parts[1] & " found at addresses:" + for item in id.addresses: + echo $item + elif line.startsWith("/consearch"): + var parts = line.split(" ") + if len(parts) == 2: + var peerId = Base58.decode(parts[1]) + echo "= Searching for peers connected to peer ", parts[1] + var peers = await udata.api.dhtFindPeersConnectedToPeer(peerId) + echo "= Found ", len(peers), " connected to peer ", parts[1] + for item in peers: + var peer = Base58.encode(item.peer) + var addresses = newSeq[string]() + var relay = false + for a in item.addresses: + addresses.add($a) + if a.protoName() == "/p2p-circuit": + relay = true + break + if relay: + echo peer, " * ", " [", addresses.join(", "), "]" + else: + echo peer, " [", addresses.join(", "), "]" + elif line.startsWith("/exit"): + quit(0) + else: + var msg = line & "\r\n" + echo "<< ", line + var pending = newSeq[Future[int]]() + for item in udata.remotes: + pending.add(item.write(msg)) + if len(pending) > 0: + var results = await all(pending) + except: + break + +proc main() {.async.} = + var data = new CustomData + data.remotes = newSeq[StreamTransport]() + + var lserver = createStreamServer(initTAddress(ConsoleAddress), + serveThread, udata = data) + lserver.start() + var thread: Thread[int] + thread.createThread(threadMain, 0) + + echo "= Starting P2P node" + data.api = await newDaemonApi({DHTFull, Bootstrap}) + await sleepAsync(3000) + var id = await data.api.identity() + + proc streamHandler(api: DaemonAPI, stream: P2PStream) {.async.} = + echo "= Peer ", Base58.encode(stream.peer), " joined chat" + data.remotes.add(stream.transp) + while true: + var line = await stream.transp.readLine() + if len(line) == 0: + break + echo ">> ", line + + await data.api.addHandler(ServerProtocols, streamHandler) + echo "= Your PeerID is ", Base58.encode(id.peer) + +when isMainModule: + waitFor(main()) + while true: + poll() diff --git a/libp2p.nimble b/libp2p.nimble index bb93ae4..3ea3aa1 100644 --- a/libp2p.nimble +++ b/libp2p.nimble @@ -5,11 +5,18 @@ version = "0.0.1" author = "Status Research & Development GmbH" description = "LibP2P implementation" license = "MIT" -skipDirs = @["tests", "Nim"] +skipDirs = @["tests", "examples", "Nim"] requires "nim > 0.18.0", + "nimcrypto", "https://github.com/status-im/nim-asyncdispatch2" task test, "Runs the test suite": exec "nim c -r tests/testvarint" - exec "nim c -r tests/testdaemon" \ No newline at end of file + exec "nim c -r tests/testbase58" + exec "nim c -r tests/testbase32" + exec "nim c -r tests/testmultiaddress" + exec "nim c -r tests/testmultihash" + exec "nim c -r tests/testmultibase" + exec "nim c -r tests/testcid" + exec "nim c -r tests/testdaemon" diff --git a/libp2p/base32.nim b/libp2p/base32.nim new file mode 100644 index 0000000..0a3fa7c --- /dev/null +++ b/libp2p/base32.nim @@ -0,0 +1,290 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implements BASE32 encoding and decoding procedures. +## This module supports RFC4648's BASE32. + +type + Base32Status* {.pure.} = enum + Error, + Success, + Incorrect, + Overrun + + Base32Alphabet* = object + decode*: array[128, int8] + encode*: array[32, uint8] + + Base32Upper* = object + ## Type to use RFC4868 alphabet in uppercase without padding + Base32Lower* = object + ## Type to use RFC4868 alphabet in lowercase without padding + Base32UpperPad* = object + ## Type to use RFC4868 alphabet in uppercase with padding + Base32LowerPad* = object + ## Type to use RFC4868 alphabet in lowercase with padding + HexBase32Upper* = object + ## Type to use RFC4868-HEX alphabet in uppercase without padding + HexBase32Lower* = object + ## Type to use RFC4868-HEX alphabet in lowercase without padding + HexBase32UpperPad* = object + ## Type to use RFC4868-HEX alphabet in uppercase with padding + HexBase32LowerPad* = object + ## Type to use RFC4868-HEX alphabet in lowercase with padding + Base32* = Base32Upper + ## By default we are using RFC4868 alphabet in uppercase without padding + Base32PadTypes* = Base32UpperPad | Base32LowerPad | + HexBase32UpperPad | HexBase32LowerPad + ## All types with padding support + Base32NoPadTypes* = Base32Upper | Base32Lower | HexBase32Upper | + HexBase32Lower + ## All types without padding + Base32Types* = Base32NoPadTypes | Base32PadTypes + ## Supported types + + Base32Error* = object of Exception + ## Base32 specific exception type + +proc newAlphabet32*(s: string): Base32Alphabet = + doAssert(len(s) == 32) + for i in 0..= 1: + outbytes[0] = chr(inbytes[0] shr 3) + outbytes[1] = chr((inbytes[0] and 7'u8) shl 2) + result = 2 + if length >= 2: + outbytes[1] = chr(cast[byte](outbytes[1]) or cast[byte](inbytes[1] shr 6)) + outbytes[2] = chr((inbytes[1] shr 1) and 31'u8) + outbytes[3] = chr((inbytes[1] and 1'u8) shl 4) + result = 4 + if length >= 3: + outbytes[3] = chr(cast[byte](outbytes[3]) or (inbytes[2] shr 4)) + outbytes[4] = chr((inbytes[2] and 15'u8) shl 1) + result = 5 + if length >= 4: + outbytes[4] = chr(cast[byte](outbytes[4]) or (inbytes[3] shr 7)) + outbytes[5] = chr((inbytes[3] shr 2) and 31'u8) + outbytes[6] = chr((inbytes[3] and 3'u8) shl 3) + result = 7 + if length >= 5: + outbytes[6] = chr(cast[byte](outbytes[6]) or (inbytes[4] shr 5)) + outbytes[7] = chr(inbytes[4] and 31'u8) + result = 8 + +proc convert8to5(inbytes: openarray[byte], outbytes: var openarray[byte], + length: int): int {.inline.} = + if length >= 2: + outbytes[0] = inbytes[0] shl 3 + outbytes[0] = outbytes[0] or (inbytes[1] shr 2) + result = 1 + if length >= 4: + outbytes[1] = (inbytes[1] and 3'u8) shl 6 + outbytes[1] = outbytes[1] or (inbytes[2] shl 1) + outbytes[1] = outbytes[1] or (inbytes[3] shr 4) + result = 2 + if length >= 5: + outbytes[2] = (inbytes[3] and 15'u8) shl 4 + outbytes[2] = outbytes[2] or (inbytes[4] shr 1) + result = 3 + if length >= 7: + outbytes[3] = (inbytes[4] and 1'u8) shl 7 + outbytes[3] = outbytes[3] or (inbytes[5] shl 2) + outbytes[3] = outbytes[3] or (inbytes[6] shr 3) + result = 4 + if length >= 8: + outbytes[4] = (inbytes[6] and 7'u8) shl 5 + outbytes[4] = outbytes[4] or (inbytes[7] and 31'u8) + result = 5 + +proc encode*(btype: typedesc[Base32Types], inbytes: openarray[byte], + outstr: var openarray[char], outlen: var int): Base32Status = + ## Encode array of bytes ``inbytes`` using BASE32 encoding and store + ## result to ``outstr``. On success ``Base32Status.Success`` will be returned + ## and ``outlen`` will be set to number of characters stored inside of + ## ``outstr``. If length of ``outstr`` is not enough then + ## ``Base32Status.Overrun`` will be returned and ``outlen`` will be set to + ## number of characters required. + when (btype is Base32Upper) or (btype is Base32UpperPad): + const alphabet = RFCUpperCaseAlphabet + elif (btype is Base32Lower) or (btype is Base32LowerPad): + const alphabet = RFCLowerCaseAlphabet + elif (btype is HexBase32Upper) or (btype is HexBase32UpperPad): + const alphabet = HEXUpperCaseAlphabet + elif (btype is HexBase32Lower) or (btype is HexBase32LowerPad): + const alphabet = HEXLowerCaseAlphabet + + if len(inbytes) == 0: + outlen = 0 + return Base32Status.Success + + let length = btype.encodedLength(len(inbytes)) + if length > len(outstr): + outlen = length + return Base32Status.Overrun + + let reminder = len(inbytes) mod 5 + let limit = len(inbytes) - reminder + var i, k: int + while i < limit: + discard convert5to8(inbytes.toOpenArray(i, i + 4), + outstr.toOpenArray(k, k + 7), 5) + for j in 0..7: + outstr[k + j] = chr(alphabet.encode[ord(outstr[k + j])]) + k += 8 + i += 5 + + if reminder != 0: + let left = convert5to8(inbytes.toOpenArray(i, i + reminder - 1), + outstr.toOpenArray(k, length - 1), reminder) + for j in 0..(left - 1): + outstr[k] = chr(alphabet.encode[ord(outstr[k])]) + inc(k) + when (btype is Base32UpperPad) or (btype is Base32LowerPad) or + (btype is HexBase32UpperPad) or (btype is HexBase32LowerPad): + while k < len(outstr): + outstr[k] = '=' + inc(k) + outlen = k + result = Base32Status.Success + +proc encode*(btype: typedesc[Base32Types], + inbytes: openarray[byte]): string {.inline.} = + ## Encode array of bytes ``inbytes`` using BASE32 encoding and return + ## encoded string. + if len(inbytes) == 0: + result = "" + else: + var length = 0 + result = newString(btype.encodedLength(len(inbytes))) + if btype.encode(inbytes, result, length) == Base32Status.Success: + result.setLen(length) + else: + result = "" + +proc decode*[T: byte|char](btype: typedesc[Base32Types], instr: openarray[T], + outbytes: var openarray[byte], outlen: var int): Base32Status = + ## Decode BASE32 string and store array of bytes to ``outbytes``. On success + ## ``Base32Status.Success`` will be returned and ``outlen`` will be set + ## to number of bytes stored. + ## + ## ## If length of ``outbytes`` is not enough to store decoded bytes, then + ## ``Base32Status.Overrun`` will be returned and ``outlen`` will be set to + ## number of bytes required. + when (btype is Base32Upper) or (btype is Base32UpperPad): + const alphabet = RFCUpperCaseAlphabet + elif (btype is Base32Lower) or (btype is Base32LowerPad): + const alphabet = RFCLowerCaseAlphabet + elif (btype is HexBase32Upper) or (btype is HexBase32UpperPad): + const alphabet = HEXUpperCaseAlphabet + elif (btype is HexBase32Lower) or (btype is HexBase32LowerPad): + const alphabet = HEXLowerCaseAlphabet + + if len(instr) == 0: + outlen = 0 + return Base32Status.Success + + let length = btype.decodedLength(len(instr)) + if length > len(outbytes): + outlen = length + return Base32Status.Overrun + + var inlen = len(instr) + when (btype is Base32PadTypes): + for i in countdown(inlen - 1, 0): + if instr[i] != '=': + break + dec(inlen) + + let reminder = inlen mod 8 + let limit = inlen - reminder + var buffer: array[8, byte] + var i, k: int + while i < limit: + for j in 0..<8: + if (cast[byte](instr[i + j]) and 0x80'u8) != 0: + outlen = 0 + zeroMem(addr outbytes[0], i + 8) + return Base32Status.Incorrect + let ch = alphabet.decode[int8(instr[i + j])] + if ch == -1: + outlen = 0 + zeroMem(addr outbytes[0], i + 8) + return Base32Status.Incorrect + buffer[j] = cast[byte](ch) + discard convert8to5(buffer, outbytes.toOpenArray(k, k + 4), 8) + k += 5 + i += 8 + + var left = 0 + if reminder != 0: + if reminder == 1 or reminder == 3 or reminder == 6: + outlen = 0 + zeroMem(addr outbytes[0], i + 8) + return Base32Status.Incorrect + for j in 0.. hi) or (carry != 0'u32): + carry = carry + uint32(256'u32 * buffer[j]) + buffer[j] = cast[byte](carry mod 58) + carry = carry div 58 + dec(j) + hi = j + inc(i) + + j = 0 + while (j < size) and (buffer[j] == 0x00'u8): + inc(j) + + let needed = zcount + size - j + outlen = needed + if len(outstr) < needed: + result = Base58Status.Overrun + else: + for k in 0.. m: + result = Base58Status.Overrun + return + break + inc(m) + dec(outlen) + + if m < binsz: + moveMem(addr outbytes[zcount], addr outbytes[binsz - outlen], outlen) + outlen += zcount + result = Base58Status.Success + +proc decode*(btype: typedesc[Base58C], instr: string): seq[byte] = + ## Decode BASE58 string ``instr`` and return sequence of bytes as result. + if len(instr) > 0: + var size = len(instr) + 4 + result = newSeq[byte](size) + if btype.decode(instr, result, size) == Base58Status.Success: + result.setLen(size) + else: + raise newException(Base58Error, "Incorrect base58 string") diff --git a/libp2p/cid.nim b/libp2p/cid.nim new file mode 100644 index 0000000..93c88a4 --- /dev/null +++ b/libp2p/cid.nim @@ -0,0 +1,262 @@ +## Nim-LibP2P +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implementes CID (Content IDentifier). +import tables +import multibase, multicodec, multihash, vbuffer, varint, base58 + +type + CidStatus* {.pure.} = enum + Error, Success, Incorrect, Overrun + + CidVersion* = enum + CIDvIncorrect, CIDv0, CIDv1, CIDvReserved + + Cid* = object + cidver*: CidVersion + mcodec*: MultiCodec + hpos*: int + data*: VBuffer + + CidError* = object of Exception + +const + ContentIdsList = [ + multiCodec("raw"), + multiCodec("dag-pb"), + multiCodec("dag-cbor"), + multiCodec("dag-json"), + multiCodec("git-raw"), + multiCodec("eth-block"), + multiCodec("eth-block-list"), + multiCodec("eth-tx-trie"), + multiCodec("eth-tx"), + multiCodec("eth-tx-receipt-trie"), + multiCodec("eth-tx-receipt"), + multiCodec("eth-state-trie"), + multiCodec("eth-account-snapshot"), + multiCodec("eth-storage-trie"), + multiCodec("bitcoin-block"), + multiCodec("bitcoin-tx"), + multiCodec("zcash-block"), + multiCodec("zcash-tx"), + multiCodec("stellar-block"), + multiCodec("stellar-tx"), + multiCodec("decred-block"), + multiCodec("decred-tx"), + multiCodec("dash-block"), + multiCodec("dash-tx"), + multiCodec("torrent-info"), + multiCodec("torrent-file"), + multiCodec("ed25519-pub") + ] + +proc initCidCodeTable(): Table[int, MultiCodec] {.compileTime.} = + result = initTable[int, MultiCodec]() + for item in ContentIdsList: + result[int(item)] = item + +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: + 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, + len(vb.buffer) - 1)): + return CidStatus.Incorrect + vb.finish() + cid.cidver = CIDv1 + cid.mcodec = mcodec + cid.hpos = offset + cid.data = vb + result = CidStatus.Success + +proc decode(data: openarray[char], cid: var Cid): CidStatus = + var buffer: seq[byte] + var plen = 0 + if len(data) < 2: + return CidStatus.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 + buffer.setLen(plen) + if len(buffer) == 0: + let length = MultiBase.decodedLength(data[0], len(data)) + if length == -1: + return CidStatus.Incorrect + buffer = newSeq[byte](length) + if MultiBase.decode(data, buffer, plen) != MultiBaseStatus.Success: + return CidStatus.Incorrect + buffer.setLen(plen) + if buffer[0] == 0x12'u8: + return CidStatus.Incorrect + result = decode(buffer, cid) + +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 + if len(data) < 2: + return false + let last = len(data) - 1 + if len(data) == 34: + if data[0] == 0x12'u8 and data[1] == 0x20'u8: + return true + var offset = 0 + var length = 0 + res = LP.getUVarint(data.toOpenArray(offset, last), length, version) + if res != VarintStatus.Success: + return false + if version != 1'u64: + return false + offset += length + if offset >= len(data): + return false + res = LP.getUVarint(data.toOpenArray(offset, last), length, codec) + if res != VarintStatus.Success: + return false + var mcodec = CodeContentIds.getOrDefault(cast[int](codec), InvalidMultiCodec) + if mcodec == InvalidMultiCodec: + return false + if not MultiHash.validate(data.toOpenArray(offset, last)): + return false + result = true + +proc mhash*(cid: Cid): MultiHash = + ## 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, + len(cid.data) - 1)) + +proc contentType*(cid: Cid): MultiCodec = + ## Returns content type part of CID + if cid.cidver notin {CIDv0, CIDv1}: + raise newException(CidError, "Incorrect CID!") + result = 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 = + ## Create new content identifier using array of bytes or string ``data``. + if decode(data, result) != CidStatus.Success: + raise newException(CidError, "Incorrect CID!") + +proc init*(ctype: typedesc[Cid], version: CidVersion, content: MultiCodec, + hash: MultiHash): Cid = + ## Create new content identifier using content type ``content`` and + ## MultiHash ``hash`` using version ``version``. + ## + ## To create ``CIDv0`` you need to use: + ## Cid.init(CIDv0, multiCodec("dag-pb"), MultiHash.digest("sha2-256", data)) + ## + ## All other encodings and hashes are not supported by CIDv0. + result.cidver = version + + if version == CIDv0: + if content != multiCodec("dag-pb"): + raise newException(CidError, + "CIDv0 supports only `dag-pb` content type!") + result.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() + 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() + else: + raise newException(CidError, "CID version is not supported" & $version) + +proc `==`*(a: Cid, b: Cid): bool = + ## Compares content identifiers ``a`` and ``b``, returns ``true`` if hashes + ## are equal, ``false`` otherwise. + if a.mcodec == b.mcodec: + var ah, bh: MultiHash + if MultiHash.decode(a.data.buffer.toOpenArray(a.hpos, + len(a.data) - 1), ah) == -1: + return false + if MultiHash.decode(b.data.buffer.toOpenArray(b.hpos, + len(b.data) - 1), bh) == -1: + return false + result = (ah == bh) + +proc base58*(cid: Cid): string = + ## Get BASE58 encoded string representation of content identifier ``cid``. + result = BTCBase58.encode(cid.data.buffer) + +proc hex*(cid: Cid): string = + ## Get hexadecimal string representation of content identifier ``cid``. + result = $(cid.data) + +proc repr*(cid: Cid): string = + ## Get string representation of content identifier ``cid``. + result = $(cid.cidver) + result.add("/") + result.add($(cid.mcodec)) + result.add("/") + result.add($(cid.mhash())) + +proc write*(vb: var VBuffer, cid: Cid) {.inline.} = + ## Write CID value ``cid`` to buffer ``vb``. + vb.writeArray(cid.data.buffer) + +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) + +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) diff --git a/libp2p/daemon/daemonapi.nim b/libp2p/daemon/daemonapi.nim index 49549d6..ec3de51 100644 --- a/libp2p/daemon/daemonapi.nim +++ b/libp2p/daemon/daemonapi.nim @@ -8,15 +8,17 @@ ## those terms. ## This module implementes API for `go-libp2p-daemon`. -import os, osproc, strutils, tables, streams +import os, osproc, strutils, tables, streams, strtabs import asyncdispatch2 -import ../varint, ../protobuf/minprotobuf, transpool +import ../varint, ../multiaddress, ../base58, ../cid +import ../protobuf/minprotobuf when not defined(windows): import posix const DefaultSocketPath* = "/tmp/p2pd.sock" + DefaultSocketPattern* = "/tmp/p2pd-$1.sock" DefaultDaemonFile* = "p2pd" type @@ -29,6 +31,7 @@ type LIST_PEERS = 5, CONNMANAGER = 6, DISCONNECT = 7 + PUBSUB = 8 DHTRequestType* {.pure.} = enum FIND_PEER = 0, @@ -46,6 +49,12 @@ type UNTAG_PEER = 1, TRIM = 2 + PSRequestType* {.pure.} = enum + GET_TOPICS = 0, + LIST_PEERS = 1, + PUBLISH = 2, + SUBSCRIBE = 3 + ResponseKind* = enum Malformed, Error, @@ -57,6 +66,7 @@ type IDENTITY = 4, DHT = 5, PEERINFO = 6 + PUBSUB = 7 DHTResponseType* {.pure.} = enum BEGIN = 0, @@ -65,16 +75,24 @@ type PeerID* = seq[byte] MultiProtocol* = string - MultiAddress* = seq[byte] - CID* = seq[byte] LibP2PPublicKey* = seq[byte] DHTValue* = seq[byte] P2PStreamFlags* {.pure.} = enum None, Closed, Inbound, Outbound - P2PDaemonFlags* {.pure.} = enum - DHTClient, DHTFull, Bootstrap + P2PDaemonFlags* = enum + DHTClient, ## Start daemon in DHT client mode + DHTFull, ## Start daemon with full DHT support + Bootstrap, ## Start daemon with bootstrap + WaitBootstrap, ## Start daemon with bootstrap and wait until daemon + ## establish connection to at least 2 peers + Logging, ## Enable capture daemon `stderr` + Verbose, ## Set daemon logging to DEBUG level + PSFloodSub, ## Enable `FloodSub` protocol in daemon + PSGossipSub, ## Enable `GossipSub` protocol in daemon + PSNoSign, ## Disable pubsub message signing (default true) + PSStrictSign ## Force strict checking pubsub message signature P2PStream* = ref object flags*: set[P2PStreamFlags] @@ -83,8 +101,12 @@ type protocol*: string transp*: StreamTransport + P2PServer = object + server*: StreamServer + address*: TransportAddress + DaemonAPI* = ref object - pool*: TransportPool + # pool*: TransportPool flags*: set[P2PDaemonFlags] address*: TransportAddress sockname*: string @@ -92,18 +114,39 @@ type ucounter*: int process*: Process handlers*: Table[string, P2PStreamCallback] - servers*: seq[StreamServer] + servers*: seq[P2PServer] + log*: string + loggerFut*: Future[void] PeerInfo* = object peer*: PeerID addresses*: seq[MultiAddress] + PubsubTicket* = ref object + topic*: string + handler*: P2PPubSubCallback + transp*: StreamTransport + + PubSubMessage* = object + peer*: PeerID + data*: seq[byte] + seqno*: seq[byte] + topics*: seq[string] + signature*: seq[byte] + key*: seq[byte] + P2PStreamCallback* = proc(api: DaemonAPI, stream: P2PStream): Future[void] {.gcsafe.} + P2PPubSubCallback* = proc(api: DaemonAPI, + ticket: PubsubTicket, + message: PubSubMessage): Future[bool] {.gcsafe.} DaemonRemoteError* = object of Exception DaemonLocalError* = object of Exception + +var daemonsCount {.threadvar.}: int + proc requestIdentity(): ProtoBuffer = ## https://github.com/libp2p/go-libp2p-daemon/blob/master/conn.go ## Processing function `doIdentify(req *pb.Request)`. @@ -112,14 +155,17 @@ proc requestIdentity(): ProtoBuffer = result.finish() proc requestConnect(peerid: PeerID, - addresses: openarray[MultiAddress]): ProtoBuffer = + addresses: openarray[MultiAddress], + timeout = 0): ProtoBuffer = ## https://github.com/libp2p/go-libp2p-daemon/blob/master/conn.go ## Processing function `doConnect(req *pb.Request)`. result = initProtoBuffer({WithVarintLength}) var msg = initProtoBuffer() msg.write(initProtoField(1, peerid)) for item in addresses: - msg.write(initProtoField(2, item)) + msg.write(initProtoField(2, item.data.buffer)) + if timeout > 0: + msg.write(initProtoField(3, timeout)) result.write(initProtoField(1, cast[uint](RequestType.CONNECT))) result.write(initProtoField(2, msg)) result.finish() @@ -135,7 +181,8 @@ proc requestDisconnect(peerid: PeerID): ProtoBuffer = result.finish() proc requestStreamOpen(peerid: PeerID, - protocols: openarray[string]): ProtoBuffer = + protocols: openarray[string], + timeout = 0): ProtoBuffer = ## https://github.com/libp2p/go-libp2p-daemon/blob/master/conn.go ## Processing function `doStreamOpen(req *pb.Request)`. result = initProtoBuffer({WithVarintLength}) @@ -143,6 +190,8 @@ proc requestStreamOpen(peerid: PeerID, msg.write(initProtoField(1, peerid)) for item in protocols: msg.write(initProtoField(2, item)) + if timeout > 0: + msg.write(initProtoField(3, timeout)) result.write(initProtoField(1, cast[uint](RequestType.STREAM_OPEN))) result.write(initProtoField(3, msg)) result.finish() @@ -198,7 +247,7 @@ proc requestDHTFindPeersConnectedToPeer(peer: PeerID, result.write(initProtoField(5, msg)) result.finish() -proc requestDHTFindProviders(cid: CID, +proc requestDHTFindProviders(cid: Cid, count: uint32, timeout = 0): ProtoBuffer = ## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go ## Processing function `doDHTFindProviders(req *pb.DHTRequest)`. @@ -206,7 +255,7 @@ proc requestDHTFindProviders(cid: CID, result = initProtoBuffer({WithVarintLength}) var msg = initProtoBuffer() msg.write(initProtoField(1, msgid)) - msg.write(initProtoField(3, cid)) + msg.write(initProtoField(3, cid.data.buffer)) msg.write(initProtoField(6, count)) if timeout > 0: msg.write(initProtoField(7, uint(timeout))) @@ -292,14 +341,14 @@ proc requestDHTPutValue(key: string, value: openarray[byte], result.write(initProtoField(5, msg)) result.finish() -proc requestDHTProvide(cid: CID, timeout = 0): ProtoBuffer = +proc requestDHTProvide(cid: Cid, timeout = 0): ProtoBuffer = ## https://github.com/libp2p/go-libp2p-daemon/blob/master/dht.go ## Processing function `doDHTProvide(req *pb.DHTRequest)`. let msgid = cast[uint](DHTRequestType.PROVIDE) result = initProtoBuffer({WithVarintLength}) var msg = initProtoBuffer() msg.write(initProtoField(1, msgid)) - msg.write(initProtoField(3, cid)) + msg.write(initProtoField(3, cid.data.buffer)) if timeout > 0: msg.write(initProtoField(7, uint(timeout))) msg.finish() @@ -345,6 +394,58 @@ proc requestCMTrim(): ProtoBuffer = result.write(initProtoField(6, msg)) result.finish() +proc requestPSGetTopics(): ProtoBuffer = + ## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go + ## Processing function `doPubsubGetTopics(req *pb.PSRequest)`. + let msgid = cast[uint](PSRequestType.GET_TOPICS) + result = initProtoBuffer({WithVarintLength}) + var msg = initProtoBuffer() + msg.write(initProtoField(1, msgid)) + msg.finish() + result.write(initProtoField(1, cast[uint](RequestType.PUBSUB))) + result.write(initProtoField(8, msg)) + result.finish() + +proc requestPSListPeers(topic: string): ProtoBuffer = + ## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go + ## Processing function `doPubsubListPeers(req *pb.PSRequest)`. + let msgid = cast[uint](PSRequestType.LIST_PEERS) + result = initProtoBuffer({WithVarintLength}) + var msg = initProtoBuffer() + msg.write(initProtoField(1, msgid)) + msg.write(initProtoField(2, topic)) + msg.finish() + result.write(initProtoField(1, cast[uint](RequestType.PUBSUB))) + result.write(initProtoField(8, msg)) + result.finish() + +proc requestPSPublish(topic: string, data: openarray[byte]): ProtoBuffer = + ## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go + ## Processing function `doPubsubPublish(req *pb.PSRequest)`. + let msgid = cast[uint](PSRequestType.PUBLISH) + result = initProtoBuffer({WithVarintLength}) + var msg = initProtoBuffer() + msg.write(initProtoField(1, msgid)) + msg.write(initProtoField(2, topic)) + msg.write(initProtoField(3, data)) + msg.finish() + result.write(initProtoField(1, cast[uint](RequestType.PUBSUB))) + result.write(initProtoField(8, msg)) + result.finish() + +proc requestPSSubscribe(topic: string): ProtoBuffer = + ## https://github.com/libp2p/go-libp2p-daemon/blob/master/pubsub.go + ## Processing function `doPubsubSubscribe(req *pb.PSRequest)`. + let msgid = cast[uint](PSRequestType.SUBSCRIBE) + result = initProtoBuffer({WithVarintLength}) + var msg = initProtoBuffer() + msg.write(initProtoField(1, msgid)) + msg.write(initProtoField(2, topic)) + msg.finish() + result.write(initProtoField(1, cast[uint](RequestType.PUBSUB))) + result.write(initProtoField(8, msg)) + result.finish() + proc checkResponse(pb: var ProtoBuffer): ResponseKind {.inline.} = result = ResponseKind.Malformed var value: uint64 @@ -365,20 +466,65 @@ proc recvMessage(conn: StreamTransport): Future[seq[byte]] {.async.} = length: int res: VarintStatus var buffer = newSeq[byte](10) - for i in 0.. MaxMessageSize: - raise newException(ValueError, "Invalid message size") - buffer.setLen(size) - await conn.readExactly(addr buffer[0], int(size)) + try: + for i in 0.. MaxMessageSize: + buffer.setLen(0) + buffer.setLen(size) + await conn.readExactly(addr buffer[0], int(size)) + except TransportIncompleteError: + buffer.setLen(0) + result = buffer -proc socketExists(filename: string): bool = - var res: Stat - result = stat(filename, res) >= 0'i32 +proc newConnection*(api: DaemonAPI): Future[StreamTransport] = + # echo "Establish new connection to daemon [", $api.address, "]" + result = connect(api.address) + +proc closeConnection*(api: DaemonAPI, transp: StreamTransport) {.async.} = + # echo "Close connection with daemon [", $api.address, "]" + transp.close() + await transp.join() + +when not defined(windows): + proc socketExists(filename: string): bool = + var res: Stat + result = stat(filename, res) >= 0'i32 + + proc loggingHandler(api: DaemonAPI): Future[void] = + var retFuture = newFuture[void]("logging.handler") + var loop = getGlobalDispatcher() + let pfd = SocketHandle(api.process.outputHandle) + var fd = AsyncFD(pfd) + if not setSocketBlocking(pfd, false): + discard close(cint(pfd)) + retFuture.fail(newException(OSError, osErrorMsg(osLastError()))) + + proc readOutputLoop(udata: pointer) {.gcsafe.} = + var buffer: array[2048, char] + let res = posix.read(cint(fd), addr buffer[0], 2000) + if res == -1 or res == 0: + removeReader(fd) + retFuture.complete() + else: + var cstr = cast[cstring](addr buffer[0]) + api.log.add(cstr) + register(AsyncFD(pfd)) + addReader(fd, readOutputLoop, nil) + result = retFuture +else: + proc socketExists(filename: string): bool = false + + proc loggingHandler(api: DaemonAPI): Future[void] = + # Not ready yet. + discard + +# This is forward declaration needed for newDaemonApi() +proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async.} proc newDaemonApi*(flags: set[P2PDaemonFlags] = {}, bootstrapNodes: seq[string] = @[], @@ -386,61 +532,112 @@ proc newDaemonApi*(flags: set[P2PDaemonFlags] = {}, daemon = DefaultDaemonFile, sockpath = DefaultSocketPath, pattern = "/tmp/nim-p2pd-$1.sock", - poolSize = 10): Future[DaemonAPI] {.async.} = - ## Initialize connections to `go-libp2p-daemon` control socket. - result = new DaemonAPI - result.flags = flags - result.servers = newSeq[StreamServer]() - result.address = initTAddress(sockpath) - result.pattern = pattern - result.ucounter = 1 - result.handlers = initTable[string, P2PStreamCallback]() + poolSize = 10, + gossipsubHeartbeatInterval = 0, + gossipsubHeartbeatDelay = 0, + peersRequired = 2): Future[DaemonAPI] {.async.} = + ## Initialize connection to `go-libp2p-daemon` control socket. + var api = new DaemonAPI + var args = newSeq[string]() + var env: StringTableRef + + api.flags = flags + api.servers = newSeq[P2PServer]() + api.pattern = pattern + api.ucounter = 1 + api.handlers = initTable[string, P2PStreamCallback]() + api.sockname = sockpath + + if api.sockname == DefaultSocketPath: + # If client not specify `sockpath` but tries to spawn many daemons, we will + # replace sockname. + if daemonsCount != 0: + api.sockname = DefaultSocketPattern % [$daemonsCount] + + api.address = initTAddress(api.sockname) + inc(daemonsCount) + # We will start daemon process only when control socket path is not default or # options are specified. - if flags == {} and sockpath == DefaultSocketPath: - result.pool = await newPool(initTAddress(sockpath), poolsize = poolSize) + if flags == {} and api.sockname == DefaultSocketPath: + discard else: - var args = newSeq[string]() # DHTFull and DHTClient could not be present at the same time - if P2PDaemonFlags.DHTFull in flags and P2PDaemonFlags.DHTClient in flags: - result.flags.excl(DHTClient) - if P2PDaemonFlags.DHTFull in result.flags: + if DHTFull in flags and DHTClient in flags: + api.flags.excl(DHTClient) + # PSGossipSub and PSFloodSub could not be present at the same time + if PSGossipSub in flags and PSFloodSub in flags: + api.flags.excl(PSFloodSub) + if DHTFull in api.flags: args.add("-dht") - if P2PDaemonFlags.DHTClient in result.flags: + if DHTClient in api.flags: args.add("-dhtClient") - if P2PDaemonFlags.Bootstrap in result.flags: + if {Bootstrap, WaitBootstrap} * api.flags != {}: args.add("-b") + if Verbose in api.flags: + env = newStringTable("IPFS_LOGGING", "debug", modeCaseSensitive) + if PSGossipSub in api.flags: + args.add("-pubsub") + args.add("-pubsubRouter=gossipsub") + if gossipsubHeartbeatInterval != 0: + let param = $gossipsubHeartbeatInterval & "ms" + args.add("-gossipsubHeartbeatInterval=" & param) + if gossipsubHeartbeatDelay != 0: + let param = $gossipsubHeartbeatDelay & "ms" + args.add("-gossipsubHeartbeatInitialDelay=" & param) + if PSFloodSub in api.flags: + args.add("-pubsub") + args.add("-pubsubRouter=floodsub") + if api.flags * {PSFloodSub, PSGossipSub} != {}: + if PSNoSign in api.flags: + args.add("-pubsubSign=false") + if PSStrictSign in api.flags: + args.add("-pubsubSignStrict=true") if len(bootstrapNodes) > 0: args.add("-bootstrapPeers=" & bootstrapNodes.join(",")) if len(id) != 0: args.add("-id=" & id) - if sockpath != DefaultSocketPath: - args.add("-sock=" & sockpath) - # We are trying to get absolute daemon path. - let cmd = findExe(daemon) - if len(cmd) == 0: - raise newException(DaemonLocalError, "Could not find daemon executable!") - # We will try to remove control socket file, because daemon will fail - # if its not able to create new socket control file. - # We can't use `existsFile()` because it do not support unix-domain socket - # endpoints. - if socketExists(sockpath): - discard tryRemoveFile(sockpath) - # Starting daemon process - result.process = startProcess(cmd, "", args, options = {poStdErrToStdOut}) - # Waiting until daemon will not be bound to control socket. - while true: - if not result.process.running(): - echo result.process.errorStream.readAll() - raise newException(DaemonLocalError, - "Daemon executable could not be started!") - if socketExists(sockpath): - break - await sleepAsync(100) - result.sockname = sockpath - result.pool = await newPool(initTAddress(sockpath), poolsize = poolSize) + if api.sockname != DefaultSocketPath: + args.add("-sock=" & api.sockname) -proc close*(api: DaemonAPI, stream: P2PStream) {.async.} = + # We are trying to get absolute daemon path. + let cmd = findExe(daemon) + if len(cmd) == 0: + raise newException(DaemonLocalError, "Could not find daemon executable!") + # We will try to remove control socket file, because daemon will fail + # if its not able to create new socket control file. + # We can't use `existsFile()` because it do not support unix-domain socket + # endpoints. + if socketExists(api.sockname): + if not tryRemoveFile(api.sockname): + if api.sockname != sockpath: + raise newException(DaemonLocalError, "Socket is already bound!") + # Starting daemon process + api.process = startProcess(cmd, "", args, env, {poStdErrToStdOut}) + # Waiting until daemon will not be bound to control socket. + while true: + if not api.process.running(): + echo api.process.errorStream.readAll() + raise newException(DaemonLocalError, + "Daemon executable could not be started!") + if socketExists(api.sockname): + break + await sleepAsync(100) + # api.pool = await newPool(api.address, poolsize = poolSize) + if Logging in api.flags: + api.loggerFut = loggingHandler(api) + + if WaitBootstrap in api.flags: + while true: + var peers = await listPeers(api) + echo len(peers) + if len(peers) >= peersRequired: + break + await sleepAsync(1000) + + result = api + +proc close*(stream: P2PStream) {.async.} = ## Close ``stream``. if P2PStreamFlags.Closed notin stream.flags: stream.transp.close() @@ -452,21 +649,27 @@ proc close*(api: DaemonAPI, stream: P2PStream) {.async.} = proc close*(api: DaemonAPI) {.async.} = ## Shutdown connections to `go-libp2p-daemon` control socket. - await api.pool.close() + # await api.pool.close() # Closing all pending servers. if len(api.servers) > 0: var pending = newSeq[Future[void]]() for server in api.servers: - server.stop() - server.close() - pending.add(server.join()) + server.server.stop() + server.server.close() + pending.add(server.server.join()) await all(pending) + for server in api.servers: + discard tryRemoveFile($(server.address)) + api.servers.setLen(0) # Closing daemon's process. - if api.flags != {}: - api.process.terminate() + api.process.kill() + discard api.process.waitForExit() + # Waiting for logger loop to exit + if not isNil(api.loggerFut): + await api.loggerFut # Attempt to delete control socket endpoint. - # if socketExists(api.sockname): - # discard tryRemoveFile(api.sockname) + if socketExists(api.sockname): + discard tryRemoveFile(api.sockname) template withMessage(m, body: untyped): untyped = let kind = m.checkResponse() @@ -484,6 +687,8 @@ proc transactMessage(transp: StreamTransport, if res != length: raise newException(DaemonLocalError, "Could not send message to daemon!") var message = await transp.recvMessage() + if len(message) == 0: + raise newException(DaemonLocalError, "Incorrect or empty message received!") result = initProtoBuffer(message) proc getPeerInfo(pb: var ProtoBuffer): PeerInfo = @@ -495,12 +700,13 @@ proc getPeerInfo(pb: var ProtoBuffer): PeerInfo = var address = newSeq[byte]() while pb.getBytes(2, address) != -1: if len(address) != 0: - result.addresses.add(address) + var copyaddr = address + result.addresses.add(MultiAddress.init(copyaddr)) address.setLen(0) proc identity*(api: DaemonAPI): Future[PeerInfo] {.async.} = ## Get Node identity information - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transactMessage(transp, requestIdentity()) pb.withMessage() do: @@ -508,55 +714,59 @@ proc identity*(api: DaemonAPI): Future[PeerInfo] {.async.} = if res == cast[int](ResponseType.IDENTITY): result = pb.getPeerInfo() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc connect*(api: DaemonAPI, peer: PeerID, - addresses: seq[MultiAddress]) {.async.} = + addresses: seq[MultiAddress], + timeout = 0) {.async.} = ## Connect to remote peer with id ``peer`` and addresses ``addresses``. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: - var pb = await transp.transactMessage(requestConnect(peer, addresses)) + var pb = await transp.transactMessage(requestConnect(peer, addresses, + timeout)) pb.withMessage() do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc disconnect*(api: DaemonAPI, peer: PeerID) {.async.} = ## Disconnect from remote peer with id ``peer``. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDisconnect(peer)) pb.withMessage() do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc openStream*(api: DaemonAPI, peer: PeerID, - protocols: seq[string]): Future[P2PStream] {.async.} = + protocols: seq[string], + timeout = 0): Future[P2PStream] {.async.} = ## Open new stream to peer ``peer`` using one of the protocols in ## ``protocols``. Returns ``StreamTransport`` for the stream. - var transp = await connect(api.address) + var transp = await api.newConnection() var stream = new P2PStream try: - var pb = await transp.transactMessage(requestStreamOpen(peer, protocols)) + var pb = await transp.transactMessage(requestStreamOpen(peer, protocols, + timeout)) pb.withMessage() do: var res = pb.enterSubmessage() if res == cast[int](ResponseType.STREAMINFO): stream.peer = newSeq[byte]() - stream.raddress = newSeq[byte]() + var raddress = newSeq[byte]() stream.protocol = "" if pb.getLengthValue(1, stream.peer) == -1: raise newException(DaemonLocalError, "Missing `peer` field!") - if pb.getLengthValue(2, stream.raddress) == -1: + if pb.getLengthValue(2, raddress) == -1: raise newException(DaemonLocalError, "Missing `address` field!") + stream.raddress = MultiAddress.init(raddress) if pb.getLengthValue(3, stream.protocol) == -1: raise newException(DaemonLocalError, "Missing `proto` field!") stream.flags.incl(Outbound) stream.transp = transp result = stream except: - transp.close() - await transp.join() + await api.closeConnection(transp) raise getCurrentException() proc streamHandler(server: StreamServer, transp: StreamTransport) {.async.} = @@ -565,12 +775,13 @@ proc streamHandler(server: StreamServer, transp: StreamTransport) {.async.} = var pb = initProtoBuffer(message) var stream = new P2PStream stream.peer = newSeq[byte]() - stream.raddress = newSeq[byte]() + var raddress = newSeq[byte]() stream.protocol = "" if pb.getLengthValue(1, stream.peer) == -1: raise newException(DaemonLocalError, "Missing `peer` field!") - if pb.getLengthValue(2, stream.raddress) == -1: + if pb.getLengthValue(2, raddress) == -1: raise newException(DaemonLocalError, "Missing `address` field!") + stream.raddress = MultiAddress.init(raddress) if pb.getLengthValue(3, stream.protocol) == -1: raise newException(DaemonLocalError, "Missing `proto` field!") stream.flags.incl(Inbound) @@ -583,7 +794,7 @@ proc streamHandler(server: StreamServer, transp: StreamTransport) {.async.} = proc addHandler*(api: DaemonAPI, protocols: seq[string], handler: P2PStreamCallback) {.async.} = ## Add stream handler ``handler`` for set of protocols ``protocols``. - var transp = await api.pool.acquire() + var transp = await api.newConnection() var sockname = api.pattern % [$api.ucounter] var localaddr = initTAddress(sockname) inc(api.ucounter) @@ -595,7 +806,7 @@ proc addHandler*(api: DaemonAPI, protocols: seq[string], var pb = await transp.transactMessage(requestStreamHandler(sockname, protocols)) pb.withMessage() do: - api.servers.add(server) + api.servers.add(P2PServer(server: server, address: localaddr)) except: for item in protocols: api.handlers.del(item) @@ -604,11 +815,11 @@ proc addHandler*(api: DaemonAPI, protocols: seq[string], await server.join() raise getCurrentException() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async.} = ## Get list of remote peers to which we are currently connected. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestListPeers()) pb.withMessage() do: @@ -623,38 +834,38 @@ proc listPeers*(api: DaemonAPI): Future[seq[PeerInfo]] {.async.} = pb.skipSubmessage() res = pb.enterSubmessage() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc cmTagPeer*(api: DaemonAPI, peer: PeerID, tag: string, weight: int) {.async.} = ## Tag peer with id ``peer`` using ``tag`` and ``weight``. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestCMTagPeer(peer, tag, weight)) withMessage(pb) do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc cmUntagPeer*(api: DaemonAPI, peer: PeerID, tag: string) {.async.} = ## Remove tag ``tag`` from peer with id ``peer``. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestCMUntagPeer(peer, tag)) withMessage(pb) do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc cmTrimPeers*(api: DaemonAPI) {.async.} = ## Trim all connections. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestCMTrim()) withMessage(pb) do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtGetSinglePeerInfo(pb: var ProtoBuffer): PeerInfo = if pb.enterSubmessage() == 2: @@ -678,6 +889,11 @@ proc enterDhtMessage(pb: var ProtoBuffer, rt: DHTResponseType) {.inline.} = else: raise newException(DaemonLocalError, "Wrong message type!") +proc enterPsMessage(pb: var ProtoBuffer) {.inline.} = + var res = pb.enterSubmessage() + if res != cast[int](ResponseType.PUBSUB): + raise newException(DaemonLocalError, "Wrong message type!") + proc getDhtMessageType(pb: var ProtoBuffer): DHTResponseType {.inline.} = var dtype: uint if pb.getVarintValue(1, dtype) == 0: @@ -695,14 +911,14 @@ proc dhtFindPeer*(api: DaemonAPI, peer: PeerID, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDHTFindPeer(peer, timeout)) withMessage(pb) do: pb.enterDhtMessage(DHTResponseType.VALUE) result = pb.dhtGetSinglePeerInfo() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtGetPublicKey*(api: DaemonAPI, peer: PeerID, timeout = 0): Future[LibP2PPublicKey] {.async.} = @@ -710,14 +926,14 @@ proc dhtGetPublicKey*(api: DaemonAPI, peer: PeerID, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDHTGetPublicKey(peer, timeout)) withMessage(pb) do: pb.enterDhtMessage(DHTResponseType.VALUE) result = pb.dhtGetSingleValue() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtGetValue*(api: DaemonAPI, key: string, timeout = 0): Future[seq[byte]] {.async.} = @@ -725,14 +941,14 @@ proc dhtGetValue*(api: DaemonAPI, key: string, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDHTGetValue(key, timeout)) withMessage(pb) do: pb.enterDhtMessage(DHTResponseType.VALUE) result = pb.dhtGetSingleValue() finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtPutValue*(api: DaemonAPI, key: string, value: seq[byte], timeout = 0) {.async.} = @@ -740,27 +956,27 @@ proc dhtPutValue*(api: DaemonAPI, key: string, value: seq[byte], ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDHTPutValue(key, value, timeout)) withMessage(pb) do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) -proc dhtProvide*(api: DaemonAPI, cid: CID, timeout = 0) {.async.} = +proc dhtProvide*(api: DaemonAPI, cid: Cid, timeout = 0) {.async.} = ## Provide content with id ``cid``. ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() try: var pb = await transp.transactMessage(requestDHTProvide(cid, timeout)) withMessage(pb) do: discard finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtFindPeersConnectedToPeer*(api: DaemonAPI, peer: PeerID, timeout = 0): Future[seq[PeerInfo]] {.async.} = @@ -768,7 +984,7 @@ proc dhtFindPeersConnectedToPeer*(api: DaemonAPI, peer: PeerID, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() var list = newSeq[PeerInfo]() try: let spb = requestDHTFindPeersConnectedToPeer(peer, timeout) @@ -777,13 +993,15 @@ proc dhtFindPeersConnectedToPeer*(api: DaemonAPI, peer: PeerID, pb.enterDhtMessage(DHTResponseType.BEGIN) while true: var message = await transp.recvMessage() + if len(message) == 0: + break var cpb = initProtoBuffer(message) if cpb.getDhtMessageType() == DHTResponseType.END: break list.add(cpb.dhtGetSinglePeerInfo()) result = list finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtGetClosestPeers*(api: DaemonAPI, key: string, timeout = 0): Future[seq[PeerID]] {.async.} = @@ -791,7 +1009,7 @@ proc dhtGetClosestPeers*(api: DaemonAPI, key: string, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() var list = newSeq[PeerID]() try: let spb = requestDHTGetClosestPeers(key, timeout) @@ -800,21 +1018,23 @@ proc dhtGetClosestPeers*(api: DaemonAPI, key: string, pb.enterDhtMessage(DHTResponseType.BEGIN) while true: var message = await transp.recvMessage() + if len(message) == 0: + break var cpb = initProtoBuffer(message) if cpb.getDhtMessageType() == DHTResponseType.END: break list.add(cpb.dhtGetSingleValue()) result = list finally: - api.pool.release(transp) + await api.closeConnection(transp) -proc dhtFindProviders*(api: DaemonAPI, cid: CID, count: uint32, - timeout = 0): Future[seq[PeerInfo]] {.async.} = +proc dhtFindProviders*(api: DaemonAPI, cid: Cid, count: uint32, + timeout = 0): Future[seq[PeerInfo]] {.async.} = ## Get ``count`` providers for content with id ``cid``. ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() var list = newSeq[PeerInfo]() try: let spb = requestDHTFindProviders(cid, count, timeout) @@ -823,13 +1043,15 @@ proc dhtFindProviders*(api: DaemonAPI, cid: CID, count: uint32, pb.enterDhtMessage(DHTResponseType.BEGIN) while true: var message = await transp.recvMessage() + if len(message) == 0: + break var cpb = initProtoBuffer(message) if cpb.getDhtMessageType() == DHTResponseType.END: break list.add(cpb.dhtGetSinglePeerInfo()) result = list finally: - api.pool.release(transp) + await api.closeConnection(transp) proc dhtSearchValue*(api: DaemonAPI, key: string, timeout = 0): Future[seq[seq[byte]]] {.async.} = @@ -837,7 +1059,7 @@ proc dhtSearchValue*(api: DaemonAPI, key: string, ## ## You can specify timeout for DHT request with ``timeout`` value. ``0`` value ## means no timeout. - var transp = await api.pool.acquire() + var transp = await api.newConnection() var list = newSeq[seq[byte]]() try: var pb = await transp.transactMessage(requestDHTSearchValue(key, timeout)) @@ -845,10 +1067,129 @@ proc dhtSearchValue*(api: DaemonAPI, key: string, pb.enterDhtMessage(DHTResponseType.BEGIN) while true: var message = await transp.recvMessage() + if len(message) == 0: + break var cpb = initProtoBuffer(message) if cpb.getDhtMessageType() == DHTResponseType.END: break list.add(cpb.dhtGetSingleValue()) result = list finally: - api.pool.release(transp) + await api.closeConnection(transp) + +proc pubsubGetTopics*(api: DaemonAPI): Future[seq[string]] {.async.} = + ## Get list of topics this node is subscribed to. + var transp = await api.newConnection() + try: + var pb = await transp.transactMessage(requestPSGetTopics()) + withMessage(pb) do: + pb.enterPsMessage() + var topics = newSeq[string]() + var topic = "" + while pb.getString(1, topic) != -1: + topics.add(topic) + topic.setLen(0) + result = topics + finally: + await api.closeConnection(transp) + +proc pubsubListPeers*(api: DaemonAPI, + topic: string): Future[seq[PeerID]] {.async.} = + ## Get list of peers we are connected to and which also subscribed to topic + ## ``topic``. + var transp = await api.newConnection() + try: + var pb = await transp.transactMessage(requestPSListPeers(topic)) + withMessage(pb) do: + pb.enterPsMessage() + var peers = newSeq[PeerID]() + var peer = newSeq[byte]() + while pb.getBytes(2, peer) != -1: + peers.add(peer) + peer.setLen(0) + result = peers + finally: + await api.closeConnection(transp) + +proc pubsubPublish*(api: DaemonAPI, topic: string, + value: seq[byte]) {.async.} = + ## Get list of peer identifiers which are subscribed to topic ``topic``. + var transp = await api.newConnection() + try: + var pb = await transp.transactMessage(requestPSPublish(topic, value)) + withMessage(pb) do: + discard + finally: + await api.closeConnection(transp) + +proc getPubsubMessage*(pb: var ProtoBuffer): PubSubMessage = + var item = newSeq[byte]() + for field in 1..6: + while true: + if pb.getBytes(field, item) == -1: + break + if field == 1: + result.peer = item + elif field == 2: + result.data = item + elif field == 3: + result.seqno = item + elif field == 4: + var copyitem = item + var stritem = cast[string](copyitem) + if len(result.topics) == 0: + result.topics = newSeq[string]() + result.topics.add(stritem) + elif field == 5: + result.signature = item + elif field == 6: + result.key = item + item.setLen(0) + +proc pubsubLoop(api: DaemonAPI, ticket: PubsubTicket) {.async.} = + while true: + var pbmessage = await ticket.transp.recvMessage() + if len(pbmessage) == 0: + break + var pb = initProtoBuffer(pbmessage) + var message = pb.getPubsubMessage() + ## We can do here `await` too + let res = await ticket.handler(api, ticket, message) + if not res: + ticket.transp.close() + await ticket.transp.join() + break + +proc pubsubSubscribe*(api: DaemonAPI, topic: string, + handler: P2PPubSubCallback): Future[PubsubTicket] {.async.} = + ## Subscribe to topic ``topic``. + var transp = await api.newConnection() + try: + var pb = await transp.transactMessage(requestPSSubscribe(topic)) + pb.withMessage() do: + var ticket = new PubsubTicket + ticket.topic = topic + ticket.handler = handler + ticket.transp = transp + asyncCheck pubsubLoop(api, ticket) + result = ticket + except: + await api.closeConnection(transp) + raise getCurrentException() + +proc `$`*(pinfo: PeerInfo): string = + ## Get string representation of ``PeerInfo`` object. + result = newStringOfCap(128) + result.add("{PeerID: '") + result.add(Base58.encode(pinfo.peer)) + result.add("' Addresses: [") + let length = len(pinfo.addresses) + for i in 0.. 0: + result = result diff --git a/libp2p/daemon/transpool.nim b/libp2p/daemon/transpool.nim index cae8d2c..a12ea62 100644 --- a/libp2p/daemon/transpool.nim +++ b/libp2p/daemon/transpool.nim @@ -52,11 +52,11 @@ proc newPool*(address: TransportAddress, poolsize: int = DefaultPoolSize, ): Future[TransportPool] {.async.} = ## Establish pool of connections to address ``address`` with size ## ``poolsize``. - result = new TransportPool - result.bufferSize = bufferSize - result.transports = newSeq[PoolItem](poolsize) + var pool = new TransportPool + pool.bufferSize = bufferSize + pool.transports = newSeq[PoolItem](poolsize) var conns = newSeq[Future[StreamTransport]](poolsize) - result.state = Connecting + pool.state = Connecting for i in 0.. 0: + vb.writeSeq(s) + result = true + +proc ip6zoneBtS(vb: var VBuffer, s: var string): bool = + ## IPv6 bufferToString() implementation. + if vb.readSeq(s) > 0: + result = true + +proc ip6zoneVB(vb: var VBuffer): bool = + ## IPv6 validateBuffer() implementation. + var s = "" + if vb.readSeq(s) > 0: + if s.find('/') == -1: + result = true + +proc portStB(s: string, vb: var VBuffer): bool = + ## Port number stringToBuffer() implementation. + var port: array[2, byte] + try: + var nport = parseInt(s) + if (nport >= 0) and (nport < 65536): + port[0] = cast[byte]((nport shr 8) and 0xFF) + port[1] = cast[byte](nport and 0xFF) + vb.writeArray(port) + result = true + except: + discard + +proc portBtS(vb: var VBuffer, s: var string): bool = + ## Port number bufferToString() implementation. + var port: array[2, byte] + if vb.readArray(port) == 2: + var nport = (cast[uint16](port[0]) shl 8) or cast[uint16](port[1]) + s = $nport + result = true + +proc portVB(vb: var VBuffer): bool = + ## Port number validateBuffer() implementation. + var port: array[2, byte] + if vb.readArray(port) == 2: + result = true + +proc p2pStB(s: string, vb: var VBuffer): bool = + ## P2P address stringToBuffer() implementation. + try: + var data = Base58.decode(s) + var mh: MultiHash + if MultiHash.decode(data, mh) >= 0: + vb.writeSeq(data) + result = true + except: + discard + +proc p2pBtS(vb: var VBuffer, s: var string): bool = + ## P2P address bufferToString() implementation. + var address = newSeq[byte]() + if vb.readSeq(address) > 0: + var mh: MultiHash + if MultiHash.decode(address, mh) >= 0: + s = Base58.encode(address) + result = true + +proc p2pVB(vb: var VBuffer): bool = + ## P2P address validateBuffer() implementation. + var address = newSeq[byte]() + if vb.readSeq(address) > 0: + var mh: MultiHash + if MultiHash.decode(address, mh) >= 0: + result = true + +proc onionStB(s: string, vb: var VBuffer): bool = + try: + var parts = s.split(':') + if len(parts) != 2: + return false + if len(parts[0]) != 16: + return false + var address = Base32Lower.decode(parts[0].toLowerAscii()) + var nport = parseInt(parts[1]) + if (nport > 0 and nport < 65536) and len(address) == 10: + address.setLen(12) + address[10] = cast[byte]((nport shr 8) and 0xFF) + address[11] = cast[byte](nport and 0xFF) + vb.writeArray(address) + result = true + except: + discard + +proc onionBtS(vb: var VBuffer, s: var string): bool = + ## ONION address bufferToString() implementation. + var buf: array[12, byte] + if vb.readArray(buf) == 12: + var nport = (cast[uint16](buf[10]) shl 8) or cast[uint16](buf[11]) + s = Base32Lower.encode(buf.toOpenArray(0, 9)) + s.add(":") + s.add($nport) + result = true + +proc onionVB(vb: var VBuffer): bool = + ## ONION address validateBuffer() implementation. + var buf: array[12, byte] + if vb.readArray(buf) == 12: + result = true + +proc unixStB(s: string, vb: var VBuffer): bool = + ## Unix socket name stringToBuffer() implementation. + if len(s) > 0: + vb.writeSeq(s) + result = true + +proc unixBtS(vb: var VBuffer, s: var string): bool = + ## Unix socket name bufferToString() implementation. + s = "" + if vb.readSeq(s) > 0: + result = true + +proc unixVB(vb: var VBuffer): bool = + ## Unix socket name validateBuffer() implementation. + var s = "" + if vb.readSeq(s) > 0: + result = true + +proc dnsStB(s: string, vb: var VBuffer): bool = + ## DNS name stringToBuffer() implementation. + if len(s) > 0: + vb.writeSeq(s) + result = true + +proc dnsBtS(vb: var VBuffer, s: var string): bool = + ## DNS name bufferToString() implementation. + s = "" + if vb.readSeq(s) > 0: + result = true + +proc dnsVB(vb: var VBuffer): bool = + ## DNS name validateBuffer() implementation. + var s = "" + if vb.readSeq(s) > 0: + if s.find('/') == -1: + result = true + +const + TranscoderIP4* = Transcoder( + stringToBuffer: ip4StB, + bufferToString: ip4BtS, + validateBuffer: ip4VB + ) + TranscoderIP6* = Transcoder( + stringToBuffer: ip6StB, + bufferToString: ip6BtS, + validateBuffer: ip6VB + ) + TranscoderIP6Zone* = Transcoder( + stringToBuffer: ip6zoneStB, + bufferToString: ip6zoneBtS, + validateBuffer: ip6zoneVB + ) + TranscoderUnix* = Transcoder( + stringToBuffer: unixStB, + bufferToString: unixBtS, + validateBuffer: unixVB + ) + TranscoderP2P* = Transcoder( + stringToBuffer: p2pStB, + bufferToString: p2pBtS, + validateBuffer: p2pVB + ) + TranscoderPort* = Transcoder( + stringToBuffer: portStB, + bufferToString: portBtS, + validateBuffer: portVB + ) + TranscoderOnion* = Transcoder( + stringToBuffer: onionStB, + bufferToString: onionBtS, + validateBuffer: onionVB + ) + TranscoderDNS* = Transcoder( + stringToBuffer: dnsStB, + bufferToString: dnsBtS, + validateBuffer: dnsVB + ) + ProtocolsList = [ + MAProtocol( + mcodec: multiCodec("ip4"), kind: Fixed, size: 4, + coder: TranscoderIP4 + ), + MAProtocol( + mcodec: multiCodec("tcp"), kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + mcodec: multiCodec("udp"), kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + mcodec: multiCodec("ip6"), kind: Fixed, size: 16, + coder: TranscoderIP6 + ), + MAProtocol( + mcodec: multiCodec("dccp"), kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + mcodec: multiCodec("sctp"), kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + mcodec: multiCodec("udt"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("utp"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("http"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("https"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("quic"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("ip6zone"), kind: Length, size: 0, + coder: TranscoderIP6Zone + ), + MAProtocol( + mcodec: multiCodec("onion"), kind: Fixed, size: 10, + coder: TranscoderOnion + ), + MAProtocol( + mcodec: multiCodec("ws"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("ipfs"), kind: Length, size: 0, + coder: TranscoderP2P + ), + MAProtocol( + mcodec: multiCodec("p2p"), kind: Length, size: 0, + coder: TranscoderP2P + ), + MAProtocol( + mcodec: multiCodec("unix"), kind: Path, size: 0, + coder: TranscoderUnix + ), + MAProtocol( + mcodec: multiCodec("dns4"), kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + mcodec: multiCodec("dns6"), kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + mcodec: multiCodec("dnsaddr"), kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + mcodec: multiCodec("p2p-circuit"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("p2p-websocket-star"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("p2p-webrtc-star"), kind: Marker, size: 0 + ), + MAProtocol( + mcodec: multiCodec("p2p-webrtc-direct"), kind: Marker, size: 0 + ) + ] + +proc initMultiAddressCodeTable(): Table[MultiCodec, + MAProtocol] {.compileTime.} = + result = initTable[MultiCodec, MAProtocol]() + for item in ProtocolsList: + result[item.mcodec] = item + +const + CodeAddresses = initMultiAddressCodeTable() + +proc trimRight(s: string, ch: char): string = + ## Consume trailing characters ``ch`` from string ``s`` and return result. + var m = 0 + for i in countdown(len(s) - 1, 0): + if s[i] == ch: + inc(m) + else: + break + result = s[0..(len(s) - 1 - m)] + +proc shcopy*(m1: var MultiAddress, m2: MultiAddress) = + shallowCopy(m1.data.buffer, m2.data.buffer) + m1.data.offset = m2.data.offset + m1.data.length = m2.data.length + +proc protoCode*(ma: MultiAddress): 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 + +proc protoName*(ma: MultiAddress): 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) + +proc protoArgument*(ma: MultiAddress, value: var openarray[byte]): int = + ## Returns MultiAddress ``ma`` protocol argument value. + ## + ## If current MultiAddress do not have argument value, then result will be + ## ``0``. + var header: uint64 + var vb: MultiAddress + 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) != 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(vb.data.buffer) + if len(value) >= result: + copyMem(addr value[0], addr vb.data.buffer[0], result) + +proc getPart(ma: MultiAddress, index: int): MultiAddress = + var header: uint64 + var data = newSeq[byte]() + var offset = 0 + var vb = ma + result.data = initVBuffer() + while offset <= index: + 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 & "'") + elif proto.kind == Fixed: + data.setLen(proto.size) + if vb.data.readArray(data) != proto.size: + raise newException(MultiAddressError, "Decoding protocol error") + if offset == index: + result.data.writeVarint(header) + result.data.writeArray(data) + result.data.finish() + elif proto.kind in {Length, Path}: + if vb.data.readSeq(data) == -1: + raise newException(MultiAddressError, "Decoding protocol error") + if offset == index: + result.data.writeVarint(header) + result.data.writeSeq(data) + result.data.finish() + elif proto.kind == Marker: + if offset == index: + result.data.writeVarint(header) + result.data.finish() + inc(offset) + +proc `[]`*(ma: MultiAddress, i: int): MultiAddress {.inline.} = + ## Returns part with index ``i`` of MultiAddress ``ma``. + result = ma.getPart(i) + +iterator items*(ma: MultiAddress): MultiAddress = + ## Iterates over all addresses inside of MultiAddress ``ma``. + var header: uint64 + var data = newSeq[byte]() + var vb = ma + while true: + if vb.data.isEmpty(): + break + var res = MultiAddress(data: initVBuffer()) + 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 & "'") + elif proto.kind == Fixed: + data.setLen(proto.size) + if vb.data.readArray(data) != proto.size: + raise newException(MultiAddressError, "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") + res.data.writeVarint(header) + res.data.writeSeq(data) + elif proto.kind == Marker: + res.data.writeVarint(header) + res.data.finish() + yield res + +proc `$`*(value: MultiAddress): string = + ## Return string representation of MultiAddress ``value``. + var header: uint64 + var vb = value + var data = newSeq[byte]() + var parts = newSeq[string]() + var part: string + while true: + if vb.data.isEmpty(): + break + 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 in {Fixed, Length, Path}: + if isNil(proto.coder.bufferToString): + raise newException(MultiAddressError, + "Missing protocol '" & $(proto.mcodec) & "' coder") + if not proto.coder.bufferToString(vb.data, part): + raise newException(MultiAddressError, "Decoding protocol error") + parts.add($(proto.mcodec)) + parts.add(part) + elif proto.kind == Marker: + parts.add($(proto.mcodec)) + if len(parts) > 0: + result = "/" & parts.join("/") + +proc hex*(value: MultiAddress): string = + ## Return hexadecimal string representation of MultiAddress ``value``. + result = $(value.data) + +proc write*(vb: var VBuffer, ma: MultiAddress) {.inline.} = + ## Write MultiAddress value ``ma`` to buffer ``vb``. + vb.writeArray(ma.data.buffer) + +proc encode*(mbtype: typedesc[MultiBase], encoding: string, + ma: MultiAddress): string {.inline.} = + ## Get MultiBase encoded representation of ``ma`` using encoding + ## ``encoding``. + result = MultiBase.encode(encoding, ma.data.buffer) + +proc validate*(ma: MultiAddress): bool = + ## Returns ``true`` if MultiAddress ``ma`` is valid. + var header: uint64 + var vb: MultiAddress + shcopy(vb, ma) + while true: + if vb.data.isEmpty(): + break + if vb.data.readVarint(header) == -1: + return false + let proto = CodeAddresses.getOrDefault(MultiCodec(header)) + if proto.kind == None: + return false + if proto.kind in {Fixed, Length, Path}: + if isNil(proto.coder.validateBuffer): + return false + if not proto.coder.validateBuffer(vb.data): + return false + else: + discard + result = true + +proc init*(mtype: typedesc[MultiAddress], protocol: int, + value: openarray[byte]): 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.code)) + 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() + +proc init*(mtype: typedesc[MultiAddress], protocol: int): 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.code)) + result.data.finish() + +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 = + ## 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") + + 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() + +proc init*(mtype: typedesc[MultiAddress], data: openarray[byte]): 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!") + +proc init*(mtype: typedesc[MultiAddress]): MultiAddress = + ## Initialize empty MultiAddress. + result.data = initVBuffer() + +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 = + ## 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!") + +proc `&=`*(m1: var MultiAddress, m2: MultiAddress) = + ## 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!") diff --git a/libp2p/multibase.nim b/libp2p/multibase.nim new file mode 100644 index 0000000..fec965a --- /dev/null +++ b/libp2p/multibase.nim @@ -0,0 +1,429 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implements MultiBase. +## +## TODO: +## 1. base64 +## 2. base32z +import tables, strutils +import base32, base58 + +type + MultibaseStatus* {.pure.} = enum + Error, Success, Overrun, Incorrect, BadCodec, NotSupported + + MultiBase* = object + + MBDecoder = proc(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus {.nimcall.} + MBEncoder = proc(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus {.nimcall.} + MBCodeSize = proc(length: int): int {.nimcall.} + + MBCodec = object + code: char + name: string + encr: MBEncoder + decr: proc(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus {.nimcall.} + encl: MBCodeSize + decl: MBCodeSize + + MultiBaseError* = object of Exception + +proc idd(inbytes: openarray[char], outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + let length = len(inbytes) + if length > len(outbytes): + outlen = length + result = MultibaseStatus.Overrun + else: + copyMem(addr outbytes[0], unsafeAddr inbytes[0], length) + outlen = length + result = MultibaseStatus.Success + +proc ide(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + let length = len(inbytes) + if length > len(outbytes): + outlen = length + result = MultibaseStatus.Overrun + else: + copyMem(addr outbytes[0], unsafeAddr inbytes[0], length) + outlen = length + result = MultibaseStatus.Success + +proc idel(length: int): int = length +proc iddl(length: int): int = length + +proc b16d(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + discard + +proc b16e(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + discard + +proc b16ud(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + discard + +proc b16ue(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + discard + +proc b16el(length: int): int = length shl 1 +proc b16dl(length: int): int = (length + 1) div 2 + +proc b32ce(r: Base32Status): MultibaseStatus {.inline.} = + result = MultibaseStatus.Error + if r == Base32Status.Incorrect: + result = MultibaseStatus.Incorrect + elif r == Base32Status.Overrun: + result = MultibaseStatus.Overrun + elif r == Base32Status.Success: + result = MultibaseStatus.Success + +proc b58ce(r: Base58Status): MultibaseStatus {.inline.} = + result = MultibaseStatus.Error + if r == Base58Status.Incorrect: + result = MultibaseStatus.Incorrect + elif r == Base58Status.Overrun: + result = MultibaseStatus.Overrun + elif r == Base58Status.Success: + result = MultibaseStatus.Success + +proc b32hd(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32Lower.decode(inbytes, outbytes, outlen)) + +proc b32he(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32Lower.encode(inbytes, outbytes, outlen)) + +proc b32hud(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32Upper.decode(inbytes, outbytes, outlen)) + +proc b32hue(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32Upper.encode(inbytes, outbytes, outlen)) + +proc b32hpd(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32LowerPad.decode(inbytes, outbytes, outlen)) + +proc b32hpe(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32LowerPad.encode(inbytes, outbytes, outlen)) + +proc b32hpud(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32UpperPad.decode(inbytes, outbytes, outlen)) + +proc b32hpue(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(HexBase32UpperPad.encode(inbytes, outbytes, outlen)) + +proc b32d(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(Base32Lower.decode(inbytes, outbytes, outlen)) + +proc b32e(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(Base32Lower.encode(inbytes, outbytes, outlen)) + +proc b32ud(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(Base32Upper.decode(inbytes, outbytes, outlen)) + +proc b32ue(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(Base32Upper.encode(inbytes, outbytes, outlen)) + +proc b32pd(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(Base32LowerPad.decode(inbytes, outbytes, outlen)) + +proc b32pe(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(Base32LowerPad.encode(inbytes, outbytes, outlen)) + +proc b32pud(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b32ce(Base32UpperPad.decode(inbytes, outbytes, outlen)) + +proc b32pue(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b32ce(Base32UpperPad.encode(inbytes, outbytes, outlen)) + +proc b32el(length: int): int = Base32Lower.encodedLength(length) +proc b32dl(length: int): int = Base32Lower.decodedLength(length) +proc b32pel(length: int): int = Base32LowerPad.encodedLength(length) +proc b32pdl(length: int): int = Base32LowerPad.decodedLength(length) + +proc b58fd(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b58ce(FLCBase58.decode(inbytes, outbytes, outlen)) + +proc b58fe(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b58ce(FLCBase58.encode(inbytes, outbytes, outlen)) + +proc b58bd(inbytes: openarray[char], + outbytes: var openarray[byte], + outlen: var int): MultibaseStatus = + result = b58ce(BTCBase58.decode(inbytes, outbytes, outlen)) + +proc b58be(inbytes: openarray[byte], + outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + result = b58ce(BTCBase58.encode(inbytes, outbytes, outlen)) + +proc b58el(length: int): int = Base58.encodedLength(length) +proc b58dl(length: int): int = Base58.decodedLength(length) + +const + MultibaseCodecs = [ + MBCodec(name: "identity", code: chr(0x00), + decr: idd, encr: ide, decl: iddl, encl: idel + ), + MBCodec(name: "base1", code: '1'), + MBCodec(name: "base2", code: '0'), + MBCodec(name: "base8", code: '7'), + MBCodec(name: "base10", code: '9'), + MBCodec(name: "base16", code: 'f', + decr: b16d, encr: b16e, decl: b16dl, encl: b16el + ), + MBCodec(name: "base16upper", code: 'F', + decr: b16ud, encr: b16ue, decl: b16dl, encl: b16el + ), + MBCodec(name: "base32hex", code: 'v', + decr: b32hd, encr: b32he, decl: b32dl, encl: b32el + ), + MBCodec(name: "base32hexupper", code: 'V', + decr: b32hud, encr: b32hue, decl: b32dl, encl: b32el + ), + MBCodec(name: "base32hexpad", code: 't', + decr: b32hpd, encr: b32hpe, decl: b32pdl, encl: b32pel + ), + MBCodec(name: "base32hexpadupper", code: 'T', + decr: b32hpud, encr: b32hpue, decl: b32pdl, encl: b32pel + ), + MBCodec(name: "base32", code: 'b', + decr: b32d, encr: b32e, decl: b32dl, encl: b32el + ), + MBCodec(name: "base32upper", code: 'B', + decr: b32ud, encr: b32ue, decl: b32dl, encl: b32el + ), + MBCodec(name: "base32pad", code: 'c', + decr: b32pd, encr: b32pe, decl: b32pdl, encl: b32pel + ), + MBCodec(name: "base32padupper", code: 'C', + decr: b32pud, encr: b32pue, decl: b32pdl, encl: b32pel + ), + MBCodec(name: "base32z", code: 'h'), + MBCodec(name: "base58flickr", code: 'Z', + decr: b58fd, encr: b58fe, decl: b58dl, encl: b58el + ), + MBCodec(name: "base58btc", code: 'z', + decr: b58bd, encr: b58be, decl: b58dl, encl: b58el + ), + MBCodec(name: "base64", code: 'm'), + MBCodec(name: "base64pad", code: 'M'), + MBCodec(name: "base64url", code: 'u'), + MBCodec(name: "base64urlpad", code: 'U') + ] + +proc initMultiBaseCodeTable(): Table[char, MBCodec] {.compileTime.} = + result = initTable[char, MBCodec]() + for item in MultibaseCodecs: + result[item.code] = item + +proc initMultiBaseNameTable(): Table[string, MBCodec] {.compileTime.} = + result = initTable[string, MBCodec]() + for item in MultibaseCodecs: + result[item.name] = item + +const + CodeMultibases = initMultiBaseCodeTable() + NameMultibases = initMultiBaseNameTable() + +proc encodedLength*(mbtype: typedesc[MultiBase], encoding: string, + length: int): int = + ## Return estimated size of buffer to store MultiBase encoded value with + ## encoding ``encoding`` of length ``length``. + ## + ## Procedure returns ``-1`` if ``encoding`` scheme is not supported or + ## not present. + let mb = NameMultibases.getOrDefault(encoding) + if len(mb.name) == 0 or isNil(mb.encl): + result = -1 + else: + if length == 0: + result = 1 + else: + result = mb.encl(length) + 1 + +proc decodedLength*(mbtype: typedesc[MultiBase], encoding: char, + length: int): int = + ## Return estimated size of buffer to store MultiBase decoded value with + ## encoding character ``encoding`` of length ``length``. + let mb = CodeMultibases.getOrDefault(encoding) + if len(mb.name) == 0 or isNil(mb.decl) or length == 0: + result = -1 + else: + if length == 1: + result = 0 + else: + result = mb.decl(length - 1) + +proc encode*(mbtype: typedesc[MultiBase], encoding: string, + inbytes: openarray[byte], outbytes: var openarray[char], + outlen: var int): MultibaseStatus = + ## Encode array ``inbytes`` using MultiBase encoding scheme ``encoding`` and + ## store encoded value to ``outbytes``. + ## + ## If ``encoding`` is not supported ``MultiBaseStatus.NotSupported`` will be + ## returned. + ## + ## If ``encoding`` is not correct string, then ``MultBaseStatus.BadCodec`` + ## will be returned. + ## + ## If length of ``outbytes`` is not enough to store encoded result, then + ## ``MultiBaseStatus.Overrun`` error will be returned and ``outlen`` will be + ## set to length required. + ## + ## On successfull encoding ``MultiBaseStatus.Success`` will be returned and + ## ``outlen`` will be set to number of encoded octets (bytes). + let mb = NameMultibases.getOrDefault(encoding) + if len(mb.name) == 0: + return MultibaseStatus.BadCodec + if isNil(mb.encr) or isNil(mb.encl): + return MultibaseStatus.NotSupported + if len(outbytes) > 1: + result = mb.encr(inbytes, outbytes.toOpenArray(1, len(outbytes) - 1), + outlen) + if result == MultiBaseStatus.Overrun: + outlen += 1 + elif result == MultiBaseStatus.Success: + outlen += 1 + outbytes[0] = mb.code + else: + if len(inbytes) == 0 and len(outbytes) == 1: + result = MultiBaseStatus.Success + outlen = 1 + outbytes[0] = mb.code + else: + result = MultiBaseStatus.Overrun + outlen = mb.encl(len(inbytes)) + 1 + +proc decode*(mbtype: typedesc[MultiBase], inbytes: openarray[char], + outbytes: var openarray[byte], outlen: var int): MultibaseStatus = + ## Decode array ``inbytes`` using MultiBase encoding and store decoded value + ## to ``outbytes``. + ## + ## If ``inbytes`` is not correct MultiBase string, then + ## ``MultiBaseStatus.BadCodec`` if first character is wrong, or + ## ``MultiBaseStatus.Incorrect`` if string has incorrect characters for + ## such encoding. + ## + ## If length of ``outbytes`` is not enough to store decoded result, then + ## ``MultiBaseStatus.Overrun`` error will be returned and ``outlen`` will be + ## set to length required. + ## + ## On successfull decoding ``MultiBaseStatus.Success`` will be returned and + ## ``outlen`` will be set to number of encoded octets (bytes). + let length = len(inbytes) + if length == 0: + return MultibaseStatus.Incorrect + let mb = CodeMultibases.getOrDefault(inbytes[0]) + if len(mb.name) == 0: + return MultibaseStatus.BadCodec + if isNil(mb.decr) or isNil(mb.decl): + return MultibaseStatus.NotSupported + if length == 1: + outlen = 0 + result = MultibaseStatus.Success + else: + result = mb.decr(inbytes.toOpenArray(1, length - 1), outbytes, outlen) + +proc encode*(mbtype: typedesc[MultiBase], encoding: string, + inbytes: openarray[byte]): 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!") + if isNil(mb.encr) or isNil(mb.encl): + raise newException(MultiBaseError, "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, len(buffer) - 1), outlen) + if res != MultiBaseStatus.Success: + raise newException(MultiBaseError, "Encoding error [" & $res & "]") + buffer.setLen(outlen + 1) + buffer[0] = mb.code + else: + buffer = newString(1) + buffer[0] = mb.code + result = buffer + +proc decode*(mbtype: typedesc[MultiBase], inbytes: openarray[char]): seq[byte] = + ## 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") + let mb = CodeMultibases.getOrDefault(inbytes[0]) + if len(mb.name) == 0: + raise newException(MultiBaseError, "MultiBase scheme is incorrect!") + if isNil(mb.decr) or isNil(mb.decl): + raise newException(MultiBaseError, "MultiBase scheme is not supported!") + if length == 1: + result = newSeq[byte]() + 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) diff --git a/libp2p/multicodec.nim b/libp2p/multicodec.nim new file mode 100644 index 0000000..b32422a --- /dev/null +++ b/libp2p/multicodec.nim @@ -0,0 +1,316 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not BE copied, modified, or distributed except according to +## those terms. + +## This module implements MultiCodec. +import tables, hashes +import varint, vbuffer + +{.deadCodeElim: on.} + +## List of officially supported codecs can BE found here +## https://github.com/multiformats/multicodec/blob/master/table.csv +const MultiCodecList = [ + ("raw", 0x55), + # serialization formats + ("cbor", 0x51), + ("protobuf", 0x50), + ("rlp", 0x60), + ("bencode", 0x63), + # multiformats + ("multicodec", 0x30), + ("multihash", 0x31), + ("multiaddr", 0x32), + ("multibase", 0x33), + # multihashes + ("identity", 0x00), + ("md4", 0xD4), + ("md5", 0xD5), + ("sha1", 0x11), + ("sha2-256", 0x12), + ("sha2-512", 0x13), + ("dbl-sha2-256", 0x56), + ("sha3-224", 0x17), + ("sha3-256", 0x16), + ("sha3-384", 0x15), + ("sha3-512", 0x14), + ("shake-128", 0x18), + ("shake-256", 0x19), + ("keccak-224", 0x1A), + ("keccak-256", 0x1B), + ("keccak-384", 0x1C), + ("keccak-512", 0x1D), + ("murmur3", 0x22), + ("blake2b-8", 0xB201), ("blake2b-16", 0xB202), ("blake2b-24", 0xB203), + ("blake2b-32", 0xB204), ("blake2b-40", 0xB205), ("blake2b-48", 0xB206), + ("blake2b-56", 0xB207), ("blake2b-64", 0xB208), ("blake2b-72", 0xB209), + ("blake2b-80", 0xB20A), ("blake2b-88", 0xB20B), ("blake2b-96", 0xB20C), + ("blake2b-104", 0xB20D), ("blake2b-112", 0xB20E), ("blake2b-120", 0xB20F), + ("blake2b-128", 0xB210), ("blake2b-136", 0xB211), ("blake2b-144", 0xB212), + ("blake2b-152", 0xB213), ("blake2b-160", 0xB214), ("blake2b-168", 0xB215), + ("blake2b-176", 0xB216), ("blake2b-184", 0xB217), ("blake2b-192", 0xB218), + ("blake2b-200", 0xB219), ("blake2b-208", 0xB21A), ("blake2b-216", 0xB21B), + ("blake2b-224", 0xB21C), ("blake2b-232", 0xB21D), ("blake2b-240", 0xB21E), + ("blake2b-248", 0xB21F), ("blake2b-256", 0xB220), ("blake2b-264", 0xB221), + ("blake2b-272", 0xB222), ("blake2b-280", 0xB223), ("blake2b-288", 0xB224), + ("blake2b-296", 0xB225), ("blake2b-304", 0xB226), ("blake2b-312", 0xB227), + ("blake2b-320", 0xB228), ("blake2b-328", 0xB229), ("blake2b-336", 0xB22A), + ("blake2b-344", 0xB22B), ("blake2b-352", 0xB22C), ("blake2b-360", 0xB22D), + ("blake2b-368", 0xB22E), ("blake2b-376", 0xB22F), ("blake2b-384", 0xB230), + ("blake2b-392", 0xB231), ("blake2b-400", 0xB232), ("blake2b-408", 0xB233), + ("blake2b-416", 0xB234), ("blake2b-424", 0xB235), ("blake2b-432", 0xB236), + ("blake2b-440", 0xB237), ("blake2b-448", 0xB238), ("blake2b-456", 0xB239), + ("blake2b-464", 0xB23A), ("blake2b-472", 0xB23B), ("blake2b-480", 0xB23C), + ("blake2b-488", 0xB23D), ("blake2b-496", 0xB23E), ("blake2b-504", 0xB23F), + ("blake2b-512", 0xB240), ("blake2s-8", 0xB241), ("blake2s-16", 0xB242), + ("blake2s-24", 0xB243), ("blake2s-32", 0xB244), ("blake2s-40", 0xB245), + ("blake2s-48", 0xB246), ("blake2s-56", 0xB247), ("blake2s-64", 0xB248), + ("blake2s-72", 0xB249), ("blake2s-80", 0xB24A), ("blake2s-88", 0xB24B), + ("blake2s-96", 0xB24C), ("blake2s-104", 0xB24D), ("blake2s-112", 0xB24E), + ("blake2s-120", 0xB24F), ("blake2s-128", 0xB250), ("blake2s-136", 0xB251), + ("blake2s-144", 0xB252), ("blake2s-152", 0xB253), ("blake2s-160", 0xB254), + ("blake2s-168", 0xB255), ("blake2s-176", 0xB256), ("blake2s-184", 0xB257), + ("blake2s-192", 0xB258), ("blake2s-200", 0xB259), ("blake2s-208", 0xB25A), + ("blake2s-216", 0xB25B), ("blake2s-224", 0xB25C), ("blake2s-232", 0xB25D), + ("blake2s-240", 0xB25E), ("blake2s-248", 0xB25F), ("blake2s-256", 0xB260), + ("skein256-8", 0xB301), ("skein256-16", 0xB302), ("skein256-24", 0xB303), + ("skein256-32", 0xB304), ("skein256-40", 0xB305), ("skein256-48", 0xB306), + ("skein256-56", 0xB307), ("skein256-64", 0xB308), ("skein256-72", 0xB309), + ("skein256-80", 0xB30A), ("skein256-88", 0xB30B), ("skein256-96", 0xB30C), + ("skein256-104", 0xB30D), ("skein256-112", 0xB30E), ("skein256-120", 0xB30F), + ("skein256-128", 0xB310), ("skein256-136", 0xB311), ("skein256-144", 0xB312), + ("skein256-152", 0xB313), ("skein256-160", 0xB314), ("skein256-168", 0xB315), + ("skein256-176", 0xB316), ("skein256-184", 0xB317), ("skein256-192", 0xB318), + ("skein256-200", 0xB319), ("skein256-208", 0xB31A), ("skein256-216", 0xB31B), + ("skein256-224", 0xB31C), ("skein256-232", 0xB31D), ("skein256-240", 0xB31E), + ("skein256-248", 0xB31F), ("skein256-256", 0xB320), + ("skein512-8", 0xB321), ("skein512-16", 0xB322), ("skein512-24", 0xB323), + ("skein512-32", 0xB324), ("skein512-40", 0xB325), ("skein512-48", 0xB326), + ("skein512-56", 0xB327), ("skein512-64", 0xB328), ("skein512-72", 0xB329), + ("skein512-80", 0xB32A), ("skein512-88", 0xB32B), ("skein512-96", 0xB32C), + ("skein512-104", 0xB32D), ("skein512-112", 0xB32E), ("skein512-120", 0xB32F), + ("skein512-128", 0xB330), ("skein512-136", 0xB331), ("skein512-144", 0xB332), + ("skein512-152", 0xB333), ("skein512-160", 0xB334), ("skein512-168", 0xB335), + ("skein512-176", 0xB336), ("skein512-184", 0xB337), ("skein512-192", 0xB338), + ("skein512-200", 0xB339), ("skein512-208", 0xB33A), ("skein512-216", 0xB33B), + ("skein512-224", 0xB33C), ("skein512-232", 0xB33D), ("skein512-240", 0xB33E), + ("skein512-248", 0xB33F), ("skein512-256", 0xB340), ("skein512-264", 0xB341), + ("skein512-272", 0xB342), ("skein512-280", 0xB343), ("skein512-288", 0xB344), + ("skein512-296", 0xB345), ("skein512-304", 0xB346), ("skein512-312", 0xB347), + ("skein512-320", 0xB348), ("skein512-328", 0xB349), ("skein512-336", 0xB34A), + ("skein512-344", 0xB34B), ("skein512-352", 0xB34C), ("skein512-360", 0xB34D), + ("skein512-368", 0xB34E), ("skein512-376", 0xB34F), ("skein512-384", 0xB350), + ("skein512-392", 0xB351), ("skein512-400", 0xB352), ("skein512-408", 0xB353), + ("skein512-416", 0xB354), ("skein512-424", 0xB355), ("skein512-432", 0xB356), + ("skein512-440", 0xB357), ("skein512-448", 0xB358), ("skein512-456", 0xB359), + ("skein512-464", 0xB35A), ("skein512-472", 0xB35B), ("skein512-480", 0xB35C), + ("skein512-488", 0xB35D), ("skein512-496", 0xB35E), ("skein512-504", 0xB35F), + ("skein512-512", 0xB360), ("skein1024-8", 0xB361), ("skein1024-16", 0xB362), + ("skein1024-24", 0xB363), ("skein1024-32", 0xB364), ("skein1024-40", 0xB365), + ("skein1024-48", 0xB366), ("skein1024-56", 0xB367), ("skein1024-64", 0xB368), + ("skein1024-72", 0xB369), ("skein1024-80", 0xB36A), ("skein1024-88", 0xB36B), + ("skein1024-96", 0xB36C), ("skein1024-104", 0xB36D), + ("skein1024-112", 0xB36E), ("skein1024-120", 0xB36F), + ("skein1024-128", 0xB370), ("skein1024-136", 0xB371), + ("skein1024-144", 0xB372), ("skein1024-152", 0xB373), + ("skein1024-160", 0xB374), ("skein1024-168", 0xB375), + ("skein1024-176", 0xB376), ("skein1024-184", 0xB377), + ("skein1024-192", 0xB378), ("skein1024-200", 0xB379), + ("skein1024-208", 0xB37A), ("skein1024-216", 0xB37B), + ("skein1024-224", 0xB37C), ("skein1024-232", 0xB37D), + ("skein1024-240", 0xB37E), ("skein1024-248", 0xB37F), + ("skein1024-256", 0xB380), ("skein1024-264", 0xB381), + ("skein1024-272", 0xB382), ("skein1024-280", 0xB383), + ("skein1024-288", 0xB384), ("skein1024-296", 0xB385), + ("skein1024-304", 0xB386), ("skein1024-312", 0xB387), + ("skein1024-320", 0xB388), ("skein1024-328", 0xB389), + ("skein1024-336", 0xB38A), ("skein1024-344", 0xB38B), + ("skein1024-352", 0xB38C), ("skein1024-360", 0xB38D), + ("skein1024-368", 0xB38E), ("skein1024-376", 0xB38F), + ("skein1024-384", 0xB390), ("skein1024-392", 0xB391), + ("skein1024-400", 0xB392), ("skein1024-408", 0xB393), + ("skein1024-416", 0xB394), ("skein1024-424", 0xB395), + ("skein1024-432", 0xB396), ("skein1024-440", 0xB397), + ("skein1024-448", 0xB398), ("skein1024-456", 0xB399), + ("skein1024-464", 0xB39A), ("skein1024-472", 0xB39B), + ("skein1024-480", 0xB39C), ("skein1024-488", 0xB39D), + ("skein1024-496", 0xB39E), ("skein1024-504", 0xB39F), + ("skein1024-512", 0xB3A0), ("skein1024-520", 0xB3A1), + ("skein1024-528", 0xB3A2), ("skein1024-536", 0xB3A3), + ("skein1024-544", 0xB3A4), ("skein1024-552", 0xB3A5), + ("skein1024-560", 0xB3A6), ("skein1024-568", 0xB3A7), + ("skein1024-576", 0xB3A8), ("skein1024-584", 0xB3A9), + ("skein1024-592", 0xB3AA), ("skein1024-600", 0xB3AB), + ("skein1024-608", 0xB3AC), ("skein1024-616", 0xB3AD), + ("skein1024-624", 0xB3AE), ("skein1024-632", 0xB3AF), + ("skein1024-640", 0xB3B0), ("skein1024-648", 0xB3B1), + ("skein1024-656", 0xB3B2), ("skein1024-664", 0xB3B3), + ("skein1024-672", 0xB3B4), ("skein1024-680", 0xB3B5), + ("skein1024-688", 0xB3B6), ("skein1024-696", 0xB3B7), + ("skein1024-704", 0xB3B8), ("skein1024-712", 0xB3B9), + ("skein1024-720", 0xB3BA), ("skein1024-728", 0xB3BB), + ("skein1024-736", 0xB3BC), ("skein1024-744", 0xB3BD), + ("skein1024-752", 0xB3BE), ("skein1024-760", 0xB3BF), + ("skein1024-768", 0xB3C0), ("skein1024-776", 0xB3C1), + ("skein1024-784", 0xB3C2), ("skein1024-792", 0xB3C3), + ("skein1024-800", 0xB3C4), ("skein1024-808", 0xB3C5), + ("skein1024-816", 0xB3C6), ("skein1024-824", 0xB3C7), + ("skein1024-832", 0xB3C8), ("skein1024-840", 0xB3C9), + ("skein1024-848", 0xB3CA), ("skein1024-856", 0xB3CB), + ("skein1024-864", 0xB3CC), ("skein1024-872", 0xB3CD), + ("skein1024-880", 0xB3CE), ("skein1024-888", 0xB3CF), + ("skein1024-896", 0xB3D0), ("skein1024-904", 0xB3D1), + ("skein1024-912", 0xB3D2), ("skein1024-920", 0xB3D3), + ("skein1024-928", 0xB3D4), ("skein1024-936", 0xB3D5), + ("skein1024-944", 0xB3D6), ("skein1024-952", 0xB3D7), + ("skein1024-960", 0xB3D8), ("skein1024-968", 0xB3D9), + ("skein1024-976", 0xB3DA), ("skein1024-984", 0xB3DB), + ("skein1024-992", 0xB3DC), ("skein1024-1000", 0xB3DD), + ("skein1024-1008", 0xB3DE), ("skein1024-1016", 0xB3DF), + ("skein1024-1024", 0xB3E0), + # multiaddrs + ("ip4", 0x04), + ("ip6", 0x29), + ("ip6zone", 0x2A), + ("tcp", 0x06), + ("udp", 0x0111), + ("dccp", 0x21), + ("sctp", 0x84), + ("udt", 0x012D), + ("utp", 0x012E), + ("unix", 0x0190), # not in multicodec list + ("ipfs", 0x01A5), + ("p2p", 0x01A5), + ("http", 0x01E0), + ("https", 0x01BB), + ("quic", 0x01CC), + ("ws", 0x01DD), + ("wss", 0x01DE), # not in multicodec list + ("p2p-websocket-star", 0x01DF), # not in multicodec list + ("p2p-webrtc-star", 0x0113), # not in multicodec list + ("p2p-webrtc-direct", 0x0114), # not in multicodec list + ("onion", 0x01BC), + ("p2p-circuit", 0x0122), + ("dns4", 0x36), + ("dns6", 0x37), + ("dnsaddr", 0x38), + # IPLD formats + ("dag-pb", 0x70), + ("dag-cbor", 0x71), + ("dag-json", 0x129), + ("git-raw", 0x78), + ("eth-block", 0x90), + ("eth-block-list", 0x91), + ("eth-tx-trie", 0x92), + ("eth-tx", 0x93), + ("eth-tx-receipt-trie", 0x94), + ("eth-tx-receipt", 0x95), + ("eth-state-trie", 0x96), + ("eth-account-snapshot", 0x97), + ("eth-storage-trie", 0x98), + ("bitcoin-block", 0xB0), + ("bitcoin-tx", 0xB1), + ("zcash-block", 0xC0), + ("zcash-tx", 0xC1), + ("stellar-block", 0xD0), + ("stellar-tx", 0xD1), + ("decred-block", 0xE0), + ("decred-tx", 0xE1), + ("dash-block", 0xF0), + ("dash-tx", 0xF1), + ("torrent-info", 0x7B), + ("torrent-file", 0x7C), + ("ed25519-pub", 0xED) +] + +type + MultiCodec* = distinct int + MultiCodecError* = object of Exception + +const + InvalidMultiCodec* = MultiCodec(-1) + +proc initMultiCodecNameTable(): Table[string, int] {.compileTime.} = + result = initTable[string, int]() + for item in MultiCodecList: + result[item[0]] = item[1] + +proc initMultiCodecCodeTable(): Table[int, string] {.compileTime.} = + result = initTable[int, string]() + for item in MultiCodecList: + result[item[1]] = item[0] + +const + CodeCodecs = initMultiCodecCodeTable() + NameCodecs = initMultiCodecNameTable() + +proc multiCodec*(name: string): MultiCodec {.compileTime.} = + ## Generate MultiCodec from string ``name`` at compile time. + var code = NameCodecs.getOrDefault(name, -1) + if code == -1: + raise newException(MultiCodecError, + "MultiCodec `" & name & "` not supported!") + result = MultiCodec(code) + +proc multiCodec*(code: int): MultiCodec {.compileTime.} = + ## Generate MultiCodec from integer ``code`` at compile time. + var name = CodeCodecs.getOrDefault(code, "") + if name == "": + raise newException(MultiCodecError, + "MultiCodec with code " & $code & " not supported!") + result = MultiCodec(code) + +proc `$`*(mc: MultiCodec): string = + ## Returns string representation of MultiCodec ``mc``. + result = CodeCodecs.getOrDefault(int(mc), "") + if result == "": + raise newException(MultiCodecError, + "MultiCodec with code " & $int(mc) & " not supported!") + +proc `==`*(mc: MultiCodec, name: string): bool {.inline.} = + ## Compares MultiCodec ``mc`` with string ``name``. + var mcname = CodeCodecs.getOrDefault(int(mc), "") + if mcname == "": + return false + result = (mcname == name) + +proc `==`*(mc: MultiCodec, code: int): bool {.inline.} = + ## Compares MultiCodec ``mc`` with integer ``code``. + result = (int(mc) == code) + +proc `==`*(a, b: MultiCodec): bool = + ## Returns ``true`` if MultiCodecs ``a`` and ``b`` are equal. + int(a) == int(b) + +proc `!=`*(a, b: MultiCodec): bool = + ## Returns ``true`` if MultiCodecs ``a`` and ``b`` are not equal. + int(a) != int(b) + +proc hash*(m: MultiCodec): Hash {.inline.} = + ## Hash procedure for tables. + result = hash(int(m)) + +proc codec*(mt: typedesc[MultiCodec], name: string): MultiCodec {.inline.} = + ## Return MultiCodec from string representation ``name``. + ## If ``name`` is not valid multicodec name, then ``InvalidMultiCodec`` will + ## be returned. + result = MultiCodec(NameCodecs.getOrDefault(name, -1)) + +proc codec*(mt: typedesc[MultiCodec], code: int): MultiCodec {.inline.} = + ## Return MultiCodec from integer representation ``code``. + ## If ``code`` is not valid multicodec code, then ``InvalidMultiCodec`` will + ## be returned. + let res = CodeCodecs.getOrDefault(code, "") + if res == "": + result = InvalidMultiCodec + else: + result = MultiCodec(code) + +proc write*(vb: var VBuffer, mc: MultiCodec) {.inline.} = + ## Write MultiCodec to buffer ``vb``. + vb.writeVarint(cast[uint](mc)) diff --git a/libp2p/multihash.nim b/libp2p/multihash.nim new file mode 100644 index 0000000..349b2e2 --- /dev/null +++ b/libp2p/multihash.nim @@ -0,0 +1,550 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implements MultiHash. +## Supported hashes are: +## 1. IDENTITY +## 2. SHA2-256/SHA2-512 +## 3. DBL-SHA2-256 +## 4. SHA3/KECCAK +## 5. SHAKE-128/SHAKE-256 +## 6. BLAKE2s/BLAKE2s +## +## Hashes which are not yet supported +## 1. SHA1 +## 2. SKEIN +## 3. MURMUR +import tables +import nimcrypto/[sha2, keccak, blake2, hash, utils] +import varint, vbuffer, base58, multicodec, multibase + +const + MaxHashSize* = 128 + +type + MHashCoderProc* = proc(data: openarray[byte], + output: var openarray[byte]) {.nimcall, gcsafe.} + MHash* = object + mcodec*: MultiCodec + size*: int + coder*: MHashCoderProc + + MultiHash* = object + data*: VBuffer + mcodec*: MultiCodec + size*: int + dpos*: int + + MultiHashError* = object of Exception + +proc identhash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var length = if len(data) > len(output): len(output) + else: len(data) + copyMem(addr output[0], unsafeAddr data[0], length) + +proc dblsha2_256hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest1 = sha256.digest(data) + var digest2 = sha256.digest(digest1.data) + var length = if sha256.sizeDigest > len(output): len(output) + else: sha256.sizeDigest + copyMem(addr output[0], addr digest2.data[0], length) + +proc blake2Bhash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = blake2_512.digest(data) + var length = if blake2_512.sizeDigest > len(output): len(output) + else: blake2_512.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc blake2Shash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = blake2_256.digest(data) + var length = if blake2_256.sizeDigest > len(output): len(output) + else: blake2_256.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha2_256hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha256.digest(data) + var length = if sha256.sizeDigest > len(output): len(output) + else: sha256.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha2_512hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha512.digest(data) + var length = if sha512.sizeDigest > len(output): len(output) + else: sha512.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha3_224hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha3_224.digest(data) + var length = if sha3_224.sizeDigest > len(output): len(output) + else: sha3_224.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha3_256hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha3_256.digest(data) + var length = if sha3_256.sizeDigest > len(output): len(output) + else: sha3_256.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha3_384hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha3_384.digest(data) + var length = if sha3_384.sizeDigest > len(output): len(output) + else: sha3_384.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc sha3_512hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = sha3_512.digest(data) + var length = if sha3_512.sizeDigest > len(output): len(output) + else: sha3_512.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc keccak_224hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = keccak224.digest(data) + var length = if keccak224.sizeDigest > len(output): len(output) + else: keccak224.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc keccak_256hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = keccak256.digest(data) + var length = if keccak256.sizeDigest > len(output): len(output) + else: keccak256.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc keccak_384hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = keccak384.digest(data) + var length = if keccak384.sizeDigest > len(output): len(output) + else: keccak384.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc keccak_512hash(data: openarray[byte], output: var openarray[byte]) = + if len(output) > 0: + var digest = keccak512.digest(data) + var length = if keccak512.sizeDigest > len(output): len(output) + else: keccak512.sizeDigest + copyMem(addr output[0], addr digest.data[0], length) + +proc shake_128hash(data: openarray[byte], output: var openarray[byte]) = + var sctx: shake128 + if len(output) > 0: + sctx.init() + sctx.update(cast[ptr uint8](unsafeAddr data[0]), uint(len(data))) + sctx.xof() + discard sctx.output(addr output[0], uint(len(output))) + sctx.clear() + +proc shake_256hash(data: openarray[byte], output: var openarray[byte]) = + var sctx: shake256 + if len(output) > 0: + sctx.init() + sctx.update(cast[ptr uint8](unsafeAddr data[0]), uint(len(data))) + sctx.xof() + discard sctx.output(addr output[0], uint(len(output))) + sctx.clear() + +const + HashesList = [ + MHash(mcodec: multiCodec("identity"), size: 0, + coder: identhash), + MHash(mcodec: multiCodec("dbl-sha2-256"), size: sha256.sizeDigest, + coder: dblsha2_256hash + ), + MHash(mcodec: multiCodec("sha2-256"), size: sha256.sizeDigest, + coder: sha2_256hash + ), + MHash(mcodec: multiCodec("sha2-512"), size: sha512.sizeDigest, + coder: sha2_512hash + ), + MHash(mcodec: multiCodec("sha3-224"), size: sha3_224.sizeDigest, + coder: sha3_224hash + ), + MHash(mcodec: multiCodec("sha3-256"), size: sha3_256.sizeDigest, + coder: sha3_256hash + ), + MHash(mcodec: multiCodec("sha3-384"), size: sha3_384.sizeDigest, + coder: sha3_384hash + ), + MHash(mcodec: multiCodec("sha3-512"), size: sha3_512.sizeDigest, + coder: sha3_512hash + ), + MHash(mcodec: multiCodec("shake-128"), size: 32, coder: shake_128hash), + MHash(mcodec: multiCodec("shake-256"), size: 64, coder: shake_256hash), + MHash(mcodec: multiCodec("keccak-224"), size: keccak_224.sizeDigest, + coder: keccak_224hash + ), + MHash(mcodec: multiCodec("keccak-256"), size: keccak_256.sizeDigest, + coder: keccak_256hash + ), + MHash(mcodec: multiCodec("keccak-384"), size: keccak_384.sizeDigest, + coder: keccak_384hash + ), + MHash(mcodec: multiCodec("keccak-512"), size: keccak_512.sizeDigest, + coder: keccak_512hash + ), + MHash(mcodec: multiCodec("blake2b-8"), size: 1, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-16"), size: 2, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-24"), size: 3, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-32"), size: 4, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-40"), size: 5, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-48"), size: 6, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-56"), size: 7, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-64"), size: 8, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-72"), size: 9, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-80"), size: 10, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-88"), size: 11, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-96"), size: 12, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-104"), size: 13, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-112"), size: 14, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-120"), size: 15, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-128"), size: 16, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-136"), size: 17, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-144"), size: 18, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-152"), size: 19, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-160"), size: 20, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-168"), size: 21, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-176"), size: 22, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-184"), size: 23, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-192"), size: 24, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-200"), size: 25, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-208"), size: 26, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-216"), size: 27, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-224"), size: 28, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-232"), size: 29, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-240"), size: 30, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-248"), size: 31, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-256"), size: 32, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-264"), size: 33, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-272"), size: 34, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-280"), size: 35, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-288"), size: 36, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-296"), size: 37, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-304"), size: 38, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-312"), size: 39, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-320"), size: 40, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-328"), size: 41, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-336"), size: 42, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-344"), size: 43, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-352"), size: 44, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-360"), size: 45, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-368"), size: 46, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-376"), size: 47, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-384"), size: 48, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-392"), size: 49, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-400"), size: 50, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-408"), size: 51, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-416"), size: 52, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-424"), size: 53, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-432"), size: 54, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-440"), size: 55, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-448"), size: 56, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-456"), size: 57, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-464"), size: 58, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-472"), size: 59, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-480"), size: 60, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-488"), size: 61, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-496"), size: 62, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-504"), size: 63, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2b-512"), size: 64, coder: blake2Bhash), + MHash(mcodec: multiCodec("blake2s-8"), size: 1, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-16"), size: 2, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-24"), size: 3, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-32"), size: 4, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-40"), size: 5, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-48"), size: 6, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-56"), size: 7, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-64"), size: 8, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-72"), size: 9, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-80"), size: 10, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-88"), size: 11, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-96"), size: 12, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-104"), size: 13, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-112"), size: 14, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-120"), size: 15, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-128"), size: 16, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-136"), size: 17, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-144"), size: 18, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-152"), size: 19, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-160"), size: 20, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-168"), size: 21, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-176"), size: 22, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-184"), size: 23, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-192"), size: 24, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-200"), size: 25, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-208"), size: 26, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-216"), size: 27, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-224"), size: 28, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-232"), size: 29, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-240"), size: 30, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-248"), size: 31, coder: blake2Shash), + MHash(mcodec: multiCodec("blake2s-256"), size: 32, coder: blake2Shash) + ] + +proc initMultiHashCodeTable(): Table[MultiCodec, MHash] {.compileTime.} = + result = initTable[MultiCodec, MHash]() + for item in HashesList: + result[item.mcodec] = item + +const + CodeHashes = initMultiHashCodeTable() + +proc digestImplWithHash(hash: MHash, data: openarray[byte]): MultiHash = + var buffer: array[MaxHashSize, byte] + result.data = initVBuffer() + result.mcodec = hash.mcodec + result.data.write(hash.mcodec) + if hash.size == 0: + result.data.writeVarint(uint(len(data))) + result.dpos = len(result.data.buffer) + result.data.writeArray(data) + result.size = len(data) + else: + result.data.writeVarint(uint(hash.size)) + result.dpos = len(result.data.buffer) + hash.coder(data, buffer.toOpenArray(0, hash.size - 1)) + result.data.writeArray(buffer.toOpenArray(0, hash.size - 1)) + result.size = hash.size + result.data.finish() + +proc digestImplWithoutHash(hash: MHash, data: openarray[byte]): MultiHash = + result.data = initVBuffer() + result.mcodec = hash.mcodec + result.size = len(data) + result.data.write(hash.mcodec) + result.data.writeVarint(uint(len(data))) + result.dpos = len(result.data.buffer) + result.data.writeArray(data) + result.data.finish() + +proc digest*(mhtype: typedesc[MultiHash], hashname: string, + data: openarray[byte]): MultiHash {.inline.} = + ## Perform digest calculation using hash algorithm with name ``hashname`` on + ## data array ``data``. + let mc = MultiCodec.codec(hashname) + if mc == InvalidMultiCodec: + raise newException(MultihashError, "Incorrect hash name") + let hash = CodeHashes.getOrDefault(mc) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + result = digestImplWithHash(hash, data) + +proc digest*(mhtype: typedesc[MultiHash], hashcode: int, + data: openarray[byte]): MultiHash {.inline.} = + ## Perform digest calculation using hash algorithm with code ``hashcode`` on + ## data array ``data``. + let hash = CodeHashes.getOrDefault(hashcode) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + result = digestImplWithHash(hash, data) + +proc init*[T](mhtype: typedesc[MultiHash], hashname: string, + mdigest: MDigest[T]): MultiHash {.inline.} = + ## Create MultiHash from nimcrypto's `MDigest` object and hash algorithm name + ## ``hashname``. + let mc = MultiCodec.codec(hashname) + if mc == InvalidMultiCodec: + raise newException(MultihashError, "Incorrect hash name") + let hash = CodeHashes.getOrDefault(mc) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + if hash.size != len(mdigest.data): + raise newException(MultiHashError, "Incorrect MDigest[T] size") + result = digestImplWithoutHash(hash, mdigest.data) + +proc init*[T](mhtype: typedesc[MultiHash], hashcode: MultiCodec, + mdigest: MDigest[T]): MultiHash {.inline.} = + ## Create MultiHash from nimcrypto's `MDigest` and hash algorithm code + ## ``hashcode``. + let hash = CodeHashes.getOrDefault(hashcode) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + if (hash.size != 0) and (hash.size != len(mdigest.data)): + raise newException(MultiHashError, "Incorrect MDigest[T] size") + result = digestImplWithoutHash(hash, mdigest.data) + +proc init*(mhtype: typedesc[MultiHash], hashname: string, + bdigest: openarray[byte]): MultiHash {.inline.} = + ## Create MultiHash from array of bytes ``bdigest`` and hash algorithm code + ## ``hashcode``. + let mc = MultiCodec.codec(hashname) + if mc == InvalidMultiCodec: + raise newException(MultihashError, "Incorrect hash name") + let hash = CodeHashes.getOrDefault(mc) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + if (hash.size != 0) and (hash.size != len(bdigest)): + raise newException(MultiHashError, "Incorrect bdigest size") + result = digestImplWithoutHash(hash, bdigest) + +proc init*(mhtype: typedesc[MultiHash], hashcode: MultiCodec, + bdigest: openarray[byte]): MultiHash {.inline.} = + ## Create MultiHash from array of bytes ``bdigest`` and hash algorithm code + ## ``hashcode``. + let hash = CodeHashes.getOrDefault(hashcode) + if isNil(hash.coder): + raise newException(MultihashError, "Hash not supported") + if (hash.size != 0) and (hash.size != len(bdigest)): + raise newException(MultiHashError, "Incorrect bdigest size") + result = digestImplWithoutHash(hash, bdigest) + +proc decode*(mhtype: typedesc[MultiHash], data: openarray[byte], + mhash: var MultiHash): int = + ## Decode MultiHash value from array of bytes ``data``. + ## + ## On success decoded MultiHash will be stored into ``mhash`` and number of + ## bytes consumed will be returned. + ## + ## On error ``-1`` will be returned. + var code, size: uint64 + var res, dpos: int + if len(data) < 2: + return -1 + var vb = initVBuffer(data) + if vb.isEmpty(): + return -1 + res = vb.readVarint(code) + if res == -1: + return -1 + dpos += res + res = vb.readVarint(size) + if res == -1: + return -1 + dpos += res + if size > 0x7FFF_FFFF'u64: + return -1 + let hash = CodeHashes.getOrDefault(MultiCodec(code)) + if isNil(hash.coder): + return -1 + if (hash.size != 0) and (hash.size != int(size)): + return -1 + if not vb.isEnough(int(size)): + return -1 + mhash = MultiHash.init(MultiCodec(code), + vb.buffer.toOpenArray(vb.offset, + vb.offset + int(size) - 1)) + result = vb.offset + int(size) + +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 + if len(data) < 2: + return false + let last = len(data) - 1 + var offset = 0 + var length = 0 + res = LP.getUVarint(data.toOpenArray(offset, last), length, code) + if res != VarintStatus.Success: + return false + offset += length + if offset >= len(data): + return false + res = LP.getUVarint(data.toOpenArray(offset, last), length, size) + if res != VarintStatus.Success: + return false + offset += length + if size > 0x7FFF_FFFF'u64: + return false + let hash = CodeHashes.getOrDefault(cast[MultiCodec](code)) + if isNil(hash.coder): + return false + if (hash.size != 0) and (hash.size != int(size)): + return false + if offset + int(size) > len(data): + return false + result = true + +proc init*(mhtype: typedesc[MultiHash], + data: openarray[byte]): MultiHash {.inline.} = + ## Create MultiHash from bytes array ``data``. + if MultiHash.decode(data, result) == -1: + raise newException(MultihashError, "Incorrect MultiHash binary format") + +proc init*(mhtype: typedesc[MultiHash], data: string): MultiHash {.inline.} = + ## Create MultiHash from hexadecimal string representation ``data``. + if MultiHash.decode(fromHex(data), result) == -1: + raise newException(MultihashError, "Incorrect MultiHash binary format") + +proc init58*(mhtype: typedesc[MultiHash], + data: string): MultiHash {.inline.} = + ## Create MultiHash from BASE58 encoded string representation ``data``. + if MultiHash.decode(Base58.decode(data), result) == -1: + raise newException(MultihashError, "Incorrect MultiHash binary format") + +proc cmp(a: openarray[byte], b: openarray[byte]): bool {.inline.} = + if len(a) != len(b): + return false + var n = len(a) + var res, diff: int + while n > 0: + dec(n) + diff = int(a[n]) - int(b[n]) + res = (res and -not(diff)) or diff + result = (res == 0) + +proc `==`*[T](mh: MultiHash, mdigest: MDigest[T]): bool = + ## Compares MultiHash with nimcrypto's MDigest[T], returns ``true`` if + ## hashes are equal, ``false`` otherwise. + if mh.dpos == 0: + return false + if len(mdigest.data) != mh.size: + return false + result = cmp(mh.data.buffer.toOpenArray(mh.dpos, mh.dpos + mh.size - 1), + mdigest.data.toOpenArray(0, len(mdigest.data) - 1)) + +proc `==`*[T](mdigest: MDigest[T], mh: MultiHash): bool {.inline.} = + ## Compares MultiHash with nimcrypto's MDigest[T], returns ``true`` if + ## hashes are equal, ``false`` otherwise. + result = `==`(mh, mdigest) + +proc `==`*(a: MultiHash, b: MultiHash): bool = + ## Compares MultiHashes ``a`` and ``b``, returns ``true`` if hashes are equal, + ## ``false`` otherwise. + if a.dpos == 0 and b.dpos == 0: + return true + if a.mcodec != b.mcodec: + return false + if a.size != b.size: + return false + result = cmp(a.data.buffer.toOpenArray(a.dpos, a.dpos + a.size - 1), + b.data.buffer.toOpenArray(b.dpos, b.dpos + b.size - 1)) + +proc hex*(value: MultiHash): string = + ## Return hexadecimal string representation of MultiHash ``value``. + result = $(value.data) + +proc base58*(value: MultiHash): string = + ## Return Base58 encoded string representation of MultiHash ``value``. + result = Base58.encode(value.data.buffer) + +proc `$`*(mh: MultiHash): string = + ## Return string representation of MultiHash ``value``. + let digest = toHex(mh.data.buffer.toOpenArray(mh.dpos, + mh.dpos + mh.size - 1)) + result = $(mh.mcodec) & "/" & digest + +proc write*(vb: var VBuffer, mh: MultiHash) {.inline.} = + ## Write MultiHash value ``mh`` to buffer ``vb``. + vb.writeArray(mh.data.buffer) + +proc encode*(mbtype: typedesc[MultiBase], encoding: string, + mh: MultiHash): string {.inline.} = + ## Get MultiBase encoded representation of ``mh`` using encoding + ## ``encoding``. + result = MultiBase.encode(encoding, mh.data.buffer) diff --git a/libp2p/protobuf/minprotobuf.nim b/libp2p/protobuf/minprotobuf.nim index feaf1a7..14d008d 100644 --- a/libp2p/protobuf/minprotobuf.nim +++ b/libp2p/protobuf/minprotobuf.nim @@ -167,9 +167,10 @@ proc write*(pb: var ProtoBuffer, field: ProtoField) = assert(res == VarintStatus.Success) pb.offset += length assert(pb.isEnough(len(field.vbuffer))) - copyMem(addr pb.buffer[pb.offset], unsafeAddr field.vbuffer[0], - len(field.vbuffer)) - pb.offset += len(field.vbuffer) + if len(field.vbuffer) > 0: + copyMem(addr pb.buffer[pb.offset], unsafeAddr field.vbuffer[0], + len(field.vbuffer)) + pb.offset += len(field.vbuffer) else: discard diff --git a/libp2p/transcoder.nim b/libp2p/transcoder.nim new file mode 100644 index 0000000..efe2ea5 --- /dev/null +++ b/libp2p/transcoder.nim @@ -0,0 +1,19 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implements transcoder interface. +import vbuffer + +type + Transcoder* = object + stringToBuffer*: proc(s: string, + vb: var VBuffer): bool {.nimcall, gcsafe.} + bufferToString*: proc(vb: var VBuffer, + s: var string): bool {.nimcall, gcsafe.} + validateBuffer*: proc(vb: var VBuffer): bool {.nimcall, gcsafe.} diff --git a/libp2p/varint.nim b/libp2p/varint.nim index e264db6..506412f 100644 --- a/libp2p/varint.nim +++ b/libp2p/varint.nim @@ -16,7 +16,7 @@ import bitops type - VarintStatus* = enum + VarintStatus* {.pure.} = enum Error, Success, Overflow, diff --git a/libp2p/vbuffer.nim b/libp2p/vbuffer.nim new file mode 100644 index 0000000..0e663d6 --- /dev/null +++ b/libp2p/vbuffer.nim @@ -0,0 +1,180 @@ +## Nim-Libp2p +## Copyright (c) 2018 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +## This module implements variable buffer. +import varint, strutils + +type + VBuffer* = object + buffer*: seq[byte] + offset*: int + length*: int + +template isEmpty*(vb: VBuffer): bool = + ## Returns ``true`` if buffer ``vb`` is empty. + len(vb.buffer) - vb.offset <= 0 + +template isEnough*(vb: VBuffer, length: int): bool = + ## Returns ``true`` if buffer ``vb`` holds at least ``length`` bytes. + len(vb.buffer) - vb.offset - length >= 0 + +proc len*(vb: VBuffer): int = + ## Returns number of bytes left in buffer ``vb``. + result = len(vb.buffer) - vb.offset + +proc isLiteral[T](s: seq[T]): bool {.inline.} = + type + SeqHeader = object + length, reserved: int + (cast[ptr SeqHeader](s).reserved and (1 shl (sizeof(int) * 8 - 2))) != 0 + +proc initVBuffer*(data: seq[byte], offset = 0): VBuffer = + ## Initialize VBuffer with shallow copy of ``data``. + if isLiteral(data): + result.buffer = data + else: + shallowCopy(result.buffer, data) + result.offset = offset + +proc initVBuffer*(data: openarray[byte], offset = 0): VBuffer = + ## Initialize VBuffer with copy of ``data``. + result.buffer = newSeq[byte](len(data)) + if len(data) > 0: + copyMem(addr result.buffer[0], unsafeAddr data[0], len(data)) + result.offset = offset + +proc initVBuffer*(): VBuffer = + ## Initialize empty VBuffer. + result.buffer = newSeqOfCap[byte](128) + +proc writeVarint*(vb: var VBuffer, value: LPSomeUVarint) = + ## Write ``value`` as variable unsigned integer. + var length = 0 + when sizeof(value) == 8: + # LibP2P varint supports only 63 bits. + var v = value and cast[type(value)](0x7FFF_FFFF_FFFF_FFFF) + else: + var v = uint64(v) + vb.buffer.setLen(len(vb.buffer) + vsizeof(v)) + let res = LP.putUVarint(toOpenArray(vb.buffer, vb.offset, len(vb.buffer) - 1), + length, v) + assert(res == VarintStatus.Success) + vb.offset += length + +proc writeSeq*[T: byte|char](vb: var VBuffer, value: openarray[T]) = + ## Write array ``value`` to buffer ``vb``, value will be prefixed with + ## varint length of the array. + var length = 0 + vb.buffer.setLen(len(vb.buffer) + vsizeof(len(value)) + len(value)) + let res = LP.putUVarint(toOpenArray(vb.buffer, vb.offset, len(vb.buffer) - 1), + length, uint(len(value))) + assert(res == VarintStatus.Success) + vb.offset += length + if len(value) > 0: + copyMem(addr vb.buffer[vb.offset], unsafeAddr value[0], len(value)) + vb.offset += len(value) + +proc writeArray*[T: byte|char](vb: var VBuffer, value: openarray[T]) = + ## Write array ``value`` to buffer ``vb``, value will NOT be prefixed with + ## varint length of the array. + var length = 0 + if len(value) > 0: + vb.buffer.setLen(len(vb.buffer) + len(value)) + copyMem(addr vb.buffer[vb.offset], unsafeAddr value[0], len(value)) + vb.offset += len(value) + +proc finish*(vb: var VBuffer) = + ## Finishes ``vb``. + vb.offset = 0 + +proc peekVarint*(vb: var VBuffer, value: var LPSomeUVarint): int = + ## Peek unsigned integer from buffer ``vb`` and store result to ``value``. + ## + ## This procedure will not adjust internal offset. + ## + ## Returns number of bytes peeked from ``vb`` or ``-1`` on error. + result = -1 + value = cast[type(value)](0) + var length = 0 + if not vb.isEmpty(): + let res = LP.getUVarint( + toOpenArray(vb.buffer, vb.offset, len(vb.buffer) - 1), length, value) + if res == VarintStatus.Success: + result = length + +proc peekSeq*[T: string|seq[byte]](vb: var VBuffer, value: var T): int = + ## Peek length prefixed array from buffer ``vb`` and store result to + ## ``value``. + ## + ## This procedure will not adjust internal offset. + ## + ## Returns number of bytes peeked from ``vb`` or ``-1`` on error. + result = -1 + value.setLen(0) + var length = 0 + var size = 0'u64 + if not vb.isEmpty() and + LP.getUVarint(toOpenArray(vb.buffer, vb.offset, len(vb.buffer) - 1), + length, size) == VarintStatus.Success: + vb.offset += length + result = length + if vb.isEnough(int(size)): + value.setLen(size) + if size > 0'u64: + copyMem(addr value[0], addr vb.buffer[vb.offset], size) + result += int(size) + vb.offset -= length + +proc peekArray*[T: char|byte](vb: var VBuffer, + value: var openarray[T]): int = + ## Peek array from buffer ``vb`` and store result to ``value``. + ## + ## This procedure will not adjust internal offset. + ## + ## Returns number of bytes peeked from ``vb`` or ``-1`` on error. + result = -1 + let length = len(value) + if vb.isEnough(length): + if length > 0: + copyMem(addr value[0], addr vb.buffer[vb.offset], length) + result = length + +proc readVarint*(vb: var VBuffer, value: var LPSomeUVarint): int {.inline.} = + ## Read unsigned integer from buffer ``vb`` and store result to ``value``. + ## + ## Returns number of bytes consumed from ``vb`` or ``-1`` on error. + result = vb.peekVarint(value) + if result != -1: + vb.offset += result + +proc readSeq*[T: string|seq[byte]](vb: var VBuffer, + value: var T): int {.inline.} = + ## Read length prefixed array from buffer ``vb`` and store result to + ## ``value``. + ## + ## Returns number of bytes consumed from ``vb`` or ``-1`` on error. + result = vb.peekSeq(value) + if result != -1: + vb.offset += result + +proc readArray*[T: char|byte](vb: var VBuffer, + value: var openarray[T]): int {.inline.} = + ## Read array from buffer ``vb`` and store result to ``value``. + ## + ## Returns number of bytes consumed from ``vb`` or ``-1`` on error. + result = vb.peekArray(value) + if result != -1: + vb.offset += result + +proc `$`*(vb: VBuffer): string = + ## Return hexadecimal string representation of buffer ``vb``. + let length = (len(vb.buffer) - vb.offset) * 2 + result = newStringOfCap(length) + for i in 0.. 0: + while i < len(a): + let c = a[i] + if i != 0 and i %% 2 == 0: + result[k] = r.byte + r = 0 + inc(k) + else: + r = r shl 4 + case c + of 'a'..'f': + r = r or (10 + ord(c) - ord('a')) + of 'A'..'F': + r = r or (10 + ord(c) - ord('A')) + of '0'..'9': + r = r or (ord(c) - ord('0')) + else: + doAssert(false) + inc(i) + result[k] = r.byte + +proc fromHex*(a: string): seq[byte] = + doAssert(len(a) %% 2 == 0) + if len(a) == 0: + result = newSeq[byte]() + else: + result = newSeq[byte](len(a) div 2) + hexToBytes(a, result) + +const TestVectors = [ + ["", ""], + ["61", "2g"], + ["626262", "a3gV"], + ["636363", "aPEr"], + ["73696d706c792061206c6f6e6720737472696e67", "2cFupjhnEsSn59qHXstmK2ffpLv2"], + ["00eb15231dfceb60925886b67d065299925915aeb172c06647", "1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L"], + ["516b6fcd0f", "ABnLTmg"], + ["bf4f89001e670274dd", "3SEo3LWLoPntC"], + ["572e4794", "3EFU7m"], + ["ecac89cad93923c02321", "EJDM8drfXA6uyA"], + ["10c8511e", "Rt5zm"], + ["00000000000000000000", "1111111111"], + ["000111d38e5fc9071ffcd20b4a763cc9ae4f252bb4e48fd66a835e252ada93ff480d6dd43dc62a641155a5", "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"], + ["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff", "1cWB5HCBdLjAuqGGReWE3R3CguuwSjw6RHn39s2yuDRTS5NsBgNiFpWgAnEx6VQi8csexkgYw3mdYrMHr8x9i7aEwP8kZ7vccXWqKDvGv3u1GxFKPuAkn8JCPPGDMf3vMMnbzm6Nh9zh1gcNsMvH3ZNLmP5fSG6DGbbi2tuwMWPthr4boWwCxf7ewSgNQeacyozhKDDQQ1qL5fQFUW52QKUZDZ5fw3KXNQJMcNTcaB723LchjeKun7MuGW5qyCBZYzA1KjofN1gYBV3NqyhQJ3Ns746GNuf9N2pQPmHz4xpnSrrfCvy6TVVz5d4PdrjeshsWQwpZsZGzvbdAdN8MKV5QsBDY"] +] + +suite "BASE58 encoding test suite": + test "Empty seq/string test": + var a = Base58.encode([]) + check len(a) == 0 + var b = Base58.decode("") + check len(b) == 0 + test "Zero test": + var s = newString(256) + for i in 0..255: + s[i] = '1' + var buffer: array[256, byte] + for i in 0..255: + var a = Base58.encode(buffer.toOpenArray(0, i)) + check a == s[0..i] + var b = Base58.decode(a) + check b == buffer[0..i] + test "Leading zero test": + var buffer: array[256, byte] + for i in 0..255: + buffer[255] = byte(i) + var a = Base58.encode(buffer) + var b = Base58.decode(a) + check: + equalMem(addr buffer[0], addr b[0], 256) == true + test "Small amount of bytes test": + var buffer1: array[1, byte] + var buffer2: array[2, byte] + for i in 0..255: + buffer1[0] = byte(i) + var enc = Base58.encode(buffer1) + var dec = Base58.decode(enc) + check: + len(dec) == 1 + dec[0] == buffer1[0] + + for i in 0..255: + for k in 0..255: + buffer2[0] = byte(i) + buffer2[1] = byte(k) + var enc = Base58.encode(buffer2) + var dec = Base58.decode(enc) + check: + len(dec) == 2 + dec[0] == buffer2[0] + dec[1] == buffer2[1] + test "Test Vectors test": + for item in TestVectors: + var a = fromHex(item[0]) + var enc = Base58.encode(a) + var dec = Base58.decode(item[1]) + check: + enc == item[1] + dec == a + test "Buffer Overrun test": + var encres = "" + var encsize = 0 + var decres: seq[byte] = @[] + var decsize = 0 + check: + Base58.encode([0'u8], encres, encsize) == Base58Status.Overrun + encsize == 1 + Base58.decode("1", decres, decsize) == Base58Status.Overrun + decsize == 5 + test "Incorrect test": + var decres = newSeq[byte](10) + var decsize = 0 + check: + Base58.decode("l", decres, decsize) == Base58Status.Incorrect + decsize == 0 + Base58.decode("2l", decres, decsize) == Base58Status.Incorrect + decsize == 0 + Base58.decode("O", decres, decsize) == Base58Status.Incorrect + decsize == 0 + Base58.decode("2O", decres, decsize) == Base58Status.Incorrect + decsize == 0 + diff --git a/tests/testcid.nim b/tests/testcid.nim new file mode 100644 index 0000000..70b89fd --- /dev/null +++ b/tests/testcid.nim @@ -0,0 +1,58 @@ +import unittest +import ../libp2p/[cid, multihash, multicodec] + +suite "Content identifier CID test suite": + + test "CIDv0 test vector": + var cid0Text = "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n" + var cid0 = Cid.init(cid0Text) + check: + $cid0 == cid0Text + cid0.version() == CIDv0 + cid0.contentType() == multiCodec("dag-pb") + cid0.mhash().mcodec == multiCodec("sha2-256") + var res = 0 + try: + var cidb0 = Cid.init("QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zIII") + except CidError: + res = 1 + check res == 1 + + test "CIDv1 test vector": + var cid1Text = "zb2rhhFAEMepUBbGyP1k8tGfz7BSciKXP6GHuUeUsJBaK6cqG" + var chex = "015512209D8453505BDC6F269678E16B3E56" & + "C2A2948A41F2C792617CC9611ED363C95B63" + var cid1 = Cid.init(cid1Text) + check: + $cid1 == cid1Text + cid1.version() == CIDv1 + cid1.contentType() == multiCodec("raw") + cid1.mhash().mcodec == multiCodec("sha2-256") + hex(cid1) == chex + + test "Comparison test": + var msg = "Hello World!" + var mmsg = "Hello World!Hello World!" + 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)) + var cid1 = Cid.init(CIDv1, multiCodec("dag-pb"), + MultiHash.digest("sha2-256", bmsg)) + var cid2 = cid1 + var cid3 = cid0 + var cid4 = Cid.init(CIDv1, multiCodec("dag-cbor"), + MultiHash.digest("sha2-256", bmsg)) + var cid5 = Cid.init(CIDv1, multiCodec("dag-pb"), + MultiHash.digest("sha2-256", bmmsg)) + var cid6 = Cid.init(CIDv1, multiCodec("dag-pb"), + MultiHash.digest("keccak-256", bmsg)) + check: + cid0 == cid1 + cid1 == cid2 + cid2 == cid3 + cid3 == cid0 + cid0 != cid4 + cid1 != cid5 + cid2 != cid4 + cid3 != cid6 diff --git a/tests/testdaemon.nim b/tests/testdaemon.nim index cd72f44..a7a0e12 100644 --- a/tests/testdaemon.nim +++ b/tests/testdaemon.nim @@ -1,16 +1,17 @@ import unittest import asyncdispatch2 -import ../libp2p/daemon/daemonapi +import ../libp2p/daemon/daemonapi, ../libp2p/multiaddress, ../libp2p/multicodec, + ../libp2p/cid, ../libp2p/multihash proc identitySpawnTest(): Future[bool] {.async.} = - var api = await newDaemonApi(sockpath = "/tmp/p2pd-1.sock") + var api = await newDaemonApi() var data = await api.identity() await api.close() result = true proc connectStreamTest(): Future[bool] {.async.} = - var api1 = await newDaemonApi(sockpath = "/tmp/p2pd-1.sock") - var api2 = await newDaemonApi(sockpath = "/tmp/p2pd-2.sock") + var api1 = await newDaemonApi() + var api2 = await newDaemonApi() var id1 = await api1.identity() var id2 = await api2.identity() @@ -26,15 +27,122 @@ proc connectStreamTest(): Future[bool] {.async.} = await api2.addHandler(protos, streamHandler) await api1.connect(id2.peer, id2.addresses) + # echo await api1.listPeers() var stream = await api1.openStream(id2.peer, protos) let sent = await stream.transp.write(test & "\r\n") doAssert(sent == len(test) + 2) var check = await wait(testFuture, 10000) doAssert(check == test) + await stream.close() await api1.close() await api2.close() result = true +proc provideCidTest(): Future[bool] {.async.} = + var api1 = await newDaemonApi({DHTFull}) + var api2 = await newDaemonApi({DHTFull}) + var msg = "ethereum2-beacon-chain" + var bmsg = cast[seq[byte]](msg) + var mh = MultiHash.digest("sha2-256", bmsg) + var cid = Cid.init(CIDv1, multiCodec("dag-pb"), mh) + + var id1 = await api1.identity() + var id2 = await api2.identity() + + await api1.connect(id2.peer, id2.addresses) + while true: + var peers = await api1.listPeers() + if len(peers) != 0: + break + + await api1.dhtProvide(cid) + var peers = await api2.dhtFindProviders(cid, 10) + + if len(peers) == 1: + if peers[0].peer == id1.peer: + result = true + + await api1.close() + await api2.close() + +# proc getOnlyOneIPv4Address(addresses: seq[MultiAddress]): seq[MultiAddress] = +# ## We doing this becuase of bug in `go-pubsub` +# ## https://github.com/libp2p/go-libp2p-pubsub/issues/130 +# if len(addresses) > 0: +# result = newSeqOfCap[MultiAddress](len(addresses)) +# let ip4 = multiCodec("ip4") +# for item in addresses: +# if item.protoCode() == ip4: +# result.add(item) +# break + +proc pubsubTest(f: set[P2PDaemonFlags]): Future[bool] {.async.} = + var pubsubData = "TEST MESSAGE" + var msgData = cast[seq[byte]](pubsubData) + var api1, api2: DaemonAPI + + api1 = await newDaemonApi(f + {Verbose, Logging}) + api2 = await newDaemonApi(f + {Verbose, Logging}) + + var id1 = await api1.identity() + var id2 = await api2.identity() + + var resultsCount = 0 + + var handlerFuture1 = newFuture[void]() + var handlerFuture2 = newFuture[void]() + + proc pubsubHandler1(api: DaemonAPI, + ticket: PubsubTicket, + message: PubSubMessage): Future[bool] {.async.} = + let smsg = cast[string](message.data) + if smsg == pubsubData: + inc(resultsCount) + handlerFuture1.complete() + # Callback must return `false` to close subscription channel. + result = false + + proc pubsubHandler2(api: DaemonAPI, + ticket: PubsubTicket, + message: PubSubMessage): Future[bool] {.async.} = + let smsg = cast[string](message.data) + if smsg == pubsubData: + inc(resultsCount) + handlerFuture2.complete() + # Callback must return `false` to close subscription channel. + result = false + + await api1.connect(id2.peer, id2.addresses) + await api2.connect(id1.peer, id1.addresses) + + var ticket1 = await api1.pubsubSubscribe("test-topic", pubsubHandler1) + var ticket2 = await api2.pubsubSubscribe("test-topic", pubsubHandler2) + + await sleepAsync(2000) + + var topics1 = await api1.pubsubGetTopics() + var topics2 = await api2.pubsubGetTopics() + + if len(topics1) == 1 and len(topics2) == 1: + var peers1 = await api1.pubsubListPeers("test-topic") + var peers2 = await api2.pubsubListPeers("test-topic") + if len(peers1) == 1 and len(peers2) == 1: + # Publish test data via api1. + await sleepAsync(500) + await api1.pubsubPublish("test-topic", msgData) + var andfut = handlerFuture1 and handlerFuture2 + await andfut or sleepAsync(10000) + + await api1.close() + await api2.close() + if resultsCount == 2: + result = true + else: + echo " -- CLIENT1 -- " + echo api1.log + echo " -- CLIENT2 -- " + echo api2.log + when isMainModule: suite "libp2p-daemon test suite": test "Simple spawn and get identity test": @@ -43,3 +151,12 @@ when isMainModule: test "Connect/Accept peer/stream test": check: waitFor(connectStreamTest()) == true + test "Provide CID test": + check: + waitFor(provideCidTest()) == true + test "GossipSub test": + check: + waitFor(pubsubTest({PSGossipSub})) == true + test "FloodSub test": + check: + waitFor(pubsubTest({PSFloodSub})) == true diff --git a/tests/testmultiaddress.nim b/tests/testmultiaddress.nim new file mode 100644 index 0000000..0f6611b --- /dev/null +++ b/tests/testmultiaddress.nim @@ -0,0 +1,218 @@ +import unittest +import ../libp2p/multiaddress + +const + SuccessVectors = [ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21/udp/1234/quic", + "/ip6zone/x/ip6/fe80::1", + "/ip6zone/x%y/ip6/fe80::1", + "/ip6zone/x%y/ip6/::", + "/ip6zone/x/ip6/fe80::1/udp/1234/quic", + "/onion/timaq4ygg2iegci7:1234", + "/onion/timaq4ygg2iegci7:80/http", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/tcp/1234/", + "/ip4/127.0.0.1/udp/1234/quic", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/unix/a/b/c/d/e", + "/unix/stdio", + "/ip4/1.2.3.4/tcp/80/unix/a/b/c/d/e/f", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + "/ip4/127.0.0.1/p2p/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234/unix/stdio", + ] + + FailureVectors = [ + "/ip4", + "/ip4/::1", + "/ip4/fdpsofodsajfdoisa", + "/ip6", + "/ip6zone", + "/ip6zone/", + "/ip6zone//ip6/fe80::1", + "/udp", + "/tcp", + "/sctp", + "/udp/65536", + "/tcp/65536", + "/quic/65536", + "/onion/9imaq4ygg2iegci7:80", + "/onion/aaimaq4ygg2iegci7:80", + "/onion/timaq4ygg2iegci7:0", + "/onion/timaq4ygg2iegci7:-1", + "/onion/timaq4ygg2iegci7", + "/onion/timaq4ygg2iegci@:666", + "/udp/1234/sctp", + "/udp/1234/udt/1234", + "/udp/1234/utp/1234", + "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/udp", + "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/tcp", + "/ip4/127.0.0.1/quic/1234", + "/ip4/127.0.0.1/ipfs", + "/ip4/127.0.0.1/ipfs/tcp", + "/ip4/127.0.0.1/p2p", + "/ip4/127.0.0.1/p2p/tcp", + "/unix" + ] + + RustSuccessVectors = [ + "/ip4/1.2.3.4", + "/ip4/0.0.0.0", + "/ip6/::1", + "/ip6/2601:9:4f81:9700:803e:ca65:66e8:c21", + "/udp/0", + "/tcp/0", + "/sctp/0", + "/udp/1234", + "/tcp/1234", + "/sctp/1234", + "/udp/65535", + "/tcp/65535", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/udp/1234/sctp/1234", + "/udp/1234/udt", + "/udp/1234/utp", + "/tcp/1234/http", + "/tcp/1234/https", + "/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip4/127.0.0.1/udp/1234", + "/ip4/127.0.0.1/udp/0", + "/ip4/127.0.0.1/tcp/1234", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC/tcp/1234", + "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/tcp/8000/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/p2p-webrtc-star/ip4/127.0.0.1/tcp/9090/ws/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC", + "/ip4/127.0.0.1/tcp/9090/p2p-circuit/ipfs/QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC" + ] + + RustSuccessExpects = [ + "0401020304", + "0400000000", + "2900000000000000000000000000000001", + "29260100094F819700803ECA6566E80C21", + "91020000", + "060000", + "84010000", + "910204D2", + "0604D2", + "840104D2", + "9102FFFF", + "06FFFF", + "A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", + "910204D2840104D2", + "910204D2AD02", + "910204D2AE02", + "0604D2E003", + "0604D2BB03", + "A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B0604D2", + "047F000001910204D2", + "047F00000191020000", + "047F0000010604D2", + "047F000001A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", + "047F000001A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B0604D2", + "29200108A07AC542013AC986FFFE317095061F40DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", + "9302047F000001062382DD03A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B", + "047F000001062382A202A503221220D52EBB89D85B02A284948203A62FF28389C57C9F42BEEC4EC20DB76A68911C0B" + ] + + RustFailureVectors = [ + "/ip4", + "/ip4/::1", + "/ip4/fdpsofodsajfdoisa", + "/ip6", + "/udp", + "/tcp", + "/sctp", + "/udp/65536", + "/tcp/65536", + "/onion/9imaq4ygg2iegci7:80", + "/onion/aaimaq4ygg2iegci7:80", + "/onion/timaq4ygg2iegci7:0", + "/onion/timaq4ygg2iegci7:-1", + "/onion/timaq4ygg2iegci7", + "/onion/timaq4ygg2iegci@:666", + "/udp/1234/sctp", + "/udp/1234/udt/1234", + "/udp/1234/utp/1234", + "/ip4/127.0.0.1/udp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/udp", + "/ip4/127.0.0.1/tcp/jfodsajfidosajfoidsa", + "/ip4/127.0.0.1/tcp", + "/ip4/127.0.0.1/ipfs", + "/ip4/127.0.0.1/ipfs/tcp", + "/p2p-circuit/50" + ] + +suite "MultiAddress test suite": + + test "go-multiaddr success test vectors": + for item in SuccessVectors: + var a = MultiAddress.init(item) + check a.isEmpty() == false + check a.validate() == true + + test "go-multiaddr failure test vectors": + for item in FailureVectors: + var r = false + try: + var a = MultiAddress.init(item) + except: + r = true + check r == true + + test "rust-multiaddr success test vectors": + ## Rust test vectors are with changed UDP encoding and without WSS + for i in 0..