package peergos.shared.io.ipfs.multiaddr; import peergos.shared.io.ipfs.multihash.*; import peergos.shared.io.ipfs.cid.*; import java.io.*; import java.net.*; import java.util.*; public class Protocol { public static int LENGTH_PREFIXED_VAR_SIZE = -1; enum Type { IP4(4, 32, "ip4"), TCP(6, 16, "tcp"), UDP(17, 16, "udp"), DCCP(33, 16, "dccp"), IP6(41, 128, "ip6"), SCTP(132, 16, "sctp"), UTP(301, 0, "utp"), UDT(302, 0, "udt"), IPFS(421, LENGTH_PREFIXED_VAR_SIZE, "ipfs"), HTTPS(443, 0, "https"), HTTP(480, 0, "http"), ONION(444, 80, "onion"); public final int code, size; public final String name; private final byte[] encoded; Type(int code, int size, String name) { this.code = code; this.size = size; this.name = name; this.encoded = encode(code); } static byte[] encode(int code) { byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(code)+6)/7]; putUvarint(varint, code); return varint; } } public final Type type; public Protocol(Type type) { this.type = type; } public void appendCode(OutputStream out) throws IOException { out.write(type.encoded); } public int size() { return type.size; } public String name() { return type.name; } public int code() { return type.code; } @Override public String toString() { return name(); } public byte[] addressToBytes(String addr) { try { switch (type) { case IP4: return Inet4Address.getByName(addr).getAddress(); case IP6: return Inet6Address.getByName(addr).getAddress(); case TCP: case UDP: case DCCP: case SCTP: int x = Integer.parseInt(addr); if (x > 65535) throw new IllegalStateException("Failed to parse "+type.name+" address "+addr + " (> 65535"); return new byte[]{(byte)(x >>8), (byte)x}; case IPFS: Multihash hash = Multihash.fromBase58(addr); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] hashBytes = hash.toBytes(); byte[] varint = new byte[(32 - Integer.numberOfLeadingZeros(hashBytes.length)+6)/7]; putUvarint(varint, hashBytes.length); bout.write(varint); bout.write(hashBytes); return bout.toByteArray(); case ONION: String[] split = addr.split(":"); if (split.length != 2) throw new IllegalStateException("Onion address needs a port: "+addr); // onion address without the ".onion" substring if (split[0].length() != 16) throw new IllegalStateException("failed to parse "+name()+" addr: "+addr+" not a Tor onion address."); byte[] onionHostBytes = Base32.decode(split[0].toUpperCase()); int port = Integer.parseInt(split[1]); if (port > 65535) throw new IllegalStateException("Port is > 65535: "+port); if (port < 1) throw new IllegalStateException("Port is < 1: "+port); ByteArrayOutputStream b = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(b); dout.write(onionHostBytes); dout.writeShort(port); dout.flush(); return b.toByteArray(); } } catch (IOException e) { throw new RuntimeException(e); } throw new IllegalStateException("Failed to parse address: "+addr); } public String readAddress(InputStream in) throws IOException { int sizeForAddress = sizeForAddress(in); byte[] buf; switch (type) { case IP4: buf = new byte[sizeForAddress]; in.read(buf); return Inet4Address.getByAddress(buf).toString().substring(1); case IP6: buf = new byte[sizeForAddress]; in.read(buf); return Inet6Address.getByAddress(buf).toString().substring(1); case TCP: case UDP: case DCCP: case SCTP: return Integer.toString((in.read() << 8) | (in.read())); case IPFS: buf = new byte[sizeForAddress]; in.read(buf); return Cid.cast(buf).toString(); case ONION: byte[] host = new byte[10]; in.read(host); String port = Integer.toString((in.read() << 8) | (in.read())); return Base32.encode(host)+":"+port; } throw new IllegalStateException("Unimplemented protocl type: "+type.name); } public int sizeForAddress(InputStream in) throws IOException { if (type.size > 0) return type.size/8; if (type.size == 0) return 0; return (int)readVarint(in); } static int putUvarint(byte[] buf, long x) { int i = 0; while (x >= 0x80) { buf[i] = (byte)(x | 0x80); x >>= 7; i++; } buf[i] = (byte)x; return i + 1; } static long readVarint(InputStream in) throws IOException { long x = 0; int s=0; for (int i=0; i < 10; i++) { int b = in.read(); if (b == -1) throw new EOFException(); if (b < 0x80) { if (i > 9 || i == 9 && b > 1) { throw new IllegalStateException("Overflow reading varint" +(-(i + 1))); } return x | (((long)b) << s); } x |= ((long)b & 0x7f) << s; s += 7; } throw new IllegalStateException("Varint too long!"); } private static Map<String, Protocol> byName = new HashMap<>(); private static Map<Integer, Protocol> byCode = new HashMap<>(); static { for (Protocol.Type t: Protocol.Type.values()) { Protocol p = new Protocol(t); byName.put(p.name(), p); byCode.put(p.code(), p); } } public static Protocol get(String name) { if (byName.containsKey(name)) return byName.get(name); throw new IllegalStateException("No protocol with name: "+name); } public static Protocol get(int code) { if (byCode.containsKey(code)) return byCode.get(code); throw new IllegalStateException("No protocol with code: "+code); } }