Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/ef-args.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ void print_help() {
po("Options:\n");
po(" -v Print version.\n");
po(" -h Top level help message.\n");
po(" -p No pad. Skip padding frames to 60 bytes,\n");
po(" allowing runt frames to be sent or matched as-is.\n");
po(" -t <timeout-in-ms> When listening on an interface (rx),\n");
po(" When listening on an interface (rx), the tool will always\n");
po(" listen during the entire timeout period. This is needed,\n");
Expand Down Expand Up @@ -320,13 +322,18 @@ int argc_cmds(int argc, const char *argv[]) {
return res;
}

int NO_PAD = 0;
int TIME_OUT_MS = 100;

int main_(int argc, const char *argv[]) {
int opt;

while ((opt = getopt(argc, (char * const*)argv, "vht:c:")) != -1) {
while ((opt = getopt(argc, (char * const*)argv, "pvht:c:")) != -1) {
switch (opt) {
case 'p':
NO_PAD = 1;
break;

case 'v':
print_version();
return 0;
Expand Down
4 changes: 2 additions & 2 deletions src/ef.c
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ buf_t *frame_to_buf(frame_t *f) {
frame_size += f->stack[i]->size;
}

if (frame_size < 60)
if (frame_size < 60 && !NO_PAD)
frame_size = 60;

buf = balloc(frame_size);
Expand All @@ -503,7 +503,7 @@ buf_t *frame_mask_to_buf(frame_t *f) {
}

frame_size_no_padding = frame_size;
if (frame_size < 60)
if (frame_size < 60 && !NO_PAD)
frame_size = 60;

buf = balloc(frame_size);
Expand Down
1 change: 1 addition & 0 deletions src/ef.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
#define DIV_ROUND(a, b) (1 + ((a - 1) / b))
#define BIT_TO_BYTE(x) (DIV_ROUND(x, 8))

extern int NO_PAD;
extern int TIME_OUT_MS;

///////////////////////////////////////////////////////////////////////////////
Expand Down
247 changes: 247 additions & 0 deletions test/test-padding.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
#include "ef.h"
#include "ef-test.h"
#include "catch_single_include.hxx"

#include <cstring>

// RAII guard to set NO_PAD for the duration of a scope
struct NoPadGuard {
NoPadGuard() { NO_PAD = 1; }
~NoPadGuard() { NO_PAD = 0; }
};

// Build a frame and its expected buf/mask from a frame spec
static void build_frame(std::vector<const char *> spec,
buf_t **out_buf, buf_t **out_mask,
frame_t **out_frame)
{
frame_t *f = parse_frame_wrap(spec);
REQUIRE(f != NULL);
*out_buf = frame_to_buf(f);
REQUIRE(*out_buf != NULL);
*out_mask = f->has_mask ? frame_mask_to_buf(f) : NULL;
*out_frame = f;
}

// Create a padded copy of a buffer: original data + pad_len zero bytes
static buf_t *make_padded_rx(const buf_t *expected, int pad_len)
{
buf_t *rx = balloc(expected->size + pad_len);
REQUIRE(rx != NULL);
memcpy(rx->data, expected->data, expected->size);
memset(rx->data + expected->size, 0, pad_len);
return rx;
}

TEST_CASE("padding: padded frame matches with correct padding", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

// Simulate a 4-byte padded RX frame
buf_t *rx = make_padded_rx(expected, 4);

CHECK(bequal_mask(rx, expected, mask, 4) == 1);

bfree(rx);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: padded frame fails with wrong padding value", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

buf_t *rx = make_padded_rx(expected, 4);

// Wrong padding value — size check fails
CHECK(bequal_mask(rx, expected, mask, 3) == 0);
CHECK(bequal_mask(rx, expected, mask, 5) == 0);
CHECK(bequal_mask(rx, expected, mask, 0) == 0);

bfree(rx);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: zero padding self-matches", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

CHECK(bequal_mask(expected, expected, mask, 0) == 1);

bfree(expected);
frame_free(f);
}

TEST_CASE("padding: padded frame with mask matches", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "ign"}, &expected, &mask, &f);

REQUIRE(mask != NULL);

// Build RX with different smac but 8 bytes of padding
frame_t *f2 = parse_frame_wrap({"eth", "dmac", "::1", "smac", "::ff"});
buf_t *rx_base = frame_to_buf(f2);
buf_t *rx = make_padded_rx(rx_base, 8);

CHECK(bequal_mask(rx, expected, mask, 8) == 1);

bfree(rx);
bfree(rx_base);
frame_free(f2);
bfree(mask);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: data mismatch in non-padding region fails", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

buf_t *rx = make_padded_rx(expected, 4);

// Corrupt a byte in the frame data (not the padding)
rx->data[0] ^= 0xff;

CHECK(bequal_mask(rx, expected, mask, 4) == 0);

bfree(rx);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: zero-filled padding bytes match", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

// make_padded_rx fills padding with zeros
buf_t *rx = make_padded_rx(expected, 8);

CHECK(bequal_mask(rx, expected, mask, 8) == 1);

bfree(rx);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: non-zero padding bytes rejected", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

buf_t *rx = make_padded_rx(expected, 8);

// Non-zero padding bytes must cause match failure
memset(rx->data + expected->size, 0xde, 8);

CHECK(bequal_mask(rx, expected, mask, 8) == 0);

bfree(rx);
bfree(expected);
frame_free(f);
}

TEST_CASE("padding: negative padding rejected", "[padding]") {
buf_t *expected, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &expected, &mask, &f);

CHECK(bequal_mask(expected, expected, mask, -1) == 0);
CHECK(bequal_mask(expected, expected, mask, -100) == 0);

bfree(expected);
frame_free(f);
}

TEST_CASE("no-pad: eth-only frame is 14 bytes", "[nopad]") {
NoPadGuard g;
buf_t *buf, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &buf, &mask, &f);

// eth header = 14 bytes, no padding to 60
CHECK(buf->size == 14);

bfree(buf);
frame_free(f);
}

TEST_CASE("no-pad: default pads to 60 bytes", "[nopad]") {
CHECK(NO_PAD == 0);
buf_t *buf, *mask;
frame_t *f;
build_frame({"eth", "dmac", "::1", "smac", "::2"}, &buf, &mask, &f);

CHECK(buf->size == 60);

bfree(buf);
frame_free(f);
}

TEST_CASE("no-pad: mask buf also skips padding", "[nopad]") {
NoPadGuard g;

// Use 'ign' on smac to force mask generation
frame_t *f = parse_frame_wrap({"eth", "dmac", "::1", "smac", "ign"});
REQUIRE(f != NULL);
buf_t *buf = frame_to_buf(f);
buf_t *mask = frame_mask_to_buf(f);
REQUIRE(buf != NULL);
REQUIRE(mask != NULL);

CHECK(buf->size == 14);
CHECK(mask->size == 14);

bfree(buf);
bfree(mask);
frame_free(f);
}

TEST_CASE("no-pad: frame with payload stays exact size", "[nopad]") {
NoPadGuard g;
buf_t *buf, *mask;
frame_t *f;

// eth(14) + data pattern cnt 4 = 18 bytes, well under 60
build_frame({"eth", "dmac", "::1", "smac", "::2",
"data", "pattern", "cnt", "4"}, &buf, &mask, &f);

CHECK(buf->size == 18);

bfree(buf);
frame_free(f);
}

TEST_CASE("no-pad: large frame unaffected", "[nopad]") {
NoPadGuard g;
buf_t *buf, *mask;
frame_t *f;

// eth(14) + data pattern cnt 100 = 114 bytes, already > 60
build_frame({"eth", "dmac", "::1", "smac", "::2",
"data", "pattern", "cnt", "100"}, &buf, &mask, &f);

CHECK(buf->size == 114);

// Same result without NO_PAD
NoPadGuard *gp = &g;
(void)gp; // suppress unused warning
NO_PAD = 0;
Comment on lines +233 to +235
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't get that. Why to use the guard at all? What does this pointer do? Some trick here?

buf_t *buf2, *mask2;
frame_t *f2;
build_frame({"eth", "dmac", "::1", "smac", "::2",
"data", "pattern", "cnt", "100"}, &buf2, &mask2, &f2);

CHECK(buf2->size == 114);

bfree(buf);
bfree(buf2);
frame_free(f);
frame_free(f2);
}