Allow AttributeRegistry to signal treat-as-withdraw
[bgpcep.git] / bgp / parser-spi / src / main / java / org / opendaylight / protocol / bgp / parser / spi / pojo / SimpleAttributeRegistry.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.bgp.parser.spi.pojo;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Preconditions;
13 import io.netty.buffer.ByteBuf;
14 import java.util.ArrayList;
15 import java.util.LinkedHashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Map.Entry;
19 import java.util.TreeMap;
20 import java.util.concurrent.atomic.AtomicReference;
21 import org.opendaylight.protocol.bgp.parser.BGPDocumentedException;
22 import org.opendaylight.protocol.bgp.parser.BGPError;
23 import org.opendaylight.protocol.bgp.parser.BGPParsingException;
24 import org.opendaylight.protocol.bgp.parser.BGPTreatAsWithdrawException;
25 import org.opendaylight.protocol.bgp.parser.spi.AttributeParser;
26 import org.opendaylight.protocol.bgp.parser.spi.AttributeRegistry;
27 import org.opendaylight.protocol.bgp.parser.spi.AttributeSerializer;
28 import org.opendaylight.protocol.bgp.parser.spi.ParsedAttributes;
29 import org.opendaylight.protocol.bgp.parser.spi.PeerSpecificParserConstraint;
30 import org.opendaylight.protocol.bgp.parser.spi.RevisedErrorHandling;
31 import org.opendaylight.protocol.concepts.AbstractRegistration;
32 import org.opendaylight.protocol.concepts.HandlerRegistry;
33 import org.opendaylight.protocol.util.BitArray;
34 import org.opendaylight.protocol.util.ByteArray;
35 import org.opendaylight.protocol.util.Values;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.Attributes;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.AttributesBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.UnrecognizedAttributes;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.UnrecognizedAttributesBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.path.attributes.attributes.UnrecognizedAttributesKey;
41 import org.opendaylight.yangtools.yang.binding.DataContainer;
42 import org.opendaylight.yangtools.yang.binding.DataObject;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 final class SimpleAttributeRegistry implements AttributeRegistry {
47
48     private static final class RawAttribute {
49         private final AttributeParser parser;
50         private final ByteBuf buffer;
51
52         RawAttribute(final AttributeParser parser, final ByteBuf buffer) {
53             this.parser = requireNonNull(parser);
54             this.buffer = requireNonNull(buffer);
55         }
56     }
57
58     private static final Logger LOG = LoggerFactory.getLogger(SimpleAttributeRegistry.class);
59     private static final int OPTIONAL_BIT = 0;
60     private static final int TRANSITIVE_BIT = 1;
61     private static final int PARTIAL_BIT = 2;
62     private static final int EXTENDED_LENGTH_BIT = 3;
63
64     private final HandlerRegistry<DataContainer, AttributeParser, AttributeSerializer> handlers =
65             new HandlerRegistry<>();
66     private final Map<AbstractRegistration, AttributeSerializer> serializers = new LinkedHashMap<>();
67     private final AtomicReference<Iterable<AttributeSerializer>> roSerializers =
68         new AtomicReference<>(this.serializers.values());
69     private final List<UnrecognizedAttributes> unrecognizedAttributes = new ArrayList<>();
70
71
72     AutoCloseable registerAttributeParser(final int attributeType, final AttributeParser parser) {
73         Preconditions.checkArgument(attributeType >= 0 && attributeType <= Values.UNSIGNED_BYTE_MAX_VALUE);
74         return this.handlers.registerParser(attributeType, parser);
75     }
76
77     synchronized AutoCloseable registerAttributeSerializer(final Class<? extends DataObject> paramClass,
78             final AttributeSerializer serializer) {
79         final AbstractRegistration reg = this.handlers.registerSerializer(paramClass, serializer);
80
81         this.serializers.put(reg, serializer);
82         return new AbstractRegistration() {
83             @Override
84             protected void removeRegistration() {
85                 synchronized (SimpleAttributeRegistry.this) {
86                     SimpleAttributeRegistry.this.serializers.remove(reg);
87                     SimpleAttributeRegistry.this.roSerializers.set(SimpleAttributeRegistry.this.serializers.values());
88                 }
89                 reg.close();
90             }
91         };
92     }
93
94     private void addAttribute(final ByteBuf buffer, final RevisedErrorHandling errorHandling,
95             final Map<Integer, RawAttribute> attributes) throws BGPDocumentedException {
96         final BitArray flags = BitArray.valueOf(buffer.readByte());
97         final int type = buffer.readUnsignedByte();
98         final int len = flags.get(EXTENDED_LENGTH_BIT) ? buffer.readUnsignedShort() : buffer.readUnsignedByte();
99         final AttributeParser parser = this.handlers.getParser(type);
100         if (attributes.containsKey(type)) {
101             if (parser != null && !parser.ignoreDuplicates(errorHandling)) {
102                 throw new BGPDocumentedException("Duplicate attribute " + type, BGPError.MALFORMED_ATTR_LIST);
103             }
104             LOG.debug("Ignoring duplicate attribute type {}", type);
105             return;
106         }
107
108         if (parser == null) {
109             processUnrecognized(flags, type, buffer, len);
110         } else {
111             attributes.put(type, new RawAttribute(parser, buffer.readSlice(len)));
112         }
113     }
114
115     private void processUnrecognized(final BitArray flags, final int type, final ByteBuf buffer, final int len)
116             throws BGPDocumentedException {
117         if (!flags.get(OPTIONAL_BIT)) {
118             throw new BGPDocumentedException("Well known attribute not recognized.",
119                 BGPError.WELL_KNOWN_ATTR_NOT_RECOGNIZED);
120         }
121         final UnrecognizedAttributes unrecognizedAttribute = new UnrecognizedAttributesBuilder()
122             .withKey(new UnrecognizedAttributesKey((short) type))
123             .setPartial(flags.get(PARTIAL_BIT))
124             .setTransitive(flags.get(TRANSITIVE_BIT))
125             .setType((short) type)
126             .setValue(ByteArray.readBytes(buffer, len)).build();
127         this.unrecognizedAttributes.add(unrecognizedAttribute);
128         LOG.debug("Unrecognized attribute were parsed: {}", unrecognizedAttribute);
129     }
130
131     @Override
132     public ParsedAttributes parseAttributes(final ByteBuf buffer, final PeerSpecificParserConstraint constraint)
133             throws BGPDocumentedException, BGPParsingException {
134         final RevisedErrorHandling errorHandling = RevisedErrorHandling.from(constraint);
135         final Map<Integer, RawAttribute> attributes = new TreeMap<>();
136         while (buffer.isReadable()) {
137             addAttribute(buffer, errorHandling, attributes);
138         }
139
140         /*
141          * TreeMap guarantees that we will be invoking the parser in the order
142          * of increasing attribute type.
143          */
144         // We may have multiple attribute errors, each specifying a withdraw. We need to finish parsing the message
145         // all attributes before we can decide whether we can discard attributes, or whether we need to terminate
146         // the session.
147         final AttributesBuilder builder = new AttributesBuilder();
148         BGPTreatAsWithdrawException withdrawCause = null;
149         for (final Entry<Integer, RawAttribute> entry : attributes.entrySet()) {
150             LOG.debug("Parsing attribute type {}", entry.getKey());
151
152             final RawAttribute a = entry.getValue();
153             try {
154                 a.parser.parseAttribute(a.buffer, builder, errorHandling, constraint);
155             } catch (BGPTreatAsWithdrawException e) {
156                 LOG.info("Attribute {} indicated treat-as-withdraw", entry.getKey(), e);
157                 if (withdrawCause == null) {
158                     withdrawCause = e;
159                 } else {
160                     withdrawCause.addSuppressed(e);
161                 }
162             }
163         }
164         builder.setUnrecognizedAttributes(this.unrecognizedAttributes);
165         return new ParsedAttributes(builder.build(), withdrawCause);
166     }
167
168     @Override
169     public void serializeAttribute(final Attributes attribute,final ByteBuf byteAggregator) {
170         for (final AttributeSerializer serializer : this.roSerializers.get()) {
171             serializer.serializeAttribute(attribute, byteAggregator);
172         }
173     }
174 }