Cleanup FlowMessageSerializer.isVlanMatchPresent()
[openflowplugin.git] / openflowplugin-impl / src / main / java / org / opendaylight / openflowplugin / impl / protocol / serialization / messages / FlowMessageSerializer.java
1 /*
2  * Copyright (c) 2016 Pantheon Technologies s.r.o. 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.openflowplugin.impl.protocol.serialization.messages;
9
10 import com.google.common.base.MoreObjects;
11 import com.google.common.base.Preconditions;
12 import io.netty.buffer.ByteBuf;
13 import java.util.ArrayList;
14 import java.util.List;
15 import java.util.Objects;
16 import java.util.Optional;
17 import java.util.stream.Collectors;
18 import org.opendaylight.openflowjava.protocol.api.extensibility.OFSerializer;
19 import org.opendaylight.openflowjava.protocol.api.extensibility.SerializerRegistry;
20 import org.opendaylight.openflowjava.protocol.api.extensibility.SerializerRegistryInjector;
21 import org.opendaylight.openflowjava.protocol.api.keys.MessageTypeKey;
22 import org.opendaylight.openflowjava.protocol.api.util.EncodeConstants;
23 import org.opendaylight.openflowjava.util.ByteBufUtils;
24 import org.opendaylight.openflowplugin.api.OFConstants;
25 import org.opendaylight.openflowplugin.impl.protocol.serialization.util.InstructionUtil;
26 import org.opendaylight.openflowplugin.openflow.md.core.sal.convertor.common.OrderComparator;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.ActionList;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.VlanCfi;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.PushVlanActionCaseBuilder;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpDstActionCase;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpDstActionCaseBuilder;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpSrcActionCase;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetTpSrcActionCaseBuilder;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.push.vlan.action._case.PushVlanActionBuilder;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.set.tp.dst.action._case.SetTpDstActionBuilder;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.set.tp.src.action._case.SetTpSrcActionBuilder;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.Flow;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowMessage;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowMessageBuilder;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.InstructionsBuilder;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.flow.MatchBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.Instruction;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCase;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.ApplyActionsCaseBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.instruction.apply.actions._case.ApplyActionsBuilder;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.InstructionBuilder;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.l2.types.rev130827.VlanId;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.Match;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatch;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.match.VlanMatchBuilder;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.model.match.types.rev131026.vlan.match.fields.VlanIdBuilder;
58 import org.opendaylight.yangtools.yang.common.Uint16;
59 import org.opendaylight.yangtools.yang.common.Uint32;
60 import org.opendaylight.yangtools.yang.common.Uint64;
61 import org.opendaylight.yangtools.yang.common.Uint8;
62
63 /**
64  * Translates FlowMod messages.
65  * OF protocol versions: 1.3
66  */
67 public class FlowMessageSerializer extends AbstractMessageSerializer<FlowMessage> implements
68         SerializerRegistryInjector {
69     private static final FlowCookie DEFAULT_COOKIE = new FlowCookie(OFConstants.DEFAULT_COOKIE);
70     private static final FlowCookie DEFAULT_COOKIE_MASK = new FlowCookie(OFConstants.DEFAULT_COOKIE_MASK);
71     private static final Uint8 DEFAULT_TABLE_ID = Uint8.ZERO;
72     private static final Uint16 DEFAULT_IDLE_TIMEOUT = Uint16.ZERO;
73     private static final Uint16 DEFAULT_HARD_TIMEOUT = Uint16.ZERO;
74     private static final Uint16 DEFAULT_PRIORITY = OFConstants.DEFAULT_FLOW_PRIORITY;
75     private static final Uint32 DEFAULT_BUFFER_ID = OFConstants.OFP_NO_BUFFER;
76     private static final Uint64 DEFAULT_OUT_PORT = Uint64.valueOf(OFConstants.OFPP_ANY);
77     private static final Uint32 DEFAULT_OUT_GROUP = OFConstants.OFPG_ANY;
78     private static final byte PADDING_IN_FLOW_MOD_MESSAGE = 2;
79     private static final FlowModFlags DEFAULT_FLAGS = new FlowModFlags(false, false, false, false, false);
80     private static final Uint16 PUSH_VLAN = Uint16.valueOf(0x8100);
81     private static final Integer PUSH_TAG = PUSH_VLAN.toJava();
82     private static final VlanCfi PUSH_CFI = new VlanCfi(1);
83
84     private static final VlanMatch VLAN_MATCH_FALSE = new VlanMatchBuilder()
85             .setVlanId(new VlanIdBuilder()
86                     .setVlanIdPresent(false)
87                     .setVlanId(new VlanId(Uint16.ZERO))
88                     .build())
89             .build();
90
91     private static final VlanMatch VLAN_MATCH_TRUE = new VlanMatchBuilder()
92             .setVlanId(new VlanIdBuilder()
93                     .setVlanIdPresent(true)
94                     .setVlanId(new VlanId(Uint16.ZERO))
95                     .build())
96             .build();
97
98     private SerializerRegistry registry;
99
100     @Override
101     public void serialize(final FlowMessage message, final ByteBuf outBuffer) {
102         if (!isVlanMatchPresent(message) && isSetVlanIdActionCasePresent(message)) {
103             writeVlanFlow(message, outBuffer);
104         } else {
105             writeFlow(message, outBuffer);
106         }
107     }
108
109     /**
110      * Serialize flow message. Needs to be separated from main serialize method to prevent recursion
111      * when serializing SetVlanId flows.
112      *
113      * @param message   flow message
114      * @param outBuffer output buffer
115      */
116     private void writeFlow(final FlowMessage message, final ByteBuf outBuffer) {
117         final int index = outBuffer.writerIndex();
118         super.serialize(message, outBuffer);
119         outBuffer.writeLong(MoreObjects.firstNonNull(message.getCookie(), DEFAULT_COOKIE).getValue().longValue());
120         outBuffer.writeLong(MoreObjects.firstNonNull(message.getCookieMask(), DEFAULT_COOKIE_MASK).getValue()
121                 .longValue());
122         outBuffer.writeByte(MoreObjects.firstNonNull(message.getTableId(), DEFAULT_TABLE_ID).toJava());
123         outBuffer.writeByte(message.getCommand().getIntValue());
124         outBuffer.writeShort(MoreObjects.firstNonNull(message.getIdleTimeout(), DEFAULT_IDLE_TIMEOUT).toJava());
125         outBuffer.writeShort(MoreObjects.firstNonNull(message.getHardTimeout(), DEFAULT_HARD_TIMEOUT).toJava());
126         outBuffer.writeShort(MoreObjects.firstNonNull(message.getPriority(), DEFAULT_PRIORITY).toJava());
127         outBuffer.writeInt(MoreObjects.firstNonNull(message.getBufferId(), DEFAULT_BUFFER_ID).intValue());
128         outBuffer.writeInt(MoreObjects.firstNonNull(message.getOutPort(), DEFAULT_OUT_PORT).intValue());
129         outBuffer.writeInt(MoreObjects.firstNonNull(message.getOutGroup(), DEFAULT_OUT_GROUP).intValue());
130         outBuffer.writeShort(createFlowModFlagsBitmask(MoreObjects.firstNonNull(message.getFlags(), DEFAULT_FLAGS)));
131         outBuffer.writeZero(PADDING_IN_FLOW_MOD_MESSAGE);
132         writeMatch(message, outBuffer);
133         writeInstructions(message, outBuffer);
134         outBuffer.setShort(index + 2, outBuffer.writerIndex() - index);
135     }
136
137     /**
138      * Instead of serializing this flow normally, we need to split it to two parts if flow contains
139      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase}.
140      *
141      * @param message   flow mod message
142      * @param outBuffer output buffer
143      */
144     private void writeVlanFlow(final FlowMessage message, final ByteBuf outBuffer) {
145         writeFlow(
146                 new FlowMessageBuilder(message)
147                         .setMatch(new MatchBuilder(message.getMatch())
148                                 .setVlanMatch(VLAN_MATCH_FALSE)
149                                 .build())
150                         .setInstructions(new InstructionsBuilder()
151                                 .setInstruction(updateSetVlanIdAction(message))
152                                 .build())
153                         .build(),
154                 outBuffer);
155
156         writeFlow(
157                 new FlowMessageBuilder(message)
158                         .setMatch(new MatchBuilder(message.getMatch())
159                                 .setVlanMatch(VLAN_MATCH_TRUE)
160                                 .build())
161                         .build(),
162                 outBuffer);
163     }
164
165     /**
166      * Serialize OpenFlowPlugin match to raw bytes.
167      *
168      * @param message   OpenFlow flow mod message
169      * @param outBuffer output buffer
170      */
171     private void writeMatch(final FlowMessage message, final ByteBuf outBuffer) {
172         Preconditions.checkNotNull(registry).<Match, OFSerializer<Match>>getSerializer(
173                 new MessageTypeKey<>(message.getVersion().toJava(), Match.class)).serialize(message.getMatch(),
174                     outBuffer);
175
176     }
177
178     /**
179      * Serialize OpenFlowPlugin instructions and set ip protocol of set-tp-src and set-tp-dst actions of need.
180      *
181      * @param message   OpenFlow flow mod message
182      * @param outBuffer output buffer
183      */
184     private void writeInstructions(final FlowMessage message, final ByteBuf outBuffer) {
185         // Try to get IP protocol from IP match
186         final Optional<Uint8> protocol = Optional
187                 .ofNullable(message.getMatch())
188                 .flatMap(m -> Optional.ofNullable(m.getIpMatch()))
189                 .flatMap(ipm -> Optional.ofNullable(ipm.getIpProtocol()));
190
191         // Update instructions if needed and then serialize all instructions
192         Optional.ofNullable(message.getInstructions())
193                 .flatMap(is -> Optional.ofNullable(is.nonnullInstruction()))
194                 .ifPresent(is -> is.values().stream()
195                         .filter(Objects::nonNull)
196                         .sorted(OrderComparator.build())
197                         .map(org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types
198                                 .rev131026.Instruction::getInstruction)
199                         .filter(Objects::nonNull)
200                         .map(i -> protocol.flatMap(p -> updateInstruction(i, p)).orElse(i))
201                         .forEach(i -> InstructionUtil.writeInstruction(i, EncodeConstants.OF13_VERSION_ID, registry,
202                                 outBuffer)));
203     }
204
205     /**
206      * Determine type of instruction and update it's actions if it is apply-actions instruction.
207      *
208      * @param instruction instruction
209      * @param protocol    protocol
210      * @return updated instruction or empty
211      */
212     private static Optional<Instruction> updateInstruction(final Instruction instruction, final Uint8 protocol) {
213         if (instruction instanceof ApplyActionsCase) {
214             // FIXME: get rid of this atrocity and just use null checks
215             return Optional.ofNullable(((ApplyActionsCase) instruction).getApplyActions())
216                     .flatMap(aa -> Optional.ofNullable(aa.nonnullAction()))
217                     .map(as -> new ApplyActionsCaseBuilder()
218                             .setApplyActions(new ApplyActionsBuilder()
219                                     .setAction(as.values().stream()
220                                             .filter(Objects::nonNull)
221                                             .map(a -> updateSetTpActions(a, protocol))
222                                             .collect(Collectors.toList()))
223                                     .build())
224                             .build());
225         }
226
227         return Optional.empty();
228     }
229
230     /**
231      * If action is set-tp-src or set-tp-dst, inject IP protocol into it, otherwise return original action.
232      *
233      * @param action   OpenFlow action
234      * @param protocol IP protocol
235      * @return updated OpenFlow action
236      */
237     private static Action updateSetTpActions(final Action action, final Uint8 protocol) {
238         if (action.getAction() instanceof SetTpSrcActionCase) {
239             final SetTpSrcActionCase actionCase = (SetTpSrcActionCase) action.getAction();
240
241             return new ActionBuilder(action)
242                     .setAction(new SetTpSrcActionCaseBuilder(actionCase)
243                             .setSetTpSrcAction(new SetTpSrcActionBuilder(
244                                     actionCase.getSetTpSrcAction())
245                                     .setIpProtocol(protocol)
246                                     .build())
247                             .build())
248                     .build();
249         } else if (action.getAction() instanceof SetTpDstActionCase) {
250             final SetTpDstActionCase actionCase = (SetTpDstActionCase) action.getAction();
251
252             return new ActionBuilder(action)
253                     .setAction(new SetTpDstActionCaseBuilder(actionCase)
254                             .setSetTpDstAction(new SetTpDstActionBuilder(
255                                     actionCase.getSetTpDstAction())
256                                     .setIpProtocol(protocol)
257                                     .build())
258                             .build())
259                     .build();
260         }
261
262         // Return original action if no modifications are needed
263         return action;
264     }
265
266     /**
267      * Create copy of instructions of original flow but insert
268      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.PushVlanActionCase}
269      * before each
270      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase}.
271      *
272      * @param message OpenFlowPlugin flow mod message
273      * @return list of instructions
274      */
275     private static List<org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list
276             .Instruction> updateSetVlanIdAction(final FlowMessage message) {
277         return message.getInstructions().nonnullInstruction().values()
278                 .stream()
279                 .map(i -> {
280                     final int[] offset = {0};
281
282                     return i.getInstruction() instanceof ApplyActionsCase
283                             // FIXME: get rid of this atrocity and just use null checks
284                             ? Optional.ofNullable(((ApplyActionsCase) i.getInstruction()).getApplyActions())
285                             .flatMap(as -> Optional.ofNullable(as.nonnullAction()))
286                             .map(a -> a.values().stream()
287                                     .sorted(OrderComparator.build())
288                                     .flatMap(action -> {
289                                         final List<org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112
290                                                 .action.list.Action> actions = new ArrayList<>();
291
292                                         // If current action is SetVlanId, insert PushVlan action before it and
293                                         // update order
294                                         if (action.getAction() instanceof SetVlanIdActionCase) {
295                                             actions.add(new ActionBuilder()
296                                                     .setAction(new PushVlanActionCaseBuilder()
297                                                             .setPushVlanAction(new PushVlanActionBuilder()
298                                                                     .setCfi(PUSH_CFI)
299                                                                     .setVlanId(((SetVlanIdActionCase) action
300                                                                         .getAction()).getSetVlanIdAction()
301                                                                             .getVlanId())
302                                                                     .setEthernetType(PUSH_VLAN)
303                                                                     .setTag(PUSH_TAG)
304                                                                     .build())
305                                                             .build())
306                                                     .withKey(action.key())
307                                                     .setOrder(action.getOrder() + offset[0])
308                                                     .build());
309
310                                             offset[0]++;
311                                         }
312
313                                         // Update offset of action if there is any inserted PushVlan actions
314                                         actions.add(offset[0] > 0
315                                                 ? new ActionBuilder(action).setOrder(action.getOrder() + offset[0])
316                                                 .withKey(new ActionKey(action.getOrder() + offset[0]))
317                                                 .build()
318                                                 : action);
319
320                                         return actions.stream();
321                                     }))
322                             .map(as -> new InstructionBuilder(i)
323                                     .setInstruction(new ApplyActionsCaseBuilder()
324                                             .setApplyActions(new ApplyActionsBuilder()
325                                                     .setAction(as.collect(Collectors.toList()))
326                                                     .build())
327                                             .build())
328                                     .build())
329                             .orElse(i)
330                             : i;
331                 }).collect(Collectors.toList());
332     }
333
334     /**
335      * Create integer bit mask from
336      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowModFlags}.
337      *
338      * @param flags flow mod flags
339      * @return bit mask
340      */
341     private static int createFlowModFlagsBitmask(final FlowModFlags flags) {
342         return ByteBufUtils.fillBitMask(0,
343                 flags.getSENDFLOWREM(),
344                 flags.getCHECKOVERLAP(),
345                 flags.getRESETCOUNTS(),
346                 flags.getNOPKTCOUNTS(),
347                 flags.getNOBYTCOUNTS());
348     }
349
350     /**
351      * Determine if flow contains vlan match.
352      *
353      * @param flow flow
354      * @return true if flow contains vlan match
355      */
356     private static boolean isVlanMatchPresent(final Flow flow) {
357         final var match = flow.getMatch();
358         return match != null && match.getVlanMatch() != null;
359     }
360
361     /**
362      * Determine if flow contains
363      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight
364      * .flow.types.rev131026.instruction.instruction.ApplyActionsCase} instruction with
365      * #{@link org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.action.SetVlanIdActionCase}
366      * action.
367      *
368      * @param flow OpenFlowPlugin flow
369      * @return true if flow contains SetVlanIdAction
370      */
371     private static boolean isSetVlanIdActionCasePresent(final Flow flow) {
372         // FIXME: get rid of this atrocity and just use null checks
373         return Optional.ofNullable(flow.getInstructions())
374                 .flatMap(is -> Optional.ofNullable(is.nonnullInstruction()))
375                 .flatMap(is -> is.values().stream()
376                         .map(org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types
377                                 .rev131026.Instruction::getInstruction)
378                         .filter(ApplyActionsCase.class::isInstance)
379                         .map(i -> ((ApplyActionsCase) i).getApplyActions())
380                         .filter(Objects::nonNull)
381                         .map(ActionList::nonnullAction)
382                         .filter(Objects::nonNull)
383                         .flatMap(map -> map.values().stream())
384                         .map(Action::getAction)
385                         .filter(SetVlanIdActionCase.class::isInstance)
386                         .findFirst())
387                 .isPresent();
388     }
389
390     @Override
391     protected byte getMessageType() {
392         return 14;
393     }
394
395     @Override
396     public void injectSerializerRegistry(final SerializerRegistry serializerRegistry) {
397         registry = serializerRegistry;
398     }
399 }