/* ************************************************************************ # # 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.channel.ChannelFuture; import divconq.net.ssl.SslHandler; 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 StreamSession { protected Channel chan = null; protected HubRouter router = null; protected SocketInfo info = null; protected boolean isServerConnector = false; protected String mode = null; protected long written = 0; protected long read = 0; public long getWritten() { return this.written; } public long getRead() { return this.read; } 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 StreamSession(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(StreamMessage m) { try { if (this.chan != null) { Logger.trace("Stream session " + this + " sending message : " + m); //m.retain(); ChannelFuture cf = this.chan.writeAndFlush(m).sync(); // we overrun the queue if we don't sync Logger.trace("Stream session " + this + " sent message : " + m); // record how much we wrote to this channel if (cf.isSuccess() && (m.getData() != null)) this.written += m.getData().writerIndex(); // 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 stream message: " + m); Logger.error("Error writing stream message: " + x); x.printStackTrace(); // TODO close channel ? } finally { //m.release(); // if the data is on the network we don't need the buffer anymore -- release in stream encoder } Logger.error("Could not write stream message"); return false; } public void keepAlive() { try { if (this.chan != null) { Logger.trace("Stream session keep alive"); StreamMessage m = Hub.instance.getBus().getLocalHub().buildStreamHello(OperationContext.getHubId()); this.chan.writeAndFlush(m); } } catch (Exception x) { System.out.println("Error writing keep alive stream 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); } // TODO from this point on StreamMessage needs to be released public void receiveMessage(StreamSession session, Channel ch, StreamMessage msg) { OperationContext.useHubContext(); Logger.trace("Stream session " + this + " got message : " + msg); // record how much we read from this channel if (msg.getData() != null) this.read += msg.getData().writerIndex(); if ("HELLO".equals(msg.getFieldAsString("Op"))) { String rhid = msg.getFieldAsString("Id"); if (OperationContext.getHubId().equals(rhid)) { System.out.println("dcBus stream " + this.getSessionMode() + " tried to connect to self, got: " + msg); msg.release(); 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 stream " + this.getSessionMode() + " tried to connect to " + expectedhubid + ", got: " + msg); msg.release(); 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.chan = ch; this.router = Hub.instance.getBus().allocateOrGetHub(rhid, session.getSocketInfo().isGateway()); this.router.addSession(this); Logger.info("dcBus stream " + this.getSessionMode() + " Greeted!"); // only server replies to HELLO, client started it if (this.isServerConnector) { // only the "server" side responds with HELLO // Send HELLO to server to initial sequence of identity and service indexing System.out.println("dcBus stream " + this.getSessionMode() + " sending HELLO"); StreamMessage icmd = Hub.instance.getBus().getLocalHub().buildStreamHello(rhid); this.write(icmd); } } } } // only accept HELLO messages until we get a valid one if (!this.isInitialized()) { System.out.println("dcBus stream " + this.getSessionMode() + " expceted HELLO message, got: " + msg); ch.close(); // don't stay with bad messages msg.release(); return; } this.router.receiveMessage(session, msg); } }