package remotep2p;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.websocket.CloseReason;
import javax.websocket.Session;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author jasamer
*/
public class ConnectionManager {
private static ConnectionManager instance = null;
protected ConnectionManager() {}
public static ConnectionManager getInstance() {
if(instance == null) {
instance = new ConnectionManager();
}
return instance;
}
private static final Logger LOGGER = Logger.getLogger(ConnectionManager.class.getName());
private final Set<Session> discoveryUpdateSessions = new HashSet<>();
private final Map<UUID, Session> discoveryConnectionByAdvertisedIdentifier = new HashMap<>();
private final Map<UUID, Map<UUID, List<ConnectionRequestEndpoint>>> requestConnectionEndpointsBySourceByTarget = new HashMap<>();
void removeDiscoveryConnection(Session session) throws IOException {
this.stopSendingDiscoveryUpdates(session);
UUID removedUUID = this.uuidForSession(session);
if (removedUUID != null) {
this.stopAdvertisingPeer(removedUUID, session);
}
}
void startSendingDiscoveryUpdates(Session session) throws IOException {
this.discoveryUpdateSessions.add(session);
for (Map.Entry<UUID, Session> discoveryConnection : this.discoveryConnectionByAdvertisedIdentifier.entrySet()) {
session
.getBasicRemote()
.sendBinary(
new RemoteP2PPacket(
RemoteP2PPacket.PEER_ADDED,
discoveryConnection.getKey()
).serialize()
);
}
}
void stopSendingDiscoveryUpdates(Session session) {
this.discoveryUpdateSessions.remove(session);
}
void startAdvertisingPeer(UUID peerIdentifier, Session session) throws IOException {
this.discoveryConnectionByAdvertisedIdentifier.put(peerIdentifier, session);
ByteBuffer discoveryData = new RemoteP2PPacket(RemoteP2PPacket.PEER_ADDED, peerIdentifier).serialize();
for (Session discoveryConnection : this.discoveryUpdateSessions) {
discoveryConnection.getBasicRemote().sendBinary(discoveryData);
}
}
void stopAdvertisingPeer(UUID peerIdentifier, Session session) throws IOException {
if (!this.discoveryConnectionByAdvertisedIdentifier.containsKey(peerIdentifier)) {
LOGGER.log(Level.WARNING, "Attempted to remove unknown Session.");
return;
}
this.discoveryConnectionByAdvertisedIdentifier.remove(peerIdentifier);
ByteBuffer removalData = new RemoteP2PPacket(RemoteP2PPacket.PEER_REMOVED, peerIdentifier).serialize();
for (Session discoveryConnection : this.discoveryUpdateSessions) {
discoveryConnection.getBasicRemote().sendBinary(removalData);
}
}
private UUID uuidForSession(Session session) {
for (Map.Entry<UUID, Session> discoveryConnection : this.discoveryConnectionByAdvertisedIdentifier.entrySet()) {
if (discoveryConnection.getValue() == session) return discoveryConnection.getKey();
}
return null;
}
private void warnAndCloseEndpoint(ConnectionAcceptEndpoint acceptEndpoint) {
LOGGER.log(Level.WARNING, "Trying to accept connection that was not requested.");
try {
acceptEndpoint
.getAcceptingSession()
.close(
new CloseReason(
CloseReason.CloseCodes.CANNOT_ACCEPT,
"Trying to accept connection that was not requested."
)
);
} catch (IOException ex) {
Logger.getLogger(ConnectionManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
void requestConnection(UUID requestingPeer, UUID acceptingPeer, ConnectionRequestEndpoint connectionRequestEndpoint) throws IOException {
Map<UUID, List<ConnectionRequestEndpoint>> requestConnectionEndpointsByTarget = this.requestConnectionEndpointsBySourceByTarget.get(requestingPeer);
if (requestConnectionEndpointsByTarget == null) {
requestConnectionEndpointsByTarget = new HashMap<>();
this.requestConnectionEndpointsBySourceByTarget.put(requestingPeer, requestConnectionEndpointsByTarget);
}
List<ConnectionRequestEndpoint> requestConnectionEndpoints = requestConnectionEndpointsByTarget.get(acceptingPeer);
if (requestConnectionEndpoints == null) {
requestConnectionEndpoints = new ArrayList<>();
requestConnectionEndpointsByTarget.put(acceptingPeer, requestConnectionEndpoints);
}
requestConnectionEndpoints.add(connectionRequestEndpoint);
this.discoveryConnectionByAdvertisedIdentifier
.get(acceptingPeer)
.getBasicRemote()
.sendBinary(
new RemoteP2PPacket(RemoteP2PPacket.CONNECTION_REQUEST, requestingPeer)
.serialize()
);
LOGGER.log(Level.INFO, "Received connection request from {0} to {1}", new Object[]{requestingPeer, acceptingPeer});
}
void acceptConnectionRequest(UUID acceptingPeer, UUID requestingPeer, ConnectionAcceptEndpoint acceptEndpoint) {
Map<UUID, List<ConnectionRequestEndpoint>> requestConnectionEndpointsByTarget = this.requestConnectionEndpointsBySourceByTarget.get(requestingPeer);
if (requestConnectionEndpointsByTarget == null) {
this.warnAndCloseEndpoint(acceptEndpoint);
return;
}
List<ConnectionRequestEndpoint> requestConnectionEndpoints = requestConnectionEndpointsByTarget.get(acceptingPeer);
if (requestConnectionEndpoints == null || requestConnectionEndpoints.isEmpty()) {
this.warnAndCloseEndpoint(acceptEndpoint);
return;
}
ConnectionRequestEndpoint requestEndpoint = requestConnectionEndpoints.remove(0);
acceptEndpoint.setRequestingSession(requestEndpoint.getRequestingSession());
requestEndpoint.setAcceptingSession(acceptEndpoint.getAcceptingSession());
requestEndpoint.confirmAccepted();
LOGGER.log(Level.INFO, "Established data connection between {0} and {1}.", new Object[]{requestingPeer, acceptingPeer});
}
}