/*
* Copyright (c) 2016, 2017 Hewlett Packard Enterprise Development LP. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.sfc.util.macchaining;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
/* VirtualMAC format
48 24 23 22 0
+-----------------+----+----+------+-----+------+
| OUI | R | B | PORT | CID | SFID |
+-----------------+----+----+------+-----+------+
*/
public final class VirtualMacAddress {
// Configuration values
private static final long BASE_OUI = 0xF00000000000L;
private static final int FLAGS_LEN = 2;
private static final int PORT_LEN = 6;
private static final int CID_LEN = 8;
private static final int MAC_OUI_SIZE = 24;
public static final int SFID_LEN;
private static final int MAX_FLAGS;
private static final int MAX_PORT;
private static final int MAX_CID;
private static final Map<UUID, Integer> MAPPING;
private static final Object LOCKER = new Object();
private static final Deque<Integer> POOL;
private static final boolean[] IN_USE;
private final int step = 1;
static {
MAPPING = new HashMap<>();
int bitsUsed = PORT_LEN + CID_LEN + FLAGS_LEN;
int bitsRemaining = MAC_OUI_SIZE - bitsUsed;
if (bitsRemaining <= 1) {
throw new IllegalArgumentException(String.format(
"The sum of PORT_LEN and CID_LEN must be lower than %d",
MAC_OUI_SIZE - FLAGS_LEN
));
}
SFID_LEN = bitsRemaining;
// TODO: Load database information and exclude the IDs already used
MAX_CID = (int) Math.pow(2, CID_LEN);
IN_USE = new boolean[MAX_CID];
POOL = new LinkedList<>();
for (int i = 0; i < MAX_CID; i++) {
POOL.add(i);
}
MAX_FLAGS = (int) Math.pow(2, FLAGS_LEN);
MAX_PORT = (int) Math.pow(2, PORT_LEN);
}
private static int reserveId() {
synchronized (LOCKER) {
if (POOL.isEmpty()) {
throw new NoSuchElementException("No more ids available (are you calling release()?)");
}
int id = POOL.pop();
IN_USE[id] = true;
return id;
}
}
private static void releaseId(int id) {
synchronized (LOCKER) {
if (id < 0 || id >= MAX_CID) {
throw new IllegalArgumentException(String.format("Id must be in between 0 and %d", MAX_CID));
}
if (!IN_USE[id]) {
throw new IllegalArgumentException(String.format("Id %d was not previously reserved", id));
}
IN_USE[id] = false;
POOL.push(id);
}
}
public static int getChainIdFor(UUID uuid) {
synchronized (MAPPING) {
Integer id = MAPPING.get(uuid);
if (id == null) {
id = reserveId();
MAPPING.put(uuid, id);
}
return id;
}
}
public static VirtualMacAddress getForwardAddress(long uuid, long port) {
UUID id = new UUID(0, uuid);
return new VirtualMacAddress(BI_FORWARD, port, getChainIdFor(id));
}
public static VirtualMacAddress getBackwardAddress(long uuid, long port) {
UUID id = new UUID(0, uuid);
return new VirtualMacAddress(BI_BACKWARD, port, getChainIdFor(id));
}
public static final int BI_BACKWARD = 0x400000;
public static final int BI_FORWARD = 0x000000;
public static final int REVERSE = 0x800000;
private final byte[] macAddr;
private final int chainId;
private final int flags;
private final int port;
public VirtualMacAddress(int flags, long port, int chainId) {
if (port >= MAX_PORT
|| port < 0) {
throw new IllegalArgumentException(String.format(
"Invalid port value. Valid values are between 0 and %d",
MAX_PORT
));
}
final int flagsMask = MAX_FLAGS - 1 << 24 - FLAGS_LEN;
if ((flags & ~flagsMask) != 0) {
throw new IllegalArgumentException(String.format(
"Invalid flags composition: 0x%06x",
flags
));
}
this.chainId = chainId;
this.port = (int) port;
this.flags = flags;
this.macAddr = toByte(
BASE_OUI
| flags
| port << CID_LEN + SFID_LEN // Flag bits: Reversed + Bidirectional
| chainId << SFID_LEN
| (int) (Math.pow(2, SFID_LEN) - 1) // All bits 1
);
}
public int getChainId() {
return chainId;
}
private MacAddress getMacAddr(byte[] hop) {
return new MacAddress(toString(hop));
}
public MacAddress getHop(short index) {
byte[] hop = toByte(toLong() + index);
return getMacAddr(hop);
}
public long getEmbeddedPort() {
return port;
}
public void release() {
releaseId(chainId);
}
//TODO: not tested yet
private byte[] toByte(long address) {
byte[] addressInBytes = new byte[]{
(byte) (address >> 40 & 0xff),
(byte) (address >> 32 & 0xff),
(byte) (address >> 24 & 0xff),
(byte) (address >> 16 & 0xff),
(byte) (address >> 8 & 0xff),
(byte) (address >> 0 & 0xff)
};
return addressInBytes;
}
private String toString(byte[] hop) {
StringBuilder builder = new StringBuilder();
for (byte b: hop) {
if (builder.length() > 0) {
builder.append(":");
}
builder.append(String.format("%02X", b & 0xFF));
}
return builder.toString();
}
private long toLong() {
long mac = 0;
for (int i = 0; i < 6; i++) {
long tmp = (macAddr[i] & 0xffL) << (5 - i) * 8;
mac |= tmp;
}
return mac;
}
}