public void initialize() { lock = LockFactory.makeLock("BaseBehaviorLock"); long oid = obj.getOid(); SubjectFilter filter = new SubjectFilter(oid); filter.addType(Behavior.MSG_TYPE_COMMAND); filter.addType(WorldManagerClient.MSG_TYPE_MOB_PATH_CORRECTION); pathState = new PathState(oid, pathObjectTypeName, true); commandSub = Engine.getAgent().createSubscription(filter, this); }
/** * Manages hooks for processing messages coming in from a subscription. Hooks are associated with * message types, which is a property in a message. * * <p>The EnginePlugin's onMessage() callback processes an incoming message by calling into its * local HookManager and finding all matching hooks for the incoming message's message type for all * the hooks. For each matching hook, it calls the hook's processMessage() method. * * @see EnginePlugin#handleMessageImpl */ public class HookManager { /** * Normally created by the EnginePlugin class. * * <p>In order to avoid copying the list when getHooks is called, we copy it when we're adding to * it. We do lock around addHook, so that different callers to addHook are synchronized against * each other, but we don't lock in getHooks. */ public HookManager() {} /** * Adds a hook to the HookManager. You can associate more than one hook with a given message type * which will be returned in order by getHooks(). * * @param msgType the message type to match * @param hook the hook to be called for matching messages * @see EnginePlugin#handleMessageImpl */ public void addHook(MessageType msgType, Hook hook) { lock.lock(); try { List<Hook> hookList = hooks.get(msgType); if (hookList == null) { hookList = new LinkedList<Hook>(); hookList.add(hook); hooks.put(msgType, hookList); } else { hookList = new LinkedList<Hook>(hookList); hookList.add(hook); hooks.put(msgType, hookList); } } finally { lock.unlock(); } } /** * Returns this list of all hooks matching the message type. They are returned in the order they * were added. The returned list should be treated as read-only. * * @param msgType the message type to match * @return A list of all hooks matching the passed in message type. */ public List<Hook> getHooks(MessageType msgType) { List<Hook> hookList = hooks.get(msgType); if (hookList == null) return nullList; else return hookList; } private LinkedList<Hook> nullList = new LinkedList<Hook>(); private Lock lock = LockFactory.makeLock("HookManager"); private Map<MessageType, List<Hook>> hooks = new HashMap<MessageType, List<Hook>>(); }
/** * This class is similar to the ObjectTracker, but is used in cases where there is no local/remote * distinction between objects. Instead, the class maintains it's own set of InterpolatedWorldNodes, * and an update thread to keep them current, and a mapping from perceiver objects to perceived * objects. When one of those objects moves, the mapping is updated, and if they have moved in or * out of range, the class either sends a NotifyReactionRadius message, or */ public class ProximityTracker implements MessageDispatch { public ProximityTracker(Namespace namespace, long instanceOid) { initialize(namespace, instanceOid); } public ProximityTracker( Namespace namespace, long instanceOid, float hystericalMargin, ObjectTracker.NotifyReactionRadiusCallback notifyCallback, ObjectTracker.RemoteObjectFilter remoteObjectFilter) { this.hystericalMargin = hystericalMargin; this.notifyCallback = notifyCallback; this.remoteObjectFilter = remoteObjectFilter; initialize(namespace, instanceOid); } private void initialize(Namespace namespace, long instanceOid) { this.namespace = namespace; this.instanceOid = instanceOid; updater = new Updater(); Thread updaterThread = new Thread(updater); updaterThread.start(); } public long getInstanceOid() { return instanceOid; } public void addTrackedPerceiver( Long perceiverOid, InterpolatedWorldNode wnode, Integer reactionRadius) { lock.lock(); try { if (perceiverDataMap.containsKey(perceiverOid)) { // Don't add the object more than once. Log.error( "ProximityTracker.addTrackedPerceiver: perceiverOid " + perceiverOid + " is already in the set of local objects, for ProximityTracker instance " + this); return; } PerceiverData perceiverData = new PerceiverData(perceiverOid, reactionRadius, wnode); perceiverDataMap.put(perceiverOid, perceiverData); } finally { lock.unlock(); } if (Log.loggingDebug) Log.debug( "ProximityTracker.addTrackedPerceiver: perceiverOid=" + perceiverOid + " reactionRadius=" + reactionRadius + " instanceOid=" + instanceOid); } public boolean hasTrackedPerceiver(Long oid) { lock.lock(); try { return perceiverDataMap.containsKey(oid); } finally { lock.unlock(); } } public void removeTrackedPerceiver(Long perceiverOid) { lock.lock(); try { // Iterate over perceived objects, removing our // perceiverOid from their oid sets. PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData != null) { if (Log.loggingDebug) Log.debug( "ProximityTracker.removeTrackedPerceiver: perceiverOid " + perceiverOid + ", inRangeOids count " + perceiverData.inRangeOids.size()); // Iterate over perceived objects, removing our // perceiverOid from their oid sets. for (Long perceivedOid : perceiverData.perceivedOids) { PerceiverData perceivedData = perceiverDataMap.get(perceivedOid); if (perceivedData != null) { perceivedData.perceivedOids.remove(perceiverOid); if (perceivedData.inRangeOids.contains(perceiverOid)) { perceivedData.inRangeOids.remove(perceiverOid); performNotification(perceiverOid, perceivedOid, false, true); } } } perceiverData.perceivedOids.clear(); perceiverData.inRangeOids.clear(); perceiverDataMap.remove(perceiverOid); } else Log.warn( "ProximityTracker.removeTrackedPerceiver: For oid=" + perceiverOid + ", didn't find PerceiverData"); } finally { lock.unlock(); } if (Log.loggingDebug) Log.debug( "ProximityTracker.removeTrackedPerceiver: oid=" + perceiverOid + " instanceOid=" + instanceOid); } public List<Long> getOidsInRadius(long perceiverOid) { lock.lock(); try { PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData == null) { Log.error( "ProximityTracker.getOidsInRadius: perceptionData for oid " + perceiverOid + " is null"); return new LinkedList<Long>(); } else return new LinkedList<Long>(perceiverData.inRangeOids); } finally { lock.unlock(); } } public void dispatchMessage(Message message, int flags, MessageCallback callback) { Engine.defaultDispatchMessage(message, flags, callback); } protected boolean maybeAddPerceivedObject(PerceptionMessage.ObjectNote objectNote) { ObjectType objType = (ObjectType) objectNote.getObjectType(); long perceivedOid = objectNote.getSubject(); long perceiverOid = objectNote.getTarget(); if (perceivedOid == perceiverOid) return true; boolean callbackNixedIt = false; if (remoteObjectFilter != null) callbackNixedIt = !remoteObjectFilter.objectShouldBeTracked(perceivedOid, objectNote); if (callbackNixedIt || !(objType.isMob())) { // if (Log.loggingDebug) // Log.debug("ProximityTracker.maybeAddPerceivedObject: ignoring oid=" + // perceivedOid // + " objType=" + objType // + " detected by " + perceiverOid // + ", instanceOid=" + instanceOid); return false; } if (Log.loggingDebug) Log.debug( "ProximityTracker.maybeAddPerceivedObject: oid=" + perceivedOid + " objType=" + objType + " detected by " + perceiverOid + ", instanceOid=" + instanceOid); lock.lock(); try { PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData == null) { Log.error( "ProximityTracker.maybeAddPerceivedObject: got perception msg with perceived obj oid=" + perceivedOid + " for unknown perceiver=" + perceiverOid); return false; } perceiverData.perceivedOids.add(perceivedOid); PerceiverData perceivedData = perceiverDataMap.get(perceivedOid); if (perceivedData != null) testProximity(perceiverData, perceivedData, true, false); } finally { lock.unlock(); } return true; } /** * Test if the perceived object has come in or out of range of the perceiver object; if so, we * change the inRangeOids set for the perceiver, and notify the perceiver. */ protected void testProximity( PerceiverData perceiverData, PerceiverData perceivedData, boolean interpolatePerceiver, boolean interpolatePerceived) { Point perceiverLoc = interpolatePerceiver ? perceiverData.wnode.getLoc() : perceiverData.lastLoc; Point perceivedLoc = interpolatePerceived ? perceivedData.wnode.getLoc() : perceivedData.lastLoc; float distance = Point.distanceTo(perceiverLoc, perceivedLoc); float reactionRadius = perceiverData.reactionRadius; long perceiverInstance = perceiverData.wnode.getInstanceOid(); long perceivedInstance = perceivedData.wnode.getInstanceOid(); boolean sameInstance = perceiverInstance == perceivedInstance; boolean inRadius = sameInstance && (distance < reactionRadius); boolean wasInRadius = perceiverData.inRangeOids.contains(perceivedData.perceiverOid); // if (Log.loggingDebug) // Log.debug("ProximityTracker.testProximity: perceiver " + // perceiverData.perceiverOid + ", perceiverLoc = " + perceiverLoc + // ", perceived " + perceivedData.perceiverOid + ", perceivedLoc = " + // perceivedLoc + // ", distance " + distance + ", reactionRadius " + reactionRadius + ", // perceiverInstance " + perceiverInstance + // ", perceivedInstance " + perceivedInstance + ", inRadius " + inRadius + ", // wasInRadius " + wasInRadius); if (inRadius == wasInRadius) return; if (sameInstance && hystericalMargin != 0f) { if (wasInRadius) inRadius = distance < (reactionRadius + hystericalMargin); else inRadius = distance < (reactionRadius - hystericalMargin); // If they are the same after hysteresis was applied, skip. if (inRadius == wasInRadius) return; } if (inRadius) { perceiverData.inRangeOids.add(perceivedData.perceiverOid); perceivedData.inRangeOids.add(perceiverData.perceiverOid); } else { perceiverData.inRangeOids.remove(perceivedData.perceiverOid); perceivedData.inRangeOids.remove(perceiverData.perceiverOid); } performNotification( perceiverData.perceiverOid, perceivedData.perceiverOid, inRadius, wasInRadius); } protected void performNotification( long perceiverOid, long perceivedOid, boolean inRadius, boolean wasInRadius) { if (Log.loggingDebug) Log.debug( "ProximityTracker.performNotification: perceiverOid " + perceiverOid + ", perceivedOid " + perceivedOid + ", inRadius " + inRadius + ", wasInRadius " + wasInRadius); if (notifyCallback != null) { notifyCallback.notifyReactionRadius(perceivedOid, perceiverOid, inRadius, wasInRadius); notifyCallback.notifyReactionRadius(perceiverOid, perceivedOid, inRadius, wasInRadius); } else { ObjectTracker.NotifyReactionRadiusMessage nmsg = new ObjectTracker.NotifyReactionRadiusMessage( perceivedOid, perceiverOid, inRadius, wasInRadius); Engine.getAgent().sendBroadcast(nmsg); nmsg = new ObjectTracker.NotifyReactionRadiusMessage( perceiverOid, perceivedOid, inRadius, wasInRadius); Engine.getAgent().sendBroadcast(nmsg); } } protected void updateEntity(PerceiverData perceiverData) { long perceiverOid = perceiverData.perceiverOid; lock.lock(); try { for (long perceivedOid : perceiverData.perceivedOids) { if (perceiverOid == perceivedOid) continue; PerceiverData perceivedData = perceiverDataMap.get(perceivedOid); if (perceivedData != null) testProximity(perceiverData, perceivedData, false, true); } } finally { lock.unlock(); } } public void handlePerception(PerceptionMessage perceptionMessage) { long targetOid = perceptionMessage.getTarget(); List<PerceptionMessage.ObjectNote> gain = perceptionMessage.getGainObjects(); List<PerceptionMessage.ObjectNote> lost = perceptionMessage.getLostObjects(); if (Log.loggingDebug) Log.debug( "ProximityTracker.handlePerception: targetOid + " + targetOid + ", instanceOid=" + instanceOid + " " + ((gain == null) ? 0 : gain.size()) + " gain and " + ((lost == null) ? 0 : lost.size()) + " lost"); if (gain != null) for (PerceptionMessage.ObjectNote note : gain) maybeAddPerceivedObject(note); if (lost != null) for (PerceptionMessage.ObjectNote note : lost) maybeRemovePerceivedObject(note.getSubject(), note, targetOid); } public void handleUpdateWorldNode(long oid, WorldManagerClient.UpdateWorldNodeMessage wnodeMsg) { PerceiverData perceiverData = perceiverDataMap.get(oid); if (perceiverData == null) { if (Log.loggingDebug) Log.debug( "ProximityTracker.handleMessage: ignoring updateWNMsg for oid " + oid + " because PerceptionData for oid not found"); return; } BasicWorldNode bwnode = wnodeMsg.getWorldNode(); if (Log.loggingDebug) Log.debug( "ProximityTracker.handleMessage: UpdateWnode for " + oid + ", loc " + bwnode.getLoc() + ", dir " + bwnode.getDir()); if (perceiverData.wnode != null) { perceiverData.previousLoc = perceiverData.lastLoc; perceiverData.wnode.setDirLocOrient(bwnode); perceiverData.wnode.setInstanceOid(bwnode.getInstanceOid()); perceiverData.lastLoc = perceiverData.wnode.getLoc(); } else Log.error( "ProximityTracker.handleMessage: In UpdateWorldNodeMessage for oid " + oid + ", perceiverData.wnode is null!"); updateEntity(perceiverData); } protected void maybeRemovePerceivedObject( long perceivedOid, PerceptionMessage.ObjectNote objectNote, long perceiverOid) { if (remoteObjectFilter != null && remoteObjectFilter.objectShouldBeTracked(perceivedOid, objectNote)) return; else removePerceivedObject(perceiverOid, perceivedOid); } protected void removePerceivedObject(long perceiverOid, long perceivedOid) { lock.lock(); try { PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData == null) { if (Log.loggingDebug) Log.debug( "ProximityTracker.removePerceivedObject: No perceiverData for oid " + perceiverOid); return; } perceiverData.perceivedOids.remove(perceivedOid); if (perceiverData.inRangeOids.contains(perceivedOid)) { performNotification(perceiverOid, perceivedOid, true, false); perceiverData.inRangeOids.remove(perceivedOid); } } finally { lock.unlock(); } } class Updater implements Runnable { public void run() { while (running) { try { update(); } catch (MVRuntimeException e) { Log.exception("ProximityTracker.Updater.run caught MVRuntimeException", e); } catch (Exception e) { Log.exception("ProximityTracker.Updater.run caught exception", e); } try { Thread.sleep(1000); } catch (InterruptedException e) { Log.warn("Updater: " + e); e.printStackTrace(); } } } protected void update() { Log.debug("Updater.update: in update"); List<Long> perceiverOids = null; lock.lock(); try { perceiverOids = new ArrayList<Long>(perceiverDataMap.keySet()); } finally { lock.unlock(); } // We loop over the copied perceiverOids causing // interpolation to happen, and capturing the location in // the PerceiverData, so we can later do comparisons // cheaply. Note that underlying map can change while // we're doing so, so we don't raise errors if it happens. for (long perceiverOid : perceiverOids) { PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData != null) { perceiverData.previousLoc = perceiverData.lastLoc; // long lastInterp = perceiverData.wnode.getLastInterp(); perceiverData.lastLoc = perceiverData.wnode.getLoc(); // if (Log.loggingDebug) // Log.debug("Updater.update: perceiverOid " + perceiverOid + ", // previousLoc " + perceiverData.previousLoc + // ", lastLoc " + perceiverData.lastLoc + ", time since interp // " + (System.currentTimeMillis() - lastInterp)); } } // Now actually do the double loop to check if inRange has // changed for (long perceiverOid : perceiverOids) { PerceiverData perceiverData = perceiverDataMap.get(perceiverOid); if (perceiverData == null) continue; // If the perceiver hasn't moved much, no need to // iterate over it's perceived entities if (perceiverData.previousLoc != null && Point.distanceToSquared(perceiverData.previousLoc, perceiverData.lastLoc) < 100f) continue; ArrayList<Long> perceivedOids = new ArrayList<Long>(perceiverData.perceivedOids); for (long perceivedOid : perceivedOids) { PerceiverData perceivedData = perceiverDataMap.get(perceivedOid); if (perceivedData == null) continue; // Invoke the testProximity method but tell it not // to interpolate, but instead get its location // from the PerceptionData.lastLoc members testProximity(perceiverData, perceivedData, false, false); } } } } public void setRunning(boolean running) { this.running = running; } protected Namespace namespace; protected long instanceOid; protected float hystericalMargin = 0f; protected ObjectTracker.NotifyReactionRadiusCallback notifyCallback = null; protected ObjectTracker.RemoteObjectFilter remoteObjectFilter = null; protected Updater updater = null; protected Thread updaterThread = null; protected boolean running = true; /** * This maps a perceiver oid into an object containing the list of perceived objects, and the list * of objects in range. */ protected Map<Long, PerceiverData> perceiverDataMap = new HashMap<Long, PerceiverData>(); protected class PerceiverData { long perceiverOid; // The reaction radius to be applied Integer reactionRadius; // The Entity associated with this PerceiverData Entity perceiverEntity; // The world node for this perceiver InterpolatedWorldNode wnode; // The last interpolated location of the entity Point lastLoc; // The previous interpolated location of the entity, used to // detect if the entity has moved Point previousLoc; // The set of object oids perceived by this perceiver Set<Long> perceivedOids = new HashSet<Long>(); // The set of object oids in range of this object Set<Long> inRangeOids = new HashSet<Long>(); public PerceiverData(long perceiverOid, Integer reactionRadius, InterpolatedWorldNode wnode) { this.perceiverOid = perceiverOid; this.reactionRadius = reactionRadius; this.wnode = wnode; this.lastLoc = wnode.getLoc(); } } // protected Map<Long, ObjectTracker.NotifyData> reactionRadiusMap = new HashMap<Long, // ObjectTracker.NotifyData>(); protected Lock lock = LockFactory.makeLock("ProximityTrackerLock"); }
public class RDPServer implements Runnable { RDPServer() {} /** rdpserversocket wants to bind on a local port */ static DatagramChannel bind(Integer port, int receiveBufferSize) throws java.net.BindException, java.io.IOException, java.net.SocketException { lock.lock(); try { // see if there is an existing datagramchannel bound to this port DatagramChannel dc = channelMap.get(port); if (dc != null) { throw new java.net.BindException("RDPServer.bind: port is already used"); } // make a new datagram channel dc = DatagramChannel.open(); dc.configureBlocking(false); dc.socket().setReceiveBufferSize(receiveBufferSize); if (port == null) { if (Log.loggingNet) Log.net("RDPServer.bind: binding to a random system port"); dc.socket().bind(null); } else { if (Log.loggingNet) Log.net("RDPServer.bind: binding to port " + port); dc.socket().bind(new InetSocketAddress(port)); } int resultingPort = dc.socket().getLocalPort(); if (Log.loggingNet) Log.net("RDPServer.bind: resulting port=" + resultingPort); // add the channel to the channel map channelMap.put(resultingPort, dc); if (Log.loggingNet) Log.net("RDPServer.bind: added dc to channel map"); // add the channel to the newChannelsSet // we want to register this channel with the selector // but the selector thread needs to do that, // so place it in this set, and wake up the selector newChannelSet.add(dc); if (Log.loggingNet) Log.net("RDPServer.bind: added dc to newChannelSet"); // in case the rdpserver was waiting while it had no sockets, // signal it channelMapNotEmpty.signal(); Log.net("RDPServer.bind: signalled channel map not empty condition"); // wakeup the selector - // it needs to register the new channel with itself selector.wakeup(); if (Log.loggingNet) Log.net("RDPServer.bind: woke up selector"); return dc; } finally { lock.unlock(); } } /** * assume the socket is already bound, now we need to add it to the socket map * * <p>this map is used when we get a packet and look up the datagramchannel to see if its * associated with a listening socket for a new rdp connection */ static void registerSocket(RDPServerSocket rdpSocket, DatagramChannel dc) { lock.lock(); try { socketMap.put(dc, rdpSocket); } finally { lock.unlock(); } } /** the conn data should already be set (remote addr, etc) */ static void registerConnection(RDPConnection con, DatagramChannel dc) { lock.lock(); try { if (Log.loggingNet) Log.net("RDPServer.registerConnection: registering con " + con); // first we get the set of connections attached to the given dc Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc); if (dcConMap == null) { dcConMap = new HashMap<ConnectionInfo, RDPConnection>(); } // add this connection to the map int localPort = con.getLocalPort(); int remotePort = con.getRemotePort(); InetAddress remoteAddr = con.getRemoteAddr(); ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort); dcConMap.put(conInfo, con); allConMap.put(dc, dcConMap); } finally { lock.unlock(); } } /** * removes this connection from the connections map the datagram channel still sticks around in * case it needs to be reused */ static void removeConnection(RDPConnection con) { lock.lock(); try { if (Log.loggingNet) Log.net("RDPServer.removeConnection: removing con " + con); con.setState(RDPConnection.CLOSED); DatagramChannel dc = con.getDatagramChannel(); // first we get the set of connections attached to the given dc Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc); if (dcConMap == null) { throw new MVRuntimeException("RDPServer.removeConnection: cannot find dc"); } int localPort = con.getLocalPort(); int remotePort = con.getRemotePort(); InetAddress remoteAddr = con.getRemoteAddr(); ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort); Object rv = dcConMap.remove(conInfo); if (rv == null) { throw new MVRuntimeException("RDPServer.removeConnection: could not find the connection"); } // close the datagramchannel if needed // conditions: no other connections on this datagramchannel // no socket listening on this datagramchannel if (dcConMap.isEmpty()) { Log.net("RDPServer.removeConnection: no other connections for this datagramchannel (port)"); // there are no more connections on this datagram channel // check if there is a serversocket listening if (getRDPSocket(dc) == null) { Log.net("RDPServer.removeConnection: no socket listening on this port - closing"); // no socket either, close the datagramchannel dc.socket().close(); channelMap.remove(localPort); Log.net("RDPServer.removeConnection: closed and removed datagramchannel/socket"); } else { Log.net("RDPServer.removeConnection: there is a socket listening on this port"); } } else { Log.net("RDPServer.removeConnection: there are other connections on this port"); } } finally { lock.unlock(); } } // //////////////////////////////////////////////////////////////// // // internal working // // //////////////////////////////////////////////////////////////// /** starts the server listens to incoming packets */ public void run() { try { while (true) { if (Log.loggingNet) Log.net("In RDPServer.run: starting new iteration"); try { Set<DatagramChannel> activeChannels = getActiveChannels(); activeChannelCalls++; Iterator<DatagramChannel> iter = activeChannels.iterator(); while (iter.hasNext()) { DatagramChannel dc = iter.next(); if (Log.loggingNet) Log.net("In RDPServer.run: about to call processActiveChannel"); processActiveChannel(dc); if (Log.loggingNet) Log.net("In RDPServer.run: returned from processActiveChannel"); } } catch (ClosedChannelException ex) { // ignore } catch (Exception e) { Log.exception("RDPServer.run caught exception", e); } } } finally { Log.warn("RDPServer.run: thread exiting"); } } /** * a DatagramChannel has data ready - process all the pending packets, whether its for a * rdpserversocket or rdpconnection. */ void processActiveChannel(DatagramChannel dc) throws ClosedChannelException { RDPPacket packet; int count = 0; // read in the packet try { Set<RDPConnection> needsAckConnections = new HashSet<RDPConnection>(); while ((packet = RDPServer.receivePacket(dc)) != null) { if (Log.loggingNet) Log.net( "RDPServer.processActiveChannel: Starting iteration with count of " + count + " packets"); // see if there is a connection already for this packet InetAddress remoteAddr = packet.getInetAddress(); int remotePort = packet.getPort(); int localPort = dc.socket().getLocalPort(); ConnectionInfo conInfo = new ConnectionInfo(remoteAddr, remotePort, localPort); RDPConnection con = RDPServer.getConnection(dc, conInfo); if (con != null) { if (Log.loggingNet) Log.net("RDPServer.processActiveChannel: found an existing connection: " + con); count++; if (processExistingConnection(con, packet)) needsAckConnections.add(con); // Prevent this from blocking getActiveChannels by // putting an upper bound on the number of packets // processed if (count >= 20) break; continue; } else { Log.net("RDPServer.processActiveChannel: did not find an existing connection"); } // there is no connection, // see if there is a socket listening for new connection RDPServerSocket rdpSocket = RDPServer.getRDPSocket(dc); if (rdpSocket != null) { count++; processNewConnection(rdpSocket, packet); return; } return; } // Finally, send out the acks for (RDPConnection con : needsAckConnections) { RDPPacket replyPacket = new RDPPacket(con); con.sendPacketImmediate(replyPacket, false); } } catch (ClosedChannelException ex) { Log.error("RDPServer.processActiveChannel: ClosedChannel " + dc.socket()); throw ex; } finally { if (Log.loggingNet) Log.net("RDPServer.processActiveChannel: Returning after processing " + count + " packets"); } } /** * there is a socket listening on the port for this packet. process if it is a new connection rdp * packet */ public void processNewConnection(RDPServerSocket serverSocket, RDPPacket packet) { if (Log.loggingNet) Log.net( "processNewConnection: RDPPACKET (localport=" + serverSocket.getPort() + "): " + packet); // int localPort = serverSocket.getPort(); InetAddress remoteAddr = packet.getInetAddress(); int remotePort = packet.getPort(); if (!packet.isSyn()) { // the client is not attemping to start a new connection // send a reset and forget about it Log.debug("socket got non-syn packet, replying with reset: packet=" + packet); RDPPacket rstPacket = RDPPacket.makeRstPacket(); rstPacket.setPort(remotePort); rstPacket.setInetAddress(remoteAddr); RDPServer.sendPacket(serverSocket.getDatagramChannel(), rstPacket); return; } // it is a syn packet, lets make a new connection for it RDPConnection con = new RDPConnection(); DatagramChannel dc = serverSocket.getDatagramChannel(); con.initConnection(dc, packet); // add new connection to allConnectionMap registerConnection(con, dc); // ack it with a syn RDPPacket synPacket = RDPPacket.makeSynPacket(con); con.sendPacketImmediate(synPacket, false); } /** returns a list of rdpserversockets */ Set<DatagramChannel> getActiveChannels() throws InterruptedException, java.io.IOException { lock.lock(); try { while (channelMap.isEmpty()) { channelMapNotEmpty.await(); } } finally { lock.unlock(); } Set<SelectionKey> readyKeys = null; do { lock.lock(); try { if (!newChannelSet.isEmpty()) { if (Log.loggingNet) Log.net("RDPServer.getActiveChannels: newChannelSet is not null"); Iterator<DatagramChannel> iter = newChannelSet.iterator(); while (iter.hasNext()) { DatagramChannel newDC = iter.next(); iter.remove(); newDC.register(selector, SelectionKey.OP_READ); } } } finally { lock.unlock(); } int numReady = selector.select(); // this is a blocking call - thread safe selectCalls++; if (numReady == 0) { if (Log.loggingNet) Log.net("RDPServer.getActiveChannels: selector returned 0"); continue; } readyKeys = selector.selectedKeys(); if (Log.loggingNet) Log.net( "RDPServer.getActiveChannels: called select - # of ready keys = " + readyKeys.size() + " == " + numReady); } while (readyKeys == null || readyKeys.isEmpty()); lock.lock(); try { // get a datagramchannel that is ready Set<DatagramChannel> activeChannels = new HashSet<DatagramChannel>(); Iterator<SelectionKey> iter = readyKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); if (Log.loggingNet) Log.net( "RDPServer.getActiveChannels: matched selectionkey: " + key + ", isAcceptable=" + key.isAcceptable() + ", isReadable=" + key.isReadable() + ", isValid=" + key.isValid() + ", isWritable=" + key.isWritable()); iter.remove(); // remove from the selected key list if (!key.isReadable() || !key.isValid()) { Log.error( "RDPServer.getActiveChannels: Throwing exception: RDPServer: not readable or invalid"); throw new MVRuntimeException("RDPServer: not readable or invalid"); } DatagramChannel dc = (DatagramChannel) key.channel(); activeChannels.add(dc); } if (Log.loggingNet) Log.net( "RDPServer.getActiveChannels: returning " + activeChannels.size() + " active channels"); return activeChannels; } finally { lock.unlock(); } } /** * returns the RDPConnection that is registered for the given datagram channel and is connected to * the host/port in ConnectionInfo returns null if there is no matching registered rdpconnection */ static RDPConnection getConnection(DatagramChannel dc, ConnectionInfo conInfo) { lock.lock(); try { Map<ConnectionInfo, RDPConnection> dcConMap = allConMap.get(dc); if (dcConMap == null) { // there isnt even a datagram associated if (Log.loggingNet) Log.net("RDPServer.getConnection: could not find datagram"); return null; } return dcConMap.get(conInfo); } finally { lock.unlock(); } } static Set<RDPConnection> getAllConnections() { lock.lock(); try { Set<RDPConnection> allCon = new HashSet<RDPConnection>(); Iterator<Map<ConnectionInfo, RDPConnection>> iter = allConMap.values().iterator(); while (iter.hasNext()) { Map<ConnectionInfo, RDPConnection> dcMap = iter.next(); allCon.addAll(dcMap.values()); } return allCon; } finally { lock.unlock(); } } /** * returns the RDPServerSocket that is registered for the given datagramchannel returns null if * none exists */ static RDPServerSocket getRDPSocket(DatagramChannel dc) { lock.lock(); try { return socketMap.get(dc); } finally { lock.unlock(); } } static CountMeter packetCounter = new CountMeter("RDPPacketReceiveCounter"); static CountMeter dataCounter = new CountMeter("RDPPacketReceiveDATA"); /** * we have a packet that belongs to the passed in connection. process the packet for the * connection. It returns true if the connection is open and the packet was a data packet */ boolean processExistingConnection(RDPConnection con, RDPPacket packet) { if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: con state=" + con + ", packet=" + packet); packetCounter.add(); int state = con.getState(); if (state == RDPConnection.LISTEN) { // something is wrong, we shouldn't be here // we get to this method after looking in the connections map // but all LISTEN connections should be listed direct // from serversockets Log.error("RDPServer.processExistingConnection: connection shouldnt be in LISTEN state"); return false; } if (state == RDPConnection.SYN_SENT) { if (!packet.isAck()) { Log.warn("got a non-ack packet when we're in SYN_SENT"); return false; } if (!packet.isSyn()) { Log.warn("got a non-syn packet when we're in SYN_SENT"); return false; } if (Log.loggingNet) Log.net("good: got syn-ack packet in syn_sent"); // make sure its acking our initial segment # if (packet.getAckNum() != con.getInitialSendSeqNum()) { if (Log.loggingNet) Log.net("syn's ack number does not match initial seq #"); return false; } con.setRcvCur(packet.getSeqNum()); con.setRcvIrs(packet.getSeqNum()); con.setMaxSendUnacks(packet.getSendUnacks()); con.setMaxReceiveSegmentSize(packet.getMaxRcvSegmentSize()); con.setSendUnackd(packet.getAckNum() + 1); // ack first before setting state to open // otherwise some other thread will get woken up and send data // before we send the ack if (Log.loggingNet) Log.net("new connection state: " + con); RDPPacket replyPacket = new RDPPacket(con); con.sendPacketImmediate(replyPacket, false); con.setState(RDPConnection.OPEN); return false; } if (state == RDPConnection.SYN_RCVD) { if (packet.getSeqNum() <= con.getRcvIrs()) { Log.error("seqnum is not above rcv initial seq num"); return false; } if (packet.getSeqNum() > (con.getRcvCur() + (con.getRcvMax() * 2))) { Log.error("seqnum is too big"); return false; } if (packet.isAck()) { if (packet.getAckNum() == con.getInitialSendSeqNum()) { if (Log.loggingNet) Log.net("got ack for our syn - setting state to open"); con.setState(RDPConnection.OPEN); // this will notify() // call the accept callback // first find the serversocket DatagramChannel dc = con.getDatagramChannel(); if (dc == null) { throw new MVRuntimeException( "RDPServer.processExistingConnection: no datagramchannel for connection that just turned OPEN"); } RDPServerSocket rdpSocket = RDPServer.getRDPSocket(dc); if (rdpSocket == null) { throw new MVRuntimeException( "RDPServer.processExistingConnection: no socket for connection that just turned OPEN"); } ClientConnection.AcceptCallback acceptCB = rdpSocket.getAcceptCallback(); if (acceptCB != null) { acceptCB.acceptConnection(con); } else { Log.warn("serversocket has no accept callback"); } if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: got ACK, removing from unack list: " + packet.getSeqNum()); con.removeUnackPacket(packet.getSeqNum()); } } } if (state == RDPConnection.CLOSE_WAIT) { // reply with a reset on all packets if (!packet.isRst()) { RDPPacket rstPacket = RDPPacket.makeRstPacket(); con.sendPacketImmediate(rstPacket, false); } } if (state == RDPConnection.OPEN) { if (packet.isRst()) { // the other side wants to close the connection // set the state, // dont call con.close() since that will send a reset packet if (Log.loggingDebug) Log.debug("RDPServer.processExistingConnection: got reset packet for con " + con); if (con.getState() != RDPConnection.CLOSE_WAIT) { con.setState(RDPConnection.CLOSE_WAIT); con.setCloseWaitTimer(); // Only invoke callback when moving into CLOSE_WAIT // state. This prevents two calls to connectionReset. Log.net("RDPServer.processExistingConnection: calling reset callback"); ClientConnection.MessageCallback pcb = con.getCallback(); pcb.connectionReset(con); } return false; } if (packet.isSyn()) { // this will close the connection (put into CLOSE_WAIT) // send a reset packet and call the connectionReset callback Log.error( "RDPServer.processExistingConnection: closing connection because we got a syn packet, con=" + con); con.close(); return false; } // TODO: shouldnt it be ok for it to have same seq num? // if it is a 0 data packet? long rcvCur = con.getRcvCur(); if (packet.getSeqNum() <= rcvCur) { if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: seqnum too small - acking/not process"); if (packet.getData() != null) { if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: sending ack even though seqnum out of range"); RDPPacket replyPacket = new RDPPacket(con); con.sendPacketImmediate(replyPacket, false); } return false; } if (packet.getSeqNum() > (rcvCur + (con.getRcvMax() * 2))) { Log.error("RDPServer.processExistingConnection: seqnum too big - discarding"); return false; } if (packet.isAck()) { if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: processing ack " + packet.getAckNum()); // lock for race condition (read then set) con.getLock().lock(); try { if (packet.getAckNum() >= con.getSendNextSeqNum()) { // acking something we didnt even send yet Log.error( "RDPServer.processExistingConnection: discarding -- got ack #" + packet.getAckNum() + ", but our next send seqnum is " + con.getSendNextSeqNum() + " -- " + con); return false; } if (con.getSendUnackd() <= packet.getAckNum()) { con.setSendUnackd(packet.getAckNum() + 1); if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: updated send_unackd num to " + con.getSendUnackd() + " (one greater than packet ack) - " + con); con.removeUnackPacketUpTo(packet.getAckNum()); } if (packet.isEak()) { List eackList = packet.getEackList(); Iterator iter = eackList.iterator(); while (iter.hasNext()) { Long seqNum = (Long) iter.next(); if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: got EACK: " + seqNum); con.removeUnackPacket(seqNum.longValue()); } } } finally { con.getLock().unlock(); if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: processed ack " + packet.getAckNum()); } } // process the data byte[] data = packet.getData(); if ((data != null) || packet.isNul()) { dataCounter.add(); // lock - since racecondition: we read then set con.getLock().lock(); try { rcvCur = con.getRcvCur(); // update rcvCur if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: rcvcur is " + rcvCur); ClientConnection.MessageCallback pcb = con.getCallback(); if (pcb == null) { Log.warn("RDPServer.processExistingConnection: no packet callback registered"); } // call callback only if we havent seen it already - eackd if (!con.hasEack(packet.getSeqNum())) { if (con.isSequenced()) { // this is a sequential connection, // make sure this is the 'next' packet // is this the next sequential packet if (packet.getSeqNum() == (rcvCur + 1)) { // this is the next packet if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: conn is sequenced and received next packet, rcvCur=" + rcvCur + ", packet=" + packet); if ((pcb != null) && (data != null)) { queueForCallbackProcessing(pcb, con, packet); } } else { // not the next packet, place it in queue if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: conn is sequenced, BUT PACKET is OUT OF ORDER: rcvcur=" + rcvCur + ", packet=" + packet); con.addSequencePacket(packet); } } else { if ((pcb != null) && (data != null)) { // make sure we havent already processed packet queueForCallbackProcessing(pcb, con, packet); } } } else { if (Log.loggingNet) Log.net(con.toString() + " already seen this packet"); } // is this the next sequential packet if (packet.getSeqNum() == (rcvCur + 1)) { con.setRcvCur(rcvCur + 1); if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection RCVD: incremented last sequenced rcvd: " + (rcvCur + 1)); // packet in order - dont add to eack // Take any additional sequential packets off eack long seqNum = rcvCur + 2; while (con.removeEack(seqNum)) { if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: removing/collapsing eack: " + seqNum); con.setRcvCur(seqNum++); } if (con.isSequenced()) { rcvCur++; // since we just process the last one Log.net( "RDPServer.processExistingConnection: connection is sequenced, processing collapsed packets."); // send any saved sequential packets also Iterator iter = con.getSequencePackets().iterator(); while (iter.hasNext()) { RDPPacket p = (RDPPacket) iter.next(); if (Log.loggingNet) Log.net( "rdpserver: stored packet seqnum=" + p.getSeqNum() + ", if equal to (rcvcur + 1)=" + (rcvCur + 1)); if (p.getSeqNum() == (rcvCur + 1)) { Log.net( "RDPServer.processExistingConnection: this is the next packet, processing"); // this is the next packet - update rcvcur rcvCur++; // process this packet Log.net( "RDPServer.processExistingConnection: processing stored sequential packet " + p); byte[] storedData = p.getData(); if (pcb != null && storedData != null) { queueForCallbackProcessing(pcb, con, packet); } iter.remove(); } } } else { if (Log.loggingNet) Log.net("RDPServer.processExistingConnection: connection is not sequenced"); } } else { if (Log.loggingNet) Log.net( "RDPServer.processExistingConnection: RCVD OUT OF ORDER: packet seq#: " + packet.getSeqNum() + ", but last sequential rcvd packet was: " + con.getRcvCur() + " -- not incrementing counter"); if (packet.getSeqNum() > rcvCur) { // must be at least + 2 larger than rcvCur if (Log.loggingNet) Log.net("adding to eack list " + packet); con.addEack(packet); } } } finally { con.getLock().unlock(); } return true; } } return false; } /** reads in an rdp packet from the datagram channel - blocking call */ static RDPPacket receivePacket(DatagramChannel dc) throws ClosedChannelException { try { if (dc == null) { throw new MVRuntimeException("RDPServer.receivePacket: datagramChannel is null"); } // get a packet from the reader staticMVBuff.rewind(); InetSocketAddress addr = (InetSocketAddress) dc.receive(staticMVBuff.getNioBuf()); if (addr == null) { return null; } RDPPacket packet = new RDPPacket(); packet.setPort(addr.getPort()); packet.setInetAddress(addr.getAddress()); packet.parse(staticMVBuff); return packet; } catch (ClosedChannelException ex) { throw ex; } catch (Exception e) { throw new MVRuntimeException("error", e); } } // Only used by receivePacket, which is guaranteed to be single-threaded. private static MVByteBuffer staticMVBuff = new MVByteBuffer(RDPConnection.DefaultMaxReceiveSegmentSize); static String printSocket(DatagramSocket socket) { return "[Socket: localPort=" + socket.getLocalPort() + ", remoteAddr=" + socket.getInetAddress() + ", localAddr=" + socket.getLocalAddress() + "]"; } static CountMeter sendMeter = new CountMeter("RDPSendPacketMeter"); static CountMeter sendDataMeter = new CountMeter("RDPSendDataPacketMeter"); /** make sure the packet as the remote address and remote port set */ static void sendPacket(DatagramChannel dc, RDPPacket packet) { sendMeter.add(); // allocate a buffer int bufSize = 100 + (packet.numEacks() * 4); if (packet.getData() != null) { bufSize += packet.getData().length; sendDataMeter.add(); } MVByteBuffer buf = new MVByteBuffer(bufSize); packet.toByteBuffer(buf); // function flips the buffer int remotePort = packet.getPort(); InetAddress remoteAddr = packet.getInetAddress(); if ((remotePort < 0) || (remoteAddr == null)) { throw new MVRuntimeException("RDPServer.sendPacket: remotePort or addr is null"); } try { int bytes = dc.send(buf.getNioBuf(), new InetSocketAddress(remoteAddr, remotePort)); if (bytes == 0) { Log.error("RDPServer.sendPacket: could not send packet, size=" + bufSize); } if (Log.loggingNet) Log.net( "RDPServer.sendPacket: remoteAddr=" + remoteAddr + ", remotePort=" + remotePort + ", numbytes sent=" + bytes); } catch (java.io.IOException e) { Log.exception( "RDPServer.sendPacket: remoteAddr=" + remoteAddr + ", remotePort=" + remotePort + ", got exception", e); throw new MVRuntimeException("RDPServer.sendPacket", e); } } // //////////////////////////////////////// // // Private Fields // // use 'rdpServer' object as static lock - including for bindmap // and connectionMap static RDPServer rdpServer = new RDPServer(); // localport -> datagramchannel for that port private static Map<Integer, DatagramChannel> channelMap = new HashMap<Integer, DatagramChannel>(); // maps datagramchannel to serversocket // so when we get a new packet on a datagram channel via select() we can // associate it with a server socket and thus its callback private static Map<DatagramChannel, RDPServerSocket> socketMap = new HashMap<DatagramChannel, RDPServerSocket>(); // map of datagram channel to a secondary map of connectioninfo->connection // when we get a packet, we check if it is associated with an existing // connection. but there can be many connections associated with a // single datagramchannel (localport), so we first look up by // datagram channel and then that returns us a second map. // we key into the connectioninfo (which makes a connection unique - // (localport, remoteport, remoteaddr) and then get the single connection private static Map<DatagramChannel, Map<ConnectionInfo, RDPConnection>> allConMap = new HashMap<DatagramChannel, Map<ConnectionInfo, RDPConnection>>(); private static Lock unsentPacketsLock = LockFactory.makeLock("unsentPacketsLock"); static Condition unsentPacketsNotEmpty = unsentPacketsLock.newCondition(); /** set of new datagram channels that need to be registered with the selector */ static Set<DatagramChannel> newChannelSet = new HashSet<DatagramChannel>(); // thread that reads in new packets static Thread rdpServerThread = null; static Thread retryThread = null; static Thread packetCallbackThread = null; // // list of datagramchannels for sockets that are dead and should be // removed // // they became dead when the other side of the connection went away // // in the case of a single user connections // static List<DatagramChannel> deadDatagramChannelList = // new LinkedList<DatagramChannel>(); static Selector selector = null; private static boolean rdpServerStarted = false; public static void startRDPServer() { if (rdpServerStarted) return; rdpServerStarted = true; rdpServerThread = new Thread(rdpServer, "RDPServer"); retryThread = new Thread(new RetryThread(), "RDPRetry"); packetCallbackThread = new Thread(new PacketCallbackThread(), "RDPCallback"); if (Log.loggingNet) Log.net("static - starting rdpserver thread"); try { selector = Selector.open(); } catch (Exception e) { Log.exception("RDPServer caught exception opening selector", e); System.exit(1); } rdpServerThread.setPriority(rdpServerThread.getPriority() + 2); if (Log.loggingDebug) Log.debug( "RDPServer: starting rdpServerThread with priority " + rdpServerThread.getPriority()); rdpServerThread.start(); retryThread.start(); packetCallbackThread.start(); } // used in the TreeSet of connections with pending packets // it has time data associated with the connection, telling us // when we need to re-visit this connection to send its pending // data, in case it was not able to send all of its packets // due to throttling static class RDPConnectionData implements Comparable { public RDPConnection con; public long readyTime; public int compareTo(Object arg0) { RDPConnectionData other = (RDPConnectionData) arg0; if (this.readyTime < other.readyTime) { if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.compareTo: readyTime compare -1: thiscon=" + this.con + ", othercon=" + other.con + ", thisready=" + this.readyTime + ", otherReady=" + other.readyTime); return -1; } else if (this.readyTime > other.readyTime) { if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.compareTo: readyTime compare 1: thiscon=" + this.con + ", othercon=" + other.con + ", thisready=" + this.readyTime + ", otherReady=" + other.readyTime); return 1; } if (this.con == other.con) { if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.compareTo: conRef compare 0: thiscon=" + this.con + ", othercon=" + other.con); return 0; } else if (this.con.hashCode() < other.con.hashCode()) { if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.compareTo: hashCode compare -1: thiscon=" + this.con + ", othercon=" + other.con); return -1; } else if (this.con.hashCode() > other.con.hashCode()) { if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.compareTo: hashCode compare 1: thiscon=" + this.con + ", othercon=" + other.con); return 1; } else { throw new RuntimeException("error"); } } public boolean equals(Object obj) { int rv = this.compareTo(obj); if (Log.loggingNet) Log.net( "RDPServer.RDPConnectionData.equals: thisObj=" + this.toString() + ", other=" + obj.toString() + ", result=" + rv); return (rv == 0); } } // ////////////////////////////////////////////////// // // RETRY THREAD - also handles CLOSE_WAIT connections - to actually close // them // static class RetryThread implements Runnable { public RetryThread() {} public void run() { // every second, go through all the packets that havent been // ack'd List<RDPConnection> conList = new LinkedList<RDPConnection>(); long lastCounterTime = System.currentTimeMillis(); while (true) { try { long startTime = System.currentTimeMillis(); long interval = startTime - lastCounterTime; if (interval > 1000) { if (Log.loggingNet) { Log.net( "RDPServer counters: activeChannelCalls " + activeChannelCalls + ", selectCalls " + selectCalls + ", transmits " + transmits + ", retransmits " + retransmits + " in " + interval + "ms"); } activeChannelCalls = 0; selectCalls = 0; transmits = 0; retransmits = 0; lastCounterTime = startTime; } if (Log.loggingNet) Log.net("RDPServer.RETRY: startTime=" + startTime); // go through all the rdpconnections and re-send any // unacked packets conList.clear(); lock.lock(); try { // make a copy since the values() collection is // backed by the map Set<RDPConnection> conCol = RDPServer.getAllConnections(); if (conCol == null) { throw new MVRuntimeException("values() returned null"); } conList.addAll(conCol); // make non map backed copy } finally { lock.unlock(); } Iterator<RDPConnection> iter = conList.iterator(); while (iter.hasNext()) { RDPConnection con = iter.next(); long currentTime = System.currentTimeMillis(); // is the connection in CLOSE_WAIT if (con.getState() == RDPConnection.CLOSE_WAIT) { long closeTime = con.getCloseWaitTimer(); long elapsedTime = currentTime - closeTime; Log.net( "RDPRetryThread: con is in CLOSE_WAIT: elapsed close timer(ms)=" + elapsedTime + ", waiting for 30seconds to elapse. con=" + con); if (elapsedTime > 30000) { // close the connection Log.net("RDPRetryThread: removing CLOSE_WAIT connection. con=" + con); removeConnection(con); } else { Log.net( "RDPRetryThread: time left on CLOSE_WAIT timer: " + (30000 - (currentTime - closeTime))); } // con.close(); continue; } if (Log.loggingNet) Log.net( "RDPServer.RETRY: resending expired packets " + con + " - current list size = " + con.unackListSize()); // see if we should send a null packet, but only if con is already open if ((con.getState() == RDPConnection.OPEN) && ((currentTime - con.getLastNullPacketTime()) > 30000)) { con.getLock().lock(); try { RDPPacket nulPacket = RDPPacket.makeNulPacket(); con.sendPacketImmediate(nulPacket, false); con.setLastNullPacketTime(); if (Log.loggingNet) Log.net("RDPServer.retry: sent nul packet: " + nulPacket); } finally { con.getLock().unlock(); } } else { if (Log.loggingNet) Log.net( "RDPServer.retry: sending nul packet in " + (30000 - (currentTime - con.getLastNullPacketTime()))); } con.resend( currentTime - resendTimerMS, // resend cutoff time currentTime - resendTimeoutMS); // giveup time } long endTime = System.currentTimeMillis(); if (Log.loggingNet) Log.net( "RDPServer.RETRY: endTime=" + endTime + ", elapse(ms)=" + (endTime - startTime)); Thread.sleep(250); } catch (Exception e) { Log.exception("RDPServer.RetryThread.run caught exception", e); } } } } static Lock lock = LockFactory.makeLock("StaticRDPServerLock"); /** * this condition gets signalled when there is a new server socket. this is useful when you are * waiting to process a new connection */ static Condition channelMapNotEmpty = lock.newCondition(); // private MVLock lock = new MVLock("RDPServerLock"); /** * maximum time (in milliseconds) a packet can be in the resend queue before the connection closes * itself - defaults to 30 seconds */ public static int resendTimeoutMS = 30000; /** how often we resend packets */ public static int resendTimerMS = 500; public static void setCounterLogging(boolean enable) { packetCounter.setLogging(enable); dataCounter.setLogging(enable); sendMeter.setLogging(enable); sendDataMeter.setLogging(enable); RDPConnection.resendMeter.setLogging(enable); } /** machinery to count select and active channel calls */ public static int activeChannelCalls = 0; public static int selectCalls = 0; public static int transmits = 0; public static int retransmits = 0; // this is the worker thread which calls the callback // we want it in a seperate thread so it doesnt block // the rdpserver connections. Currently unused. static class CallbackThread implements Runnable { CallbackThread(RDPPacketCallback cb, RDPConnection con, RDPPacket packet, MVByteBuffer buf) { this.cb = cb; this.con = con; this.packet = packet; this.buf = buf; } public void run() { cb.processPacket(con, buf); } RDPConnection con = null; RDPPacketCallback cb = null; RDPPacket packet = null; MVByteBuffer buf = null; } /** * Machinery to process a queue of packets that have been received but not yet subjected to packet * processing. */ static class PacketCallbackStruct { PacketCallbackStruct( ClientConnection.MessageCallback cb, ClientConnection con, RDPPacket packet) { this.cb = cb; this.con = con; this.packet = packet; } ClientConnection con = null; ClientConnection.MessageCallback cb = null; RDPPacket packet = null; } static void queueForCallbackProcessing( ClientConnection.MessageCallback pcb, ClientConnection con, RDPPacket packet) { queuedPacketCallbacksLock.lock(); try { queuedPacketCallbacks.addLast(new PacketCallbackStruct(pcb, con, packet)); queuedPacketCallbacksNotEmpty.signal(); } finally { queuedPacketCallbacksLock.unlock(); } } static LinkedList<PacketCallbackStruct> queuedPacketCallbacks = new LinkedList<PacketCallbackStruct>(); static Lock queuedPacketCallbacksLock = LockFactory.makeLock("queuedPacketCallbacksLock"); static Condition queuedPacketCallbacksNotEmpty = queuedPacketCallbacksLock.newCondition(); // this is the worker thread that processes the queue of packets // received but not yet subjected to callback processing. static class PacketCallbackThread implements Runnable { PacketCallbackThread() {} public void run() { while (true) { LinkedList<PacketCallbackStruct> list = null; try { queuedPacketCallbacksLock.lock(); try { queuedPacketCallbacksNotEmpty.await(); } catch (Exception e) { Log.error( "RDPServer.PacketCallbackThread: queuedPacketCallbacksNotEmpty.await() caught exception " + e.getMessage()); } list = queuedPacketCallbacks; queuedPacketCallbacks = new LinkedList<PacketCallbackStruct>(); } finally { queuedPacketCallbacksLock.unlock(); } if (Log.loggingNet) Log.net("RDPServer.PacketCallbackThread: Got " + list.size() + " queued packets"); for (PacketCallbackStruct pcs : list) { try { callbackProcessPacket(pcs.cb, pcs.con, pcs.packet); } catch (Exception e) { Log.exception("RDPServer.PacketCallbackThread: ", e); } } } } } static void callbackProcessPacket( ClientConnection.MessageCallback pcb, ClientConnection clientCon, RDPPacket packet) { if (packet.isNul()) { return; } byte[] data = packet.getData(); MVByteBuffer buf = new MVByteBuffer(data); RDPConnection con = (RDPConnection) clientCon; // If this is a multiple-message message . . . if (buf.getLong() == -1 && buf.getInt() == RDPConnection.aggregatedMsgId) { con.aggregatedReceives++; PacketAggregator.allAggregatedReceives++; // Get the count of sub buffers int size = buf.getInt(); con.receivedMessagesAggregated += size; PacketAggregator.allReceivedMessagesAggregated += size; if (Log.loggingNet) Log.net( "RDPServer.callbackProcessPacket: processing aggregated message with " + size + " submessages"); MVByteBuffer subBuf = null; for (int i = 0; i < size; i++) { try { subBuf = buf.getByteBuffer(); } catch (Exception e) { Log.error("In CallbackThread, error getting aggregated subbuffer: " + e.getMessage()); } if (subBuf != null) pcb.processPacket(con, subBuf); } } else { con.unaggregatedReceives++; PacketAggregator.allUnaggregatedReceives++; buf.rewind(); pcb.processPacket(con, buf); } } }