blob: f22fdde909f74fa5d652ca158b7470b5636605dc [file] [log] [blame]
Carmelo Cascone00a59962017-06-16 17:51:49 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Carmelo Cascone00a59962017-06-16 17:51:49 +09003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.net.pi.impl;
18
19import com.google.common.collect.Maps;
20import com.google.common.collect.Sets;
21import org.onlab.util.ImmutableByteSequence;
22import org.onosproject.net.Device;
23import org.onosproject.net.flow.FlowRule;
Carmelo Cascone00a59962017-06-16 17:51:49 +090024import org.onosproject.net.flow.TrafficSelector;
25import org.onosproject.net.flow.TrafficTreatment;
26import org.onosproject.net.flow.criteria.Criterion;
27import org.onosproject.net.flow.criteria.PiCriterion;
28import org.onosproject.net.flow.instructions.Instruction;
29import org.onosproject.net.flow.instructions.PiInstruction;
30import org.onosproject.net.pi.model.PiActionModel;
31import org.onosproject.net.pi.model.PiActionParamModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080032import org.onosproject.net.pi.model.PiMatchFieldId;
33import org.onosproject.net.pi.model.PiMatchFieldModel;
Yi Tseng02c4c572018-01-22 17:52:10 -080034import org.onosproject.net.pi.model.PiMatchType;
Carmelo Cascone00a59962017-06-16 17:51:49 +090035import org.onosproject.net.pi.model.PiPipeconf;
36import org.onosproject.net.pi.model.PiPipelineInterpreter;
37import org.onosproject.net.pi.model.PiPipelineModel;
Carmelo Cascone87892e22017-11-13 16:01:29 -080038import org.onosproject.net.pi.model.PiTableId;
Carmelo Cascone00a59962017-06-16 17:51:49 +090039import org.onosproject.net.pi.model.PiTableModel;
40import org.onosproject.net.pi.runtime.PiAction;
41import org.onosproject.net.pi.runtime.PiActionParam;
42import org.onosproject.net.pi.runtime.PiExactFieldMatch;
43import org.onosproject.net.pi.runtime.PiFieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +090044import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
Carmelo Cascone0e896a02017-07-31 07:22:27 +020045import org.onosproject.net.pi.runtime.PiMatchKey;
Carmelo Cascone00a59962017-06-16 17:51:49 +090046import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
47import org.onosproject.net.pi.runtime.PiTableAction;
48import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone00a59962017-06-16 17:51:49 +090049import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080050import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone00a59962017-06-16 17:51:49 +090051import org.slf4j.Logger;
52import org.slf4j.LoggerFactory;
53
54import java.util.Collection;
55import java.util.Map;
56import java.util.Optional;
57import java.util.Set;
58import java.util.StringJoiner;
59
60import static java.lang.String.format;
61import static org.onlab.util.ImmutableByteSequence.ByteSequenceTrimException;
Carmelo Cascone81929aa2018-04-07 01:38:55 -070062import static org.onlab.util.ImmutableByteSequence.prefixOnes;
Carmelo Cascone00a59962017-06-16 17:51:49 +090063import static org.onosproject.net.flow.criteria.Criterion.Type.PROTOCOL_INDEPENDENT;
64import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020065import static org.onosproject.net.pi.impl.PiUtils.getInterpreterOrNull;
66import static org.onosproject.net.pi.impl.PiUtils.translateTableId;
Carmelo Cascone00a59962017-06-16 17:51:49 +090067
68/**
69 * Implementation of flow rule translation logic.
70 */
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080071final class PiFlowRuleTranslatorImpl {
Carmelo Cascone00a59962017-06-16 17:51:49 +090072
Carmelo Casconea3d811c2017-08-22 01:03:45 +020073 public static final int MAX_PI_PRIORITY = (int) Math.pow(2, 24);
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080074 private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslatorImpl.class);
Carmelo Cascone00a59962017-06-16 17:51:49 +090075
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080076 private PiFlowRuleTranslatorImpl() {
Carmelo Cascone00a59962017-06-16 17:51:49 +090077 // Hide constructor.
78 }
79
Carmelo Cascone87b9b392017-10-02 18:33:20 +020080 /**
81 * Returns a PI table entry equivalent to the given flow rule, for the given pipeconf and device.
82 *
83 * @param rule flow rule
84 * @param pipeconf pipeconf
85 * @param device device
86 * @return PI table entry
87 * @throws PiTranslationException if the flow rule cannot be translated
88 */
89 static PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf, Device device)
90 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +090091
92 PiPipelineModel pipelineModel = pipeconf.pipelineModel();
93
94 // Retrieve interpreter, if any.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020095 final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
96 // Get table model.
97 final PiTableId piTableId = translateTableId(rule.table(), interpreter);
98 final PiTableModel tableModel = getTableModel(piTableId, pipelineModel);
99 // Translate selector.
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700100 final PiMatchKey piMatchKey;
101 final boolean needPriority;
102 if (rule.selector().criteria().isEmpty()) {
103 piMatchKey = PiMatchKey.EMPTY;
104 needPriority = false;
105 } else {
106 final Collection<PiFieldMatch> fieldMatches = translateFieldMatches(
107 interpreter, rule.selector(), tableModel);
108 piMatchKey = PiMatchKey.builder()
109 .addFieldMatches(fieldMatches)
110 .build();
111 // FIXME: P4Runtime limit
112 // Need to ignore priority if no TCAM lookup match field
113 needPriority = fieldMatches.stream()
114 .anyMatch(match -> match.type() == PiMatchType.TERNARY ||
115 match.type() == PiMatchType.RANGE);
116 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200117 // Translate treatment.
118 final PiTableAction piTableAction = translateTreatment(rule.treatment(), interpreter, piTableId, pipelineModel);
Carmelo Casconea62ac3d2017-08-30 03:19:00 +0200119
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200120 // Build PI entry.
121 final PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900122
Carmelo Cascone00a59962017-06-16 17:51:49 +0900123 tableEntryBuilder
124 .forTable(piTableId)
Yi Tsengdf3eec52018-02-15 14:56:02 -0800125 .withMatchKey(piMatchKey);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900126
Yi Tsengd28936e2018-02-23 22:11:11 +0100127 if (piTableAction != null) {
128 tableEntryBuilder.withAction(piTableAction);
129 }
130
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700131 if (needPriority) {
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700132 // In the P4 world 0 is the highest priority, in ONOS the lowest one.
133 // FIXME: move priority conversion to the P4Runtime driver
134 final int newPriority;
135 if (rule.priority() > MAX_PI_PRIORITY) {
136 log.warn("Flow rule priority too big, setting translated priority to max value {}: {}",
137 MAX_PI_PRIORITY, rule);
138 newPriority = 0;
139 } else {
140 newPriority = MAX_PI_PRIORITY - rule.priority();
141 }
142 tableEntryBuilder.withPriority(newPriority);
Yi Tseng02c4c572018-01-22 17:52:10 -0800143 }
144
Carmelo Cascone00a59962017-06-16 17:51:49 +0900145 if (!rule.isPermanent()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200146 if (tableModel.supportsAging()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900147 tableEntryBuilder.withTimeout((double) rule.timeout());
148 } else {
149 log.warn("Flow rule is temporary, but table '{}' doesn't support " +
Carmelo Cascone87892e22017-11-13 16:01:29 -0800150 "aging, translating to permanent.", tableModel.id());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900151 }
152
153 }
154
155 return tableEntryBuilder.build();
156 }
157
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200158
159 /**
160 * Returns a PI action equivalent to the given treatment, optionally using the given interpreter. This method also
161 * checks that the produced PI table action is suitable for the given table ID and pipeline model. If suitable, the
162 * returned action instance will have parameters well-sized, according to the table model.
163 *
164 * @param treatment traffic treatment
165 * @param interpreter interpreter
166 * @param tableId PI table ID
167 * @param pipelineModel pipeline model
168 * @return PI table action
169 * @throws PiTranslationException if the treatment cannot be translated or if the PI action is not suitable for the
170 * given pipeline model
171 */
172 static PiTableAction translateTreatment(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
173 PiTableId tableId, PiPipelineModel pipelineModel)
174 throws PiTranslationException {
175 PiTableModel tableModel = getTableModel(tableId, pipelineModel);
176 return typeCheckAction(buildAction(treatment, interpreter, tableId), tableModel);
177 }
178
179 private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
180 throws PiTranslationException {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800181 return pipelineModel.table(piTableId)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200182 .orElseThrow(() -> new PiTranslationException(format(
183 "Not such a table in pipeline model: %s", piTableId)));
184 }
185
Carmelo Cascone00a59962017-06-16 17:51:49 +0900186 /**
187 * Builds a PI action out of the given treatment, optionally using the given interpreter.
188 */
Yi Tseng82512da2017-08-16 19:46:36 -0700189 private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200190 PiTableId tableId)
191 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900192
193 PiTableAction piTableAction = null;
194
195 // If treatment has only one instruction of type PiInstruction, use that.
196 for (Instruction inst : treatment.allInstructions()) {
197 if (inst.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
198 if (treatment.allInstructions().size() == 1) {
199 piTableAction = ((PiInstruction) inst).action();
200 } else {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200201 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900202 "Unable to translate treatment, found multiple instructions " +
203 "of which one is protocol-independent: %s", treatment));
204 }
205 }
206 }
207
208 if (piTableAction == null && interpreter != null) {
209 // No PiInstruction, use interpreter to build action.
210 try {
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400211 piTableAction = interpreter.mapTreatment(treatment, tableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900212 } catch (PiPipelineInterpreter.PiInterpreterException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200213 throw new PiTranslationException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900214 "Interpreter was unable to translate treatment. " + e.getMessage());
215 }
216 }
217
Yi Tseng82512da2017-08-16 19:46:36 -0700218 return piTableAction;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900219 }
220
Yi Tseng82512da2017-08-16 19:46:36 -0700221 private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200222 throws PiTranslationException {
Yi Tsengd28936e2018-02-23 22:11:11 +0100223 if (piTableAction == null) {
224 // skip check if null
225 return null;
226 }
Yi Tseng82512da2017-08-16 19:46:36 -0700227 switch (piTableAction.type()) {
228 case ACTION:
229 return checkPiAction((PiAction) piTableAction, table);
230 default:
231 // FIXME: should we check? how?
232 return piTableAction;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900233
Yi Tseng82512da2017-08-16 19:46:36 -0700234 }
235 }
236
237 private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200238 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900239 // Table supports this action?
Carmelo Cascone87892e22017-11-13 16:01:29 -0800240 PiActionModel actionModel = table.action(piAction.id()).orElseThrow(
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200241 () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800242 piAction.id(), table.id())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900243
244 // Is the number of runtime parameters correct?
245 if (actionModel.params().size() != piAction.parameters().size()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200246 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900247 "Wrong number of runtime parameters for action '%s', expected %d but found %d",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800248 actionModel.id(), actionModel.params().size(), piAction.parameters().size()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900249 }
250
251 // Forge a new action instance with well-sized parameters.
252 // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
253 PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
254 for (PiActionParam param : piAction.parameters()) {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800255 PiActionParamModel paramModel = actionModel.param(param.id())
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200256 .orElseThrow(() -> new PiTranslationException(format(
Carmelo Cascone87892e22017-11-13 16:01:29 -0800257 "Not such parameter '%s' for action '%s'", param.id(), actionModel)));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900258 try {
259 newActionBuilder.withParameter(new PiActionParam(param.id(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700260 param.value().fit(paramModel.bitWidth())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900261 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200262 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900263 "Size mismatch for parameter '%s' of action '%s': %s",
264 param.id(), piAction.id(), e.getMessage()));
265 }
266 }
267
268 return newActionBuilder.build();
269 }
270
271 /**
272 * Builds a collection of PI field matches out of the given selector, optionally using the given interpreter. The
273 * field matches returned are guaranteed to be compatible for the given table model.
274 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200275 private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
276 TrafficSelector selector, PiTableModel tableModel)
277 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900278
Carmelo Cascone87892e22017-11-13 16:01:29 -0800279 Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900280
281 // If present, find a PiCriterion and get its field matches as a map. Otherwise, use an empty map.
Carmelo Cascone87892e22017-11-13 16:01:29 -0800282 Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
Carmelo Cascone00a59962017-06-16 17:51:49 +0900283 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
284 .map(c -> (PiCriterion) c)
285 .findFirst()
286 .map(PiCriterion::fieldMatches)
287 .map(c -> {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800288 Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900289 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
290 return fieldMap;
291 })
292 .orElse(Maps.newHashMap());
293
294 Set<Criterion> translatedCriteria = Sets.newHashSet();
295 Set<Criterion> ignoredCriteria = Sets.newHashSet();
Carmelo Cascone87892e22017-11-13 16:01:29 -0800296 Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
297 Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900298
Carmelo Cascone87892e22017-11-13 16:01:29 -0800299 for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900300
Carmelo Cascone87892e22017-11-13 16:01:29 -0800301 PiMatchFieldId fieldId = fieldModel.id();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900302
Carmelo Cascone87892e22017-11-13 16:01:29 -0800303 int bitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900304
305 Optional<Criterion.Type> criterionType =
306 interpreter == null
307 ? Optional.empty()
Carmelo Cascone87892e22017-11-13 16:01:29 -0800308 : interpreter.mapPiMatchFieldId(fieldId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900309
310 Criterion criterion = criterionType.map(selector::getCriterion).orElse(null);
311
312 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
313 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
314 // Can ignore if the match is ternary or LPM.
315 switch (fieldModel.matchType()) {
316 case TERNARY:
Carmelo Cascone00a59962017-06-16 17:51:49 +0900317 case LPM:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700318 // Skip field.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900319 break;
320 // FIXME: Can we handle the case of RANGE or VALID match?
321 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200322 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900323 "No value found for required match field '%s'", fieldId));
324 }
325 // Next field.
326 continue;
327 }
328
329 PiFieldMatch fieldMatch = null;
330
331 if (criterion != null) {
332 // Criterion mapping is possible for this field id.
333 try {
334 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
335 translatedCriteria.add(criterion);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200336 } catch (PiTranslationException ex) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900337 // Ignore exception if the same field was found in PiCriterion.
338 if (piCriterionFields.containsKey(fieldId)) {
339 ignoredCriteria.add(criterion);
340 } else {
341 throw ex;
342 }
343 }
344 }
345
346 if (piCriterionFields.containsKey(fieldId)) {
347 // Field was found in PiCriterion.
348 if (fieldMatch != null) {
349 // Field was already translated from other criterion.
350 // Throw exception only if we are trying to match on different values of the same field...
351 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200352 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900353 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
354 "what found in PiCriterion.", fieldId, criterion.type()));
355 }
356 ignoredPiCriterionFields.add(fieldId);
357 } else {
358 fieldMatch = piCriterionFields.get(fieldId);
359 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
360 usedPiCriterionFields.add(fieldId);
361 }
362 }
363
364 fieldMatches.put(fieldId, fieldMatch);
365 }
366
367 // Check if all criteria have been translated.
368 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
369 selector.criteria().stream()
370 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
371 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
372 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
373 if (skippedCriteriaJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200374 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900375 "The following criteria cannot be translated for table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800376 tableModel.id(), skippedCriteriaJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900377 }
378
379 // Check if all fields found in PiCriterion have been used.
380 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
381 piCriterionFields.keySet().stream()
382 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
383 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
384 if (skippedPiFieldsJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200385 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900386 "The following PiCriterion field matches are not supported in table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800387 tableModel.id(), skippedPiFieldsJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900388 }
389
390 return fieldMatches.values();
391 }
392
Carmelo Cascone87892e22017-11-13 16:01:29 -0800393 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200394 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900395
396 // Check parameter type and size
397 if (!fieldModel.matchType().equals(fieldMatch.type())) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200398 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900399 "Wrong match type for field '%s', expected %s, but found %s",
400 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
401 }
402
Carmelo Cascone87892e22017-11-13 16:01:29 -0800403 int modelBitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900404
405 /*
406 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
407 pipeline model. We duplicate the field match, fitting the byte sequences to the bit-width specified in the
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700408 model. We also normalize ternary (and LPM) field matches by setting to 0 unused bits, as required by P4Runtime.
409
410 These operations are expensive when performed for each field match of each flow rule, but should be
Carmelo Cascone00a59962017-06-16 17:51:49 +0900411 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
412 */
413
414 try {
415 switch (fieldModel.matchType()) {
416 case EXACT:
417 return new PiExactFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700418 ((PiExactFieldMatch) fieldMatch).value().fit(modelBitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900419 case TERNARY:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700420 PiTernaryFieldMatch ternField = (PiTernaryFieldMatch) fieldMatch;
421 ImmutableByteSequence ternMask = ternField.mask().fit(modelBitWidth);
422 ImmutableByteSequence ternValue = ternField.value()
423 .fit(modelBitWidth)
424 .bitwiseAnd(ternMask);
425 return new PiTernaryFieldMatch(fieldMatch.fieldId(), ternValue, ternMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900426 case LPM:
427 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
428 if (lpmfield.prefixLength() > modelBitWidth) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200429 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900430 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
431 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
432 }
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700433 ImmutableByteSequence lpmMask = prefixOnes(modelBitWidth * Byte.SIZE,
434 lpmfield.prefixLength());
435 ImmutableByteSequence lpmValue = lpmfield.value()
436 .fit(modelBitWidth)
437 .bitwiseAnd(lpmMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900438 return new PiLpmFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700439 lpmValue, lpmfield.prefixLength());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900440 case RANGE:
441 return new PiRangeFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700442 ((PiRangeFieldMatch) fieldMatch).lowValue().fit(modelBitWidth),
443 ((PiRangeFieldMatch) fieldMatch).highValue().fit(modelBitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900444 case VALID:
445 return fieldMatch;
446 default:
447 // Should never be here.
Ray Milkey986a47a2018-01-25 11:38:51 -0800448 throw new IllegalArgumentException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900449 "Unrecognized match type " + fieldModel.matchType().name());
450 }
451 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200452 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900453 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
454 }
455 }
456}