Lambdas are reinterpreted before being sent to Linc-OE switches. This includes
adding ability to intercept messages at the switch driver for modification before
being sent down.

Reference: ONOS-1980

Change-Id: I405b89a0fc3844555c9efa0cd9fc887a90d00280
diff --git a/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java b/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
index e1461e3..4c64940 100644
--- a/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
+++ b/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
@@ -24,30 +24,61 @@
 import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus;
 import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply;
 import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
+import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
+import org.projectfloodlight.openflow.protocol.OFInstructionType;
 import org.projectfloodlight.openflow.protocol.OFMessage;
 import org.projectfloodlight.openflow.protocol.OFObject;
 import org.projectfloodlight.openflow.protocol.OFPortDesc;
 import org.projectfloodlight.openflow.protocol.OFStatsReply;
 import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.action.OFActionCircuit;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.protocol.OFActionType;
+import org.projectfloodlight.openflow.types.CircuitSignalID;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.U8;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
 import java.util.Set;
+import java.util.BitSet;
+import java.util.stream.Collectors;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.projectfloodlight.openflow.protocol.OFFlowMod.Builder;
 
 /**
  * LINC-OE Optical Emulator switch class.
  */
 public class OFOpticalSwitchImplLINC13
  extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch {
+    // default number of lambdas, assuming 50GHz channels.
+    private static final int NUM_CHLS = 80;
+    private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_13);
 
     private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false);
     private long barrierXidToWaitFor = -1;
 
     private OFCircuitPortsReply wPorts;
+    // book-keeping maps for allocated Linc-OE lambdas
+    protected final ConcurrentMap<OFPort, BitSet> portChannelMap = new ConcurrentHashMap<>();
+    protected final ConcurrentMap<Match, Integer> matchMap = new ConcurrentHashMap<>();
 
     @Override
     public void startDriverHandshake() {
@@ -170,4 +201,136 @@
         return ImmutableSet.of(PortDescPropertyType.OPTICAL_TRANSPORT);
     }
 
+    @Override
+    public OFMessage prepareMessage(OFMessage msg) {
+        if (OFVersion.OF_13 != msg.getVersion() || msg.getType() != OFType.FLOW_MOD) {
+            return msg;
+        }
+        OFFlowMod fm = (OFFlowMod) msg;
+        Match match = fm.getMatch();
+        // Don't touch FlowMods that aren't Optical-related.
+        if (match.get(MatchField.OCH_SIGTYPE) == null) {
+            return msg;
+        }
+
+        OFMessage newFM;
+        Builder builder = null;
+        List<OFAction> actions = new ArrayList<>();
+        if (fm.getCommand() == OFFlowModCommand.ADD) {
+            builder = factory.buildFlowAdd();
+            int lambda = allocateLambda(match.get(MatchField.IN_PORT), match);
+            CircuitSignalID sigid = new CircuitSignalID((byte) 1, (byte) 2, (short) lambda, (short) 1);
+            List<OFInstruction> instructions = fm.getInstructions();
+
+            newFM = buildFlowMod(builder, fm, buildMatch(match, sigid), buildActions(instructions, sigid));
+        } else if (fm.getCommand() == OFFlowModCommand.DELETE) {
+            builder = factory.buildFlowDelete();
+            int lambda = freeLambda(match.get(MatchField.IN_PORT), match);
+            CircuitSignalID sigid = new CircuitSignalID((byte) 1, (byte) 2, (short) lambda, (short) 1);
+
+            newFM = buildFlowMod(builder, fm, buildMatch(match, sigid), actions);
+        } else {
+            newFM = msg;
+        }
+        log.debug("new FM = {}", newFM);
+        return newFM;
+    }
+
+    // fetch the next available channel as the flat lambda value, or the lambda
+    // associated with a port/match combination
+    private int allocateLambda(OFPort port, Match match) {
+        Integer lambda = null;
+        synchronized (this) {
+            BitSet channels = portChannelMap.getOrDefault(port, new BitSet(NUM_CHLS + 1));
+            lambda = matchMap.get(match);
+            if (lambda == null) {
+                // TODO : double check behavior when bitset is full
+                // Linc lambdas start at 1.
+                lambda = channels.nextClearBit(1);
+                channels.set(lambda);
+                portChannelMap.put(port, channels);
+                matchMap.put(match, lambda);
+            }
+        }
+        return lambda;
+    }
+
+    // free lambda that was mapped to Port/Match combination and return its
+    // value to caller.
+    private int freeLambda(OFPort port, Match match) {
+        synchronized (this) {
+            Integer lambda = matchMap.get(match);
+            if (lambda != null) {
+                portChannelMap.get(port).clear(lambda);
+                return lambda;
+            }
+            // 1 is a sane-ish default for Linc.
+            return 1;
+        }
+    }
+
+    // build matches - *tons of assumptions are made here based on Linc-OE's behavior.*
+    // gridType = 1 (DWDM)
+    // channelSpacing = 2 (50GHz)
+    // spectralWidth = 1 (fixed grid default value)
+    private Match buildMatch(Match original, CircuitSignalID sigid) {
+        Match.Builder mBuilder = factory.buildMatch();
+
+        original.getMatchFields().forEach(mf -> {
+            String name = mf.getName();
+            if (MatchField.OCH_SIGID.getName().equals(name)) {
+                mBuilder.setExact(MatchField.OCH_SIGID, sigid);
+            } else if (MatchField.OCH_SIGTYPE.getName().equals(name)) {
+                mBuilder.setExact(MatchField.OCH_SIGTYPE, U8.of((short) 1));
+            } else if (MatchField.IN_PORT.getName().equals(name)) {
+                mBuilder.setExact(MatchField.IN_PORT, original.get(MatchField.IN_PORT));
+            }
+        });
+
+        return mBuilder.build();
+    }
+
+    private List<OFAction> buildActions(List<OFInstruction> iList, CircuitSignalID sigid) {
+        List<OFAction> actions = new ArrayList<>();
+        Map<OFInstructionType, OFInstruction> instructions = iList.stream()
+                .collect(Collectors.toMap(OFInstruction::getType, inst -> inst));
+
+        OFInstruction inst = instructions.get(OFInstructionType.APPLY_ACTIONS);
+        if (inst != null) {
+            OFInstructionApplyActions iaa = (OFInstructionApplyActions) inst;
+            if (iaa.getActions() == null) {
+                return actions;
+            }
+            iaa.getActions().forEach(action -> {
+                if (OFActionType.EXPERIMENTER == action.getType()) {
+                    OFActionCircuit.Builder cBuilder = factory.actions().buildCircuit()
+                            .setField(factory.oxms()
+                                    .buildOchSigid()
+                                    .setValue(sigid)
+                                    .build());
+                    actions.add(cBuilder.build());
+                } else {
+                    actions.add(action);
+                }
+            });
+        }
+        return actions;
+    }
+
+    private OFMessage buildFlowMod(Builder builder, OFFlowMod fm, Match m, List<OFAction> act) {
+        return builder
+                .setXid(fm.getXid())
+                .setCookie(fm.getCookie())
+                .setCookieMask(fm.getCookieMask())
+                .setTableId(fm.getTableId())
+                .setIdleTimeout(fm.getIdleTimeout())
+                .setHardTimeout(fm.getHardTimeout())
+                .setBufferId(fm.getBufferId())
+                .setOutPort(fm.getOutPort())
+                .setOutGroup(fm.getOutGroup())
+                .setFlags(fm.getFlags())
+                .setMatch(m)
+                .setActions(act)
+                .build();
+    }
 }
diff --git a/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java b/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
index 49ca5a8..0d2dc3a 100644
--- a/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
@@ -148,4 +148,16 @@
      * @return string representation of the connection to the device
      */
     String channelId();
+
+    /**
+     * Prepares a message to be sent, if necessary. Default is to do nothing,
+     * since most Devices do not need to pre-process a message that's about to
+     * be sent.
+     *
+     * @param msg The message to prepare for sending
+     * @return the prepared OFMessage
+     */
+    default OFMessage prepareMessage(OFMessage msg) {
+        return msg;
+    }
 }
diff --git a/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java b/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
index 50c0351..94e82a2 100644
--- a/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
@@ -97,7 +97,7 @@
     @Override
     public final void sendMsg(OFMessage m) {
         if (role == RoleState.MASTER && channel.isWritable()) {
-            channel.write(Collections.singletonList(m));
+            channel.write(Collections.singletonList(prepareMessage(m)));
         }
     }
 
@@ -119,6 +119,7 @@
                                                    "a non role request message");
     }
 
+    @Override
     public final void sendHandshakeMessage(OFMessage message) {
         if (!this.isDriverHandshakeComplete()) {
             channel.write(Collections.singletonList(message));
@@ -155,7 +156,6 @@
         return channelId;
     }
 
-
     //************************
     // Switch features related
     //************************