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

Java tutorial

Introduction

Here is the source code for com.offbynull.voip.kademlia.JoinSubcoroutine.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.Context;
import com.offbynull.peernetic.core.actor.helpers.AddressTransformer;
import com.offbynull.peernetic.core.actor.helpers.IdGenerator;
import com.offbynull.peernetic.core.actor.helpers.RequestSubcoroutine;
import com.offbynull.peernetic.core.actor.helpers.Subcoroutine;
import static com.offbynull.peernetic.core.gateways.log.LogMessage.info;
import com.offbynull.peernetic.core.shuttle.Address;
import static com.offbynull.voip.kademlia.AddressConstants.ROUTER_EXT_HANDLER_RELATIVE_ADDRESS;
import com.offbynull.voip.kademlia.externalmessages.PingRequest;
import com.offbynull.voip.kademlia.externalmessages.PingResponse;
import com.offbynull.voip.kademlia.model.BitString;
import com.offbynull.voip.kademlia.model.Id;
import com.offbynull.voip.kademlia.model.Node;
import com.offbynull.voip.kademlia.model.Router;
import com.offbynull.voip.kademlia.model.RouterChangeSet;
import java.security.SecureRandom;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.Validate;

final class JoinSubcoroutine implements Subcoroutine<Void> {

    private final Address subAddress;
    private final Address logAddress;
    private final Address timerAddress;

    private final AddressTransformer addressTransformer;
    private final IdGenerator idGenerator;
    private final SecureRandom secureRandom;

    private final State state;

    private final String bootstrapLink;
    private final Router router;
    private final Id baseId;

    private final GraphHelper graphHelper;

    public JoinSubcoroutine(Address subAddress, State state, String bootstrapLink) {
        Validate.notNull(subAddress);
        Validate.notNull(state);

        this.subAddress = subAddress;
        this.logAddress = state.getLogAddress();
        this.timerAddress = state.getTimerAddress();

        this.addressTransformer = state.getAddressTransformer();
        this.idGenerator = state.getIdGenerator();
        this.secureRandom = state.getSecureRandom();

        this.state = state;

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

        this.graphHelper = state.getGraphHelper();
    }

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

    @Override
    public Void run(Continuation cnt) throws Exception {
        Context ctx = (Context) cnt.getContext();

        if (bootstrapLink == null) {
            ctx.addOutgoingMessage(subAddress, logAddress,
                    info("Initial node in the network. No bootstrap to join."));
            return null;
        }

        // 0. Get ID of bootstarp node
        ctx.addOutgoingMessage(subAddress, logAddress, info("Getting ID for bootstrap link {}", bootstrapLink));
        Address destinationAddress = addressTransformer.toAddress(bootstrapLink)
                .appendSuffix(ROUTER_EXT_HANDLER_RELATIVE_ADDRESS);
        PingResponse pingResponse = new RequestSubcoroutine.Builder<PingResponse>()
                .sourceAddress(subAddress, idGenerator).destinationAddress(destinationAddress)
                .timerAddress(timerAddress).request(new PingRequest(null))
                .addExpectedResponseType(PingResponse.class).attemptInterval(Duration.ofSeconds(2L)).maxAttempts(5)
                .throwExceptionIfNoResponse(false).build().run(cnt);

        Validate.isTrue(pingResponse != null, "Bootstrap link {} did not respond to ping", bootstrapLink);
        Id bootstrapId = pingResponse.getId();
        Validate.isTrue(!bootstrapId.equals(baseId), "Bootstrap ID {} conflicts with self", bootstrapId); // bootstrap must not be self

        Node bootstrapNode = new Node(pingResponse.getId(), bootstrapLink);

        // 1. Add bootstrap node to routing tree
        ctx.addOutgoingMessage(subAddress, logAddress,
                info("Attempting to join the network via {}...", bootstrapNode));
        applyNodesToRouter(ctx, Collections.singletonList(bootstrapNode));

        // 2. Find yourself
        List<Node> closestNodes;

        ctx.addOutgoingMessage(subAddress, logAddress, info("Attempting to find self...", bootstrapNode));
        closestNodes = new FindSubcoroutine(subAddress.appendSuffix("selffind"), state, baseId, 20, false, false)
                .run(cnt);
        Validate.validState(!closestNodes.isEmpty(), "No results from bootstrap");
        Validate.validState(!closestNodes.get(0).getId().equals(baseId), "Self already exists in network");
        applyNodesToRouter(ctx, closestNodes);

        // There's a race condition here where 2 nodes with the same ID can be joining at the same time. There's no way to detect that case.

        // 3. Start bucket refreshes for all buckets
        ctx.addOutgoingMessage(subAddress, logAddress, info("Populating routing tree..."));
        List<BitString> bucketPrefixes = router.dumpBucketPrefixes();

        int idBitLength = baseId.getBitLength();
        int idByteLength = idBitLength / 8 + (idBitLength % 8 == 0 ? 0 : 1);
        byte[] idBytes = new byte[idByteLength];
        for (BitString bucketPrefix : bucketPrefixes) {
            // If the prefix is our own ID, don't try to find it
            if (baseId.getBitString().equals(bucketPrefix)) {
                continue;
            }

            // Create random id in bucket's range
            secureRandom.nextBytes(idBytes);
            Id randomId = Id.create(BitString.createLogicalOrder(idBytes, 0, idBitLength).setBits(0, bucketPrefix));

            ctx.addOutgoingMessage(subAddress, logAddress, info("Searching for random ID {}...", randomId));

            // Find closest nodes
            closestNodes = new FindSubcoroutine(subAddress.appendSuffix("bucketfind"), state, randomId, 20, false,
                    false).run(cnt);

            // Touch router with these nodes
            applyNodesToRouter(ctx, closestNodes);
        }

        // 4. Advertise self to closest nodes so people can reach you
        ctx.addOutgoingMessage(subAddress, logAddress, info("Finding closest nodes to self..."));
        closestNodes = new FindSubcoroutine(subAddress.appendSuffix("adv"), state, baseId, 20, true, true).run(cnt);
        applyNodesToRouter(ctx, closestNodes);

        return null;
    }

    private void applyNodesToRouter(Context ctx, List<Node> nodes) {
        for (Node node : nodes) {
            if (node.getId().equals(baseId)) { // If we reached a node with our own id, skip it
                continue;
            }

            RouterChangeSet changeSet = router.touch(ctx.getTime(), node);
            graphHelper.applyRouterChanges(ctx, changeSet);
        }
    }
}