Merge pull request #1 from status-im/chat

[WIP] Do not merge! Chat and further improvements.
This commit is contained in:
Eugene Kabanov 2018-12-16 15:56:04 +02:00 committed by GitHub
commit f9a807af53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 4899 additions and 145 deletions

129
examples/chat.nim Normal file
View File

@ -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()

View File

@ -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/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"

290
libp2p/base32.nim Normal file
View File

@ -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..<len(s):
result.encode[i] = cast[uint8](s[i])
for i in 0..<len(result.decode):
result.decode[i] = -1
for i in 0..<len(result.encode):
result.decode[int(result.encode[i])] = int8(i)
const
RFCUpperCaseAlphabet* = newAlphabet32("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567")
RFCLowerCaseAlphabet* = newAlphabet32("abcdefghijklmnopqrstuvwxyz234567")
HEXUpperCaseAlphabet* = newAlphabet32("0123456789ABCDEFGHIJKLMNOPQRSTUV")
HEXLowerCaseAlphabet* = newAlphabet32("0123456789abcdefghijklmnopqrstuv")
proc encodedLength*(btype: typedesc[Base32Types], length: int): int =
## Return estimated length of BASE32 encoded value for plain length
## ``length``.
let reminder = length mod 5
when btype is Base32NoPadTypes:
result = ((length div 5) * 8) + (((reminder * 8) + 4) div 5)
else:
result = ((length div 5) * 8)
if reminder != 0:
result += 8
proc decodedLength*(btype: typedesc[Base32Types], length: int): int =
## Return estimated length of decoded value of BASE32 encoded value of length
## ``length``.
let reminder = length mod 8
result = (length div 8) * 5 + ((reminder * 5) div 8)
proc convert5to8(inbytes: openarray[byte], outbytes: var openarray[char],
length: int): int {.inline.} =
if length >= 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..<reminder:
if (cast[byte](instr[i + j]) and 0x80'u8) != 0:
outlen = 0
zeroMem(addr outbytes[0], i + 8)
result = Base32Status.Incorrect
return
let ch = alphabet.decode[int8(instr[i + j])]
if ch == -1:
outlen = 0
zeroMem(addr outbytes[0], i + 8)
result = Base32Status.Incorrect
return
buffer[j] = cast[byte](ch)
left = convert8to5(buffer.toOpenArray(0, reminder - 1),
outbytes.toOpenArray(k, length - 1), reminder)
outlen = k + left
result = Base32Status.Success
proc decode*[T: byte|char](btype: typedesc[Base32Types],
instr: openarray[T]): seq[byte] =
## Decode BASE32 string ``instr`` and return sequence of bytes as result.
if len(instr) == 0:
result = newSeq[byte]()
else:
var length = 0
result = newSeq[byte](btype.decodedLength(len(instr)))
if btype.decode(instr, result, length) == Base32Status.Success:
result.setLen(length)
else:
raise newException(Base32Error, "Incorrect base58 string")

239
libp2p/base58.nim Normal file
View File

@ -0,0 +1,239 @@
## 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 BASE58 encoding and decoding procedures.
## This module supports two variants of BASE58 encoding (Bitcoin and Flickr).
type
Base58Status* {.pure.} = enum
Error,
Success,
Incorrect,
Overrun
Base58Alphabet* = object
decode*: array[128, int8]
encode*: array[58, uint8]
BTCBase58* = object
## Type to use Bitcoin alphabet
FLCBase58* = object
## Type to use Flickr alphabet
Base58* = BtcBase58
## By default we are using Bitcoin alphabet
Base58C* = BTCBase58 | FLCBase58
## Supported types
Base58Error* = object of Exception
## Base58 specific exception type
proc newAlphabet58*(s: string): Base58Alphabet =
doAssert(len(s) == 58)
for i in 0..<len(s):
result.encode[i] = cast[uint8](s[i])
for i in 0..<len(result.decode):
result.decode[i] = -1
for i in 0..<len(result.encode):
result.decode[int(result.encode[i])] = int8(i)
const
BTCAlphabet* = newAlphabet58("123456789ABCDEFGHJKLMNPQRSTUV" &
"WXYZabcdefghijkmnopqrstuvwxyz")
FlickrAlphabet* = newAlphabet58("123456789abcdefghijkmnopqrstu" &
"vwxyzABCDEFGHJKLMNPQRSTUVWXYZ")
proc encodedLength*(btype: typedesc[Base58C], length: int): int =
## Return estimated length of BASE58 encoded value for plain length
## ``length``.
result = (length * 138) div 100 + 1
proc decodedLength*(btype: typedesc[Base58C], length: int): int =
## Return estimated length of decoded value of BASE58 encoded value of length
## ``length``.
result = length + 4
proc encode*(btype: typedesc[Base58C], inbytes: openarray[byte],
outstr: var openarray[char], outlen: var int): Base58Status =
## Encode array of bytes ``inbytes`` using BASE58 encoding and store
## result to ``outstr``. On success ``Base58Status.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
## ``Base58Status.Overrun`` will be returned and ``outlen`` will be set to
## number of characters required.
when btype is BTCBase58:
const alphabet = BTCAlphabet
elif btype is FLCBase58:
const alphabet = FlickrAlphabet
let binsz = len(inbytes)
var zcount = 0
while zcount < binsz and inbytes[zcount] == 0x00'u8:
inc(zcount)
let size = ((binsz - zcount) * 138) div 100 + 1
var buffer = newSeq[uint8](size)
var hi = size - 1
var i = zcount
var j = size - 1
while i < binsz:
var carry = uint32(inbytes[i])
j = size - 1
while (j > 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..<zcount:
outstr[k] = cast[char](alphabet.encode[0])
i = zcount
while j < size:
outstr[i] = cast[char](alphabet.encode[buffer[j]])
inc(j)
inc(i)
result = Base58Status.Success
proc encode*(btype: typedesc[Base58C],
inbytes: openarray[byte]): string {.inline.} =
## Encode array of bytes ``inbytes`` using BASE58 encoding and return
## encoded string.
var size = (len(inbytes) * 138) div 100 + 1
result = newString(size)
if btype.encode(inbytes, result.toOpenArray(0, size - 1),
size) == Base58Status.Success:
result.setLen(size)
else:
result = ""
proc decode*[T: byte|char](btype: typedesc[Base58C], instr: openarray[T],
outbytes: var openarray[byte], outlen: var int): Base58Status =
## Decode BASE58 string and store array of bytes to ``outbytes``. On success
## ``Base58Status.Success`` will be returned and ``outlen`` will be set
## to number of bytes stored.
##
## Length of ``outbytes`` must be equal or more then ``len(instr) + 4``.
##
## If ``instr`` has characters which are not part of BASE58 alphabet, then
## ``Base58Status.Incorrect`` will be returned and ``outlen`` will be set to
## ``0``.
##
## If length of ``outbytes`` is not enough to store decoded bytes, then
## ``Base58Status.Overrun`` will be returned and ``outlen`` will be set to
## number of bytes required.
when btype is BTCBase58:
const alphabet = BTCAlphabet
elif btype is FLCBase58:
const alphabet = FlickrAlphabet
if len(instr) == 0:
outlen = 0
return Base58Status.Success
let binsz = len(instr) + 4
if len(outbytes) < binsz:
outlen = binsz
return Base58Status.Overrun
var bytesleft = binsz mod 4
var zeromask: uint32
if bytesleft != 0:
zeromask = cast[uint32](0xFFFF_FFFF'u32 shl (bytesleft * 8))
let size = (binsz + 3) div 4
var buffer = newSeq[uint32](size)
var zcount = 0
while zcount < len(instr) and instr[zcount] == cast[char](alphabet.encode[0]):
inc(zcount)
for i in zcount..<len(instr):
if (cast[byte](instr[i]) and 0x80'u8) != 0:
outlen = 0
result = Base58Status.Incorrect
return
let ch = alphabet.decode[int8(instr[i])]
if ch == -1:
outlen = 0
result = Base58Status.Incorrect
return
var c = cast[uint32](ch)
for j in countdown(size - 1, 0):
let t = cast[uint64](buffer[j]) * 58 + c
c = cast[uint32]((t and 0x3F_0000_0000'u64) shr 32)
buffer[j] = cast[uint32](t and 0xFFFF_FFFF'u32)
if c != 0:
outlen = 0
result = Base58Status.Incorrect
return
if (buffer[0] and zeromask) != 0:
outlen = 0
result = Base58Status.Incorrect
return
var boffset = 0
var joffset = 0
if bytesleft == 3:
outbytes[boffset] = cast[uint8]((buffer[0] and 0xFF_0000'u32) shr 16)
inc(boffset)
bytesleft = 2
if bytesleft == 2:
outbytes[boffset] = cast[uint8]((buffer[0] and 0xFF00'u32) shr 8)
inc(boffset)
bytesleft = 1
if bytesleft == 1:
outbytes[boffset] = cast[uint8]((buffer[0] and 0xFF'u32))
inc(boffset)
joffset = 1
while joffset < size:
outbytes[boffset + 0] = cast[byte]((buffer[joffset] shr 0x18) and 0xFF)
outbytes[boffset + 1] = cast[byte]((buffer[joffset] shr 0x10) and 0xFF)
outbytes[boffset + 2] = cast[byte]((buffer[joffset] shr 0x8) and 0xFF)
outbytes[boffset + 3] = cast[byte](buffer[joffset] and 0xFF)
boffset += 4
inc(joffset)
outlen = binsz
var m = 0
while m < binsz:
if outbytes[m] != 0x00:
if zcount > 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")

262
libp2p/cid.nim Normal file
View File

@ -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)

View File

@ -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,57 +466,140 @@ proc recvMessage(conn: StreamTransport): Future[seq[byte]] {.async.} =
length: int
res: VarintStatus
var buffer = newSeq[byte](10)
try:
for i in 0..<len(buffer):
await conn.readExactly(addr buffer[i], 1)
res = PB.getUVarint(buffer.toOpenArray(0, i), length, size)
if res == VarintStatus.Success:
break
if res != VarintStatus.Success or size > MaxMessageSize:
raise newException(ValueError, "Invalid message size")
buffer.setLen(0)
buffer.setLen(size)
await conn.readExactly(addr buffer[0], int(size))
except TransportIncompleteError:
buffer.setLen(0)
result = buffer
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] = @[],
id: string = "",
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)
if api.sockname != DefaultSocketPath:
args.add("-sock=" & api.sockname)
# We are trying to get absolute daemon path.
let cmd = findExe(daemon)
if len(cmd) == 0:
@ -424,23 +608,36 @@ proc newDaemonApi*(flags: set[P2PDaemonFlags] = {},
# 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)
if socketExists(api.sockname):
if not tryRemoveFile(api.sockname):
if api.sockname != sockpath:
raise newException(DaemonLocalError, "Socket is already bound!")
# Starting daemon process
result.process = startProcess(cmd, "", args, options = {poStdErrToStdOut})
api.process = startProcess(cmd, "", args, env, {poStdErrToStdOut})
# Waiting until daemon will not be bound to control socket.
while true:
if not result.process.running():
echo result.process.errorStream.readAll()
if not api.process.running():
echo api.process.errorStream.readAll()
raise newException(DaemonLocalError,
"Daemon executable could not be started!")
if socketExists(sockpath):
if socketExists(api.sockname):
break
await sleepAsync(100)
result.sockname = sockpath
result.pool = await newPool(initTAddress(sockpath), poolsize = poolSize)
# api.pool = await newPool(api.address, poolsize = poolSize)
if Logging in api.flags:
api.loggerFut = loggingHandler(api)
proc close*(api: DaemonAPI, stream: P2PStream) {.async.} =
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,
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..<length:
result.add("'")
result.add($pinfo.addresses[i])
result.add("'")
if i < length - 1:
result.add(", ")
result.add("]}")
if len(pinfo.addresses) > 0:
result = result

View File

@ -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..<poolsize:
conns[i] = connect(address, bufferSize)
# Waiting for all connections to be established.
@ -68,10 +68,11 @@ proc newPool*(address: TransportAddress, poolsize: int = DefaultPoolSize,
else:
let transp = conns[i].read()
let item = PoolItem(transp: transp)
result.transports[i] = item
pool.transports[i] = item
# Setup available connections event
result.event = newAsyncEvent()
result.state = Connected
pool.event = newAsyncEvent()
pool.state = Connected
result = pool
proc acquire*(pool: TransportPool): Future[StreamTransport] {.async.} =
## Acquire non-busy connection from pool ``pool``.

674
libp2p/multiaddress.nim Normal file
View File

@ -0,0 +1,674 @@
## 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 multicodec, multihash, multibase, transcoder, base58, base32, vbuffer
{.deadCodeElim:on.}
type
MAKind* = enum
None, Fixed, Length, Path, Marker
MAProtocol* = object
mcodec*: MultiCodec
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 >= 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!")

429
libp2p/multibase.nim Normal file
View File

@ -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)

316
libp2p/multicodec.nim Normal file
View File

@ -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))

550
libp2p/multihash.nim Normal file
View File

@ -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)

View File

@ -167,6 +167,7 @@ proc write*(pb: var ProtoBuffer, field: ProtoField) =
assert(res == VarintStatus.Success)
pb.offset += length
assert(pb.isEnough(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)

19
libp2p/transcoder.nim Normal file
View File

@ -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.}

View File

@ -16,7 +16,7 @@
import bitops
type
VarintStatus* = enum
VarintStatus* {.pure.} = enum
Error,
Success,
Overflow,

180
libp2p/vbuffer.nim Normal file
View File

@ -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..<len(vb.buffer):
result.add(toHex(vb.buffer[i]))

407
tests/testbase32.nim Normal file
View File

@ -0,0 +1,407 @@
import unittest
import ../libp2p/base32
const TVBaseUpperPadding = [
["f", "MY======"],
["fo", "MZXQ===="],
["foo", "MZXW6==="],
["foob", "MZXW6YQ="],
["fooba", "MZXW6YTB"],
["foobar", "MZXW6YTBOI======"]
]
const TVBaseUpperNoPadding = [
["f", "MY"],
["fo", "MZXQ"],
["foo", "MZXW6"],
["foob", "MZXW6YQ"],
["fooba", "MZXW6YTB"],
["foobar", "MZXW6YTBOI"]
]
const TVBaseLowerPadding = [
["f", "my======"],
["fo", "mzxq===="],
["foo", "mzxw6==="],
["foob", "mzxw6yq="],
["fooba", "mzxw6ytb"],
["foobar", "mzxw6ytboi======"]
]
const TVBaseLowerNoPadding = [
["f", "my"],
["fo", "mzxq"],
["foo", "mzxw6"],
["foob", "mzxw6yq"],
["fooba", "mzxw6ytb"],
["foobar", "mzxw6ytboi"]
]
const TVHexUpperPadding = [
["f", "CO======"],
["fo", "CPNG===="],
["foo", "CPNMU==="],
["foob", "CPNMUOG="],
["fooba", "CPNMUOJ1"],
["foobar", "CPNMUOJ1E8======"]
]
const TVHexUpperNoPadding = [
["f", "CO"],
["fo", "CPNG"],
["foo", "CPNMU"],
["foob", "CPNMUOG"],
["fooba", "CPNMUOJ1"],
["foobar", "CPNMUOJ1E8"]
]
const TVHexLowerPadding = [
["f", "co======"],
["fo", "cpng===="],
["foo", "cpnmu==="],
["foob", "cpnmuog="],
["fooba", "cpnmuoj1"],
["foobar", "cpnmuoj1e8======"]
]
const TVHexLowerNoPadding = [
["f", "co"],
["fo", "cpng"],
["foo", "cpnmu"],
["foob", "cpnmuog"],
["fooba", "cpnmuoj1"],
["foobar", "cpnmuoj1e8"]
]
suite "BASE32 encoding test suite":
test "Empty seq/string test":
var empty1 = newSeq[byte]()
var empty2 = ""
var encoded = newString(16)
var decoded = newSeq[byte](16)
var o1, o2, o3, o4, o5, o6, o7, o8: int
var e1 = Base32Upper.encode(empty1)
var e2 = Base32Lower.encode(empty1)
var e3 = Base32UpperPad.encode(empty1)
var e4 = Base32LowerPad.encode(empty1)
var e5 = HexBase32Upper.encode(empty1)
var e6 = HexBase32Lower.encode(empty1)
var e7 = HexBase32UpperPad.encode(empty1)
var e8 = HexBase32LowerPad.encode(empty1)
check:
Base32Upper.encode(empty1, encoded, o1) == Base32Status.Success
Base32Lower.encode(empty1, encoded, o2) == Base32Status.Success
Base32UpperPad.encode(empty1, encoded, o3) == Base32Status.Success
Base32LowerPad.encode(empty1, encoded, o4) == Base32Status.Success
HexBase32Upper.encode(empty1, encoded, o5) == Base32Status.Success
HexBase32Lower.encode(empty1, encoded, o6) == Base32Status.Success
HexBase32UpperPad.encode(empty1, encoded, o7) == Base32Status.Success
HexBase32LowerPad.encode(empty1, encoded, o8) == Base32Status.Success
len(e1) == 0
len(e2) == 0
len(e3) == 0
len(e4) == 0
len(e5) == 0
len(e6) == 0
len(e7) == 0
len(e8) == 0
o1 == 0
o2 == 0
o3 == 0
o4 == 0
o5 == 0
o6 == 0
o7 == 0
o8 == 0
var d1 = Base32Upper.decode("")
var d2 = Base32Lower.decode("")
var d3 = Base32UpperPad.decode("")
var d4 = Base32LowerPad.decode("")
var d5 = HexBase32Upper.decode("")
var d6 = HexBase32Lower.decode("")
var d7 = HexBase32UpperPad.decode("")
var d8 = HexBase32LowerPad.decode("")
check:
Base32Upper.decode(empty2, decoded, o1) == Base32Status.Success
Base32Lower.decode(empty2, decoded, o2) == Base32Status.Success
Base32UpperPad.decode(empty2, decoded, o3) == Base32Status.Success
Base32LowerPad.decode(empty2, decoded, o4) == Base32Status.Success
HexBase32Upper.decode(empty2, decoded, o5) == Base32Status.Success
HexBase32Lower.decode(empty2, decoded, o6) == Base32Status.Success
HexBase32UpperPad.decode(empty2, decoded, o7) == Base32Status.Success
HexBase32LowerPad.decode(empty2, decoded, o8) == Base32Status.Success
len(d1) == 0
len(d2) == 0
len(d3) == 0
len(d4) == 0
len(d5) == 0
len(d6) == 0
len(d7) == 0
len(d8) == 0
o1 == 0
o2 == 0
o3 == 0
o4 == 0
o5 == 0
o6 == 0
o7 == 0
o8 == 0
test "Zero test":
var s = newString(256)
for i in 0..255:
s[i] = 'A'
var buffer: array[256, byte]
for i in 0..255:
var a = Base32.encode(buffer.toOpenArray(0, i))
var b = Base32.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 = Base32.encode(buffer)
var b = Base32.decode(a)
check:
equalMem(addr buffer[0], addr b[0], 256) == true
test "BASE32 uppercase padding test vectors":
for item in TVBaseUpperPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = Base32UpperPad.encode(plain)
var e2 = newString(Base32UpperPad.encodedLength(len(plain)))
check:
Base32UpperPad.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = Base32UpperPad.decode(expect)
var d2 = newSeq[byte](Base32UpperPad.decodedLength(len(expect)))
check:
Base32UpperPad.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "BASE32 lowercase padding test vectors":
for item in TVBaseLowerPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = Base32LowerPad.encode(plain)
var e2 = newString(Base32LowerPad.encodedLength(len(plain)))
check:
Base32LowerPad.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = Base32LowerPad.decode(expect)
var d2 = newSeq[byte](Base32LowerPad.decodedLength(len(expect)))
check:
Base32LowerPad.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "BASE32 uppercase no-padding test vectors":
for item in TVBaseUpperNoPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = Base32Upper.encode(plain)
var e2 = newString(Base32Upper.encodedLength(len(plain)))
check:
Base32Upper.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = Base32Upper.decode(expect)
var d2 = newSeq[byte](Base32Upper.decodedLength(len(expect)))
check:
Base32Upper.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "BASE32 lowercase no-padding test vectors":
for item in TVBaseLowerNoPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = Base32Lower.encode(plain)
var e2 = newString(Base32Lower.encodedLength(len(plain)))
check:
Base32Lower.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = Base32Lower.decode(expect)
var d2 = newSeq[byte](Base32Lower.decodedLength(len(expect)))
check:
Base32Lower.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "HEX-BASE32 uppercase padding test vectors":
for item in TVHexUpperPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = HexBase32UpperPad.encode(plain)
var e2 = newString(HexBase32UpperPad.encodedLength(len(plain)))
check:
HexBase32UpperPad.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = HexBase32UpperPad.decode(expect)
var d2 = newSeq[byte](HexBase32UpperPad.decodedLength(len(expect)))
check:
HexBase32UpperPad.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "HEX-BASE32 lowercase padding test vectors":
for item in TVHexLowerPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = HexBase32LowerPad.encode(plain)
var e2 = newString(HexBase32LowerPad.encodedLength(len(plain)))
check:
HexBase32LowerPad.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = HexBase32LowerPad.decode(expect)
var d2 = newSeq[byte](HexBase32LowerPad.decodedLength(len(expect)))
check:
HexBase32LowerPad.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "HEX-BASE32 uppercase no-padding test vectors":
for item in TVHexUpperNoPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = HexBase32Upper.encode(plain)
var e2 = newString(HexBase32Upper.encodedLength(len(plain)))
check:
HexBase32Upper.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = HexBase32Upper.decode(expect)
var d2 = newSeq[byte](HexBase32Upper.decodedLength(len(expect)))
check:
HexBase32Upper.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "HEX-BASE32 lowercase no-padding test vectors":
for item in TVHexLowerNoPadding:
let plain = cast[seq[byte]](item[0])
let expect = item[1]
var elen = 0
var dlen = 0
var e1 = HexBase32Lower.encode(plain)
var e2 = newString(HexBase32Lower.encodedLength(len(plain)))
check:
HexBase32Lower.encode(plain, e2, elen) == Base32Status.Success
e2.setLen(elen)
check:
e1 == expect
e2 == expect
var d1 = HexBase32Lower.decode(expect)
var d2 = newSeq[byte](HexBase32Lower.decodedLength(len(expect)))
check:
HexBase32Lower.decode(expect, d2, dlen) == Base32Status.Success
d2.setLen(dlen)
check:
d1 == plain
d2 == plain
test "Buffer Overrun test":
var encres = ""
var encsize = 0
var decres: seq[byte] = @[]
var decsize = 0
check:
Base32.encode([0'u8], encres, encsize) == Base32Status.Overrun
encsize == Base32.encodedLength(1)
Base32.decode("AA", decres, decsize) == Base32Status.Overrun
decsize == Base32.decodedLength(2)
test "Incorrect test":
var decres = newSeq[byte](10)
var decsize = 0
check:
Base32.decode("A", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32.decode("AAA", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32.decode("AAAAAA", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32Upper.decode("aa", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32Upper.decode("11", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32Lower.decode("AA", decres, decsize) == Base32Status.Incorrect
decsize == 0
Base32Lower.decode("11", decres, decsize) == Base32Status.Incorrect
decsize == 0
HexBase32Upper.decode("aa", decres, decsize) == Base32Status.Incorrect
decsize == 0
HexBase32Upper.decode("WW", decres, decsize) == Base32Status.Incorrect
decsize == 0
HexBase32Lower.decode("AA", decres, decsize) == Base32Status.Incorrect
decsize == 0
HexBase32Lower.decode("ww", decres, decsize) == Base32Status.Incorrect
decsize == 0

130
tests/testbase58.nim Normal file
View File

@ -0,0 +1,130 @@
import unittest
import ../libp2p/base58
proc hexToBytes*(a: string, result: var openarray[byte]) =
doAssert(len(a) == 2 * len(result))
var i = 0
var k = 0
var r = 0
if len(a) > 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

58
tests/testcid.nim Normal file
View File

@ -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

View File

@ -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

218
tests/testmultiaddress.nim Normal file
View File

@ -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..<len(RustSuccessVectors):
var a = MultiAddress.init(RustSuccessVectors[i])
check:
hex(a) == RustSuccessExpects[i]
test "rust-multiaddr failure test vectors":
for item in RustFailureVectors:
var r = false
try:
var a = MultiAddress.init(item)
except:
r = true
check r == true
test "Concatenation test":
var ma1 = MultiAddress.init()
var ma2 = MultiAddress.init()
var ma3 = MultiAddress.init("/ip4/127.0.0.1")
var ma4 = MultiAddress.init("/udp/30000")
var ma5 = MultiAddress.init("/p2p-circuit")
var cma = ma1 & ma3 & ma4 & ma5
ma2 &= ma3
ma2 &= ma4
ma2 &= ma5
check:
$cma == "/ip4/127.0.0.1/udp/30000/p2p-circuit"
$ma2 == "/ip4/127.0.0.1/udp/30000/p2p-circuit"

306
tests/testmultibase.nim Normal file
View File

@ -0,0 +1,306 @@
import unittest
import ../libp2p/multibase
const GoTestVectors = [
[
"identity",
"\x00Decentralize everything!!!",
"Decentralize everything!!!"
],
# [
# "base16",
# "f446563656e7472616c697a652065766572797468696e67212121",
# "Decentralize everything!!!"
# ],
# [
# "base16upper",
# "F446563656E7472616C697A652065766572797468696E67212121",
# "Decentralize everything!!!"
# ],
[
"base32",
"birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee",
"Decentralize everything!!!"
],
[
"base32upper",
"BIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE",
"Decentralize everything!!!"
],
[
"base32pad",
"cirswgzloorzgc3djpjssazlwmvzhs5dinfxgoijbee======",
"Decentralize everything!!!"
],
[
"base32padupper",
"CIRSWGZLOORZGC3DJPJSSAZLWMVZHS5DINFXGOIJBEE======",
"Decentralize everything!!!"
],
[
"base32hex",
"v8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144",
"Decentralize everything!!!"
],
[
"base32hexupper",
"V8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144",
"Decentralize everything!!!"
],
[
"base32hexpad",
"t8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144======",
"Decentralize everything!!!"
],
[
"base32hexpadupper",
"T8HIM6PBEEHP62R39F9II0PBMCLP7IT38D5N6E89144======",
"Decentralize everything!!!"
],
[
"base58btc",
"z36UQrhJq9fNDS7DiAHM9YXqDHMPfr4EMArvt",
"Decentralize everything!!!"
],
# [
# "base64",
# "mRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
# "Decentralize everything!!!"
# ],
# [
# "base64url",
# "uRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE",
# "Decentralize everything!!!"
# ],
# [
# "base64pad",
# "MRGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
# "Decentralize everything!!!"
# ],
# [
# "base64urlpad",
# "URGVjZW50cmFsaXplIGV2ZXJ5dGhpbmchISE=",
# "Decentralize everything!!!"
# ],
]
suite "MultiBase test suite":
test "Zero-length data encoding/decoding test":
var enc = newString(1)
var dec = newSeq[byte]()
var plain = newSeq[byte]()
var olens: array[21, int]
check:
MultiBase.encodedLength("identity", 0) == 1
MultiBase.decodedLength('\x00', 0) == -1
MultiBase.decodedLength('\x00', 1) == 0
check:
MultiBase.encode("identity", plain) == "\x00"
# MultiBase.encode("base1", plain) == "1"
# MultiBase.encode("base2", plain) == "0"
# MultiBase.encode("base8", plain) == "7"
# MultiBase.encode("base10", plain) == "9"
# MultiBase.encode("base16", plain) == "f"
# MultiBase.encode("base16upper", plain) == "F"
MultiBase.encode("base32hex", plain) == "v"
MultiBase.encode("base32hexupper", plain) == "V"
MultiBase.encode("base32hexpad", plain) == "t"
MultiBase.encode("base32hexpadupper", plain) == "T"
MultiBase.encode("base32", plain) == "b"
MultiBase.encode("base32upper", plain) == "B"
MultiBase.encode("base32pad", plain) == "c"
MultiBase.encode("base32padupper", plain) == "C"
MultiBase.encode("base58btc", plain) == "z"
MultiBase.encode("base58flickr", plain) == "Z"
# MultiBase.encode("base64", plain) == "m"
# MultiBase.encode("base64pad", plain) == "M"
# MultiBase.encode("base64url", plain) == "u"
# MultiBase.encode("base64urlpad", plain) == "U"
check:
len(MultiBase.decode("\x00")) == 0
# len(MultiBase.decode("1")) == 0
# len(MultiBase.decode("0")) == 0
# len(MultiBase.decode("7")) == 0
# len(MultiBase.decode("9")) == 0
# len(MultiBase.decode("f")) == 0
# len(MultiBase.decode("F")) == 0
len(MultiBase.decode("v")) == 0
len(MultiBase.decode("V")) == 0
len(MultiBase.decode("t")) == 0
len(MultiBase.decode("T")) == 0
len(MultiBase.decode("b")) == 0
len(MultiBase.decode("B")) == 0
len(MultiBase.decode("c")) == 0
len(MultiBase.decode("C")) == 0
len(MultiBase.decode("z")) == 0
len(MultiBase.decode("Z")) == 0
# len(MultiBase.decode("m")) == 0
# len(MultiBase.decode("M")) == 0
# len(MultiBase.decode("u")) == 0
# len(MultiBase.decode("U")) == 0
check:
MultiBase.encode("identity", plain, enc,
olens[0]) == MultiBaseStatus.Success
enc == "\x00"
olens[0] == 1
# MultiBase.encode("base1", plain, enc,
# olens[1]) == MultiBaseStatus.Success
# enc == "1"
# olens[1] == 1
# MultiBase.encode("base2", plain, enc,
# olens[2]) == MultiBaseStatus.Success
# enc == "0"
# olens[2] == 1
# MultiBase.encode("base8", plain, enc,
# olens[3]) == MultiBaseStatus.Success
# enc == "7"
# olens[3] == 1
# MultiBase.encode("base10", plain, enc,
# olens[4]) == MultiBaseStatus.Success
# enc == "9"
# olens[4] == 1
# MultiBase.encode("base16", plain, enc,
# olens[5]) == MultiBaseStatus.Success
# enc == "f"
# olens[5] == 1
# MultiBase.encode("base16upper", plain, enc,
# olens[6]) == MultiBaseStatus.Success
# enc == "F"
# olens[6] == 1
MultiBase.encode("base32hex", plain, enc,
olens[7]) == MultiBaseStatus.Success
enc == "v"
olens[7] == 1
MultiBase.encode("base32hexupper", plain, enc,
olens[8]) == MultiBaseStatus.Success
enc == "V"
olens[8] == 1
MultiBase.encode("base32hexpad", plain, enc,
olens[9]) == MultiBaseStatus.Success
enc == "t"
olens[9] == 1
MultiBase.encode("base32hexpadupper", plain, enc,
olens[10]) == MultiBaseStatus.Success
enc == "T"
olens[10] == 1
MultiBase.encode("base32", plain, enc,
olens[11]) == MultiBaseStatus.Success
enc == "b"
olens[11] == 1
MultiBase.encode("base32upper", plain, enc,
olens[12]) == MultiBaseStatus.Success
enc == "B"
olens[12] == 1
MultiBase.encode("base32pad", plain, enc,
olens[13]) == MultiBaseStatus.Success
enc == "c"
olens[13] == 1
MultiBase.encode("base32padupper", plain, enc,
olens[14]) == MultiBaseStatus.Success
enc == "C"
olens[14] == 1
MultiBase.encode("base58btc", plain, enc,
olens[15]) == MultiBaseStatus.Success
enc == "z"
olens[15] == 1
MultiBase.encode("base58flickr", plain, enc,
olens[16]) == MultiBaseStatus.Success
enc == "Z"
olens[16] == 1
check:
MultiBase.decode("", dec, olens[0]) == MultiBaseStatus.Incorrect
MultiBase.decode("\x00", dec, olens[0]) == MultiBaseStatus.Success
olens[0] == 0
# MultiBase.decode("1", dec, olens[1]) == MultiBaseStatus.Success
# olens[1] == 0
# MultiBase.decode("0", dec, olens[2]) == MultiBaseStatus.Success
# olens[2] == 0
# MultiBase.decode("7", dec, olens[3]) == MultiBaseStatus.Success
# olens[3] == 0
# MultiBase.decode("9", dec, olens[4]) == MultiBaseStatus.Success
# olens[4] == 0
# MultiBase.decode("f", dec, olens[5]) == MultiBaseStatus.Success
# olens[5] == 0
# MultiBase.decode("F", dec, olens[6]) == MultiBaseStatus.Success
# olens[6] == 0
MultiBase.decode("v", dec, olens[7]) == MultiBaseStatus.Success
olens[7] == 0
MultiBase.decode("V", dec, olens[8]) == MultiBaseStatus.Success
olens[8] == 0
MultiBase.decode("t", dec, olens[9]) == MultiBaseStatus.Success
olens[9] == 0
MultiBase.decode("T", dec, olens[10]) == MultiBaseStatus.Success
olens[10] == 0
MultiBase.decode("b", dec, olens[11]) == MultiBaseStatus.Success
olens[11] == 0
MultiBase.decode("B", dec, olens[12]) == MultiBaseStatus.Success
olens[12] == 0
MultiBase.decode("c", dec, olens[13]) == MultiBaseStatus.Success
olens[13] == 0
MultiBase.decode("C", dec, olens[14]) == MultiBaseStatus.Success
olens[14] == 0
MultiBase.decode("z", dec, olens[15]) == MultiBaseStatus.Success
olens[15] == 0
MultiBase.decode("Z", dec, olens[16]) == MultiBaseStatus.Success
olens[16] == 0
# MultiBase.decode("m", dec, olens[16]) == MultiBaseStatus.Success
# olens[16] == 0
# MultiBase.decode("M", dec, olens[16]) == MultiBaseStatus.Success
# olens[16] == 0
# MultiBase.decode("u", dec, olens[16]) == MultiBaseStatus.Success
# olens[16] == 0
# MultiBase.decode("U", dec, olens[16]) == MultiBaseStatus.Success
# olens[16] == 0
test "go-multibase test vectors":
for item in GoTestVectors:
let encoding = item[0]
let encoded = item[1]
var expect = item[2]
var bexpect = cast[seq[byte]](expect)
var outlen = 0
check:
MultiBase.encode(encoding, bexpect) == encoded
MultiBase.decode(encoded) == bexpect
let elength = MultiBase.encodedLength(encoding, len(expect))
var ebuffer = newString(elength)
outlen = 0
check:
MultiBase.encode(encoding, bexpect, ebuffer,
outlen) == MultiBaseStatus.Success
ebuffer.setLen(outlen)
check:
encoded == ebuffer
let dlength = MultiBase.decodedLength(encoded[0], len(encoded))
var dbuffer = newSeq[byte](dlength)
outlen = 0
check:
MultiBase.decode(encoded, dbuffer, outlen) == MultiBaseStatus.Success
dbuffer.setLen(outlen)
check:
bexpect == dbuffer
test "Unknown codec test":
var data = @[0x00'u8, 0x01'u8]
var ebuffer = newString(100)
var dbuffer = newSeq[byte](100)
var outlen = 0
check:
MultiBase.encode("unknown", data, ebuffer,
outlen) == MultiBaseStatus.BadCodec
MultiBase.decode("\x01\x00", dbuffer, outlen) == MultiBaseStatus.BadCodec
var r1 = false
var r2 = false
try:
var enc = MultiBase.encode("unknwon", data)
except MultiBaseError:
r1 = true
try:
var dec = MultiBase.decode("\x01\x00")
except MultiBaseError:
r2 = true
check:
r1 == true
r2 == true

80
tests/testmultihash.nim Normal file
View File

@ -0,0 +1,80 @@
import unittest
import ../libp2p/multihash
const
RustTestVectors = [
# TODO: SHA1
# [
# "sha1",
# "beep boop",
# "11147c8357577f51d4f0a8d393aa1aaafb28863d9421"
# ],
[
"sha2-256",
"helloworld",
"1220936A185CAAA266BB9CBE981E9E05CB78CD732B0B3280EB944412BB6F8F8F07AF",
],
[
"sha2-256",
"beep boop",
"122090EA688E275D580567325032492B597BC77221C62493E76330B85DDDA191EF7C",
],
[
"sha2-512",
"hello world",
"1340309ECC489C12D6EB4CC40F50C902F2B4D0ED77EE511A7C7A9BCD3CA86D4CD86F989DD35BC5FF499670DA34255B45B0CFD830E81F605DCF7DC5542E93AE9CD76F"
],
[
"sha3-224",
"hello world",
"171CDFB7F18C77E928BB56FAEB2DA27291BD790BC1045CDE45F3210BB6C5"
],
[
"sha3-256",
"hello world",
"1620644BCC7E564373040999AAC89E7622F3CA71FBA1D972FD94A31C3BFBF24E3938"
],
[
"sha3-384",
"hello world",
"153083BFF28DDE1B1BF5810071C6643C08E5B05BDB836EFFD70B403EA8EA0A634DC4997EB1053AA3593F590F9C63630DD90B"
],
[
"sha3-512",
"hello world",
"1440840006653E9AC9E95117A15C915CAAB81662918E925DE9E004F774FF82D7079A40D4D27B1B372657C61D46D470304C88C788B3A4527AD074D1DCCBEE5DBAA99A"
],
[
"keccak-224",
"hello world",
"1A1C25F3ECFEBABE99686282F57F5C9E1F18244CFEE2813D33F955AAE568"
],
[
"keccak-256",
"hello world",
"1B2047173285A8D7341E5E972FC677286384F802F8EF42A5EC5F03BBFA254CB01FAD"
],
[
"keccak-384",
"hello world",
"1C3065FC99339A2A40E99D3C40D695B22F278853CA0F925CDE4254BCAE5E22ECE47E6441F91B6568425ADC9D95B0072EB49F"
],
[
"keccak-512",
"hello world",
"1D403EE2B40047B8060F68C67242175660F4174D0AF5C01D47168EC20ED619B0B7C42181F40AA1046F39E2EF9EFC6910782A998E0013D172458957957FAC9405B67D"
]
]
suite "MultiHash test suite":
test "rust-multihash test vectors":
for item in RustTestVectors:
var msg = item[1]
var bmsg = cast[seq[byte]](msg)
var mh1 = MultiHash.digest(item[0], bmsg)
var mh2 = MultiHash.init(item[2])
check:
hex(mh1) == item[2]
hex(mh1) == hex(mh2)
mh1 == mh2