/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt.peer; import nxt.BlockchainProcessor; import nxt.Nxt; import nxt.util.CountingInputReader; import nxt.util.CountingOutputWriter; import nxt.util.JSON; import nxt.util.Logger; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; import org.eclipse.jetty.websocket.servlet.WebSocketCreator; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import org.json.simple.JSONValue; import org.json.simple.parser.ParseException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.net.InetSocketAddress; import java.util.Collections; import java.util.HashMap; import java.util.Map; public final class PeerServlet extends WebSocketServlet { abstract static class PeerRequestHandler { abstract JSONStreamAware processRequest(JSONObject request, Peer peer); abstract boolean rejectWhileDownloading(); } private static final Map<String,PeerRequestHandler> peerRequestHandlers; static { Map<String,PeerRequestHandler> map = new HashMap<>(); map.put("addPeers", AddPeers.instance); map.put("getCumulativeDifficulty", GetCumulativeDifficulty.instance); map.put("getInfo", GetInfo.instance); map.put("getMilestoneBlockIds", GetMilestoneBlockIds.instance); map.put("getNextBlockIds", GetNextBlockIds.instance); map.put("getNextBlocks", GetNextBlocks.instance); map.put("getPeers", GetPeers.instance); map.put("getTransactions", GetTransactions.instance); map.put("getUnconfirmedTransactions", GetUnconfirmedTransactions.instance); map.put("processBlock", ProcessBlock.instance); map.put("processTransactions", ProcessTransactions.instance); peerRequestHandlers = Collections.unmodifiableMap(map); } static final JSONStreamAware UNSUPPORTED_REQUEST_TYPE; static { JSONObject response = new JSONObject(); response.put("error", Errors.UNSUPPORTED_REQUEST_TYPE); UNSUPPORTED_REQUEST_TYPE = JSON.prepare(response); } private static final JSONStreamAware UNSUPPORTED_PROTOCOL; static { JSONObject response = new JSONObject(); response.put("error", Errors.UNSUPPORTED_PROTOCOL); UNSUPPORTED_PROTOCOL = JSON.prepare(response); } private static final JSONStreamAware UNKNOWN_PEER; static { JSONObject response = new JSONObject(); response.put("error", Errors.UNKNOWN_PEER); UNKNOWN_PEER = JSON.prepare(response); } private static final JSONStreamAware SEQUENCE_ERROR; static { JSONObject response = new JSONObject(); response.put("error", Errors.SEQUENCE_ERROR); SEQUENCE_ERROR = JSON.prepare(response); } private static final JSONStreamAware MAX_INBOUND_CONNECTIONS; static { JSONObject response = new JSONObject(); response.put("error", Errors.MAX_INBOUND_CONNECTIONS); MAX_INBOUND_CONNECTIONS = JSON.prepare(response); } private static final JSONStreamAware DOWNLOADING; static { JSONObject response = new JSONObject(); response.put("error", Errors.DOWNLOADING); DOWNLOADING = JSON.prepare(response); } private static final BlockchainProcessor blockchainProcessor = Nxt.getBlockchainProcessor(); static JSONStreamAware error(Exception e) { JSONObject response = new JSONObject(); response.put("error", Peers.hideErrorDetails ? e.getClass().getName() : e.toString()); return response; } /** * Configure the WebSocket factory * * @param factory WebSocket factory */ @Override public void configure(WebSocketServletFactory factory) { factory.getPolicy().setIdleTimeout(Peers.webSocketIdleTimeout); factory.getPolicy().setMaxBinaryMessageSize(Peers.MAX_MESSAGE_SIZE); factory.setCreator(new PeerSocketCreator()); } /** * Process HTTP POST request * * @param req HTTP request * @param resp HTTP response * @throws ServletException Servlet processing error * @throws IOException I/O error */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { JSONStreamAware jsonResponse; // // Process the peer request // PeerImpl peer = Peers.findOrCreatePeer(req.getRemoteAddr()); if (peer == null) { jsonResponse = UNKNOWN_PEER; } else { jsonResponse = process(peer, req.getReader()); } // // Return the response // resp.setContentType("text/plain; charset=UTF-8"); try (CountingOutputWriter writer = new CountingOutputWriter(resp.getWriter())) { JSON.writeJSONString(jsonResponse, writer); if (peer != null) { peer.updateUploadedVolume(writer.getCount()); } } catch (RuntimeException | IOException e) { if (peer != null) { if ((Peers.communicationLoggingMask & Peers.LOGGING_MASK_EXCEPTIONS) != 0) { if (e instanceof RuntimeException) { Logger.logDebugMessage("Error sending response to peer " + peer.getHost(), e); } else { Logger.logDebugMessage(String.format("Error sending response to peer %s: %s", peer.getHost(), e.getMessage() != null ? e.getMessage() : e.toString())); } } peer.blacklist(e); } throw e; } } /** * Process WebSocket POST request * * @param webSocket WebSocket for the connection * @param requestId Request identifier * @param request Request message */ void doPost(PeerWebSocket webSocket, long requestId, String request) { JSONStreamAware jsonResponse; // // Process the peer request // InetSocketAddress socketAddress = webSocket.getRemoteAddress(); if (socketAddress == null) { return; } String remoteAddress = socketAddress.getHostString(); PeerImpl peer = Peers.findOrCreatePeer(remoteAddress); if (peer == null) { jsonResponse = UNKNOWN_PEER; } else { peer.setInboundWebSocket(webSocket); jsonResponse = process(peer, new StringReader(request)); } // // Return the response // try { StringWriter writer = new StringWriter(1000); JSON.writeJSONString(jsonResponse, writer); String response = writer.toString(); webSocket.sendResponse(requestId, response); if (peer != null) { peer.updateUploadedVolume(response.length()); } } catch (RuntimeException | IOException e) { if (peer != null) { if ((Peers.communicationLoggingMask & Peers.LOGGING_MASK_EXCEPTIONS) != 0) { if (e instanceof RuntimeException) { Logger.logDebugMessage("Error sending response to peer " + peer.getHost(), e); } else { Logger.logDebugMessage(String.format("Error sending response to peer %s: %s", peer.getHost(), e.getMessage() != null ? e.getMessage() : e.toString())); } } peer.blacklist(e); } } } /** * Process the peer request * * @param peer Peer * @param inputReader Input reader * @return JSON response */ private JSONStreamAware process(PeerImpl peer, Reader inputReader) { // // Check for blacklisted peer // if (peer.isBlacklisted()) { JSONObject jsonObject = new JSONObject(); jsonObject.put("error", Errors.BLACKLISTED); jsonObject.put("cause", peer.getBlacklistingCause()); return jsonObject; } Peers.addPeer(peer); // // Process the request // try (CountingInputReader cr = new CountingInputReader(inputReader, Peers.MAX_REQUEST_SIZE)) { JSONObject request = (JSONObject)JSONValue.parseWithException(cr); peer.updateDownloadedVolume(cr.getCount()); if (request.get("protocol") == null || ((Number)request.get("protocol")).intValue() != 1) { Logger.logDebugMessage("Unsupported protocol " + request.get("protocol")); return UNSUPPORTED_PROTOCOL; } PeerRequestHandler peerRequestHandler = peerRequestHandlers.get((String)request.get("requestType")); if (peerRequestHandler == null) { return UNSUPPORTED_REQUEST_TYPE; } if (peer.getState() == Peer.State.DISCONNECTED) { peer.setState(Peer.State.CONNECTED); } if (peer.getVersion() == null && !"getInfo".equals(request.get("requestType"))) { return SEQUENCE_ERROR; } if (!peer.isInbound()) { if (Peers.hasTooManyInboundPeers()) { return MAX_INBOUND_CONNECTIONS; } Peers.notifyListeners(peer, Peers.Event.ADD_INBOUND); } peer.setLastInboundRequest(Nxt.getEpochTime()); if (peerRequestHandler.rejectWhileDownloading() && blockchainProcessor.isDownloading()) { return DOWNLOADING; } return peerRequestHandler.processRequest(request, peer); } catch (RuntimeException|ParseException|IOException e) { Logger.logDebugMessage("Error processing POST request: " + e.toString()); peer.blacklist(e); return error(e); } } /** * WebSocket creator for peer connections */ private class PeerSocketCreator implements WebSocketCreator { /** * Create a peer WebSocket * * @param req WebSocket upgrade request * @param resp WebSocket upgrade response * @return WebSocket */ @Override public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) { return Peers.useWebSockets ? new PeerWebSocket(PeerServlet.this) : null; } } }