diff --git a/storage/conf.nim b/storage/conf.nim index ee6c877a..ca743daa 100644 --- a/storage/conf.nim +++ b/storage/conf.nim @@ -208,16 +208,21 @@ type mixEnabled* {. desc: "Route DHT provider lookups through the Mix protocol via the " & - "dht-mix-proxy. Hides the requester's identity from the proxy.", + "dht-mix-proxy. Hides the requester's identity from the proxy", defaultValue: false, name: "mix-enabled" .}: bool mixPool* {. + desc: "Path to the Mix relay pool JSON file", defaultValue: "", name: "mix-pool" + .}: string + + mixPoolJson* {. desc: - "Path to the Mix relay pool JSON file " & "(produced by the `mix_pool` tool).", + "Inline JSON content of the Mix relay pool." & + "Takes precedence over --mix-pool when non-empty", defaultValue: "", - name: "mix-pool" + name: "mix-pool-json" .}: string maxPeers* {. diff --git a/storage/storage.nim b/storage/storage.nim index fabd0ded..91f102ba 100644 --- a/storage/storage.nim +++ b/storage/storage.nim @@ -94,7 +94,12 @@ proc start*(s: StorageServer) {.async.} = mixPub, mixPriv, switch.peerInfo.peerId, mixAddr, switch.peerInfo.privateKey ).valueOr: raise newException(StorageError, "Failed to build Mix node info: " & error.msg) - relayPool = loadRelayPubInfoTable(s.config.mixPool).valueOr: + relayPool = ( + if s.config.mixPoolJson.len > 0: + loadRelayPubInfoTableFromJson(s.config.mixPoolJson) + else: + loadRelayPubInfoTableFromFile(s.config.mixPool) + ).valueOr: raise newException(StorageError, "Failed to load Mix relay pool: " & error.msg) mixProto = MixProtocol.new(mixNodeInfo, switch) diff --git a/storage/utils/mixidentity.nim b/storage/utils/mixidentity.nim index 49a9a273..db9bb854 100644 --- a/storage/utils/mixidentity.nim +++ b/storage/utils/mixidentity.nim @@ -155,37 +155,25 @@ proc pubInfoFromJson(node: JsonNode): ?!MixPubInfo = success MixPubInfo.init(peerId, multiAddr, mixPubKey, libp2pPubKey) -proc loadRelayPubInfoTable*(poolPath: string): ?!Table[PeerId, MixPubInfo] = +proc loadRelayPubInfoTableFromJson*(poolJson: string): ?!Table[PeerId, MixPubInfo] = ## Expected format: ## { "version": 1, "relays": [ { peerId, multiAddr, mixPubKey, libp2pPubKey }, ... ] } - if poolPath.len == 0: + if poolJson.len == 0: return success initTable[PeerId, MixPubInfo]() - if not fileExists(poolPath): - return failure("Mix pool file does not exist: " & poolPath) - - let raw = - try: - readFile(poolPath) - except IOError as exc: - return failure("Failed to read pool " & poolPath & ": " & exc.msg) - let parsed = try: - parseJson(raw) + parseJson(poolJson) except CatchableError as exc: return failure("Failed to parse pool JSON: " & exc.msg) let versionNode = parsed.getOrDefault("version") if versionNode.isNil or versionNode.getInt() != PoolFormatVersion: - return failure( - "Unsupported pool version (expected " & $PoolFormatVersion & " in " & poolPath & - ")" - ) + return failure("Unsupported pool version (expected " & $PoolFormatVersion & ")") let relaysNode = parsed.getOrDefault("relays") if relaysNode.isNil or relaysNode.kind != JArray: - return failure("Pool file missing 'relays' array: " & poolPath) + return failure("Pool JSON missing 'relays' array") var t = initTable[PeerId, MixPubInfo]() for entry in relaysNode: @@ -194,4 +182,19 @@ proc loadRelayPubInfoTable*(poolPath: string): ?!Table[PeerId, MixPubInfo] = success t +proc loadRelayPubInfoTableFromFile*(poolPath: string): ?!Table[PeerId, MixPubInfo] = + if poolPath.len == 0: + return success initTable[PeerId, MixPubInfo]() + + if not fileExists(poolPath): + return failure("Mix pool file does not exist: " & poolPath) + + let poolJson = + try: + readFile(poolPath) + except IOError as exc: + return failure("Failed to read pool " & poolPath & ": " & exc.msg) + + loadRelayPubInfoTableFromJson(poolJson) + {.pop.} diff --git a/tests/storage/utils/testmixidentity.nim b/tests/storage/utils/testmixidentity.nim new file mode 100644 index 00000000..0f04c8b0 --- /dev/null +++ b/tests/storage/utils/testmixidentity.nim @@ -0,0 +1,74 @@ +import std/tables + +import pkg/unittest2 +import pkg/libp2p/peerid +import pkg/storage/utils/mixidentity {.all.} + +const SamplePoolJson = """ +{ + "version": 1, + "relays": [ + { + "peerId": "16Uiu2HAmNNzXL3wnW64pPFJDwrSJnNaX4CNLeWPbzdPcVJhRTGwP", + "multiAddr": "/ip4/127.0.0.1/tcp/4242", + "mixPubKey": "8a6571e8665fb1c894215f97d6a244591b655b1f5fd5ff7f928ef8b74aa66c5f", + "libp2pPubKey": "03907bc5a41bec7c5ba11f8dfe6c7f779328d2d5bb48c9a978a11e09f3fbf61b3e" + }, + { + "peerId": "16Uiu2HAmM6CDJa9HJQ76cRubcpAmrHfMcUCvYncA9M4BfFFEszQn", + "multiAddr": "/ip4/127.0.0.1/tcp/4243", + "mixPubKey": "f268d04a1a0903ecf63a3441b986eae414579aa47ff22b071370e6fcd9d3b45c", + "libp2pPubKey": "037d526dab2572c2336f721813964011899ba7d11a3ebebed1d22d1dea2b74e547" + } + ] +} +""" + +suite "mixidentity / loadRelayPubInfoTableFromJson": + test "empty string yields an empty table": + let res = loadRelayPubInfoTableFromJson("") + check res.isOk + check res.get.len == 0 + + test "parses a well-formed pool": + let res = loadRelayPubInfoTableFromJson(SamplePoolJson) + check res.isOk + let t = res.get + check t.len == 2 + let + p0 = PeerId.init("16Uiu2HAmNNzXL3wnW64pPFJDwrSJnNaX4CNLeWPbzdPcVJhRTGwP").get + p1 = PeerId.init("16Uiu2HAmM6CDJa9HJQ76cRubcpAmrHfMcUCvYncA9M4BfFFEszQn").get + check p0 in t + check p1 in t + + test "rejects malformed JSON": + let res = loadRelayPubInfoTableFromJson("{not json") + check res.isErr + + test "rejects unsupported version": + let + json = """{"version": 99, "relays": []}""" + res = loadRelayPubInfoTableFromJson(json) + check res.isErr + + test "rejects missing relays array": + let + json = """{"version": 1}""" + res = loadRelayPubInfoTableFromJson(json) + check res.isErr + + test "rejects entry missing required field": + let json = """ +{ + "version": 1, + "relays": [ + { + "peerId": "16Uiu2HAmNNzXL3wnW64pPFJDwrSJnNaX4CNLeWPbzdPcVJhRTGwP", + "multiAddr": "/ip4/127.0.0.1/tcp/4242", + "mixPubKey": "8a6571e8665fb1c894215f97d6a244591b655b1f5fd5ff7f928ef8b74aa66c5f" + } + ] +} +""" + let res = loadRelayPubInfoTableFromJson(json) + check res.isErr