package org.scribble.net.session; import java.io.IOException; import java.nio.channels.SelectionKey; import java.security.GeneralSecurityException; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.scribble.main.RuntimeScribbleException; import org.scribble.main.ScribbleRuntimeException; import org.scribble.net.Buf; import org.scribble.net.ScribMessageFormatter; import org.scribble.net.scribsock.ScribServerSocket; import org.scribble.sesstype.name.Role; // FIXME: factor out between role-endpoint based socket and channel-endpoint sockets //.. initiator and joiner endpoints public abstract class SessionEndpoint<S extends Session, R extends Role> implements AutoCloseable { public final Buf<?> gc = new Buf<>(); public final Session sess; public final Role self; public final ScribMessageFormatter smf; protected boolean init = false; //private boolean bound = false; private boolean complete = false; private boolean closed = false; protected final Map<Role, ScribServerSocket> servs = new HashMap<>(); // For multi-role endpoints? // Or currently only self is used as a key, but should be peer roles instead? // Generally, think was in the middle of refactoring for explicit connections protected final Map<Role, BinaryChannelEndpoint> chans = new HashMap<>(); private final ScribInputSelector sel; private final Map<Role, SelectionKey> keys = new HashMap<>(); public SessionEndpoint(S sess, R self, ScribMessageFormatter smf) throws IOException, ScribbleRuntimeException { this.sess = sess; this.self = self; this.smf = smf; sess.project(this); this.sel = new ScribInputSelector(this); this.sel.start(); } /*// FIXME: generalise roles and server socks for endpoints playing multiple roles public SessionEndpoint(Session sess, Role self, ScribMessageFormatter smf, ScribServerSocket ss) throws IOException, ScribbleRuntimeException { this(sess, self, smf); //register(self, ss); this.sel.pause(); this.servs.put(self, ss); this.sel.unpause(); } // FIXME: allowing multiple serversocks to be registered, but it just overrides the self entry (already set by constructor) -- server socks not identified with peer (or protocol state) // -- and InitSocket accept always uses the self server sock //public synchronized void register(Role self, ScribServerSocket ss) throws IOException, ScribbleRuntimeException // TODO: was refactoring for explicit connections protected synchronized void register(Role self, ScribServerSocket ss) throws IOException, ScribbleRuntimeException { this.sel.pause(); this.servs.put(self, ss); this.sel.unpause(); }*/ /*public synchronized void register(Role peer, String host, int port) throws IOException { throw new RuntimeException("TODO: " + host + ", " + port); }*/ public synchronized void register(Role peer, BinaryChannelEndpoint c) throws IOException { this.sel.pause(); SelectionKey key = this.sel.register(c.getSelectableChannel()); key.attach(peer); this.keys.put(peer, key); this.chans.put(peer, c); this.sel.unpause(); } // w is uninitialised (need to use wrapChannel) public synchronized void reregister(Role peer, BinaryChannelWrapper w) throws IOException, GeneralSecurityException { /*this.sel.pause(); SelectionKey old = getChannelEndpoint(peer).getSelectableChannel().keyFor(this.sel.getSelector()); if (old == null) { throw new RuntimeException("Endpoint not yet registered for: " + peer); } old.cancel(); SelectionKey key = this.sel.register(c.getSelectableChannel()); key.attach(peer); this.chans.put(peer, c); this.sel.unpause();*/ BinaryChannelEndpoint c = getChannelEndpoint(peer); c.sync(); this.sel.pause(); // FIXME: consume all pending messages/futures first? or ok to leave data in existing bb -- or only permit in states where bb is empty? // Underlying selectable channel is the same, so no need to cancel key and re-reg -- OK to assume in general? w.wrapChannel(c); w.clientHandshake(); this.chans.put(peer, w); // SelectoinKey unchanged? this.sel.unpause(); } public synchronized void deregister(Role peer) throws IOException { this.sel.pause(); try { //this.keys.remove(peer).cancel(); this.sel.deregister(this.keys.remove(peer)); this.chans.remove(peer).close(); } finally { this.sel.unpause(); } } public ScribInputSelector getSelector() { return this.sel; } public BinaryChannelEndpoint getChannelEndpoint(Role role) { if (!this.chans.containsKey(role)) { throw new RuntimeScribbleException(this.self + " is not connected to: " + role); } return this.chans.get(role); } public void setCompleted() { this.complete = true; } public boolean isCompleted() { return this.complete; } @Override public synchronized void close() throws ScribbleRuntimeException { if (!this.closed) { try { this.closed = true; this.sel.close(); this.servs.values().stream().forEach((ss) -> ss.unbind()); } finally { if (!isCompleted()) // Subsumes use -- must be used for sess to be completed { throw new ScribbleRuntimeException("Session not completed: " + this.self); } } } } public ScribServerSocket getSelfServerSocket() { ScribServerSocket ss = this.servs.get(this.self); if (ss == null) { throw new RuntimeScribbleException("No server registered."); } return ss; } protected Set<Role> getPeers() { return this.chans.keySet(); } /*public void connect(Role role, Callable<? extends BinaryChannelEndpoint> cons, String host, int port) throws ScribbleRuntimeException, UnknownHostException, IOException { // Can connect unlimited, as long as not already used via init if (this.init) { throw new ScribbleRuntimeException("Socket already initialised: " + this.getClass()); } if (this.chans.containsKey(role)) { throw new ScribbleRuntimeException("Already connected to: " + role); } try { BinaryChannelEndpoint c = cons.call(); c.initClient(this, host, port); register(role, c); } catch (Exception e) { if (e instanceof IOException) { throw (IOException) e; } throw new IOException(e); } } public void accept(ScribServerSocket ss, Role role) throws IOException, ScribbleRuntimeException { if (this.init) { throw new ScribbleRuntimeException("Socket already initialised: " + this.getClass()); } if (this.chans.containsKey(role)) { throw new ScribbleRuntimeException("Already connected to: " + role); } register(role, ss.accept(this)); // FIXME: serv map in SessionEndpoint not currently used } public void disconnect(Role role) throws IOException, ScribbleRuntimeException { if (!this.chans.containsKey(role)) { throw new ScribbleRuntimeException("Not connected to: " + role); } deregister(role); }*/ /*public void init() { this.initialised = true; }*/ /*protected boolean isInitialised() { return this.initialised; }*/ //public void bind() throws ScribbleRuntimeException public void init() throws ScribbleRuntimeException { /*if (this.bound) { throw new ScribbleRuntimeException("Session endpoint already bound."); }*/ //if (!this.initialised) if (this.init) { throw new ScribbleRuntimeException("Session endpoint already initialised."); } //this.bound = true; this.init = true; } /*public final Session sess; //public final Principal self; public final Role self; public final ScribMessageFormatter smf; private boolean complete = false; private final Map<Role, SocketEndpoint> socks = new HashMap<>(); // Includes SelfSocketEndpoint private final EndpointInputQueues queues = new EndpointInputQueues(); //protected final LocalProtocolDecl root; //public final Monitor monitor; protected SessionEndpoint(Session sess, Role self, ScribMessageFormatter smf) //throws ScribbleException, IOException { this.sess = sess; this.self = self; this.smf = smf; /*ProtocolName lpn = Projector.makeProjectedProtocolName(sess.proto, this.self.role); this.root = (LocalProtocolDecl) projections.get(lpn.getPrefix()).protos.get(0);* / //this.monitor = createMonitor(sess.impath, sess.source, sess.proto, self); } public void setCompleted() { this.complete = true; } public boolean isCompleted() { return this.complete; } // Only for remote endpoints (self SocketEndpoint is done in above constructor; but not recorded in role-principal map) public void register(Role peer, SocketWrapper sw) //throws IOException { this.socks.put(peer, new SocketEndpoint(this, peer, sw)); } /*public Set<Role> getRemoteRoles() { return this.remroles.keySet(); } public Principal getRemotePrincipal(Role role) { return this.remroles.get(role); }* / public SocketEndpoint getSocketEndpoint(Role role) { if (!this.socks.containsKey(role)) { throw new RuntimeScribbleException(this.self + " is not connected to: " + role); } return this.socks.get(role); } public EndpointInputQueues getInputQueues() { return this.queues; } public void close() { for (SocketEndpoint se : this.socks.values()) { try { se.close(); } catch (IOException e) { // FIXME: } } } /* // proto is fullname private static Monitor createMonitor(List<String> impath, String source, ProtocolName proto, Role self) throws ScribbleException, IOException { // FIXME: API (strings vs non-strings, simple vs full names) Job job = Tool.getWellFormedJob(impath, source); Map<ModuleName, Module> projections = Tool.getProjections(job, proto.getSimpleName().toString(), self.toString()); Map<ProtocolName, ProtocolState> graphs = Tool.buildGraphs(job, projections); ProtocolName lpn = Projector.makeProjectedProtocolName(proto, self); System.out.println("[DEBUG] Projected graph: " + graphs.get(lpn).toDot()); return new Monitor(graphs, lpn); }*/ /*public Set<Role> getRemoteRoles() { return this.remroles.keySet(); } public Principal getRemotePrincipal(Role role) { return this.remroles.get(role); }*/ }