Skip to content

Commit 03d7a51

Browse files
authored
Merge pull request #148 from dorakerman/fix_lpm_match_dor_non_byte_aligned_fields
LPM Masking: Fix masking for fields with bitwidth non divisable by 8.
2 parents 22ed0c9 + 01a625b commit 03d7a51

File tree

3 files changed

+102
-9
lines changed

3 files changed

+102
-9
lines changed

p4runtime_sh/shell.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -440,19 +440,36 @@ def _sanitize_and_convert_mf_lpm(self, prefix, length, field_info):
440440

441441
barray = bytearray(prefix)
442442
transformed = False
443-
r = length % 8
444-
byte_mask = 0xff & ((0xff << (8 - r)))
445-
if barray[first_byte_masked] & byte_mask != barray[first_byte_masked]:
446-
transformed = True
447-
barray[first_byte_masked] = barray[first_byte_masked] & byte_mask
448-
449-
for i in range(first_byte_masked + 1, len(prefix)):
450-
if barray[i] != 0:
443+
# When prefix mask and field bit width are not aligned to the size of a
444+
# byte, we need to make sure mask is applied to the all bytes in prefix
445+
# that should be masked.
446+
# Therefore, we will create a byte array mask for all the bits in the
447+
# field and apply it to entire prefix, zeroing out the bits that should
448+
# not be part of the prefix. We can skip bytes that are fully inside
449+
# the mask.
450+
mask = ((1 << length) - 1) # Create mask for the prefix length.
451+
mask = mask << (field_info.bitwidth - length) # Shift left to the correct position.
452+
nbytes = (field_info.bitwidth + 7) // 8
453+
bytes_mask = bytearray(mask.to_bytes(nbytes, byteorder='big'))
454+
455+
# Prefix len is aligned to num of byte needed to represent bitwidth in parsing stage.
456+
if len(bytes_mask) != len(prefix): # Should not happen, safety check.
457+
raise UserError("Invalid prefix length")
458+
459+
idxs_to_apply_mask = list(range(first_byte_masked, len(bytes_mask)))
460+
if first_byte_masked > 0:
461+
idxs_to_apply_mask.insert(0, 0) # Always apply mask to first byte.
462+
463+
transformed = False
464+
for i in idxs_to_apply_mask:
465+
if barray[i] & bytes_mask[i] != barray[i]:
451466
transformed = True
452-
barray[i] = 0
467+
barray[i] = barray[i] & bytes_mask[i]
468+
453469
if transformed:
454470
_print("LPM value was transformed to conform to the P4Runtime spec "
455471
"(trailing bits must be unset)")
472+
456473
mf.lpm.value = bytes(bytes_utils.make_canonical_if_option_set(barray))
457474
return mf
458475

p4runtime_sh/test.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,54 @@ def test_table_entry_lpm(self, input_, value, length):
380380

381381
self.servicer.Write.assert_called_once_with(ProtoCmp(expected_req), ANY)
382382

383+
@nose2.tools.params(
384+
("0xfaafe/16", "\\x0f\\xaa\\xf0", 16, "0x3/1", "\\x02", 1),
385+
("0xfaafe/20", "\\x0f\\xaa\\xfe", 20, "0x3/2", "\\x03", 2),
386+
("0xfaafe/8", "\\x0f\\xa0\\x00", 8, "0x1/1", "\\x00", 1),
387+
("0xfaafe/1", "\\x08\\x00\\x00", 1, "0x1/2", "\\x01", 2))
388+
def test_table_entry_lpm_bitwidth_not_multiply_of_8(
389+
self, input_20, value_20, length_20, input_2, value_2, length_2
390+
):
391+
te = sh.TableEntry("LpmTwo")(action="actionA")
392+
te.match["header_test.field20"] = input_20
393+
te.match["header_test.field2"] = input_2
394+
te.action["param"] = "aa:bb:cc:dd:ee:ff"
395+
te.insert()
396+
397+
# Cannot use format here because it would require escaping all braces,
398+
# which would make wiriting tests much more annoying
399+
expected_entry = """
400+
table_id: 33567647
401+
match {
402+
field_id: 1
403+
lpm {
404+
value: "%s"
405+
prefix_len: %s
406+
}
407+
}
408+
match {
409+
field_id: 2
410+
lpm {
411+
value: "%s"
412+
prefix_len: %s
413+
}
414+
}
415+
action {
416+
action {
417+
action_id: 16783703
418+
params {
419+
param_id: 1
420+
value: "\\xaa\\xbb\\xcc\\xdd\\xee\\xff"
421+
}
422+
}
423+
}
424+
""" % (value_20, length_20, value_2, length_2)
425+
426+
expected_req = self.make_write_request(
427+
p4runtime_pb2.Update.INSERT, P4RuntimeEntity.table_entry, expected_entry)
428+
429+
self.servicer.Write.assert_called_once_with(ProtoCmp(expected_req), ANY)
430+
383431
def test_table_entry_lpm_dont_care(self):
384432
te = sh.TableEntry("LpmOne")
385433
with self.assertRaisesRegex(UserError, "LPM don't care match"):

p4runtime_sh/testdata/unittest.p4info.pb.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ tables {
5050
}
5151
size: 512
5252
}
53+
tables {
54+
preamble {
55+
id: 33567647
56+
name: "LpmTwo"
57+
alias: "LpmTwo"
58+
}
59+
match_fields {
60+
id: 1
61+
name: "header_test.field20"
62+
bitwidth: 20
63+
match_type: LPM
64+
}
65+
match_fields {
66+
id: 2
67+
name: "header_test.field2"
68+
bitwidth: 2
69+
match_type: LPM
70+
}
71+
action_refs {
72+
id: 16783703
73+
}
74+
action_refs {
75+
id: 16800567
76+
annotations: "@defaultonly"
77+
scope: DEFAULT_ONLY
78+
}
79+
size: 512
80+
}
5381
tables {
5482
preamble {
5583
id: 33584148

0 commit comments

Comments
 (0)