/** * Use {@link DatagramAdapter#addErrorsCallback(Function)} to add callback to catch errors. This method call this backs. * @param message */ private void callErrorCallbacks(String message) { report(logLabel, ()->"error: " + message, VerboseLevel.BASE); for(Function<String, String> fn : errorCallbacks) { fn.apply(message); } }
public SocketListenThread(DatagramSocket socket){ byte[] buf = new byte[DatagramAdapter.MAX_PACKET_SIZE]; receivedDatagram = new DatagramPacket(buf, buf.length); threadSocket = socket; try { threadSocket.setSoTimeout(500); } catch (SocketException e) { report(logLabel, ()->"setSoTimeout failed, exception: " + e, VerboseLevel.BASE); } }
/** * We have received {@link PacketTypes#SESSION_ACK} packet. * Need to stop retransmitting of any handshake packets. * @param packet received {@link Packet} */ private void onReceiveSessionAck(Packet packet) { report(logLabel, ()->"received session_ack from " + packet.senderNodeId, VerboseLevel.BASE); SessionReader sessionReader = getSessionReader(packet.senderNodeId); if (sessionReader != null) { sessionReader.removeHandshakePacketsFromRetransmitMap(); } } }
@Override public void shutdown() { report(logLabel, ()->"shutting down...", VerboseLevel.BASE); socketListenThread.isActive.set(false); socket.close(); timerHandshake.cancel(); timerHandshake.purge(); timerRetransmit.cancel(); timerRetransmit.purge(); timerProtectionFromDuple.cancel(); timerProtectionFromDuple.purge(); try { socketListenThread.join(); } catch (InterruptedException e) { report(logLabel, ()->"shutting down... InterruptedException: "+e, VerboseLevel.BASE); } report(logLabel, ()->"shutting down... done", VerboseLevel.BASE); }
/** * If session for remote node is already created - returns it, otherwise creates new {@link Session} * @param destination {@link NodeInfo} to remote node * @return session */ private Session getOrCreateSession(NodeInfo destination) { Session s = sessionsByRemoteId.computeIfAbsent(destination.getNumber(), (k)->{ Session session = new Session(destination); report(logLabel, ()->"session created for nodeId "+destination.getNumber(), VerboseLevel.BASE); session.sessionKey = sessionKey; return session; }); report(logLabel, ()->">>local node: "+myNodeInfo.getNumber()+" remote node: "+s.remoteNodeInfo.getNumber(), VerboseLevel.DETAILED); report(logLabel, ()->">>local nonce: "+s.localNonce+" remote nonce: "+s.remoteNonce, VerboseLevel.DETAILED); report(logLabel, ()->">>state: "+s.state, VerboseLevel.DETAILED); report(logLabel, ()->">>session key: "+s.sessionKey.hashCode(), VerboseLevel.DETAILED); return s; }
/** * Method creates {@link DatagramPacket} from given {@link Packet} and sends it to address:port from destination. * @param destination instance of {@link NodeInfo} with net address for sending. * @param packet data to send. It's {@link Packet#makeByteArray()} should returns data with size less than {@link DatagramAdapter#MAX_PACKET_SIZE} */ private void sendPacket(NodeInfo destination, Packet packet) { byte[] payload = packet.makeByteArray(); DatagramPacket dp = new DatagramPacket(payload, payload.length, destination.getNodeAddress().getAddress(), destination.getNodeAddress().getPort()); try { report(logLabel, ()->"sendPacket datagram size: " + payload.length, VerboseLevel.DETAILED); if ((testMode == TestModes.LOST_PACKETS || testMode == TestModes.LOST_AND_SHUFFLE_PACKETS) && (new Random().nextInt(100) < lostPacketsPercent)) report(logLabel, ()->"test mode: skip socket.send", VerboseLevel.BASE); else socket.send(dp); } catch (Exception e) { callErrorCallbacks("sendPacket exception: " + e); } }
/** * Checks time of active handshake procedures and restarts them if time is up {@link UDPAdapter#HANDSHAKE_TIMEOUT_MILLIS} * @param s session * @param now pass here Instant.now() */ private void restartHandshakeIfNeeded(Session s, Instant now) { if (s.state.get() == Session.STATE_HANDSHAKE) { if (s.handshakeExpiresAt.isBefore(now)) { report(logLabel, ()->"handshaking with nodeId="+s.remoteNodeInfo.getNumber()+" is timed out, restart", VerboseLevel.BASE); s.handshakeStep.set(Session.HANDSHAKE_STEP_WAIT_FOR_WELCOME); s.handshakeExpiresAt = now.plusMillis(HANDSHAKE_TIMEOUT_MILLIS); sendHello(s); } } }
/** * We have received {@link PacketTypes#SESSION_PART1} packet. Waiting for part2 or continue if it has got already. * @param packet received {@link Packet} */ private void onReceiveSessionPart1(Packet packet) { report(logLabel, ()->"received session_part1 from " + packet.senderNodeId, VerboseLevel.BASE); Session session = getOrCreateSession(packet.senderNodeId); if (session != null) { session.protectFromDuples(packet.packetId, ()->{ session.removeHandshakePacketsFromRetransmitMap(); if ((session.state.get() == Session.STATE_HANDSHAKE) && (session.handshakeStep.get() == Session.HANDSHAKE_STEP_WAIT_FOR_SESSION)) { session.handshake_sessionPart1 = packet.payload; onReceiveSession(session); } }); } }
/** * We have received {@link PacketTypes#SESSION_PART2} packet. Waiting for part1 or continue if it has got already. * @param packet received {@link Packet} */ private void onReceiveSessionPart2(Packet packet) { report(logLabel, ()->"received session_part2 from " + packet.senderNodeId, VerboseLevel.BASE); Session session = getOrCreateSession(packet.senderNodeId); if (session != null) { session.protectFromDuples(packet.packetId, ()->{ session.removeHandshakePacketsFromRetransmitMap(); if ((session.state.get() == Session.STATE_HANDSHAKE) && (session.handshakeStep.get() == Session.HANDSHAKE_STEP_WAIT_FOR_SESSION)) { session.handshake_sessionPart2 = packet.payload; onReceiveSession(session); } }); } }
@Override synchronized public void send(NodeInfo destination, byte[] payload) throws InterruptedException { report(logLabel, () -> "send to "+destination.getNumber()+", isActive: "+socketListenThread.isActive.get(), VerboseLevel.DETAILED); if (!socketListenThread.isActive.get()) return; Session session = getOrCreateSession(destination); if (session.state.get() == Session.STATE_HANDSHAKE) { session.addPayloadToOutputQueue(destination, payload); } else { if (session.retransmitMap.size() > MAX_RETRANSMIT_QUEUE_SIZE) session.addPayloadToOutputQueue(destination, payload); else sendPayload(session, payload); } }
/** * We have received {@link PacketTypes#KEY_REQ_PART1} packet. Waiting for part2 or continue if it has got already. * @param packet received {@link Packet} */ private void onReceiveKeyReqPart1(Packet packet) { report(logLabel, ()->"received key_req_part1 from " + packet.senderNodeId + " (packetId="+packet.packetId+")", VerboseLevel.BASE); SessionReader sessionReader = getOrCreateSessionReaderCandidate(packet.senderNodeId); if (sessionReader != null) { sessionReader.protectFromDuples(packet.packetId, ()->{ sessionReader.removeHandshakePacketsFromRetransmitMap(); sessionReader.handshake_keyReqPart1 = packet.payload; onReceiveKeyReq(sessionReader); }); } }
/** * We have received {@link PacketTypes#KEY_REQ_PART2} packet. Waiting for part1 or continue if it has got already. * @param packet received {@link Packet} */ private void onReceiveKeyReqPart2(Packet packet) { report(logLabel, ()->"received key_req_part2 from " + packet.senderNodeId + " (packetId="+packet.packetId+")", VerboseLevel.BASE); SessionReader sessionReader = getOrCreateSessionReaderCandidate(packet.senderNodeId); if (sessionReader != null) { sessionReader.protectFromDuples(packet.packetId, ()->{ sessionReader.removeHandshakePacketsFromRetransmitMap(); sessionReader.handshake_keyReqPart2 = packet.payload; onReceiveKeyReq(sessionReader); }); } }
/** * We have received {@link PacketTypes#ACK} packet. Need to stop retransmitting of ack-ed packet. * @param packet received {@link Packet} */ private void onReceiveAck(Packet packet) throws EncryptionError, SymmetricKey.AuthenticationFailed { report(logLabel, ()->"received ack from " + packet.senderNodeId, VerboseLevel.DETAILED); Session session = getOrCreateSession(packet.senderNodeId); if (session != null) { if (session.state.get() == Session.STATE_EXCHANGING) { Integer ackPacketId = Boss.load(new SymmetricKey(session.sessionKey.getKey()).etaDecrypt(packet.payload)); session.removePacketFromRetransmitMap(ackPacketId); } } }
/** * If sessionRader for remote node is already created - returns it, otherwise creates new {@link SessionReader} * @param remoteId id of remote node, {@link NodeInfo} will be got from {@link UDPAdapter#netConfig} * @return sessionReader */ private SessionReader getOrCreateSessionReaderCandidate(int remoteId) { NodeInfo destination = netConfig.getInfo(remoteId); if (destination == null) { callErrorCallbacks("(getOrCreateSessionReaderCandidate) unknown nodeId has received: "+remoteId); return null; } SessionReader sr = sessionReaderCandidates.computeIfAbsent(destination.getNumber(), (k)-> { SessionReader sessionReader = new SessionReader(); sessionReader.remoteNodeInfo = destination; report(logLabel, ()->"sessionReader created for nodeId "+destination.getNumber(), VerboseLevel.BASE); return sessionReader; }); return sr; }
/** * Each adapter will try to send blocks until have got special {@link Packet} with type {@link PacketTypes#ACK}, * that means receiver have got block. So when we got packet and all is ok - call this method. * @param sessionReader is {@link SessionReader} in which sending is. * @param packetId is id of packet we have got. */ private void sendAck(SessionReader sessionReader, Integer packetId) throws EncryptionError { report(logLabel, ()->"send ack to "+sessionReader.remoteNodeInfo.getNumber(), VerboseLevel.DETAILED); Packet packet = new Packet(0, myNodeInfo.getNumber(), sessionReader.remoteNodeInfo.getNumber(), PacketTypes.ACK, new SymmetricKey(sessionReader.sessionKey.getKey()).etaEncrypt(Boss.pack(packetId))); sendPacket(sessionReader.remoteNodeInfo, packet); }
/** * ACK packets are used only for respond to DATA packets. Retransmission of handshake's packet types stops on each * next handshake step. But last step need to be ACK-ed. For this used {@link PacketTypes#SESSION_ACK} packet. * @param session is {@link Session} in which sending is. * @throws EncryptionError */ private void sendSessionAck(Session session) throws EncryptionError { report(logLabel, ()->"send session_ack to "+session.remoteNodeInfo.getNumber(), VerboseLevel.BASE); Packet packet = new Packet(0, myNodeInfo.getNumber(), session.remoteNodeInfo.getNumber(), PacketTypes.SESSION_ACK, new SymmetricKey(session.sessionKey.getKey()).etaEncrypt(Do.randomBytes(32))); sendPacket(session.remoteNodeInfo, packet); }
/** * When someone send us {@link PacketTypes#HELLO} typed {@link UDPAdapter.Packet}, * we should respond with {@link PacketTypes#WELCOME}. * @param sessionReader is {@link UDPAdapter.SessionReader} in which sending is. */ private void sendWelcome(SessionReader sessionReader) { try { report(logLabel, () -> "send welcome to " + sessionReader.remoteNodeInfo.getNumber(), VerboseLevel.BASE); byte[] data = sessionReader.localNonce; byte[] sign = new PrivateKey(ownPrivateKey.pack()).sign(data, HashType.SHA512); byte[] payload = Boss.dumpToArray(Arrays.asList(data, sign)); Packet packet = new Packet(getNextPacketId(), myNodeInfo.getNumber(), sessionReader.remoteNodeInfo.getNumber(), PacketTypes.WELCOME, payload); sendPacket(sessionReader.remoteNodeInfo, packet); sessionReader.removeHandshakePacketsFromRetransmitMap(); sessionReader.addPacketToRetransmitMap(packet.packetId, packet, sessionReader.localNonce); } catch (EncryptionError e) { callErrorCallbacks("(sendWelcome) EncryptionError: " + e); } }
/** * We have sent {@link PacketTypes#HELLO} typed {@link Packet}, * and have got {@link PacketTypes#WELCOME} typed {@link Packet} - it means we can continue handshake and send * request for session's keys. KEY_REQ's payload is more than 512 bytes, so used two parts here. * @param session is {@link Session} in which sending is. * @param payloadPart1 is prepared in {@link SocketListenThread#onReceiveWelcome(Packet)} * @param payloadPart2 is prepared in {@link SocketListenThread#onReceiveWelcome(Packet)} */ private void sendKeyReq(Session session, byte[] payloadPart1, byte[] payloadPart2) throws EncryptionError { report(logLabel, ()->"send key_req to "+session.remoteNodeInfo.getNumber(), VerboseLevel.BASE); Packet packet1 = new Packet(getNextPacketId(), myNodeInfo.getNumber(), session.remoteNodeInfo.getNumber(), PacketTypes.KEY_REQ_PART1, payloadPart1); Packet packet2 = new Packet(getNextPacketId(), myNodeInfo.getNumber(), session.remoteNodeInfo.getNumber(), PacketTypes.KEY_REQ_PART2, payloadPart2); sendPacket(session.remoteNodeInfo, packet1); sendPacket(session.remoteNodeInfo, packet2); session.addPacketToRetransmitMap(packet1.packetId, packet1, payloadPart1); session.addPacketToRetransmitMap(packet2.packetId, packet2, payloadPart2); }
/** * This is first step of creation and installation of the session. * @param session is {@link Session} in which sending is. */ private void sendHello(Session session) { try { report(logLabel, () -> "send hello to " + session.remoteNodeInfo.getNumber(), VerboseLevel.BASE); byte[] helloNonce = Do.randomBytes(64); Packet packet = new Packet(getNextPacketId(), myNodeInfo.getNumber(), session.remoteNodeInfo.getNumber(), PacketTypes.HELLO, new PublicKey(session.remoteNodeInfo.getPublicKey().pack()).encrypt(helloNonce)); sendPacket(session.remoteNodeInfo, packet); session.addPacketToRetransmitMap(packet.packetId, packet, helloNonce); } catch (EncryptionError e) { callErrorCallbacks("(sendHello) EncryptionError: " + e); } }
/** * Someone who sent {@link PacketTypes#HELLO} typed {@link Packet}, * send us new KEY_REQ typed {@link Packet} - if all is ok we send session keys to. * SESSION's payload is more than 512 bytes, so used two parts here. * From now we ready to data exchange. * @param sessionReader is {@link SessionReader} in which sending is. */ private void sendSessionKey(SessionReader sessionReader) throws EncryptionError { report(logLabel, ()->"send session_key to "+sessionReader.remoteNodeInfo.getNumber(), VerboseLevel.BASE); List data = Arrays.asList(sessionReader.sessionKey.getKey(), sessionReader.remoteNonce); byte[] packed = Boss.pack(data); byte[] encrypted = new PublicKey(sessionReader.remoteNodeInfo.getPublicKey().pack()).encrypt(packed); byte[] sign = new PrivateKey(ownPrivateKey.pack()).sign(encrypted, HashType.SHA512); Packet packet1 = new Packet(getNextPacketId(), myNodeInfo.getNumber(), sessionReader.remoteNodeInfo.getNumber(), PacketTypes.SESSION_PART1, encrypted); Packet packet2 = new Packet(getNextPacketId(), myNodeInfo.getNumber(), sessionReader.remoteNodeInfo.getNumber(), PacketTypes.SESSION_PART2, sign); sendPacket(sessionReader.remoteNodeInfo, packet1); sendPacket(sessionReader.remoteNodeInfo, packet2); sessionReader.addPacketToRetransmitMap(packet1.packetId, packet1, encrypted); sessionReader.addPacketToRetransmitMap(packet2.packetId, packet2, sign); }