org.bidib.jbidibc.netbidib.server.NetBidibServerHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jbidibc-netbidib-server Show documentation
Show all versions of jbidibc-netbidib-server Show documentation
jBiDiB jbidibc NetBiDiB Server POM
The newest version!
package org.bidib.jbidibc.netbidib.server;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.bidib.jbidibc.messages.BidibLibrary;
import org.bidib.jbidibc.messages.BidibMessagePublisher;
import org.bidib.jbidibc.messages.ConnectionListener;
import org.bidib.jbidibc.messages.HostAdapter;
import org.bidib.jbidibc.messages.Node;
import org.bidib.jbidibc.messages.StringData;
import org.bidib.jbidibc.messages.enums.PairingResult;
import org.bidib.jbidibc.messages.exception.InvalidConfigurationException;
import org.bidib.jbidibc.messages.exception.ProtocolException;
import org.bidib.jbidibc.messages.exception.ProtocolInvalidContentException;
import org.bidib.jbidibc.messages.helpers.Context;
import org.bidib.jbidibc.messages.helpers.DefaultContext;
import org.bidib.jbidibc.messages.message.BidibCommand;
import org.bidib.jbidibc.messages.message.BidibMessage;
import org.bidib.jbidibc.messages.message.BidibMessageInterface;
import org.bidib.jbidibc.messages.message.BidibRequestFactory;
import org.bidib.jbidibc.messages.message.BidibResponseFactory;
import org.bidib.jbidibc.messages.message.LocalBidibUpResponse;
import org.bidib.jbidibc.messages.message.LocalLogoffMessage;
import org.bidib.jbidibc.messages.message.LocalLogonAckMessage;
import org.bidib.jbidibc.messages.message.LocalLogonMessage;
import org.bidib.jbidibc.messages.message.StringResponse;
import org.bidib.jbidibc.messages.message.SysPVersionResponse;
import org.bidib.jbidibc.messages.message.SysUniqueIdResponse;
import org.bidib.jbidibc.messages.message.netbidib.LocalLinkMessage;
import org.bidib.jbidibc.messages.message.netbidib.LocalProtocolSignatureMessage;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibCommandMessage;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData.LogonStatus;
import org.bidib.jbidibc.messages.message.netbidib.NetBidibLinkData.PairingStatus;
import org.bidib.jbidibc.messages.utils.ByteUtils;
import org.bidib.jbidibc.messages.utils.MessageUtils;
import org.bidib.jbidibc.messages.utils.NodeUtils;
import org.bidib.jbidibc.messages.utils.ThreadFactoryBuilder;
import org.bidib.jbidibc.netbidib.ConnectionUpdateEvent;
import org.bidib.jbidibc.netbidib.NetBidibContextKeys;
import org.bidib.jbidibc.netbidib.exception.AccessDeniedException;
import org.bidib.jbidibc.netbidib.exception.PairingDeniedException;
import org.bidib.jbidibc.netbidib.pairingstore.LocalPairingStore.PairingLookupResult;
import org.bidib.jbidibc.netbidib.pairingstore.PairingStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
public abstract class NetBidibServerHandler extends SimpleChannelInboundHandler
implements BidibMessagePublisher {
private static final Logger LOGGER = LoggerFactory.getLogger(NetBidibServerHandler.class);
private BidibRequestFactory bidibRequestFactory;
protected BidibResponseFactory responseFactory;
private final HostAdapter hostAdapter;
private final String backendPortName;
protected final Object pairedPartnerLock = new Object();
/**
* The link data of the paired remote partner.
*/
protected NetBidibLinkData pairedPartner;
/**
* The link data of the server partner (ourself).
*/
protected final NetBidibLinkData serverLinkData;
private PairingStore pairingStore;
/**
* The channel group to pool all TCP connections. It is used to guaranty that no more than one connection is opened.
*/
private ChannelGroup channelGroup;
protected ChannelHandlerContext ctx;
private Set remoteConnectionListeners = new HashSet<>();
private final Consumer> lazyInitializationCallback;
private final Function messageContentSupplier;
private RoleTypeEnum roleType;
private final org.bidib.jbidibc.messages.logger.Logger splitMessageLogger;
private final AtomicBoolean isFirstPacket = new AtomicBoolean(true);
protected final ScheduledExecutorService localNodeDataWorkers;
/**
* Constructor
*
* @param channelGroup
* the channel group to pool all TCP connections.
* @param hostAdapter
* the host adapter
* @param backendPortName
* the port identifier of the backend, e.g. COM8
* @param serverLinkData
* the server link data instance to use
*/
public NetBidibServerHandler(final ChannelGroup channelGroup, final HostAdapter hostAdapter,
final String backendPortName, final NetBidibLinkData serverLinkData,
final Consumer> lazyInitializationCallback,
Function messageContentSupplier, final RoleTypeEnum roleType,
final NetBidibLinkData pairedPartner) {
LOGGER.info("Create new NetBidibServerHandler instance. Provided roleType: {}", roleType);
this.channelGroup = channelGroup;
this.hostAdapter = hostAdapter;
this.backendPortName = backendPortName;
this.serverLinkData = serverLinkData;
this.lazyInitializationCallback = lazyInitializationCallback;
this.messageContentSupplier = messageContentSupplier;
this.roleType = roleType;
this.pairedPartner = pairedPartner;
splitMessageLogger = new org.bidib.jbidibc.messages.logger.Logger() {
@Override
public void debug(String format, Object... arguments) {
LOGGER.debug(format, arguments);
}
@Override
public void info(String format, Object... arguments) {
LOGGER.info(format, arguments);
}
@Override
public void warn(String format, Object... arguments) {
LOGGER.warn(format, arguments);
}
@Override
public void error(String format, Object... arguments) {
LOGGER.error(format, arguments);
}
};
this.localNodeDataWorkers =
Executors
.newScheduledThreadPool(1,
new ThreadFactoryBuilder().setNameFormat("localNodeDataWorkers-thread-%d").build());
}
protected boolean handleLocalBidibUpResponse() {
return this.roleType == RoleTypeEnum.INTERFACE;
}
public void addRemoteConnectionListener(final ConnectionListener remoteConnectionListener) {
synchronized (this.remoteConnectionListeners) {
this.remoteConnectionListeners.add(remoteConnectionListener);
}
}
public void removeRemoteConnectionListener(final ConnectionListener remoteConnectionListener) {
synchronized (this.remoteConnectionListeners) {
this.remoteConnectionListeners.remove(remoteConnectionListener);
}
}
public void setPairingStore(final PairingStore pairingStore) {
this.pairingStore = pairingStore;
}
protected void performLazyInitialization() {
LOGGER.info("Perform the lazy initialization.");
if (this.bidibRequestFactory == null) {
this.bidibRequestFactory = new BidibRequestFactory();
this.bidibRequestFactory.setEscapeMagic(false);
this.bidibRequestFactory.initialize();
}
if (this.responseFactory == null) {
this.responseFactory = new BidibResponseFactory();
}
if (this.lazyInitializationCallback != null) {
LOGGER.info("Call the lazy initialization callback.");
// this will set the toHostPublisher
this.lazyInitializationCallback.accept(this);
}
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
final SocketAddress remoteAddress = ctx.channel().remoteAddress();
LOGGER.info("Session created. IP: {}, channel: {}", remoteAddress, ctx.channel());
// prevent multiple simultaneous connections
if (hasActiveConnection()) {
LOGGER.warn("More than one session: reject!");
// send 'SHUTDOWN' message, then close the Channel
ctx.channel().writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
return;
}
LOGGER.info("Connected");
performLazyInitialization();
// add to channel group and set the state
channelGroup.add(ctx.channel());
ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
if (remoteAddress != null) {
ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).set(remoteAddress.toString());
}
else {
final SocketAddress localAddress = ctx.channel().localAddress();
LOGGER.info("No remoteAddress available. Use localAddress: {}", localAddress);
// ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).set(localAddress.toString());
}
super.channelRegistered(ctx);
LOGGER.info("Store the channelHandlerContext: {}", ctx);
this.ctx = ctx;
synchronized (this.remoteConnectionListeners) {
for (ConnectionListener connectionListener : this.remoteConnectionListeners) {
try {
connectionListener
.opened(ctx.channel().remoteAddress() != null ? ctx.channel().remoteAddress().toString()
: "unknown");
}
catch (Exception ex) {
LOGGER.warn("Notify that the client connection was closed failed.", ex);
}
}
}
}
private void sendInitialLocalProtocolSignatureMessage() throws ProtocolException {
String requestorName = this.serverLinkData.getRequestorName();
LOGGER
.info("Send the initial LocalProtocolSignatureMessage to the client. Current requestorName: {}",
requestorName);
if (StringUtils.isBlank(requestorName)
|| !requestorName.startsWith(LocalProtocolSignatureMessage.EMITTER_PREFIX_BIDIB)) {
LOGGER.warn("Invalid requestor name provided: {}", requestorName);
throw new IllegalArgumentException("Invalid requestor name provided.");
}
// use BiDiB emitter
NetBidibCommandMessage message =
bidibRequestFactory
.createLocalProtocolSignature(requestorName /* LocalProtocolSignatureMessage.EMITTER_PREFIX_BIDIB */);
publishMessage(ctx, message);
final Context context = new DefaultContext();
context.register(NetBidibContextKeys.KEY_PORT, backendPortName);
final ConnectionListener connectionListener = new ConnectionListener() {
@Override
public void opened(String port) {
LOGGER.info("The port was opened: {}", port);
}
@Override
public void closed(String port) {
}
@Override
public void status(String messageKey, final Context context) {
LOGGER.info("Received status event, messageKey: {}, context: {}", messageKey, context);
}
@Override
public void stall(boolean stall) {
// TODO Auto-generated method stub
}
@Override
public void pairingFinished(PairingResult pairingResult) {
LOGGER.info("The pairing process has finished. Current pairingResult: {}", pairingResult);
// TODO Auto-generated method stub
}
};
context.register(NetBidibContextKeys.KEY_CONNECTION_LISTENER, connectionListener);
try {
LOGGER.info("Initialize the host adapter.");
hostAdapter.initialize(context);
LOGGER.info("Connect to the backend. Provided context: {}", context);
// this call will connect to the backend
hostAdapter.signalConnectionOpened(context);
// // TODO wait until the root node is available
//
// // disable spontaneous events from backend
// LOGGER.info("Disable all spontaneous events from backend.");
// BidibCommand sysDisableMessage = bidibRequestFactory.createSysDisable();
// sysDisableMessage.setAddr(Node.ROOTNODE_ADDR);
// hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(sysDisableMessage));
this.localNodeDataWorkers.schedule(() -> fetchLocalBidibNodeData(), 20, TimeUnit.MILLISECONDS);
// fetchLocalBidibNodeData();
}
catch (Exception ex) {
LOGGER.warn("Connect to backend failed.", ex);
throw new InvalidConfigurationException("Connect to backend failed.");
}
}
private void fetchLocalBidibNodeData() {
// ask the backend for the names and uniqueId
LOGGER.info("Get the productname, username, uniqueId and protocol version from the root node.");
BidibCommand stringGetMessage =
bidibRequestFactory.createStringGet(StringData.NAMESPACE_NODE, StringData.INDEX_PRODUCTNAME);
stringGetMessage.setAddr(Node.ROOTNODE_ADDR);
sendLocalBidibDownMessage(stringGetMessage);
stringGetMessage = bidibRequestFactory.createStringGet(StringData.NAMESPACE_NODE, StringData.INDEX_USERNAME);
stringGetMessage.setAddr(Node.ROOTNODE_ADDR);
sendLocalBidibDownMessage(stringGetMessage);
// get the unique id from the backend
BidibCommand getUniqueIdMessage = bidibRequestFactory.createSysGetUniqueId();
getUniqueIdMessage.setAddr(Node.ROOTNODE_ADDR);
sendLocalBidibDownMessage(getUniqueIdMessage);
// get the product version from the backend
BidibCommand getPVersionMessage = bidibRequestFactory.createSysGetPVersion();
getUniqueIdMessage.setAddr(Node.ROOTNODE_ADDR);
sendLocalBidibDownMessage(getPVersionMessage);
}
private void sendLocalBidibDownMessage(final BidibCommand bidibCommand) {
BidibCommand localBidibDownMessage = bidibRequestFactory.createLocalBidibDown(bidibCommand);
localBidibDownMessage.setAddr(Node.ROOTNODE_ADDR);
LOGGER.info("Prepared the localBidibDownMessage to forward to backend: {}", localBidibDownMessage);
hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(localBidibDownMessage));
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
SocketAddress remoteAddress = ctx.channel().remoteAddress();
LOGGER.info("Session closed. IP: {}", remoteAddress);
if (remoteAddress == null) {
final SocketAddress localAddress = ctx.channel().localAddress();
LOGGER.info("No remoteAddress available. Use localAddress: {}", localAddress);
remoteAddress = localAddress;
}
String connectedRemoteAddress = ctx.channel().attr(ConnectionState.REMOTE_ADDRESS_KEY).get();
LOGGER.info("The connected remote address: {}", connectedRemoteAddress);
// only close for primary connection
if (hasActiveConnection() && !Objects.equals(remoteAddress.toString(), connectedRemoteAddress)) {
LOGGER.info("The primary connection is still established.");
return;
}
// set the state and remove from channel group (by super call)
ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
super.channelUnregistered(ctx);
final Context context = new DefaultContext();
if (pairedPartner.getUniqueId() != null) {
context.register(Context.UNIQUE_ID, pairedPartner.getUniqueId());
}
LOGGER.info("Signal that the connection to the guest was closed.");
try {
hostAdapter.signalConnectionClosed(context);
}
catch (Exception ex) {
LOGGER.warn("Signal that the connection to the guest was closed failed.", ex);
}
try {
LOGGER.info("Clear the saved serverLinkData.");
serverLinkData.clear(false);
}
catch (Exception ex) {
LOGGER.warn("Remove uniqueid from serverLinkData failed.", ex);
}
// release the pairedPartner
synchronized (pairedPartnerLock) {
LOGGER.info("Clear the paired partner link data: {}", pairedPartner);
pairedPartner.clear(true);
}
LOGGER.info("Release the stored channelHandlerContext: {}", ctx);
this.ctx = null;
// only trigger the "no connection" event when no active connection exists
if (!hasActiveConnection()) {
ctx.fireUserEventTriggered(new ConnectionUpdateEvent(ConnectionState.NOT_CONNECTED));
}
synchronized (this.remoteConnectionListeners) {
for (ConnectionListener connectionListener : this.remoteConnectionListeners) {
try {
connectionListener
.closed(ctx.channel().remoteAddress() != null ? ctx.channel().remoteAddress().toString()
: "unknown");
}
catch (Exception ex) {
LOGGER.warn("Notify that the client connection was closed failed.", ex);
}
}
}
LOGGER.info("Disconnected");
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
// process the data that is received from the remote partner over netBidib
byte[] messageContent = new byte[in.readableBytes()];
in.readBytes(messageContent);
LOGGER.info("< processReceivedMessage(messageArray), isFirstPacket);
}
catch (ProtocolException ex) {
LOGGER.warn("Create messages from provided data failed.", ex);
}
}
private void processReceivedMessage(byte[] messageArray) throws ProtocolException {
BidibMessage message = null;
try {
// let the request factory create the commands
List commands = bidibRequestFactory.create(messageArray);
LOGGER.info("Received commands: {}", commands);
for (BidibMessageInterface bidibCommand : commands) {
switch (ByteUtils.getInt(bidibCommand.getType())) {
case BidibLibrary.MSG_LOCAL_PROTOCOL_SIGNATURE:
// respond the protocol signature
final LocalProtocolSignatureMessage localProtocolSignatureMessage =
(LocalProtocolSignatureMessage) bidibCommand;
processMsgLocalProtocolSignature(ctx, localProtocolSignatureMessage);
break;
case BidibLibrary.MSG_LOCAL_LINK:
LocalLinkMessage localLinkMessage = (LocalLinkMessage) bidibCommand;
processMsgLocalLink(ctx, localLinkMessage);
break;
case BidibLibrary.MSG_LOCAL_LOGON:
// forward the command to the backend
if (hostAdapter != null) {
// the bidibCommand encodes the raw message
hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(bidibCommand));
}
else {
LOGGER.warn("No hostAdapter assigned.");
}
break;
case BidibLibrary.MSG_LOCAL_LOGON_ACK:
// if MSG_LOCAL_LOGON_ACK is received here we are connected to the remote partner
// without bidib-distributed
LocalLogonAckMessage localLogonAckMessage = (LocalLogonAckMessage) bidibCommand;
processMsgLocalLogonAck(ctx, localLogonAckMessage);
break;
case BidibLibrary.MSG_LOCAL_LOGON_REJECTED:
LOGGER.warn("Process the MSG_LOCAL_LOGON_REJECTED !!!");
break;
default:
LOGGER.debug("Processing BiDiB node related command: {}", bidibCommand);
final LogonStatus logonStatus = this.pairedPartner.getLogonStatus();
if (LogonStatus.LOGGED_ON == logonStatus) {
// forward the command to the backend
if (hostAdapter != null) {
// the bidibCommand encodes the raw message
hostAdapter.forwardMessageToBackend(messageContentSupplier.apply(bidibCommand));
}
else {
LOGGER.warn("No hostAdapter assigned.");
}
}
else {
LOGGER.warn("The logon status for the paired partner is not LOGGED_ON.");
// TODO init the disconnect
throw new IllegalArgumentException("The paired partner is not LOGGED_ON.");
}
break;
}
}
}
catch (ProtocolException ex) {
LOGGER.warn("Process received messages failed: {}", ByteUtils.bytesToHex(messageArray), ex);
StringBuilder sb = new StringBuilder("< {
Boolean paired = Boolean.FALSE;
if (pairingCallback != null) {
LOGGER.info("Use the pairing callback to get the paired result.");
synchronized (pairedPartnerLock) {
paired = pairingCallback.apply(pairedPartner, pairingTimeout);
}
}
else {
LOGGER.warn("No pairingCallback available. Accept every client.");
paired = Boolean.TRUE;
}
LOGGER
.info("After pairing callback. Pairing success: {}, pairedPartner: {}", paired, pairedPartner);
try {
if (Boolean.TRUE.equals(paired)) {
publishPairedStatus(ctx, BidibLibrary.BIDIB_LINK_STATUS_PAIRED);
}
else {
publishPairedStatus(ctx, BidibLibrary.BIDIB_LINK_STATUS_UNPAIRED);
}
}
catch (ProtocolException ex) {
LOGGER.warn("Publish paired status failed.", ex);
}
catch (PairingDeniedException ex) {
LOGGER.warn("Pairing was denied. We must close the connection.", ex);
// TODO: handle exception
channelGroup.close();
}
}, 5, TimeUnit.MILLISECONDS);
break;
default:
LOGGER.warn("Unhandled message: {}", localLinkMessage);
break;
}
}
private final ScheduledExecutorService pairingWorker =
Executors
.newScheduledThreadPool(1, new ThreadFactoryBuilder().setNameFormat("pairingWorkers-thread-%d").build());
private BiFunction pairingCallback;
public void setPairingCallback(final BiFunction pairingCallback) {
LOGGER.info("Set the pairing callback: {}", pairingCallback);
this.pairingCallback = pairingCallback;
}
/**
* Process the MSG_LOCAL_LOGON_ACK netBidib message that was received from the remote partner.
*
* @param ctx
* the netty context
* @param localLogonAckMessage
* the message
*/
private void processMsgLocalLogonAck(
final ChannelHandlerContext ctx, final LocalLogonAckMessage localLogonAckMessage) {
LOGGER.info("Received MSG_LOCAL_LOGON_ACK: {}", localLogonAckMessage);
int nodeAddress = localLogonAckMessage.getNodeAddress();
long uniqueId = localLogonAckMessage.getSenderUniqueId();
LOGGER.info("Current nodeAddress: {}, uniqueId: {}", nodeAddress, ByteUtils.formatHexUniqueId(uniqueId));
// TODO use the provided nodeAddress as the local bidib address
if (serverLinkData.getUniqueId() == uniqueId) {
synchronized (pairedPartnerLock) {
pairedPartner.setLogonStatus(LogonStatus.LOGGED_ON);
LOGGER.info("Current pairedPartner: {}", pairedPartner);
}
}
else {
LOGGER
.warn("The provided uniqueId does not match, provided: {}, expected: {}",
ByteUtils.formatHexUniqueId(uniqueId), ByteUtils.formatHexUniqueId(serverLinkData.getUniqueId()));
}
}
protected void publishPairedStatus(final ChannelHandlerContext ctx, int descriptor) throws ProtocolException {
LOGGER.info("Publish the paired status: {}", descriptor);
if (BidibLibrary.BIDIB_LINK_STATUS_PAIRED == descriptor) {
LOGGER.info("The BIDIB_LINK_STATUS_PAIRED is published.");
// set the state to paired
ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.PAIRED);
LOGGER.info("Send the status pairing status PAIRED message to the client.");
NetBidibCommandMessage statusPaired =
bidibRequestFactory
.createLocalLinkStatusPaired(serverLinkData.getUniqueId(), pairedPartner.getUniqueId());
publishMessage(ctx, statusPaired);
serverLinkData.setPairingStatus(PairingStatus.PAIRED);
LOGGER.info("Current pairedPartner: {}, serverLinkData: {}", pairedPartner, serverLinkData);
if (PairingStatus.PAIRED == pairedPartner.getPairingStatus()
&& PairingStatus.PAIRED == serverLinkData.getPairingStatus()
&& LogonStatus.LOGGED_OFF == pairedPartner.getLogonStatus()) {
LOGGER.info("The pairedPartner accepted the pairing already. Send the LOGON.");
LocalLogonMessage localLogonMessage =
new LocalLogonMessage(new byte[] { 0 }, 0, serverLinkData.getUniqueId());
publishMessage(ctx, localLogonMessage);
pairedPartner.setLogonStatus(LogonStatus.LOGGED_ON);
}
else {
LOGGER.info("LOGON was not sent.");
}
}
else {
LOGGER.info("The BIDIB_LINK_STATUS_UNPAIRED is published.");
// set the state to unpaired
ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.UNPAIRED);
NetBidibCommandMessage statusUnpaired =
bidibRequestFactory
.createLocalLinkStatusUnpaired(serverLinkData.getUniqueId(), pairedPartner.getUniqueId());
publishMessage(ctx, statusUnpaired);
serverLinkData.setPairingStatus(PairingStatus.UNPAIRED);
LOGGER
.info("Throw PairingDeniedException to signal the connection is not paired. Current uniqueId: {}",
ByteUtils.formatHexUniqueId(pairedPartner.getUniqueId()));
throw new PairingDeniedException(
"Pairing denied for uniqueId: " + ByteUtils.formatHexUniqueId(pairedPartner.getUniqueId()));
}
}
/**
* Process the {@code LocalBidibUpResponse} and publish {@code LocalLinkMessage} messages to the connected partner.
*
* @param localBidibUpResponse
* the localBidibUpResponse message received from the backend
* @return {@code true}: send the pairing status to the connected partner, {@code false}: don't send the pairing
* status to the connected partner
* @throws ProtocolException
* thrown if illegal message content is detected or processing of message failed.
*/
protected boolean processLocalBidibUpResponseFromBackend(final LocalBidibUpResponse localBidibUpResponse)
throws ProtocolException {
boolean sendPairingStatusIfRequired = false;
try {
// add missing header info to the wrapped message
byte[] wrapped = localBidibUpResponse.getWrappedMessage();
byte[] raw = ByteUtils.concat(new byte[] { ByteUtils.getLowByte(wrapped.length + 2), 0, 0 }, wrapped);
BidibMessageInterface message = responseFactory.create(raw);
LOGGER.info("Received wrapped message: {}", message);
boolean publish = false;
switch (ByteUtils.getInt(message.getType())) {
case BidibLibrary.MSG_SYS_UNIQUE_ID:
SysUniqueIdResponse response = (SysUniqueIdResponse) message;
synchronized (pairedPartnerLock) {
publish = serverLinkData.getUniqueId() == null;
serverLinkData.setUniqueId(NodeUtils.getUniqueId(response.getUniqueId()));
if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
LOGGER.info("Publish the uniqueId: {}", serverLinkData.getUniqueId());
LocalLinkMessage myUID =
new LocalLinkMessage(new byte[] { 0 }, 0, serverLinkData.getUniqueId());
publishMessage(ctx, myUID);
}
}
break;
case BidibLibrary.MSG_STRING:
StringResponse stringResponse = (StringResponse) message;
StringData stringData = stringResponse.getStringData();
switch (stringData.getIndex()) {
case StringData.INDEX_PRODUCTNAME:
synchronized (pairedPartnerLock) {
publish = serverLinkData.getProdString() == null;
serverLinkData.setProdString(stringData.getValue());
// publish if the paired partner is available
if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
LocalLinkMessage myProd =
new LocalLinkMessage(new byte[] { 0 }, 0,
BidibLibrary.BIDIB_LINK_DESCRIPTOR_PROD_STRING,
serverLinkData.getProdString());
LOGGER.info("Publish the product string: {}", myProd);
publishMessage(ctx, myProd);
}
}
break;
case StringData.INDEX_USERNAME:
synchronized (pairedPartnerLock) {
publish = serverLinkData.getUserString() == null;
serverLinkData.setUserString(stringData.getValue());
if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
LocalLinkMessage myUser =
new LocalLinkMessage(new byte[] { 0 }, 0,
BidibLibrary.BIDIB_LINK_DESCRIPTOR_USER_STRING,
serverLinkData.getUserString());
LOGGER.info("Publish the user string: {}", myUser);
publishMessage(ctx, myUser);
}
}
break;
default:
break;
}
break;
case BidibLibrary.MSG_SYS_P_VERSION:
SysPVersionResponse pVersionResponse = (SysPVersionResponse) message;
synchronized (pairedPartnerLock) {
publish = serverLinkData.getProtocolVersion() == null;
serverLinkData.setProtocolVersion(pVersionResponse.getVersion());
if (pairedPartner != null && pairedPartner.getUniqueId() != null && publish) {
LocalLinkMessage myProtocolVersion =
new LocalLinkMessage(new byte[] { 0 }, 0, BidibLibrary.BIDIB_LINK_DESCRIPTOR_P_VERSION,
serverLinkData.getProtocolVersion());
LOGGER.info("Publish the protocol version: {}", myProtocolVersion);
publishMessage(ctx, myProtocolVersion);
}
sendPairingStatusIfRequired = true;
}
break;
default:
break;
}
}
catch (ProtocolException ex) {
LOGGER.warn("Create message from wrapped message data failed: {}", ex.getMessage());
throw new ProtocolInvalidContentException("Create message from wrapped message data failed.", ex);
}
catch (Exception ex) {
LOGGER.warn("Process the LocalBidibUpResponse message failed.", ex);
throw new ProtocolException("Process the LocalBidibUpResponse message failed.");
}
return sendPairingStatusIfRequired;
}
/**
* Publish the message to the connected host.
*
* @param ctx
* the context
* @param message
* the message
*/
private void publishMessage(final ChannelHandlerContext ctx, final BidibMessageInterface message) {
LOGGER.info("Publish the message to channel: {}", message);
byte[] msg = message.getContent();
// this.socketChannel.writeAndFlush(Unpooled.copiedBuffer(msg));
channelGroup.iterator().next().writeAndFlush(Unpooled.copiedBuffer(msg));
LOGGER.info("Write message to socketChannel has finished, msg: {}", ByteUtils.bytesToHex(msg));
// ctx.channel().writeAndFlush(Unpooled.copiedBuffer(msg));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
LOGGER.debug("channelReadComplete.");
// ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.warn("Exception in handler. Close the channelHandlerContext.", cause);
if (cause instanceof IOException) {
LOGGER.warn("IOException: {}", cause.getMessage());
}
else {
LOGGER.warn("Exception caught in server handler. Will close connection.", cause);
}
ctx.channel().attr(ConnectionState.STATE_KEY).set(ConnectionState.Phase.NOT_CONNECTED);
ctx.close();
LOGGER.info("Clear the info of the paired partner.");
synchronized (pairedPartnerLock) {
pairedPartner.clear(true);
}
}
/**
* Returns true if currently an active connection exists.
*
* @return channel group has an active connection
*/
public boolean hasActiveConnection() {
return !channelGroup.isEmpty();
}
// public void setPairedPartner(NetBidibLinkData pairedPartner) {
// LOGGER.info("Set the new paired partner: {}", pairedPartner);
//
// synchronized (pairedPartnerLock) {
// this.pairedPartner = pairedPartner;
// }
//
// }
}