blob: 2b4b0f3f45ca4ff27bb95791aea7c5b55d72e098 [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;
Daniele Morod900fe42021-02-11 16:12:57 +010043import org.onosproject.net.pi.runtime.PiActionSet;
Carmelo Cascone00a59962017-06-16 17:51:49 +090044import org.onosproject.net.pi.runtime.PiExactFieldMatch;
45import org.onosproject.net.pi.runtime.PiFieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +090046import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
Carmelo Cascone0e896a02017-07-31 07:22:27 +020047import org.onosproject.net.pi.runtime.PiMatchKey;
Daniele Moroc6f2f7f2020-12-18 10:55:57 +010048import org.onosproject.net.pi.runtime.PiOptionalFieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +090049import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
50import org.onosproject.net.pi.runtime.PiTableAction;
51import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone00a59962017-06-16 17:51:49 +090052import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080053import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone00a59962017-06-16 17:51:49 +090054import org.slf4j.Logger;
55import org.slf4j.LoggerFactory;
56
57import java.util.Collection;
58import java.util.Map;
Carmelo Cascone00a59962017-06-16 17:51:49 +090059import java.util.Set;
60import java.util.StringJoiner;
61
62import static java.lang.String.format;
63import static org.onlab.util.ImmutableByteSequence.ByteSequenceTrimException;
Carmelo Cascone81929aa2018-04-07 01:38:55 -070064import static org.onlab.util.ImmutableByteSequence.prefixOnes;
Carmelo Cascone00a59962017-06-16 17:51:49 +090065import static org.onosproject.net.flow.criteria.Criterion.Type.PROTOCOL_INDEPENDENT;
66import static org.onosproject.net.pi.impl.CriterionTranslatorHelper.translateCriterion;
Carmelo Cascone87b9b392017-10-02 18:33:20 +020067import static org.onosproject.net.pi.impl.PiUtils.getInterpreterOrNull;
68import static org.onosproject.net.pi.impl.PiUtils.translateTableId;
Carmelo Cascone00a59962017-06-16 17:51:49 +090069
70/**
71 * Implementation of flow rule translation logic.
72 */
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080073final class PiFlowRuleTranslatorImpl {
Carmelo Cascone00a59962017-06-16 17:51:49 +090074
Carmelo Casconea3d811c2017-08-22 01:03:45 +020075 public static final int MAX_PI_PRIORITY = (int) Math.pow(2, 24);
Carmelo Cascone6af4e172018-06-15 16:01:30 +020076 public static final int MIN_PI_PRIORITY = 1;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080077 private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslatorImpl.class);
Carmelo Cascone00a59962017-06-16 17:51:49 +090078
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080079 private PiFlowRuleTranslatorImpl() {
Carmelo Cascone00a59962017-06-16 17:51:49 +090080 // Hide constructor.
81 }
82
Carmelo Cascone87b9b392017-10-02 18:33:20 +020083 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -070084 * Returns a PI table entry equivalent to the given flow rule, for the given
85 * pipeconf and device.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020086 *
87 * @param rule flow rule
88 * @param pipeconf pipeconf
89 * @param device device
90 * @return PI table entry
91 * @throws PiTranslationException if the flow rule cannot be translated
92 */
93 static PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf, Device device)
94 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +090095
96 PiPipelineModel pipelineModel = pipeconf.pipelineModel();
97
98 // Retrieve interpreter, if any.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020099 final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
100 // Get table model.
101 final PiTableId piTableId = translateTableId(rule.table(), interpreter);
102 final PiTableModel tableModel = getTableModel(piTableId, pipelineModel);
103 // Translate selector.
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700104 final PiMatchKey piMatchKey;
105 final boolean needPriority;
106 if (rule.selector().criteria().isEmpty()) {
107 piMatchKey = PiMatchKey.EMPTY;
108 needPriority = false;
109 } else {
110 final Collection<PiFieldMatch> fieldMatches = translateFieldMatches(
111 interpreter, rule.selector(), tableModel);
112 piMatchKey = PiMatchKey.builder()
113 .addFieldMatches(fieldMatches)
114 .build();
115 // FIXME: P4Runtime limit
116 // Need to ignore priority if no TCAM lookup match field
Yi Tseng667538d2018-06-22 02:19:23 +0800117 needPriority = tableModel.matchFields().stream()
118 .anyMatch(match -> match.matchType() == PiMatchType.TERNARY ||
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100119 match.matchType() == PiMatchType.RANGE ||
120 match.matchType() == PiMatchType.OPTIONAL);
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700121 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200122 // Translate treatment.
123 final PiTableAction piTableAction = translateTreatment(rule.treatment(), interpreter, piTableId, pipelineModel);
Carmelo Casconea62ac3d2017-08-30 03:19:00 +0200124
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200125 // Build PI entry.
126 final PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900127
Carmelo Cascone00a59962017-06-16 17:51:49 +0900128 tableEntryBuilder
129 .forTable(piTableId)
Yi Tsengdf3eec52018-02-15 14:56:02 -0800130 .withMatchKey(piMatchKey);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900131
Yi Tsengd28936e2018-02-23 22:11:11 +0100132 if (piTableAction != null) {
133 tableEntryBuilder.withAction(piTableAction);
134 }
135
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700136 if (needPriority) {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200137 // FIXME: move priority check to P4Runtime driver.
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700138 final int newPriority;
139 if (rule.priority() > MAX_PI_PRIORITY) {
140 log.warn("Flow rule priority too big, setting translated priority to max value {}: {}",
141 MAX_PI_PRIORITY, rule);
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200142 newPriority = MAX_PI_PRIORITY;
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700143 } else {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200144 newPriority = MIN_PI_PRIORITY + rule.priority();
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700145 }
146 tableEntryBuilder.withPriority(newPriority);
Yi Tseng02c4c572018-01-22 17:52:10 -0800147 }
148
Carmelo Cascone00a59962017-06-16 17:51:49 +0900149 if (!rule.isPermanent()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200150 if (tableModel.supportsAging()) {
Carmelo Casconef11a3212020-08-13 17:34:17 -0700151 tableEntryBuilder.withTimeout(rule.timeout());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900152 } else {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200153 log.debug("Flow rule is temporary, but table '{}' doesn't support " +
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700154 "aging, translating to permanent.", tableModel.id());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900155 }
156
157 }
158
159 return tableEntryBuilder.build();
160 }
161
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200162
163 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700164 * Returns a PI action equivalent to the given treatment, optionally using
165 * the given interpreter. This method also checks that the produced PI table
166 * action is suitable for the given table ID and pipeline model. If
167 * suitable, the returned action instance will have parameters well-sized,
168 * according to the table model.
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200169 *
170 * @param treatment traffic treatment
171 * @param interpreter interpreter
172 * @param tableId PI table ID
173 * @param pipelineModel pipeline model
174 * @return PI table action
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700175 * @throws PiTranslationException if the treatment cannot be translated or
176 * if the PI action is not suitable for the
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200177 * given pipeline model
178 */
179 static PiTableAction translateTreatment(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
180 PiTableId tableId, PiPipelineModel pipelineModel)
181 throws PiTranslationException {
182 PiTableModel tableModel = getTableModel(tableId, pipelineModel);
183 return typeCheckAction(buildAction(treatment, interpreter, tableId), tableModel);
184 }
185
186 private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
187 throws PiTranslationException {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800188 return pipelineModel.table(piTableId)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200189 .orElseThrow(() -> new PiTranslationException(format(
190 "Not such a table in pipeline model: %s", piTableId)));
191 }
192
Carmelo Cascone00a59962017-06-16 17:51:49 +0900193 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700194 * Builds a PI action out of the given treatment, optionally using the given
195 * interpreter.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900196 */
Yi Tseng82512da2017-08-16 19:46:36 -0700197 private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200198 PiTableId tableId)
199 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900200
201 PiTableAction piTableAction = null;
202
203 // If treatment has only one instruction of type PiInstruction, use that.
204 for (Instruction inst : treatment.allInstructions()) {
205 if (inst.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
206 if (treatment.allInstructions().size() == 1) {
207 piTableAction = ((PiInstruction) inst).action();
208 } else {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200209 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900210 "Unable to translate treatment, found multiple instructions " +
211 "of which one is protocol-independent: %s", treatment));
212 }
213 }
214 }
215
216 if (piTableAction == null && interpreter != null) {
217 // No PiInstruction, use interpreter to build action.
218 try {
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400219 piTableAction = interpreter.mapTreatment(treatment, tableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900220 } catch (PiPipelineInterpreter.PiInterpreterException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200221 throw new PiTranslationException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900222 "Interpreter was unable to translate treatment. " + e.getMessage());
223 }
224 }
225
Yi Tseng82512da2017-08-16 19:46:36 -0700226 return piTableAction;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900227 }
228
Yi Tseng82512da2017-08-16 19:46:36 -0700229 private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200230 throws PiTranslationException {
Yi Tsengd28936e2018-02-23 22:11:11 +0100231 if (piTableAction == null) {
232 // skip check if null
233 return null;
234 }
Yi Tseng82512da2017-08-16 19:46:36 -0700235 switch (piTableAction.type()) {
236 case ACTION:
237 return checkPiAction((PiAction) piTableAction, table);
Daniele Morod900fe42021-02-11 16:12:57 +0100238 case ACTION_SET:
239 for (var actProfAct : ((PiActionSet) piTableAction).actions()) {
240 checkPiAction(actProfAct.action(), table);
241 }
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800242 case ACTION_PROFILE_GROUP_ID:
Daniele Morod900fe42021-02-11 16:12:57 +0100243 if (table.actionProfile() == null || !table.actionProfile().hasSelector()) {
244 throw new PiTranslationException(format(
245 "action is of type '%s', but table '%s' does not" +
246 "implement an action profile with dynamic selection",
247 piTableAction.type(), table.id()));
248 }
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800249 case ACTION_PROFILE_MEMBER_ID:
250 if (!table.tableType().equals(PiTableType.INDIRECT)) {
251 throw new PiTranslationException(format(
252 "action is indirect of type '%s', but table '%s' is of type '%s'",
253 piTableAction.type(), table.id(), table.tableType()));
254 }
Daniele Morod900fe42021-02-11 16:12:57 +0100255 if (!piTableAction.type().equals(PiTableAction.Type.ACTION_SET) &&
256 table.oneShotOnly()) {
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800257 throw new PiTranslationException(format(
Daniele Morod900fe42021-02-11 16:12:57 +0100258 "table '%s' supports only one shot programming", table.id()
259 ));
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800260 }
Yi Tseng82512da2017-08-16 19:46:36 -0700261 return piTableAction;
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800262 default:
263 throw new PiTranslationException(format(
264 "Unknown table action type %s", piTableAction.type()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900265
Yi Tseng82512da2017-08-16 19:46:36 -0700266 }
267 }
268
269 private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200270 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900271 // Table supports this action?
Carmelo Cascone87892e22017-11-13 16:01:29 -0800272 PiActionModel actionModel = table.action(piAction.id()).orElseThrow(
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200273 () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800274 piAction.id(), table.id())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900275
276 // Is the number of runtime parameters correct?
277 if (actionModel.params().size() != piAction.parameters().size()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200278 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900279 "Wrong number of runtime parameters for action '%s', expected %d but found %d",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800280 actionModel.id(), actionModel.params().size(), piAction.parameters().size()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900281 }
282
283 // Forge a new action instance with well-sized parameters.
284 // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
285 PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
286 for (PiActionParam param : piAction.parameters()) {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800287 PiActionParamModel paramModel = actionModel.param(param.id())
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200288 .orElseThrow(() -> new PiTranslationException(format(
Carmelo Cascone87892e22017-11-13 16:01:29 -0800289 "Not such parameter '%s' for action '%s'", param.id(), actionModel)));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900290 try {
291 newActionBuilder.withParameter(new PiActionParam(param.id(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100292 paramModel.hasBitWidth() ?
293 param.value().fit(paramModel.bitWidth()) :
294 param.value()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900295 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200296 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900297 "Size mismatch for parameter '%s' of action '%s': %s",
298 param.id(), piAction.id(), e.getMessage()));
299 }
300 }
301
302 return newActionBuilder.build();
303 }
304
305 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700306 * Builds a collection of PI field matches out of the given selector,
307 * optionally using the given interpreter. The field matches returned are
308 * guaranteed to be compatible for the given table model.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900309 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200310 private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
311 TrafficSelector selector, PiTableModel tableModel)
312 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900313
Carmelo Cascone87892e22017-11-13 16:01:29 -0800314 Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900315
316 // 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 -0800317 Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
Carmelo Cascone00a59962017-06-16 17:51:49 +0900318 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
319 .map(c -> (PiCriterion) c)
320 .findFirst()
321 .map(PiCriterion::fieldMatches)
322 .map(c -> {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800323 Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900324 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
325 return fieldMap;
326 })
327 .orElse(Maps.newHashMap());
328
329 Set<Criterion> translatedCriteria = Sets.newHashSet();
330 Set<Criterion> ignoredCriteria = Sets.newHashSet();
Carmelo Cascone87892e22017-11-13 16:01:29 -0800331 Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
332 Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900333
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700334
335 Map<PiMatchFieldId, Criterion> criterionMap = Maps.newHashMap();
336 if (interpreter != null) {
337 // NOTE: if two criterion types map to the same match field ID, and
338 // those two criterion types are present in the selector, this won't
339 // work. This is unlikely to happen since those cases should be
340 // mutually exclusive:
341 // e.g. ICMPV6_TYPE -> metadata.my_normalized_icmp_type
342 // ICMPV4_TYPE -> metadata.my_normalized_icmp_type
343 // A packet can be either ICMPv6 or ICMPv4 but not both.
344 selector.criteria()
345 .stream()
346 .map(Criterion::type)
347 .filter(t -> t != PROTOCOL_INDEPENDENT)
348 .forEach(t -> {
349 PiMatchFieldId mfid = interpreter.mapCriterionType(t)
350 .orElse(null);
351 if (mfid != null) {
352 if (criterionMap.containsKey(mfid)) {
353 log.warn("Detected criterion mapping " +
354 "conflict for PiMatchFieldId {}",
355 mfid);
356 }
357 criterionMap.put(mfid, selector.getCriterion(t));
358 }
359 });
360 }
361
Carmelo Cascone87892e22017-11-13 16:01:29 -0800362 for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900363
Carmelo Cascone87892e22017-11-13 16:01:29 -0800364 PiMatchFieldId fieldId = fieldModel.id();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900365
Carmelo Cascone87892e22017-11-13 16:01:29 -0800366 int bitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900367
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700368 Criterion criterion = criterionMap.get(fieldId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900369
370 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
371 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
Carmelo Casconef11a3212020-08-13 17:34:17 -0700372 // Can ignore if match is ternary-like, as it means "don't care".
Carmelo Cascone00a59962017-06-16 17:51:49 +0900373 switch (fieldModel.matchType()) {
374 case TERNARY:
Carmelo Cascone00a59962017-06-16 17:51:49 +0900375 case LPM:
Carmelo Casconef11a3212020-08-13 17:34:17 -0700376 case RANGE:
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100377 case OPTIONAL:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700378 // Skip field.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900379 break;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900380 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200381 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900382 "No value found for required match field '%s'", fieldId));
383 }
384 // Next field.
385 continue;
386 }
387
388 PiFieldMatch fieldMatch = null;
389
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100390 // TODO: we currently do not support fields with arbitrary bit width
391 if (criterion != null && fieldModel.hasBitWidth()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900392 // Criterion mapping is possible for this field id.
393 try {
394 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
395 translatedCriteria.add(criterion);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200396 } catch (PiTranslationException ex) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900397 // Ignore exception if the same field was found in PiCriterion.
398 if (piCriterionFields.containsKey(fieldId)) {
399 ignoredCriteria.add(criterion);
400 } else {
401 throw ex;
402 }
403 }
404 }
405
406 if (piCriterionFields.containsKey(fieldId)) {
407 // Field was found in PiCriterion.
408 if (fieldMatch != null) {
409 // Field was already translated from other criterion.
410 // Throw exception only if we are trying to match on different values of the same field...
411 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200412 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900413 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
414 "what found in PiCriterion.", fieldId, criterion.type()));
415 }
416 ignoredPiCriterionFields.add(fieldId);
417 } else {
418 fieldMatch = piCriterionFields.get(fieldId);
419 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
420 usedPiCriterionFields.add(fieldId);
421 }
422 }
423
424 fieldMatches.put(fieldId, fieldMatch);
425 }
426
427 // Check if all criteria have been translated.
428 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
429 selector.criteria().stream()
430 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
431 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
432 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
433 if (skippedCriteriaJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200434 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900435 "The following criteria cannot be translated for table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800436 tableModel.id(), skippedCriteriaJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900437 }
438
439 // Check if all fields found in PiCriterion have been used.
440 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
441 piCriterionFields.keySet().stream()
442 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
443 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
444 if (skippedPiFieldsJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200445 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900446 "The following PiCriterion field matches are not supported in table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800447 tableModel.id(), skippedPiFieldsJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900448 }
449
450 return fieldMatches.values();
451 }
452
Carmelo Cascone87892e22017-11-13 16:01:29 -0800453 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200454 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900455
456 // Check parameter type and size
457 if (!fieldModel.matchType().equals(fieldMatch.type())) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200458 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900459 "Wrong match type for field '%s', expected %s, but found %s",
460 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
461 }
462
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100463 // Check if the arbitrary bit width is supported
464 if (!fieldModel.hasBitWidth() &&
465 !fieldModel.matchType().equals(PiMatchType.EXACT) &&
466 !fieldModel.matchType().equals(PiMatchType.OPTIONAL)) {
467 throw new PiTranslationException(format(
468 "Arbitrary bit width for field '%s' and match type %s is not supported",
469 fieldMatch.fieldId(), fieldModel.matchType().name()));
470 }
471
Carmelo Cascone87892e22017-11-13 16:01:29 -0800472 int modelBitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900473
474 /*
475 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
476 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 -0700477 model. We also normalize ternary (and LPM) field matches by setting to 0 unused bits, as required by P4Runtime.
478
479 These operations are expensive when performed for each field match of each flow rule, but should be
Carmelo Cascone00a59962017-06-16 17:51:49 +0900480 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
481 */
482
483 try {
484 switch (fieldModel.matchType()) {
485 case EXACT:
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100486 PiExactFieldMatch exactField = (PiExactFieldMatch) fieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900487 return new PiExactFieldMatch(fieldMatch.fieldId(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100488 fieldModel.hasBitWidth() ?
489 exactField.value().fit(modelBitWidth) :
490 exactField.value());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900491 case TERNARY:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700492 PiTernaryFieldMatch ternField = (PiTernaryFieldMatch) fieldMatch;
493 ImmutableByteSequence ternMask = ternField.mask().fit(modelBitWidth);
494 ImmutableByteSequence ternValue = ternField.value()
495 .fit(modelBitWidth)
496 .bitwiseAnd(ternMask);
497 return new PiTernaryFieldMatch(fieldMatch.fieldId(), ternValue, ternMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900498 case LPM:
499 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
500 if (lpmfield.prefixLength() > modelBitWidth) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200501 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900502 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
503 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
504 }
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700505 ImmutableByteSequence lpmValue = lpmfield.value()
Carmelo Cascone181f3f42018-04-11 17:37:53 -0700506 .fit(modelBitWidth);
507 ImmutableByteSequence lpmMask = prefixOnes(lpmValue.size(),
508 lpmfield.prefixLength());
509 lpmValue = lpmValue.bitwiseAnd(lpmMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900510 return new PiLpmFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700511 lpmValue, lpmfield.prefixLength());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900512 case RANGE:
513 return new PiRangeFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700514 ((PiRangeFieldMatch) fieldMatch).lowValue().fit(modelBitWidth),
515 ((PiRangeFieldMatch) fieldMatch).highValue().fit(modelBitWidth));
Daniele Moroc6f2f7f2020-12-18 10:55:57 +0100516 case OPTIONAL:
517 PiOptionalFieldMatch optionalField = (PiOptionalFieldMatch) fieldMatch;
518 return new PiOptionalFieldMatch(fieldMatch.fieldId(),
519 fieldModel.hasBitWidth() ?
520 optionalField.value().fit(modelBitWidth) :
521 optionalField.value());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900522 default:
523 // Should never be here.
Ray Milkey986a47a2018-01-25 11:38:51 -0800524 throw new IllegalArgumentException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900525 "Unrecognized match type " + fieldModel.matchType().name());
526 }
527 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200528 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900529 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
530 }
531 }
532}