package peergos.shared.io.ipfs.cid;
import peergos.shared.io.ipfs.multibase.*;
import peergos.shared.io.ipfs.multihash.*;
import peergos.shared.util.*;
import java.io.*;
import java.util.*;
public class Cid extends Multihash {
public static final class CidEncodingException extends RuntimeException {
public CidEncodingException(String message) {
super(message);
}
}
public enum Codec {
Raw(0x55),
DagProtobuf(0x70),
DagCbor(0x71),
EthereumBlock(0x90),
EthereumTx(0x91),
BitcoinBlock(0xb0),
BitcoinTx(0xb1),
ZcashBlock(0xc0),
ZcashTx(0xc1);
public long type;
Codec(long type) {
this.type = type;
}
private static Map<Long, Codec> lookup = new TreeMap<>();
static {
for (Codec c: Codec.values())
lookup.put(c.type, c);
}
public static Codec lookup(long c) {
if (!lookup.containsKey(c))
throw new IllegalStateException("Unknown Codec type: " + c);
return lookup.get(c);
}
}
public final long version;
public final Codec codec;
public Cid(long version, Codec codec, Multihash hash) {
super(hash);
this.version = version;
this.codec = codec;
}
public Cid(long version, Codec codec, Multihash.Type type, byte[] hash) {
super(type, hash);
this.version = version;
this.codec = codec;
}
public Cid(Multihash h) {
this(0, Codec.DagProtobuf, h);
}
private byte[] toBytesV0() {
return super.toBytes();
}
private static final int MAX_VARINT_LEN64 = 10;
private byte[] toBytesV1() {
byte[] hashBytes = super.toBytes();
byte[] res = new byte[2 * MAX_VARINT_LEN64 + hashBytes.length];
int index = putUvarint(res, 0, version);
index = putUvarint(res, index, codec.type);
System.arraycopy(hashBytes, 0, res, index, hashBytes.length);
return Arrays.copyOfRange(res, 0, index + hashBytes.length);
}
@Override
public byte[] toBytes() {
if (version == 0)
return toBytesV0();
else if (version == 1)
return toBytesV1();
throw new IllegalStateException("Unknown cid version: " + version);
}
@Override
public String toString() {
if (version == 0) {
return super.toString();
} else if (version == 1) {
return Multibase.encode(Multibase.Base.Base58BTC, toBytesV1());
}
throw new IllegalStateException("Unknown Cid version: " + version);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (! (o instanceof Multihash)) return false;
if (!super.equals(o)) return false;
if (o instanceof Cid) {
Cid cid = (Cid) o;
if (version != cid.version) return false;
return codec == cid.codec;
}
// o must be a Multihash
return version == 0 && super.equals(o);
}
@Override
public int hashCode() {
int result = super.hashCode();
if (version == 0)
return result;
result = 31 * result + (int) (version ^ (version >>> 32));
result = 31 * result + (codec != null ? codec.hashCode() : 0);
return result;
}
public static Cid buildCidV0(Multihash h) {
return new Cid(h);
}
public static Cid buildCidV1(Codec c, Multihash.Type type, byte[] hash) {
return new Cid(1, c, type, hash);
}
public static Cid decode(String v) {
if (v.length() < 2)
throw new IllegalStateException("Cid too short!");
// support legacy format
if (v.length() == 46 && v.startsWith("Qm"))
return buildCidV0(Multihash.fromBase58(v));
byte[] data = Multibase.decode(v);
return cast(data);
}
public static Cid cast(byte[] data) {
if (data.length == 34 && data[0] == 18 && data[1] == 32)
return buildCidV0(new Multihash(data));
InputStream in = new ByteArrayInputStream(data);
try {
long version = readVarint(in);
if (version != 0 && version != 1)
throw new CidEncodingException("Invalid Cif version number: " + version);
long codec = readVarint(in);
if (version != 0 && version != 1)
throw new CidEncodingException("Invalid Cif version number: " + version);
Multihash hash = Multihash.deserialize(new DataInputStream(in));
return new Cid(version, Codec.lookup(codec), hash);
} catch (Exception e) {
throw new CidEncodingException("Invalid cid bytes: " + ArrayOps.bytesToHex(data));
}
}
private 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 int putUvarint(byte[] buf, int index, long x) {
while (x >= 0x80) {
buf[index] = (byte)(x | 0x80);
x >>= 7;
index++;
}
buf[index] = (byte)x;
return index + 1;
}
}