/** * 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.factory; import java.util.ArrayList; import java.util.List; import org.jboss.netty.buffer.ChannelBuffer; 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; import org.openflow.protocol.vendor.OFByteArrayVendorData; import org.openflow.protocol.vendor.OFVendorData; import org.openflow.protocol.vendor.OFVendorDataType; import org.openflow.protocol.vendor.OFVendorId; /** * A basic OpenFlow factory that supports naive creation of both Messages and * Actions. * * @author David Erickson (daviderickson@cs.stanford.edu) * @author Rob Sherwood (rob.sherwood@stanford.edu) * */ public enum BasicFactory implements OFMessageFactory, OFActionFactory, OFStatisticsFactory, OFVendorDataFactory { SINGLETON_INSTANCE; private final OFVendorActionRegistry vendorActionRegistry; private BasicFactory() { vendorActionRegistry = OFVendorActionRegistry.getInstance(); } public static BasicFactory getInstance() { return SINGLETON_INSTANCE; } /** * create and return a new instance of a message for OFType t. Also injects * factories for those message types that implement the *FactoryAware * interfaces. * * @return a newly created instance that may be modified / used freely by * the caller */ @Override public OFMessage getMessage(OFType t) { OFMessage message = t.newInstance(); injectFactories(message); return message; } @Override public List<OFMessage> parseMessage(ChannelBuffer data) throws MessageParseException { List<OFMessage> msglist = new ArrayList<OFMessage>(); OFMessage msg = null; while (data.readableBytes() >= OFMessage.MINIMUM_LENGTH) { data.markReaderIndex(); msg = this.parseMessageOne(data); if (msg == null) { data.resetReaderIndex(); break; } else { msglist.add(msg); } } if (msglist.size() == 0) { return null; } return msglist; } public OFMessage parseMessageOne(ChannelBuffer data) throws MessageParseException { try { OFMessage demux = new OFMessage(); OFMessage ofm = null; if (data.readableBytes() < OFMessage.MINIMUM_LENGTH) return ofm; data.markReaderIndex(); demux.readFrom(data); data.resetReaderIndex(); if (demux.getLengthU() > data.readableBytes()) return ofm; ofm = getMessage(demux.getType()); if (ofm == null) return null; injectFactories(ofm); ofm.readFrom(data); if (OFMessage.class.equals(ofm.getClass())) { // advance the position for un-implemented messages data.readerIndex(data.readerIndex()+(ofm.getLengthU() - OFMessage.MINIMUM_LENGTH)); } return ofm; } catch (Exception e) { /* Write the offending data along with the error message */ data.resetReaderIndex(); String msg = "Message Parse Error for packet:" + dumpBuffer(data) + "\nException: " + e.toString(); data.resetReaderIndex(); throw new MessageParseException(msg, e); } } private void injectFactories(OFMessage ofm) { if (ofm instanceof OFActionFactoryAware) { ((OFActionFactoryAware)ofm).setActionFactory(this); } if (ofm instanceof OFMessageFactoryAware) { ((OFMessageFactoryAware)ofm).setMessageFactory(this); } if (ofm instanceof OFStatisticsFactoryAware) { ((OFStatisticsFactoryAware)ofm).setStatisticsFactory(this); } if (ofm instanceof OFVendorDataFactoryAware) { ((OFVendorDataFactoryAware)ofm).setVendorDataFactory(this); } } @Override public OFAction getAction(OFActionType t) { return t.newInstance(); } @Override public List<OFAction> parseActions(ChannelBuffer data, int length) { return parseActions(data, length, 0); } @Override public List<OFAction> parseActions(ChannelBuffer data, int length, int limit) { List<OFAction> results = new ArrayList<OFAction>(); OFAction demux = new OFAction(); OFAction ofa; int end = data.readerIndex() + length; while (limit == 0 || results.size() <= limit) { if ((data.readableBytes() < OFAction.MINIMUM_LENGTH || (data.readerIndex() + OFAction.MINIMUM_LENGTH) > end)) return results; data.markReaderIndex(); demux.readFrom(data); data.resetReaderIndex(); if ((demux.getLengthU() > data.readableBytes() || (data.readerIndex() + demux.getLengthU()) > end)) return results; 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; } @Override public OFStatistics getStatistics(OFType t, OFStatisticsType st) { return st.newInstance(t); } @Override public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st, ChannelBuffer data, int length) { return parseStatistics(t, st, data, length, 0); } /** * @param t * OFMessage type: should be one of stats_request or stats_reply * @param st * statistics type of this message, e.g., DESC, TABLE * @param data * buffer to read from * @param length * length of statistics * @param limit * number of statistics to grab; 0 == all * * @return list of statistics */ @Override public List<OFStatistics> parseStatistics(OFType t, OFStatisticsType st, ChannelBuffer data, int length, int limit) { List<OFStatistics> results = new ArrayList<OFStatistics>(); OFStatistics statistics = getStatistics(t, st); int start = data.readerIndex(); int count = 0; while (limit == 0 || results.size() <= limit) { // TODO Create a separate MUX/DEMUX path for vendor stats if (statistics instanceof OFVendorStatistics) ((OFVendorStatistics)statistics).setLength(length); /** * can't use data.remaining() here, b/c there could be other data * buffered past this message */ if ((length - count) >= statistics.getLength()) { if (statistics instanceof OFActionFactoryAware) ((OFActionFactoryAware)statistics).setActionFactory(this); statistics.readFrom(data); results.add(statistics); count += statistics.getLength(); statistics = getStatistics(t, st); } else { if (count < length) { /** * Nasty case: partial/incomplete statistic found even * though we have a full message. Found when NOX sent * agg_stats request with wrong agg statistics length (52 * instead of 56) * * just throw the rest away, or we will break framing */ data.readerIndex(start + length); } return results; } } return results; // empty; no statistics at all } @Override public OFVendorData getVendorData(OFVendorId vendorId, OFVendorDataType vendorDataType) { if (vendorDataType == null) return null; return vendorDataType.newInstance(); } /** * Attempts to parse and return the OFVendorData contained in the given * ChannelBuffer, beginning right after the vendor id. * @param vendor the vendor id that was parsed from the OFVendor message. * @param data the ChannelBuffer from which to parse the vendor data * @param length the length to the end of the enclosing message. * @return an OFVendorData instance */ @Override public OFVendorData parseVendorData(int vendor, ChannelBuffer data, int length) { OFVendorDataType vendorDataType = null; OFVendorId vendorId = OFVendorId.lookupVendorId(vendor); if (vendorId != null) { data.markReaderIndex(); vendorDataType = vendorId.parseVendorDataType(data, length); data.resetReaderIndex(); } OFVendorData vendorData = getVendorData(vendorId, vendorDataType); if (vendorData == null) vendorData = new OFByteArrayVendorData(); vendorData.readFrom(data, length); return vendorData; } public static String dumpBuffer(ChannelBuffer data) { // NOTE: Reads all the bytes in buffer from current read offset. // Set/Reset ReaderIndex if you want to read from a different location int len = data.readableBytes(); StringBuffer sb = new StringBuffer(); for (int i=0 ; i<len; i++) { if (i%32 == 0) sb.append("\n"); if (i%4 == 0) sb.append(" "); sb.append(String.format("%02x", data.getUnsignedByte(i))); } return sb.toString(); } }