Add MultiAddress pattern matching procedures (go-multiaddr-fmt) with tests.

Add some comments.
This commit is contained in:
cheatfate 2019-03-20 11:41:37 +02:00
parent 4fa5ee3c93
commit f8dc3abe36
No known key found for this signature in database
GPG Key ID: 46ADD633A7201F95
4 changed files with 220 additions and 1 deletions

View File

@ -6,6 +6,9 @@
## at your option.
## This file may not be copied, modified, or distributed except according to
## those terms.
## This module implements cross-platform network interfaces list.
## Currently supported OSes are Windows, Linux, MacOS, BSD(not tested).
import algorithm
from strutils import toHex
import ipnet

View File

@ -7,6 +7,7 @@
## This file may not be copied, modified, or distributed except according to
## those terms.
## This module implements various IP network utility procedures.
import endians, strutils
import chronos
export chronos

View File

@ -10,7 +10,7 @@
## This module implements MultiAddress.
import tables, strutils, net
import multicodec, multihash, multibase, transcoder, base58, base32, vbuffer
import peer
from peer import PeerID
{.deadCodeElim:on.}
@ -27,6 +27,18 @@ type
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 Exception
proc ip4StB(s: string, vb: var VBuffer): bool =
@ -219,6 +231,21 @@ proc dnsVB(vb: var VBuffer): bool =
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,
@ -349,6 +376,40 @@ const
)
]
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"))
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]()
@ -527,6 +588,12 @@ proc `$`*(value: MultiAddress): string =
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)
@ -715,3 +782,54 @@ proc isWire*(ma: MultiAddress): bool =
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

View File

@ -1,6 +1,12 @@
import unittest
import ../libp2p/multiaddress
type
PatternVector = object
pattern: MaPattern
good: seq[string]
bad: seq[string]
const
SuccessVectors = [
"/ip4/1.2.3.4",
@ -203,6 +209,88 @@ const
"/ip4/127.0.0.1/udp/1234/quic"
]
PatternVectors = [
PatternVector(pattern: IP,
good: @["/ip4/0.0.0.0", "/ip6/fc00::"],
bad: @["/ip4/0.0.0.0/tcp/555", "/udp/789/ip6/fc00::"]
),
PatternVector(pattern: TCP,
good: @["/ip4/0.0.7.6/tcp/1234", "/ip6/::/tcp/0"],
bad: @["/tcp/12345", "/ip6/fc00::/udp/5523/tcp/9543"]
),
PatternVector(pattern: UDP,
good: @["/ip4/0.0.7.6/udp/1234", "/ip6/::/udp/0"],
bad: @["/udp/12345", "/ip6/fc00::/tcp/5523/udp/9543"],
),
PatternVector(pattern: UTP,
good: @["/ip4/1.2.3.4/udp/3456/utp", "/ip6/::/udp/0/utp"],
bad: @[
"/ip4/0.0.0.0/tcp/12345/utp",
"/ip6/fc00::/ip4/0.0.0.0/udp/1234/utp",
"/utp"
]
),
PatternVector(pattern: QUIC,
good: @["/ip4/1.2.3.4/udp/1234/quic", "/ip6/::/udp/1234/quic"],
bad: @[
"/ip4/0.0.0.0/tcp/12345/quic",
"/ip6/fc00::/ip4/0.0.0.0/udp/1234/quic",
"/quic"
]
),
PatternVector(pattern: IPFS,
good: @[
"/ip4/1.2.3.4/tcp/1234/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip6/::/tcp/1234/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip6/::/udp/1234/utp/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip4/0.0.0.0/udp/1234/utp/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
],
bad: @[
"/ip4/1.2.3.4/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip6/::/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/tcp/123/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip6/::/udp/1234/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip6/::/utp/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
]
),
PatternVector(pattern: DNS,
good: @["/dnsaddr/example.io", "/dns4/example.io", "/dns6/example.io"],
bad: @["/ip4/127.0.0.1"],
),
PatternVector(pattern: WebRTCDirect,
good: @[
"/ip4/1.2.3.4/tcp/3456/http/p2p-webrtc-direct",
"/ip6/::/tcp/0/http/p2p-webrtc-direct"
],
bad: @[
"/ip4/0.0.0.0", "/ip6/fc00::", "/udp/12345",
"/ip6/fc00::/tcp/5523/udp/9543"
]
),
PatternVector(pattern: HTTP,
good: @[
"/ip4/1.2.3.4/http", "/dns4/example.io/http",
"/dns6/::/tcp/7011/http", "/dnsaddr/example.io/http",
"/ip6/fc00::/http"
],
bad: @[
"/ip4/1.2.3.4/https", "/ip4/0.0.0.0/tcp/12345/quic",
"/ip6/fc00::/tcp/5523"
]
),
PatternVector(pattern: HTTPS,
good: @[
"/ip4/1.2.3.4/https", "/dns4/example.io/https",
"/dns6/::/tcp/7011/https", "/ip6/fc00::/https"
],
bad: @[
"/ip4/1.2.3.4/http", "/ip4/0.0.0.0/tcp/12345/quic",
"/ip6/fc00::/tcp/5523"
]
)
]
suite "MultiAddress test suite":
test "go-multiaddr success test vectors":
@ -264,3 +352,12 @@ suite "MultiAddress test suite":
check:
hex(a) == PathExpects[i]
$a == PathVectors[i]
test "MultiAddress pattern matching test vectors":
for item in PatternVectors:
for gitem in item.good:
var a = MultiAddress.init(gitem)
check item.pattern.match(a) == true
for bitem in item.bad:
var a = MultiAddress.init(bitem)
check item.pattern.match(a) == false