nim-libp2p/libp2p/multiaddress.nim

902 lines
28 KiB
Nim

## 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 nativesockets
import tables, strutils, net
import chronos
import multicodec, multihash, multibase, transcoder, base58, base32, vbuffer
from peer import PeerID
{.deadCodeElim:on.}
type
MAKind* = enum
None, Fixed, Length, Path, Marker
MAProtocol* = object
mcodec*: MultiCodec
size*: int
kind: MAKind
coder*: Transcoder
MultiAddress* = object
data*: VBuffer
MaPatternOp* = enum
Eq, Or, And
MaPattern* = object
operator*: MaPatternOp
args*: seq[MaPattern]
value*: MultiCodec
MaPatResult* = object
flag*: bool
rem*: seq[MultiCodec]
MultiAddressError* = object of CatchableError
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 >= 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
proc pEq(codec: string): MaPattern =
## ``Equal`` operator for pattern
result.operator = Eq
result.value = multiCodec(codec)
proc pOr(args: varargs[MaPattern]): MaPattern =
## ``Or`` operator for pattern
result.operator = Or
result.args = @args
proc pAnd(args: varargs[MaPattern]): MaPattern =
## ``And`` operator for pattern
result.operator = And
result.args = @args
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
)
]
DNS4* = pEq("dns4")
DNS6* = pEq("dns6")
IP4* = pEq("ip4")
IP6* = pEq("ip6")
DNS* = pOr(pEq("dnsaddr"), DNS4, DNS6)
IP* = pOr(IP4, IP6)
TCP* = pOr(pAnd(DNS, pEq("tcp")), pAnd(IP, pEq("tcp")))
UDP* = pOr(pAnd(DNS, pEq("udp")), pAnd(IP, pEq("udp")))
UTP* = pAnd(UDP, pEq("utp"))
QUIC* = pAnd(UDP, pEq("quic"))
UNIX* = pEq("unix")
Unreliable* = pOr(UDP)
Reliable* = pOr(TCP, UTP, QUIC)
IPFS* = pAnd(Reliable, pEq("p2p"))
HTTP* = pOr(
pAnd(TCP, pEq("http")),
pAnd(IP, pEq("http")),
pAnd(DNS, pEq("http"))
)
HTTPS* = pOr(
pAnd(TCP, pEq("https")),
pAnd(IP, pEq("https")),
pAnd(DNS, pEq("https"))
)
WebRTCDirect* = pOr(
pAnd(HTTP, pEq("p2p-webrtc-direct")),
pAnd(HTTPS, pEq("p2p-webrtc-direct"))
)
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(buffer)
if len(value) >= result:
copyMem(addr value[0], addr 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))
if proto.kind == Path and part[0] == '/':
parts.add(part[1..^1])
else:
parts.add(part)
elif proto.kind == Marker:
parts.add($(proto.mcodec))
if len(parts) > 0:
result = "/" & parts.join("/")
proc protocols*(value: MultiAddress): seq[MultiCodec] =
## Returns list of protocol codecs inside of MultiAddress ``value``.
result = newSeq[MultiCodec]()
for item in value.items():
result.add(item.protoCode())
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], address: TransportAddress): MultiAddress =
## Returns a multiaddr of ``address``.
case address.family
of AddressFamily.IPv4:
var a = IpAddress(
family: IpAddressFamily.IPv4,
address_v4: address.address_v4
)
result = MultiAddress.init(a, Protocol.IPPROTO_TCP, address.port)
of AddressFamily.IPv6:
var a = IpAddress(family: IpAddressFamily.IPv6,
address_v6: address.address_v6)
result = MultiAddress.init(a, Protocol.IPPROTO_TCP, address.port)
else:
raise newException(TransportAddressError,
"Invalid address for transport!")
proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec,
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.mcodec))
if proto.kind in {Fixed, Length, Path}:
if len(value) == 0:
raise newException(MultiAddressError, "Value must not be empty array")
if proto.kind == Fixed:
result.data.writeArray(value)
else:
var data = newSeq[byte](len(value))
copyMem(addr data[0], unsafeAddr value[0], len(value))
result.data.writeSeq(data)
result.data.finish()
proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec,
value: PeerID): MultiAddress {.inline.} =
## Initialize MultiAddress object from protocol id ``protocol`` and peer id
## ``value``.
init(mtype, protocol, cast[seq[byte]](value))
proc init*(mtype: typedesc[MultiAddress], protocol: MultiCodec): MultiAddress =
## Initialize MultiAddress object from protocol id ``protocol``.
let proto = CodeAddresses.getOrDefault(protocol)
if proto.kind == None:
raise newException(MultiAddressError, "Protocol not found")
result.data = initVBuffer()
if proto.kind != Marker:
raise newException(MultiAddressError, "Protocol missing value")
result.data.writeVarint(cast[uint64](proto.mcodec))
result.data.finish()
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 init*(mtype: typedesc[MultiAddress],
address: IpAddress, protocol: Protocol, port: Port): MultiAddress =
## Initialize MultiAddress using stdlib's net.IpAddress (IPv4/IPv6) and
## net.Protocol (UDP/TCP) information.
result.data = initVBuffer()
let familyProto = case address.family
of IpAddressFamily.IPv4: getProtocol("ip4")
of IpAddressFamily.IPv6: getProtocol("ip6")
let protoProto = case protocol
of IPPROTO_TCP: getProtocol("tcp")
of IPPROTO_UDP: getProtocol("udp")
else: raise newException(AssertionError,
"protocol should be either TCP or UDP")
result.data.write(familyProto.mcodec)
if not familyProto.coder.stringToBuffer($address, result.data):
raise newException(MultiAddressError, "Error encoding IPv4/IPv6 address")
result.data.write(protoProto.mcodec)
if not protoProto.coder.stringToBuffer($port, result.data):
raise newException(MultiAddressError, "Error encoding port number")
result.data.finish()
proc init*(mtype: typedesc[MultiAddress], address: TransportAddress,
protocol: Protocol = IPPROTO_TCP): MultiAddress =
## Initialize MultiAddress using chronos.TransportAddress (IPv4/IPv6/Unix)
## and protocol information (UDP/TCP).
result.data = initVBuffer()
let protoProto = case protocol
of IPPROTO_TCP: getProtocol("tcp")
of IPPROTO_UDP: getProtocol("udp")
else: raise newException(AssertionError,
"protocol should be either TCP or UDP")
if address.family == AddressFamily.IPv4:
result.data.write(getProtocol("ip4").mcodec)
result.data.writeArray(address.address_v4)
result.data.write(protoProto.mcodec)
discard protoProto.coder.stringToBuffer($address.port, result.data)
elif address.family == AddressFamily.IPv6:
result.data.write(getProtocol("ip6").mcodec)
result.data.writeArray(address.address_v6)
result.data.write(protoProto.mcodec)
discard protoProto.coder.stringToBuffer($address.port, result.data)
elif address.family == AddressFamily.Unix:
result.data.write(getProtocol("unix").mcodec)
result.data.writeSeq(address.address_un)
result.data.finish()
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!")
proc isWire*(ma: MultiAddress): bool =
## Returns ``true`` if MultiAddress ``ma`` is one of:
## - {IP4}/{TCP, UDP}
## - {IP6}/{TCP, UDP}
## - {UNIX}/{PATH}
var state = 0
try:
for part in ma.items():
if state == 0:
let code = part.protoCode()
if code == multiCodec("ip4") or code == multiCodec("ip6"):
inc(state)
continue
elif code == multiCodec("unix"):
result = true
break
else:
result = false
break
elif state == 1:
if part.protoCode == multiCodec("tcp") or
part.protoCode == multiCodec("udp"):
inc(state)
result = true
else:
result = false
break
else:
result = false
break
except:
result = false
proc matchPart(pat: MaPattern, protos: seq[MultiCodec]): MaPatResult =
var empty: seq[MultiCodec]
var pcs = protos
if pat.operator == Or:
for a in pat.args:
let res = a.matchPart(pcs)
if res.flag:
return MaPatResult(flag: true, rem: res.rem)
result = MaPatResult(flag: false, rem: empty)
elif pat.operator == And:
if len(pcs) < len(pat.args):
return MaPatResult(flag: false, rem: empty)
for i in 0..<len(pat.args):
let res = pat.args[i].matchPart(pcs)
if not res.flag:
return MaPatResult(flag: false, rem: res.rem)
pcs = res.rem
result = MaPatResult(flag: true, rem: pcs)
elif pat.operator == Eq:
if len(pcs) == 0:
return MaPatResult(flag: false, rem: empty)
if pcs[0] == pat.value:
return MaPatResult(flag: true, rem: pcs[1..^1])
result = MaPatResult(flag: false, rem: empty)
proc match*(pat: MaPattern, address: MultiAddress): bool =
## Match full ``address`` using pattern ``pat`` and return ``true`` if
## ``address`` satisfies pattern.
var protos = address.protocols()
let res = matchPart(pat, protos)
result = res.flag and (len(res.rem) == 0)
proc matchPartial*(pat: MaPattern, address: MultiAddress): bool =
## Match prefix part of ``address`` using pattern ``pat`` and return
## ``true`` if ``address`` starts with pattern.
var protos = address.protocols()
let res = matchPart(pat, protos)
result = res.flag
proc `$`*(pat: MaPattern): string =
## Return pattern ``pat`` as string.
var sub = newSeq[string]()
for a in pat.args:
sub.add($a)
if pat.operator == And:
result = sub.join("/")
elif pat.operator == Or:
result = "(" & sub.join("|") & ")"
elif pat.operator == Eq:
result = $pat.value