package net.floodlightcontroller.util;

import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.action.OFActionEnqueue;
import org.projectfloodlight.openflow.protocol.action.OFActionExperimenter;
import org.projectfloodlight.openflow.protocol.action.OFActionGroup;
import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
import org.projectfloodlight.openflow.protocol.action.OFActionPopMpls;
import org.projectfloodlight.openflow.protocol.action.OFActionPushMpls;
import org.projectfloodlight.openflow.protocol.action.OFActionPushPbb;
import org.projectfloodlight.openflow.protocol.action.OFActionPushVlan;
import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst;
import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc;
import org.projectfloodlight.openflow.protocol.action.OFActionSetField;
import org.projectfloodlight.openflow.protocol.action.OFActionSetMplsLabel;
import org.projectfloodlight.openflow.protocol.action.OFActionSetMplsTc;
import org.projectfloodlight.openflow.protocol.action.OFActionSetMplsTtl;
import org.projectfloodlight.openflow.protocol.action.OFActionSetNwDst;
import org.projectfloodlight.openflow.protocol.action.OFActionSetNwEcn;
import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc;
import org.projectfloodlight.openflow.protocol.action.OFActionSetNwTos;
import org.projectfloodlight.openflow.protocol.action.OFActionSetNwTtl;
import org.projectfloodlight.openflow.protocol.action.OFActionSetQueue;
import org.projectfloodlight.openflow.protocol.action.OFActionSetTpDst;
import org.projectfloodlight.openflow.protocol.action.OFActionSetTpSrc;
import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanPcp;
import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanVid;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmArpOp;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmArpSha;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmArpSpa;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmArpTha;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmArpTpa;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthDst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthSrc;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmEthType;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIcmpv4Code;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIcmpv4Type;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIcmpv6Code;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIcmpv6Type;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpDscp;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpEcn;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpProto;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv4Dst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv4Src;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6Dst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6Flabel;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6NdSll;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6NdTarget;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6NdTll;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmIpv6Src;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmMetadata;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmMplsLabel;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmMplsTc;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmSctpDst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmSctpSrc;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmTcpDst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmTcpSrc;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmUdpDst;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmUdpSrc;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmVlanPcp;
import org.projectfloodlight.openflow.protocol.oxm.OFOxmVlanVid;
import org.projectfloodlight.openflow.types.ArpOpcode;
import org.projectfloodlight.openflow.types.EthType;
import org.projectfloodlight.openflow.types.ICMPv4Code;
import org.projectfloodlight.openflow.types.ICMPv4Type;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.projectfloodlight.openflow.types.IPv6Address;
import org.projectfloodlight.openflow.types.IPv6FlowLabel;
import org.projectfloodlight.openflow.types.IpDscp;
import org.projectfloodlight.openflow.types.IpEcn;
import org.projectfloodlight.openflow.types.IpProtocol;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBooleanValue;
import org.projectfloodlight.openflow.types.OFGroup;
import org.projectfloodlight.openflow.types.OFMetadata;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.TransportPort;
import org.projectfloodlight.openflow.types.U32;
import org.projectfloodlight.openflow.types.U64;
import org.projectfloodlight.openflow.types.U8;
import org.projectfloodlight.openflow.types.VlanPcp;
import org.projectfloodlight.openflow.types.VlanVid;
import org.slf4j.Logger;

/**
 * OFAction helper functions. Use with any OpenFlowJ-Loxi Action.
 * String utility functions for converting OFActions to and from
 * dpctl/ofctl-style strings, which is primarily used by the
 * static flow pusher.
 * 
 * Includes string methods refactored from StaticFlowEntryPusher
 *
 * @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu>
 */
public class ActionUtils {
	/* OF1.3 ACTIONS (includes OF1.0) */
	public static final String STR_OUTPUT = "output";
	public static final String STR_ENQUEUE = "enqueue";
	public static final String STR_VLAN_STRIP = "strip_vlan";
	public static final String STR_VLAN_POP = "pop_vlan";
	public static final String STR_VLAN_PUSH = "push_vlan";
	public static final String STR_VLAN_SET_PCP = "set_vlan_pcp";
	public static final String STR_VLAN_SET_VID = "set_vlan_vid";
	public static final String STR_QUEUE_SET = "set_queue";
	public static final String STR_DL_SRC_SET = "set_eth_src";
	public static final String STR_DL_DST_SET = "set_eth_dst";
	public static final String STR_NW_SRC_SET = "set_ipv4_src";
	public static final String STR_NW_DST_SET = "set_ipv4_dst";
	public static final String STR_NW_ECN_SET = "set_ip_ecn";
	public static final String STR_NW_TOS_SET = "set_ip_tos";
	public static final String STR_NW_TTL_SET = "set_ip_ttl";
	public static final String STR_NW_TTL_DEC = "dec_ip_ttl";
	public static final String STR_TTL_IN_COPY = "copy_ip_ttl_in";
	public static final String STR_TTL_OUT_COPY = "copy_ip_ttl_out";
	public static final String STR_MPLS_LABEL_SET = "set_mpls_label";
	public static final String STR_MPLS_TC_SET = "set_mpls_tc";
	public static final String STR_MPLS_TTL_SET = "set_mpls_ttl";
	public static final String STR_MPLS_TTL_DEC = "dec_mpls_ttl";
	public static final String STR_MPLS_PUSH = "push_mpls";
	public static final String STR_MPLS_POP = "pop_mpls";
	public static final String STR_TP_SRC_SET = "set_tp_src";
	public static final String STR_TP_DST_SET = "set_tp_dst";
	public static final String STR_PBB_PUSH = "push_pbb";
	public static final String STR_PBB_POP = "pop_pbb";
	public static final String STR_GROUP = "group";
	public static final String STR_FIELD_SET = "set_field";
	public static final String STR_EXPERIMENTER = "experimenter";
	public static final String STR_NOT_APPLICABLE = "n/a";

	/* OF1.3 set-field operations are defined as any OF1.3 match.
	 * We will borrow MatchUtils's String definitions of all OF1.3
	 * set-field operations to be consistent.
	 */

	/**
	 * Returns a String representation of all the OpenFlow actions.
	 * @param actions; A list of OFActions to encode into one string
	 * @return A dpctl-style string of the actions
	 */
	public static String actionsToString(List<OFAction> actions, Logger log) {
		StringBuilder sb = new StringBuilder();
		for (OFAction a : actions) {
			if (sb.length() > 0) {
				sb.append(',');
			}
			switch(a.getType()) {
			case OUTPUT:
				sb.append(STR_OUTPUT + "=" + Integer.toString(((OFActionOutput)a).getPort().getPortNumber()));
				break;
			case ENQUEUE:
				long queue = ((OFActionEnqueue)a).getQueueId();
				OFPort port = ((OFActionEnqueue)a).getPort();
				sb.append(STR_ENQUEUE + "=" + Integer.toString(port.getPortNumber()) + ":0x" + String.format("%02x", queue));
				break;
			case STRIP_VLAN:
				sb.append(STR_VLAN_STRIP);
				break;
			case POP_VLAN:
				sb.append(STR_VLAN_POP);
				break;
			case PUSH_VLAN:
				sb.append(STR_VLAN_PUSH + "=" + Integer.toString(((OFActionPushVlan)a).getEthertype().getValue()));
				break;
			case SET_VLAN_VID:
				sb.append(STR_VLAN_SET_VID + "=" + Short.toString(((OFActionSetVlanVid)a).getVlanVid().getVlan()));
				break;
			case SET_VLAN_PCP:
				sb.append(STR_VLAN_SET_PCP + "=" + Byte.toString(((OFActionSetVlanPcp)a).getVlanPcp().getValue()));
				break;
			case SET_QUEUE:
				sb.append(STR_QUEUE_SET + "=" + Long.toString(((OFActionSetQueue)a).getQueueId()));
			case SET_DL_SRC:
				sb.append(STR_DL_SRC_SET + "=" +  ((OFActionSetDlSrc)a).getDlAddr().toString());
				break;
			case SET_DL_DST:
				sb.append(STR_DL_DST_SET + "=" + ((OFActionSetDlDst)a).getDlAddr().toString());
				break;
			case SET_NW_ECN:
				sb.append(STR_NW_ECN_SET + "=" + Byte.toString(((OFActionSetNwEcn)a).getNwEcn().getEcnValue()));
				break;
			case SET_NW_TOS:
				sb.append(STR_NW_TOS_SET + "=" + Short.toString(((OFActionSetNwTos)a).getNwTos()));
				break;
			case SET_NW_TTL:
				sb.append(STR_NW_TTL_SET + "=" + Short.toString(((OFActionSetNwTtl)a).getNwTtl()));
				break;
			case DEC_NW_TTL:
				sb.append(STR_NW_TTL_DEC);
				break;
			case SET_MPLS_LABEL:
				sb.append(STR_MPLS_LABEL_SET + "=" + Long.toString(((OFActionSetMplsLabel)a).getMplsLabel()));
				break;
			case SET_MPLS_TC:
				sb.append(STR_MPLS_TC_SET + "=" + Short.toString(((OFActionSetMplsTc)a).getMplsTc()));
				break;
			case SET_MPLS_TTL:
				sb.append(STR_MPLS_TTL_SET + "=" + Short.toString(((OFActionSetMplsTtl)a).getMplsTtl()));
				break;
			case DEC_MPLS_TTL:
				sb.append(STR_MPLS_TTL_DEC);
				break;
			case PUSH_MPLS:
				sb.append(STR_MPLS_PUSH + "=" + Integer.toString(((OFActionPushMpls)a).getEthertype().getValue()));
				break;
			case POP_MPLS:
				sb.append(STR_MPLS_POP + "=" + Integer.toString(((OFActionPopMpls)a).getEthertype().getValue()));
				break;
			case SET_NW_SRC:
				sb.append(STR_NW_SRC_SET + "=" + ((OFActionSetNwSrc)a).getNwAddr().toString());
				break;
			case SET_NW_DST:
				sb.append(STR_NW_DST_SET + "=" + ((OFActionSetNwDst)a).getNwAddr().toString());
				break;
			case SET_TP_SRC:
				sb.append(STR_TP_SRC_SET + "=" + ((OFActionSetTpSrc)a).getTpPort().toString());
				break;
			case SET_TP_DST:
				sb.append(STR_TP_DST_SET + "=" + ((OFActionSetTpDst)a).getTpPort().toString());
				break;
			case COPY_TTL_IN:
				sb.append(STR_TTL_IN_COPY);
				break;
			case COPY_TTL_OUT:
				sb.append(STR_TTL_OUT_COPY);
				break;
			case PUSH_PBB:
				sb.append(STR_PBB_PUSH + "=" + Integer.toString(((OFActionPushPbb)a).getEthertype().getValue()));
				break;
			case POP_PBB:
				sb.append(STR_PBB_POP);
				break;
			case EXPERIMENTER:
				sb.append(STR_EXPERIMENTER + "=" + Long.toString(((OFActionExperimenter)a).getExperimenter()));
				break;
			case GROUP:
				sb.append(STR_GROUP + "=" + Integer.toString(((OFActionGroup)a).getGroup().getGroupNumber()));
				break;
			case SET_FIELD:
				log.debug("Got Set-Field action. Setting " + ((OFActionSetField)a));
				/* ARP */
				if (((OFActionSetField)a).getField() instanceof OFOxmArpOp) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ARP_OPCODE + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmArpOp) ((OFActionSetField) a).getField()).getValue().getOpcode()));
				} else if (((OFActionSetField)a).getField() instanceof OFOxmArpSha) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ARP_SHA + MatchUtils.SET_FIELD_DELIM + ((OFOxmArpSha) ((OFActionSetField) a).getField()).getValue().toString()); // macaddress formats string already
				} else if (((OFActionSetField)a).getField() instanceof OFOxmArpTha) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ARP_DHA + MatchUtils.SET_FIELD_DELIM + ((OFOxmArpTha) ((OFActionSetField) a).getField()).getValue().toString());
				} else if (((OFActionSetField)a).getField() instanceof OFOxmArpSpa) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ARP_SPA + MatchUtils.SET_FIELD_DELIM + ((OFOxmArpSpa) ((OFActionSetField) a).getField()).getValue().toString()); // ipaddress formats string already
				} else if (((OFActionSetField)a).getField() instanceof OFOxmArpTpa) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ARP_DPA + MatchUtils.SET_FIELD_DELIM + ((OFOxmArpTpa) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6NdSll) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_ND_SSL + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6NdSll) ((OFActionSetField) a).getField()).getValue().toString()); // macaddress formats string already
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6NdTll) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_ND_TTL + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6NdTll) ((OFActionSetField) a).getField()).getValue().toString()); // macaddress formats string already
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6NdTarget) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_ND_TARGET + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6NdTarget) ((OFActionSetField) a).getField()).getValue().toString()); 
				}
				/* DATA LAYER */
				else if (((OFActionSetField)a).getField() instanceof OFOxmEthType) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_DL_TYPE + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmEthType) ((OFActionSetField) a).getField()).getValue().getValue()));
				} else if (((OFActionSetField)a).getField() instanceof OFOxmEthSrc) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_DL_SRC + MatchUtils.SET_FIELD_DELIM + ((OFOxmEthSrc) ((OFActionSetField) a).getField()).getValue().toString());
				} else if (((OFActionSetField)a).getField() instanceof OFOxmEthDst) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_DL_DST + MatchUtils.SET_FIELD_DELIM + ((OFOxmEthDst) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmVlanVid) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_DL_VLAN + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmVlanVid) ((OFActionSetField) a).getField()).getValue().getVlan())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmVlanPcp) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_DL_VLAN_PCP + MatchUtils.SET_FIELD_DELIM + Byte.toString(((OFOxmVlanPcp) ((OFActionSetField) a).getField()).getValue().getValue())); 
				} 
				/* ICMP */
				else if (((OFActionSetField)a).getField() instanceof OFOxmIcmpv4Code) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ICMP_CODE + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmIcmpv4Code) ((OFActionSetField) a).getField()).getValue().getCode())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIcmpv4Type) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ICMP_TYPE + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmIcmpv4Type) ((OFActionSetField) a).getField()).getValue().getType())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIcmpv6Code) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ICMPV6_CODE + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmIcmpv6Code) ((OFActionSetField) a).getField()).getValue().getRaw())); 
				}  else if (((OFActionSetField)a).getField() instanceof OFOxmIcmpv6Type) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_ICMPV6_TYPE + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmIcmpv6Type) ((OFActionSetField) a).getField()).getValue().getRaw())); 
				}
				/* NETWORK LAYER */
				else if (((OFActionSetField)a).getField() instanceof OFOxmIpProto) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_NW_PROTO + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmIpProto) ((OFActionSetField) a).getField()).getValue().getIpProtocolNumber())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv4Src) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_NW_SRC + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv4Src) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv4Dst) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_NW_DST + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv4Dst) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6Src) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_SRC + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6Src) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6Dst) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_DST + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6Dst) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpv6Flabel) {                		
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_IPV6_FLOW_LABEL + MatchUtils.SET_FIELD_DELIM + ((OFOxmIpv6Flabel) ((OFActionSetField) a).getField()).getValue().toString()); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpEcn) { //TODO @Ryan ECN and DSCP need to have their own columns for OF1.3....
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_NW_ECN + MatchUtils.SET_FIELD_DELIM + Byte.toString(((OFOxmIpEcn) ((OFActionSetField) a).getField()).getValue().getEcnValue())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmIpDscp) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_NW_DSCP + MatchUtils.SET_FIELD_DELIM + Byte.toString(((OFOxmIpDscp) ((OFActionSetField) a).getField()).getValue().getDscpValue())); 
				} 
				/* TRANSPORT LAYER, TCP, UDP, and SCTP */
				else if (((OFActionSetField)a).getField() instanceof OFOxmTcpSrc) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_TCP_SRC + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmTcpSrc) ((OFActionSetField) a).getField()).getValue().getPort())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmTcpDst) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_TCP_DST + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmTcpDst) ((OFActionSetField) a).getField()).getValue().getPort())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmUdpSrc) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_UDP_SRC + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmUdpSrc) ((OFActionSetField) a).getField()).getValue().getPort())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmUdpDst) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_UDP_DST + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmUdpDst) ((OFActionSetField) a).getField()).getValue().getPort())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmSctpSrc) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_SCTP_SRC + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmSctpSrc) ((OFActionSetField) a).getField()).getValue().getPort())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmSctpDst) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_SCTP_DST + MatchUtils.SET_FIELD_DELIM + Integer.toString(((OFOxmSctpDst) ((OFActionSetField) a).getField()).getValue().getPort())); 
				}
				/* MPLS */
				else if (((OFActionSetField)a).getField() instanceof OFOxmMplsLabel) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_MPLS_LABEL + MatchUtils.SET_FIELD_DELIM + Long.toString(((OFOxmMplsLabel) ((OFActionSetField) a).getField()).getValue().getValue())); 
				} else if (((OFActionSetField)a).getField() instanceof OFOxmMplsTc) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_MPLS_TC + MatchUtils.SET_FIELD_DELIM + Short.toString(((OFOxmMplsTc) ((OFActionSetField) a).getField()).getValue().getValue())); 
				} // MPLS_BOS not implemented in loxi
				/* METADATA */
				else if (((OFActionSetField)a).getField() instanceof OFOxmMetadata) {
					sb.append(STR_FIELD_SET + "=" + MatchUtils.STR_METADATA + MatchUtils.SET_FIELD_DELIM + Long.toString(((OFOxmMetadata) ((OFActionSetField) a).getField()).getValue().getValue().getValue())); 
				} else {
					log.error("Could not decode Set-Field action field: {}", ((OFActionSetField) a));
				}
				break;
			default:
				log.error("Could not decode action: {}", a);
				break;
			}

		}

		return sb.toString();
	}

	/**
	 * Parses OFFlowMod actions from strings.
	 * @param fmb The OFFlowMod.Builder to set the actions for
	 * @param bigString The string containing all the actions
	 * @param log A logger to log for errors.
	 */
	public static void fromString(OFFlowMod.Builder fmb, String bigString, Logger log) {
		List<OFAction> actions = new LinkedList<OFAction>();
		if (bigString != null && !bigString.trim().isEmpty()) {
			bigString = bigString.toLowerCase();
			String[] bigStringSplit = bigString.split(","); // split into separate action=value or action=key@value pairs

			String[] tmp;
			ArrayDeque<String[]> actionToDecode = new ArrayDeque<String[]>();
			for (int i = 0; i < bigStringSplit.length; i++) {
				tmp = bigStringSplit[i].split("="); // split into separate [action, value] or [action, key@value] singles
				if (tmp.length != 2) {
					log.debug("Token " + bigStringSplit[i] + " does not have form 'key=value' parsing " + bigString);
				}
				actionToDecode.add(tmp); // actionToDecode contains [key, value] pairs. Create a queue of pairs to process.
			}	

			while (!actionToDecode.isEmpty()) {
				String[] keyPair = actionToDecode.pollFirst();
				String key;
				String pair;
				if (keyPair.length != 2) {
					log.debug("[Key, Value] {} does not have form 'key=value' parsing, which is okay for some actions e.g. 'pop_vlan'.", keyPair);
					key = keyPair[0]; // could the be case of a constant actions (e.g. copy_ttl_in)
					pair = "";
				} else {
					key = keyPair[0];
					pair = keyPair[1];
				}

				OFAction a = null;
				try {
					switch (key) {
					case STR_OUTPUT:
						a = decode_output(pair, fmb.getVersion(), log);
						break;
					case STR_ENQUEUE:
						a = decode_enqueue(pair, fmb.getVersion(), log);
						break;
					case STR_DL_SRC_SET:
						a = decode_set_src_mac(pair, fmb.getVersion(), log);
						break;
					case STR_DL_DST_SET:
						a = decode_set_dst_mac(pair, fmb.getVersion(), log);
						break;
					case STR_EXPERIMENTER:
						//no-op. Not implemented
						log.error("OFAction EXPERIMENTER not implemented.");
						break;
					case STR_FIELD_SET: /* ONLY OF1.1+ should get in here. These should only be header fields valid within a set-field. */
						String[] actionData = pair.split(MatchUtils.SET_FIELD_DELIM);
						if (actionData.length != 2) {
							throw new IllegalArgumentException("[Action, Data] " + keyPair + " does not have form 'action=data'" + actionData);
						}

						switch (actionData[0]) {
						case MatchUtils.STR_ARP_OPCODE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpOp().setValue(ArpOpcode.of(Integer.parseInt(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpOp().setValue(ArpOpcode.of(Integer.parseInt(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_ARP_SHA:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpSha().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_ARP_DHA:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpTha().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_ARP_SPA:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpSpa().setValue(IPv4Address.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_ARP_DPA:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildArpTpa().setValue(IPv4Address.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_IPV6_ND_SSL:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6NdSll().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_IPV6_ND_TTL:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6NdTll().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_IPV6_ND_TARGET:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6NdTarget().setValue(IPv6Address.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_DL_TYPE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildEthType().setValue(EthType.of(Integer.parseInt(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildEthType().setValue(EthType.of(Integer.parseInt(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_DL_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildEthSrc().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_DL_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildEthDst().setValue(MacAddress.of(actionData[1])).build())
							.build();
							break;
						case MatchUtils.STR_DL_VLAN:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildVlanVid().setValue(OFVlanVidMatch.ofVlan(Integer.parseInt(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildVlanVid().setValue(OFVlanVidMatch.ofVlan(Integer.parseInt(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_DL_VLAN_PCP:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildVlanPcp().setValue(VlanPcp.of(Byte.parseByte(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildVlanPcp().setValue(VlanPcp.of(Byte.parseByte(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_ICMP_CODE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv4Code().setValue(ICMPv4Code.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv4Code().setValue(ICMPv4Code.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_ICMP_TYPE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv4Type().setValue(ICMPv4Type.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv4Type().setValue(ICMPv4Type.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_ICMPV6_CODE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv6Code().setValue(U8.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv6Code().setValue(U8.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_ICMPV6_TYPE:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv6Type().setValue(U8.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIcmpv6Type().setValue(U8.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_NW_PROTO:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpProto().setValue(IpProtocol.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpProto().setValue(IpProtocol.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_NW_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv4Src().setValue(IPv4Address.of(actionData[1])).build())
							.build();						
							break;
						case MatchUtils.STR_NW_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv4Dst().setValue(IPv4Address.of(actionData[1])).build())
							.build();						
							break;
						case MatchUtils.STR_IPV6_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6Src().setValue(IPv6Address.of(actionData[1])).build())
							.build();						
							break;
						case MatchUtils.STR_IPV6_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6Dst().setValue(IPv6Address.of(actionData[1])).build())
							.build();						
							break;
						case MatchUtils.STR_IPV6_FLOW_LABEL:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6Flabel().setValue(IPv6FlowLabel.of(Integer.parseInt(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();			
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpv6Flabel().setValue(IPv6FlowLabel.of(Integer.parseInt(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_NW_ECN:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpEcn().setValue(IpEcn.of(Byte.parseByte(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpEcn().setValue(IpEcn.of(Byte.parseByte(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_NW_DSCP:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpDscp().setValue(IpDscp.of(Byte.parseByte(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildIpDscp().setValue(IpDscp.of(Byte.parseByte(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_SCTP_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildSctpSrc().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_SCTP_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildSctpDst().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_TCP_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildTcpSrc().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_TCP_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildTcpDst().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_UDP_SRC:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildUdpSrc().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_UDP_DST:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildUdpDst().setValue(TransportPort.of(Integer.parseInt(actionData[1]))).build())
							.build();	
							break;
						case MatchUtils.STR_MPLS_LABEL:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMplsLabel().setValue(U32.of(Long.parseLong(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMplsLabel().setValue(U32.of(Long.parseLong(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_MPLS_TC:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMplsTc().setValue(U8.of(Short.parseShort(actionData[1].replaceFirst("0x", ""), 16))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMplsTc().setValue(U8.of(Short.parseShort(actionData[1]))).build())
										.build();
							}
							break;
						case MatchUtils.STR_MPLS_BOS:
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
							.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMplsBos().setValue(OFBooleanValue.of(Boolean.parseBoolean(actionData[1]))).build()) // interprets anything other than "true" as false
							.build();
							break;
						case MatchUtils.STR_METADATA:
							if (actionData[1].startsWith("0x")) {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMetadata().setValue(OFMetadata.of(U64.of(Long.parseLong(actionData[1].replaceFirst("0x", ""), 16)))).build())
										.build();
							} else {
								a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetField()
										.setField(OFFactories.getFactory(fmb.getVersion()).oxms().buildMetadata().setValue(OFMetadata.of(U64.of(Long.parseLong(actionData[1])))).build())
										.build();
							}
							break;
						default:
							log.error("UNEXPECTED OF1.2+ SET-FIELD '{}'", actionData);
							break;
						}					
						break;
					case STR_GROUP:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildGroup()
									.setGroup(OFGroup.of(Integer.parseInt(pair.replaceFirst("0x", ""), 16)))
									.build();	
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildGroup()
									.setGroup(OFGroup.of(Integer.parseInt(pair)))
									.build();		
						}
						break;
					case STR_MPLS_LABEL_SET:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsLabel()
									.setMplsLabel(Long.parseLong(pair.replaceFirst("0x", ""), 16))
									.build();			
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsLabel()
									.setMplsLabel(Long.parseLong(pair))
									.build();					
						}
						break;
					case STR_MPLS_POP:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPopMpls()
									.setEthertype(EthType.of(Integer.parseInt(pair.replaceFirst("0x", ""), 16)))
									.build();
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPopMpls()
									.setEthertype(EthType.of(Integer.parseInt(pair)))
									.build();	
						}
						break;
					case STR_MPLS_PUSH:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushMpls()
									.setEthertype(EthType.of(Integer.parseInt(pair.replaceFirst("0x", ""), 16)))
									.build();		
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushMpls()
									.setEthertype(EthType.of(Integer.parseInt(pair)))
									.build();			
						}
						break;
					case STR_MPLS_TC_SET:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsTc()
									.setMplsTc(Short.parseShort(pair.replaceFirst("0x", ""), 16))
									.build();	
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsTc()
									.setMplsTc(Short.parseShort(pair))
									.build();			
						}
						break;
					case STR_MPLS_TTL_DEC:
						a = OFFactories.getFactory(fmb.getVersion()).actions().decMplsTtl();
						break;
					case STR_MPLS_TTL_SET:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsTtl()
									.setMplsTtl(Short.parseShort(pair.replaceFirst("0x", ""), 16))
									.build();	
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetMplsTtl()
									.setMplsTtl(Short.parseShort(pair))
									.build();				
						}
						break;
					case STR_NW_TOS_SET:
						a = decode_set_tos_bits(pair, fmb.getVersion(), log); // should only be used by OF1.0
						break;
					case STR_NW_SRC_SET:
						a = decode_set_src_ip(pair, fmb.getVersion(), log);
						break;
					case STR_NW_DST_SET:
						a = decode_set_dst_ip(pair, fmb.getVersion(), log);
						break;
					case STR_NW_ECN_SET: // loxi does not support DSCP set for OF1.1
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetNwEcn()
									.setNwEcn(IpEcn.of(Byte.parseByte(pair.replaceFirst("0x", ""), 16)))
									.build();		
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetNwEcn()
									.setNwEcn(IpEcn.of(Byte.parseByte(pair)))
									.build();							
						}
						break;
					case STR_NW_TTL_DEC:
						a = OFFactories.getFactory(fmb.getVersion()).actions().decNwTtl();
						break;
					case STR_NW_TTL_SET:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetNwTtl()
									.setNwTtl(Short.parseShort(pair.replaceFirst("0x", ""), 16))
									.build();
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetNwTtl()
									.setNwTtl(Short.parseShort(pair))
									.build();						
						}
						break;
					case STR_PBB_POP:
						a = OFFactories.getFactory(fmb.getVersion()).actions().popPbb();
						break;
					case STR_PBB_PUSH:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushPbb()
									.setEthertype(EthType.of(Integer.parseInt(pair.replaceFirst("0x", ""), 16)))
									.build();				
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushPbb()
									.setEthertype(EthType.of(Integer.parseInt(pair)))
									.build();					
						}
						break;
					case STR_QUEUE_SET:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetQueue()
									.setQueueId(Long.parseLong(pair.replaceFirst("0x", ""), 16))
									.build();	
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildSetQueue()
									.setQueueId(Long.parseLong(pair))
									.build();					
						}
						break;
					case STR_TP_SRC_SET:
						a = decode_set_src_port(pair, fmb.getVersion(), log);
						break;
					case STR_TP_DST_SET:
						a = decode_set_dst_port(pair, fmb.getVersion(), log);
						break;
					case STR_TTL_IN_COPY:
						a = OFFactories.getFactory(fmb.getVersion()).actions().copyTtlIn();
						break;
					case STR_TTL_OUT_COPY:
						a = OFFactories.getFactory(fmb.getVersion()).actions().copyTtlOut();
						break;
					case STR_VLAN_POP:
						a = OFFactories.getFactory(fmb.getVersion()).actions().popVlan();
						break;
					case STR_VLAN_PUSH:
						if (pair.startsWith("0x")) {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushVlan()
									.setEthertype(EthType.of(Integer.parseInt(pair.replaceFirst("0x", ""), 16)))
									.build();
						} else {
							a = OFFactories.getFactory(fmb.getVersion()).actions().buildPushVlan()
									.setEthertype(EthType.of(Integer.parseInt(pair)))
									.build();		
						}
						break;
					case STR_VLAN_STRIP:
						a = OFFactories.getFactory(fmb.getVersion()).actions().stripVlan();
						break;
					case STR_VLAN_SET_VID:
						a = decode_set_vlan_id(pair, fmb.getVersion(), log);
						break;
					case STR_VLAN_SET_PCP:
						a = decode_set_vlan_priority(pair, fmb.getVersion(), log);
						break;
					default:
						log.error("UNEXPECTED ACTION KEY '{}'", keyPair);
						break;
					}

				} catch (Exception e) {
					log.error("Illegal Action: " + e.getMessage());
				}
				if (a != null) {
					actions.add(a);
				}
			}
			log.debug("actions: {}", actions);
			fmb.setActions(actions);
		} else {
			log.debug("actions not found --> drop");
		}		
		return;
	} 
	
	/**
	 * Parse string and numerical port representations.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. Data can be any signed integer
	 * or hex (w/leading 0x prefix) as a string or the special string port
	 * STR_PORT_* as defined in {@link MatchUtils}.
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionOutput decode_output(String actionToDecode, OFVersion version, Logger log) {
		OFActionOutput.Builder ab = OFFactories.getFactory(version).actions().buildOutput();
		actionToDecode = actionToDecode.trim().toLowerCase();
		OFPort port = OFPort.ZERO;
		switch (actionToDecode) {
		case MatchUtils.STR_PORT_ALL:
			port = OFPort.ALL;
			break;
		case MatchUtils.STR_PORT_CONTROLLER:
			port = OFPort.CONTROLLER;
			break;
		case MatchUtils.STR_PORT_FLOOD:
			port = OFPort.FLOOD;
			break;
		case MatchUtils.STR_PORT_IN_PORT:
			port = OFPort.IN_PORT;
			break;
		case MatchUtils.STR_PORT_LOCAL:
			port = OFPort.LOCAL;
			break;
		case MatchUtils.STR_PORT_NORMAL:
			port = OFPort.NORMAL;
			break;
		case MatchUtils.STR_PORT_TABLE:
			port = OFPort.TABLE;
			break;
		default:
			log.debug("Port {} was not a special port string. Parsing as raw int or hex", actionToDecode);
		}
		if (!port.equals(OFPort.ZERO)) {
			ab.setPort(port);
			ab.setMaxLen(Integer.MAX_VALUE);
			log.debug("action {}", ab.build());
			return ab.build();
		} else {
			try {
				port = OFPort.of(U32.of(actionToDecode.contains("0x") ? 
						Long.parseLong(actionToDecode.replaceFirst("0x", ""), 16) : 
							Long.parseLong(actionToDecode)).getRaw());
				ab.setPort(port);
				ab.setMaxLen(Integer.MAX_VALUE);
				return ab.build();
			} catch (NumberFormatException e) {
				log.error("Could not parse port: '{}'", actionToDecode);
				return null;
			}
		}
	}

	/**
	 * Parse enqueue actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. Data with a leading 0x is permitted.
	 *
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionEnqueue decode_enqueue(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(actionToDecode);
		if (n.matches()) {
			OFPort port = OFPort.of(0);
			if (n.group(1) != null) {
				try {
					port = OFPort.of(get_short(n.group(1)));
				}
				catch (NumberFormatException e) {
					log.debug("Invalid port-num in: '{}' (error ignored)", actionToDecode);
					return null;
				}
			}

			int queueid = 0;
			if (n.group(2) != null) {
				try {
					queueid = get_int(n.group(2));
				}
				catch (NumberFormatException e) {
					log.debug("Invalid queue-id in: '{}' (error ignored)", actionToDecode);
					return null;
				}
			}
			OFActionEnqueue.Builder ab = OFFactories.getFactory(version).actions().buildEnqueue();
			ab.setPort(port);
			ab.setQueueId(queueid);
			log.debug("action {}", ab.build());
			return ab.build();
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
	}

	/**
	 * Parse set_vlan_id actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. Data with a leading 0x is permitted.
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetVlanVid decode_set_vlan_id(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("((?:0x)?\\d+)").matcher(actionToDecode);
		if (n.matches()) {            
			if (n.group(1) != null) {
				try {
					VlanVid vlanid = VlanVid.ofVlan(get_short(n.group(1)));
					OFActionSetVlanVid.Builder ab = OFFactories.getFactory(version).actions().buildSetVlanVid();
					ab.setVlanVid(vlanid);
					log.debug("action {}", ab.build());
					return ab.build();
				}
				catch (NumberFormatException e) {
					log.debug("Invalid VLAN in: {} (error ignored)", actionToDecode);
					return null;
				}
			}          
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_vlan_pcp actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. Data with a leading 0x is permitted.
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetVlanPcp decode_set_vlan_priority(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("((?:0x)?\\d+)").matcher(actionToDecode); 
		if (n.matches()) {            
			if (n.group(1) != null) {
				try {
					VlanPcp prior = VlanPcp.of(get_byte(n.group(1)));
					OFActionSetVlanPcp.Builder ab = OFFactories.getFactory(version).actions().buildSetVlanPcp();
					ab.setVlanPcp(prior);
					log.debug("action {}", ab.build());
					return ab.build();
				}
				catch (NumberFormatException e) {
					log.debug("Invalid VLAN priority in: {} (error ignored)", actionToDecode);
					return null;
				}
			}
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_dl_src actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder.
	 * 
	 * TODO should consider using MacAddress's built-in parser....
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetDlSrc decode_set_src_mac(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(actionToDecode); 
		if (n.matches()) {
			MacAddress macaddr = MacAddress.of(get_mac_addr(n, actionToDecode, log));
			if (macaddr != null) {
				OFActionSetDlSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetDlSrc();
				ab.setDlAddr(macaddr);
				log.debug("action {}", ab.build());
				return ab.build();
			}            
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_dl_dst actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder.
	 * 
	 * TODO should consider using MacAddress's built-in parser....
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetDlDst decode_set_dst_mac(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(actionToDecode);
		if (n.matches()) {
			MacAddress macaddr = MacAddress.of(get_mac_addr(n, actionToDecode, log));            
			if (macaddr != null) {
				OFActionSetDlDst.Builder ab = OFFactories.getFactory(version).actions().buildSetDlDst();
				ab.setDlAddr(macaddr);
				log.debug("action {}", ab.build());
				return ab.build();
			}
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_tos actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. A leading 0x is permitted.
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetNwTos decode_set_tos_bits(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("((?:0x)?\\d+)").matcher(actionToDecode); 
		if (n.matches()) {
			if (n.group(1) != null) {
				try {
					byte tosbits = get_byte(n.group(1));
					OFActionSetNwTos.Builder ab = OFFactories.getFactory(version).actions().buildSetNwTos();
					ab.setNwTos(tosbits);
					log.debug("action {}", ab.build());
					return ab.build();
				}
				catch (NumberFormatException e) {
					log.debug("Invalid dst-port in: {} (error ignored)", actionToDecode);
					return null;
				}
			}
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_nw_src actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder.
	 * 
	 * TODO should consider using IPv4AddressWithMask's built-in parser....
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetNwSrc decode_set_src_ip(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(actionToDecode);
		if (n.matches()) {
			IPv4Address ipaddr = IPv4Address.of(get_ip_addr(n, actionToDecode, log));
			OFActionSetNwSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetNwSrc();
			ab.setNwAddr(ipaddr);
			log.debug("action {}", ab.build());
			return ab.build();
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
	}

	/**
	 * Parse set_nw_dst actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder.
	 * 
	 * TODO should consider using IPv4AddressWithMask's built-in parser....
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetNwDst decode_set_dst_ip(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(actionToDecode);
		if (n.matches()) {
			IPv4Address ipaddr = IPv4Address.of(get_ip_addr(n, actionToDecode, log));
			OFActionSetNwDst.Builder ab = OFFactories.getFactory(version).actions().buildSetNwDst();
			ab.setNwAddr(ipaddr);
			log.debug("action {}", ab.build());
			return ab.build();
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
	}

	/**
	 * Parse set_tp_src actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. A leading 0x is permitted.
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFActionSetTpSrc decode_set_src_port(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("((?:0x)?\\d+)").matcher(actionToDecode); 
		if (n.matches()) {
			if (n.group(1) != null) {
				try {
					TransportPort portnum = TransportPort.of(get_int(n.group(1)));
					OFActionSetTpSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetTpSrc();
					ab.setTpPort(portnum);
					log.debug("action {}", ab.build());
					return ab.build();
				} 
				catch (NumberFormatException e) {
					log.debug("Invalid src-port in: {} (error ignored)", actionToDecode);
					return null;
				}
			}
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * Parse set_tp_dst actions.
	 * The key and delimiter for the action should be omitted, and only the
	 * data should be presented to this decoder. A leading 0x is permitted.
	 * 
	 * @param actionToDecode; The action as a string to decode
	 * @param version; The OF version to create the action for
	 * @param log
	 * @return
	 */
	private static OFAction decode_set_dst_port(String actionToDecode, OFVersion version, Logger log) {
		Matcher n = Pattern.compile("((?:0x)?\\d+)").matcher(actionToDecode);
		if (n.matches()) {
			if (n.group(1) != null) {
				try {
					TransportPort portnum = TransportPort.of(get_int(n.group(1)));
					OFActionSetTpDst.Builder ab = OFFactories.getFactory(version).actions().buildSetTpDst();
					ab.setTpPort(portnum);
					log.debug("action {}", ab.build());
					return ab.build();
				}
				catch (NumberFormatException e) {
					log.debug("Invalid dst-port in: {} (error ignored)", actionToDecode);
					return null;
				}
			}
		}
		else {
			log.debug("Invalid action: '{}'", actionToDecode);
			return null;
		}
		return null;
	}

	/**
	 * This is out of date and should be replaced with the built-in parser of MacAddress
	 * @param n
	 * @param actionToDecode
	 * @param log
	 * @return
	 */
	private static byte[] get_mac_addr(Matcher n, String actionToDecode, Logger log) {
		byte[] macaddr = new byte[6];     
		for (int i=0; i<6; i++) {
			if (n.group(i+1) != null) {
				try {
					macaddr[i] = get_byte("0x" + n.group(i+1));
				}
				catch (NumberFormatException e) {
					log.debug("Invalid src-mac in: '{}' (error ignored)", actionToDecode);
					return null;
				}
			}
			else { 
				log.debug("Invalid src-mac in: '{}' (null, error ignored)", actionToDecode);
				return null;
			}
		}  
		return macaddr;
	}

	/**
	 * This is out of date and should be replaced with the built-in parser of IPv4AddressWithMask
	 * @param n
	 * @param actionToDecode
	 * @param log
	 * @return
	 */
	private static int get_ip_addr(Matcher n, String actionToDecode, Logger log) {
		int ipaddr = 0;
		for (int i=0; i<4; i++) {
			if (n.group(i+1) != null) {
				try {
					ipaddr = ipaddr<<8;
					ipaddr = ipaddr | get_int(n.group(i+1));
				}
				catch (NumberFormatException e) {
					log.debug("Invalid src-ip in: '{}' (error ignored)", actionToDecode);
					return 0;
				}
			}
			else {
				log.debug("Invalid src-ip in: '{}' (null, error ignored)", actionToDecode);
				return 0;
			}
		}
		return ipaddr;
	}

	/**
	 * Parse int as decimal, hex (start with 0x or #) or octal (starts with 0)
	 * @param str
	 * @return
	 */
	private static int get_int(String str) {
		return Integer.decode(str);
	}

	/**
	 * Parse short as decimal, hex (start with 0x or #) or octal (starts with 0)
	 * @param str
	 * @return
	 */
	private static short get_short(String str) {
		return (short)(int)Integer.decode(str);
	}

	/**
	 * Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0)
	 * @param str
	 * @return
	 */
	private static byte get_byte(String str) {
		return Integer.decode(str).byteValue();
	}

}