/*
* Copyright (c) 2012-2015 Spotify AB
*
* 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.spotify.netty4.handler.codec.zmtp;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.CombinedChannelDuplexHandler;
import io.netty.handler.codec.ReplayingDecoder;
import static com.spotify.netty4.handler.codec.zmtp.ZMTPUtils.checkNotNull;
/**
* A ZMTP codec for Netty.
*
* Note: A single codec instance is not {@link Sharable} among multiple {@link Channel} instances.
*/
public class ZMTPCodec extends ReplayingDecoder<Void> {
private final ZMTPSession session;
private final ZMTPHandshaker handshaker;
private final ZMTPConfig config;
public ZMTPCodec(final ZMTPSession session) {
this.config = session.config();
this.session = checkNotNull(session, "session");
this.handshaker = config.protocol().handshaker(config);
}
/**
* Get the {@link ZMTPSession} for this codec.
*/
public ZMTPSession session() {
return session;
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
ctx.writeAndFlush(handshaker.greeting());
}
@Override
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
if (!session.handshakeFuture().isDone()) {
session.handshakeFailure(new ClosedChannelException());
ctx.fireUserEventTriggered(new ZMTPHandshakeFailure(session));
}
}
@Override
protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out)
throws Exception {
// Discard input if handshake failed. It is expected that the user will close the channel.
if (session.handshakeFuture().isDone()) {
assert !session.handshakeFuture().isSuccess();
in.skipBytes(in.readableBytes());
}
// Shake hands
final ZMTPHandshake handshake;
try {
handshake = handshaker.handshake(in, ctx);
if (handshake == null) {
// Handshake is not yet done. Await more input.
return;
}
} catch (Exception e) {
session.handshakeFailure(e);
ctx.fireUserEventTriggered(new ZMTPHandshakeFailure(session));
throw e;
}
// Handshake is done.
session.handshakeSuccess(handshake);
// Replace this handler with the framing encoder and decoder
if (actualReadableBytes() > 0) {
out.add(in.readBytes(actualReadableBytes()));
}
final ZMTPDecoder decoder = config.decoder().decoder(session);
final ZMTPEncoder encoder = config.encoder().encoder(session);
final ZMTPWireFormat wireFormat = ZMTPWireFormats.wireFormat(session.negotiatedVersion());
final ChannelHandler handler =
new CombinedChannelDuplexHandler<ZMTPFramingDecoder, ZMTPFramingEncoder>(
new ZMTPFramingDecoder(wireFormat, decoder),
new ZMTPFramingEncoder(wireFormat, encoder));
ctx.pipeline().replace(this, ctx.name(), handler);
// Tell the user that the handshake is complete
ctx.fireUserEventTriggered(new ZMTPHandshakeSuccess(session, handshake));
}
public static Builder builder() {
return new Builder();
}
public static ZMTPCodec from(final ZMTPConfig config) {
return new ZMTPCodec(ZMTPSession.from(config));
}
public static ZMTPCodec of(final ZMTPSocketType socketType) {
return builder().socketType(socketType).build();
}
public static ZMTPCodec from(final ZMTPSession session) {
return new ZMTPCodec(session);
}
public static class Builder {
private final ZMTPConfig.Builder config = ZMTPConfig.builder();
private Builder() {
}
public Builder protocol(final ZMTPProtocol protocol) {
config.protocol(protocol);
return this;
}
public Builder interop(final boolean interop) {
config.interop(interop);
return this;
}
public Builder socketType(final ZMTPSocketType socketType) {
config.socketType(socketType);
return this;
}
public Builder localIdentity(final CharSequence localIdentity) {
config.localIdentity(localIdentity);
return this;
}
public Builder localIdentity(final byte[] localIdentity) {
config.localIdentity(localIdentity);
return this;
}
public Builder localIdentity(final ByteBuffer localIdentity) {
config.localIdentity(localIdentity);
return this;
}
public Builder encoder(final ZMTPEncoder.Factory encoder) {
config.encoder(encoder);
return this;
}
public Builder encoder(final Class<? extends ZMTPEncoder> encoder) {
config.encoder(encoder);
return this;
}
public Builder decoder(final ZMTPDecoder.Factory decoder) {
config.decoder(decoder);
return this;
}
public Builder decoder(final Class<? extends ZMTPDecoder> decoder) {
config.decoder(decoder);
return this;
}
public Builder identityGenerator(final ZMTPIdentityGenerator identityGenerator) {
config.identityGenerator(identityGenerator);
return this;
}
public ZMTPCodec build() {
return ZMTPCodec.from(config.build());
}
}
}