diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index 023e679..cd3bd65 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -27,11 +27,11 @@ jobs: export REVISION=${{ github.run_number }} echo "REVISION=$REVISION" >> $GITHUB_ENV - - name: Set up JDK 15 + - name: Set up JDK 17 if: github.ref == 'refs/heads/master' uses: actions/setup-java@v1 with: - java-version: 15 + java-version: 17 server-id: mchv-release-distribution server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD diff --git a/pom.xml b/pom.xml index 4b342ca..dae3925 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ UTF-8 0-SNAPSHOT - 3.1.10 + 3.1.12 33 @@ -159,6 +159,11 @@ jackson-dataformat-yaml 2.13.2 + + jakarta.xml.bind + jakarta.xml.bind-api + 4.0.0 + net.minecrell terminalconsoleappender diff --git a/src/main/java/it/tdlight/reactiveapi/AtomixReactiveApiMultiClient.java b/src/main/java/it/tdlight/reactiveapi/AtomixReactiveApiMultiClient.java index 52d4d93..b78f069 100644 --- a/src/main/java/it/tdlight/reactiveapi/AtomixReactiveApiMultiClient.java +++ b/src/main/java/it/tdlight/reactiveapi/AtomixReactiveApiMultiClient.java @@ -6,7 +6,7 @@ import io.atomix.cluster.messaging.ClusterEventService; import io.atomix.cluster.messaging.MessagingException; import it.tdlight.jni.TdApi; import it.tdlight.reactiveapi.Event.ClientBoundEvent; -import it.tdlight.reactiveapi.Event.Request; +import it.tdlight.reactiveapi.Event.OnRequest.Request; import java.net.ConnectException; import java.time.Duration; import java.time.Instant; diff --git a/src/main/java/it/tdlight/reactiveapi/BaseAtomixReactiveApiClient.java b/src/main/java/it/tdlight/reactiveapi/BaseAtomixReactiveApiClient.java index 57efdb8..6543f65 100644 --- a/src/main/java/it/tdlight/reactiveapi/BaseAtomixReactiveApiClient.java +++ b/src/main/java/it/tdlight/reactiveapi/BaseAtomixReactiveApiClient.java @@ -1,30 +1,39 @@ package it.tdlight.reactiveapi; +import static it.tdlight.reactiveapi.Event.SERIAL_VERSION; + import io.atomix.cluster.messaging.ClusterEventService; import io.atomix.cluster.messaging.MessagingException; import io.atomix.core.Atomix; +import it.tdlight.common.utils.LibraryVersion; import it.tdlight.jni.TdApi; import it.tdlight.reactiveapi.Event.ClientBoundEvent; +import it.tdlight.reactiveapi.Event.Ignored; import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested; import it.tdlight.reactiveapi.Event.OnOtherDeviceLoginRequested; import it.tdlight.reactiveapi.Event.OnPasswordRequested; +import it.tdlight.reactiveapi.Event.OnRequest.Request; import it.tdlight.reactiveapi.Event.OnUpdateData; import it.tdlight.reactiveapi.Event.OnUpdateError; import it.tdlight.reactiveapi.Event.OnUserLoginCodeRequested; -import it.tdlight.reactiveapi.Event.Request; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.net.ConnectException; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletionException; import java.util.concurrent.TimeoutException; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.SerializationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.Disposable; @@ -119,7 +128,12 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo if (bytes == null || bytes.length == 0) { return null; } - return TdApi.Deserializer.deserialize(new DataInputStream(new ByteArrayInputStream(bytes))); + var dis = new DataInputStream(new ByteArrayInputStream(bytes)); + var serialVersion = dis.readInt(); + if (serialVersion != SERIAL_VERSION) { + return new TdApi.Error(400, "Conflicting protocol version"); + } + return TdApi.Deserializer.deserialize(dis); } catch (IOException ex) { throw new SerializationException(ex); } @@ -129,6 +143,7 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo try (var byteArrayOutputStream = new ByteArrayOutputStream()) { try (var dataOutputStream = new DataOutputStream(byteArrayOutputStream)) { dataOutputStream.writeLong(request.liveId()); + dataOutputStream.writeInt(SERIAL_VERSION); dataOutputStream.writeLong(request.timeout().toEpochMilli()); request.request().serialize(dataOutputStream); dataOutputStream.flush(); @@ -164,9 +179,13 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo } } - static ClientBoundEvent deserializeEvent(DataInputStream is) throws IOException { + static @NotNull ClientBoundEvent deserializeEvent(DataInputStream is) throws IOException { var liveId = is.readLong(); var userId = is.readLong(); + var dataVersion = is.readInt(); + if (dataVersion != SERIAL_VERSION) { + return new Ignored(liveId, userId); + } return switch (is.readByte()) { case 0x01 -> new OnUpdateData(liveId, userId, (TdApi.Update) TdApi.Deserializer.deserialize(is)); case 0x02 -> new OnUpdateError(liveId, userId, (TdApi.Error) TdApi.Deserializer.deserialize(is)); diff --git a/src/main/java/it/tdlight/reactiveapi/Event.java b/src/main/java/it/tdlight/reactiveapi/Event.java index f15c6b2..385aec0 100644 --- a/src/main/java/it/tdlight/reactiveapi/Event.java +++ b/src/main/java/it/tdlight/reactiveapi/Event.java @@ -1,17 +1,25 @@ package it.tdlight.reactiveapi; +import it.tdlight.common.utils.LibraryVersion; import it.tdlight.jni.TdApi; import it.tdlight.reactiveapi.Event.ClientBoundEvent; +import it.tdlight.reactiveapi.Event.OnRequest.InvalidRequest; +import it.tdlight.reactiveapi.Event.OnRequest.Request; import it.tdlight.reactiveapi.Event.ServerBoundEvent; import java.io.DataInput; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.time.Instant; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.SerializationException; +import org.jetbrains.annotations.Nullable; /** * Any event received from a session */ -public sealed interface Event permits ClientBoundEvent, ServerBoundEvent { +public sealed interface Event { + + int SERIAL_VERSION = ArrayUtils.hashCode(LibraryVersion.VERSION.getBytes(StandardCharsets.US_ASCII)); /** * @@ -22,8 +30,7 @@ public sealed interface Event permits ClientBoundEvent, ServerBoundEvent { /** * Event received after choosing the user id of the session */ - sealed interface ClientBoundEvent extends Event permits OnLoginCodeRequested, OnOtherDeviceLoginRequested, - OnPasswordRequested, OnUpdate { + sealed interface ClientBoundEvent extends Event { /** * @@ -32,13 +39,12 @@ public sealed interface Event permits ClientBoundEvent, ServerBoundEvent { long userId(); } - sealed interface ServerBoundEvent extends Event permits Request {} + sealed interface ServerBoundEvent extends Event {} /** * TDLib is asking for an authorization code */ - sealed interface OnLoginCodeRequested extends ClientBoundEvent - permits OnBotLoginCodeRequested, OnUserLoginCodeRequested {} + sealed interface OnLoginCodeRequested extends ClientBoundEvent {} record OnUserLoginCodeRequested(long liveId, long userId, long phoneNumber) implements OnLoginCodeRequested {} @@ -49,21 +55,32 @@ public sealed interface Event permits ClientBoundEvent, ServerBoundEvent { record OnPasswordRequested(long liveId, long userId, String passwordHint, boolean hasRecoveryEmail, String recoveryEmailPattern) implements ClientBoundEvent {} + record Ignored(long liveId, long userId) implements ClientBoundEvent {} + /** * Event received from TDLib */ - sealed interface OnUpdate extends ClientBoundEvent permits OnUpdateData, OnUpdateError {} + sealed interface OnUpdate extends ClientBoundEvent {} record OnUpdateData(long liveId, long userId, TdApi.Update update) implements OnUpdate {} record OnUpdateError(long liveId, long userId, TdApi.Error error) implements OnUpdate {} - record Request(long liveId, TdApi.Function request, Instant timeout) implements - ServerBoundEvent { + sealed interface OnRequest extends ServerBoundEvent { - public static Request deserialize(DataInput dataInput) { + record Request(long liveId, TdApi.Function request, Instant timeout) + implements OnRequest {} + + record InvalidRequest(long liveId) implements OnRequest {} + + static Event.OnRequest deserialize(DataInput dataInput) { try { var liveId = dataInput.readLong(); + var dataVersion = dataInput.readInt(); + if (dataVersion != SERIAL_VERSION) { + // Deprecated request + return new InvalidRequest<>(liveId); + } long millis = dataInput.readLong(); var timeout = Instant.ofEpochMilli(millis); @SuppressWarnings("unchecked") diff --git a/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java b/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java index 12d0f30..e58ed45 100644 --- a/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java +++ b/src/main/java/it/tdlight/reactiveapi/ReactiveApiPublisher.java @@ -2,6 +2,7 @@ package it.tdlight.reactiveapi; import static it.tdlight.reactiveapi.AuthPhase.LOGGED_IN; import static it.tdlight.reactiveapi.AuthPhase.LOGGED_OUT; +import static it.tdlight.reactiveapi.Event.SERIAL_VERSION; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.completedFuture; @@ -30,10 +31,12 @@ import it.tdlight.reactiveapi.Event.ClientBoundEvent; import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested; import it.tdlight.reactiveapi.Event.OnOtherDeviceLoginRequested; import it.tdlight.reactiveapi.Event.OnPasswordRequested; +import it.tdlight.reactiveapi.Event.OnRequest; +import it.tdlight.reactiveapi.Event.OnRequest.InvalidRequest; +import it.tdlight.reactiveapi.Event.OnRequest.Request; import it.tdlight.reactiveapi.Event.OnUpdateData; import it.tdlight.reactiveapi.Event.OnUpdateError; import it.tdlight.reactiveapi.Event.OnUserLoginCodeRequested; -import it.tdlight.reactiveapi.Event.Request; import it.tdlight.reactiveapi.ResultingEvent.ClientBoundResultingEvent; import it.tdlight.reactiveapi.ResultingEvent.ClusterBoundResultingEvent; import it.tdlight.reactiveapi.ResultingEvent.ResultingEventPublisherClosed; @@ -439,6 +442,7 @@ public abstract class ReactiveApiPublisher { throws IOException { dataOutputStream.writeLong(clientBoundEvent.liveId()); dataOutputStream.writeLong(clientBoundEvent.userId()); + dataOutputStream.writeInt(SERIAL_VERSION); if (clientBoundEvent instanceof OnUpdateData onUpdateData) { dataOutputStream.writeByte(0x1); onUpdateData.update().serialize(dataOutputStream); @@ -485,6 +489,7 @@ public abstract class ReactiveApiPublisher { var object = response.getObject(); try (var byteArrayOutputStream = new ByteArrayOutputStream()) { try (var dataOutputStream = new DataOutputStream(byteArrayOutputStream)) { + dataOutputStream.writeInt(SERIAL_VERSION); //dataOutputStream.writeLong(id); object.serialize(dataOutputStream); return byteArrayOutputStream.toByteArray(); @@ -494,7 +499,11 @@ public abstract class ReactiveApiPublisher { } } - private CompletableFuture handleRequest(Request requestObj) { + private CompletableFuture handleRequest(OnRequest onRequestObj) { + if (onRequestObj instanceof OnRequest.InvalidRequest invalidRequest) { + return completedFuture(new Response(invalidRequest.liveId(), new TdApi.Error(400, "Conflicting protocol version"))); + } + var requestObj = (Request) onRequestObj; if (liveId != requestObj.liveId()) { LOG.error("Received a request for another session!"); return completedFuture(new Response(liveId, @@ -522,8 +531,8 @@ public abstract class ReactiveApiPublisher { } } - private static Request deserializeRequest(byte[] bytes) { - return Request.deserialize(new DataInputStream(new ByteArrayInputStream(bytes))); + private static OnRequest deserializeRequest(byte[] bytes) { + return OnRequest.deserialize(new DataInputStream(new ByteArrayInputStream(bytes))); } @Override diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 6574e27..3d9c76e 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -27,4 +27,6 @@ module tdlib.reactive.api { requires it.unimi.dsi.fastutil; requires net.minecrell.terminalconsole; requires org.jline.reader; + requires jdk.unsupported; + requires jakarta.xml.bind; } \ No newline at end of file