org.globus.workspace.network.defaults.MacUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.globus.workspace.network.defaults.MacUtil.java

Source

/*
 * Copyright 1999-2008 University of Chicago
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.globus.workspace.network.defaults;

import org.globus.workspace.network.Association;
import org.globus.workspace.network.AssociationEntry;
import org.nimbustools.api.services.rm.ResourceRequestDeniedException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

public class MacUtil {

    private static final Log logger = LogFactory.getLog(MacUtil.class.getName());

    public static final String VALID_MAC_CHARACTERS = "0123456789ABCDEF";

    public static final char[] MAC_ARRAY = VALID_MAC_CHARACTERS.toCharArray();

    private static final Object list_lock = new Object();

    private static final Random random = new Random();

    public static List<String> handleMacs(Map<String, Association> previous, Map<String, Association> current,
            String macPrefix) throws ResourceRequestDeniedException {
        if (previous == null) {
            throw new IllegalArgumentException("previous may not be null");
        }
        if (current == null) {
            throw new IllegalArgumentException("current may not be null");
        }
        if (macPrefix == null) {
            throw new IllegalArgumentException("macPrefix may not be null");
        }

        final Map<String, AssociationEntry> inUse = getInUseMacMap(previous);

        final Set<String> explicit = new HashSet<String>();
        for (Map.Entry<String, Association> assocPair : current.entrySet()) {
            final String assocName = assocPair.getKey();
            final Association assoc = assocPair.getValue();
            if (assoc == null || assoc.getEntries() == null) {
                continue;
            }
            for (Object entryObject : assoc.getEntries()) {
                final AssociationEntry entry = (AssociationEntry) entryObject;
                if (entry.isExplicitMac()) {
                    final AssociationEntry inUseEntry = inUse.get(entry.getMac());
                    if (inUseEntry != null) {
                        final String inUseIp = inUseEntry.getIpAddress();
                        if (inUseIp == null || !inUseIp.equals(entry.getIpAddress())) {
                            logger.error("An explicit MAC in network '" + assocName
                                    + "' collides with an in-use network entry! " + "Explicit: " + entry.toString()
                                    + "\nIn use: " + inUseEntry.toString());
                            entry.setMac(null);
                            entry.setExplicitMac(false);
                        }

                    }

                }
                if (entry.isExplicitMac()) {
                    if (!explicit.add(entry.getMac())) {
                        logger.warn("Duplicate explicit MAC? " + entry.getMac());
                    }
                }
            }
        }

        final List<String> macList = new ArrayList<String>();
        macList.addAll(inUse.keySet());
        macList.addAll(explicit);

        for (Association assoc : current.values()) {
            for (Object entryObject : assoc.getEntries()) {
                final AssociationEntry entry = (AssociationEntry) entryObject;
                _setMac(entry, macPrefix, macList, explicit);
            }
        }

        return macList;
    }

    private static Map<String, AssociationEntry> getInUseMacMap(Map<String, Association> previous) {
        Map<String, AssociationEntry> inUse = new HashMap<String, AssociationEntry>();
        for (Association assoc : previous.values()) {
            for (Object entryObject : assoc.getEntries()) {
                final AssociationEntry entry = (AssociationEntry) entryObject;
                if (entry.isInUse()) {
                    final String mac = entry.getMac();
                    if (mac == null || mac.length() == 0) {
                        logger.warn("Network association entry is supposedly " + "in use but has no MAC address..? "
                                + entry.toString());
                    }
                    final AssociationEntry clobbered = inUse.put(mac, entry);
                    if (clobbered != null) {
                        logger.warn("There appear to be duplicate MAC addresses in use: " + clobbered.toString()
                                + "\nand\n" + entry.toString());
                    }
                }
            }
        }
        return inUse;
    }
    /*
    public static void setMacs(Hashtable new_associations,
                           String macPrefix,
                           List macs)
            
        throws ResourceRequestDeniedException {
        
    if (macPrefix == null) {
        throw new IllegalArgumentException("macPrefix may not be null");
    }
        
    if (new_associations == null) {
        return; // *** EARLY RETURN ***
    }
        
    final Enumeration els = new_associations.elements();
        
    while (els.hasMoreElements()) {
        final Association assoc = (Association) els.nextElement();
        final List entries = assoc.getEntries();
        for (Object entry : entries) {
            _setMac((AssociationEntry) entry, macPrefix, macs);
        }
    }
    } */

    private static void _setMac(AssociationEntry entry, String macPrefix, List<String> macs, Set<String> explicit)
            throws ResourceRequestDeniedException {

        if (entry == null) {
            return; // *** EARLY RETURN ***
        }

        final String entryId = "[entry with ip " + entry.getIpAddress() + "]";

        final String previousMac = entry.getMac();
        boolean prefixReplacement = false;
        boolean explicitReplacement = false;
        if (previousMac != null) {

            if (entry.isExplicitMac()) {
                return; // *** EARLY RETURN ***
            }

            if (explicit.contains(previousMac)) {
                explicitReplacement = true;
            } else if (previousMac.startsWith(macPrefix)) {
                return; // *** EARLY RETURN ***
            } else {
                prefixReplacement = true;
            }
        }

        final String newMac = pickNew(macs, macPrefix);
        entry.setMac(newMac);

        if (prefixReplacement) {
            logger.debug("Replaced previous MAC '" + previousMac + "' with " + "a MAC that has the new prefix: '"
                    + newMac + "' " + entryId);
        } else if (explicitReplacement) {
            logger.warn("Replaced previous MAC '" + previousMac + "' with '" + newMac
                    + "' because it collided with an explicitly specified MAC. " + entryId);
        }

    }

    static String pickNew(List macs, String macPrefix)

            throws ResourceRequestDeniedException {

        final int needed;
        final int len = macPrefix.length();
        switch (len) {
        case 0:
            throw new ResourceRequestDeniedException("prefix length 0?");
        case 1:
            needed = 11;
            break;
        case 2:
            needed = 10;
            break;
        case 3:
            needed = 10;
            break;
        case 4:
            needed = 9;
            break;
        case 5:
            needed = 8;
            break;
        case 6:
            needed = 8;
            break;
        case 7:
            needed = 7;
            break;
        case 8:
            needed = 6;
            break;
        case 9:
            needed = 6;
            break;
        case 10:
            needed = 5;
            break;
        case 11:
            needed = 4;
            break;
        case 12:
            needed = 4;
            break;
        case 13:
            needed = 3;
            break;
        case 14:
            needed = 2;
            break;
        case 15:
            needed = 2;
            break;
        case 16:
            needed = 1;
            break;
        case 17:
            needed = 0;
            break;
        default:
            throw new ResourceRequestDeniedException("prefix length >17?");
        }

        if (needed == 0) {
            logger.warn("prefix is full MAC address, conflict detection OFF");
            return macPrefix;
        }

        synchronized (list_lock) {

            final String result;
            if (needed < 4) {
                // slow, but tries every single permutation
                result = _pickNewSerially(macs, macPrefix, needed);
            } else {
                // Faster.  Does not get at every single permutation but is very
                // unlikely to fail when address range is 16^4 or higher
                result = _pickNewWithRandomComponent(macs, macPrefix, needed);
            }

            if (result == null) {
                throw new ResourceRequestDeniedException("no unique MAC address is available");
            } else {
                macs.add(result);
                return result;
            }
        }
    }

    // slow, but tries every single permutation
    private static String _pickNewSerially(List macs, String prefix, int needed) {

        if (needed == 1) {
            for (int i = 0; i < MAC_ARRAY.length; i++) {
                final String attempt = prefix + MAC_ARRAY[i];
                if (uniqueMacTest(macs, attempt)) {
                    return attempt;
                }
            }
            return null;
        }

        final int L = prefix.length();
        if (L == 2 || L == 5 || L == 8 || L == 11 || L == 14) {
            prefix += ":";
        }

        for (int i = 0; i < MAC_ARRAY.length; i++) {
            final String answer = _pickNewSerially(macs, prefix + MAC_ARRAY[i], needed - 1);
            if (answer != null) {
                return answer;
            }
        }

        return null;
    }

    private static boolean uniqueMacTest(List macs, String attempt) {
        for (Object mac1 : macs) {
            final String mac = (String) mac1;
            if (attempt.equals(mac)) {
                return false;
            }
        }
        return true;
    }

    // This is much faster than _pickNewSerially(), especially as the number
    // of uniques stored in the "macs" List increases into the 100s/1000s.
    // This will not get at every variation but in practice when a prefix is
    // short enough (more digits needed), this does not matter (unless setup
    // actually requires many many thousands of uniques at one time).
    private static String _pickNewWithRandomComponent(List macs, String macPrefix, long needed)
            throws ResourceRequestDeniedException {

        final long limit = (long) StrictMath.pow(16, needed);

        long count = 0;
        while (true) {

            if (count >= limit) {
                // Even at lowest allowable limit (~65000), this is very
                // unlikely to ever occur.
                throw new ResourceRequestDeniedException("Search limit reached (" + count
                        + ") looking for MAC to assign.  Please inform developers.");
            }

            String attempt = appendRandomCharacter(macPrefix);
            while (attempt.length() < 17) {
                attempt = appendRandomCharacter(attempt);
            }

            if (uniqueMacTest(macs, attempt)) {
                return attempt;
            } else {
                count += 1;
            }
        }
    }

    private static String appendRandomCharacter(String sofar) {
        final int L = sofar.length();
        String newString = sofar;
        if (L == 2 || L == 5 || L == 8 || L == 11 || L == 14) {
            newString += ":";
        }
        newString += randomChar();
        return newString;
    }

    private static char randomChar() {
        return MAC_ARRAY[random.nextInt(MAC_ARRAY.length)];
    }

    public static void main(String[] args) throws Exception {

        final long mstart = System.currentTimeMillis();
        final List macs = new LinkedList();
        for (int i = 0; i < 4096; i++) {
            final String result = pickNew(macs, "AB:CD:12:34:");
            if (result == null) {
                System.out.println("RAN OUT");
                break;
            } else {
                System.out.println(result);
            }
        }
        final long mstop = System.currentTimeMillis();
        System.out.println("ELAPSED: " + Long.toString(mstop - mstart) + "ms");
    }

    public static boolean isValidMac(String mac, boolean prefixOk) {
        if (mac == null) {
            throw new IllegalArgumentException("mac may not be null");
        }

        if (!prefixOk && mac.length() != 17) {
            throw new IllegalArgumentException("MAC must be 17 characters long");
        } else if (mac.length() > 17) {
            throw new IllegalArgumentException("MAC length cannot be more than 17 characters");
        }

        mac = mac.toUpperCase();
        final char[] macChars = mac.toCharArray();

        for (int i = 0; i < macChars.length; i++) {
            boolean thisOneOK = false;
            boolean expectedSeparator = false;

            if (i == 2 || i == 5 || i == 8 || i == 11 || i == 14) {
                if (':' == macChars[i]) {
                    thisOneOK = true;
                }
                expectedSeparator = true;
            } else {
                for (int j = 0; j < MAC_ARRAY.length; j++) {
                    if (MAC_ARRAY[j] == macChars[i]) {
                        thisOneOK = true;
                        break;
                    }
                }
            }
            if (!thisOneOK) {

                final String tail;
                if (expectedSeparator) {
                    tail = " (expected separator ':')";
                } else {
                    tail = " (expected hex character)";
                }

                logger.warn("Invalid character in MAC (" + mac + "): '" + macChars[i] + "'" + tail);
                return false;
            }
        }
        return true;
    }
}