All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.nextop.client.node.MultiNode Maven / Gradle / Ivy

package io.nextop.client.node;


import com.google.common.collect.ImmutableSet;
import io.nextop.Authority;
import io.nextop.client.MessageControl;
import io.nextop.client.MessageControlChannel;
import io.nextop.client.MessageControlNode;
import io.nextop.client.MessageControlState;
import io.nextop.log.NL;
import rx.Scheduler;

import javax.annotation.Nullable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.*;

// multi down

// this node maintains multiple (n) downstream nodes ranked by preference
// routes control to the highest pref downsteam that is active
// when the active state of a downstream changes, this node ensures that the active state of the highest downstream is exclusively set
public class MultiNode extends AbstractMessageControlNode {

    private final DownstreamState[] downstreamStates;

    private Queue pendingMessageControls = new LinkedList();

    private boolean active = false;

    private Collection localSubnets = Collections.emptyList();


    /** @param downstreams the order is the preference (0 top) */
    public MultiNode(Downstream ... downstreams) {
        int n = downstreams.length;
        downstreamStates = new DownstreamState[n];
        for (int i = 0; i < n; ++i) {
            downstreamStates[i] = new DownstreamState(downstreams[i], false);
        }
    }


    @Nullable
    private MessageControlNode getActiveDownstream() {
        for (DownstreamState state : downstreamStates) {
            if (state.downActive) {
                assert state.upActive;
                return state.downstream.node;
            }
        }
        return null;
    }

    private void setActiveDownstream() {
        // scan the down states for the first with upActive and compatible
        // if there is another set downActive, unset that, then set the new

        if (!active) {
            clearActiveDownstream();
        } else {

            int firstUpActiveIndex = -1;
            for (int i = 0, n = downstreamStates.length; i < n; ++i) {
                DownstreamState state = downstreamStates[i];
                if (state.upActive && state.compatible) {
                    firstUpActiveIndex = i;
                    break;
                }
            }

            if (firstUpActiveIndex < 0) {
                clearActiveDownstream();
            } else if (!downstreamStates[firstUpActiveIndex].downActive) {
                // unset
                clearActiveDownstream();

                // set
                DownstreamState activeDownstreamState = downstreamStates[firstUpActiveIndex];
                activeDownstreamState.downActive = true;
                activeDownstreamState.downstream.node.onActive(true);
                for (@Nullable MessageControl mc; null != (mc = pendingMessageControls.poll()); ) {
                    activeDownstreamState.downstream.node.onMessageControl(mc);
                }
            }
        }
    }

    private void clearActiveDownstream() {
        for (int i = 0, n = downstreamStates.length; i < n; ++i) {
            DownstreamState state = downstreamStates[i];
            if (state.downActive) {
                state.downstream.node.onActive(false);
                break;
            }
        }
        assert null == getActiveDownstream();
    }


    @Override
    protected void initSelf(@Nullable Bundle savedState) {
        upstream.onActive(true);

        try {
            localSubnets = findLocalSubnets();
        } catch (IOException e) {
            NL.nl.handled("node.multi.init", e);
            // go forward with no subnets
        }
    }


    @Override
    protected void initDownstream(final @Nullable Bundle savedState) {
        final int n = downstreamStates.length;
        for (int i = 0; i < n; ++i) {
            final DownstreamState state = downstreamStates[i];
            state.downstream.node.init(new MessageControlChannel() {
                @Override
                public void onActive(boolean active) {
                    state.upActive = active;
                    setActiveDownstream();
                }

                @Override
                public void onMessageControl(MessageControl mc) {
                    switch (mc.dir) {
                        case SEND:
                            // route to the active
                            MultiNode.this.onMessageControl(mc);
                            break;
                        case RECEIVE:
                            // up
                            upstream.onMessageControl(mc);
                            break;
                        default:
                            throw new IllegalArgumentException();
                    }
                }

                @Override
                public MessageControlState getMessageControlState() {
                    return MultiNode.this.getMessageControlState();
                }

                @Override
                public void post(Runnable r) {
                    MultiNode.this.post(r);
                }

                @Override
                public void postDelayed(Runnable r, int delayMs) {
                    MultiNode.this.postDelayed(r, delayMs);
                }

                @Override
                public Scheduler getScheduler() {
                    return MultiNode.this.getScheduler();
                }
            }, savedState);
        }
    }

    @Override
    public void onActive(boolean active) {
        this.active = active;
        setActiveDownstream();
        // TODO on inactive with pending, should pending be sent back upstream?
    }

    @Override
    public void onMessageControl(MessageControl mc) {
        // filter incompatible downstreams
        // adjust the compatibility state to the AND of all seen messages
        // see #3 https://github.com/nextopio/nextop-client/issues/3
        // TODO reset the compatibility state at some point
        if (contains(localSubnets, mc.message.route.via.authority)) {
            // filter incompatible downstreams
            boolean modified = false;
            for (DownstreamState state : downstreamStates) {
                if (state.compatible && !state.downstream.support.contains(Downstream.Support.LOCAL)) {
                    state.compatible = false;
                    modified = true;
                }
            }
            if (modified) {
                setActiveDownstream();
            }
        }

        @Nullable MessageControlNode activeDownstream = getActiveDownstream();
        if (null != activeDownstream) {
            activeDownstream.onMessageControl(mc);
        } else {
            // append to the queue for when the active state flips, see #setActiveDownstream
            pendingMessageControls.add(mc);
        }
    }



    private static final class DownstreamState {
        final Downstream downstream;
        // set by the downstream up into the multi node
        boolean upActive;
        // set by the multi node into the downstream
        boolean downActive = false;

        /** this is set false depending on the downstream support for messages.
         * @see Downstream#support */
        // TODO currently if set, it is never reset; do that at some point
        boolean compatible = true;

        DownstreamState(Downstream downstream, boolean upActive) {
            this.downstream = downstream;
            this.upActive = upActive;
        }
    }


    public static final class Downstream {
        public static enum Support {
            LOCAL
        }


        public static Downstream create(MessageControlNode node, Support ... support) {
            return new Downstream(node, ImmutableSet.copyOf(support));
        }


        public final MessageControlNode node;
        public final ImmutableSet support;

        Downstream(MessageControlNode node, ImmutableSet support) {
            this.node = node;
            this.support = support;
        }
    }



    /////// SUBNET ///////
    /* this is used to address #3
     * @see https://github.com/nextopio/nextop-client/issues/3 */

    private static Collection findLocalSubnets() throws IOException {
        Queue is = new LinkedList();
        Enumeration e = NetworkInterface.getNetworkInterfaces();
        while (e.hasMoreElements()) {
            is.add(e.nextElement());
        }

        List subnets = new ArrayList(4);
        for (NetworkInterface i; null != (i = is.poll()); ) {
            for (InterfaceAddress ia : i.getInterfaceAddresses()) {
                subnets.add(Subnet.valueOf(ia));
            }
            @Nullable NetworkInterface p = i.getParent();
            if (null != p) {
                is.offer(p);
            }
        }
        return subnets;
    }

    private static final class Subnet {
        static Subnet valueOf(InterfaceAddress ia) {
            InetAddress a = ia.getAddress();
            return new Subnet(a.getHostName(), bits(a.getAddress()), ia.getNetworkPrefixLength());
        }


        final String host;
        final BitSet address;
        final int prefixLength;

        Subnet(String host, BitSet address, int prefixLength) {
            this.host = host;
            this.address = address;
            this.prefixLength = prefixLength;
        }
    }
    private static BitSet bits(byte[] bytes) {
        // TODO java7: BitSet.valueOf(bytes)
        BitSet bits = new BitSet(8 * bytes.length);
        for (int i = 0, n = bytes.length; i < n; ++i) {
            int b = 0xFF & bytes[i];
            int j = 8 * i;
            for (int k = 0; k < 8; ++k) {
                bits.set(j + k, 0 != (b >>> (7 - k)));
            }
        }
        return bits;
    }

    static boolean contains(Collection subnets, Authority authority) {
        switch (authority.type) {
            case LOCAL:
                return false;
            case NAMED:
                for (Subnet subnet : subnets) {
                    if (subnet.host.equals(authority.getHost())) {
                        return true;
                    }
                }
                return false;
            case IP:
                BitSet address = bits(authority.getIp().getAddress());
                top:
                for (Subnet subnet : subnets) {
                    for (int i = 0, n = subnet.prefixLength; i < n; ++i) {
                        if (address.get(i) != subnet.address.get(i)) {
                            continue top;
                        }
                    }
                    return true;
                }
                return false;
            default:
                throw new IllegalArgumentException();
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy