diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java b/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java index 9182f4b..e469017 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/Overgram.java @@ -1,14 +1,23 @@ package io.github.lonamiwebs.overgram; +import io.github.lonamiwebs.overgram.crypto.Authenticator; +import io.github.lonamiwebs.overgram.network.MTProtoPlainSender; import io.github.lonamiwebs.overgram.network.connection.Connection; import io.github.lonamiwebs.overgram.network.connection.TcpFull; +import io.github.lonamiwebs.overgram.utils.BinaryWriter; import java.io.IOException; public class Overgram { - public static void main(final String... args) throws IOException { + public static void main(final String... args) throws IOException, ClassNotFoundException { final Connection connection = new TcpFull(); - connection.connect("149.154.167.91", 443); - connection.disconnect(); + try { + connection.connect("149.154.167.91", 443); + + final MTProtoPlainSender sender = new MTProtoPlainSender(connection); + Authenticator.doAuthentication(sender); + } finally { + connection.disconnect(); + } } } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/Authenticator.java b/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/Authenticator.java new file mode 100644 index 0000000..ac550f0 --- /dev/null +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/crypto/Authenticator.java @@ -0,0 +1,22 @@ +package io.github.lonamiwebs.overgram.crypto; + +import io.github.lonamiwebs.overgram.network.MTProtoPlainSender; +import io.github.lonamiwebs.overgram.tl.Functions; +import io.github.lonamiwebs.overgram.tl.TLObject; +import io.github.lonamiwebs.overgram.tl.Types; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.SecureRandom; + +public class Authenticator { + + public static void doAuthentication(final MTProtoPlainSender sender) throws IOException, ClassNotFoundException { + final byte[] nonceBytes = new byte[16]; + new SecureRandom().nextBytes(nonceBytes); + final BigInteger nonce = new BigInteger(nonceBytes); + + final TLObject result = sender.send(new Functions.ReqPqMulti().nonce(nonce)); + assert result instanceof Types.ResPQ; + } +} diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoPlainSender.java b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoPlainSender.java new file mode 100644 index 0000000..04646ef --- /dev/null +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoPlainSender.java @@ -0,0 +1,57 @@ +package io.github.lonamiwebs.overgram.network; + +import io.github.lonamiwebs.overgram.network.connection.Connection; +import io.github.lonamiwebs.overgram.tl.TLObject; +import io.github.lonamiwebs.overgram.tl.TLRequest; +import io.github.lonamiwebs.overgram.utils.BinaryReader; +import io.github.lonamiwebs.overgram.utils.BinaryWriter; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class MTProtoPlainSender { + private final Connection connection; + private final MTProtoState state; + + public MTProtoPlainSender(final Connection connection) { + this.connection = connection; + this.state = new MTProtoState(); + } + + public TLObject send(final TLRequest request) throws IOException, ClassNotFoundException { + final long msgId = state.getNewMsgId(); + + final BinaryWriter writer = new BinaryWriter(); + request.serialize(writer); + final byte[] requestBytes = writer.toBytes(); + writer.clear(); + + writer.write(0L); + writer.write(msgId); + writer.write(requestBytes.length); + writer.writeRaw(requestBytes); + connection.send(writer.toBytes()); + + final byte[] body = connection.recv(); + if (body[0] == (byte) 0x6c + && body[1] == (byte) 0xfe + && body[2] == (byte) 0xff + && body[3] == (byte) 0xff) { + // -404 as little endian, broken authorization + throw new RuntimeException(); + } + + final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body).order(ByteOrder.LITTLE_ENDIAN)); + final long authKeyId = reader.readLong(); + assert authKeyId == 0; + + final long serverMsgId = reader.readLong(); + assert serverMsgId != 0; + + final int length = reader.readInt(); + assert length > 0; + + return reader.readTl(); + } +} diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java new file mode 100644 index 0000000..76fa68b --- /dev/null +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/network/MTProtoState.java @@ -0,0 +1,64 @@ +package io.github.lonamiwebs.overgram.network; + +import io.github.lonamiwebs.overgram.tl.TLObject; + +import java.security.SecureRandom; + +public class MTProtoState { + + private final long id; + private long timeOffset; + private int sequence; + private long lastMsgId; + + public MTProtoState() { + id = new SecureRandom().nextLong(); + timeOffset = 0; + sequence = 0; + lastMsgId = 0; + + // TODO auth_key, salt + } + + public Object createMessage(final TLObject object, final long afterId) { + throw new UnsupportedOperationException(); + } + + public static Object calcKey(final Object authKey, final byte[] msgKey, final boolean client) { + throw new UnsupportedOperationException(); + } + + public byte[] packMessage(final Object message) { + throw new UnsupportedOperationException(); + } + + public Object unpackMessage(final byte[] body) { + throw new UnsupportedOperationException(); + } + + public long getNewMsgId() { + final long now = System.currentTimeMillis(); + long newMsgId = (((now / 1000) + timeOffset) << 32) | ((now % 1000) << 2); + if (lastMsgId >= newMsgId) { + newMsgId = lastMsgId + 4; + } + + lastMsgId = newMsgId; + return newMsgId; + } + + public void updateTimeOffset(long correctMsgId) { + final long now = System.currentTimeMillis() / 1000L; + final long correct = correctMsgId >> 32; + timeOffset = correct - now; + lastMsgId = 0; + } + + public int getSeqNo(final boolean contentRelated) { + if (contentRelated) { + return 1 + 2 * sequence++; + } else { + return 2 * sequence; + } + } +} diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/network/connection/TcpFull.java b/lib/src/main/java/io/github/lonamiwebs/overgram/network/connection/TcpFull.java index 7b52752..0ca40cd 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/network/connection/TcpFull.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/network/connection/TcpFull.java @@ -31,8 +31,7 @@ public class TcpFull extends Connection { public void send(final byte[] data) throws IOException { final int length = data.length + 12; - final ByteBuffer buffer = ByteBuffer.allocate(length); - buffer.order(ByteOrder.LITTLE_ENDIAN); + final ByteBuffer buffer = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(length); buffer.putInt(counter); @@ -48,7 +47,7 @@ public class TcpFull extends Connection { @Override public byte[] recv() throws IOException { - final ByteBuffer buffer = ByteBuffer.wrap(client.read(8)); + final ByteBuffer buffer = ByteBuffer.wrap(client.read(8)).order(ByteOrder.LITTLE_ENDIAN); final int length = buffer.getInt(); final int seq = buffer.getInt(); final byte[] body = client.read(length - 12); diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLObject.java b/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLObject.java index f7eaa30..6640ac8 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLObject.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/tl/TLObject.java @@ -6,5 +6,5 @@ import io.github.lonamiwebs.overgram.utils.BinaryWriter; public abstract class TLObject { public abstract void serialize(final BinaryWriter writer); - public abstract void deserialize(final BinaryReader reader); + public abstract void deserialize(final BinaryReader reader) throws ClassNotFoundException; } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java index 9c9a895..749c064 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryReader.java @@ -1,5 +1,9 @@ package io.github.lonamiwebs.overgram.utils; +import io.github.lonamiwebs.overgram.tl.TLObject; +import io.github.lonamiwebs.overgram.tl.Types; + +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -40,6 +44,18 @@ public class BinaryReader { return result; } + public BigInteger readInt128() { + final byte[] bytes = new byte[16]; + buffer.get(bytes); + return new BigInteger(bytes); + } + + public BigInteger readInt256() { + final byte[] bytes = new byte[32]; + buffer.get(bytes); + return new BigInteger(bytes); + } + public double readDouble() { return buffer.getDouble(); } @@ -63,7 +79,9 @@ public class BinaryReader { final byte[] data = new byte[length]; buffer.get(data); - buffer.position(buffer.position() + padding); + if (padding > 0) { + buffer.position(buffer.position() + 4 - padding); + } return data; } @@ -91,14 +109,16 @@ public class BinaryReader { return result; } - public Object readTl() { - return null; + public TLObject readTl() throws ClassNotFoundException { + final TLObject object = Types.getFromId(readInt()); + object.deserialize(this); + return object; } - public List readTlList() { + public List readTlList() throws ClassNotFoundException { buffer.position(buffer.position() + 4); // vector code final int size = buffer.getInt(); - final List result = new ArrayList<>(size); + final List result = new ArrayList<>(size); for (int i = 0; i < size; ++i) { result.add(readTl()); } diff --git a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java index 3f542be..0e92c3f 100644 --- a/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java +++ b/lib/src/main/java/io/github/lonamiwebs/overgram/utils/BinaryWriter.java @@ -3,55 +3,100 @@ package io.github.lonamiwebs.overgram.utils; import io.github.lonamiwebs.overgram.tl.TLObject; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; public class BinaryWriter { - private final ByteBuffer buffer; + + private static final ByteOrder ORDER = ByteOrder.LITTLE_ENDIAN; + + private final ByteArrayOutputStream buffer; public BinaryWriter() { - buffer = ByteBuffer.allocate(128); - buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer = new ByteArrayOutputStream(128); } public void write(final int value) { - buffer.putInt(value); + try { + buffer.write(ByteBuffer.allocate(4).order(ORDER).putInt(value).array()); + } catch (IOException ignored) { + throw new RuntimeException(); + } } public void write(final long value) { - buffer.putLong(value); - } - - public void write(final double value) { - buffer.putDouble(value); - } - - public void write(final boolean value) { - buffer.putInt(value ? 0x997275b5 : 0xbc799737); - } - - public void write(final byte[] bytes) { - int padding; - if (bytes.length < 0xfe) { - padding = (bytes.length + 1) % 4; - buffer.put((byte) bytes.length); - buffer.put(bytes); - } else { - padding = bytes.length % 4; - buffer.putInt(bytes.length << 8 | 0xfe); - buffer.put(bytes); + try { + buffer.write(ByteBuffer.allocate(8).order(ORDER).putLong(value).array()); + } catch (IOException ignored) { + throw new RuntimeException(); } - if (padding != 0) { - for (padding = 4 - padding; padding-- != 0; ) { - buffer.put((byte) 0); + } + + public void write(final BigInteger value) { + final byte[] bytes = value.toByteArray(); + + // It's really unlike an int256 has only 16 bytes so we're good + final int size = bytes.length > 16 ? 32 : 16; + if (ORDER == ByteOrder.BIG_ENDIAN) { + for (int i = 0; i < size; ++i) { + buffer.write(i < bytes.length ? bytes[i] : 0); + } + } else { + for (int i = size; i-- != 0; ) { + buffer.write(i < bytes.length ? bytes[i] : 0); } } } + public void write(final double value) { + try { + buffer.write(ByteBuffer.allocate(8).order(ORDER).putDouble(value).array()); + } catch (IOException ignored) { + throw new RuntimeException(); + } + } + + public void write(final boolean value) { + write(value ? 0x997275b5 : 0xbc799737); + } + + public void write(final byte[] bytes) { + try { + int padding; + if (bytes.length < 0xfe) { + padding = (bytes.length + 1) % 4; + buffer.write(bytes.length); + buffer.write(bytes); + } else { + padding = bytes.length % 4; + write(bytes.length << 8 | 0xfe); + buffer.write(bytes); + } + if (padding != 0) { + for (padding = 4 - padding; padding-- != 0; ) { + buffer.write(0); + } + } + } catch (IOException ignored) { + throw new RuntimeException(); + } + } + + public void writeRaw(final byte[] bytes) { + try { + buffer.write(bytes); + } catch (IOException ignored) { + } + } + public void write(final String string) { - buffer.put(StandardCharsets.UTF_8.encode(string)); + write(StandardCharsets.UTF_8.encode(string).array()); } // TODO Handle boxed vs unboxed types (and vector<>) @@ -88,4 +133,21 @@ public class BinaryWriter { throw new UnsupportedOperationException(); } } + + public void clear() { + buffer.reset(); + } + + public int size() { + return buffer.size(); + } + + public byte[] toBytes() { + return buffer.toByteArray(); + } + + @Override + public String toString() { + return Arrays.toString(toBytes()); + } }