Implement Extended Optional Parameters Length 54/80854/8
authorRobert Varga <robert.varga@pantheon.tech>
Thu, 14 Mar 2019 11:35:40 +0000 (12:35 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Tue, 30 Apr 2019 11:21:24 +0000 (13:21 +0200)
This corrects handling of optional parameters to comply with RFC4271
and adds the ability to use draft-ietf-idr-ext-opt-param-05.

As per the draft, outbound encoding is used only when necessary and
on inbound it is correctly detected when sent by peer.

JIRA: BGPCEP-868
Change-Id: If822d9b9ce4bf982da5f698aac5248b28b0aaa79
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
bgp/parser-impl/src/main/java/org/opendaylight/protocol/bgp/parser/impl/message/BGPOpenMessageParser.java
bgp/parser-impl/src/main/java/org/opendaylight/protocol/bgp/parser/impl/message/open/CapabilityParameterParser.java
bgp/parser-impl/src/test/java/org/opendaylight/protocol/bgp/parser/impl/OpenTest.java
bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterLengthOverflowException.java [new file with mode: 0644]
bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterRegistry.java
bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterSerializer.java
bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterUtil.java
bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/pojo/SimpleParameterRegistry.java
bgp/parser-spi/src/test/java/org/opendaylight/protocol/bgp/parser/spi/UtilsTest.java
bgp/parser-spi/src/test/java/org/opendaylight/protocol/bgp/parser/spi/pojo/BgpTestActivator.java
bgp/parser-spi/src/test/java/org/opendaylight/protocol/bgp/parser/spi/pojo/SimpleRegistryTest.java

index ef29013f5b55c2ba70f5495c098f4b791b9da522..b97dbf0aea16a2fd422454e573fc721617f09763 100644 (file)
@@ -8,20 +8,29 @@
 package org.opendaylight.protocol.bgp.parser.impl.message;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
 import static java.util.Objects.requireNonNull;
 
+import com.google.common.collect.ImmutableList;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufUtil;
 import io.netty.buffer.Unpooled;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
+import java.util.OptionalInt;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPError;
 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
 import org.opendaylight.protocol.bgp.parser.spi.MessageParser;
 import org.opendaylight.protocol.bgp.parser.spi.MessageSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.MessageUtil;
+import org.opendaylight.protocol.bgp.parser.spi.ParameterLengthOverflowException;
+import org.opendaylight.protocol.bgp.parser.spi.ParameterParser;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterRegistry;
+import org.opendaylight.protocol.bgp.parser.spi.ParameterSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.PeerSpecificParserConstraint;
 import org.opendaylight.protocol.util.Ipv4Util;
 import org.opendaylight.protocol.util.Values;
@@ -49,6 +58,9 @@ public final class BGPOpenMessageParser implements MessageParser, MessageSeriali
     private static final int BGP_ID_SIZE = 4;
     private static final int OPT_PARAM_LENGTH_SIZE = 1;
 
+    // Optional Parameter Type for Extended Optional Parameters Length
+    private static final int OPT_PARAM_EXT_PARAM = 255;
+
     private static final int MIN_MSG_LENGTH = VERSION_SIZE + AS_SIZE
             + HOLD_TIME_SIZE + BGP_ID_SIZE + OPT_PARAM_LENGTH_SIZE;
 
@@ -85,26 +97,72 @@ public final class BGPOpenMessageParser implements MessageParser, MessageSeriali
         msgBody.writeShort(open.getHoldTimer());
         msgBody.writeBytes(Ipv4Util.bytesForAddress(open.getBgpIdentifier()));
 
-        final ByteBuf paramsBuffer = Unpooled.buffer();
-        final List<BgpParameters> params = open.getBgpParameters();
-        if (params != null) {
-            for (final BgpParameters param : params) {
-                this.reg.serializeParameter(param, paramsBuffer);
+        serializeParameters(open.getBgpParameters(), msgBody);
+
+        MessageUtil.formatMessage(TYPE, msgBody, bytes);
+    }
+
+    private void serializeParameters(final List<BgpParameters> params, final ByteBuf msgBody) {
+        if (params == null || params.isEmpty()) {
+            msgBody.writeByte(0);
+            return;
+        }
+
+        final ByteBuf normal = normalSerializeParameters(params);
+        if (normal != null) {
+            final int length = normal.writerIndex();
+            verify(length <= Values.UNSIGNED_BYTE_MAX_VALUE);
+            msgBody.writeByte(length);
+            msgBody.writeBytes(normal);
+            return;
+        }
+
+        final ByteBuf buffer = Unpooled.buffer();
+        for (final BgpParameters param : params) {
+            final Optional<ParameterSerializer> optSer = reg.findSerializer(param);
+            if (optSer.isPresent()) {
+                optSer.get().serializeExtendedParameter(param, buffer);
+            } else {
+                LOG.debug("Ignoring unregistered parameter {}", param);
             }
         }
-        final int paramsLength = paramsBuffer.writerIndex();
-        if (paramsLength > 255) {
-            LOG.error("OPEN message message optional parameter length {} exceeds maximum length supported by BGP. "
-                + "Adjust advertized capabilities toward the peer to fit into limit of 255 bytes by trimming {}",
-                paramsLength, params);
-            throw new IllegalArgumentException(String.format(
-                "Cannot encode OPEN message because optional parameter length %s exceeds length field size.",
-                paramsLength));
+
+        final int length = buffer.writerIndex();
+        checkState(length <= Values.UNSIGNED_SHORT_MAX_VALUE);
+
+        // The non-extended Optional Parameters Length field MUST be set to 255
+        msgBody.writeByte(Values.UNSIGNED_BYTE_MAX_VALUE);
+        // The subsequent one-octet field, that in the non-extended format would
+        // be the first Optional Parameter Type field, MUST be set to 255
+        msgBody.writeByte(OPT_PARAM_EXT_PARAM);
+        // Extended Optional Parameters Length
+        msgBody.writeShort(length);
+        msgBody.writeBytes(buffer);
+    }
+
+    private ByteBuf normalSerializeParameters(final List<BgpParameters> params) {
+        final ByteBuf buffer = Unpooled.buffer();
+        for (final BgpParameters param : params) {
+            final Optional<ParameterSerializer> optSer = reg.findSerializer(param);
+            if (optSer.isPresent()) {
+                try {
+                    optSer.get().serializeParameter(param, buffer);
+                } catch (ParameterLengthOverflowException e) {
+                    LOG.debug("Forcing extended parameter serialization", e);
+                    return null;
+                }
+            } else {
+                LOG.debug("Ingnoring unregistered parameter {}", param);
+            }
         }
-        msgBody.writeByte(paramsLength);
-        msgBody.writeBytes(paramsBuffer);
 
-        MessageUtil.formatMessage(TYPE, msgBody, bytes);
+        final int length = buffer.writerIndex();
+        if (length > Values.UNSIGNED_BYTE_MAX_VALUE) {
+            LOG.debug("Final parameter size is {}, forcing extended serialization", length);
+            return null;
+        }
+
+        return buffer;
     }
 
     /**
@@ -140,43 +198,89 @@ public final class BGPOpenMessageParser implements MessageParser, MessageSeriali
             throw new BGPDocumentedException("BGP Identifier is not a valid IPv4 Address", BGPError.BAD_BGP_ID, e);
         }
         final int optLength = body.readUnsignedByte();
-
-        final List<BgpParameters> optParams = new ArrayList<>();
-        if (optLength > 0) {
-            fillParams(body.slice(), optParams);
-        }
+        final List<BgpParameters> optParams = parseParameters(body.slice(), optLength);
         LOG.debug("BGP Open message was parsed: AS = {}, holdTimer = {}, bgpId = {}, optParams = {}", as,
                 holdTime, bgpId, optParams);
         return new OpenBuilder().setMyAsNumber(as.getValue().intValue()).setHoldTimer(holdTime)
                 .setBgpIdentifier(bgpId).setBgpParameters(optParams).build();
     }
 
-    private void fillParams(final ByteBuf buffer, final List<BgpParameters> params) throws BGPDocumentedException {
-        checkArgument(buffer != null && buffer.isReadable(), "Buffer cannot be null or empty.");
+    private List<BgpParameters> parseParameters(final ByteBuf buffer, final int length) throws BGPDocumentedException {
+        if (length == 0) {
+            return ImmutableList.of();
+        }
         if (LOG.isTraceEnabled()) {
-            LOG.trace("Started parsing of BGP parameter: {}", ByteBufUtil.hexDump(buffer));
+            LOG.trace("Started parsing of BGP parameter: {} length {}", ByteBufUtil.hexDump(buffer), length);
+        }
+
+        final int realLength;
+        final OptionalInt extendedLength = extractExtendedLength(buffer, length);
+        if (extendedLength.isPresent()) {
+            realLength = extendedLength.getAsInt();
+            if (realLength < Values.UNSIGNED_BYTE_MAX_VALUE) {
+                LOG.debug("Peer used Extended Optional Parameters Length to encode length {}", realLength);
+            }
+        } else {
+            realLength = length;
+        }
+
+        // We have determined the real length, we can trim the buffer now
+        if (buffer.readableBytes() > realLength) {
+            buffer.writerIndex(buffer.readerIndex() + realLength);
+            LOG.trace("Truncated BGP parameter buffer to length {}: {}", realLength, ByteBufUtil.hexDump(buffer));
         }
+
+        final int lengthSize = extendedLength.isPresent() ? 1 : 2;
+        final List<BgpParameters> params = new ArrayList<>();
         while (buffer.isReadable()) {
-            if (buffer.readableBytes() <= 2) {
+            final int paramType = buffer.readUnsignedByte();
+            final Optional<ParameterParser> parser = reg.findParser(paramType);
+            if (!parser.isPresent()) {
+                throw new BGPDocumentedException("Parameter " + paramType + " not supported",
+                    BGPError.OPT_PARAM_NOT_SUPPORTED);
+            }
+            if (buffer.readableBytes() <= lengthSize) {
                 throw new BGPDocumentedException("Malformed parameter encountered (" + buffer.readableBytes()
-                        + " bytes left)", BGPError.OPT_PARAM_NOT_SUPPORTED);
+                        + " bytes left)", BGPError.UNSPECIFIC_OPEN_ERROR);
             }
-            final int paramType = buffer.readUnsignedByte();
-            final int paramLength = buffer.readUnsignedByte();
+            final int paramLength = extendedLength.isPresent() ? buffer.readUnsignedShort() : buffer.readUnsignedByte();
             final ByteBuf paramBody = buffer.readSlice(paramLength);
 
             final BgpParameters param;
             try {
-                param = this.reg.parseParameter(paramType, paramBody);
+                param = parser.get().parseParameter(paramBody);
             } catch (final BGPParsingException e) {
                 throw new BGPDocumentedException("Optional parameter not parsed", BGPError.UNSPECIFIC_OPEN_ERROR, e);
             }
-            if (param != null) {
-                params.add(param);
-            } else {
-                LOG.debug("Ignoring BGP Parameter type: {}", paramType);
-            }
+
+            params.add(verifyNotNull(param));
         }
+
         LOG.trace("Parsed BGP parameters: {}", params);
+        return params;
+    }
+
+    private static OptionalInt extractExtendedLength(final ByteBuf buffer, final int length)
+            throws BGPDocumentedException {
+        final int type = buffer.markReaderIndex().readUnsignedByte();
+        if (type != OPT_PARAM_EXT_PARAM) {
+            // Not extended length
+            buffer.resetReaderIndex();
+            return OptionalInt.empty();
+        }
+        if (length != Values.UNSIGNED_BYTE_MAX_VALUE) {
+            LOG.debug("Peer uses Extended Optional Parameters Length, but indicated RFC4271 length as {}", length);
+        }
+        if (length < 3) {
+            throw new BGPDocumentedException("Malformed Extended Length parameter encountered ("
+                    + (length - 1) + " bytes left)", BGPError.UNSPECIFIC_OPEN_ERROR);
+        }
+        final int avail = buffer.readableBytes();
+        if (avail < 2) {
+            throw new BGPDocumentedException("Buffer underrun: require 2 bytes, only " + avail + " bytes left",
+                BGPError.UNSPECIFIC_OPEN_ERROR);
+        }
+
+        return OptionalInt.of(buffer.readUnsignedShort());
     }
 }
index f5f547909de5365a15c3331041a427a399553ad6..d6db82097e350f7a9cb728934b4164b49a48d227 100644 (file)
@@ -7,9 +7,9 @@
  */
 package org.opendaylight.protocol.bgp.parser.impl.message.open;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
-import com.google.common.base.Preconditions;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.ByteBufUtil;
 import io.netty.buffer.Unpooled;
@@ -19,6 +19,7 @@ import java.util.List;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
 import org.opendaylight.protocol.bgp.parser.spi.CapabilityRegistry;
+import org.opendaylight.protocol.bgp.parser.spi.ParameterLengthOverflowException;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterParser;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterUtil;
@@ -46,8 +47,7 @@ public final class CapabilityParameterParser implements ParameterParser, Paramet
 
     @Override
     public BgpParameters parseParameter(final ByteBuf buffer) throws BGPParsingException, BGPDocumentedException {
-        Preconditions.checkArgument(buffer != null && buffer.readableBytes() != 0,
-                "Byte array cannot be null or empty.");
+        checkArgument(buffer != null && buffer.readableBytes() != 0, "Byte array cannot be null or empty.");
 
         if (LOG.isTraceEnabled()) {
             LOG.trace("Started parsing of BGP Capabilities: {}", Arrays.toString(ByteArray.getAllBytes(buffer)));
@@ -77,15 +77,33 @@ public final class CapabilityParameterParser implements ParameterParser, Paramet
     }
 
     @Override
-    public void serializeParameter(final BgpParameters parameter, final ByteBuf byteAggregator) {
-        if (parameter.getOptionalCapabilities() != null) {
-            final ByteBuf buffer = Unpooled.buffer();
-            for (final OptionalCapabilities optionalCapa : parameter.getOptionalCapabilities()) {
-                LOG.trace("Started serializing BGP Capability: {}", optionalCapa);
-                serializeOptionalCapability(optionalCapa, buffer);
-            }
-            ParameterUtil.formatParameter(TYPE, buffer, byteAggregator);
+    public void serializeParameter(final BgpParameters parameter, final ByteBuf output)
+            throws ParameterLengthOverflowException {
+        final ByteBuf buffer = serializeOptionalCapabilities(parameter.getOptionalCapabilities());
+        if (buffer != null) {
+            ParameterUtil.formatParameter(TYPE, buffer, output);
+        }
+    }
+
+    @Override
+    public void serializeExtendedParameter(final BgpParameters parameter, final ByteBuf output) {
+        final ByteBuf buffer = serializeOptionalCapabilities(parameter.getOptionalCapabilities());
+        if (buffer != null) {
+            ParameterUtil.formatExtendedParameter(TYPE, buffer, output);
+        }
+    }
+
+    private ByteBuf serializeOptionalCapabilities(final List<OptionalCapabilities> capabilities) {
+        if (capabilities == null) {
+            return null;
+        }
+
+        final ByteBuf buffer = Unpooled.buffer();
+        for (final OptionalCapabilities optionalCapa : capabilities) {
+            LOG.trace("Started serializing BGP Capability: {}", optionalCapa);
+            serializeOptionalCapability(optionalCapa, buffer);
         }
+        return buffer;
     }
 
     private void serializeOptionalCapability(final OptionalCapabilities optionalCapa, final ByteBuf byteAggregator) {
@@ -93,7 +111,7 @@ public final class CapabilityParameterParser implements ParameterParser, Paramet
             final CParameters cap = optionalCapa.getCParameters();
             final ByteBuf bytes = Unpooled.buffer();
             this.reg.serializeCapability(cap, bytes);
-            Preconditions.checkArgument(bytes != null, "Unhandled capability class %s", cap.implementedInterface());
+            checkArgument(bytes != null, "Unhandled capability class %s", cap.implementedInterface());
 
             if (LOG.isTraceEnabled()) {
                 LOG.trace("BGP capability serialized to: {}", ByteBufUtil.hexDump(bytes));
index 29f0fe8a0ff74218167d64cf7c8a60475518614b..d633bd7f344a99de8c2cf373d2d735ad1e326540 100644 (file)
@@ -9,12 +9,13 @@ package org.opendaylight.protocol.bgp.parser.impl;
 
 import static org.junit.Assert.assertEquals;
 
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.List;
 import org.junit.Test;
+import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.impl.message.BGPOpenMessageParser;
 import org.opendaylight.protocol.bgp.parser.spi.pojo.ServiceLoaderBGPExtensionProviderContext;
 import org.opendaylight.protocol.util.ByteArray;
@@ -23,7 +24,6 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.Open;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.OpenBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.ProtocolVersion;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParameters;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParametersBuilder;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.OptionalCapabilities;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.bgp.parameters.OptionalCapabilitiesBuilder;
@@ -37,23 +37,23 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.mult
 public class OpenTest {
 
     @Test
-    public void testSerializeOpen() throws Exception {
-        final List<OptionalCapabilities> optionalCapas = Lists.newArrayList();
-        final OptionalCapabilities optionalCapabilitiesBuilder = new OptionalCapabilitiesBuilder()
-                .setCParameters(new CParametersBuilder()
-                    .setAs4BytesCapability(new As4BytesCapabilityBuilder().setAsNumber(new AsNumber(1000L)).build())
-                    .addAugmentation(CParameters1.class, new CParameters1Builder()
-                        .setGracefulRestartCapability(new GracefulRestartCapabilityBuilder()
-                            .setRestartFlags(new GracefulRestartCapability.RestartFlags(false)).setRestartTime(0)
-                            .setTables(Collections.emptyList()).build())
-                        .build())
-                    .build())
-                .build();
-        optionalCapas.add(optionalCapabilitiesBuilder);
-        final List<BgpParameters> tlvs = Lists.newArrayList(new BgpParametersBuilder()
-            .setOptionalCapabilities(optionalCapas).build());
+    public void testSerializeOpen() throws BGPDocumentedException {
         final Open open = new OpenBuilder().setBgpIdentifier(new Ipv4Address("127.0.0.1")).setMyAsNumber(30)
-                .setHoldTimer(3).setVersion(new ProtocolVersion((short) 4)).setBgpParameters(tlvs).build();
+                .setHoldTimer(3).setVersion(new ProtocolVersion((short) 4)).setBgpParameters(ImmutableList.of(
+                    new BgpParametersBuilder()
+                        .setOptionalCapabilities(ImmutableList.of(new OptionalCapabilitiesBuilder()
+                            .setCParameters(new CParametersBuilder()
+                                .setAs4BytesCapability(new As4BytesCapabilityBuilder()
+                                    .setAsNumber(new AsNumber(1000L)).build())
+                                .addAugmentation(CParameters1.class, new CParameters1Builder()
+                                    .setGracefulRestartCapability(new GracefulRestartCapabilityBuilder()
+                                        .setRestartFlags(new GracefulRestartCapability.RestartFlags(false))
+                                        .setRestartTime(0)
+                                        .setTables(ImmutableList.of()).build())
+                                    .build())
+                                .build())
+                            .build())).build()))
+                .build();
         final ByteBuf msg = Unpooled.buffer();
         new BGPOpenMessageParser(ServiceLoaderBGPExtensionProviderContext.getSingletonInstance().getParameterRegistry())
             .serializeMessage(open, msg);
@@ -79,4 +79,34 @@ public class OpenTest {
                 .augmentation(CParameters1.class).getGracefulRestartCapability());
 
     }
+
+    @Test
+    public void testSerializeLongOpen() throws BGPDocumentedException {
+        final List<OptionalCapabilities> capabilities = new ArrayList<>();
+        for (int i = 0; i < 200; ++i) {
+            capabilities.add(new OptionalCapabilitiesBuilder()
+                .setCParameters(new CParametersBuilder()
+                    .setAs4BytesCapability(new As4BytesCapabilityBuilder()
+                        .setAsNumber(new AsNumber(1000L)).build())
+                    .build())
+                .build());
+        }
+
+        final Open open = new OpenBuilder().setBgpIdentifier(new Ipv4Address("127.0.0.1")).setMyAsNumber(30)
+                .setHoldTimer(3).setVersion(new ProtocolVersion((short) 4)).setBgpParameters(ImmutableList.of(
+                    new BgpParametersBuilder().setOptionalCapabilities(capabilities).build()))
+                .build();
+
+        final ByteBuf msg = Unpooled.buffer();
+        new BGPOpenMessageParser(ServiceLoaderBGPExtensionProviderContext.getSingletonInstance().getParameterRegistry())
+            .serializeMessage(open, msg);
+
+        assertEquals(1235, msg.readableBytes());
+
+        final byte[] temp = ByteArray.cutBytes(ByteArray.getAllBytes(msg), 19);
+        final Open openResult = new BGPOpenMessageParser(ServiceLoaderBGPExtensionProviderContext.getSingletonInstance()
+            .getParameterRegistry()).parseMessageBody(Unpooled.copiedBuffer(temp), temp.length, null);
+
+        assertEquals(200, openResult.getBgpParameters().get(0).getOptionalCapabilities().size());
+    }
 }
diff --git a/bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterLengthOverflowException.java b/bgp/parser-spi/src/main/java/org/opendaylight/protocol/bgp/parser/spi/ParameterLengthOverflowException.java
new file mode 100644 (file)
index 0000000..8a20e21
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.protocol.bgp.parser.spi;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Exception reported when a {@link ParameterSerializer} detects its output does not fit 255 bytes and hence cannot
+ * be held in plain RFC4271 OPEN message.
+ */
+@Beta
+public final class ParameterLengthOverflowException extends Exception {
+    private static final long serialVersionUID = 1L;
+
+    public ParameterLengthOverflowException(final String message) {
+        super(requireNonNull(message));
+    }
+
+    public static void throwIf(final boolean expression, final String format, final Object... args)
+            throws ParameterLengthOverflowException {
+        if (expression) {
+            throw new ParameterLengthOverflowException(String.format(format, args));
+        }
+    }
+}
index 4a64d2f8467b91e86903cccb0ae278235f35d333..c02ae75730f024bed844d324f0370616fd12a357 100644 (file)
@@ -7,13 +7,24 @@
  */
 package org.opendaylight.protocol.bgp.parser.spi;
 
-import io.netty.buffer.ByteBuf;
-import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
-import org.opendaylight.protocol.bgp.parser.BGPParsingException;
+import java.util.Optional;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParameters;
 
 public interface ParameterRegistry {
-    BgpParameters parseParameter(int parameterType, ByteBuf bytes) throws BGPParsingException, BGPDocumentedException;
+    /**
+     * Find a parser for specified parameter type.
+     *
+     * @param parameterType Parameter type
+     * @return Parser, if registered
+     */
+    Optional<ParameterParser> findParser(int parameterType);
 
-    void serializeParameter(BgpParameters parameter, ByteBuf bytes);
+    /**
+     * Find a serializer for specified parameter.
+     *
+     * @param parameter Parameter to serialize
+     * @return Serializer, if registered
+     * @throws NullPointerException if any argument is null
+     */
+    Optional<ParameterSerializer> findSerializer(BgpParameters parameter);
 }
index 455b20d9acd003b7f714464fc4cd27340fb435d1..93ac971424ec44847a70d5b36b711f4ef5c92ad7 100644 (file)
@@ -11,5 +11,24 @@ import io.netty.buffer.ByteBuf;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.open.message.BgpParameters;
 
 public interface ParameterSerializer {
-    void serializeParameter(BgpParameters parameter, ByteBuf byteAggregator);
+    /**
+     * Serialize parameter using RFC4271 encoding.
+     *
+     * @param parameter Parameter to serialize
+     * @param output Output buffer
+     * @throws NullPointerException if any argument is null
+     * @throws ParameterLengthOverflowException when the parameter does not fit into 255 bytes
+     */
+    void serializeParameter(BgpParameters parameter, ByteBuf output) throws ParameterLengthOverflowException;
+
+    /**
+     * Serialize parameter using
+     * <a href="https://tools.ietf.org/html/draft-ietf-idr-ext-opt-param-05">Extended Optional Parameters Length</a>
+     * encoding.
+     *
+     * @param parameter Parameter to serialize
+     * @param output Output buffer
+     * @throws NullPointerException if any argument is null
+     */
+    void serializeExtendedParameter(BgpParameters parameter, ByteBuf output);
 }
index 82fee6d4db5790154bf4d1113765f83ffb59e217..ebcb9f1dcbc1474025f88c8d984fae97d905b91b 100644 (file)
@@ -7,8 +7,10 @@
  */
 package org.opendaylight.protocol.bgp.parser.spi;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
+import org.opendaylight.protocol.util.Values;
 
 /**
  * Utility class which is intended for formatting parameter.
@@ -18,23 +20,38 @@ public final class ParameterUtil {
     }
 
     /**
-     * Adds header to parameter value.
+     * Adds header to parameter value in RFC4271 format.
      *
      * @param type of the parameter
      * @param value parameter value
      * @param buffer ByteBuf where the parameter will be copied with its header
      * @throws IllegalArgumentException if value length exceeds 255 bytes
      */
-    public static void formatParameter(final int type, final ByteBuf value, final ByteBuf buffer) {
+    public static void formatParameter(final int type, final ByteBuf value, final ByteBuf buffer)
+            throws ParameterLengthOverflowException {
         final int valueLength = value.writerIndex();
-        if (valueLength > 255) {
-            throw new IllegalArgumentException(String.format(
-                "Cannot encode parameter %s because value length %s exceeds parameter length field size (value %s)",
-                type, valueLength, ByteBufUtil.hexDump(value)));
-        }
+        ParameterLengthOverflowException.throwIf(valueLength > Values.UNSIGNED_BYTE_MAX_VALUE,
+            "Cannot encode %s-byte value", valueLength);
 
         buffer.writeByte(type);
         buffer.writeByte(valueLength);
         buffer.writeBytes(value);
     }
+
+    /**
+     * Adds header to parameter value in draft-ietf-idr-ext-opt-param-05 format.
+     *
+     * @param type of the parameter
+     * @param value parameter value
+     * @param buffer ByteBuf where the parameter will be copied with its header
+     * @throws IllegalArgumentException if value length exceeds 65535 bytes
+     */
+    public static void formatExtendedParameter(final int type, final ByteBuf value, final ByteBuf buffer) {
+        final int valueLength = value.writerIndex();
+        checkArgument(valueLength < Values.UNSIGNED_SHORT_MAX_VALUE, "Cannot encode %s-byte value", valueLength);
+
+        buffer.writeByte(type);
+        buffer.writeShort(valueLength);
+        buffer.writeBytes(value);
+    }
 }
index 527d04ca2ab9540ff82e93035c04ac4d202f86b9..67ca8f78c5b4e31e9a6d53e994a90c563946c5d8 100644 (file)
@@ -9,9 +9,7 @@ package org.opendaylight.protocol.bgp.parser.spi.pojo;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import io.netty.buffer.ByteBuf;
-import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
-import org.opendaylight.protocol.bgp.parser.BGPParsingException;
+import java.util.Optional;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterParser;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterRegistry;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterSerializer;
@@ -37,21 +35,12 @@ final class SimpleParameterRegistry implements ParameterRegistry {
     }
 
     @Override
-    public BgpParameters parseParameter(final int parameterType, final ByteBuf buffer) throws BGPParsingException,
-            BGPDocumentedException {
-        final ParameterParser parser = this.handlers.getParser(parameterType);
-        if (parser == null) {
-            return null;
-        }
-        return parser.parseParameter(buffer);
+    public Optional<ParameterParser> findParser(final int parameterType) {
+        return Optional.ofNullable(handlers.getParser(parameterType));
     }
 
     @Override
-    public void serializeParameter(final BgpParameters parameter, final ByteBuf bytes) {
-        final ParameterSerializer serializer = this.handlers.getSerializer(parameter.implementedInterface());
-        if (serializer == null) {
-            return;
-        }
-        serializer.serializeParameter(parameter,bytes);
+    public Optional<ParameterSerializer> findSerializer(final BgpParameters parameter) {
+        return Optional.ofNullable(handlers.getSerializer(parameter.implementedInterface()));
     }
 }
index acf1d7ac2fdd7ca1f1ad2ba0081d60d35932851e..4f09857958960e6c449c7676d8c33a7f8306e6ab 100644 (file)
@@ -9,24 +9,26 @@ package org.opendaylight.protocol.bgp.parser.spi;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.doReturn;
 
 import com.google.common.primitives.UnsignedBytes;
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
 import java.util.Arrays;
 import java.util.Optional;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnitRunner;
 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
 import org.opendaylight.protocol.util.ByteArray;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev180329.BgpTableType;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.Ipv4AddressFamily;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev180329.UnicastSubsequentAddressFamily;
 
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
 public class UtilsTest {
 
     @Mock private AddressFamilyRegistry afiReg;
@@ -34,14 +36,13 @@ public class UtilsTest {
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        Mockito.doReturn(1).when(this.afiReg).numberForClass(Ipv4AddressFamily.class);
-        Mockito.doReturn(Ipv4AddressFamily.class).when(this.afiReg).classForFamily(1);
-        Mockito.doReturn(null).when(this.afiReg).classForFamily(2);
-
-        Mockito.doReturn(1).when(this.safiReg).numberForClass(UnicastSubsequentAddressFamily.class);
-        Mockito.doReturn(UnicastSubsequentAddressFamily.class).when(this.safiReg).classForFamily(1);
-        Mockito.doReturn(null).when(this.safiReg).classForFamily(3);
+        doReturn(1).when(this.afiReg).numberForClass(Ipv4AddressFamily.class);
+        doReturn(Ipv4AddressFamily.class).when(this.afiReg).classForFamily(1);
+        doReturn(null).when(this.afiReg).classForFamily(2);
+
+        doReturn(1).when(this.safiReg).numberForClass(UnicastSubsequentAddressFamily.class);
+        doReturn(UnicastSubsequentAddressFamily.class).when(this.safiReg).classForFamily(1);
+        doReturn(null).when(this.safiReg).classForFamily(3);
     }
 
     @Test
@@ -65,7 +66,7 @@ public class UtilsTest {
     }
 
     @Test
-    public void testParameterUtil() {
+    public void testParameterUtil() throws ParameterLengthOverflowException {
         final byte[] result = new byte[] { 1, 2, 4, 8 };
         final ByteBuf aggregator = Unpooled.buffer();
         ParameterUtil.formatParameter(1, Unpooled.wrappedBuffer(new byte[] { 4, 8 }), aggregator);
@@ -114,7 +115,7 @@ public class UtilsTest {
         final ByteBuf bytesBuf = Unpooled.copiedBuffer(bytes);
         final Optional<BgpTableType> parsedAfiSafi = MultiprotocolCapabilitiesUtil.parseMPAfiSafi(bytesBuf, this.afiReg,
             this.safiReg);
-        Assert.assertFalse(parsedAfiSafi.isPresent());
+        assertFalse(parsedAfiSafi.isPresent());
     }
 
     @Test
@@ -123,6 +124,31 @@ public class UtilsTest {
         final ByteBuf bytesBuf = Unpooled.copiedBuffer(bytes);
         final Optional<BgpTableType> parsedAfiSafi = MultiprotocolCapabilitiesUtil.parseMPAfiSafi(bytesBuf, this.afiReg,
             this.safiReg);
-        Assert.assertFalse(parsedAfiSafi.isPresent());
+        assertFalse(parsedAfiSafi.isPresent());
+    }
+
+    @Test(expected = ParameterLengthOverflowException.class)
+    public void testFormatParameterOverflow() throws ParameterLengthOverflowException {
+        ParameterUtil.formatParameter(2, Unpooled.buffer().writeZero(256), Unpooled.buffer());
+    }
+
+    @Test
+    public void testFormatParameter() throws ParameterLengthOverflowException {
+        final ByteBuf output = Unpooled.buffer();
+        ParameterUtil.formatParameter(2, Unpooled.buffer().writeZero(255), output);
+
+        assertEquals(257, output.readableBytes());
+        assertEquals(2, output.readUnsignedByte());
+        assertEquals(255, output.readUnsignedByte());
+    }
+
+    @Test
+    public void testFormatExtendedParameter() {
+        final ByteBuf output = Unpooled.buffer();
+        ParameterUtil.formatExtendedParameter(2, Unpooled.buffer().writeZero(256), output);
+
+        assertEquals(259, output.readableBytes());
+        assertEquals(2, output.readUnsignedByte());
+        assertEquals(256, output.readUnsignedShort());
     }
 }
index 2e13a617cd231558c12a9a34f1ac0af35d0a73ba..f2466682efb244e49ee926f1d258aee946995a5c 100644 (file)
@@ -5,19 +5,18 @@
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  */
-
 package org.opendaylight.protocol.bgp.parser.spi.pojo;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 
 import io.netty.buffer.ByteBuf;
 import java.util.ArrayList;
 import java.util.List;
-import org.junit.Assert;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
@@ -35,6 +34,7 @@ import org.opendaylight.protocol.bgp.parser.spi.MessageSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.NextHopParserSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.NlriParser;
 import org.opendaylight.protocol.bgp.parser.spi.NlriSerializer;
+import org.opendaylight.protocol.bgp.parser.spi.ParameterLengthOverflowException;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterParser;
 import org.opendaylight.protocol.bgp.parser.spi.ParameterSerializer;
 import org.opendaylight.protocol.bgp.parser.spi.PeerSpecificParserConstraint;
@@ -158,46 +158,42 @@ public class BgpTestActivator extends AbstractBGPExtensionProviderActivator {
     private void initMock() {
         MockitoAnnotations.initMocks(this);
         try {
-            Mockito.doNothing().when(this.attrParser).parseAttribute(any(ByteBuf.class), any(AttributesBuilder.class),
+            doNothing().when(this.attrParser).parseAttribute(any(ByteBuf.class), any(AttributesBuilder.class),
                 any(RevisedErrorHandling.class), any(PeerSpecificParserConstraint.class));
             doReturn(EMPTY).when(this.attrParser).toString();
-            Mockito.doNothing().when(this.attrSerializer).serializeAttribute(any(Attributes.class), any(ByteBuf.class));
+            doNothing().when(this.attrSerializer).serializeAttribute(any(Attributes.class), any(ByteBuf.class));
             doReturn(EMPTY).when(this.attrSerializer).toString();
 
             doReturn(null).when(this.paramParser).parseParameter(any(ByteBuf.class));
             doReturn(EMPTY).when(this.paramParser).toString();
-            Mockito.doNothing().when(this.paramSerializer).serializeParameter(any(BgpParameters.class),
-                any(ByteBuf.class));
+            doNothing().when(this.paramSerializer).serializeParameter(any(BgpParameters.class), any(ByteBuf.class));
             doReturn(EMPTY).when(this.paramSerializer).toString();
 
             doReturn(null).when(this.capaParser).parseCapability(any(ByteBuf.class));
             doReturn(EMPTY).when(this.capaParser).toString();
-            Mockito.doNothing().when(this.capaSerializer).serializeCapability(any(CParameters.class),
-                any(ByteBuf.class));
+            doNothing().when(this.capaSerializer).serializeCapability(any(CParameters.class), any(ByteBuf.class));
             doReturn(EMPTY).when(this.capaSerializer).toString();
 
             doReturn(null).when(this.sidTlvParser).parseBgpPrefixSidTlv(any(ByteBuf.class));
             doReturn(EMPTY).when(this.sidTlvParser).toString();
-            Mockito.doNothing().when(this.sidTlvSerializer).serializeBgpPrefixSidTlv(any(BgpPrefixSidTlv.class),
+            doNothing().when(this.sidTlvSerializer).serializeBgpPrefixSidTlv(any(BgpPrefixSidTlv.class),
                 any(ByteBuf.class));
             doReturn(EMPTY).when(this.sidTlvSerializer).toString();
             doReturn(0).when(this.sidTlvSerializer).getType();
 
-            doReturn(mock(Notification.class)).when(this.msgParser).parseMessageBody(any(ByteBuf.class),
-                Mockito.anyInt(), any(PeerSpecificParserConstraint.class));
+            doReturn(mock(Notification.class)).when(this.msgParser).parseMessageBody(any(ByteBuf.class), anyInt(),
+                any(PeerSpecificParserConstraint.class));
             doReturn(EMPTY).when(this.msgParser).toString();
-            Mockito.doNothing().when(this.msgSerializer).serializeMessage(any(Notification.class),
-                any(ByteBuf.class));
+            doNothing().when(this.msgSerializer).serializeMessage(any(Notification.class), any(ByteBuf.class));
             doReturn(EMPTY).when(this.msgSerializer).toString();
 
-            Mockito.doNothing().when(this.nlriParser).parseNlri(any(ByteBuf.class), any(MpUnreachNlriBuilder.class),
-                any());
-            Mockito.doNothing().when(this.nlriParser).parseNlri(any(ByteBuf.class), any(MpReachNlriBuilder.class),
-                any());
+            doNothing().when(this.nlriParser).parseNlri(any(ByteBuf.class), any(MpUnreachNlriBuilder.class), any());
+            doNothing().when(this.nlriParser).parseNlri(any(ByteBuf.class), any(MpReachNlriBuilder.class), any());
             doReturn(EMPTY).when(this.nlriParser).toString();
 
-        } catch (BGPDocumentedException | BGPParsingException | BGPTreatAsWithdrawException e) {
-            Assert.fail();
+        } catch (BGPDocumentedException | BGPParsingException | BGPTreatAsWithdrawException
+                | ParameterLengthOverflowException e) {
+            throw new IllegalStateException("Mock setup failed", e);
         }
     }
 }
index 9a9515c7d34b697205ec850b752386f6421192df..b7e422c6a0c50c8502e49ac720a8278ef984adf8 100644 (file)
@@ -18,6 +18,7 @@ import static org.mockito.Mockito.verify;
 
 import io.netty.buffer.ByteBuf;
 import io.netty.buffer.Unpooled;
+import java.util.Optional;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -99,15 +100,9 @@ public class SimpleRegistryTest {
         final ParameterRegistry paramReg = this.ctx.getParameterRegistry();
         final BgpParameters param = mock(BgpParameters.class);
         Mockito.doReturn(BgpParameters.class).when(param).implementedInterface();
-        final byte[] paramBytes = {
-            0x00, 0x00
-        };
-        final ByteBuf buffer = Unpooled.buffer(paramBytes.length);
-        paramReg.serializeParameter(param, buffer);
-        paramReg.parseParameter(0, Unpooled.wrappedBuffer(paramBytes));
-        verify(this.activator.paramParser, times(1)).parseParameter(any(ByteBuf.class));
-        verify(this.activator.paramSerializer, times(1)).serializeParameter(any(BgpParameters.class),
-            any(ByteBuf.class));
+
+        assertEquals(Optional.of(activator.paramParser), paramReg.findParser(0));
+        assertEquals(Optional.of(activator.paramSerializer), paramReg.findSerializer(param));
     }
 
     @Test