blob: 6b345ebb1b655a7ce54b9a46092a8279d3e96175 [file] [log] [blame]
Carmelo Cascone8d99b172017-07-18 17:26:31 -04001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Carmelo Cascone8d99b172017-07-18 17:26:31 -04003 *
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.p4runtime.ctl;
18
19import com.google.common.collect.ImmutableList;
20import com.google.protobuf.ByteString;
21import org.onlab.util.ImmutableByteSequence;
22import org.onosproject.net.pi.model.PiPipeconf;
23import org.onosproject.net.pi.runtime.PiAction;
24import org.onosproject.net.pi.runtime.PiActionId;
25import org.onosproject.net.pi.runtime.PiActionParam;
26import org.onosproject.net.pi.runtime.PiActionParamId;
27import org.onosproject.net.pi.runtime.PiExactFieldMatch;
28import org.onosproject.net.pi.runtime.PiFieldMatch;
29import org.onosproject.net.pi.runtime.PiHeaderFieldId;
30import org.onosproject.net.pi.runtime.PiLpmFieldMatch;
Carmelo Cascone0e896a02017-07-31 07:22:27 +020031import org.onosproject.net.pi.runtime.PiMatchKey;
Carmelo Cascone8d99b172017-07-18 17:26:31 -040032import org.onosproject.net.pi.runtime.PiRangeFieldMatch;
33import org.onosproject.net.pi.runtime.PiTableAction;
34import org.onosproject.net.pi.runtime.PiTableEntry;
35import org.onosproject.net.pi.runtime.PiTableId;
36import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
37import org.onosproject.net.pi.runtime.PiValidFieldMatch;
38import org.slf4j.Logger;
39import p4.P4RuntimeOuterClass.Action;
40import p4.P4RuntimeOuterClass.FieldMatch;
41import p4.P4RuntimeOuterClass.TableAction;
42import p4.P4RuntimeOuterClass.TableEntry;
43import p4.config.P4InfoOuterClass;
44
45import java.util.Collection;
46import java.util.Collections;
47
48import static java.lang.String.format;
49import static org.onlab.util.ImmutableByteSequence.copyFrom;
50import static org.slf4j.LoggerFactory.getLogger;
51
52/**
53 * Encoder of table entries, from ONOS Pi* format, to P4Runtime protobuf messages, and vice versa.
54 */
55final class TableEntryEncoder {
56
57
58 private static final Logger log = getLogger(TableEntryEncoder.class);
59
60 private static final String HEADER_PREFIX = "hdr.";
61 private static final String VALUE_OF_PREFIX = "value of ";
62 private static final String MASK_OF_PREFIX = "mask of ";
63 private static final String HIGH_RANGE_VALUE_OF_PREFIX = "high range value of ";
64 private static final String LOW_RANGE_VALUE_OF_PREFIX = "low range value of ";
65
66 // TODO: implement cache of encoded entities.
67
68 private TableEntryEncoder() {
69 // hide.
70 }
71
72 /**
73 * Returns a collection of P4Runtime table entry protobuf messages, encoded from the given collection of PI
74 * table entries for the given pipeconf. If a PI table entry cannot be encoded, it is skipped, hence the returned
75 * collection might have different size than the input one.
76 * <p>
77 * Please check the log for an explanation of any error that might have occurred.
78 *
79 * @param piTableEntries PI table entries
80 * @param pipeconf PI pipeconf
81 * @return collection of P4Runtime table entry protobuf messages
82 */
83 static Collection<TableEntry> encode(Collection<PiTableEntry> piTableEntries, PiPipeconf pipeconf) {
84
85 P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
86
87 if (browser == null) {
88 log.error("Unable to get a P4Info browser for pipeconf {}, skipping encoding of all table entries");
89 return Collections.emptyList();
90 }
91
92 ImmutableList.Builder<TableEntry> tableEntryMsgListBuilder = ImmutableList.builder();
93
94 for (PiTableEntry piTableEntry : piTableEntries) {
95 try {
96 tableEntryMsgListBuilder.add(encodePiTableEntry(piTableEntry, browser));
97 } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
98 log.error("Unable to encode PI table entry: {}", e.getMessage());
99 }
100 }
101
102 return tableEntryMsgListBuilder.build();
103 }
104
105 /**
106 * Returns a collection of PI table entry objects, decoded from the given collection of P4Runtime table entry
107 * messages for the given pipeconf. If a table entry message cannot be decoded, it is skipped, hence the returned
108 * collection might have different size than the input one.
109 * <p>
110 * Please check the log for an explanation of any error that might have occurred.
111 *
112 * @param tableEntryMsgs P4Runtime table entry messages
113 * @param pipeconf PI pipeconf
114 * @return collection of PI table entry objects
115 */
116 static Collection<PiTableEntry> decode(Collection<TableEntry> tableEntryMsgs, PiPipeconf pipeconf) {
117
118 P4InfoBrowser browser = PipeconfHelper.getP4InfoBrowser(pipeconf);
119
120 if (browser == null) {
121 log.error("Unable to get a P4Info browser for pipeconf {}, skipping decoding of all table entries");
122 return Collections.emptyList();
123 }
124
125 ImmutableList.Builder<PiTableEntry> piTableEntryListBuilder = ImmutableList.builder();
126
127 for (TableEntry tableEntryMsg : tableEntryMsgs) {
128 try {
129 piTableEntryListBuilder.add(decodeTableEntryMsg(tableEntryMsg, browser));
130 } catch (P4InfoBrowser.NotFoundException | EncodeException e) {
131 log.error("Unable to decode table entry message: {}", e.getMessage());
132 }
133 }
134
135 return piTableEntryListBuilder.build();
136 }
137
138 private static TableEntry encodePiTableEntry(PiTableEntry piTableEntry, P4InfoBrowser browser)
139 throws P4InfoBrowser.NotFoundException, EncodeException {
140
141 TableEntry.Builder tableEntryMsgBuilder = TableEntry.newBuilder();
142
143 P4InfoOuterClass.Table tableInfo = browser.tables().getByName(piTableEntry.table().id());
144
145 // Table id.
146 tableEntryMsgBuilder.setTableId(tableInfo.getPreamble().getId());
147
148 // Priority.
Yi Tseng0c2841c2017-08-15 10:54:14 -0700149 // FIXME: check on P4Runtime if/what is the default priority.
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400150 int priority = piTableEntry.priority().orElse(0);
151 tableEntryMsgBuilder.setPriority(priority);
152
153 // Controller metadata (cookie)
154 tableEntryMsgBuilder.setControllerMetadata(piTableEntry.cookie());
155
156 // Timeout.
157 if (piTableEntry.timeout().isPresent()) {
158 log.warn("Found PI table entry with timeout set, not supported in P4Runtime: {}", piTableEntry);
159 }
160
161 // Table action.
162 tableEntryMsgBuilder.setAction(encodePiTableAction(piTableEntry.action(), browser));
163
164 // Field matches.
Carmelo Cascone0e896a02017-07-31 07:22:27 +0200165 for (PiFieldMatch piFieldMatch : piTableEntry.matchKey().fieldMatches()) {
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400166 tableEntryMsgBuilder.addMatch(encodePiFieldMatch(piFieldMatch, tableInfo, browser));
167 }
168
169 return tableEntryMsgBuilder.build();
170 }
171
172 private static PiTableEntry decodeTableEntryMsg(TableEntry tableEntryMsg, P4InfoBrowser browser)
173 throws P4InfoBrowser.NotFoundException, EncodeException {
174
175 PiTableEntry.Builder piTableEntryBuilder = PiTableEntry.builder();
176
177 P4InfoOuterClass.Table tableInfo = browser.tables().getById(tableEntryMsg.getTableId());
178
179 // Table id.
180 piTableEntryBuilder.forTable(PiTableId.of(tableInfo.getPreamble().getName()));
181
182 // Priority.
183 piTableEntryBuilder.withPriority(tableEntryMsg.getPriority());
184
185 // Controller metadata (cookie)
186 piTableEntryBuilder.withCookie(tableEntryMsg.getControllerMetadata());
187
188 // Table action.
189 piTableEntryBuilder.withAction(decodeTableActionMsg(tableEntryMsg.getAction(), browser));
190
191 // Timeout.
192 // FIXME: how to decode table entry messages with timeout, given that the timeout value is lost after encoding?
193
Carmelo Cascone0e896a02017-07-31 07:22:27 +0200194 // Match key for field matches.
195 PiMatchKey.Builder piMatchKeyBuilder = PiMatchKey.builder();
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400196 for (FieldMatch fieldMatchMsg : tableEntryMsg.getMatchList()) {
Carmelo Cascone0e896a02017-07-31 07:22:27 +0200197 piMatchKeyBuilder.addFieldMatch(decodeFieldMatchMsg(fieldMatchMsg, tableInfo, browser));
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400198 }
Carmelo Cascone0e896a02017-07-31 07:22:27 +0200199 piTableEntryBuilder.withMatchKey(piMatchKeyBuilder.build());
Carmelo Cascone8d99b172017-07-18 17:26:31 -0400200
201 return piTableEntryBuilder.build();
202 }
203
204 private static FieldMatch encodePiFieldMatch(PiFieldMatch piFieldMatch, P4InfoOuterClass.Table tableInfo,
205 P4InfoBrowser browser)
206 throws P4InfoBrowser.NotFoundException, EncodeException {
207
208 FieldMatch.Builder fieldMatchMsgBuilder = FieldMatch.newBuilder();
209
210 // FIXME: check how field names for stacked headers are constructed in P4Runtime.
211 String fieldName = piFieldMatch.fieldId().id();
212 int tableId = tableInfo.getPreamble().getId();
213 P4InfoOuterClass.MatchField matchFieldInfo = browser.matchFields(tableId).getByNameOrAlias(fieldName);
214 String entityName = format("field match '%s' of table '%s'",
215 matchFieldInfo.getName(), tableInfo.getPreamble().getName());
216 int fieldId = matchFieldInfo.getId();
217 int fieldBitwidth = matchFieldInfo.getBitwidth();
218
219 fieldMatchMsgBuilder.setFieldId(fieldId);
220
221 switch (piFieldMatch.type()) {
222 case EXACT:
223 PiExactFieldMatch fieldMatch = (PiExactFieldMatch) piFieldMatch;
224 ByteString exactValue = ByteString.copyFrom(fieldMatch.value().asReadOnlyBuffer());
225 assertSize(VALUE_OF_PREFIX + entityName, exactValue, fieldBitwidth);
226 return fieldMatchMsgBuilder.setExact(
227 FieldMatch.Exact
228 .newBuilder()
229 .setValue(exactValue)
230 .build())
231 .build();
232 case TERNARY:
233 PiTernaryFieldMatch ternaryMatch = (PiTernaryFieldMatch) piFieldMatch;
234 ByteString ternaryValue = ByteString.copyFrom(ternaryMatch.value().asReadOnlyBuffer());
235 ByteString ternaryMask = ByteString.copyFrom(ternaryMatch.mask().asReadOnlyBuffer());
236 assertSize(VALUE_OF_PREFIX + entityName, ternaryValue, fieldBitwidth);
237 assertSize(MASK_OF_PREFIX + entityName, ternaryMask, fieldBitwidth);
238 return fieldMatchMsgBuilder.setTernary(
239 FieldMatch.Ternary
240 .newBuilder()
241 .setValue(ternaryValue)
242 .setMask(ternaryMask)
243 .build())
244 .build();
245 case LPM:
246 PiLpmFieldMatch lpmMatch = (PiLpmFieldMatch) piFieldMatch;
247 ByteString lpmValue = ByteString.copyFrom(lpmMatch.value().asReadOnlyBuffer());
248 int lpmPrefixLen = lpmMatch.prefixLength();
249 assertSize(VALUE_OF_PREFIX + entityName, lpmValue, fieldBitwidth);
250 assertPrefixLen(entityName, lpmPrefixLen, fieldBitwidth);
251 return fieldMatchMsgBuilder.setLpm(
252 FieldMatch.LPM.newBuilder()
253 .setValue(lpmValue)
254 .setPrefixLen(lpmPrefixLen)
255 .build())
256 .build();
257 case RANGE:
258 PiRangeFieldMatch rangeMatch = (PiRangeFieldMatch) piFieldMatch;
259 ByteString rangeHighValue = ByteString.copyFrom(rangeMatch.highValue().asReadOnlyBuffer());
260 ByteString rangeLowValue = ByteString.copyFrom(rangeMatch.lowValue().asReadOnlyBuffer());
261 assertSize(HIGH_RANGE_VALUE_OF_PREFIX + entityName, rangeHighValue, fieldBitwidth);
262 assertSize(LOW_RANGE_VALUE_OF_PREFIX + entityName, rangeLowValue, fieldBitwidth);
263 return fieldMatchMsgBuilder.setRange(
264 FieldMatch.Range.newBuilder()
265 .setHigh(rangeHighValue)
266 .setLow(rangeLowValue)
267 .build())
268 .build();
269 case VALID:
270 PiValidFieldMatch validMatch = (PiValidFieldMatch) piFieldMatch;
271 return fieldMatchMsgBuilder.setValid(
272 FieldMatch.Valid.newBuilder()
273 .setValue(validMatch.isValid())
274 .build())
275 .build();
276 default:
277 throw new EncodeException(format(
278 "Building of match type %s not implemented", piFieldMatch.type()));
279 }
280 }
281
282 private static PiFieldMatch decodeFieldMatchMsg(FieldMatch fieldMatchMsg, P4InfoOuterClass.Table tableInfo,
283 P4InfoBrowser browser)
284 throws P4InfoBrowser.NotFoundException, EncodeException {
285
286 int tableId = tableInfo.getPreamble().getId();
287 String fieldMatchName = browser.matchFields(tableId).getById(fieldMatchMsg.getFieldId()).getName();
288 if (fieldMatchName.startsWith(HEADER_PREFIX)) {
289 fieldMatchName = fieldMatchName.substring(HEADER_PREFIX.length());
290 }
291
292 // FIXME: Add support for decoding of stacked header names.
293 String[] pieces = fieldMatchName.split("\\.");
294 if (pieces.length != 2) {
295 throw new EncodeException(format("unrecognized field match name '%s'", fieldMatchName));
296 }
297 PiHeaderFieldId headerFieldId = PiHeaderFieldId.of(pieces[0], pieces[1]);
298
299 FieldMatch.FieldMatchTypeCase typeCase = fieldMatchMsg.getFieldMatchTypeCase();
300
301 switch (typeCase) {
302 case EXACT:
303 FieldMatch.Exact exactFieldMatch = fieldMatchMsg.getExact();
304 ImmutableByteSequence exactValue = copyFrom(exactFieldMatch.getValue().asReadOnlyByteBuffer());
305 return new PiExactFieldMatch(headerFieldId, exactValue);
306 case TERNARY:
307 FieldMatch.Ternary ternaryFieldMatch = fieldMatchMsg.getTernary();
308 ImmutableByteSequence ternaryValue = copyFrom(ternaryFieldMatch.getValue().asReadOnlyByteBuffer());
309 ImmutableByteSequence ternaryMask = copyFrom(ternaryFieldMatch.getMask().asReadOnlyByteBuffer());
310 return new PiTernaryFieldMatch(headerFieldId, ternaryValue, ternaryMask);
311 case LPM:
312 FieldMatch.LPM lpmFieldMatch = fieldMatchMsg.getLpm();
313 ImmutableByteSequence lpmValue = copyFrom(lpmFieldMatch.getValue().asReadOnlyByteBuffer());
314 int lpmPrefixLen = lpmFieldMatch.getPrefixLen();
315 return new PiLpmFieldMatch(headerFieldId, lpmValue, lpmPrefixLen);
316 case RANGE:
317 FieldMatch.Range rangeFieldMatch = fieldMatchMsg.getRange();
318 ImmutableByteSequence rangeHighValue = copyFrom(rangeFieldMatch.getHigh().asReadOnlyByteBuffer());
319 ImmutableByteSequence rangeLowValue = copyFrom(rangeFieldMatch.getLow().asReadOnlyByteBuffer());
320 return new PiRangeFieldMatch(headerFieldId, rangeLowValue, rangeHighValue);
321 case VALID:
322 FieldMatch.Valid validFieldMatch = fieldMatchMsg.getValid();
323 return new PiValidFieldMatch(headerFieldId, validFieldMatch.getValue());
324 default:
325 throw new EncodeException(format(
326 "Decoding of field match type '%s' not implemented", typeCase.name()));
327 }
328 }
329
330 private static TableAction encodePiTableAction(PiTableAction piTableAction, P4InfoBrowser browser)
331 throws P4InfoBrowser.NotFoundException, EncodeException {
332
333 TableAction.Builder tableActionMsgBuilder = TableAction.newBuilder();
334
335 switch (piTableAction.type()) {
336 case ACTION:
337 PiAction piAction = (PiAction) piTableAction;
338 int actionId = browser.actions().getByName(piAction.id().name()).getPreamble().getId();
339
340 Action.Builder actionMsgBuilder = Action.newBuilder().setActionId(actionId);
341
342 for (PiActionParam p : piAction.parameters()) {
343 P4InfoOuterClass.Action.Param paramInfo = browser.actionParams(actionId).getByName(p.id().name());
344 ByteString paramValue = ByteString.copyFrom(p.value().asReadOnlyBuffer());
345 assertSize(format("param '%s' of action '%s'", p.id(), piAction.id()),
346 paramValue, paramInfo.getBitwidth());
347 actionMsgBuilder.addParams(Action.Param.newBuilder()
348 .setParamId(paramInfo.getId())
349 .setValue(paramValue)
350 .build());
351 }
352
353 tableActionMsgBuilder.setAction(actionMsgBuilder.build());
354 break;
355
356 default:
357 throw new EncodeException(
358 format("Building of table action type %s not implemented", piTableAction.type()));
359 }
360
361 return tableActionMsgBuilder.build();
362 }
363
364 private static PiTableAction decodeTableActionMsg(TableAction tableActionMsg, P4InfoBrowser browser)
365 throws P4InfoBrowser.NotFoundException, EncodeException {
366
367 TableAction.TypeCase typeCase = tableActionMsg.getTypeCase();
368
369 switch (typeCase) {
370 case ACTION:
371 PiAction.Builder piActionBuilder = PiAction.builder();
372 Action actionMsg = tableActionMsg.getAction();
373 // Action ID.
374 int actionId = actionMsg.getActionId();
375 String actionName = browser.actions().getById(actionId).getPreamble().getName();
376 piActionBuilder.withId(PiActionId.of(actionName));
377 // Params.
378 for (Action.Param paramMsg : actionMsg.getParamsList()) {
379 String paramName = browser.actionParams(actionId).getById(paramMsg.getParamId()).getName();
380 ImmutableByteSequence paramValue = copyFrom(paramMsg.getValue().asReadOnlyByteBuffer());
381 piActionBuilder.withParameter(new PiActionParam(PiActionParamId.of(paramName), paramValue));
382 }
383 return piActionBuilder.build();
384
385 default:
386 throw new EncodeException(
387 format("Decoding of table action type %s not implemented", typeCase.name()));
388 }
389 }
390
391 private static void assertSize(String entityDescr, ByteString value, int bitWidth)
392 throws EncodeException {
393
394 int byteWidth = (int) Math.ceil((float) bitWidth / 8);
395 if (value.size() != byteWidth) {
396 throw new EncodeException(format("Wrong size for %s, expected %d bytes, but found %d",
397 entityDescr, byteWidth, value.size()));
398 }
399 }
400
401 private static void assertPrefixLen(String entityDescr, int prefixLength, int bitWidth)
402 throws EncodeException {
403
404 if (prefixLength > bitWidth) {
405 throw new EncodeException(format(
406 "wrong prefix length for %s, field size is %d bits, but found one is %d",
407 entityDescr, bitWidth, prefixLength));
408 }
409 }
410}