diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 52ad72269..1c74e7ff2 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -18,7 +18,7 @@ import ../libp2p/[switch, multiaddress, peerinfo, crypto/crypto, - peer, + peerid, protocols/protocol, muxers/muxer, muxers/mplex/mplex, diff --git a/docs/tutorial/directchat/second.nim b/docs/tutorial/directchat/second.nim index d1858baae..de83d3db5 100644 --- a/docs/tutorial/directchat/second.nim +++ b/docs/tutorial/directchat/second.nim @@ -12,7 +12,7 @@ import ../libp2p/[switch, transports/tcptransport, multiaddress, peerinfo, - peer, + peerid, protocols/protocol, protocols/secure/secure, protocols/secure/secio, diff --git a/docs/tutorial/second.nim b/docs/tutorial/second.nim index d1858baae..de83d3db5 100644 --- a/docs/tutorial/second.nim +++ b/docs/tutorial/second.nim @@ -12,7 +12,7 @@ import ../libp2p/[switch, transports/tcptransport, multiaddress, peerinfo, - peer, + peerid, protocols/protocol, protocols/secure/secure, protocols/secure/secio, diff --git a/examples/directchat.nim b/examples/directchat.nim index 4afb181b6..22a9472f0 100644 --- a/examples/directchat.nim +++ b/examples/directchat.nim @@ -13,7 +13,7 @@ import ../libp2p/[switch, # manage transports, a single entry transports/tcptransport, # listen and dial to other peers using client-server protocol multiaddress, # encode different addressing schemes. For example, /ip4/7.7.7.7/tcp/6543 means it is using IPv4 protocol and TCP peerinfo, # manage the information of a peer, such as peer ID and public / private key - peer, # Implement how peers interact + peerid, # Implement how peers interact protocols/protocol, # define the protocol base type protocols/secure/secure, # define the protocol of secure connection protocols/secure/secio, # define the protocol of secure input / output, allows encrypted communication that uses public keys to validate signed messages instead of a certificate authority like in TLS diff --git a/libp2p/crypto/crypto.nim b/libp2p/crypto/crypto.nim index 9c996f357..ef3807485 100644 --- a/libp2p/crypto/crypto.nim +++ b/libp2p/crypto/crypto.nim @@ -95,7 +95,7 @@ const SupportedSchemesInt* = {int8(RSA), int8(Ed25519), int8(Secp256k1), int8(ECDSA)} -template orError(exp: untyped, err: CryptoError): untyped = +template orError*(exp: untyped, err: untyped): untyped = (exp.mapErr do (_: auto) -> auto: err) proc random*(t: typedesc[PrivateKey], scheme: PKScheme, diff --git a/libp2p/daemon/daemonapi.nim b/libp2p/daemon/daemonapi.nim index 68570ddb3..e610fd384 100644 --- a/libp2p/daemon/daemonapi.nim +++ b/libp2p/daemon/daemonapi.nim @@ -10,11 +10,11 @@ ## This module implementes API for `go-libp2p-daemon`. import os, osproc, strutils, tables, strtabs import chronos -import ../varint, ../multiaddress, ../multicodec, ../cid, ../peer +import ../varint, ../multiaddress, ../multicodec, ../cid, ../peerid import ../wire, ../multihash, ../protobuf/minprotobuf import ../crypto/crypto -export peer, multiaddress, multicodec, multihash, cid, crypto, wire +export peerid, multiaddress, multicodec, multihash, cid, crypto, wire when not defined(windows): import posix diff --git a/libp2p/multiaddress.nim b/libp2p/multiaddress.nim index 0d642e1fa..c6064f075 100644 --- a/libp2p/multiaddress.nim +++ b/libp2p/multiaddress.nim @@ -14,9 +14,8 @@ import nativesockets import tables, strutils, stew/shims/net import chronos -import multicodec, multihash, multibase, transcoder, vbuffer +import multicodec, multihash, multibase, transcoder, vbuffer, peerid import stew/[base58, base32, endians2, results] -from peer import PeerID export results type diff --git a/libp2p/peer.nim b/libp2p/peerid.nim similarity index 83% rename from libp2p/peer.nim rename to libp2p/peerid.nim index a8d90af87..e20417d0c 100644 --- a/libp2p/peer.nim +++ b/libp2p/peerid.nim @@ -8,10 +8,15 @@ ## those terms. ## This module implementes API for libp2p peer. + +{.push raises: [Defect].} + import hashes import nimcrypto/utils, stew/base58 import crypto/crypto, multicodec, multihash, vbuffer import protobuf/minprotobuf +import stew/results +export results const maxInlineKeyLength* = 42 @@ -143,37 +148,51 @@ proc init*(pid: var PeerID, data: string): bool = pid = opid result = true -proc init*(t: typedesc[PeerID], data: openarray[byte]): PeerID {.inline.} = +proc init*(t: typedesc[PeerID], data: openarray[byte]): Result[PeerID, cstring] {.inline.} = ## Create new peer id from raw binary representation ``data``. - if not init(result, data): - raise newException(PeerIDError, "Incorrect PeerID binary form") + var res: PeerID + if not init(res, data): + err("peerid: incorrect PeerID binary form") + else: + ok(res) -proc init*(t: typedesc[PeerID], data: string): PeerID {.inline.} = +proc init*(t: typedesc[PeerID], data: string): Result[PeerID, cstring] {.inline.} = ## Create new peer id from base58 encoded string representation ``data``. - if not init(result, data): - raise newException(PeerIDError, "Incorrect PeerID string") + var res: PeerID + if not init(res, data): + err("peerid: incorrect PeerID string") + else: + ok(res) -proc init*(t: typedesc[PeerID], pubkey: PublicKey): PeerID = +proc init*(t: typedesc[PeerID], pubkey: PublicKey): Result[PeerID, cstring] = ## Create new peer id from public key ``pubkey``. - var pubraw = pubkey.getBytes().tryGet() + var pubraw = ? pubkey.getBytes().orError("peerid: failed to get bytes from given key") var mh: MultiHash if len(pubraw) <= maxInlineKeyLength: - mh = MultiHash.digest("identity", pubraw).tryGet() + mh = ? MultiHash.digest("identity", pubraw) else: - mh = MultiHash.digest("sha2-256", pubraw).tryGet() - result.data = mh.data.buffer + mh = ? MultiHash.digest("sha2-256", pubraw) + ok(PeerID(data: mh.data.buffer)) -proc init*(t: typedesc[PeerID], seckey: PrivateKey): PeerID {.inline.} = +proc init*(t: typedesc[PeerID], seckey: PrivateKey): Result[PeerID, cstring] {.inline.} = ## Create new peer id from private key ``seckey``. - result = PeerID.init(seckey.getKey().tryGet()) + PeerID.init(? seckey.getKey().orError("invalid private key")) proc match*(pid: PeerID, pubkey: PublicKey): bool {.inline.} = ## Returns ``true`` if ``pid`` matches public key ``pubkey``. - result = (pid == PeerID.init(pubkey)) + let p = PeerID.init(pubkey) + if p.isErr: + false + else: + pid == p.get() proc match*(pid: PeerID, seckey: PrivateKey): bool {.inline.} = ## Returns ``true`` if ``pid`` matches private key ``seckey``. - result = (pid == PeerID.init(seckey)) + let p = PeerID.init(seckey) + if p.isErr: + false + else: + pid == p.get() ## Serialization/Deserialization helpers diff --git a/libp2p/peerinfo.nim b/libp2p/peerinfo.nim index bfe5048ba..978d04808 100644 --- a/libp2p/peerinfo.nim +++ b/libp2p/peerinfo.nim @@ -9,7 +9,7 @@ import options, sequtils import chronos, chronicles -import peer, multiaddress, crypto/crypto +import peerid, multiaddress, crypto/crypto ## A peer can be constructed in one of tree ways: ## 1) A local peer with a private key @@ -65,7 +65,7 @@ proc init*(p: typedesc[PeerInfo], key: PrivateKey, addrs: openarray[MultiAddress] = [], protocols: openarray[string] = []): PeerInfo {.inline.} = - result = PeerInfo(keyType: HasPrivate, peerId: PeerID.init(key), + result = PeerInfo(keyType: HasPrivate, peerId: PeerID.init(key).tryGet(), privateKey: key) result.postInit(addrs, protocols) @@ -80,7 +80,7 @@ proc init*(p: typedesc[PeerInfo], peerId: string, addrs: openarray[MultiAddress] = [], protocols: openarray[string] = []): PeerInfo {.inline.} = - result = PeerInfo(keyType: HasPublic, peerId: PeerID.init(peerId)) + result = PeerInfo(keyType: HasPublic, peerId: PeerID.init(peerId).tryGet()) result.postInit(addrs, protocols) proc init*(p: typedesc[PeerInfo], @@ -88,7 +88,7 @@ proc init*(p: typedesc[PeerInfo], addrs: openarray[MultiAddress] = [], protocols: openarray[string] = []): PeerInfo {.inline.} = result = PeerInfo(keyType: HasPublic, - peerId: PeerID.init(key), + peerId: PeerID.init(key).tryGet(), key: some(key)) result.postInit(addrs, protocols) diff --git a/libp2p/protocols/identify.nim b/libp2p/protocols/identify.nim index b2d3c501a..735d740af 100644 --- a/libp2p/protocols/identify.nim +++ b/libp2p/protocols/identify.nim @@ -12,7 +12,7 @@ import chronos, chronicles import ../protobuf/minprotobuf, ../peerinfo, ../stream/connection, - ../peer, + ../peerid, ../crypto/crypto, ../multiaddress, ../protocols/protocol, @@ -142,16 +142,18 @@ proc identify*(p: Identify, if not isNil(remotePeerInfo) and result.pubKey.isSome: let peer = PeerID.init(result.pubKey.get()) + if peer.isErr: + raise newException(IdentityInvalidMsgError, $peer.error) + else: + # do a string comaprison of the ids, + # because that is the only thing we + # have in most cases + if peer.get() != remotePeerInfo.peerId: + trace "Peer ids don't match", + remote = peer.get().pretty(), + local = remotePeerInfo.id - # do a string comparison of the ids, - # because that is the only thing we - # have in most cases - if peer != remotePeerInfo.peerId: - trace "Peer ids don't match", - remote = peer.pretty(), - local = remotePeerInfo.id - - raise newException(IdentityNoMatchError, "Peer ids don't match") + raise newException(IdentityNoMatchError, "Peer ids don't match") proc push*(p: Identify, conn: Connection) {.async.} = await conn.write(IdentifyPushCodec) diff --git a/libp2p/protocols/pubsub/floodsub.nim b/libp2p/protocols/pubsub/floodsub.nim index 91f31c162..adf2b7d93 100644 --- a/libp2p/protocols/pubsub/floodsub.nim +++ b/libp2p/protocols/pubsub/floodsub.nim @@ -14,7 +14,7 @@ import pubsub, timedcache, rpc/[messages, message], ../../stream/connection, - ../../peer, + ../../peerid, ../../peerinfo, ../../utility, ../../errors diff --git a/libp2p/protocols/pubsub/gossipsub.nim b/libp2p/protocols/pubsub/gossipsub.nim index 64e8a82d3..9498266c0 100644 --- a/libp2p/protocols/pubsub/gossipsub.nim +++ b/libp2p/protocols/pubsub/gossipsub.nim @@ -18,7 +18,7 @@ import pubsub, ../protocol, ../../peerinfo, ../../stream/connection, - ../../peer, + ../../peerid, ../../errors, ../../utility diff --git a/libp2p/protocols/pubsub/pubsub.nim b/libp2p/protocols/pubsub/pubsub.nim index da1c24a0c..434edc15c 100644 --- a/libp2p/protocols/pubsub/pubsub.nim +++ b/libp2p/protocols/pubsub/pubsub.nim @@ -13,7 +13,7 @@ import pubsubpeer, rpc/[message, messages], ../protocol, ../../stream/connection, - ../../peer, + ../../peerid, ../../peerinfo import metrics diff --git a/libp2p/protocols/pubsub/pubsubpeer.nim b/libp2p/protocols/pubsub/pubsubpeer.nim index 1ea920085..ee9851b45 100644 --- a/libp2p/protocols/pubsub/pubsubpeer.nim +++ b/libp2p/protocols/pubsub/pubsubpeer.nim @@ -11,7 +11,7 @@ import options, hashes, strutils, tables, hashes import chronos, chronicles, nimcrypto/sha2, metrics import rpc/[messages, message, protobuf], timedcache, - ../../peer, + ../../peerid, ../../peerinfo, ../../stream/connection, ../../crypto/crypto, diff --git a/libp2p/protocols/pubsub/rpc/message.nim b/libp2p/protocols/pubsub/rpc/message.nim index d7a35ae14..d203035d4 100644 --- a/libp2p/protocols/pubsub/rpc/message.nim +++ b/libp2p/protocols/pubsub/rpc/message.nim @@ -15,7 +15,7 @@ import metrics import chronicles import nimcrypto/sysrand import messages, protobuf, - ../../../peer, + ../../../peerid, ../../../peerinfo, ../../../crypto/crypto, ../../../protobuf/minprotobuf diff --git a/libp2p/protocols/pubsub/rpc/messages.nim b/libp2p/protocols/pubsub/rpc/messages.nim index afb6a5f3e..5a0ae615d 100644 --- a/libp2p/protocols/pubsub/rpc/messages.nim +++ b/libp2p/protocols/pubsub/rpc/messages.nim @@ -9,7 +9,7 @@ import options, sequtils import ../../../utility -import ../../../peer +import ../../../peerid type SubOpts* = object diff --git a/libp2p/protocols/pubsub/rpc/protobuf.nim b/libp2p/protocols/pubsub/rpc/protobuf.nim index 556232475..ce42275d0 100644 --- a/libp2p/protocols/pubsub/rpc/protobuf.nim +++ b/libp2p/protocols/pubsub/rpc/protobuf.nim @@ -10,7 +10,7 @@ import options import chronicles import messages, - ../../../peer, + ../../../peerid, ../../../utility, ../../../protobuf/minprotobuf @@ -186,7 +186,7 @@ proc decodeMessages*(pb: var ProtoBuffer): seq[Message] {.gcsafe.} = if pb.getBytes(1, fromPeer) < 0: break try: - msg.fromPeer = PeerID.init(fromPeer) + msg.fromPeer = PeerID.init(fromPeer).tryGet() except CatchableError as err: debug "Invalid fromPeer in message", msg = err.msg break diff --git a/libp2p/protocols/secure/noise.nim b/libp2p/protocols/secure/noise.nim index cdd37eec0..4319573ed 100644 --- a/libp2p/protocols/secure/noise.nim +++ b/libp2p/protocols/secure/noise.nim @@ -12,7 +12,7 @@ import chronicles import stew/[endians2, byteutils] import nimcrypto/[utils, sysrand, sha2, hmac] import ../../stream/lpstream -import ../../peer +import ../../peerid import ../../peerinfo import ../../protobuf/minprotobuf import ../../utility @@ -460,7 +460,7 @@ method handshake*(p: Noise, conn: Connection, initiator: bool): Future[SecureCon let pid = PeerID.init(remotePubKey) if not conn.peerInfo.peerId.validate(): raise newException(NoiseHandshakeError, "Failed to validate peerId.") - if pid != conn.peerInfo.peerId: + if pid.isErr or pid.get() != conn.peerInfo.peerId: var failedKey: PublicKey discard extractPublicKey(conn.peerInfo.peerId, failedKey) diff --git a/libp2p/protocols/secure/secio.nim b/libp2p/protocols/secure/secio.nim index 3d369b92d..331092461 100644 --- a/libp2p/protocols/secure/secio.nim +++ b/libp2p/protocols/secure/secio.nim @@ -13,7 +13,7 @@ import secure, ../../peerinfo, ../../crypto/crypto, ../../crypto/ecnist, - ../../peer, + ../../peerid, ../../utility export hmac, sha2, sha, hash, rijndael, bcmode @@ -297,7 +297,7 @@ method handshake*(s: Secio, conn: Connection, initiator: bool = false): Future[S SecioCiphers, SecioHashes) - localPeerId = PeerID.init(s.localPublicKey) + localPeerId = PeerID.init(s.localPublicKey).tryGet() trace "Local proposal", schemes = SecioExchanges, ciphers = SecioCiphers, @@ -320,7 +320,7 @@ method handshake*(s: Secio, conn: Connection, initiator: bool = false): Future[S trace "Remote public key incorrect or corrupted", pubkey = remoteBytesPubkey.shortLog raise (ref SecioError)(msg: "Remote public key incorrect or corrupted") - remotePeerId = PeerID.init(remotePubkey) + remotePeerId = PeerID.init(remotePubkey).tryGet() # TODO: PeerID check against supplied PeerID let order = getOrder(remoteBytesPubkey, localNonce, localBytesPubkey, diff --git a/libp2p/standard_setup.nim b/libp2p/standard_setup.nim index 991378a1e..73695d437 100644 --- a/libp2p/standard_setup.nim +++ b/libp2p/standard_setup.nim @@ -5,7 +5,7 @@ const import options, tables, chronos, - switch, peer, peerinfo, stream/connection, multiaddress, + switch, peerid, peerinfo, stream/connection, multiaddress, crypto/crypto, transports/[transport, tcptransport], muxers/[muxer, mplex/mplex, mplex/types], protocols/[identify, secure/secure], @@ -17,7 +17,7 @@ import protocols/secure/secio export - switch, peer, peerinfo, connection, multiaddress, crypto + switch, peerid, peerinfo, connection, multiaddress, crypto type SecureProtocol* {.pure.} = enum diff --git a/libp2p/switch.nim b/libp2p/switch.nim index 6b7b82471..c7047c98c 100644 --- a/libp2p/switch.nim +++ b/libp2p/switch.nim @@ -31,7 +31,7 @@ import stream/connection, protocols/pubsub/pubsub, muxers/muxer, errors, - peer + peerid logScope: topics = "switch" diff --git a/tests/pubsub/testgossipsub.nim b/tests/pubsub/testgossipsub.nim index 6a610ffad..197c967c0 100644 --- a/tests/pubsub/testgossipsub.nim +++ b/tests/pubsub/testgossipsub.nim @@ -13,7 +13,7 @@ import unittest, sequtils, options, tables, sets import chronos, stew/byteutils import chronicles import utils, ../../libp2p/[errors, - peer, + peerid, peerinfo, stream/connection, crypto/crypto, diff --git a/tests/pubsub/testmcache.nim b/tests/pubsub/testmcache.nim index 118be8b3b..90a1b1139 100644 --- a/tests/pubsub/testmcache.nim +++ b/tests/pubsub/testmcache.nim @@ -2,7 +2,7 @@ import unittest, options, sets, sequtils import stew/byteutils -import ../../libp2p/[peer, +import ../../libp2p/[peerid, crypto/crypto, protocols/pubsub/mcache, protocols/pubsub/rpc/message, @@ -11,7 +11,7 @@ import ../../libp2p/[peer, suite "MCache": test "put/get": var mCache = newMCache(3, 5) - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes()) let msgId = defaultMsgIdProvider(msg) mCache.put(msgId, msg) @@ -21,13 +21,13 @@ suite "MCache": var mCache = newMCache(3, 5) for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["foo"]) mCache.put(defaultMsgIdProvider(msg), msg) for i in 0..<5: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["bar"]) mCache.put(defaultMsgIdProvider(msg), msg) @@ -42,7 +42,7 @@ suite "MCache": var mCache = newMCache(1, 5) for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["foo"]) mCache.put(defaultMsgIdProvider(msg), msg) @@ -51,7 +51,7 @@ suite "MCache": check mCache.window("foo").len == 0 for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["bar"]) mCache.put(defaultMsgIdProvider(msg), msg) @@ -60,7 +60,7 @@ suite "MCache": check mCache.window("bar").len == 0 for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["baz"]) mCache.put(defaultMsgIdProvider(msg), msg) @@ -72,19 +72,19 @@ suite "MCache": var mCache = newMCache(1, 5) for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["foo"]) mCache.put(defaultMsgIdProvider(msg), msg) for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["bar"]) mCache.put(defaultMsgIdProvider(msg), msg) for i in 0..<3: - var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()), + var msg = Message(fromPeer: PeerID.init(PrivateKey.random(ECDSA).get()).get(), seqno: "12345".toBytes(), topicIDs: @["baz"]) mCache.put(defaultMsgIdProvider(msg), msg) diff --git a/tests/pubsub/testmessage.nim b/tests/pubsub/testmessage.nim index 77128ef05..1c9092d45 100644 --- a/tests/pubsub/testmessage.nim +++ b/tests/pubsub/testmessage.nim @@ -1,6 +1,6 @@ import unittest -import ../../libp2p/[peer, peerinfo, +import ../../libp2p/[peerid, peerinfo, crypto/crypto, protocols/pubsub/rpc/message, protocols/pubsub/rpc/messages] diff --git a/tests/testdaemon.nim b/tests/testdaemon.nim index cd8642a75..485b27886 100644 --- a/tests/testdaemon.nim +++ b/tests/testdaemon.nim @@ -1,7 +1,7 @@ import unittest import chronos import ../libp2p/daemon/daemonapi, ../libp2p/multiaddress, ../libp2p/multicodec, - ../libp2p/cid, ../libp2p/multihash, ../libp2p/peer + ../libp2p/cid, ../libp2p/multihash, ../libp2p/peerid when defined(nimHasUsed): {.used.} diff --git a/tests/testidentify.nim b/tests/testidentify.nim index 305e373de..d91f7079a 100644 --- a/tests/testidentify.nim +++ b/tests/testidentify.nim @@ -3,7 +3,7 @@ import chronos, strutils import ../libp2p/[protocols/identify, multiaddress, peerinfo, - peer, + peerid, stream/connection, multistream, transports/transport, diff --git a/tests/testinterop.nim b/tests/testinterop.nim index e3dea9350..988846ec6 100644 --- a/tests/testinterop.nim +++ b/tests/testinterop.nim @@ -11,7 +11,7 @@ import ../libp2p/[daemon/daemonapi, varint, multihash, standard_setup, - peer, + peerid, peerinfo, switch, stream/connection, diff --git a/tests/testpeer.nim b/tests/testpeer.nim index 949e796df..2f497951b 100644 --- a/tests/testpeer.nim +++ b/tests/testpeer.nim @@ -11,7 +11,7 @@ ## https://github.com/libp2p/go-libp2p-peer import unittest import nimcrypto/utils, stew/base58 -import ../libp2p/crypto/crypto, ../libp2p/peer +import ../libp2p/crypto/crypto, ../libp2p/peerid when defined(nimHasUsed): {.used.} @@ -103,11 +103,11 @@ suite "Peer testing suite": for i in 0..