diff --git a/src/ef-args.c b/src/ef-args.c index d42bae6..abfeff4 100644 --- a/src/ef-args.c +++ b/src/ef-args.c @@ -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 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"); @@ -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; diff --git a/src/ef.c b/src/ef.c index 5a1d5d9..ae3c147 100644 --- a/src/ef.c +++ b/src/ef.c @@ -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); @@ -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); diff --git a/src/ef.h b/src/ef.h index 10a3863..af31deb 100644 --- a/src/ef.h +++ b/src/ef.h @@ -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; /////////////////////////////////////////////////////////////////////////////// diff --git a/test/test-padding.cxx b/test/test-padding.cxx new file mode 100644 index 0000000..b3e75e8 --- /dev/null +++ b/test/test-padding.cxx @@ -0,0 +1,247 @@ +#include "ef.h" +#include "ef-test.h" +#include "catch_single_include.hxx" + +#include + +// 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 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; + 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); +}