All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.hekate.messaging.internal.MessagingProtocolCodec Maven / Gradle / Ivy

/*
 * Copyright 2022 The Hekate Project
 *
 * The Hekate Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.hekate.messaging.internal;

import io.hekate.cluster.ClusterAddress;
import io.hekate.cluster.ClusterNodeId;
import io.hekate.codec.Codec;
import io.hekate.codec.DataReader;
import io.hekate.codec.DataWriter;
import io.hekate.messaging.MessageMetaData;
import io.hekate.messaging.MessagingChannelId;
import io.hekate.messaging.internal.MessagingProtocol.AffinityNotification;
import io.hekate.messaging.internal.MessagingProtocol.AffinityRequest;
import io.hekate.messaging.internal.MessagingProtocol.AffinitySubscribeRequest;
import io.hekate.messaging.internal.MessagingProtocol.AffinityVoidRequest;
import io.hekate.messaging.internal.MessagingProtocol.Connect;
import io.hekate.messaging.internal.MessagingProtocol.ErrorResponse;
import io.hekate.messaging.internal.MessagingProtocol.FinalResponse;
import io.hekate.messaging.internal.MessagingProtocol.Notification;
import io.hekate.messaging.internal.MessagingProtocol.Request;
import io.hekate.messaging.internal.MessagingProtocol.RequestBase;
import io.hekate.messaging.internal.MessagingProtocol.ResponseChunk;
import io.hekate.messaging.internal.MessagingProtocol.SubscribeRequest;
import io.hekate.messaging.internal.MessagingProtocol.VoidRequest;
import io.hekate.messaging.internal.MessagingProtocol.VoidResponse;
import io.hekate.network.NetworkMessage;
import io.hekate.util.format.ToString;
import java.io.EOFException;
import java.io.IOException;

import static io.hekate.codec.CodecUtils.readClusterAddress;
import static io.hekate.codec.CodecUtils.readNodeId;
import static io.hekate.codec.CodecUtils.writeClusterAddress;
import static io.hekate.codec.CodecUtils.writeNodeId;

class MessagingProtocolCodec implements Codec {
    private static final MessagingProtocol.Type[] TYPES_CACHE = MessagingProtocol.Type.values();

    private static final int FLAG_BYTES = 1;

    private static final int MASK_TYPE = 0b0000_1111;

    private static final int MASK_RETRANSMIT = 0b1000_0000;

    private static final int MASK_HAS_TIMEOUT = 0b0100_0000;

    private static final int MASK_HAS_METADATA = 0b0010_0000;

    private static final NetworkMessage.Preview TYPE_PREVIEW = rd -> getType(rd.readByte());

    private static final NetworkMessage.PreviewInt REQUEST_ID_PREVIEW = rd -> {
        int skipped = rd.skipBytes(FLAG_BYTES);

        if (skipped < FLAG_BYTES) {
            throw new EOFException("Failed to skip bytes [expected=" + FLAG_BYTES + ", skipped=" + skipped + ']');
        }

        return rd.readVarInt();
    };

    private static final NetworkMessage.PreviewInt AFFINITY_PREVIEW = rd -> {
        int skipped = rd.skipBytes(FLAG_BYTES);

        if (skipped < FLAG_BYTES) {
            throw new EOFException("Failed to skip bytes [expected=" + FLAG_BYTES + ", skipped=" + skipped + ']');
        }

        return rd.readInt();
    };

    private static final NetworkMessage.PreviewBoolean HAS_TIMEOUT_PREVIEW = rd -> hasTimeout(rd.readByte());

    private final Codec delegate;

    public MessagingProtocolCodec(Codec delegate) {
        this.delegate = delegate;
    }

    public static MessagingProtocol.Type previewType(NetworkMessage msg) throws IOException {
        return msg.preview(TYPE_PREVIEW);
    }

    public static int previewAffinity(NetworkMessage msg) throws IOException {
        return msg.previewInt(AFFINITY_PREVIEW);
    }

    public static boolean previewHasTimeout(NetworkMessage msg) throws IOException {
        return msg.previewBoolean(HAS_TIMEOUT_PREVIEW);
    }

    public static int previewRequestId(NetworkMessage msg) throws IOException {
        return msg.previewInt(REQUEST_ID_PREVIEW);
    }

    @Override
    public boolean isStateful() {
        return delegate.isStateful();
    }

    @Override
    public Class baseType() {
        return MessagingProtocol.class;
    }

    @Override
    public void encode(MessagingProtocol msg, DataWriter out) throws IOException {
        MessagingProtocol.Type type = msg.messageType();

        int flags = 0;

        flags = appendType(flags, type);

        switch (type) {
            case CONNECT: {
                Connect connect = (Connect)msg;

                out.writeByte(flags);

                writeNodeId(connect.to(), out);
                writeClusterAddress(connect.from(), out);

                encodeSourceId(connect.channelId(), out);

                break;
            }
            case AFFINITY_NOTIFICATION: {
                AffinityNotification notification = msg.cast();

                flags = appendHasTimeout(flags, notification.hasTimeout());
                flags = appendIsRetransmit(flags, notification.isRetransmit());
                flags = appendHasMetaData(flags, notification.hasMetaData());

                out.writeByte(flags);
                out.writeInt(notification.affinity());

                if (notification.hasTimeout()) {
                    out.writeVarLong(notification.timeout());
                }

                if (notification.hasMetaData()) {
                    encodeMetaData(notification.metaData(), out);
                }

                delegate.encode(notification.payload(), out);

                break;
            }
            case NOTIFICATION: {
                Notification notification = msg.cast();

                flags = appendHasTimeout(flags, notification.hasTimeout());
                flags = appendIsRetransmit(flags, notification.isRetransmit());
                flags = appendHasMetaData(flags, notification.hasMetaData());

                out.writeByte(flags);

                if (notification.hasTimeout()) {
                    out.writeVarLong(notification.timeout());
                }

                if (notification.hasMetaData()) {
                    encodeMetaData(notification.metaData(), out);
                }

                delegate.encode(notification.payload(), out);

                break;
            }
            case AFFINITY_REQUEST: {
                AffinityRequest request = msg.cast();

                flags = appendHasTimeout(flags, request.hasTimeout());
                flags = appendIsRetransmit(flags, request.isRetransmit());
                flags = appendHasMetaData(flags, request.hasMetaData());

                out.writeByte(flags);
                out.writeInt(request.affinity());
                out.writeVarInt(request.requestId());

                if (request.hasTimeout()) {
                    out.writeVarLong(request.timeout());
                }

                if (request.hasMetaData()) {
                    encodeMetaData(request.metaData(), out);
                }

                delegate.encode(request.payload(), out);

                break;
            }
            case REQUEST:
            case VOID_REQUEST:
            case SUBSCRIBE: {
                RequestBase request = msg.cast();

                flags = appendHasTimeout(flags, request.hasTimeout());
                flags = appendIsRetransmit(flags, request.isRetransmit());
                flags = appendHasMetaData(flags, request.hasMetaData());

                out.writeByte(flags);
                out.writeVarInt(request.requestId());

                if (request.hasTimeout()) {
                    out.writeVarLong(request.timeout());
                }

                if (request.hasMetaData()) {
                    encodeMetaData(request.metaData(), out);
                }

                delegate.encode(request.payload(), out);

                break;
            }
            case AFFINITY_VOID_REQUEST: {
                AffinityVoidRequest request = msg.cast();

                flags = appendHasTimeout(flags, request.hasTimeout());
                flags = appendIsRetransmit(flags, request.isRetransmit());
                flags = appendHasMetaData(flags, request.hasMetaData());

                out.writeByte(flags);
                out.writeInt(request.affinity());
                out.writeVarInt(request.requestId());

                if (request.hasTimeout()) {
                    out.writeVarLong(request.timeout());
                }

                if (request.hasMetaData()) {
                    encodeMetaData(request.metaData(), out);
                }

                delegate.encode(request.payload(), out);

                break;
            }
            case AFFINITY_SUBSCRIBE: {
                AffinitySubscribeRequest request = msg.cast();

                flags = appendHasTimeout(flags, request.hasTimeout());
                flags = appendIsRetransmit(flags, request.isRetransmit());
                flags = appendHasMetaData(flags, request.hasMetaData());

                out.writeByte(flags);
                out.writeInt(request.affinity());
                out.writeVarInt(request.requestId());

                if (request.hasTimeout()) {
                    out.writeVarLong(request.timeout());
                }

                if (request.hasMetaData()) {
                    encodeMetaData(request.metaData(), out);
                }

                delegate.encode(request.payload(), out);

                break;
            }
            case RESPONSE_CHUNK:
            case FINAL_RESPONSE: {
                ResponseChunk response = msg.cast();

                flags = appendHasMetaData(flags, response.hasMetaData());

                out.writeByte(flags);
                out.writeVarInt(response.requestId());

                if (response.hasMetaData()) {
                    encodeMetaData(response.metaData(), out);
                }

                delegate.encode(response.payload(), out);

                break;
            }
            case VOID_RESPONSE: {
                VoidResponse response = msg.cast();

                out.writeByte(flags);
                out.writeVarInt(response.requestId());

                break;
            }
            case ERROR_RESPONSE:
                ErrorResponse response = msg.cast();

                out.writeByte(flags);
                out.writeVarInt(response.requestId());
                out.writeUTF(response.stackTrace());

                break;
            default: {
                throw new IllegalArgumentException("Unexpected message type: " + type);
            }
        }
    }

    @Override
    public MessagingProtocol decode(DataReader in) throws IOException {
        byte flags = in.readByte();

        MessagingProtocol.Type type = getType(flags);

        switch (type) {
            case CONNECT: {
                ClusterNodeId to = readNodeId(in);
                ClusterAddress from = readClusterAddress(in);

                MessagingChannelId sourceId = decodeSourceId(in);

                return new Connect(to, from, sourceId);
            }
            case AFFINITY_NOTIFICATION: {
                boolean retransmit = isRetransmit(flags);
                int affinity = in.readInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeNotificationPayload(in);

                return new AffinityNotification<>(affinity, retransmit, timeout, payload, metaData);
            }
            case NOTIFICATION: {
                boolean retransmit = isRetransmit(flags);
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeNotificationPayload(in);

                return new Notification<>(retransmit, timeout, payload, metaData);
            }
            case AFFINITY_REQUEST: {
                boolean retransmit = isRetransmit(flags);
                int affinity = in.readInt();
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new AffinityRequest<>(affinity, requestId, retransmit, timeout, payload, metaData);
            }
            case REQUEST: {
                boolean retransmit = isRetransmit(flags);
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new Request<>(requestId, retransmit, timeout, payload, metaData);
            }
            case AFFINITY_VOID_REQUEST: {
                boolean retransmit = isRetransmit(flags);
                int affinity = in.readInt();
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new AffinityVoidRequest<>(affinity, requestId, retransmit, timeout, payload, metaData);
            }
            case VOID_REQUEST: {
                boolean retransmit = isRetransmit(flags);
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new VoidRequest<>(requestId, retransmit, timeout, payload, metaData);
            }
            case AFFINITY_SUBSCRIBE: {
                boolean retransmit = isRetransmit(flags);
                int affinity = in.readInt();
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new AffinitySubscribeRequest<>(affinity, requestId, retransmit, timeout, payload, metaData);
            }
            case SUBSCRIBE: {
                boolean retransmit = isRetransmit(flags);
                int requestId = in.readVarInt();
                long timeout = hasTimeout(flags) ? in.readVarLong() : 0;
                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeRequestPayload(requestId, in);

                return new SubscribeRequest<>(requestId, retransmit, timeout, payload, metaData);
            }
            case RESPONSE_CHUNK: {
                int requestId = in.readVarInt();

                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeResponsePayload(requestId, in);

                return new ResponseChunk<>(requestId, payload, metaData);
            }
            case FINAL_RESPONSE: {
                int requestId = in.readVarInt();

                MessageMetaData metaData = hasMetaData(flags) ? decodeMetaData(in) : null;

                T payload = decodeResponsePayload(requestId, in);

                return new FinalResponse<>(requestId, payload, metaData);
            }
            case VOID_RESPONSE: {
                int requestId = in.readVarInt();

                return new MessagingProtocol.VoidResponse(requestId);
            }
            case ERROR_RESPONSE: {
                int requestId = in.readVarInt();
                String stackTrace = in.readUTF();

                return new ErrorResponse(requestId, stackTrace);
            }
            default: {
                throw new IllegalArgumentException("Unexpected message type: " + type);
            }
        }
    }

    private void encodeMetaData(MessageMetaData metaData, DataWriter out) throws IOException {
        metaData.writeTo(out);
    }

    private MessageMetaData decodeMetaData(DataReader in) throws IOException {
        return MessageMetaData.readFrom(in);
    }

    private void encodeSourceId(MessagingChannelId id, DataWriter out) throws IOException {
        out.writeLong(id.hiBits());
        out.writeLong(id.loBits());
    }

    private MessagingChannelId decodeSourceId(DataReader in) throws IOException {
        long hiBits = in.readLong();
        long loBits = in.readLong();

        return new MessagingChannelId(hiBits, loBits);
    }

    private T decodeRequestPayload(int requestId, DataReader in) throws RequestPayloadDecodeException {
        try {
            return delegate.decode(in);
        } catch (Throwable t) {
            throw new RequestPayloadDecodeException(requestId, t);
        }
    }

    private T decodeResponsePayload(int requestId, DataReader in) throws ResponsePayloadDecodeException {
        try {
            return delegate.decode(in);
        } catch (Throwable t) {
            throw new ResponsePayloadDecodeException(requestId, t);
        }
    }

    private T decodeNotificationPayload(DataReader in) throws NotificationPayloadDecodeException {
        try {
            return delegate.decode(in);
        } catch (Throwable t) {
            throw new NotificationPayloadDecodeException(t);
        }
    }

    private static int appendType(int flags, MessagingProtocol.Type type) {
        return (byte)(flags | (type.ordinal() & MASK_TYPE));
    }

    private static MessagingProtocol.Type getType(byte flags) {
        return TYPES_CACHE[flags & MASK_TYPE];
    }

    private static boolean isRetransmit(byte flags) {
        return (flags & MASK_RETRANSMIT) != 0;
    }

    private static boolean hasTimeout(byte flags) {
        return (flags & MASK_HAS_TIMEOUT) != 0;
    }

    private static boolean hasMetaData(byte flags) {
        return (flags & MASK_HAS_METADATA) != 0;
    }

    private static int appendIsRetransmit(int flags, boolean retransmit) {
        if (retransmit) {
            return flags | MASK_RETRANSMIT;
        } else {
            return flags;
        }
    }

    private static int appendHasTimeout(int flags, boolean hasTimeout) {
        if (hasTimeout) {
            return flags | MASK_HAS_TIMEOUT;
        } else {
            return flags;
        }
    }

    private static int appendHasMetaData(int flags, boolean hasMetaData) {
        if (hasMetaData) {
            return flags | MASK_HAS_METADATA;
        } else {
            return flags;
        }
    }

    @Override
    public String toString() {
        return ToString.format(this);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy