/* * Copyright 2015 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ch.dissem.bitmessage.networking; import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.InternalContext.ContextHolder; import ch.dissem.bitmessage.entity.CustomMessage; import ch.dissem.bitmessage.entity.GetData; import ch.dissem.bitmessage.entity.NetworkMessage; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.NodeException; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.utils.Collections; import ch.dissem.bitmessage.utils.Property; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.util.*; import java.util.concurrent.*; import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; import static ch.dissem.bitmessage.utils.DebugUtils.inc; import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; import static java.util.Collections.newSetFromMap; /** * Handles all the networky stuff. */ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { final Collection connections = new ConcurrentLinkedQueue<>(); private final ExecutorService pool = Executors.newCachedThreadPool( pool("network") .lowPrio() .daemon() .build()); private InternalContext ctx; private ServerRunnable server; private volatile boolean running; final Set requestedObjects = newSetFromMap(new ConcurrentHashMap(50_000)); @Override public void setContext(InternalContext context) { this.ctx = context; } @Override public Future synchronize(InetAddress server, int port, long timeoutInSeconds) { try { Connection connection = Connection.sync(ctx, server, port, ctx.getNetworkListener(), timeoutInSeconds); Future reader = pool.submit(connection.getReader()); pool.execute(connection.getWriter()); return reader; } catch (IOException e) { throw new ApplicationException(e); } } @Override public CustomMessage send(InetAddress server, int port, CustomMessage request) { try (Socket socket = new Socket(server, port)) { socket.setSoTimeout(Connection.READ_TIMEOUT); new NetworkMessage(request).write(socket.getOutputStream()); NetworkMessage networkMessage = Factory.getNetworkMessage(3, socket.getInputStream()); if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { return (CustomMessage) networkMessage.getPayload(); } else { if (networkMessage == null) { throw new NodeException("No response from node " + server); } else { throw new NodeException("Unexpected response from node " + server + ": " + networkMessage.getPayload().getCommand()); } } } catch (IOException e) { throw new NodeException(e.getMessage(), e); } } @Override public void start() { if (running) { throw new IllegalStateException("Network already running - you need to stop first."); } try { running = true; connections.clear(); server = new ServerRunnable(ctx, this); pool.execute(server); pool.execute(new ConnectionOrganizer(ctx, this)); } catch (IOException e) { throw new ApplicationException(e); } } @Override public boolean isRunning() { return running; } @Override public void stop() { server.close(); synchronized (connections) { running = false; for (Connection c : connections) { c.disconnect(); } } requestedObjects.clear(); } void startConnection(Connection c) { if (!running) return; synchronized (connections) { if (!running) return; // prevent connecting twice to the same node if (connections.contains(c)) { return; } connections.add(c); } pool.execute(c.getReader()); pool.execute(c.getWriter()); } @Override public void offer(final InventoryVector iv) { List target = new LinkedList<>(); for (Connection connection : connections) { if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { target.add(connection); } } List randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target); for (Connection connection : randomSubset) { connection.offer(iv); } } @Override public Property getNetworkStatus() { TreeSet streams = new TreeSet<>(); TreeMap incomingConnections = new TreeMap<>(); TreeMap outgoingConnections = new TreeMap<>(); for (Connection connection : connections) { if (connection.getState() == ACTIVE) { for (long stream : connection.getStreams()) { streams.add(stream); if (connection.getMode() == SERVER) { inc(incomingConnections, stream); } else { inc(outgoingConnections, stream); } } } } Property[] streamProperties = new Property[streams.size()]; int i = 0; for (Long stream : streams) { int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0; int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0; streamProperties[i] = new Property("stream " + stream, null, new Property("nodes", incoming + outgoing), new Property("incoming", incoming), new Property("outgoing", outgoing) ); i++; } return new Property("network", null, new Property("connectionManager", running ? "running" : "stopped"), new Property("connections", null, streamProperties), new Property("requestedObjects", requestedObjects.size()) ); } @Override public void request(Collection inventoryVectors) { if (!running || inventoryVectors.isEmpty()) return; Map> distribution = new HashMap<>(); for (Connection connection : connections) { if (connection.getState() == ACTIVE) { distribution.put(connection, new LinkedList()); } } Iterator iterator = inventoryVectors.iterator(); if (!iterator.hasNext()) { return; } InventoryVector next = iterator.next(); Connection previous = null; do { for (Connection connection : distribution.keySet()) { if (connection == previous) { next = iterator.next(); } if (connection.knowsOf(next)) { List ivs = distribution.get(connection); if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { connection.send(new GetData.Builder().inventory(ivs).build()); ivs.clear(); } ivs.add(next); iterator.remove(); if (iterator.hasNext()) { next = iterator.next(); previous = connection; } else { break; } } } } while (iterator.hasNext()); for (Connection connection : distribution.keySet()) { List ivs = distribution.get(connection); if (!ivs.isEmpty()) { connection.send(new GetData.Builder().inventory(ivs).build()); } } } }