/*
* Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and
* contributors. All rights reserved.
*
* Licensed 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 com.noctarius.tengi.server.impl.transport;
import com.noctarius.tengi.core.connection.Connection;
import com.noctarius.tengi.core.connection.HandshakeHandler;
import com.noctarius.tengi.core.connection.Transport;
import com.noctarius.tengi.core.model.Identifier;
import com.noctarius.tengi.core.model.Message;
import com.noctarius.tengi.server.impl.ConnectionManager;
import com.noctarius.tengi.spi.buffer.MemoryBuffer;
import com.noctarius.tengi.spi.buffer.impl.MemoryBufferFactory;
import com.noctarius.tengi.spi.connection.ConnectionContext;
import com.noctarius.tengi.spi.connection.packets.Handshake;
import com.noctarius.tengi.spi.serialization.Serializer;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableDecoder;
import com.noctarius.tengi.spi.serialization.codec.AutoClosableEncoder;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import static com.noctarius.tengi.server.impl.ServerUtil.CONNECTION_ID;
import static com.noctarius.tengi.server.impl.ServerUtil.connectionAttribute;
public abstract class ServerConnectionProcessor<T>
extends SimpleChannelInboundHandler<T> {
private final ConnectionManager connectionManager;
private final Serializer serializer;
private final Transport transport;
protected ServerConnectionProcessor(ConnectionManager connectionManager, Serializer serializer, Transport transport) {
this.connectionManager = connectionManager;
this.serializer = serializer;
this.transport = transport;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.channel().close();
Identifier connectionId = connectionAttribute(ctx, CONNECTION_ID);
getConnectionManager().exceptionally(connectionId, cause);
}
@Override
protected final void channelRead0(ChannelHandlerContext ctx, T msg)
throws Exception {
try (AutoClosableDecoder decoder = decode(ctx, msg)) {
if (decoder == null) {
ctx.close();
return;
}
boolean loggedIn = decoder.readBoolean();
if (!loggedIn) {
handleHandshakeRequest(ctx, decoder);
return;
}
Identifier connectionId = decoder.readObject();
connectionAttribute(ctx, CONNECTION_ID, connectionId);
Message message = decoder.readObject();
connectionManager.publishMessage(ctx.channel(), connectionId, message);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
ctx.flush();
}
protected Serializer getSerializer() {
return serializer;
}
protected Transport getTransport() {
return transport;
}
protected ConnectionManager getConnectionManager() {
return connectionManager;
}
protected abstract AutoClosableDecoder decode(ChannelHandlerContext ctx, T msg)
throws Exception;
protected abstract ConnectionContext createConnectionContext(ChannelHandlerContext ctx, Identifier connectionId);
private void handleHandshakeRequest(ChannelHandlerContext ctx, AutoClosableDecoder decoder)
throws Exception {
Object request = decoder.readObject();
if (!(request instanceof Handshake)) {
ctx.close();
return;
}
Identifier connectionId = Identifier.randomIdentifier();
HandshakeHandler handshakeHandler = connectionManager.getHandshakeHandler();
Handshake handshakeResponse = handshakeHandler.handleHandshake(connectionId, (Handshake) request);
if (handshakeResponse == null) {
ctx.close();
return;
}
if (handshakeResponse == request) {
ctx.close();
throw new IllegalStateException("Handshake could not be accepted, illegal verification");
}
connectionAttribute(ctx, CONNECTION_ID, connectionId);
ConnectionContext connectionContext = createConnectionContext(ctx, connectionId);
Connection connection = connectionManager.assignConnection(connectionId, connectionContext, transport);
connectionContext.writeSocket(ctx.channel(), connection, createHandshakeResponse(ctx, handshakeResponse));
}
private MemoryBuffer createHandshakeResponse(ChannelHandlerContext ctx, Handshake handshakeResponse)
throws Exception {
ByteBuf buffer = ctx.alloc().buffer();
MemoryBuffer memoryBuffer = MemoryBufferFactory.create(buffer);
try (AutoClosableEncoder encoder = serializer.retrieveEncoder(memoryBuffer)) {
encoder.writeObject("response", handshakeResponse);
}
return memoryBuffer;
}
}