Fix raw type warnings in bgp/bmp
[bgpcep.git] / bgp / parser-impl / src / main / java / org / opendaylight / protocol / bgp / parser / impl / message / BGPOpenMessageParser.java
index e7396fd5aa3735e37be0bc902a96aec304000b9b..deaa7cea90000f81124313c2975c1afd45871056 100644 (file)
  */
 package org.opendaylight.protocol.bgp.parser.impl.message;
 
-import java.util.Arrays;
+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.Map;
-import java.util.Map.Entry;
-
+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.impl.message.open.BGPParameterParser;
 import org.opendaylight.protocol.bgp.parser.spi.MessageParser;
 import org.opendaylight.protocol.bgp.parser.spi.MessageSerializer;
-import org.opendaylight.protocol.concepts.Ipv4Util;
-import org.opendaylight.protocol.util.ByteArray;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.Open;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.OpenBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130918.open.BgpParameters;
+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;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4AddressNoZone;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.Open;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.OpenBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.open.message.BgpParameters;
 import org.opendaylight.yangtools.yang.binding.Notification;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.common.Uint32;
+import org.opendaylight.yangtools.yang.common.netty.ByteBufUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.primitives.UnsignedBytes;
-
 /**
  * Parser for BGP Open message.
  */
 public final class BGPOpenMessageParser implements MessageParser, MessageSerializer {
-       public static final MessageParser PARSER;
-       public static final MessageSerializer SERIALIZER;
-
-       static {
-               final BGPOpenMessageParser p = new BGPOpenMessageParser();
-               PARSER = p;
-               SERIALIZER = p;
-       }
-
-       private static final Logger logger = LoggerFactory.getLogger(BGPOpenMessageParser.class);
-
-       private static final int VERSION_SIZE = 1;
-       private static final int AS_SIZE = 2;
-       private static final int HOLD_TIME_SIZE = 2;
-       private static final int BGP_ID_SIZE = 4;
-       private static final int OPT_PARAM_LENGTH_SIZE = 1;
-
-       private static final int MIN_MSG_LENGTH = VERSION_SIZE + AS_SIZE + HOLD_TIME_SIZE + BGP_ID_SIZE + OPT_PARAM_LENGTH_SIZE;
-
-       private static final int BGP_VERSION = 4;
-
-       private BGPOpenMessageParser() {
-
-       }
-
-       /**
-        * Serializes given BGP Open message to byte array, without the header.
-        * 
-        * @param msg BGP Open message to be serialized.
-        * @return BGP Open message converted to byte array
-        */
-       @Override
-       public byte[] serializeMessage(final Notification msg) {
-               if (msg == null) {
-                       throw new IllegalArgumentException("BGPOpen message cannot be null");
-               }
-               logger.trace("Started serializing open message: {}", msg);
-               final Open open = (Open) msg;
-
-               final Map<byte[], Integer> optParams = Maps.newHashMap();
-
-               int optParamsLength = 0;
-
-               if (open.getBgpParameters() != null) {
-                       for (final BgpParameters param : open.getBgpParameters()) {
-                               final byte[] p = BGPParameterParser.put(param);
-                               optParams.put(p, p.length);
-                               optParamsLength += p.length;
-                       }
-               }
-
-               final byte[] msgBody = (open.getBgpParameters() == null || open.getBgpParameters().isEmpty()) ? new byte[MIN_MSG_LENGTH]
-                               : new byte[MIN_MSG_LENGTH + optParamsLength];
-
-               int offset = 0;
-
-               msgBody[offset] = ByteArray.intToBytes(BGP_VERSION)[(Integer.SIZE / Byte.SIZE) - 1];
-               offset += VERSION_SIZE;
-
-               // When our AS number does not fit into two bytes, we report it as AS_TRANS
-               int openAS = open.getMyAsNumber();
-               if (openAS > 65535) {
-                       openAS = 2345;
-               }
-
-               System.arraycopy(ByteArray.longToBytes(openAS), 6, msgBody, offset, AS_SIZE);
-               offset += AS_SIZE;
-
-               System.arraycopy(ByteArray.intToBytes(open.getHoldTimer()), 2, msgBody, offset, HOLD_TIME_SIZE);
-               offset += HOLD_TIME_SIZE;
-
-               System.arraycopy(Ipv4Util.bytesForAddress(open.getBgpIdentifier()), 0, msgBody, offset, BGP_ID_SIZE);
-               offset += BGP_ID_SIZE;
-
-               msgBody[offset] = ByteArray.intToBytes(optParamsLength)[Integer.SIZE / Byte.SIZE - 1];
-
-               int index = MIN_MSG_LENGTH;
-               if (optParams != null) {
-                       for (final Entry<byte[], Integer> entry : optParams.entrySet()) {
-                               System.arraycopy(entry.getKey(), 0, msgBody, index, entry.getValue());
-                               index += entry.getValue();
-                       }
-               }
-               logger.trace("Open message serialized to: {}", Arrays.toString(msgBody));
-               return msgBody;
-       }
-
-       /**
-        * Parses given byte array to BGP Open message
-        * 
-        * @param bytes byte array representing BGP Open message, without header
-        * @return BGP Open Message
-        * @throws BGPDocumentedException if the parsing was unsuccessful
-        */
-       @Override
-       public Open parseMessage(final byte[] bytes, final int messageLength) throws BGPDocumentedException {
-               if (bytes == null || bytes.length == 0) {
-                       throw new IllegalArgumentException("Byte array cannot be null or empty.");
-               }
-               logger.trace("Started parsing of open message: {}", Arrays.toString(bytes));
-
-               if (bytes.length < MIN_MSG_LENGTH) {
-                       throw new BGPDocumentedException("Open message too small.", BGPError.BAD_MSG_LENGTH, ByteArray.intToBytes(bytes.length));
-               }
-               if (UnsignedBytes.toInt(bytes[0]) != BGP_VERSION) {
-                       throw new BGPDocumentedException("BGP Protocol version " + UnsignedBytes.toInt(bytes[0]) + " not supported.", BGPError.VERSION_NOT_SUPPORTED, ByteArray.subByte(
-                                       ByteArray.intToBytes(BGP_VERSION), 2, 2));
-               }
-
-               int offset = VERSION_SIZE;
-               final AsNumber as = new AsNumber(ByteArray.bytesToLong(ByteArray.subByte(bytes, offset, AS_SIZE)));
-               offset += AS_SIZE;
-
-               // TODO: BAD_PEER_AS Error: when is an AS unacceptable?
-
-               final short holdTime = ByteArray.bytesToShort(ByteArray.subByte(bytes, offset, HOLD_TIME_SIZE));
-               offset += HOLD_TIME_SIZE;
-               if (holdTime == 1 || holdTime == 2) {
-                       throw new BGPDocumentedException("Hold time value not acceptable.", BGPError.HOLD_TIME_NOT_ACC);
-               }
-
-               Ipv4Address bgpId = null;
-               try {
-                       bgpId = Ipv4Util.addressForBytes(ByteArray.subByte(bytes, offset, BGP_ID_SIZE));
-               } catch (final IllegalArgumentException e) {
-                       throw new BGPDocumentedException("BGP Identifier is not a valid IPv4 Address", BGPError.BAD_BGP_ID);
-               }
-               offset += BGP_ID_SIZE;
-
-               final int optLength = UnsignedBytes.toInt(bytes[offset]);
-
-               List<BgpParameters> optParams = Lists.newArrayList();
-               if (optLength > 0) {
-                       try {
-                               optParams = BGPParameterParser.parse(ByteArray.subByte(bytes, MIN_MSG_LENGTH, optLength));
-                       } catch (final BGPParsingException e) {
-                               throw new BGPDocumentedException("Optional parameter not parsed: ." + e.getMessage(), BGPError.UNSPECIFIC_OPEN_ERROR);
-                       }
-               }
-               logger.trace("Open message was parsed: AS = {}, holdTimer = {}, bgpId = {}, optParams = {}", as, holdTime, bgpId, optParams);
-               return new OpenBuilder().setMyAsNumber(as.getValue().intValue()).setHoldTimer((int) holdTime).setBgpIdentifier(bgpId).setBgpParameters(
-                               optParams).build();
-       }
-
-       @Override
-       public int messageType() {
-               return 1;
-       }
+
+    private static final Logger LOG = LoggerFactory.getLogger(BGPOpenMessageParser.class);
+
+    public static final int TYPE = 1;
+
+    private static final int VERSION_SIZE = 1;
+    private static final int AS_SIZE = 2;
+    private static final int HOLD_TIME_SIZE = 2;
+    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;
+
+    private static final int BGP_VERSION = 4;
+
+    public static final int AS_TRANS = 23456;
+
+    private final ParameterRegistry reg;
+
+    public BGPOpenMessageParser(final ParameterRegistry reg) {
+        this.reg = requireNonNull(reg);
+    }
+
+    /**
+     * Serializes given BGP Open message to byte array, without the header.
+     *
+     * @param msg BGP Open message to be serialized.
+     * @param bytes ByteBuf where the message will be serialized
+     */
+    @Override
+    public void serializeMessage(final Notification<?> msg, final ByteBuf bytes) {
+        checkArgument(msg instanceof Open, "Message needs to be of type Open, not %s", msg);
+        final Open open = (Open) msg;
+        final ByteBuf msgBody = Unpooled.buffer()
+                .writeByte(BGP_VERSION);
+
+        // When our AS number does not fit into two bytes, we report it as AS_TRANS
+        int openAS = open.getMyAsNumber().toJava();
+        if (openAS > Values.UNSIGNED_SHORT_MAX_VALUE) {
+            openAS = AS_TRANS;
+        }
+        msgBody
+            .writeShort(openAS)
+            .writeShort(open.getHoldTimer().toJava())
+            .writeBytes(Ipv4Util.bytesForAddress(open.getBgpIdentifier()));
+
+        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 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);
+            }
+        }
+
+        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;
+    }
+
+    /**
+     * Parses given byte array to BGP Open message.
+     *
+     * @param body byte array representing BGP Open message, without header
+     * @param messageLength the length of the message
+     * @return {@link Open} BGP Open Message
+     * @throws BGPDocumentedException if the parsing was unsuccessful
+     */
+    @Override
+    public Open parseMessageBody(final ByteBuf body, final int messageLength,
+            final PeerSpecificParserConstraint constraint) throws BGPDocumentedException {
+        checkArgument(body != null, "Buffer cannot be null.");
+
+        if (body.readableBytes() < MIN_MSG_LENGTH) {
+            throw BGPDocumentedException.badMessageLength("Open message too small.", messageLength);
+        }
+        final int version = body.readUnsignedByte();
+        if (version != BGP_VERSION) {
+            throw new BGPDocumentedException("BGP Protocol version " + version + " not supported.",
+                    BGPError.VERSION_NOT_SUPPORTED);
+        }
+        final AsNumber as = new AsNumber(Uint32.valueOf(body.readUnsignedShort()));
+        final Uint16 holdTime = ByteBufUtils.readUint16(body);
+        if (Uint16.ONE.equals(holdTime) || Uint16.TWO.equals(holdTime)) {
+            throw new BGPDocumentedException("Hold time value not acceptable.", BGPError.HOLD_TIME_NOT_ACC);
+        }
+        final Ipv4AddressNoZone bgpId;
+        try {
+            bgpId = Ipv4Util.addressForByteBuf(body);
+        } catch (final IllegalArgumentException e) {
+            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 = parseParameters(body.slice(), optLength);
+        LOG.debug("BGP Open message was parsed: AS = {}, holdTimer = {}, bgpId = {}, optParams = {}", as,
+                holdTime, bgpId, optParams);
+        return new OpenBuilder().setMyAsNumber(Uint16.valueOf(as.getValue())).setHoldTimer(holdTime)
+                .setBgpIdentifier(bgpId).setBgpParameters(optParams).build();
+    }
+
+    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: {} 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()) {
+            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.UNSPECIFIC_OPEN_ERROR);
+            }
+            final int paramLength = extendedLength.isPresent() ? buffer.readUnsignedShort() : buffer.readUnsignedByte();
+            final ByteBuf paramBody = buffer.readSlice(paramLength);
+
+            final BgpParameters param;
+            try {
+                param = parser.get().parseParameter(paramBody);
+            } catch (final BGPParsingException e) {
+                throw new BGPDocumentedException("Optional parameter not parsed", BGPError.UNSPECIFIC_OPEN_ERROR, e);
+            }
+
+            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());
+    }
 }