com.offbynull.voip.kademlia.RefreshSubcoroutine.java Source code

Java tutorial

Introduction

Here is the source code for com.offbynull.voip.kademlia.RefreshSubcoroutine.java

Source

/*
 * Copyright (c) 2015, Kasra Faghihi, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.offbynull.voip.kademlia;

import com.offbynull.coroutines.user.Continuation;
import com.offbynull.peernetic.core.actor.helpers.AddressTransformer;
import com.offbynull.peernetic.core.actor.helpers.IdGenerator;
import com.offbynull.peernetic.core.actor.helpers.MultiRequestSubcoroutine;
import com.offbynull.peernetic.core.actor.helpers.MultiRequestSubcoroutine.Response;
import com.offbynull.peernetic.core.actor.helpers.SleepSubcoroutine;
import com.offbynull.peernetic.core.actor.helpers.Subcoroutine;
import com.offbynull.peernetic.core.shuttle.Address;
import com.offbynull.voip.kademlia.externalmessages.PingRequest;
import com.offbynull.voip.kademlia.externalmessages.PingResponse;
import com.offbynull.voip.kademlia.model.Id;
import com.offbynull.voip.kademlia.model.Node;
import com.offbynull.voip.kademlia.model.NodeNotFoundException;
import com.offbynull.voip.kademlia.model.Router;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;

final class RefreshSubcoroutine implements Subcoroutine<Void> {

    private final Address subAddress;
    private final Address timerAddress;
    private final Address logAddress;
    private final AddressTransformer addressTransformer;
    private final IdGenerator idGenerator;
    private final SecureRandom secureRandom;

    private final Router router;
    private final Id baseId;

    public RefreshSubcoroutine(Address subAddress, State state) {
        Validate.notNull(subAddress);
        Validate.notNull(state);

        this.subAddress = subAddress;

        this.timerAddress = state.getTimerAddress();
        this.logAddress = state.getLogAddress();
        this.addressTransformer = state.getAddressTransformer();
        this.idGenerator = state.getIdGenerator();
        this.secureRandom = state.getSecureRandom();

        this.baseId = state.getBaseId();
        this.router = state.getRouter();

    }

    @Override
    public Address getAddress() {
        return subAddress;
    }

    @Override
    public Void run(Continuation cnt) throws Exception {
        int idBitLength = baseId.getBitLength();
        int idByteLength = idBitLength / 8 + (idBitLength % 8 == 0 ? 0 : 1);
        byte[] idBytes = new byte[idByteLength];

        while (true) {
            // Sleep for a bit
            new SleepSubcoroutine.Builder().sourceAddress(subAddress).duration(Duration.ofSeconds(5L))
                    .timerAddress(timerAddress).build().run(cnt);

            // Get closest nodes (from the router) to some random id
            secureRandom.nextBytes(idBytes);
            Id randomId = Id.create(idBytes, idBitLength);
            List<Node> nodesToPing = router.find(randomId, 5, true); // include stale nodes, because it may have come back up again / we may
                                                                     // have come back up again and we want to unstale it if we can contact
                                                                     // it

            // Ping them
            Address sourceAddress = subAddress.appendSuffix("refresh" + idGenerator.generate());
            MultiRequestSubcoroutine.Builder<PingResponse> multiReqBuilder = new MultiRequestSubcoroutine.Builder<PingResponse>()
                    .sourceAddress(sourceAddress).timerAddress(timerAddress).request(new PingRequest(baseId))
                    .addExpectedResponseType(PingResponse.class).attemptInterval(Duration.ofSeconds(2L))
                    .maxAttempts(5);

            Map<String, Node> suffixToNode = new HashMap<>();
            for (Node node : nodesToPing) {
                String link = node.getLink();
                Address dstAddress = addressTransformer.toAddress(link);

                String suffix = idGenerator.generate();
                suffixToNode.put(suffix, node);

                multiReqBuilder.addDestinationAddress(suffix, dstAddress);
            }

            List<Response<PingResponse>> responses = multiReqBuilder.build().run(cnt);

            // Remove nodes that answered from suffixToNode
            for (Response<PingResponse> response : responses) {
                String suffix = response.getUniqueSourceAddressSuffix();
                suffixToNode.remove(suffix);
            }

            // Mark nodes that didn't answer as stale
            for (Node unresponsiveNode : suffixToNode.values()) {
                // DONT BOTHER WITH TRYING TO CALCULATE LOCKING/UNLOCKING LOGIC. THE LOGIC WILL BECOME EXTREMELY CONVOLUTED. THE QUERY
                // DID 5 REQUEST. IF NO ANSWER WAS GIVEN IN THE ALLOTED TIME, THEN MARK AS STALE!
                try {
                    router.stale(unresponsiveNode);
                } catch (NodeNotFoundException nnfe) { // may have been removed (already marked as stale) / may not be in routing tree
                    // Do nothing
                }
            }
        }
    }
}