diff --git a/examples/chat.nim b/examples/chat.nim index a69abb6d1..3ce9493ae 100644 --- a/examples/chat.nim +++ b/examples/chat.nim @@ -1,5 +1,5 @@ import asyncdispatch2, nimcrypto, strutils -import ../libp2p/daemon/daemonapi +import ../libp2p/daemon/daemonapi, ../libp2p/[base58, multiaddress] const ConsoleAddress = "/tmp/console-chat.sock" @@ -39,19 +39,23 @@ proc serveThread(server: StreamServer, if line.startsWith("/connect"): var parts = line.split(" ") if len(parts) == 2: - var address = fromHex(parts[1]) - echo "= Searching for peer ", toHex(address) - var id = await udata.api.dhtFindPeer(address) - echo "===" - echo repr id - echo "===" - echo "= Connecting to peer ", toHex(address) - await udata.api.connect(id.peer, id.addresses) - echo "= Opening stream to peer chat ", toHex(address) - var stream = await udata.api.openStream(id.peer, ServerProtocols) + 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 ", toHex(address) + echo "= Connected to peer chat ", parts[1] asyncCheck remoteReader(stream.transp) + elif line.startsWith("/exit"): + quit(0) else: var msg = line & "\r\n" echo "<< ", line @@ -88,7 +92,7 @@ proc main() {.async.} = echo ">> ", line await data.api.addHandler(ServerProtocols, streamHandler) - echo "= Your PeerID is ", toHex(id.peer) + echo "= Your PeerID is ", Base58.encode(id.peer) when isMainModule: waitFor(main()) diff --git a/libp2p/base58.nim b/libp2p/base58.nim index 2e99a9bde..9a2643391 100644 --- a/libp2p/base58.nim +++ b/libp2p/base58.nim @@ -111,6 +111,8 @@ proc encode*(btype: typedesc[Base58C], if btype.encode(inbytes, result.toOpenArray(0, size - 1), size) == Base58Status.Success: result.setLen(size) + else: + result = "" proc decode*(btype: typedesc[Base58C], instr: string, outbytes: var openarray[byte], outlen: var int): Base58Status = diff --git a/libp2p/daemon/daemonapi.nim b/libp2p/daemon/daemonapi.nim index 5fa1c6b66..cdf0e62b6 100644 --- a/libp2p/daemon/daemonapi.nim +++ b/libp2p/daemon/daemonapi.nim @@ -10,7 +10,7 @@ ## This module implementes API for `go-libp2p-daemon`. import os, osproc, strutils, tables, streams import asyncdispatch2 -import ../varint, ../protobuf/minprotobuf, transpool +import ../varint, ../multiaddress, ../protobuf/minprotobuf, transpool when not defined(windows): import posix @@ -65,7 +65,7 @@ type PeerID* = seq[byte] MultiProtocol* = string - MultiAddress* = seq[byte] + # MultiAddress* = seq[byte] CID* = seq[byte] LibP2PPublicKey* = seq[byte] DHTValue* = seq[byte] @@ -120,7 +120,7 @@ proc requestConnect(peerid: PeerID, 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))) @@ -501,7 +501,8 @@ 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.} = @@ -549,12 +550,13 @@ proc openStream*(api: DaemonAPI, peer: PeerID, 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) @@ -571,12 +573,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) diff --git a/libp2p/multiaddress.nim b/libp2p/multiaddress.nim new file mode 100644 index 000000000..553b28cfc --- /dev/null +++ b/libp2p/multiaddress.nim @@ -0,0 +1,636 @@ +## 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 MultiAddress. +import tables, strutils, net +import transcoder, base58, vbuffer + +{.deadCodeElim:on.} + +const + P_IP4* = 0x0004 + P_TCP* = 0x0006 + P_UDP* = 0x0111 + P_DCCP* = 0x0021 + P_IP6* = 0x0029 + P_IP6ZONE* = 0x002A + P_DNS4* = 0x0036 + P_DNS6* = 0x0037 + P_DNSADDR* = 0x0038 + P_QUIC* = 0x01CC + P_SCTP* = 0x0084 + P_UDT* = 0x012D + P_UTP* = 0x012E + P_UNIX* = 0x0190 + P_P2P* = 0x01A5 + P_IPFS* = 0x01A5 # alias for backwards compatability + P_HTTP* = 0x01E0 + P_HTTPS* = 0x01BB + P_ONION* = 0x01BC + P_WS* = 0x01DD + LP2P_WSSTAR* = 0x01DF + LP2P_WRTCSTAR* = 0x0113 + LP2P_WRTCDIR* = 0x0114 + P_P2PCIRCUIT* = 0x0122 + +type + MAKind* = enum + None, Fixed, Length, Path, Marker + + MAProtocol* = object + name*: string + code*: int + size*: int + kind: MAKind + coder*: Transcoder + + MultiAddress* = object + data*: VBuffer + + MultiAddressError* = object of Exception + +proc ip4StB(s: string, vb: var VBuffer): bool = + ## IPv4 stringToBuffer() implementation. + try: + var a = parseIpAddress(s) + if a.family == IpAddressFamily.IPv4: + vb.writeArray(a.address_v4) + result = true + except: + discard + +proc ip4BtS(vb: var VBuffer, s: var string): bool = + ## IPv4 bufferToString() implementation. + var a = IpAddress(family: IpAddressFamily.IPv4) + if vb.readArray(a.address_v4) == 4: + s = $a + result = true + +proc ip4VB(vb: var VBuffer): bool = + ## IPv4 validateBuffer() implementation. + var a = IpAddress(family: IpAddressFamily.IPv4) + if vb.readArray(a.address_v4) == 4: + result = true + +proc ip6StB(s: string, vb: var VBuffer): bool = + ## IPv6 stringToBuffer() implementation. + try: + var a = parseIpAddress(s) + if a.family == IpAddressFamily.IPv6: + vb.writeArray(a.address_v6) + result = true + except: + discard + +proc ip6BtS(vb: var VBuffer, s: var string): bool = + ## IPv6 bufferToString() implementation. + var a = IpAddress(family: IpAddressFamily.IPv6) + if vb.readArray(a.address_v6) == 16: + s = $a + result = true + +proc ip6VB(vb: var VBuffer): bool = + ## IPv6 validateBuffer() implementation. + var a = IpAddress(family: IpAddressFamily.IPv6) + if vb.readArray(a.address_v6) == 16: + result = true + +proc ip6zoneStB(s: string, vb: var VBuffer): bool = + ## IPv6 stringToBuffer() implementation. + if len(s) > 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 < 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) + 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: + s = Base58.encode(address) + result = true + +proc p2pVB(vb: var VBuffer): bool = + ## P2P address validateBuffer() implementation. + ## TODO (multihash required) + var address = newSeq[byte]() + if vb.readSeq(address) > 0: + result = true + +proc onionStB(s: string, vb: var VBuffer): bool = + # TODO (base32, multihash required) + discard + +proc onionBtS(vb: var VBuffer, s: var string): bool = + # TODO (base32, multihash required) + discard + +proc onionVB(vb: var VBuffer): bool = + # TODO (base32, multihash required) + discard + +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( + name: "ip4", code: P_IP4, kind: Fixed, size: 4, + coder: TranscoderIP4 + ), + MAProtocol( + name: "tcp", code: P_TCP, kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + name: "udp", code: P_UDP, kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + name: "ip6", code: P_IP6, kind: Fixed, size: 16, + coder: TranscoderIP6 + ), + MAProtocol( + name: "dccp", code: P_DCCP, kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + name: "sctp", code: P_SCTP, kind: Fixed, size: 2, + coder: TranscoderPort + ), + MAProtocol( + name: "udt", code: P_UDT, kind: Marker, size: 0 + ), + MAProtocol( + name: "utp", code: P_UTP, kind: Marker, size: 0 + ), + MAProtocol( + name: "http", code: P_HTTP, kind: Marker, size: 0 + ), + MAProtocol( + name: "https", code: P_HTTPS, kind: Marker, size: 0 + ), + MAProtocol( + name: "quic", code: P_QUIC, kind: Marker, size: 0 + ), + MAProtocol( + name: "ip6zone", code: P_IP6ZONE, kind: Length, size: 0, + coder: TranscoderIP6Zone + ), + MAProtocol( + name: "onion", code: P_ONION, kind: Fixed, size: 10, + coder: TranscoderOnion + ), + MAProtocol( + name: "ws", code: P_WS, kind: Marker, size: 0 + ), + MAProtocol( + name: "ws", code: P_WS, kind: Marker, size: 0 + ), + MAProtocol( + name: "ipfs", code: P_IPFS, kind: Length, size: 0, + coder: TranscoderP2P + ), + MAProtocol( + name: "p2p", code: P_P2P, kind: Length, size: 0, + coder: TranscoderP2P + ), + MAProtocol( + name: "unix", code: P_UNIX, kind: Path, size: 0, + coder: TranscoderUnix + ), + MAProtocol( + name: "dns4", code: P_DNS4, kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + name: "dns6", code: P_DNS6, kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + name: "dnsaddr", code: P_DNS6, kind: Length, size: 0, + coder: TranscoderDNS + ), + MAProtocol( + name: "p2p-circuit", code: P_P2PCIRCUIT, kind: Marker, size: 0 + ), + MAProtocol( + name: "p2p-websocket-star", code: LP2P_WSSTAR, kind: Marker, size: 0 + ), + MAProtocol( + name: "p2p-webrtc-star", code: LP2P_WRTCSTAR, kind: Marker, size: 0 + ), + MAProtocol( + name: "p2p-webrtc-direct", code: LP2P_WRTCDIR, kind: Marker, size: 0 + ) + ] + +proc initMultiAddressNameTable(): Table[string, MAProtocol] {.compileTime.} = + result = initTable[string, MAProtocol]() + for item in ProtocolsList: + result[item.name] = item + +proc initMultiAddressCodeTable(): Table[int, MAProtocol] {.compileTime.} = + result = initTable[int, MAProtocol]() + for item in ProtocolsList: + result[item.code] = item + +const + CodeAddresses = initMultiAddressCodeTable() + NameAddresses = initMultiAddressNameTable() + +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 protoCode*(mtype: typedesc[MultiAddress], protocol: string): int = + ## Returns protocol code from protocol name ``protocol``. + let proto = NameAddresses.getOrDefault(protocol) + if proto.kind == None: + raise newException(MultiAddressError, "Protocol not found") + result = proto.code + +proc protoName*(mtype: typedesc[MultiAddress], protocol: int): string = + ## Returns protocol name from protocol code ``protocol``. + let proto = CodeAddresses.getOrDefault(protocol) + if proto.kind == None: + raise newException(MultiAddressError, "Protocol not found") + result = proto.name + +proc protoCode*(ma: MultiAddress): int = + ## Returns MultiAddress ``ma`` protocol code. + discard + +proc protoName*(ma: MultiAddress): string = + ## Returns MultiAddress ``ma`` protocol name. + discard + +proc protoValue*(ma: MultiAddress, value: var openarray[byte]): int = + ## Returns MultiAddress ``ma`` protocol address value. + discard + +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(int(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.} = + 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(int(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(int(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.name & "' transcoder") + if not proto.coder.bufferToString(vb.data, part): + raise newException(MultiAddressError, "Decoding protocol error") + parts.add(proto.name) + parts.add(part) + elif proto.kind == Marker: + parts.add(proto.name) + if len(parts) > 0: + result = "/" & parts.join("/") + +proc hex*(value: MultiAddress): string = + ## Return hexadecimal string representation of MultiAddress ``value``. + result = $(value.data) + +proc buffer*(value: MultiAddress): seq[byte] = + ## Returns shallow copy of internal buffer + shallowCopy(result, value.data.buffer) + +proc validate*(ma: MultiAddress): bool = + ## Returns ``true`` if MultiAddress ``ma`` is valid. + var header: uint64 + var vb = ma + while true: + if vb.data.isEmpty(): + break + if vb.data.readVarint(header) == -1: + return false + let proto = CodeAddresses.getOrDefault(int(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 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 = NameAddresses.getOrDefault(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.writeVarint(cast[uint](proto.code)) + if not proto.coder.stringToBuffer(parts[offset + 1], result.data): + 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.writeVarint(cast[uint](proto.code)) + if not proto.coder.stringToBuffer(path, result.data): + raise newException(MultiAddressError, + "Error encoding `$1/$2`" % [part, path]) + break + elif proto.kind == Marker: + result.data.writeVarint(cast[uint](proto.code)) + 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/transcoder.nim b/libp2p/transcoder.nim new file mode 100644 index 000000000..a9079d7a8 --- /dev/null +++ b/libp2p/transcoder.nim @@ -0,0 +1,17 @@ +## 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.} + bufferToString*: proc(vb: var VBuffer, s: var string): bool {.nimcall.} + validateBuffer*: proc(vb: var VBuffer): bool {.nimcall.} diff --git a/libp2p/vbuffer.nim b/libp2p/vbuffer.nim new file mode 100644 index 000000000..0e663d66f --- /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..