blob: f399508dd207bd88e44af92077aae7c0d75a19f0 [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;
Carmelo Cascone99c59db2019-01-17 15:39:35 -080040import org.onosproject.net.pi.model.PiTableType;
Carmelo Cascone00a59962017-06-16 17:51:49 +090041import org.onosproject.net.pi.runtime.PiAction;
42import org.onosproject.net.pi.runtime.PiActionParam;
43import org.onosproject.net.pi.runtime.PiExactFieldMatch;
44import org.onosproject.net.pi.runtime.PiFieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +090045import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
Carmelo Cascone0e896a02017-07-31 07:22:27 +020046import org.onosproject.net.pi.runtime.PiMatchKey;
Daniele Moroc6f2f7f2020-12-18 10:55:57 +010047import org.onosproject.net.pi.runtime.PiOptionalFieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +090048import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
49import org.onosproject.net.pi.runtime.PiTableAction;
50import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone00a59962017-06-16 17:51:49 +090051import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080052import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone00a59962017-06-16 17:51:49 +090053import org.slf4j.Logger;
54import org.slf4j.LoggerFactory;
55
56import java.util.Collection;
57import java.util.Map;
Carmelo Cascone00a59962017-06-16 17:51:49 +090058import java.util.Set;
59import java.util.StringJoiner;
60
61import static java.lang.String.format;
62import static org.onlab.util.ImmutableByteSequence.ByteSequenceTrimException;
Carmelo Cascone81929aa2018-04-07 01:38:55 -070063import static org.onlab.util.ImmutableByteSequence.prefixOnes;
Carmelo Cascone00a59962017-06-16 17:51:49 +090064import static org.onosproject.net.flow.criteria.Criterion.Type.PROTOCOL_INDEPENDENT;
65import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020066import static org.onosproject.net.pi.impl.PiUtils.getInterpreterOrNull;
67import static org.onosproject.net.pi.impl.PiUtils.translateTableId;
Carmelo Cascone00a59962017-06-16 17:51:49 +090068
69/**
70 * Implementation of flow rule translation logic.
71 */
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080072final class PiFlowRuleTranslatorImpl {
Carmelo Cascone00a59962017-06-16 17:51:49 +090073
Carmelo Casconea3d811c2017-08-22 01:03:45 +020074 public static final int MAX_PI_PRIORITY = (int) Math.pow(2, 24);
Carmelo Cascone6af4e172018-06-15 16:01:30 +020075 public static final int MIN_PI_PRIORITY = 1;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080076 private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslatorImpl.class);
Carmelo Cascone00a59962017-06-16 17:51:49 +090077
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080078 private PiFlowRuleTranslatorImpl() {
Carmelo Cascone00a59962017-06-16 17:51:49 +090079 // Hide constructor.
80 }
81
Carmelo Cascone87b9b392017-10-02 18:33:20 +020082 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -070083 * Returns a PI table entry equivalent to the given flow rule, for the given
84 * pipeconf and device.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020085 *
86 * @param rule flow rule
87 * @param pipeconf pipeconf
88 * @param device device
89 * @return PI table entry
90 * @throws PiTranslationException if the flow rule cannot be translated
91 */
92 static PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf, Device device)
93 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +090094
95 PiPipelineModel pipelineModel = pipeconf.pipelineModel();
96
97 // Retrieve interpreter, if any.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020098 final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
99 // Get table model.
100 final PiTableId piTableId = translateTableId(rule.table(), interpreter);
101 final PiTableModel tableModel = getTableModel(piTableId, pipelineModel);
102 // Translate selector.
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700103 final PiMatchKey piMatchKey;
104 final boolean needPriority;
105 if (rule.selector().criteria().isEmpty()) {
106 piMatchKey = PiMatchKey.EMPTY;
107 needPriority = false;
108 } else {
109 final Collection<PiFieldMatch> fieldMatches = translateFieldMatches(
110 interpreter, rule.selector(), tableModel);
111 piMatchKey = PiMatchKey.builder()
112 .addFieldMatches(fieldMatches)
113 .build();
114 // FIXME: P4Runtime limit
115 // Need to ignore priority if no TCAM lookup match field
Yi Tseng667538d2018-06-22 02:19:23 +0800116 needPriority = tableModel.matchFields().stream()
117 .anyMatch(match -> match.matchType() == PiMatchType.TERNARY ||
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100118 match.matchType() == PiMatchType.RANGE ||
119 match.matchType() == PiMatchType.OPTIONAL);
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700120 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200121 // Translate treatment.
122 final PiTableAction piTableAction = translateTreatment(rule.treatment(), interpreter, piTableId, pipelineModel);
Carmelo Casconea62ac3d2017-08-30 03:19:00 +0200123
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200124 // Build PI entry.
125 final PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900126
Carmelo Cascone00a59962017-06-16 17:51:49 +0900127 tableEntryBuilder
128 .forTable(piTableId)
Yi Tsengdf3eec52018-02-15 14:56:02 -0800129 .withMatchKey(piMatchKey);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900130
Yi Tsengd28936e2018-02-23 22:11:11 +0100131 if (piTableAction != null) {
132 tableEntryBuilder.withAction(piTableAction);
133 }
134
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700135 if (needPriority) {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200136 // FIXME: move priority check to P4Runtime driver.
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700137 final int newPriority;
138 if (rule.priority() > MAX_PI_PRIORITY) {
139 log.warn("Flow rule priority too big, setting translated priority to max value {}: {}",
140 MAX_PI_PRIORITY, rule);
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200141 newPriority = MAX_PI_PRIORITY;
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700142 } else {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200143 newPriority = MIN_PI_PRIORITY + rule.priority();
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700144 }
145 tableEntryBuilder.withPriority(newPriority);
Yi Tseng02c4c572018-01-22 17:52:10 -0800146 }
147
Carmelo Cascone00a59962017-06-16 17:51:49 +0900148 if (!rule.isPermanent()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200149 if (tableModel.supportsAging()) {
Carmelo Casconef11a3212020-08-13 17:34:17 -0700150 tableEntryBuilder.withTimeout(rule.timeout());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900151 } else {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200152 log.debug("Flow rule is temporary, but table '{}' doesn't support " +
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700153 "aging, translating to permanent.", tableModel.id());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900154 }
155
156 }
157
158 return tableEntryBuilder.build();
159 }
160
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200161
162 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700163 * Returns a PI action equivalent to the given treatment, optionally using
164 * the given interpreter. This method also checks that the produced PI table
165 * action is suitable for the given table ID and pipeline model. If
166 * suitable, the returned action instance will have parameters well-sized,
167 * according to the table model.
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200168 *
169 * @param treatment traffic treatment
170 * @param interpreter interpreter
171 * @param tableId PI table ID
172 * @param pipelineModel pipeline model
173 * @return PI table action
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700174 * @throws PiTranslationException if the treatment cannot be translated or
175 * if the PI action is not suitable for the
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200176 * given pipeline model
177 */
178 static PiTableAction translateTreatment(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
179 PiTableId tableId, PiPipelineModel pipelineModel)
180 throws PiTranslationException {
181 PiTableModel tableModel = getTableModel(tableId, pipelineModel);
182 return typeCheckAction(buildAction(treatment, interpreter, tableId), tableModel);
183 }
184
185 private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
186 throws PiTranslationException {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800187 return pipelineModel.table(piTableId)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200188 .orElseThrow(() -> new PiTranslationException(format(
189 "Not such a table in pipeline model: %s", piTableId)));
190 }
191
Carmelo Cascone00a59962017-06-16 17:51:49 +0900192 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700193 * Builds a PI action out of the given treatment, optionally using the given
194 * interpreter.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900195 */
Yi Tseng82512da2017-08-16 19:46:36 -0700196 private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200197 PiTableId tableId)
198 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900199
200 PiTableAction piTableAction = null;
201
202 // If treatment has only one instruction of type PiInstruction, use that.
203 for (Instruction inst : treatment.allInstructions()) {
204 if (inst.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
205 if (treatment.allInstructions().size() == 1) {
206 piTableAction = ((PiInstruction) inst).action();
207 } else {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200208 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900209 "Unable to translate treatment, found multiple instructions " +
210 "of which one is protocol-independent: %s", treatment));
211 }
212 }
213 }
214
215 if (piTableAction == null && interpreter != null) {
216 // No PiInstruction, use interpreter to build action.
217 try {
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400218 piTableAction = interpreter.mapTreatment(treatment, tableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900219 } catch (PiPipelineInterpreter.PiInterpreterException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200220 throw new PiTranslationException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900221 "Interpreter was unable to translate treatment. " + e.getMessage());
222 }
223 }
224
Yi Tseng82512da2017-08-16 19:46:36 -0700225 return piTableAction;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900226 }
227
Yi Tseng82512da2017-08-16 19:46:36 -0700228 private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200229 throws PiTranslationException {
Yi Tsengd28936e2018-02-23 22:11:11 +0100230 if (piTableAction == null) {
231 // skip check if null
232 return null;
233 }
Yi Tseng82512da2017-08-16 19:46:36 -0700234 switch (piTableAction.type()) {
235 case ACTION:
236 return checkPiAction((PiAction) piTableAction, table);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800237 case ACTION_PROFILE_GROUP_ID:
238 case ACTION_PROFILE_MEMBER_ID:
239 if (!table.tableType().equals(PiTableType.INDIRECT)) {
240 throw new PiTranslationException(format(
241 "action is indirect of type '%s', but table '%s' is of type '%s'",
242 piTableAction.type(), table.id(), table.tableType()));
243 }
244 if (piTableAction.type().equals(PiTableAction.Type.ACTION_PROFILE_GROUP_ID)
245 && (table.actionProfile() == null || !table.actionProfile().hasSelector())) {
246 throw new PiTranslationException(format(
247 "action is of type '%s', but table '%s' does not" +
248 "implement an action profile with dynamic selection",
249 piTableAction.type(), table.id()));
250 }
Yi Tseng82512da2017-08-16 19:46:36 -0700251 return piTableAction;
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800252 default:
253 throw new PiTranslationException(format(
254 "Unknown table action type %s", piTableAction.type()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900255
Yi Tseng82512da2017-08-16 19:46:36 -0700256 }
257 }
258
259 private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200260 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900261 // Table supports this action?
Carmelo Cascone87892e22017-11-13 16:01:29 -0800262 PiActionModel actionModel = table.action(piAction.id()).orElseThrow(
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200263 () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800264 piAction.id(), table.id())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900265
266 // Is the number of runtime parameters correct?
267 if (actionModel.params().size() != piAction.parameters().size()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200268 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900269 "Wrong number of runtime parameters for action '%s', expected %d but found %d",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800270 actionModel.id(), actionModel.params().size(), piAction.parameters().size()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900271 }
272
273 // Forge a new action instance with well-sized parameters.
274 // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
275 PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
276 for (PiActionParam param : piAction.parameters()) {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800277 PiActionParamModel paramModel = actionModel.param(param.id())
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200278 .orElseThrow(() -> new PiTranslationException(format(
Carmelo Cascone87892e22017-11-13 16:01:29 -0800279 "Not such parameter '%s' for action '%s'", param.id(), actionModel)));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900280 try {
281 newActionBuilder.withParameter(new PiActionParam(param.id(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100282 paramModel.hasBitWidth() ?
283 param.value().fit(paramModel.bitWidth()) :
284 param.value()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900285 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200286 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900287 "Size mismatch for parameter '%s' of action '%s': %s",
288 param.id(), piAction.id(), e.getMessage()));
289 }
290 }
291
292 return newActionBuilder.build();
293 }
294
295 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700296 * Builds a collection of PI field matches out of the given selector,
297 * optionally using the given interpreter. The field matches returned are
298 * guaranteed to be compatible for the given table model.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900299 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200300 private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
301 TrafficSelector selector, PiTableModel tableModel)
302 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900303
Carmelo Cascone87892e22017-11-13 16:01:29 -0800304 Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900305
306 // 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 -0800307 Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
Carmelo Cascone00a59962017-06-16 17:51:49 +0900308 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
309 .map(c -> (PiCriterion) c)
310 .findFirst()
311 .map(PiCriterion::fieldMatches)
312 .map(c -> {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800313 Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900314 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
315 return fieldMap;
316 })
317 .orElse(Maps.newHashMap());
318
319 Set<Criterion> translatedCriteria = Sets.newHashSet();
320 Set<Criterion> ignoredCriteria = Sets.newHashSet();
Carmelo Cascone87892e22017-11-13 16:01:29 -0800321 Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
322 Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900323
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700324
325 Map<PiMatchFieldId, Criterion> criterionMap = Maps.newHashMap();
326 if (interpreter != null) {
327 // NOTE: if two criterion types map to the same match field ID, and
328 // those two criterion types are present in the selector, this won't
329 // work. This is unlikely to happen since those cases should be
330 // mutually exclusive:
331 // e.g. ICMPV6_TYPE -> metadata.my_normalized_icmp_type
332 // ICMPV4_TYPE -> metadata.my_normalized_icmp_type
333 // A packet can be either ICMPv6 or ICMPv4 but not both.
334 selector.criteria()
335 .stream()
336 .map(Criterion::type)
337 .filter(t -> t != PROTOCOL_INDEPENDENT)
338 .forEach(t -> {
339 PiMatchFieldId mfid = interpreter.mapCriterionType(t)
340 .orElse(null);
341 if (mfid != null) {
342 if (criterionMap.containsKey(mfid)) {
343 log.warn("Detected criterion mapping " +
344 "conflict for PiMatchFieldId {}",
345 mfid);
346 }
347 criterionMap.put(mfid, selector.getCriterion(t));
348 }
349 });
350 }
351
Carmelo Cascone87892e22017-11-13 16:01:29 -0800352 for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900353
Carmelo Cascone87892e22017-11-13 16:01:29 -0800354 PiMatchFieldId fieldId = fieldModel.id();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900355
Carmelo Cascone87892e22017-11-13 16:01:29 -0800356 int bitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900357
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700358 Criterion criterion = criterionMap.get(fieldId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900359
360 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
361 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
Carmelo Casconef11a3212020-08-13 17:34:17 -0700362 // Can ignore if match is ternary-like, as it means "don't care".
Carmelo Cascone00a59962017-06-16 17:51:49 +0900363 switch (fieldModel.matchType()) {
364 case TERNARY:
Carmelo Cascone00a59962017-06-16 17:51:49 +0900365 case LPM:
Carmelo Casconef11a3212020-08-13 17:34:17 -0700366 case RANGE:
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100367 case OPTIONAL:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700368 // Skip field.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900369 break;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900370 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200371 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900372 "No value found for required match field '%s'", fieldId));
373 }
374 // Next field.
375 continue;
376 }
377
378 PiFieldMatch fieldMatch = null;
379
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100380 // TODO: we currently do not support fields with arbitrary bit width
381 if (criterion != null && fieldModel.hasBitWidth()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900382 // Criterion mapping is possible for this field id.
383 try {
384 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
385 translatedCriteria.add(criterion);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200386 } catch (PiTranslationException ex) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900387 // Ignore exception if the same field was found in PiCriterion.
388 if (piCriterionFields.containsKey(fieldId)) {
389 ignoredCriteria.add(criterion);
390 } else {
391 throw ex;
392 }
393 }
394 }
395
396 if (piCriterionFields.containsKey(fieldId)) {
397 // Field was found in PiCriterion.
398 if (fieldMatch != null) {
399 // Field was already translated from other criterion.
400 // Throw exception only if we are trying to match on different values of the same field...
401 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200402 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900403 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
404 "what found in PiCriterion.", fieldId, criterion.type()));
405 }
406 ignoredPiCriterionFields.add(fieldId);
407 } else {
408 fieldMatch = piCriterionFields.get(fieldId);
409 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
410 usedPiCriterionFields.add(fieldId);
411 }
412 }
413
414 fieldMatches.put(fieldId, fieldMatch);
415 }
416
417 // Check if all criteria have been translated.
418 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
419 selector.criteria().stream()
420 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
421 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
422 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
423 if (skippedCriteriaJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200424 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900425 "The following criteria cannot be translated for table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800426 tableModel.id(), skippedCriteriaJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900427 }
428
429 // Check if all fields found in PiCriterion have been used.
430 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
431 piCriterionFields.keySet().stream()
432 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
433 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
434 if (skippedPiFieldsJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200435 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900436 "The following PiCriterion field matches are not supported in table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800437 tableModel.id(), skippedPiFieldsJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900438 }
439
440 return fieldMatches.values();
441 }
442
Carmelo Cascone87892e22017-11-13 16:01:29 -0800443 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200444 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900445
446 // Check parameter type and size
447 if (!fieldModel.matchType().equals(fieldMatch.type())) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200448 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900449 "Wrong match type for field '%s', expected %s, but found %s",
450 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
451 }
452
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100453 // Check if the arbitrary bit width is supported
454 if (!fieldModel.hasBitWidth() &&
455 !fieldModel.matchType().equals(PiMatchType.EXACT) &&
456 !fieldModel.matchType().equals(PiMatchType.OPTIONAL)) {
457 throw new PiTranslationException(format(
458 "Arbitrary bit width for field '%s' and match type %s is not supported",
459 fieldMatch.fieldId(), fieldModel.matchType().name()));
460 }
461
Carmelo Cascone87892e22017-11-13 16:01:29 -0800462 int modelBitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900463
464 /*
465 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
466 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 -0700467 model. We also normalize ternary (and LPM) field matches by setting to 0 unused bits, as required by P4Runtime.
468
469 These operations are expensive when performed for each field match of each flow rule, but should be
Carmelo Cascone00a59962017-06-16 17:51:49 +0900470 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
471 */
472
473 try {
474 switch (fieldModel.matchType()) {
475 case EXACT:
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100476 PiExactFieldMatch exactField = (PiExactFieldMatch) fieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900477 return new PiExactFieldMatch(fieldMatch.fieldId(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100478 fieldModel.hasBitWidth() ?
479 exactField.value().fit(modelBitWidth) :
480 exactField.value());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900481 case TERNARY:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700482 PiTernaryFieldMatch ternField = (PiTernaryFieldMatch) fieldMatch;
483 ImmutableByteSequence ternMask = ternField.mask().fit(modelBitWidth);
484 ImmutableByteSequence ternValue = ternField.value()
485 .fit(modelBitWidth)
486 .bitwiseAnd(ternMask);
487 return new PiTernaryFieldMatch(fieldMatch.fieldId(), ternValue, ternMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900488 case LPM:
489 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
490 if (lpmfield.prefixLength() > modelBitWidth) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200491 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900492 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
493 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
494 }
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700495 ImmutableByteSequence lpmValue = lpmfield.value()
Carmelo Cascone181f3f42018-04-11 17:37:53 -0700496 .fit(modelBitWidth);
497 ImmutableByteSequence lpmMask = prefixOnes(lpmValue.size(),
498 lpmfield.prefixLength());
499 lpmValue = lpmValue.bitwiseAnd(lpmMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900500 return new PiLpmFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700501 lpmValue, lpmfield.prefixLength());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900502 case RANGE:
503 return new PiRangeFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700504 ((PiRangeFieldMatch) fieldMatch).lowValue().fit(modelBitWidth),
505 ((PiRangeFieldMatch) fieldMatch).highValue().fit(modelBitWidth));
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100506 case OPTIONAL:
507 PiOptionalFieldMatch optionalField = (PiOptionalFieldMatch) fieldMatch;
508 return new PiOptionalFieldMatch(fieldMatch.fieldId(),
509 fieldModel.hasBitWidth() ?
510 optionalField.value().fit(modelBitWidth) :
511 optionalField.value());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900512 default:
513 // Should never be here.
Ray Milkey986a47a2018-01-25 11:38:51 -0800514 throw new IllegalArgumentException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900515 "Unrecognized match type " + fieldModel.matchType().name());
516 }
517 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200518 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900519 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
520 }
521 }
522}