WIP
This commit is contained in:
@@ -25,17 +25,9 @@ import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
|
||||
// Vault provides secure AES/GCM encryption and decryption with memory hygiene.
|
||||
// It uses standard JCE providers but ensures that application-level buffers can be wiped.
|
||||
//
|
||||
// Purpose:
|
||||
// This provides a way to use opaque handles to javax.crypto.SecretKey objects in discrete memory
|
||||
// instead of exposing your plaintext secret keys to other parts of the system that might leave
|
||||
// dead references on the heap somewhere that are vulnerable until GC finally hits.
|
||||
//
|
||||
// The important thing to remember is that there is still a burden on the caller to take advantage
|
||||
// of whatever the current platform's best key management facilities are and stick to them.
|
||||
|
||||
/**
|
||||
* Vault provides secure AES/GCM encryption and decryption with memory hygiene.
|
||||
*/
|
||||
public final class Vault {
|
||||
|
||||
private static final String AES_GCM = "AES/GCM/NoPadding";
|
||||
@@ -43,14 +35,36 @@ public final class Vault {
|
||||
|
||||
private Vault() {}
|
||||
|
||||
/**
|
||||
* Holds the results of an encryption operation.
|
||||
*/
|
||||
public static final class Ciphertext {
|
||||
private final byte[] iv;
|
||||
private final byte[] data;
|
||||
|
||||
public Ciphertext(byte[] iv, byte[] data) {
|
||||
this.iv = iv;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public byte[] getIv() { return iv; }
|
||||
public byte[] getData() { return data; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts plaintext using AES/GCM.
|
||||
*
|
||||
* @param key The SecretKey to use.
|
||||
* @param iv The initialization vector (should be 12 bytes).
|
||||
* @param plaintext The data to encrypt.
|
||||
* @return The ciphertext including the authentication tag.
|
||||
* @throws Exception if encryption fails.
|
||||
* Letting the provider generate the IV (required by Android Keystore).
|
||||
*/
|
||||
public static Ciphertext encrypt(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] iv = cipher.getIV();
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
return new Ciphertext(iv, ciphertext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts plaintext using AES/GCM with a specific IV.
|
||||
*/
|
||||
public static byte[] encrypt(SecretKey key, byte[] iv, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
@@ -61,13 +75,6 @@ public final class Vault {
|
||||
|
||||
/**
|
||||
* Decrypts ciphertext using AES/GCM.
|
||||
*
|
||||
* @param key The SecretKey to use.
|
||||
* @param iv The initialization vector used during encryption.
|
||||
* @param ciphertext The data to decrypt (including the authentication tag).
|
||||
* @return The plaintext data. The caller is RESPONSIBLE for wiping this buffer
|
||||
* using CryptoUtils.wipe() once it is no longer needed.
|
||||
* @throws Exception if decryption or authentication fails.
|
||||
*/
|
||||
public static byte[] decrypt(SecretKey key, byte[] iv, byte[] ciphertext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(AES_GCM);
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class Mnemonic {
|
||||
private static final int WIDTH = 12; // bits per word
|
||||
private static final int MAX_DATA_CHUNKS = 22; // For 256-bit seed
|
||||
|
||||
private static final String[] WORDS = {
|
||||
public static final String[] WORDS = {
|
||||
"aardvark", "abacus", "abalone", "abandon", "abbey", "abdomen", "abduct", "abhor", "abide",
|
||||
"ability", "able", "abnormal", "aboard", "abolish", "abort", "above", "abrasive", "abridged",
|
||||
"abroad", "abrupt", "abscond", "absence", "absinthe", "absorb", "abstract", "absurd", "abundant",
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
package swiss.qpq.gajumaru.core.encoding;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ZJ: The tiny JSON parser, ported from zj.erl.
|
||||
*/
|
||||
public final class ZJ {
|
||||
|
||||
private ZJ() {}
|
||||
|
||||
public static Object decode(String json) {
|
||||
if (json == null || json.isEmpty()) return null;
|
||||
return new Parser(json).parse();
|
||||
}
|
||||
|
||||
public static String encode(Object value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
encodeValue(value, sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void encodeValue(Object value, StringBuilder sb) {
|
||||
if (value == null) {
|
||||
sb.append("null");
|
||||
} else if (value instanceof String) {
|
||||
sb.append('"');
|
||||
escape((String) value, sb);
|
||||
sb.append('"');
|
||||
} else if (value instanceof Boolean) {
|
||||
sb.append(value);
|
||||
} else if (value instanceof Number) {
|
||||
sb.append(value);
|
||||
} else if (value instanceof Map) {
|
||||
encodeMap((Map<?, ?>) value, sb);
|
||||
} else if (value instanceof List) {
|
||||
encodeList((List<?>) value, sb);
|
||||
} else if (value instanceof Object[]) {
|
||||
encodeArray((Object[]) value, sb);
|
||||
} else {
|
||||
// Fallback for other objects, though not strictly in ZJ
|
||||
sb.append('"');
|
||||
escape(value.toString(), sb);
|
||||
sb.append('"');
|
||||
}
|
||||
}
|
||||
|
||||
private static void encodeMap(Map<?, ?> map, StringBuilder sb) {
|
||||
sb.append('{');
|
||||
boolean first = true;
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
if (!first) sb.append(',');
|
||||
first = false;
|
||||
sb.append('"');
|
||||
escape(entry.getKey().toString(), sb);
|
||||
sb.append("\":");
|
||||
encodeValue(entry.getValue(), sb);
|
||||
}
|
||||
sb.append('}');
|
||||
}
|
||||
|
||||
private static void encodeList(List<?> list, StringBuilder sb) {
|
||||
sb.append('[');
|
||||
boolean first = true;
|
||||
for (Object item : list) {
|
||||
if (!first) sb.append(',');
|
||||
first = false;
|
||||
encodeValue(item, sb);
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
|
||||
private static void encodeArray(Object[] array, StringBuilder sb) {
|
||||
sb.append('[');
|
||||
boolean first = true;
|
||||
for (Object item : array) {
|
||||
if (!first) sb.append(',');
|
||||
first = false;
|
||||
encodeValue(item, sb);
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
|
||||
private static void escape(String s, StringBuilder sb) {
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
char c = s.charAt(i);
|
||||
switch (c) {
|
||||
case '"' -> sb.append("\\\"");
|
||||
case '\\' -> sb.append("\\\\");
|
||||
case '\b' -> sb.append("\\b");
|
||||
case '\f' -> sb.append("\\f");
|
||||
case '\n' -> sb.append("\\n");
|
||||
case '\r' -> sb.append("\\r");
|
||||
case '\t' -> sb.append("\\t");
|
||||
default -> {
|
||||
if (c < 32 || c > 126) {
|
||||
sb.append(String.format("\\u%04x", (int) c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class Parser {
|
||||
private final String json;
|
||||
private int pos = 0;
|
||||
|
||||
Parser(String json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
Object parse() {
|
||||
seek();
|
||||
if (pos >= json.length()) return null;
|
||||
return value();
|
||||
}
|
||||
|
||||
private Object value() {
|
||||
char c = json.charAt(pos);
|
||||
return switch (c) {
|
||||
case '{' -> object();
|
||||
case '[' -> array();
|
||||
case '"' -> string();
|
||||
case 't' -> bool(true);
|
||||
case 'f' -> bool(false);
|
||||
case 'n' -> nil();
|
||||
default -> {
|
||||
if (c == '-' || (c >= '0' && c <= '9')) {
|
||||
yield number();
|
||||
}
|
||||
throw new RuntimeException("Unexpected character at " + pos + ": " + c);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, Object> object() {
|
||||
pos++; // skip '{'
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
seek();
|
||||
if (pos < json.length() && json.charAt(pos) == '}') {
|
||||
pos++;
|
||||
return map;
|
||||
}
|
||||
while (true) {
|
||||
seek();
|
||||
String key = string();
|
||||
seek();
|
||||
if (pos >= json.length() || json.charAt(pos) != ':') {
|
||||
throw new RuntimeException("Expected ':' at " + pos);
|
||||
}
|
||||
pos++;
|
||||
seek();
|
||||
Object val = value();
|
||||
map.put(key, val);
|
||||
seek();
|
||||
if (pos < json.length() && json.charAt(pos) == '}') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
if (pos >= json.length() || json.charAt(pos) != ',') {
|
||||
throw new RuntimeException("Expected ',' or '}' at " + pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private List<Object> array() {
|
||||
pos++; // skip '['
|
||||
List<Object> list = new ArrayList<>();
|
||||
seek();
|
||||
if (pos < json.length() && json.charAt(pos) == ']') {
|
||||
pos++;
|
||||
return list;
|
||||
}
|
||||
while (true) {
|
||||
seek();
|
||||
list.add(value());
|
||||
seek();
|
||||
if (pos < json.length() && json.charAt(pos) == ']') {
|
||||
pos++;
|
||||
break;
|
||||
}
|
||||
if (pos >= json.length() || json.charAt(pos) != ',') {
|
||||
throw new RuntimeException("Expected ',' or ']' at " + pos);
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private String string() {
|
||||
if (json.charAt(pos) != '"') throw new RuntimeException("Expected '\"' at " + pos);
|
||||
pos++;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (pos < json.length()) {
|
||||
char c = json.charAt(pos++);
|
||||
if (c == '"') return sb.toString();
|
||||
if (c == '\\') {
|
||||
if (pos >= json.length()) throw new RuntimeException("Unterminated escape at " + pos);
|
||||
char esc = json.charAt(pos++);
|
||||
switch (esc) {
|
||||
case '"' -> sb.append('"');
|
||||
case '\\' -> sb.append('\\');
|
||||
case '/' -> sb.append('/');
|
||||
case 'b' -> sb.append('\b');
|
||||
case 'f' -> sb.append('\f');
|
||||
case 'n' -> sb.append('\n');
|
||||
case 'r' -> sb.append('\r');
|
||||
case 't' -> sb.append('\t');
|
||||
case 'u' -> {
|
||||
if (pos + 4 > json.length()) throw new RuntimeException("Invalid unicode escape");
|
||||
String hex = json.substring(pos, pos + 4);
|
||||
sb.append((char) Integer.parseInt(hex, 16));
|
||||
pos += 4;
|
||||
}
|
||||
default -> throw new RuntimeException("Unknown escape: " + esc);
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Unterminated string");
|
||||
}
|
||||
|
||||
private Boolean bool(boolean expected) {
|
||||
String s = expected ? "true" : "false";
|
||||
if (json.startsWith(s, pos)) {
|
||||
pos += s.length();
|
||||
return expected;
|
||||
}
|
||||
throw new RuntimeException("Expected " + s + " at " + pos);
|
||||
}
|
||||
|
||||
private Object nil() {
|
||||
if (json.startsWith("null", pos)) {
|
||||
pos += 4;
|
||||
return null;
|
||||
}
|
||||
throw new RuntimeException("Expected null at " + pos);
|
||||
}
|
||||
|
||||
private Number number() {
|
||||
int start = pos;
|
||||
if (json.charAt(pos) == '-') pos++;
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
boolean isFloat = false;
|
||||
if (pos < json.length() && json.charAt(pos) == '.') {
|
||||
isFloat = true;
|
||||
pos++;
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
}
|
||||
if (pos < json.length() && (json.charAt(pos) == 'e' || json.charAt(pos) == 'E')) {
|
||||
isFloat = true;
|
||||
pos++;
|
||||
if (pos < json.length() && (json.charAt(pos) == '+' || json.charAt(pos) == '-')) pos++;
|
||||
while (pos < json.length() && Character.isDigit(json.charAt(pos))) pos++;
|
||||
}
|
||||
String s = json.substring(start, pos);
|
||||
if (isFloat) return Double.parseDouble(s);
|
||||
try {
|
||||
return Long.parseLong(s);
|
||||
} catch (NumberFormatException e) {
|
||||
return new BigInteger(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void seek() {
|
||||
while (pos < json.length() && Character.isWhitespace(json.charAt(pos))) pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package swiss.qpq.gajumaru.core.tools;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* GRIDS URL handling, ported from hz_grids.erl.
|
||||
*/
|
||||
public final class Grids {
|
||||
|
||||
private Grids() {}
|
||||
|
||||
public enum Verb {
|
||||
SPEND,
|
||||
TRANSFER,
|
||||
SIGN
|
||||
}
|
||||
|
||||
public enum Context {
|
||||
CHAIN,
|
||||
NODE,
|
||||
HTTP,
|
||||
HTTPS
|
||||
}
|
||||
|
||||
public static final class ParseResult {
|
||||
public final Verb verb;
|
||||
public final Context context;
|
||||
public final String location;
|
||||
public final String recipient;
|
||||
public final BigInteger amount;
|
||||
public final byte[] payload;
|
||||
public final String url; // For SIGN verb, the reconstructed URL
|
||||
|
||||
private ParseResult(Verb verb, Context context, String location, String recipient, BigInteger amount, byte[] payload, String url) {
|
||||
this.verb = verb;
|
||||
this.context = context;
|
||||
this.location = location;
|
||||
this.recipient = recipient;
|
||||
this.amount = amount;
|
||||
this.payload = payload;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public static ParseResult spend(Context context, String location, String recipient, BigInteger amount, byte[] payload) {
|
||||
return new ParseResult(Verb.SPEND, context, location, recipient, amount, payload, null);
|
||||
}
|
||||
|
||||
public static ParseResult transfer(Context context, String location, String recipient, BigInteger amount, byte[] payload) {
|
||||
return new ParseResult(Verb.TRANSFER, context, location, recipient, amount, payload, null);
|
||||
}
|
||||
|
||||
public static ParseResult sign(Context context, String url) {
|
||||
return new ParseResult(Verb.SIGN, context, null, null, null, null, url);
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseResult parse(String gridsUrl) {
|
||||
try {
|
||||
URI uri = new URI(gridsUrl);
|
||||
String scheme = uri.getScheme();
|
||||
String host = uri.getHost();
|
||||
String path = uri.getPath();
|
||||
String query = uri.getQuery();
|
||||
|
||||
if (!"grids".equals(scheme) && !"grid".equals(scheme)) {
|
||||
throw new IllegalArgumentException("Invalid scheme: " + scheme);
|
||||
}
|
||||
|
||||
if (path.startsWith("/1/s/")) {
|
||||
String recipient = path.substring(5);
|
||||
Map<String, String> qargs = parseQuery(query);
|
||||
BigInteger amount = new BigInteger(qargs.getOrDefault("a", "0"));
|
||||
byte[] payload = decodePayload(qargs.getOrDefault("p", ""));
|
||||
return ParseResult.spend(Context.CHAIN, host, recipient, amount, payload);
|
||||
} else if (path.startsWith("/1/t/")) {
|
||||
String recipient = path.substring(5);
|
||||
Map<String, String> qargs = parseQuery(query);
|
||||
BigInteger amount = new BigInteger(qargs.getOrDefault("a", "0"));
|
||||
byte[] payload = decodePayload(qargs.getOrDefault("p", ""));
|
||||
String location = host + (uri.getPort() != -1 ? ":" + uri.getPort() : "");
|
||||
return ParseResult.transfer(Context.NODE, location, recipient, amount, payload);
|
||||
} else if (path.startsWith("/1/d/")) {
|
||||
String subPath = path.substring(5);
|
||||
String httpScheme = "grids".equals(scheme) ? "https" : "http";
|
||||
String httpUrl = httpScheme + "://" + host + (uri.getPort() != -1 ? ":" + uri.getPort() : "") + subPath;
|
||||
if (query != null) httpUrl += "?" + query;
|
||||
return ParseResult.sign("grids".equals(scheme) ? Context.HTTPS : Context.HTTP, httpUrl);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown verb in path: " + path);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse GRIDS URL: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final String REQ_TYPE_MESSAGE = "message";
|
||||
public static final String REQ_TYPE_TX = "tx";
|
||||
public static final String REQ_TYPE_ACK = "ack";
|
||||
|
||||
public static Map<String, Object> makeRequest(String type, Object payload, String publicId, String networkId) {
|
||||
Map<String, Object> req = new HashMap<>();
|
||||
req.put("grids", 1);
|
||||
req.put("chain", "gajumaru");
|
||||
req.put("network_id", networkId);
|
||||
req.put("type", type);
|
||||
req.put("public_id", publicId);
|
||||
req.put("payload", payload);
|
||||
return req;
|
||||
}
|
||||
|
||||
private static Map<String, String> parseQuery(String query) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (query == null || query.isEmpty()) return params;
|
||||
String[] pairs = query.split("&");
|
||||
for (String pair : pairs) {
|
||||
int idx = pair.indexOf("=");
|
||||
if (idx > 0) {
|
||||
params.put(URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8),
|
||||
URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
params.put(URLDecoder.decode(pair, StandardCharsets.UTF_8), "");
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private static byte[] decodePayload(String p) {
|
||||
if (p == null || p.isEmpty()) return new byte[0];
|
||||
// The Erlang side uses uri_string:compose_query which percent-encodes.
|
||||
// URLDecoder.decode should have handled it in parseQuery.
|
||||
// Assuming payload is just string data or hex?
|
||||
// hz_grids.erl says: Payload = list_to_binary(proplists:get_value("p", ArgList, ""))
|
||||
return p.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,14 @@ import java.util.Base64;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import swiss.qpq.gajumaru.core.encoding.Base58;
|
||||
import swiss.qpq.gajumaru.core.encoding.RLP;
|
||||
import swiss.qpq.gajumaru.core.encoding.ApiEncoder;
|
||||
import swiss.qpq.gajumaru.core.encoding.ZJ;
|
||||
import swiss.qpq.gajumaru.core.formatting.GajuFormat;
|
||||
import swiss.qpq.gajumaru.core.crypto.Keccak256;
|
||||
import swiss.qpq.gajumaru.core.crypto.Blake2b;
|
||||
@@ -19,6 +22,7 @@ import swiss.qpq.gajumaru.core.data.SpendTx;
|
||||
import swiss.qpq.gajumaru.core.data.SignedTx;
|
||||
import swiss.qpq.gajumaru.core.encoding.Mnemonic;
|
||||
import swiss.qpq.gajumaru.core.tools.CryptoUtils;
|
||||
import swiss.qpq.gajumaru.core.tools.Grids;
|
||||
|
||||
public class Testinator {
|
||||
public static void main(String[] args) {
|
||||
@@ -45,6 +49,9 @@ public class Testinator {
|
||||
case "spend_tx" -> { System.out.print(spend_tx(args[1])); }
|
||||
case "signed_tx" -> { System.out.print(signed_tx(args[1])); }
|
||||
case "mnemonic" -> { System.out.print(mnemonic(args[1])); }
|
||||
case "zj_encode" -> { System.out.print(zj_encode(args[1])); }
|
||||
case "zj_decode" -> { System.out.print(zj_decode(args[1])); }
|
||||
case "grids_parse" -> { System.out.print(grids_parse(args[1])); }
|
||||
case "fe_parity" -> { System.out.print(fe_parity(args[1])); }
|
||||
case "reduce_parity" -> { System.out.print(reduce_parity(args[1])); }
|
||||
case "smb_parity" -> { System.out.print(smb_parity(args[1])); }
|
||||
@@ -493,4 +500,56 @@ public class Testinator {
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String zj_encode(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "zj_encode.test");
|
||||
Path resPath = Path.of(workingPath, "zj_encode.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) continue;
|
||||
// For testing, we decode then re-encode to check consistency
|
||||
Object val = ZJ.decode(line);
|
||||
results.add(ZJ.encode(val));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String zj_decode(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "zj_decode.test");
|
||||
Path resPath = Path.of(workingPath, "zj_decode.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) continue;
|
||||
Object val = ZJ.decode(line);
|
||||
// We encode it back to string for comparison or output format
|
||||
results.add(ZJ.encode(val));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
|
||||
private static String grids_parse(String workingPath) throws IOException {
|
||||
Path testPath = Path.of(workingPath, "grids_parse.test");
|
||||
Path resPath = Path.of(workingPath, "grids_parse.java.txt");
|
||||
List<String> lines = Files.readAllLines(testPath);
|
||||
List<String> results = new ArrayList<>();
|
||||
for (String url : lines) {
|
||||
if (url.trim().isEmpty()) continue;
|
||||
Grids.ParseResult res = Grids.parse(url);
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("verb", res.verb.name());
|
||||
map.put("context", res.context.name());
|
||||
map.put("location", res.location);
|
||||
map.put("recipient", res.recipient);
|
||||
map.put("amount", res.amount != null ? res.amount.toString() : null);
|
||||
map.put("payload", res.payload != null ? CryptoUtils.binToHex(res.payload) : null);
|
||||
map.put("url", res.url);
|
||||
results.add(ZJ.encode(map));
|
||||
}
|
||||
Files.write(resPath, results);
|
||||
return resPath.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user