blob: 364742750fda676c39c9b8da79abe42038a02911 [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 Cascone4a0b7f62020-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(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700280 param.value().fit(paramModel.bitWidth())));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900281 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200282 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900283 "Size mismatch for parameter '%s' of action '%s': %s",
284 param.id(), piAction.id(), e.getMessage()));
285 }
286 }
287
288 return newActionBuilder.build();
289 }
290
291 /**
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700292 * Builds a collection of PI field matches out of the given selector,
293 * optionally using the given interpreter. The field matches returned are
294 * guaranteed to be compatible for the given table model.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900295 */
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200296 private static Collection<PiFieldMatch> translateFieldMatches(PiPipelineInterpreter interpreter,
297 TrafficSelector selector, PiTableModel tableModel)
298 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900299
Carmelo Cascone87892e22017-11-13 16:01:29 -0800300 Map<PiMatchFieldId, PiFieldMatch> fieldMatches = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900301
302 // 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 -0800303 Map<PiMatchFieldId, PiFieldMatch> piCriterionFields = selector.criteria().stream()
Carmelo Cascone00a59962017-06-16 17:51:49 +0900304 .filter(c -> c.type().equals(PROTOCOL_INDEPENDENT))
305 .map(c -> (PiCriterion) c)
306 .findFirst()
307 .map(PiCriterion::fieldMatches)
308 .map(c -> {
Carmelo Cascone87892e22017-11-13 16:01:29 -0800309 Map<PiMatchFieldId, PiFieldMatch> fieldMap = Maps.newHashMap();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900310 c.forEach(fieldMatch -> fieldMap.put(fieldMatch.fieldId(), fieldMatch));
311 return fieldMap;
312 })
313 .orElse(Maps.newHashMap());
314
315 Set<Criterion> translatedCriteria = Sets.newHashSet();
316 Set<Criterion> ignoredCriteria = Sets.newHashSet();
Carmelo Cascone87892e22017-11-13 16:01:29 -0800317 Set<PiMatchFieldId> usedPiCriterionFields = Sets.newHashSet();
318 Set<PiMatchFieldId> ignoredPiCriterionFields = Sets.newHashSet();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900319
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700320
321 Map<PiMatchFieldId, Criterion> criterionMap = Maps.newHashMap();
322 if (interpreter != null) {
323 // NOTE: if two criterion types map to the same match field ID, and
324 // those two criterion types are present in the selector, this won't
325 // work. This is unlikely to happen since those cases should be
326 // mutually exclusive:
327 // e.g. ICMPV6_TYPE -> metadata.my_normalized_icmp_type
328 // ICMPV4_TYPE -> metadata.my_normalized_icmp_type
329 // A packet can be either ICMPv6 or ICMPv4 but not both.
330 selector.criteria()
331 .stream()
332 .map(Criterion::type)
333 .filter(t -> t != PROTOCOL_INDEPENDENT)
334 .forEach(t -> {
335 PiMatchFieldId mfid = interpreter.mapCriterionType(t)
336 .orElse(null);
337 if (mfid != null) {
338 if (criterionMap.containsKey(mfid)) {
339 log.warn("Detected criterion mapping " +
340 "conflict for PiMatchFieldId {}",
341 mfid);
342 }
343 criterionMap.put(mfid, selector.getCriterion(t));
344 }
345 });
346 }
347
Carmelo Cascone87892e22017-11-13 16:01:29 -0800348 for (PiMatchFieldModel fieldModel : tableModel.matchFields()) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900349
Carmelo Cascone87892e22017-11-13 16:01:29 -0800350 PiMatchFieldId fieldId = fieldModel.id();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900351
Carmelo Cascone87892e22017-11-13 16:01:29 -0800352 int bitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900353
Carmelo Cascone33f36a02019-04-17 20:05:21 -0700354 Criterion criterion = criterionMap.get(fieldId);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900355
356 if (!piCriterionFields.containsKey(fieldId) && criterion == null) {
357 // Neither a field in PiCriterion is available nor a Criterion mapping is possible.
Carmelo Cascone4a0b7f62020-08-13 17:34:17 -0700358 // Can ignore if match is ternary-like, as it means "don't care".
Carmelo Cascone00a59962017-06-16 17:51:49 +0900359 switch (fieldModel.matchType()) {
360 case TERNARY:
Carmelo Cascone00a59962017-06-16 17:51:49 +0900361 case LPM:
Carmelo Cascone4a0b7f62020-08-13 17:34:17 -0700362 case RANGE:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700363 // Skip field.
Carmelo Cascone00a59962017-06-16 17:51:49 +0900364 break;
Carmelo Cascone00a59962017-06-16 17:51:49 +0900365 default:
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200366 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900367 "No value found for required match field '%s'", fieldId));
368 }
369 // Next field.
370 continue;
371 }
372
373 PiFieldMatch fieldMatch = null;
374
375 if (criterion != null) {
376 // Criterion mapping is possible for this field id.
377 try {
378 fieldMatch = translateCriterion(criterion, fieldId, fieldModel.matchType(), bitWidth);
379 translatedCriteria.add(criterion);
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200380 } catch (PiTranslationException ex) {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900381 // Ignore exception if the same field was found in PiCriterion.
382 if (piCriterionFields.containsKey(fieldId)) {
383 ignoredCriteria.add(criterion);
384 } else {
385 throw ex;
386 }
387 }
388 }
389
390 if (piCriterionFields.containsKey(fieldId)) {
391 // Field was found in PiCriterion.
392 if (fieldMatch != null) {
393 // Field was already translated from other criterion.
394 // Throw exception only if we are trying to match on different values of the same field...
395 if (!fieldMatch.equals(piCriterionFields.get(fieldId))) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200396 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900397 "Duplicate match field '%s': instance translated from criterion '%s' is different to " +
398 "what found in PiCriterion.", fieldId, criterion.type()));
399 }
400 ignoredPiCriterionFields.add(fieldId);
401 } else {
402 fieldMatch = piCriterionFields.get(fieldId);
403 fieldMatch = typeCheckFieldMatch(fieldMatch, fieldModel);
404 usedPiCriterionFields.add(fieldId);
405 }
406 }
407
408 fieldMatches.put(fieldId, fieldMatch);
409 }
410
411 // Check if all criteria have been translated.
412 StringJoiner skippedCriteriaJoiner = new StringJoiner(", ");
413 selector.criteria().stream()
414 .filter(c -> !c.type().equals(PROTOCOL_INDEPENDENT))
415 .filter(c -> !translatedCriteria.contains(c) && !ignoredCriteria.contains(c))
416 .forEach(c -> skippedCriteriaJoiner.add(c.type().name()));
417 if (skippedCriteriaJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200418 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900419 "The following criteria cannot be translated for table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800420 tableModel.id(), skippedCriteriaJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900421 }
422
423 // Check if all fields found in PiCriterion have been used.
424 StringJoiner skippedPiFieldsJoiner = new StringJoiner(", ");
425 piCriterionFields.keySet().stream()
426 .filter(k -> !usedPiCriterionFields.contains(k) && !ignoredPiCriterionFields.contains(k))
427 .forEach(k -> skippedPiFieldsJoiner.add(k.id()));
428 if (skippedPiFieldsJoiner.length() > 0) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200429 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900430 "The following PiCriterion field matches are not supported in table '%s': %s",
Carmelo Cascone87892e22017-11-13 16:01:29 -0800431 tableModel.id(), skippedPiFieldsJoiner.toString()));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900432 }
433
434 return fieldMatches.values();
435 }
436
Carmelo Cascone87892e22017-11-13 16:01:29 -0800437 private static PiFieldMatch typeCheckFieldMatch(PiFieldMatch fieldMatch, PiMatchFieldModel fieldModel)
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200438 throws PiTranslationException {
Carmelo Cascone00a59962017-06-16 17:51:49 +0900439
440 // Check parameter type and size
441 if (!fieldModel.matchType().equals(fieldMatch.type())) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200442 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900443 "Wrong match type for field '%s', expected %s, but found %s",
444 fieldMatch.fieldId(), fieldModel.matchType().name(), fieldMatch.type().name()));
445 }
446
Carmelo Cascone87892e22017-11-13 16:01:29 -0800447 int modelBitWidth = fieldModel.bitWidth();
Carmelo Cascone00a59962017-06-16 17:51:49 +0900448
449 /*
450 Here we try to be robust against wrong size fields with the goal of having PiCriterion independent of the
451 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 -0700452 model. We also normalize ternary (and LPM) field matches by setting to 0 unused bits, as required by P4Runtime.
453
454 These operations are expensive when performed for each field match of each flow rule, but should be
Carmelo Cascone00a59962017-06-16 17:51:49 +0900455 mitigated by the translation cache provided by PiFlowRuleTranslationServiceImpl.
456 */
457
458 try {
459 switch (fieldModel.matchType()) {
460 case EXACT:
461 return new PiExactFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700462 ((PiExactFieldMatch) fieldMatch).value().fit(modelBitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900463 case TERNARY:
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700464 PiTernaryFieldMatch ternField = (PiTernaryFieldMatch) fieldMatch;
465 ImmutableByteSequence ternMask = ternField.mask().fit(modelBitWidth);
466 ImmutableByteSequence ternValue = ternField.value()
467 .fit(modelBitWidth)
468 .bitwiseAnd(ternMask);
469 return new PiTernaryFieldMatch(fieldMatch.fieldId(), ternValue, ternMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900470 case LPM:
471 PiLpmFieldMatch lpmfield = (PiLpmFieldMatch) fieldMatch;
472 if (lpmfield.prefixLength() > modelBitWidth) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200473 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900474 "Invalid prefix length for LPM field '%s', found %d but field has bit-width %d",
475 fieldMatch.fieldId(), lpmfield.prefixLength(), modelBitWidth));
476 }
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700477 ImmutableByteSequence lpmValue = lpmfield.value()
Carmelo Cascone181f3f42018-04-11 17:37:53 -0700478 .fit(modelBitWidth);
479 ImmutableByteSequence lpmMask = prefixOnes(lpmValue.size(),
480 lpmfield.prefixLength());
481 lpmValue = lpmValue.bitwiseAnd(lpmMask);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900482 return new PiLpmFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone81929aa2018-04-07 01:38:55 -0700483 lpmValue, lpmfield.prefixLength());
Carmelo Cascone00a59962017-06-16 17:51:49 +0900484 case RANGE:
485 return new PiRangeFieldMatch(fieldMatch.fieldId(),
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700486 ((PiRangeFieldMatch) fieldMatch).lowValue().fit(modelBitWidth),
487 ((PiRangeFieldMatch) fieldMatch).highValue().fit(modelBitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900488 default:
489 // Should never be here.
Ray Milkey986a47a2018-01-25 11:38:51 -0800490 throw new IllegalArgumentException(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900491 "Unrecognized match type " + fieldModel.matchType().name());
492 }
493 } catch (ByteSequenceTrimException e) {
Carmelo Cascone87b9b392017-10-02 18:33:20 +0200494 throw new PiTranslationException(format(
Carmelo Cascone00a59962017-06-16 17:51:49 +0900495 "Size mismatch for field %s: %s", fieldMatch.fieldId(), e.getMessage()));
496 }
497 }
498}