/* ************************************************************************
#
# DivConq
#
# http://divconq.com/
#
# Copyright:
# Copyright 2014 eTimeline, LLC. All rights reserved.
#
# License:
# See the license.txt file in the project's top-level directory for details.
#
# Authors:
# * Andy White
#
************************************************************************ */
package divconq.bus.net;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import divconq.net.ssl.SslHandler;
import divconq.bus.Message;
import divconq.bus.HubRouter;
import divconq.hub.Hub;
import divconq.lang.op.OperationContext;
import divconq.log.Logger;
import divconq.util.KeyUtil;
import divconq.util.StringUtil;
public class Session {
protected Channel chan = null;
protected HubRouter router = null;
protected SocketInfo info = null;
protected boolean isServerConnector = false;
protected String mode = null;
public SocketInfo getSocketInfo() {
return this.info;
}
public Channel getChannel() {
return this.chan;
}
public void setChannel(Channel chan) {
this.chan = chan;
}
public HubRouter getRouter() {
return this.router;
}
public void setRouter(HubRouter router) {
this.router = router;
}
public boolean isServerConnector() {
return this.isServerConnector;
}
public String getSessionMode() {
return this.mode;
}
public Session(SocketInfo info, boolean isServer) {
this.info = info;
this.isServerConnector = isServer;
this.mode = isServer ? "Server" : "Client";
}
public void close() {
try {
if (this.chan != null)
this.chan.close().await(2000);
}
catch (InterruptedException x) {
// ignore
}
}
// should already have TaskContext if needed (SERVICES and HELLO do not need task context)
public boolean write(Message m) {
try {
//if ((this.chan != null) && this.chan.isWritable()) {
if (this.chan != null) {
this.chan.writeAndFlush(new TextWebSocketFrame(m.toString())); // TODO shouldn't need this.sync(); // TODO prefer another approach that doesn't block...
// if we get here there is a decent chance the message was sent (no proof, just good chance)
return true;
}
}
catch (Exception x) {
Logger.error("Error writing bus message: " + m);
Logger.error("Error writing bus message: " + x);
x.printStackTrace();
// TODO close channel ?
}
Logger.error("Could not write bus message");
return false;
}
public void keepAlive() {
try {
if (this.chan != null) {
Logger.trace("dcBus session keep alive");
Message m = Hub.instance.getBus().getLocalHub().buildHello(OperationContext.getHubId());
this.chan.writeAndFlush(new TextWebSocketFrame(m.toString()));
}
}
catch (Exception x) {
System.out.println("Error writing keep alive message: " + x);
// TODO close channel ?
}
}
public String getAttribute(String string) {
// TODO Auto-generated method stub
return null;
}
public void closed() {
if (this.router != null)
this.router.removeSession(this);
}
public boolean isInitialized() {
return (this.router != null);
}
public void receiveMessage(Session session, Channel ch, Message msg) {
if ("HELLO".equals(msg.getFieldAsString("Kind"))) {
String rhid = msg.getFieldAsString("Id");
if (OperationContext.getHubId().equals(rhid)) {
System.out.println("dcBus " + this.getSessionMode() + " tried to connect to self, got: " + msg);
ch.close(); // don't stay with bad messages
return;
}
String expectedhubid = this.info.getHubId();
if (StringUtil.isNotEmpty(expectedhubid) && !expectedhubid.equals(rhid)) {
System.out.println("dcBus " + this.getSessionMode() + " tried to connect to " + expectedhubid + ", got: " + msg);
ch.close(); // don't stay with bad messages
return;
}
if (StringUtil.isDataInteger(rhid) && (rhid.length() == 5)) {
if (!this.isInitialized()) {
if (this.info.isUseSsl()) {
// TODO maybe check the getPeerCertificateChain to see if the hubid and cert match up
try {
// ensure that connection has a client cert - if so it has gone through trust manager so we don't
// need to verify cert, just be sure there is one
// if client does not present a cert we could still get this far
SslHandler sh = (SslHandler) ch.pipeline().get("ssl");
// TODO store in member var
X509Certificate[] xcerts = sh.engine().getSession().getPeerCertificateChain();
// TODO log not sysout
for (X509Certificate xc : xcerts)
System.out.println("confirmed cert is present: " + xc.getSubjectDN());
if (StringUtil.isNotEmpty(this.info.getTargetthumbprint())) {
String expected = this.info.getTargetthumbprint();
String got = KeyUtil.getCertThumbprint(xcerts[0]);
if (!expected.equals(got))
throw new SSLPeerUnverifiedException("Certificate does not match expected thumbprint: " + got);
}
}
catch (SSLPeerUnverifiedException x) {
// TODO log, count, raise EVENT
System.err.println("Peer Cert Error connecting dcBus " + this.getSessionMode() + ": " + x);
ch.close(); // don't stay with bad messages
return;
}
}
this.router = Hub.instance.getBus().allocateOrGetHub(rhid, session.getSocketInfo().isGateway());
this.router.addSession(this);
this.chan = ch;
System.out.println("dcBus " + this.getSessionMode() + " Greeted!");
// only server replies to HELLO, client started it
if (this.isServerConnector) {
// Send HELLO to server to initial sequence of identity and service indexing
System.out.println("dcBus " + this.getSessionMode() + " sending HELLO");
Message icmd = Hub.instance.getBus().getLocalHub().buildHello(rhid);
this.write(icmd);
}
}
}
}
// only accept HELLO messages until we get a valid one
if (!this.isInitialized()) {
System.out.println("dcBus " + this.getSessionMode() + " expceted HELLO message, got: " + msg);
ch.close(); // don't stay with bad messages
return;
}
this.router.receiveMessage(session, msg);
}
}