diff --git a/src/main/java/org/openflow/protocol/action/OFActionType.java b/src/main/java/org/openflow/protocol/action/OFActionType.java index b0c2c47d9af7b3181539f9f7a9174bf709a96bc4..18229170b77dc1a01984cfc9f156272f5ad858c5 100644 --- a/src/main/java/org/openflow/protocol/action/OFActionType.java +++ b/src/main/java/org/openflow/protocol/action/OFActionType.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * 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 @@ -94,7 +94,7 @@ public enum OFActionType { VENDOR (0xffff, OFActionVendor.class, new Instantiable<OFAction>() { @Override public OFAction instantiate() { - return new OFActionVendor(); + return new OFActionVendorGeneric(); }}); protected static OFActionType[] mapping; diff --git a/src/main/java/org/openflow/protocol/action/OFActionVendor.java b/src/main/java/org/openflow/protocol/action/OFActionVendor.java index b5a15c280cd5342cd28171f378f0afa212a1d3f3..c8f3cd1ec94e84a31948c9bac0af305b5c0d7763 100644 --- a/src/main/java/org/openflow/protocol/action/OFActionVendor.java +++ b/src/main/java/org/openflow/protocol/action/OFActionVendor.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * 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 @@ -24,7 +24,7 @@ import org.jboss.netty.buffer.ChannelBuffer; * * @author David Erickson (daviderickson@cs.stanford.edu) */ -public class OFActionVendor extends OFAction { +public abstract class OFActionVendor extends OFAction { public static int MINIMUM_LENGTH = 8; protected int vendor; diff --git a/src/main/java/org/openflow/protocol/action/OFActionVendorGeneric.java b/src/main/java/org/openflow/protocol/action/OFActionVendorGeneric.java new file mode 100644 index 0000000000000000000000000000000000000000..4f7859f5ec43e8e13ff8340c50f5cda6f2d2311c --- /dev/null +++ b/src/main/java/org/openflow/protocol/action/OFActionVendorGeneric.java @@ -0,0 +1,96 @@ +/** +* Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior +* University +* +* 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.openflow.protocol.action; + + +import java.util.Arrays; + +import org.jboss.netty.buffer.ChannelBuffer; + +/** A generic / unparsed vendor action. This action is returned by + * BasicFactory.readFromWire if no more specific OFVendorActionFactory + * is registered. + * + * @author David Erickson (daviderickson@cs.stanford.edu) + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFActionVendorGeneric extends OFActionVendor { + public static int MINIMUM_LENGTH = 8; + + private final static byte[] EMPTY_ARRAY = new byte[0]; + + protected byte[] vendorData; + + public OFActionVendorGeneric() { + super(); + } + + public byte[] getVendorData() { + return vendorData; + } + + public void setVendorData(byte[] vendorData) { + this.vendorData = vendorData; + } + + @Override + public void readFrom(ChannelBuffer data) { + super.readFrom(data); + + int vendorDataLength = this.getLength() - MINIMUM_LENGTH; + if (vendorDataLength > 0) { + vendorData = new byte[vendorDataLength]; + data.readBytes(vendorData); + } else { + vendorData = EMPTY_ARRAY; + } + } + + @Override + public void writeTo(ChannelBuffer data) { + super.writeTo(data); + data.writeInt(this.vendor); + data.writeBytes(vendorData); + } + + @Override + public int hashCode() { + final int prime = 379; + int result = super.hashCode(); + result = prime * result + Arrays.hashCode(vendorData); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + if (!(obj instanceof OFActionVendorGeneric)) { + return false; + } + OFActionVendorGeneric other = (OFActionVendorGeneric) obj; + if (!Arrays.equals(vendorData, other.vendorData)) { + return false; + } + return true; + } +} diff --git a/src/main/java/org/openflow/protocol/factory/BasicFactory.java b/src/main/java/org/openflow/protocol/factory/BasicFactory.java index 7b06f2c0c91a5a8a966be3430d34f4dbbb9f23cc..c4d148337526ecbae5ea8d9aa67d89e657404da7 100644 --- a/src/main/java/org/openflow/protocol/factory/BasicFactory.java +++ b/src/main/java/org/openflow/protocol/factory/BasicFactory.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * 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 @@ -25,6 +25,7 @@ import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFType; import org.openflow.protocol.action.OFAction; import org.openflow.protocol.action.OFActionType; +import org.openflow.protocol.action.OFActionVendor; import org.openflow.protocol.statistics.OFStatistics; import org.openflow.protocol.statistics.OFStatisticsType; import org.openflow.protocol.statistics.OFVendorStatistics; @@ -45,6 +46,12 @@ import org.openflow.protocol.vendor.OFVendorId; public class BasicFactory implements OFMessageFactory, OFActionFactory, OFStatisticsFactory, OFVendorDataFactory { + private final OFVendorActionRegistry vendorActionRegistry; + + public BasicFactory() { + vendorActionRegistry = OFVendorActionRegistry.getInstance(); + } + /** * create and return a new instance of a message for OFType t. Also injects * factories for those message types that implement the *FactoryAware @@ -169,19 +176,42 @@ public class BasicFactory implements OFMessageFactory, OFActionFactory, (data.readerIndex() + demux.getLengthU()) > end)) return results; - ofa = getAction(demux.getType()); - ofa.readFrom(data); - if (OFAction.class.equals(ofa.getClass())) { - // advance the position for un-implemented messages - data.readerIndex(data.readerIndex()+(ofa.getLengthU() - - OFAction.MINIMUM_LENGTH)); - } + ofa = parseActionOne(demux.getType(), data); results.add(ofa); } return results; } + private OFAction parseActionOne(OFActionType type, ChannelBuffer data) { + OFAction ofa; + data.markReaderIndex(); + ofa = getAction(type); + ofa.readFrom(data); + + if(type == OFActionType.VENDOR) { + OFActionVendor vendorAction = (OFActionVendor) ofa; + + OFVendorActionFactory vendorActionFactory = vendorActionRegistry.get(vendorAction.getVendor()); + + if(vendorActionFactory != null) { + // if we have a specific vendorActionFactory for this vendor id, + // delegate to it for vendor-specific reparsing of the message + data.resetReaderIndex(); + OFActionVendor newAction = vendorActionFactory.readFrom(data); + if(newAction != null) + ofa = newAction; + } + } + + if (OFAction.class.equals(ofa.getClass())) { + // advance the position for un-implemented messages + data.readerIndex(data.readerIndex()+(ofa.getLengthU() - + OFAction.MINIMUM_LENGTH)); + } + return ofa; + } + @Override public OFActionFactory getActionFactory() { return this; diff --git a/src/main/java/org/openflow/protocol/factory/OFVendorActionFactory.java b/src/main/java/org/openflow/protocol/factory/OFVendorActionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..ea27b6119820c834a00b5723f8c73efd910fbefc --- /dev/null +++ b/src/main/java/org/openflow/protocol/factory/OFVendorActionFactory.java @@ -0,0 +1,27 @@ +package org.openflow.protocol.factory; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.protocol.action.OFActionVendor; + +/** Interface contract for an actionfactory that creates vendor-specific actions. + * VendorActionFactories are registered with the BasicFactory for a specific + * vendor id. + * <p> + * <b>Note:</b> Implementations are expected to be thread-safe. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public interface OFVendorActionFactory { + + /** parse the data from the wire, create and return a vendor-specific action. + * + * @param data contains a serialized vendor action at the current readerPosition. + * The full message is guaranteed to be available in the buffer. + * + * @return upon success returns a newly allocated vendor-specific + * action instance, and advances the readerPosition in data for the + * entire length. Upon failure, returns null and leaves the readerPosition + * in data unmodified. + */ + OFActionVendor readFrom(ChannelBuffer data); +} diff --git a/src/main/java/org/openflow/protocol/factory/OFVendorActionRegistry.java b/src/main/java/org/openflow/protocol/factory/OFVendorActionRegistry.java new file mode 100644 index 0000000000000000000000000000000000000000..190bdd4fd42f7bf4ef58dc88a9ea52399eb5a512 --- /dev/null +++ b/src/main/java/org/openflow/protocol/factory/OFVendorActionRegistry.java @@ -0,0 +1,34 @@ +package org.openflow.protocol.factory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** Singleton registry object that holds a mapping from vendor ids to vendor-specific + * mapping factories. Threadsafe. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFVendorActionRegistry { + private static class InstanceHolder { + private final static OFVendorActionRegistry instance = new OFVendorActionRegistry(); + } + + public static OFVendorActionRegistry getInstance() { + return InstanceHolder.instance; + } + private final Map <Integer, OFVendorActionFactory> vendorActionFactories; + + public OFVendorActionRegistry() { + vendorActionFactories = new ConcurrentHashMap<Integer, OFVendorActionFactory>(); + } + + public OFVendorActionFactory register(int vendorId, OFVendorActionFactory factory) { + return vendorActionFactories.put(vendorId, factory); + } + + public OFVendorActionFactory get(int vendorId) { + return vendorActionFactories.get(vendorId); + } + + +} diff --git a/src/test/java/org/openflow/protocol/BasicFactoryTest.java b/src/test/java/org/openflow/protocol/BasicFactoryTest.java index 6825008d98b0f9c0134b77a3f13dc39b54911fd7..312fcd3f57d616c6716e7f702e56a086a44609d0 100644 --- a/src/test/java/org/openflow/protocol/BasicFactoryTest.java +++ b/src/test/java/org/openflow/protocol/BasicFactoryTest.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * 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 @@ -17,16 +17,23 @@ package org.openflow.protocol; +import static org.junit.Assert.assertArrayEquals; + import java.util.List; +import junit.framework.TestCase; + import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; +import org.openflow.protocol.action.MockVendorAction; +import org.openflow.protocol.action.MockVendorActionFactory; +import org.openflow.protocol.action.OFAction; +import org.openflow.protocol.action.OFActionVendorGeneric; import org.openflow.protocol.factory.BasicFactory; import org.openflow.protocol.factory.MessageParseException; +import org.openflow.protocol.factory.OFVendorActionRegistry; import org.openflow.util.U16; -import junit.framework.TestCase; - public class BasicFactoryTest extends TestCase { public void testCreateAndParse() throws MessageParseException { @@ -78,4 +85,50 @@ public class BasicFactoryTest extends TestCase { } } + public void testCustomVendorAction() throws MessageParseException { + BasicFactory factory = new BasicFactory(); + OFVendorActionRegistry.getInstance().register( + MockVendorAction.VENDOR_ID, new MockVendorActionFactory()); + + + byte[] deadBeefMessage = { + (byte) 0xff, (byte) 0xff, // action vendor + 0x00, 0x10, // length + (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte)0xef, // deadbeaf + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08 // pad + }; + + ChannelBuffer buf = ChannelBuffers.copiedBuffer(deadBeefMessage); + + List<OFAction> actions = factory.parseActions(buf,deadBeefMessage.length); + assertEquals(1, actions.size()); + OFAction ofAction = actions.get(0); + assertTrue("Action should be MockVendorAction, but is "+ofAction.getClass(), ofAction instanceof MockVendorAction); + assertArrayEquals( new byte[] { 1,2,3,4,5,6,7,8}, ((MockVendorAction)ofAction).getMockData()); + + + } + + public void testGenericVendorAction() throws MessageParseException { + byte[] nonDeadBeefMessage = { + (byte) 0xff, (byte) 0xff, // action vendor + 0x00, 0x10, // length + (byte) 0x7e, (byte) 0xe7, (byte) 0xbe, (byte)0xef, // deadbeaf + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08 // pad + }; + + BasicFactory factory = new BasicFactory(); + OFVendorActionRegistry.getInstance().register( + MockVendorAction.VENDOR_ID, new MockVendorActionFactory()); + + ChannelBuffer buf = ChannelBuffers.copiedBuffer(nonDeadBeefMessage); + + List<OFAction> actions = factory.parseActions(buf,nonDeadBeefMessage.length); + assertEquals(1, actions.size()); + OFAction ofAction = actions.get(0); + assertTrue("Action should be OFActionVendorGeneric, but is "+ofAction.getClass(), ofAction instanceof OFActionVendorGeneric); + } + } diff --git a/src/test/java/org/openflow/protocol/action/MockVendorAction.java b/src/test/java/org/openflow/protocol/action/MockVendorAction.java new file mode 100644 index 0000000000000000000000000000000000000000..49b69fb8181d4a6dd6959a40786d643fe05ead20 --- /dev/null +++ b/src/test/java/org/openflow/protocol/action/MockVendorAction.java @@ -0,0 +1,41 @@ +package org.openflow.protocol.action; + +import org.jboss.netty.buffer.ChannelBuffer; + + +public class MockVendorAction extends OFActionVendor { + public static final int VENDOR_ID = 0xdeadbeef; + + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + private byte[] mockData; + + public byte[] getMockData() { + return mockData; + } + + public void setMockData(byte[] mockData) { + this.mockData = mockData; + } + + @Override + public void readFrom(ChannelBuffer data) { + super.readFrom(data); + + int dataLength = getLength() - MINIMUM_LENGTH; + if(dataLength > 0) { + mockData = new byte[dataLength]; + data.readBytes(mockData); + } else { + mockData = EMPTY_BYTE_ARRAY; + } + + } + + @Override + public void writeTo(ChannelBuffer data) { + super.writeTo(data); + data.writeBytes(mockData); + } + + +} diff --git a/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java b/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..bbc254ca0074cc9e418c5541f976d000074cf0b1 --- /dev/null +++ b/src/test/java/org/openflow/protocol/action/MockVendorActionFactory.java @@ -0,0 +1,15 @@ +package org.openflow.protocol.action; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.openflow.protocol.factory.OFVendorActionFactory; + +public class MockVendorActionFactory implements OFVendorActionFactory { + + @Override + public OFActionVendor readFrom(ChannelBuffer data) { + MockVendorAction action = new MockVendorAction(); + action.readFrom(data); + return action; + } + +} diff --git a/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java b/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..31ad675d6a0a708c5a69a7ae3b0dcb21bc24e5c5 --- /dev/null +++ b/src/test/java/org/openflow/protocol/action/OFVendorActionRegistryTest.java @@ -0,0 +1,17 @@ +package org.openflow.protocol.action; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.openflow.protocol.factory.OFVendorActionRegistry; + +public class OFVendorActionRegistryTest { + + @Test + public void test() { + MockVendorActionFactory factory = new MockVendorActionFactory(); + OFVendorActionRegistry.getInstance().register(MockVendorAction.VENDOR_ID, factory); + assertEquals(factory, OFVendorActionRegistry.getInstance().get(MockVendorAction.VENDOR_ID)); + } + +}