BUG-2383 : split ipv4/6 to separate model
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / RIBImpl.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.rib.impl;
9
10 import com.google.common.base.MoreObjects;
11 import com.google.common.base.MoreObjects.ToStringHelper;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.util.concurrent.FutureCallback;
16 import com.google.common.util.concurrent.Futures;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.concurrent.BlockingQueue;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.LinkedBlockingQueue;
26 import javax.annotation.concurrent.ThreadSafe;
27 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
28 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
29 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
30 import org.opendaylight.controller.md.sal.common.api.data.AsyncTransaction;
31 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
32 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
33 import org.opendaylight.controller.md.sal.common.api.data.TransactionChain;
34 import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
35 import org.opendaylight.protocol.bgp.rib.DefaultRibReference;
36 import org.opendaylight.protocol.bgp.rib.impl.spi.AdjRIBsOut;
37 import org.opendaylight.protocol.bgp.rib.impl.spi.AdjRIBsOutRegistration;
38 import org.opendaylight.protocol.bgp.rib.impl.spi.BGPDispatcher;
39 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
40 import org.opendaylight.protocol.bgp.rib.spi.AbstractAdjRIBs;
41 import org.opendaylight.protocol.bgp.rib.spi.AdjRIBsIn;
42 import org.opendaylight.protocol.bgp.rib.spi.BGPObjectComparator;
43 import org.opendaylight.protocol.bgp.rib.spi.Peer;
44 import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
45 import org.opendaylight.protocol.framework.ReconnectStrategyFactory;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Address;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Ipv4Prefix;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.bgp.rib.rib.loc.rib.tables.routes.Ipv4RoutesCase;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.bgp.rib.rib.loc.rib.tables.routes.Ipv6RoutesCase;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.prefixes.DestinationIpv4Builder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.prefixes.destination.ipv4.Ipv4Prefixes;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.ipv4.prefixes.destination.ipv4.Ipv4PrefixesBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.update.path.attributes.mp.reach.nlri.advertized.routes.destination.type.DestinationIpv4CaseBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.Update;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.UpdateBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.update.Nlri;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.update.PathAttributes;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev130919.update.WithdrawnRoutes;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.BgpTableType;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.PathAttributes1;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.PathAttributes2;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.MpReachNlri;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.MpReachNlriBuilder;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.MpUnreachNlri;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.MpUnreachNlriBuilder;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.mp.reach.nlri.AdvertizedRoutesBuilder;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.update.path.attributes.mp.unreach.nlri.WithdrawnRoutesBuilder;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.BgpRib;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.RibId;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.Route;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.Rib;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.RibBuilder;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.RibKey;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.LocRib;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.LocRibBuilder;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.Ipv4AddressFamily;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev130919.UnicastSubsequentAddressFamily;
81 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
82 import org.slf4j.Logger;
83 import org.slf4j.LoggerFactory;
84
85 @ThreadSafe
86 public final class RIBImpl extends DefaultRibReference implements AutoCloseable, RIB, TransactionChainListener {
87     private static final Logger LOG = LoggerFactory.getLogger(RIBImpl.class);
88     private static final Update EOR = new UpdateBuilder().build();
89     private static final TablesKey IPV4_UNICAST_TABLE = new TablesKey(Ipv4AddressFamily.class, UnicastSubsequentAddressFamily.class);
90
91     /*
92      * FIXME: performance: this needs to be turned into a Peer->offset map.
93      *        The offset is used to locate a the per-peer state entry in the
94      *        RIB tables.
95      *
96      *        For the first release, that map is updated whenever configuration
97      *        changes and remains constant on peer flaps. On re-configuration
98      *        a resize task is scheduled, so large tables may take some time
99      *        before they continue reacting to updates.
100      *
101      *        For subsequent releases, if we make the reformat process concurrent,
102      *        we can trigger reformats when Graceful Restart Time expires for a
103      *        particular peer.
104      */
105     private final ConcurrentMap<Peer, AdjRIBsOut> ribOuts = new ConcurrentHashMap<>();
106     private final ReconnectStrategyFactory tcpStrategyFactory;
107     private final ReconnectStrategyFactory sessionStrategyFactory;
108
109     /**
110      * BGP Best Path selection comparator for ingress best path selection.
111      */
112     private final BGPObjectComparator comparator;
113     private final BGPDispatcher dispatcher;
114     private final BindingTransactionChain chain;
115     private final AsNumber localAs;
116     private final Ipv4Address bgpIdentifier;
117     private final Set<BgpTableType> localTables;
118     private final RIBTables tables;
119     private final BlockingQueue<Peer> peers;
120     private final DataBroker dataBroker;
121
122     private final Runnable scheduler = new Runnable() {
123         @Override
124         public void run() {
125             try {
126                 final Peer peer = RIBImpl.this.peers.take();
127                 LOG.debug("Advertizing loc-rib to new peer {}.", peer);
128                 for (final BgpTableType key : RIBImpl.this.localTables) {
129
130                     synchronized (RIBImpl.this) {
131                         final AdjRIBsTransactionImpl trans = new AdjRIBsTransactionImpl(RIBImpl.this.ribOuts, RIBImpl.this.comparator, RIBImpl.this.chain.newWriteOnlyTransaction());
132                         final AbstractAdjRIBs<?, ?, ?> adj = (AbstractAdjRIBs<?, ?, ?>) RIBImpl.this.tables.get(new TablesKey(key.getAfi(), key.getSafi()));
133                         adj.addAllEntries(trans);
134                         Futures.addCallback(trans.commit(), new FutureCallback<Void>() {
135                             @Override
136                             public void onSuccess(final Void result) {
137                                 LOG.trace("Advertizing {} to peer {} committed successfully", key.getAfi(), peer);
138                             }
139                             @Override
140                             public void onFailure(final Throwable t) {
141                                 LOG.error("Failed to update peer {} with RIB {}", peer, t);
142                             }
143                         });
144                     }
145                 }
146             } catch (final InterruptedException e) {
147                 LOG.info("Scheduler thread was interrupted.", e);
148             }
149         }
150     };
151
152     public RIBImpl(final RibId ribId, final AsNumber localAs, final Ipv4Address localBgpId, final RIBExtensionConsumerContext extensions,
153         final BGPDispatcher dispatcher, final ReconnectStrategyFactory tcpStrategyFactory,
154         final ReconnectStrategyFactory sessionStrategyFactory, final DataBroker dps, final List<BgpTableType> localTables) {
155         super(InstanceIdentifier.builder(BgpRib.class).child(Rib.class, new RibKey(Preconditions.checkNotNull(ribId))).build());
156         this.chain = dps.createTransactionChain(this);
157         this.localAs = Preconditions.checkNotNull(localAs);
158         this.comparator = new BGPObjectComparator(localAs);
159         this.bgpIdentifier = Preconditions.checkNotNull(localBgpId);
160         this.dispatcher = Preconditions.checkNotNull(dispatcher);
161         this.sessionStrategyFactory = Preconditions.checkNotNull(sessionStrategyFactory);
162         this.tcpStrategyFactory = Preconditions.checkNotNull(tcpStrategyFactory);
163         this.localTables = ImmutableSet.copyOf(localTables);
164         this.tables = new RIBTables(extensions);
165         this.peers = new LinkedBlockingQueue<>();
166         this.dataBroker = dps;
167
168         LOG.debug("Instantiating RIB table {} at {}", ribId, getInstanceIdentifier());
169
170         final WriteTransaction trans = this.chain.newWriteOnlyTransaction();
171
172         // put empty BgpRib if not exists
173         trans.put(LogicalDatastoreType.OPERATIONAL, getInstanceIdentifier(), new RibBuilder().setKey(new RibKey(ribId)).setId(ribId).setLocRib(
174             new LocRibBuilder().setTables(Collections.<Tables> emptyList()).build()).build(), true);
175
176         for (final BgpTableType t : localTables) {
177             final TablesKey key = new TablesKey(t.getAfi(), t.getSafi());
178             if (this.tables.create(trans, this, key) == null) {
179                 LOG.debug("Did not create local table for unhandled table type {}", t);
180             }
181         }
182
183         Futures.addCallback(trans.submit(), new FutureCallback<Void>() {
184             @Override
185             public void onSuccess(final Void result) {
186                 LOG.trace("Change committed successfully");
187             }
188
189             @Override
190             public void onFailure(final Throwable t) {
191                 LOG.error("Failed to initiate RIB {}", getInstanceIdentifier(), t);
192             }
193         });
194     }
195
196     synchronized void initTables(final byte[] remoteBgpId) {
197     }
198
199     @Override
200     public synchronized void updateTables(final Peer peer, final Update message) {
201         final AdjRIBsTransactionImpl trans = new AdjRIBsTransactionImpl(this.ribOuts, this.comparator, this.chain.newWriteOnlyTransaction());
202
203         if (!EOR.equals(message)) {
204             final WithdrawnRoutes wr = message.getWithdrawnRoutes();
205             if (wr != null) {
206                 final AdjRIBsIn<?, ?> ari = this.tables.get(IPV4_UNICAST_TABLE);
207                 if (ari != null) {
208                     /*
209                      * create MPUnreach for the routes to be handled in the same way as linkstate routes
210                      */
211                     final List<Ipv4Prefixes> prefixes = new ArrayList<>();
212                     for (final Ipv4Prefix p : wr.getWithdrawnRoutes()) {
213                         prefixes.add(new Ipv4PrefixesBuilder().setPrefix(p).build());
214                     }
215                     ari.removeRoutes(
216                         trans,
217                         peer,
218                         new MpUnreachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(UnicastSubsequentAddressFamily.class).setWithdrawnRoutes(
219                             new WithdrawnRoutesBuilder().setDestinationType(
220                                 new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.inet.rev150305.update.path.attributes.mp.unreach.nlri.withdrawn.routes.destination.type.DestinationIpv4CaseBuilder().setDestinationIpv4(
221                                     new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build()).build());
222                 } else {
223                     LOG.debug("Not removing objects from unhandled IPv4 Unicast");
224                 }
225             }
226
227             final PathAttributes attrs = message.getPathAttributes();
228             if (attrs != null) {
229                 final PathAttributes2 mpu = attrs.getAugmentation(PathAttributes2.class);
230                 if (mpu != null) {
231                     final MpUnreachNlri nlri = mpu.getMpUnreachNlri();
232                     final AdjRIBsIn<?, ?> ari = this.tables.get(new TablesKey(nlri.getAfi(), nlri.getSafi()));
233                     // EOR messages do not contain withdrawn routes
234                     if (nlri.getWithdrawnRoutes() != null) {
235                         if (ari != null) {
236                             ari.removeRoutes(trans, peer, nlri);
237                         } else {
238                             LOG.debug("Not removing objects from unhandled NLRI {}", nlri);
239                         }
240                     } else {
241                         ari.markUptodate(trans, peer);
242                     }
243                 }
244             }
245
246             final Nlri ar = message.getNlri();
247             if (ar != null) {
248                 final AdjRIBsIn<?, ?> ari = this.tables.get(IPV4_UNICAST_TABLE);
249                 if (ari != null) {
250                     /*
251                      * create MPReach for the routes to be handled in the same way as linkstate routes
252                      */
253                     final List<Ipv4Prefixes> prefixes = new ArrayList<>();
254                     for (final Ipv4Prefix p : ar.getNlri()) {
255                         prefixes.add(new Ipv4PrefixesBuilder().setPrefix(p).build());
256                     }
257                     final MpReachNlriBuilder b = new MpReachNlriBuilder().setAfi(Ipv4AddressFamily.class).setSafi(
258                         UnicastSubsequentAddressFamily.class).setAdvertizedRoutes(
259                             new AdvertizedRoutesBuilder().setDestinationType(
260                                 new DestinationIpv4CaseBuilder().setDestinationIpv4(
261                                     new DestinationIpv4Builder().setIpv4Prefixes(prefixes).build()).build()).build());
262                     if (attrs != null) {
263                         b.setCNextHop(attrs.getCNextHop());
264                     }
265
266                     ari.addRoutes(trans, peer, b.build(), attrs);
267                 } else {
268                     LOG.debug("Not adding objects from unhandled IPv4 Unicast");
269                 }
270             }
271
272             if (attrs != null) {
273                 final PathAttributes1 mpr = attrs.getAugmentation(PathAttributes1.class);
274                 if (mpr != null) {
275                     final MpReachNlri nlri = mpr.getMpReachNlri();
276
277                     final AdjRIBsIn<?, ?> ari = this.tables.get(new TablesKey(nlri.getAfi(), nlri.getSafi()));
278                     if (ari != null) {
279                         if (message.equals(ari.endOfRib())) {
280                             ari.markUptodate(trans, peer);
281                         } else {
282                             ari.addRoutes(trans, peer, nlri, attrs);
283                         }
284                     } else {
285                         LOG.debug("Not adding objects from unhandled NLRI {}", nlri);
286                     }
287                 }
288             }
289         } else {
290             final AdjRIBsIn<?, ?> ari = this.tables.get(IPV4_UNICAST_TABLE);
291             if (ari != null) {
292                 ari.markUptodate(trans, peer);
293             } else {
294                 LOG.debug("End-of-RIB for IPv4 Unicast ignored");
295             }
296         }
297
298         Futures.addCallback(trans.commit(), new FutureCallback<Void>() {
299             @Override
300             public void onSuccess(final Void result) {
301                 LOG.debug("RIB modification successfully committed.");
302             }
303
304             @Override
305             public void onFailure(final Throwable t) {
306                 LOG.error("Failed to commit RIB modification", t);
307             }
308         });
309     }
310
311     @Override
312     public synchronized void clearTable(final Peer peer, final TablesKey key) {
313         final AdjRIBsIn<?, ?> ari = this.tables.get(key);
314         if (ari != null) {
315             final AdjRIBsTransactionImpl trans = new AdjRIBsTransactionImpl(this.ribOuts, this.comparator, this.chain.newWriteOnlyTransaction());
316             ari.clear(trans, peer);
317
318             Futures.addCallback(trans.commit(), new FutureCallback<Void>() {
319                 @Override
320                 public void onSuccess(final Void result) {
321                     LOG.trace("Table {} cleared successfully", key);
322                 }
323
324                 @Override
325                 public void onFailure(final Throwable t) {
326                     LOG.error("Failed to clear table {}", key, t);
327                 }
328             });
329         }
330     }
331
332     @Override
333     public String toString() {
334         return addToStringAttributes(MoreObjects.toStringHelper(this)).toString();
335     }
336
337     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
338         return toStringHelper;
339     }
340
341     @SuppressWarnings("unchecked")
342     protected <K, V extends Route> AdjRIBsIn<K, V> getTable(final TablesKey key) {
343         return (AdjRIBsIn<K, V>) this.tables.get(key);
344     }
345
346     @Override
347     public synchronized void close() throws InterruptedException, ExecutionException {
348         final WriteTransaction t = this.chain.newWriteOnlyTransaction();
349         t.delete(LogicalDatastoreType.OPERATIONAL, getInstanceIdentifier());
350         t.submit().get();
351         this.chain.close();
352     }
353
354     @Override
355     public AsNumber getLocalAs() {
356         return this.localAs;
357     }
358
359     @Override
360     public Ipv4Address getBgpIdentifier() {
361         return this.bgpIdentifier;
362     }
363
364     @Override
365     public Set<? extends BgpTableType> getLocalTables() {
366         return this.localTables;
367     }
368
369     @Override
370     public ReconnectStrategyFactory getTcpStrategyFactory() {
371         return this.tcpStrategyFactory;
372     }
373
374     @Override
375     public ReconnectStrategyFactory getSessionStrategyFactory() {
376         return this.sessionStrategyFactory;
377     }
378
379     @Override
380     public BGPDispatcher getDispatcher() {
381         return this.dispatcher;
382     }
383
384     @Override
385     public void initTable(final Peer bgpPeer, final TablesKey key) {
386         // FIXME: BUG-196: support graceful restart
387     }
388
389     @Override
390     public AdjRIBsOutRegistration registerRIBsOut(final Peer peer, final AdjRIBsOut aro) {
391         final AdjRIBsOutRegistration reg = new AdjRIBsOutRegistration(aro) {
392             @Override
393             protected void removeRegistration() {
394                 RIBImpl.this.ribOuts.remove(peer, aro);
395             }
396         };
397
398         this.ribOuts.put(peer, aro);
399         LOG.debug("Registering this peer {} to RIB-Out {}", peer, this.ribOuts);
400         try {
401             this.peers.put(peer);
402             new Thread(this.scheduler).start();
403         } catch (final InterruptedException e) {
404             //
405         }
406         return reg;
407     }
408
409     @Override
410     public void onTransactionChainFailed(final TransactionChain<?, ?> chain, final AsyncTransaction<?, ?> transaction, final Throwable cause) {
411         LOG.error("Broken chain in RIB {} transaction {}", getInstanceIdentifier(), transaction.getIdentifier(), cause);
412     }
413
414     @Override
415     public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
416         LOG.info("RIB {} closed successfully", getInstanceIdentifier());
417     }
418
419     @Override
420     public long getRoutesCount(final TablesKey key) {
421         try {
422             final Optional<Tables> tableMaybe = this.dataBroker.newReadOnlyTransaction().read(LogicalDatastoreType.OPERATIONAL,
423                     getInstanceIdentifier().child(LocRib.class).child(Tables.class, key)).checkedGet();
424             if (tableMaybe.isPresent()) {
425                 final Tables table = tableMaybe.get();
426                 if (table.getRoutes() instanceof Ipv4RoutesCase) {
427                     final Ipv4RoutesCase routesCase = (Ipv4RoutesCase) table.getRoutes();
428                     if (routesCase.getIpv4Routes() != null && routesCase.getIpv4Routes().getIpv4Route() != null) {
429                         return routesCase.getIpv4Routes().getIpv4Route().size();
430                     }
431                 } else if (table.getRoutes() instanceof Ipv6RoutesCase) {
432                     final Ipv6RoutesCase routesCase = (Ipv6RoutesCase) table.getRoutes();
433                     if (routesCase.getIpv6Routes() != null && routesCase.getIpv6Routes().getIpv6Route() != null) {
434                         return routesCase.getIpv6Routes().getIpv6Route().size();
435                     }
436                 }
437             }
438         } catch (final ReadFailedException e) {
439             //no-op
440         }
441         return 0;
442     }
443 }