package edu.washington.cs.oneswarm.f2f.servicesharing; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.util.SHA1Simple; import edu.uw.cse.netlab.utils.ByteManip; import edu.washington.cs.oneswarm.f2f.BigFatLock; import edu.washington.cs.oneswarm.f2f.messaging.OSF2FHashSearch; import edu.washington.cs.oneswarm.f2f.network.OverlayManager; import edu.washington.cs.oneswarm.ui.gwt.rpc.ClientServiceDTO; import edu.washington.cs.oneswarm.ui.gwt.rpc.SharedServiceDTO; /** * This class manages local client and server services. It keeps records of * active * services, and allows new services to be registered. * * @author isdal * */ public class ServiceSharingManager { public static final int CHT_DEBUG_SEARCH_PREFIX = 1650551921; public static final String DEBUG_KEY_NOUNCE = "rkAx1ucFeYOQUDdwzJlG2dwTAcwuRkv8pDQBAuK0Dv78NkDQHfNcspFpTmlvLgHxgjnIJpATmXaTzyrb"; private final static String CLIENT_SERVICE_CONFIG_KEY = "SERVICE_CLIENT"; private final static ServiceSharingManager instance = new ServiceSharingManager(); private static BigFatLock lock = OverlayManager.lock; public final static Logger logger = Logger.getLogger(ServiceSharingManager.class.getName()); private final static String SHARED_SERVICE_CONFIG_KEY = "SHARED_SERVICE"; public static ServiceSharingManager getInstance() { return instance; } public HashMap<Long, ClientService> clientServices = new HashMap<Long, ClientService>(); public HashMap<Long, SharedService> sharedServices = new HashMap<Long, SharedService>(); private ServiceSharingManager() { } /* * Debug services used before we launch service sharing. Add a service * sharing to cht.oneswarm.org port 11743 for testing. */ private void enableDebugServices() { try { boolean beta = COConfigurationManager.getBooleanParameter("oneswarm.beta.updates", false); boolean pl = System.getProperty("oneswarm.experimental.config.file") != null; if (!(beta || pl)) { return; } if (!COConfigurationManager.getBooleanParameter("Send Version Info", false)) { return; } if (!COConfigurationManager.getBooleanParameter("OSF2F.Use DHT Proxy", false)) { return; } long searchKey = createChtDebugSearchKey(COConfigurationManager.getStringParameter( "ID", "")); InetAddress cht = InetAddress.getByName("128.208.2.60"); registerSharedService(searchKey, "cht.oneswarm.org", new InetSocketAddress(cht, 11743), false); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } protected long createChtDebugSearchKey(String id) throws IOException, UnsupportedEncodingException { ByteArrayOutputStream input = new ByteArrayOutputStream(); input.write(DEBUG_KEY_NOUNCE.getBytes("UTF-8")); input.write(id.getBytes("UTF-8")); byte[] key = new SHA1Simple().calculateHash(input.toByteArray()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); // magic number for recognizing debug searches dos.writeInt(CHT_DEBUG_SEARCH_PREFIX); for (int i = 0; i < 4; i++) { dos.writeByte(key[i]); } long searchKey = ByteManip.btol(baos.toByteArray()); return searchKey; } public void clearLocalServices() { ArrayList<Long> currentServices = new ArrayList<Long>(clientServices.keySet()); for (Long key : currentServices) { deregisterClientService(key); } currentServices.clear(); currentServices.addAll(sharedServices.keySet()); for (Long key : currentServices) { deregisterServerService(key); } } public void deregisterClientService(long searchKey) { try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( CLIENT_SERVICE_CONFIG_KEY, new LinkedList<Long>()); services.remove(Long.valueOf(searchKey)); COConfigurationManager.setParameter(CLIENT_SERVICE_CONFIG_KEY, services); ClientService cs = clientServices.remove(searchKey); if (cs != null) { cs.deactivate(); } } finally { lock.unlock(); } } public void deregisterServerService(long searchKey) { try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( SHARED_SERVICE_CONFIG_KEY, new LinkedList<Long>()); services.remove(Long.valueOf(searchKey)); COConfigurationManager.setParameter(SHARED_SERVICE_CONFIG_KEY, services); SharedService service = sharedServices.remove(searchKey); if (service != null) { service.clean(); } } finally { lock.unlock(); } } private void enableLowLatencyNetwork() { // If the LowLatencyMessageWrite is enabled an attempt is made to write // messages to the channel straight away, so there is no need to set // network.control.write.idle.time to a lower value. The default setting // will delay outgoing messages up to 50ms. // This will trigger tcp read selects every x ms (instead of the default // 25ms). The default setting delays incoming messages up to 25ms. COConfigurationManager.setParameter("network.tcp.read.select.time", 4); } public ClientService getClientService(long infohashhash) { ClientService service = null; try { lock.lock(); service = clientServices.get(infohashhash); } finally { lock.unlock(); } return service; } public List<ClientService> getClientServices() { List<ClientService> cs = new ArrayList<ClientService>(); try { lock.lock(); cs.addAll(clientServices.values()); } finally { lock.unlock(); } Collections.sort(cs); return cs; } public SharedService getSharedService(long infohashhash) { SharedService service = null; try { lock.lock(); service = sharedServices.get(infohashhash); } finally { lock.unlock(); } if (service == null || !service.isEnabled()) { return null; } return service; } public List<SharedService> getSharedServices() { List<SharedService> cs = new ArrayList<SharedService>(); try { lock.lock(); cs.addAll(sharedServices.values()); } finally { lock.unlock(); } Collections.sort(cs); return cs; } public SharedService handleSearch(OSF2FHashSearch search) { long searchKey = search.getInfohashhash(); return getSharedService(searchKey); } public void loadConfiguredClientServices() { boolean enableLowLatencyNetwork = false; enableDebugServices(); try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( CLIENT_SERVICE_CONFIG_KEY, new LinkedList<Long>()); for (Long key : services) { ClientService cs = new ClientService(key); if (cs.getPort() == -1) { continue; } clientServices.put(key, cs); cs.activate(); enableLowLatencyNetwork = true; } } finally { lock.unlock(); } if (enableLowLatencyNetwork) { enableLowLatencyNetwork(); } } public void loadConfiguredSharedServices() { boolean enableLowLatencyNetwork = false; try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( SHARED_SERVICE_CONFIG_KEY, new LinkedList<Long>()); for (Long key : services) { SharedService cs = new SharedService(key); sharedServices.put(key, cs); enableLowLatencyNetwork = true; } } finally { lock.unlock(); } if (enableLowLatencyNetwork) { enableLowLatencyNetwork(); } } public int registerClientService(String name, int suggestedPort, long searchKey) { enableLowLatencyNetwork(); int port = suggestedPort; try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( CLIENT_SERVICE_CONFIG_KEY, new LinkedList<Long>()); ClientService cs = clientServices.get(searchKey); // Create a new if (cs == null) { if (!services.contains(Long.valueOf(searchKey))) { services.add(Long.valueOf(searchKey)); COConfigurationManager.setParameter(CLIENT_SERVICE_CONFIG_KEY, services); } cs = new ClientService(searchKey); cs.setName(name); cs.setPort(port); clientServices.put(searchKey, cs); } else { cs.setName(name); cs.setPort(port); } // Activate if needed, and update the port if not set if (!cs.active) { cs.activate(); } port = cs.getPort(); } finally { lock.unlock(); } return port; } public void registerSharedService(long searchKey, String name, InetSocketAddress address) { registerSharedService(searchKey, name, address, true); } public void registerSharedService(long searchKey, String name, InetSocketAddress address, boolean loadAutomatically) { logger.fine("Registering service: key=" + searchKey + " name=" + name + " address=" + address); enableLowLatencyNetwork(); try { lock.lock(); @SuppressWarnings("unchecked") List<Long> services = COConfigurationManager.getListParameter( SHARED_SERVICE_CONFIG_KEY, new LinkedList<Long>()); SharedService ss = sharedServices.get(searchKey); if (ss == null) { // create a new service if (loadAutomatically && !services.contains(Long.valueOf(searchKey))) { services.add(Long.valueOf(searchKey)); COConfigurationManager.setParameter(SHARED_SERVICE_CONFIG_KEY, services); } ss = new SharedService(searchKey); ss.setName(name); ss.setAddress(address); logger.info("created new service: " + ss); sharedServices.put(searchKey, ss); } else { // update name and address ss.setName(name); ss.setAddress(address); } } finally { lock.unlock(); } } public void updateClients(ArrayList<ClientServiceDTO> services) { HashSet<ClientService> toRemove = new HashSet<ClientService>(); toRemove.addAll(getClientServices()); for (ClientServiceDTO serviceDTO : services) { if (serviceDTO.isDummy()) { continue; } long key = Long.valueOf(serviceDTO.getSearchKey(), 16); ClientService existing = getClientService(key); if (existing == null) { registerClientService(serviceDTO.getName(), serviceDTO.getPort(), key); } else { existing.setName(serviceDTO.getName()); existing.setPort(serviceDTO.getPort()); // Remove this one from the set of services to remove // after we are done. toRemove.remove(existing); } } for (ClientService clientService : toRemove) { deregisterClientService(clientService.serverSearchKey); } } public void updateSharedServices(ArrayList<SharedServiceDTO> services) throws UnknownHostException { HashSet<SharedService> toRemove = new HashSet<SharedService>(); toRemove.addAll(getSharedServices()); for (SharedServiceDTO serviceDTO : services) { if (serviceDTO.isDummy()) { continue; } long key = Long.valueOf(serviceDTO.getSearchKey(), 16); SharedService existing = getSharedService(key); if (existing == null) { registerSharedService( key, serviceDTO.getName(), new InetSocketAddress(InetAddress.getByName(serviceDTO.address), serviceDTO .getPort())); } else { existing.setName(serviceDTO.getName()); existing.setAddress(new InetSocketAddress( InetAddress.getByName(serviceDTO.address), serviceDTO.getPort())); // Remove this one from the set of services to remove // after we are done. toRemove.remove(existing); } } for (SharedService sharedService : toRemove) { deregisterServerService(sharedService.searchKey); } } }