feat: xmrrpc (and wowrpc) uri parsing + tests

This commit is contained in:
julian 2024-12-11 11:54:14 -06:00
parent 2873595e40
commit 4af7243265
2 changed files with 236 additions and 0 deletions

View file

@ -0,0 +1,132 @@
abstract interface class NodeQrData {
final String host;
final int port;
final String? label;
NodeQrData({required this.host, required this.port, this.label});
String encode();
String get scheme;
}
abstract class LibMoneroNodeQrData extends NodeQrData {
final String user;
final String password;
LibMoneroNodeQrData({
required super.host,
required super.port,
super.label,
required this.user,
required this.password,
});
@override
String encode() {
String? userInfo;
if (user.isNotEmpty) {
userInfo = user;
if (password.isNotEmpty) {
userInfo += ":$password";
}
}
final uri = Uri(
scheme: scheme,
userInfo: userInfo,
port: port,
host: host,
queryParameters: {"label": label},
);
return uri.toString();
}
@override
String toString() {
return "$runtimeType {"
"scheme: $scheme, "
"host: $host, "
"port: $port, "
"user: $user, "
"password: $password, "
"label: $label"
"}";
}
}
class MoneroNodeQrData extends LibMoneroNodeQrData {
MoneroNodeQrData({
required super.host,
required super.port,
required super.user,
required super.password,
super.label,
});
@override
String get scheme => "xmrrpc";
}
class WowneroNodeQrData extends LibMoneroNodeQrData {
WowneroNodeQrData({
required super.host,
required super.port,
required super.user,
required super.password,
super.label,
});
@override
String get scheme => "wowrpc";
}
abstract final class NodeQrUtil {
static ({String? user, String? password}) _parseUserInfo(String? userInfo) {
if (userInfo == null || userInfo.isEmpty) {
return (user: null, password: null);
}
final splitIndex = userInfo.indexOf(":");
if (splitIndex == -1) {
return (user: userInfo, password: null);
}
return (
user: userInfo.substring(0, splitIndex),
password: userInfo.substring(splitIndex + 1),
);
}
static NodeQrData decodeUri(String uriString) {
final uri = Uri.tryParse(uriString);
if (uri == null) throw Exception("Invalid uri string.");
if (!uri.hasAuthority) throw Exception("Uri has no authority.");
final userInfo = _parseUserInfo(uri.userInfo);
final query = uri.queryParameters;
switch (uri.scheme) {
case "xmrrpc":
return MoneroNodeQrData(
host: uri.host,
port: uri.port,
user: userInfo.user ?? "",
password: userInfo.password ?? "",
label: query["label"],
);
case "wowrpc":
return WowneroNodeQrData(
host: uri.host,
port: uri.port,
user: userInfo.user ?? "",
password: userInfo.password ?? "",
label: query["label"],
);
default:
throw Exception("Unknown node uri scheme \"${uri.scheme}\" found.");
}
}
}

View file

@ -0,0 +1,104 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:stackwallet/utilities/node_uri_util.dart';
void main() {
test("Valid xmrrpc scheme node uri", () {
expect(
NodeQrUtil.decodeUri(
"xmrrpc://nodo:password@bob.onion:18083?label=Nodo Tor Node",
),
isA<MoneroNodeQrData>(),
);
});
test("Valid wowrpc scheme node uri", () {
expect(
NodeQrUtil.decodeUri(
"wowrpc://nodo:password@10.0.0.10:18083",
),
isA<WowneroNodeQrData>(),
);
});
test("Invalid authority node uri", () {
String? message;
try {
NodeQrUtil.decodeUri(
"nodo:password@bob.onion:18083?label=Nodo Tor Node",
);
} catch (e) {
message = e.toString();
}
expect(message, "Exception: Uri has no authority.");
});
test("Empty uri string", () {
String? message;
try {
NodeQrUtil.decodeUri("");
} catch (e) {
message = e.toString();
}
expect(message, "Exception: Uri has no authority.");
});
test("Invalid uri string", () {
String? message;
try {
NodeQrUtil.decodeUri("::invalid@@@.ok");
} catch (e) {
message = e.toString();
}
expect(message, "Exception: Invalid uri string.");
});
test("Unknown uri string", () {
String? message;
try {
NodeQrUtil.decodeUri("http://u:p@host.com:80/lol?hmm=42");
} catch (e) {
message = e.toString();
}
expect(message, "Exception: Unknown node uri scheme \"http\" found.");
});
test("decoding to model", () {
final data = NodeQrUtil.decodeUri(
"xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node",
);
expect(data.scheme, "xmrrpc");
expect(data.host, "bob.onion");
expect(data.port, 18083);
expect(data.label, "Nodo Tor Node");
expect((data as MoneroNodeQrData?)?.user, "nodo");
expect((data as MoneroNodeQrData?)?.password, "password");
});
test("encoding to string", () {
const validString =
"xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node";
final data = NodeQrUtil.decodeUri(
validString,
);
expect(data.encode(), validString);
});
test("normal to string", () {
const validString =
"xmrrpc://nodo:password@bob.onion:18083?label=Nodo+Tor+Node";
final data = NodeQrUtil.decodeUri(
validString,
);
expect(
data.toString(),
"MoneroNodeQrData {"
"scheme: xmrrpc, "
"host: bob.onion, "
"port: 18083, "
"user: nodo, "
"password: password, "
"label: Nodo Tor Node"
"}",
);
});
}