blob: 835054985579a501d2eb3dd7077d20632770a4e2 [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;
Carmelo Cascone00a59962017-06-16 17:51:49 +090047import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
48import org.onosproject.net.pi.runtime.PiTableAction;
49import org.onosproject.net.pi.runtime.PiTableEntry;
Carmelo Cascone00a59962017-06-16 17:51:49 +090050import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080051import org.onosproject.net.pi.service.PiTranslationException;
Carmelo Cascone00a59962017-06-16 17:51:49 +090052import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54
55import java.util.Collection;
56import java.util.Map;
Carmelo Cascone00a59962017-06-16 17:51:49 +090057import 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 Cascone6af4e172018-06-15 16:01:30 +020074 public static final int MIN_PI_PRIORITY = 1;
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080075 private static final Logger log = LoggerFactory.getLogger(PiFlowRuleTranslatorImpl.class);
Carmelo Cascone00a59962017-06-16 17:51:49 +090076
Carmelo Cascone326ad2d2017-11-28 18:09:13 -080077 private PiFlowRuleTranslatorImpl() {
Carmelo Cascone00a59962017-06-16 17:51:49 +090078 // Hide constructor.
79 }
80
Carmelo Cascone87b9b392017-10-02 18:33:20 +020081 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -070082 * Returns a PI table entry equivalent to the given flow rule, for the given
83 * pipeconf and device.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020084 *
85 * @param rule flow rule
86 * @param pipeconf pipeconf
87 * @param device device
88 * @return PI table entry
89 * @throws PiTranslationException if the flow rule cannot be translated
90 */
91 static PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf, Device device)
92 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +090093
94 PiPipelineModel pipelineModel = pipeconf.pipelineModel();
95
96 // Retrieve interpreter, if any.
Carmelo Cascone87b9b392017-10-02 18:33:20 +020097 final PiPipelineInterpreter interpreter = getInterpreterOrNull(device, pipeconf);
98 // Get table model.
99 final PiTableId piTableId = translateTableId(rule.table(), interpreter);
100 final PiTableModel tableModel = getTableModel(piTableId, pipelineModel);
101 // Translate selector.
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700102 final PiMatchKey piMatchKey;
103 final boolean needPriority;
104 if (rule.selector().criteria().isEmpty()) {
105 piMatchKey = PiMatchKey.EMPTY;
106 needPriority = false;
107 } else {
108 final Collection<PiFieldMatch> fieldMatches = translateFieldMatches(
109 interpreter, rule.selector(), tableModel);
110 piMatchKey = PiMatchKey.builder()
111 .addFieldMatches(fieldMatches)
112 .build();
113 // FIXME: P4Runtime limit
114 // Need to ignore priority if no TCAM lookup match field
Yi Tseng667538d2018-06-22 02:19:23 +0800115 needPriority = tableModel.matchFields().stream()
116 .anyMatch(match -> match.matchType() == PiMatchType.TERNARY ||
117 match.matchType() == PiMatchType.RANGE);
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700118 }
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200119 // Translate treatment.
120 final PiTableAction piTableAction = translateTreatment(rule.treatment(), interpreter, piTableId, pipelineModel);
Carmelo Casconea62ac3d2017-08-30 03:19:00 +0200121
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200122 // Build PI entry.
123 final PiTableEntry.Builder tableEntryBuilder = PiTableEntry.builder();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900124
Carmelo Cascone00a59962017-06-16 17:51:49 +0900125 tableEntryBuilder
126 .forTable(piTableId)
Yi Tsengdf3eec52018-02-15 14:56:02 -0800127 .withMatchKey(piMatchKey);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900128
Yi Tsengd28936e2018-02-23 22:11:11 +0100129 if (piTableAction != null) {
130 tableEntryBuilder.withAction(piTableAction);
131 }
132
Carmelo Cascone4256bde2018-03-23 18:02:15 -0700133 if (needPriority) {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200134 // FIXME: move priority check to P4Runtime driver.
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700135 final int newPriority;
136 if (rule.priority() > MAX_PI_PRIORITY) {
137 log.warn("Flow rule priority too big, setting translated priority to max value {}: {}",
138 MAX_PI_PRIORITY, rule);
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200139 newPriority = MAX_PI_PRIORITY;
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700140 } else {
Carmelo Cascone6af4e172018-06-15 16:01:30 +0200141 newPriority = MIN_PI_PRIORITY + rule.priority();
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700142 }
143 tableEntryBuilder.withPriority(newPriority);
Yi Tseng02c4c572018-01-22 17:52:10 -0800144 }
145
Carmelo Cascone00a59962017-06-16 17:51:49 +0900146 if (!rule.isPermanent()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200147 if (tableModel.supportsAging()) {
Carmelo Casconef11a3212020-08-13 17:34:17 -0700148 tableEntryBuilder.withTimeout(rule.timeout());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900149 } else {
Carmelo Casconee5b28722018-06-22 17:28:28 +0200150 log.debug("Flow rule is temporary, but table '{}' doesn't support " +
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700151 "aging, translating to permanent.", tableModel.id());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900152 }
153
154 }
155
156 return tableEntryBuilder.build();
157 }
158
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200159
160 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700161 * Returns a PI action equivalent to the given treatment, optionally using
162 * the given interpreter. This method also checks that the produced PI table
163 * action is suitable for the given table ID and pipeline model. If
164 * suitable, the returned action instance will have parameters well-sized,
165 * according to the table model.
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200166 *
167 * @param treatment traffic treatment
168 * @param interpreter interpreter
169 * @param tableId PI table ID
170 * @param pipelineModel pipeline model
171 * @return PI table action
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700172 * @throws PiTranslationException if the treatment cannot be translated or
173 * if the PI action is not suitable for the
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200174 * given pipeline model
175 */
176 static PiTableAction translateTreatment(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
177 PiTableId tableId, PiPipelineModel pipelineModel)
178 throws PiTranslationException {
179 PiTableModel tableModel = getTableModel(tableId, pipelineModel);
180 return typeCheckAction(buildAction(treatment, interpreter, tableId), tableModel);
181 }
182
183 private static PiTableModel getTableModel(PiTableId piTableId, PiPipelineModel pipelineModel)
184 throws PiTranslationException {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800185 return pipelineModel.table(piTableId)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200186 .orElseThrow(() -> new PiTranslationException(format(
187 "Not such a table in pipeline model: %s", piTableId)));
188 }
189
Carmelo Cascone00a59962017-06-16 17:51:49 +0900190 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700191 * Builds a PI action out of the given treatment, optionally using the given
192 * interpreter.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900193 */
Yi Tseng82512da2017-08-16 19:46:36 -0700194 private static PiTableAction buildAction(TrafficTreatment treatment, PiPipelineInterpreter interpreter,
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200195 PiTableId tableId)
196 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900197
198 PiTableAction piTableAction = null;
199
200 // If treatment has only one instruction of type PiInstruction, use that.
201 for (Instruction inst : treatment.allInstructions()) {
202 if (inst.type() == Instruction.Type.PROTOCOL_INDEPENDENT) {
203 if (treatment.allInstructions().size() == 1) {
204 piTableAction = ((PiInstruction) inst).action();
205 } else {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200206 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900207 "Unable to translate treatment, found multiple instructions " +
208 "of which one is protocol-independent: %s", treatment));
209 }
210 }
211 }
212
213 if (piTableAction == null && interpreter != null) {
214 // No PiInstruction, use interpreter to build action.
215 try {
Carmelo Casconef3a1a382017-07-27 12:04:39 -0400216 piTableAction = interpreter.mapTreatment(treatment, tableId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900217 } catch (PiPipelineInterpreter.PiInterpreterException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200218 throw new PiTranslationException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900219 "Interpreter was unable to translate treatment. " + e.getMessage());
220 }
221 }
222
Yi Tseng82512da2017-08-16 19:46:36 -0700223 return piTableAction;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900224 }
225
Yi Tseng82512da2017-08-16 19:46:36 -0700226 private static PiTableAction typeCheckAction(PiTableAction piTableAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200227 throws PiTranslationException {
Yi Tsengd28936e2018-02-23 22:11:11 +0100228 if (piTableAction == null) {
229 // skip check if null
230 return null;
231 }
Yi Tseng82512da2017-08-16 19:46:36 -0700232 switch (piTableAction.type()) {
233 case ACTION:
234 return checkPiAction((PiAction) piTableAction, table);
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800235 case ACTION_PROFILE_GROUP_ID:
236 case ACTION_PROFILE_MEMBER_ID:
237 if (!table.tableType().equals(PiTableType.INDIRECT)) {
238 throw new PiTranslationException(format(
239 "action is indirect of type '%s', but table '%s' is of type '%s'",
240 piTableAction.type(), table.id(), table.tableType()));
241 }
242 if (piTableAction.type().equals(PiTableAction.Type.ACTION_PROFILE_GROUP_ID)
243 && (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 }
Yi Tseng82512da2017-08-16 19:46:36 -0700249 return piTableAction;
Carmelo Cascone99c59db2019-01-17 15:39:35 -0800250 default:
251 throw new PiTranslationException(format(
252 "Unknown table action type %s", piTableAction.type()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900253
Yi Tseng82512da2017-08-16 19:46:36 -0700254 }
255 }
256
257 private static PiTableAction checkPiAction(PiAction piAction, PiTableModel table)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200258 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900259 // Table supports this action?
Carmelo Cascone87892e22017-11-13 16:01:29 -0800260 PiActionModel actionModel = table.action(piAction.id()).orElseThrow(
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200261 () -> new PiTranslationException(format("Not such action '%s' for table '%s'",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800262 piAction.id(), table.id())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900263
264 // Is the number of runtime parameters correct?
265 if (actionModel.params().size() != piAction.parameters().size()) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200266 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900267 "Wrong number of runtime parameters for action '%s', expected %d but found %d",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800268 actionModel.id(), actionModel.params().size(), piAction.parameters().size()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900269 }
270
271 // Forge a new action instance with well-sized parameters.
272 // The same comment as in typeCheckFieldMatch() about duplicating field match instances applies here.
273 PiAction.Builder newActionBuilder = PiAction.builder().withId(piAction.id());
274 for (PiActionParam param : piAction.parameters()) {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800275 PiActionParamModel paramModel = actionModel.param(param.id())
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200276 .orElseThrow(() -> new PiTranslationException(format(
Carmelo Cascone87892e22017-11-13 16:01:29 -0800277 "Not such parameter '%s' for action '%s'", param.id(), actionModel)));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900278 try {
279 newActionBuilder.withParameter(new PiActionParam(param.id(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100280 paramModel.hasBitWidth() ?
281 param.value().fit(paramModel.bitWidth()) :
282 param.value()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900283 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200284 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900285 "Size mismatch for parameter '%s' of action '%s': %s",
286 param.id(), piAction.id(), e.getMessage()));
287 }
288 }
289
290 return newActionBuilder.build();
291 }
292
293 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700294 * Builds a collection of PI field matches out of the given selector,
295 * optionally using the given interpreter. The field matches returned are
296 * guaranteed to be compatible for the given table model.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900297 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200298 private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
299 TrafficSelector selector, PiTableModel tableModel)
300 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900301
Carmelo Cascone87892e22017-11-13 16:01:29 -0800302 Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900303
304 // 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 -0800305 Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
Carmelo Cascone00a59962017-06-16 17:51:49 +0900306 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
307 .map(c -> (PiCriterion) c)
308 .findFirst()
309 .map(PiCriterion::fieldMatches)
310 .map(c -> {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800311 Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900312 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
313 return fieldMap;
314 })
315 .orElse(Maps.newHashMap());
316
317 Set<Criterion> translatedCriteria = Sets.newHashSet();
318 Set<Criterion> ignoredCriteria = Sets.newHashSet();
Carmelo Cascone87892e22017-11-13 16:01:29 -0800319 Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
320 Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900321
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700322
323 Map<PiMatchFieldId, Criterion> criterionMap = Maps.newHashMap();
324 if (interpreter != null) {
325 // NOTE: if two criterion types map to the same match field ID, and
326 // those two criterion types are present in the selector, this won't
327 // work. This is unlikely to happen since those cases should be
328 // mutually exclusive:
329 // e.g. ICMPV6_TYPE -> metadata.my_normalized_icmp_type
330 // ICMPV4_TYPE -> metadata.my_normalized_icmp_type
331 // A packet can be either ICMPv6 or ICMPv4 but not both.
332 selector.criteria()
333 .stream()
334 .map(Criterion::type)
335 .filter(t -> t != PROTOCOL_INDEPENDENT)
336 .forEach(t -> {
337 PiMatchFieldId mfid = interpreter.mapCriterionType(t)
338 .orElse(null);
339 if (mfid != null) {
340 if (criterionMap.containsKey(mfid)) {
341 log.warn("Detected criterion mapping " +
342 "conflict for PiMatchFieldId {}",
343 mfid);
344 }
345 criterionMap.put(mfid, selector.getCriterion(t));
346 }
347 });
348 }
349
Carmelo Cascone87892e22017-11-13 16:01:29 -0800350 for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900351
Carmelo Cascone87892e22017-11-13 16:01:29 -0800352 PiMatchFieldId fieldId = fieldModel.id();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900353
Carmelo Cascone87892e22017-11-13 16:01:29 -0800354 int bitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900355
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700356 Criterion criterion = criterionMap.get(fieldId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900357
358 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
359 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
Carmelo Casconef11a3212020-08-13 17:34:17 -0700360 // Can ignore if match is ternary-like, as it means "don't care".
Carmelo Cascone00a59962017-06-16 17:51:49 +0900361 switch (fieldModel.matchType()) {
362 case TERNARY:
Carmelo Cascone00a59962017-06-16 17:51:49 +0900363 case LPM:
Carmelo Casconef11a3212020-08-13 17:34:17 -0700364 case RANGE:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700365 // Skip field.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900366 break;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900367 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200368 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900369 "No value found for required match field '%s'", fieldId));
370 }
371 // Next field.
372 continue;
373 }
374
375 PiFieldMatch fieldMatch = null;
376
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100377 // TODO: we currently do not support fields with arbitrary bit width
378 if (criterion != null && fieldModel.hasBitWidth()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900379 // Criterion mapping is possible for this field id.
380 try {
381 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
382 translatedCriteria.add(criterion);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200383 } catch (PiTranslationException ex) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900384 // Ignore exception if the same field was found in PiCriterion.
385 if (piCriterionFields.containsKey(fieldId)) {
386 ignoredCriteria.add(criterion);
387 } else {
388 throw ex;
389 }
390 }
391 }
392
393 if (piCriterionFields.containsKey(fieldId)) {
394 // Field was found in PiCriterion.
395 if (fieldMatch != null) {
396 // Field was already translated from other criterion.
397 // Throw exception only if we are trying to match on different values of the same field...
398 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200399 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900400 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
401 "what found in PiCriterion.", fieldId, criterion.type()));
402 }
403 ignoredPiCriterionFields.add(fieldId);
404 } else {
405 fieldMatch = piCriterionFields.get(fieldId);
406 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
407 usedPiCriterionFields.add(fieldId);
408 }
409 }
410
411 fieldMatches.put(fieldId, fieldMatch);
412 }
413
414 // Check if all criteria have been translated.
415 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
416 selector.criteria().stream()
417 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
418 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
419 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
420 if (skippedCriteriaJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200421 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900422 "The following criteria cannot be translated for table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800423 tableModel.id(), skippedCriteriaJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900424 }
425
426 // Check if all fields found in PiCriterion have been used.
427 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
428 piCriterionFields.keySet().stream()
429 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
430 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
431 if (skippedPiFieldsJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200432 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900433 "The following PiCriterion field matches are not supported in table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800434 tableModel.id(), skippedPiFieldsJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900435 }
436
437 return fieldMatches.values();
438 }
439
Carmelo Cascone87892e22017-11-13 16:01:29 -0800440 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200441 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900442
443 // Check parameter type and size
444 if (!fieldModel.matchType().equals(fieldMatch.type())) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200445 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900446 "Wrong match type for field '%s', expected %s, but found %s",
447 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
448 }
449
Carmelo Cascone87892e22017-11-13 16:01:29 -0800450 int modelBitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900451
452 /*
453 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
454 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 -0700455 model. We also normalize ternary (and LPM) field matches by setting to 0 unused bits, as required by P4Runtime.
456
457 These operations are expensive when performed for each field match of each flow rule, but should be
Carmelo Cascone00a59962017-06-16 17:51:49 +0900458 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
459 */
460
461 try {
462 switch (fieldModel.matchType()) {
463 case EXACT:
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100464 // TODO: arbitrary bit width is supported only for the EXACT match case.
465 PiExactFieldMatch exactField = (PiExactFieldMatch) fieldMatch;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900466 return new PiExactFieldMatch(fieldMatch.fieldId(),
Daniele Moro5c82b0f2020-12-07 20:56:30 +0100467 fieldModel.hasBitWidth() ?
468 exactField.value().fit(modelBitWidth) :
469 exactField.value());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900470 case TERNARY:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700471 PiTernaryFieldMatch ternField = (PiTernaryFieldMatch) fieldMatch;
472 ImmutableByteSequence ternMask = ternField.mask().fit(modelBitWidth);
473 ImmutableByteSequence ternValue = ternField.value()
474 .fit(modelBitWidth)
475 .bitwiseAnd(ternMask);
476 return new PiTernaryFieldMatch(fieldMatch.fieldId(), ternValue, ternMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900477 case LPM:
478 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
479 if (lpmfield.prefixLength() > modelBitWidth) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200480 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900481 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
482 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
483 }
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700484 ImmutableByteSequence lpmValue = lpmfield.value()
Carmelo Cascone181f3f42018-04-11 17:37:53 -0700485 .fit(modelBitWidth);
486 ImmutableByteSequence lpmMask = prefixOnes(lpmValue.size(),
487 lpmfield.prefixLength());
488 lpmValue = lpmValue.bitwiseAnd(lpmMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900489 return new PiLpmFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700490 lpmValue, lpmfield.prefixLength());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900491 case RANGE:
492 return new PiRangeFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700493 ((PiRangeFieldMatch) fieldMatch).lowValue().fit(modelBitWidth),
494 ((PiRangeFieldMatch) fieldMatch).highValue().fit(modelBitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900495 default:
496 // Should never be here.
Ray Milkey986a47a2018-01-25 11:38:51 -0800497 throw new IllegalArgumentException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900498 "Unrecognized match type " + fieldModel.matchType().name());
499 }
500 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200501 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900502 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
503 }
504 }
505}