commit 3c5d23d1b6313e6dfe2a931a24039fb5466d0921
parent 059a39cdc42a1babf9d66c9008d11fb8d45621d3
Author: Nick Mathewson <nickm@torproject.org>
Date: Thu, 17 Apr 2025 13:15:04 -0400
prop359: Implement relay cell encoder/decoders
I decided not to use a codec-based approach here.
Since we aren't implementing prop340, there is exactly one cell
per message, so we don't need to keep any state
in between cells or messages.
Diffstat:
8 files changed, 684 insertions(+), 151 deletions(-)
diff --git a/src/core/or/include.am b/src/core/or/include.am
@@ -31,6 +31,7 @@ LIBTOR_APP_A_SOURCES += \
src/core/or/reasons.c \
src/core/or/relay.c \
src/core/or/relay_cell.c \
+ src/core/or/relay_msg.c \
src/core/or/scheduler.c \
src/core/or/scheduler_kist.c \
src/core/or/scheduler_vanilla.c \
diff --git a/src/core/or/or.h b/src/core/or/or.h
@@ -557,6 +557,15 @@ static inline int get_circ_id_size(int wide_circ_ids)
/** Largest number of bytes that can fit in a relay cell payload. */
#define RELAY_PAYLOAD_SIZE (CELL_PAYLOAD_SIZE-RELAY_HEADER_SIZE)
+/** Number of bytes used for a relay cell's header, in the v0 format. */
+#define RELAY_HEADER_SIZE_V0 (1+2+2+4+2)
+/** Number of bytes used for a relay cell's header, in the v1 format,
+ * if no StreamID is used. */
+#define RELAY_HEADER_SIZE_V1_NO_STREAM_ID (16+1+2)
+/** Number of bytes used for a relay cell's header, in the v1 format,
+ * if a StreamID is used. */
+#define RELAY_HEADER_SIZE_V1_WITH_STREAM_ID (16+1+2+2)
+
/** Identifies a circuit on an or_connection */
typedef uint32_t circid_t;
/** Identifies a stream on a circuit */
diff --git a/src/core/or/relay_cell.c b/src/core/or/relay_cell.c
@@ -47,58 +47,18 @@
#define DIGEST_OFFSET_V0 (5)
#define DIGEST_OFFSET_V1 (2)
-/** Return the offset where the padding should start. The <b>data_len</b> is
- * the relay payload length expected to be put in the cell. It can not be
- * bigger than the relay payload size else this function assert().
- *
- * Value will always be smaller than CELL_PAYLOAD_SIZE because this offset is
- * for the entire cell length not just the data payload length. Zero is
- * returned if there is no room for padding.
+/**
+ * TODO #41051: Remove this entirely.
*
- * This function always skips the first 4 bytes after the payload because
- * having some unused zero bytes has saved us a lot of times in the past. */
-STATIC size_t
-get_pad_cell_offset(size_t data_len, uint8_t relay_cell_proto)
-{
- /* This is never supposed to happen but in case it does, stop right away
- * because if tor is tricked somehow into not adding random bytes to the
- * payload with this function returning 0 for a bad data_len, the entire
- * authenticated SENDME design can be bypassed leading to bad denial of
- * service attacks. */
- tor_assert(data_len <= relay_cell_get_payload_size(relay_cell_proto));
-
- /* If the offset is larger than the cell payload size, we return an offset
- * of zero indicating that no padding needs to be added. */
- size_t offset = relay_cell_get_header_size(relay_cell_proto) + data_len +
- RELAY_CELL_PADDING_GAP;
- if (offset >= CELL_PAYLOAD_SIZE) {
- return 0;
- }
- return offset;
-}
-
-/* Add random bytes to the unused portion of the payload, to foil attacks
- * where the other side can predict all of the bytes in the payload and thus
- * compute the authenticated SENDME cells without seeing the traffic. See
- * proposal 289. */
+ * I've moved this functionality into relay_msg.c, where it lives
+ * more happily. This function shouldn't have any callsites left
+ * at the end of this branch.
+ **/
void
relay_cell_pad_payload(cell_t *cell, size_t data_len)
{
- size_t pad_offset, pad_len;
-
- tor_assert(cell);
-
- pad_offset = get_pad_cell_offset(data_len, cell->relay_cell_proto);
- if (pad_offset == 0) {
- /* We can't add padding so we are done. */
- return;
- }
-
- /* Remember here that the cell_payload is the length of the header and
- * payload size so we offset it using the full length of the cell. */
- pad_len = CELL_PAYLOAD_SIZE - pad_offset;
- crypto_fast_rng_getbytes(get_thread_fast_rng(),
- cell->payload + pad_offset, pad_len);
+ (void)cell;
+ (void)data_len;
}
/** Return true iff the given cell recognized field is zero. */
@@ -167,19 +127,3 @@ relay_cell_set_digest(cell_t *cell, uint8_t *new_cell_digest)
memcpy(cell_digest_ptr, new_cell_digest, cell_digest_len);
}
-
-/** Set the payload in the given cell for the given relay cell protocol
- * version. This also takes care of the padding.
- *
- * This is part of the fast path. No memory allocation. */
-void
-relay_cell_set_payload(cell_t *cell, const uint8_t *payload,
- size_t payload_len)
-{
- if (payload_len) {
- memcpy(cell->payload + relay_cell_get_header_size(cell->relay_cell_proto),
- payload, payload_len);
- }
- /* Add random padding to the cell if we can. */
- relay_cell_pad_payload(cell, payload_len);
-}
diff --git a/src/core/or/relay_cell.h b/src/core/or/relay_cell.h
@@ -13,11 +13,6 @@
#include "core/or/cell_st.h"
-/** When padding a cell with randomness, leave this many zeros after the
- * payload. Historically, it has paid off to keep unused bytes after the
- * payload for the future of our C-tor maze and protocol. */
-#define RELAY_CELL_PADDING_GAP 4
-
/* TODO #41051: Most of these functions no longer make sense under CGO,
* and we are only going to use the new proto format with CGO. */
@@ -27,53 +22,59 @@ uint8_t *relay_cell_get_digest(cell_t *cell);
size_t relay_cell_get_digest_len(const cell_t *cell);
/* Setters. */
-void relay_cell_set_payload(cell_t *cell, const uint8_t *payload,
- size_t payload_len);
void relay_cell_set_digest(cell_t *cell, uint8_t *cell_digest);
-void relay_cell_pad_payload(cell_t *cell, size_t payload_len);
+void relay_cell_pad_payload(cell_t *cell, size_t data_len);
/*
* NOTE: The following are inlined for performance reasons. These values are
* accessed everywhere and so, even if not expensive, we avoid a function call.
*/
-/** Return the size of the relay cell header for the given relay cell
- * protocol version. */
-static inline size_t
-relay_cell_get_header_size(uint8_t relay_cell_proto)
+/** Return true iff 'cmd' uses a stream ID when using
+ * the v1 relay message format. */
+static bool
+relay_cmd_expects_streamid_in_v1(uint8_t relay_command)
{
- /* Specified in tor-spec.txt. */
- switch (relay_cell_proto) {
- case 0: return (1 + 2 + 2 + 4 + 2); // 11
- // TODO #41051: This doesn't really make sense, for two reasons.
- // First, we're not going to do this protocol without CGO,
- // so we no longer have separate "recognized" and "digest" fields.
- // Second, the 16-byte tag under CGO does not include
- // the length and command fields,
- // which are counted above.
- case 1: return (2 + 14); // 16
- default:
- tor_fragile_assert();
- return 0;
+ switch (relay_command) {
+ case RELAY_COMMAND_BEGIN:
+ case RELAY_COMMAND_BEGIN_DIR:
+ case RELAY_COMMAND_CONNECTED:
+ case RELAY_COMMAND_DATA:
+ case RELAY_COMMAND_END:
+ case RELAY_COMMAND_RESOLVE:
+ case RELAY_COMMAND_RESOLVED:
+ case RELAY_COMMAND_XOFF:
+ case RELAY_COMMAND_XON:
+ return true;
+ default:
+ return false;
}
}
-/** Return the size of the relay cell payload for the given relay cell
- * protocol version. */
-/* TODO #41051: This depends on the command too, since the stream ID is
- conditional */
-/* TODO #41051: This is a _maximum_. */
+/** Return the size of the relay cell payload for the given relay
+ * cell format. */
static inline size_t
-relay_cell_get_payload_size(uint8_t relay_cell_proto)
+relay_cell_max_payload_size(relay_cell_fmt_t format,
+ uint8_t relay_command)
{
- return CELL_PAYLOAD_SIZE - relay_cell_get_header_size(relay_cell_proto);
+ switch (format) {
+ case RELAY_CELL_FORMAT_V0:
+ return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0;
+ case RELAY_CELL_FORMAT_V1: {
+ if (relay_cmd_expects_streamid_in_v1(relay_command)) {
+ return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID;
+ } else {
+ return CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID;
+ }
+ }
+ default:
+ tor_fragile_assert();
+ return 0;
+ }
}
#ifdef RELAY_CELL_PRIVATE
-STATIC size_t get_pad_cell_offset(size_t payload_len,
- uint8_t relay_cell_proto);
-
#endif /* RELAY_CELL_PRIVATE */
#endif /* TOR_RELAY_CELL_H */
diff --git a/src/core/or/relay_msg.c b/src/core/or/relay_msg.c
@@ -10,18 +10,16 @@
#include "core/or/relay_msg.h"
+#include "core/or/relay_cell.h"
+#include "lib/crypt_ops/crypto_rand.h"
+
+#include "core/or/cell_st.h"
+#include "core/or/relay_msg_st.h"
+
/*
* Public API
*/
-/** Called just before the consensus is changed with the given networkstatus_t
- * object. */
-void
-relay_msg_consensus_has_changed(const networkstatus_t *ns)
-{
- relay_msg_enabled = get_param_enabled(ns);
-}
-
/** Free the given relay message. */
void
relay_msg_free_(relay_msg_t *msg)
@@ -42,3 +40,208 @@ relay_msg_clear(relay_msg_t *msg)
tor_free(msg->body);
memset(msg, 0, sizeof(*msg));
}
+
+/* Positions of fields within a v0 message. */
+#define V0_CMD_OFFSET 0
+#define V0_STREAM_ID_OFFSET 3
+#define V0_LEN_OFFSET 9
+#define V0_PAYLOAD_OFFSET 11
+
+/* Positions of fields within a v1 message. */
+#define V1_CMD_OFFSET 16
+#define V1_LEN_OFFSET 17
+#define V1_STREAM_ID_OFFSET 19
+#define V1_PAYLOAD_OFFSET_NO_STREAM_ID 19
+#define V1_PAYLOAD_OFFSET_WITH_STREAM_ID 21
+
+/* Add random bytes to the unused portion of the payload, to foil attacks
+ * where the other side can predict all of the bytes in the payload and thus
+ * compute the authenticated SENDME cells without seeing the traffic. See
+ * proposal 289. */
+static void
+relay_cell_pad(cell_t *cell, size_t end_of_message)
+{
+ // We add 4 bytes of zero before padding, for forward-compatibility.
+ const size_t skip = 4;
+
+ if (end_of_message + skip >= CELL_PAYLOAD_SIZE) {
+ /* nothing to do. */
+ return;
+ }
+
+ crypto_fast_rng_getbytes(get_thread_fast_rng(),
+ &cell->payload[end_of_message + skip],
+ CELL_PAYLOAD_SIZE - (end_of_message + skip));
+}
+
+/** Encode the relay message in 'msg' into cell, according to the
+ * v0 rules. */
+static int
+encode_v0_cell(const relay_msg_t *msg,
+ cell_t *cell_out)
+{
+ if (BUG(msg->length > CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0))
+ return -1;
+
+ uint8_t *out = cell_out->payload;
+
+ out[V0_CMD_OFFSET] = (uint8_t) msg->command;
+ set_uint16(out+V0_STREAM_ID_OFFSET, htons(msg->stream_id));
+ set_uint16(out+V0_LEN_OFFSET, htons(msg->length));
+ memcpy(out + RELAY_HEADER_SIZE_V0, msg->body, msg->length);
+ relay_cell_pad(cell_out, RELAY_HEADER_SIZE_V0 + msg->length);
+
+ return 0;
+}
+
+/** Encode the relay message in 'msg' into cell, according to the
+ * v0 rules. */
+static int
+encode_v1_cell(const relay_msg_t *msg,
+ cell_t *cell_out)
+{
+ bool expects_streamid = relay_cmd_expects_streamid_in_v1(msg->command);
+ size_t maxlen;
+ if (expects_streamid) {
+ maxlen = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID;
+ } else {
+ maxlen = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID;
+ }
+
+ if (BUG(msg->length > maxlen))
+ return -1;
+
+ uint8_t *out = cell_out->payload;
+ out[V1_CMD_OFFSET] = msg->command;
+ set_uint16(out+V1_LEN_OFFSET, htons(msg->length));
+ size_t payload_offset;
+ if (expects_streamid) {
+ if (BUG(msg->stream_id == 0))
+ return -1;
+ set_uint16(out+V1_STREAM_ID_OFFSET, htons(msg->stream_id));
+ payload_offset = V1_PAYLOAD_OFFSET_WITH_STREAM_ID;
+ } else {
+ if (BUG(msg->stream_id != 0))
+ return -1;
+
+ payload_offset = V1_PAYLOAD_OFFSET_NO_STREAM_ID;
+ }
+
+ memcpy(out + payload_offset, msg->body, msg->length);
+ relay_cell_pad(cell_out, payload_offset + msg->length);
+ return 0;
+}
+
+/** Try to decode 'cell' into a newly allocated V0 relay message.
+ *
+ * Return NULL on error.
+ */
+static relay_msg_t *
+decode_v0_cell(const cell_t *cell)
+{
+ relay_msg_t *out = tor_malloc_zero(sizeof(relay_msg_t));
+ out->is_relay_early = (cell->command == CELL_RELAY_EARLY);
+
+ const uint8_t *body = cell->payload;
+ out->command = get_uint8(body + V0_CMD_OFFSET);
+ out->stream_id = ntohs(get_uint16(body + V0_STREAM_ID_OFFSET));
+ out->length = ntohs(get_uint16(body + V0_LEN_OFFSET));
+
+ if (out->length > CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0) {
+ goto err;
+ }
+ out->body = tor_memdup_nulterm(body + V0_PAYLOAD_OFFSET, out->length);
+
+ return out;
+ err:
+ relay_msg_free(out);
+ return NULL;
+}
+
+/** Try to decode 'cell' into a newly allocated V0 relay message.
+ *
+ * Return NULL on error.
+ */
+static relay_msg_t *
+decode_v1_cell(const cell_t *cell)
+{
+ relay_msg_t *out = tor_malloc_zero(sizeof(relay_msg_t));
+ out->is_relay_early = (cell->command == CELL_RELAY_EARLY);
+
+ const uint8_t *body = cell->payload;
+ out->command = get_uint8(body + V1_CMD_OFFSET);
+ if (! is_known_relay_command(out->command))
+ goto err;
+ out->length = ntohs(get_uint16(body + V1_LEN_OFFSET));
+ size_t payload_offset;
+ if (relay_cmd_expects_streamid_in_v1(out->command)) {
+ out->stream_id = ntohs(get_uint16(body + V1_STREAM_ID_OFFSET));
+ payload_offset = V1_PAYLOAD_OFFSET_WITH_STREAM_ID;
+ } else {
+ payload_offset = V1_PAYLOAD_OFFSET_NO_STREAM_ID;
+ }
+
+ if (out->length > CELL_PAYLOAD_SIZE - payload_offset)
+ goto err;
+ out->body = tor_memdup_nulterm(body + payload_offset, out->length);
+
+ return out;
+ err:
+ relay_msg_free(out);
+ return NULL;
+}
+/**
+ * Encode 'msg' into 'cell' according to the rules of 'format'.
+ *
+ * Does not set any "recognized", "digest" or "tag" fields,
+ * since those are necessarily part of the crypto logic.
+ *
+ * Clears the circuit ID on the cell.
+ *
+ * Return 0 on success, and -1 if 'msg' is not well-formed.
+ */
+int
+relay_msg_encode_cell(relay_cell_fmt_t format,
+ const relay_msg_t *msg,
+ cell_t *cell_out)
+{
+ memset(cell_out, 0, sizeof(cell_t));
+ cell_out->relay_cell_proto = format;
+ cell_out->command = msg->is_relay_early ?
+ CELL_RELAY_EARLY : CELL_RELAY;
+
+ switch (format) {
+ case RELAY_CELL_FORMAT_V0:
+ return encode_v0_cell(msg, cell_out);
+ case RELAY_CELL_FORMAT_V1:
+ return encode_v1_cell(msg, cell_out);
+ default:
+ tor_fragile_assert();
+ return -1;
+ }
+}
+
+/**
+ * Decode 'cell' (which must be RELAY or RELAY_EARLY) into a newly allocated
+ * 'relay_msg_t'.
+ *
+ * Return NULL on error.
+ */
+relay_msg_t *
+relay_msg_decode_cell(relay_cell_fmt_t format,
+ const cell_t *cell)
+{
+ // TODO #41051: Either remove the format argument here,
+ // or the format field in cell_t.
+ tor_assert(cell->relay_cell_proto == format);
+
+ switch (format) {
+ case RELAY_CELL_FORMAT_V0:
+ return decode_v0_cell(cell);
+ case RELAY_CELL_FORMAT_V1:
+ return decode_v1_cell(cell);
+ default:
+ tor_fragile_assert();
+ return NULL;
+ }
+}
diff --git a/src/core/or/relay_msg.h b/src/core/or/relay_msg.h
@@ -17,6 +17,13 @@
void relay_msg_free_(relay_msg_t *msg);
void relay_msg_clear(relay_msg_t *msg);
+int relay_msg_encode_cell(relay_cell_fmt_t format,
+ const relay_msg_t *msg,
+ cell_t *cell_out) ATTR_WUR;
+relay_msg_t *relay_msg_decode_cell(
+ relay_cell_fmt_t format,
+ const cell_t *cell) ATTR_WUR;
+
#define relay_msg_free(msg) \
FREE_AND_NULL(relay_msg_t, relay_msg_free_, (msg))
diff --git a/src/test/test_cell_formats.c b/src/test/test_cell_formats.c
@@ -17,9 +17,11 @@
#include "core/crypto/onion_fast.h"
#include "core/crypto/onion_ntor.h"
#include "core/or/relay.h"
+#include "core/or/relay_msg.h"
#include "core/or/cell_st.h"
#include "core/or/cell_queue_st.h"
+#include "core/or/relay_msg_st.h"
#include "core/or/var_cell_st.h"
#include "test/test.h"
@@ -1200,6 +1202,411 @@ test_cfmt_is_destroy(void *arg)
tor_free(chan);
}
+static void
+test_cfmt_relay_msg_encoding_simple(void *arg)
+{
+ (void)arg;
+ relay_msg_t *msg1 = NULL;
+ cell_t cell;
+ char *mem_op_hex_tmp = NULL;
+ int r;
+
+ /* Simple message: Data, fits easily in cell. */
+ msg1 = tor_malloc_zero(sizeof(relay_msg_t));
+ msg1->command = RELAY_COMMAND_DATA;
+ msg1->stream_id = 0x250;
+ msg1->length = 11;
+ msg1->body = tor_memdup("hello world", 11);
+
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(cell.command, OP_EQ, CELL_RELAY);
+ tt_int_op(cell.circ_id, OP_EQ, 0);
+ // command, recognized, streamid, digest, len, payload, zero-padding.
+ test_memeq_hex(cell.payload,
+ "02" "0000" "0250" "00000000" "000B"
+ "68656c6c6f20776f726c64" "00000000");
+ // random padding
+ size_t used = RELAY_HEADER_SIZE_V0 + 11 + 4;
+ tt_assert(!fast_mem_is_zero((char*)cell.payload + used,
+ CELL_PAYLOAD_SIZE - used));
+
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(cell.command, OP_EQ, CELL_RELAY);
+ tt_int_op(cell.circ_id, OP_EQ, 0);
+ // tag, command, len, optional streamid, payload, zero-padding
+ test_memeq_hex(cell.payload,
+ "00000000000000000000000000000000"
+ "02" "000B" "0250"
+ "68656c6c6f20776f726c64" "00000000");
+ // random padding.
+ used = RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 11 + 4;
+ tt_assert(!fast_mem_is_zero((char*)cell.payload + used,
+ CELL_PAYLOAD_SIZE - used));
+
+ /* Message without stream ID: SENDME, fits easily in cell. */
+ relay_msg_clear(msg1);
+ msg1->command = RELAY_COMMAND_SENDME;
+ msg1->stream_id = 0;
+ msg1->length = 20;
+ msg1->body = tor_memdup("hello i am a tag....", 20);
+
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(cell.command, OP_EQ, CELL_RELAY);
+ tt_int_op(cell.circ_id, OP_EQ, 0);
+ // command, recognized, streamid, digest, len, payload, zero-padding.
+ test_memeq_hex(cell.payload,
+ "05" "0000" "0000" "00000000" "0014"
+ "68656c6c6f206920616d2061207461672e2e2e2e" "00000000");
+ // random padding
+ used = RELAY_HEADER_SIZE_V0 + 20 + 4;
+ tt_assert(!fast_mem_is_zero((char*)cell.payload + used,
+ CELL_PAYLOAD_SIZE - used));
+
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, 0);
+ tt_int_op(cell.command, OP_EQ, CELL_RELAY);
+ tt_int_op(cell.circ_id, OP_EQ, 0);
+ // tag, command, len, optional streamid, payload, zero-padding
+ test_memeq_hex(cell.payload,
+ "00000000000000000000000000000000"
+ "05" "0014"
+ "68656c6c6f206920616d2061207461672e2e2e2e" "00000000");
+ // random padding.
+ used = RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 20 + 4;
+ tt_assert(!fast_mem_is_zero((char*)cell.payload + used,
+ CELL_PAYLOAD_SIZE - used));
+
+ done:
+ relay_msg_free(msg1);
+ tor_free(mem_op_hex_tmp);
+}
+
+/** Helper for test_cfmt_relay_cell_padding.
+ * Requires that that the body of 'msg' ends with 'pre_padding_byte',
+ * and that when encoded, the zero-padding (if any) will appear at
+ * offset 'zeros_begin_at' in the message.
+ */
+static void
+msg_encoder_padding_test(const relay_msg_t *msg,
+ relay_cell_fmt_t fmt,
+ uint8_t pre_padding_byte,
+ size_t zeros_begin_at)
+{
+ cell_t cell;
+ int n = 16, i;
+ /* We set this to 0 as soon as we find that the first byte of
+ * random padding has been set. */
+ bool padded_first = false;
+ /* We set this to true as soon as we find that the last byte of
+ * random padding has been set */
+ bool padded_last = false;
+
+ tt_int_op(zeros_begin_at, OP_LE, CELL_PAYLOAD_SIZE);
+
+ size_t expect_n_zeros = MIN(4, CELL_PAYLOAD_SIZE - zeros_begin_at);
+ ssize_t first_random_at = -1;
+ if (CELL_PAYLOAD_SIZE - zeros_begin_at > 4) {
+ first_random_at = CELL_PAYLOAD_SIZE - zeros_begin_at + 4;
+ }
+
+ for (i = 0; i < n; ++i) {
+ memset(&cell, 0, sizeof(cell));
+ tt_int_op(0, OP_EQ,
+ relay_msg_encode_cell(fmt, msg, &cell));
+
+ const uint8_t *body = cell.payload;
+ tt_int_op(body[zeros_begin_at - 1], OP_EQ, pre_padding_byte);
+
+ if (expect_n_zeros) {
+ tt_assert(fast_mem_is_zero((char*)body + zeros_begin_at,
+ expect_n_zeros));
+ }
+ if (first_random_at >= 0) {
+ if (body[first_random_at])
+ padded_first = true;
+ if (body[CELL_PAYLOAD_SIZE-1])
+ padded_last = true;
+ }
+ }
+
+ if (first_random_at >= 0) {
+ tt_assert(padded_first);
+ tt_assert(padded_last);
+ }
+
+ done:
+ ;
+}
+
+static void
+test_cfmt_relay_cell_padding(void *arg)
+{
+ (void)arg;
+ relay_msg_t *msg1 = NULL;
+
+ /* Simple message; we'll adjust the length and encode it. */
+ msg1 = tor_malloc_zero(sizeof(relay_msg_t));
+ msg1->command = RELAY_COMMAND_DATA;
+ msg1->stream_id = 0x250;
+ msg1->body = tor_malloc(500); // Longer than it needs to be.
+ memset(msg1->body, 0xff, 500);
+
+ // Empty message
+ msg1->length = 0;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0x00,
+ RELAY_HEADER_SIZE_V0);
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0x50,
+ RELAY_HEADER_SIZE_V1_WITH_STREAM_ID);
+
+ // Short message
+ msg1->length = 10;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff,
+ RELAY_HEADER_SIZE_V0 + 10);
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 10);
+
+ // Message where zeros extend exactly up to the end of the cell.
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 - 4;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff,
+ RELAY_HEADER_SIZE_V0 + msg1->length);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID - 4;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length);
+
+ // Message where zeros would intersect with the end of the cell.
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 - 3;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff,
+ RELAY_HEADER_SIZE_V0 + msg1->length);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID - 3;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length);
+
+ // Message with no room for zeros
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V0, 0xff,
+ RELAY_HEADER_SIZE_V0 + msg1->length);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + msg1->length);
+
+ ///////////////
+ // V1 cases with no stream ID.
+ msg1->stream_id = 0;
+ msg1->command = RELAY_COMMAND_EXTENDED;
+
+ msg1->length = 0;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0x00,
+ RELAY_HEADER_SIZE_V1_NO_STREAM_ID);
+ msg1->length = 10;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 10);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID - 4;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID - 3;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length);
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID;
+ msg_encoder_padding_test(msg1, RELAY_CELL_FORMAT_V1, 0xff,
+ RELAY_HEADER_SIZE_V1_NO_STREAM_ID + msg1->length);
+
+ relay_msg_free(msg1);
+}
+
+static void
+test_cfmt_relay_msg_encoding_error(void *arg)
+{
+ (void)arg;
+ relay_msg_t *msg1 = NULL;
+ int r;
+ cell_t cell;
+
+ msg1 = tor_malloc_zero(sizeof(relay_msg_t));
+ msg1->command = RELAY_COMMAND_DATA;
+ msg1->stream_id = 0x250;
+ msg1->body = tor_malloc(500); // Longer than it needs to be.
+ memset(msg1->body, 0xff, 500);
+
+ tor_capture_bugs_(5);
+ // Too long for v0.
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V0 + 1;
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V0, msg1, &cell);
+ tt_int_op(r, OP_EQ, -1);
+
+ // Too long for v1, with stream ID.
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_WITH_STREAM_ID + 1;
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, -1);
+
+ // Too long for v1 with no stream ID.
+ msg1->command = RELAY_COMMAND_EXTENDED;
+ msg1->stream_id = 0;
+ msg1->length = CELL_PAYLOAD_SIZE - RELAY_HEADER_SIZE_V1_NO_STREAM_ID + 1;
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, -1);
+
+ // Invalid (present) stream ID for V1.
+ msg1->stream_id = 10;
+ msg1->length = 20;
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, -1);
+
+ // Invalid (absent) stream ID for V1.
+ msg1->stream_id = 0;
+ msg1->command = RELAY_COMMAND_DATA;
+ r = relay_msg_encode_cell(RELAY_CELL_FORMAT_V1, msg1, &cell);
+ tt_int_op(r, OP_EQ, -1);
+
+ done:
+ tor_end_capture_bugs_();
+ relay_msg_free(msg1);
+}
+
+static void
+test_cfmt_relay_msg_decoding_simple(void *arg)
+{
+ (void) arg;
+ cell_t cell;
+ relay_msg_t *msg1 = NULL;
+ const char *s;
+
+ memset(&cell, 0, sizeof(cell));
+ cell.command = CELL_RELAY;
+
+ // V0 decoding, short message.
+ s = "02" "0000" "0250" "00000000" "000B"
+ "68656c6c6f20776f726c64" "00000000";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ cell.relay_cell_proto = RELAY_CELL_FORMAT_V0;
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell);
+ tt_assert(msg1);
+
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA);
+ tt_int_op(msg1->stream_id, OP_EQ, 0x250);
+ tt_int_op(msg1->length, OP_EQ, 11);
+ tt_mem_op(msg1->body, OP_EQ, "hello world", 11);
+ relay_msg_free(msg1);
+
+ // V0 decoding, message up to length of cell.
+ memset(cell.payload, 0, sizeof(cell.payload));
+ s = "02" "0000" "0250" "00000000" "01F2";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell);
+ tt_assert(msg1);
+
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA);
+ tt_int_op(msg1->stream_id, OP_EQ, 0x250);
+ tt_int_op(msg1->length, OP_EQ, 498);
+ tt_assert(fast_mem_is_zero((char*)msg1->body, 498));
+ relay_msg_free(msg1);
+
+ // V1 decoding, short message, no stream ID.
+ s = "00000000000000000000000000000000"
+ "05" "0014"
+ "68656c6c6f206920616d2061207461672e2e2e2e" "00000000";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ cell.relay_cell_proto = RELAY_CELL_FORMAT_V1;
+
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_assert(msg1);
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_SENDME);
+ tt_int_op(msg1->stream_id, OP_EQ, 0);
+ tt_int_op(msg1->length, OP_EQ, 20);
+ tt_mem_op(msg1->body, OP_EQ, "hello i am a tag....", 20);
+ relay_msg_free(msg1);
+
+ // V1 decoding, up to length of cell, no stream ID.
+ memset(cell.payload, 0, sizeof(cell.payload));
+ s = "00000000000000000000000000000000"
+ "05" "01EA";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_assert(msg1);
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_SENDME);
+ tt_int_op(msg1->stream_id, OP_EQ, 0);
+ tt_int_op(msg1->length, OP_EQ, 490);
+ tt_assert(fast_mem_is_zero((char*)msg1->body, 490));
+ relay_msg_free(msg1);
+
+ // V1 decoding, short message, with stream ID.
+ s = "00000000000000000000000000000000"
+ "02" "000B" "0250"
+ "68656c6c6f20776f726c64" "00000000";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_assert(msg1);
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA);
+ tt_int_op(msg1->stream_id, OP_EQ, 0x250);
+ tt_int_op(msg1->length, OP_EQ, 11);
+ tt_mem_op(msg1->body, OP_EQ, "hello world", 11);
+ relay_msg_free(msg1);
+
+ // V1 decoding, up to length of cell, with stream ID.
+ memset(cell.payload, 0, sizeof(cell.payload));
+ s = "00000000000000000000000000000000"
+ "02" "01E8" "0250";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_assert(msg1);
+ tt_int_op(msg1->command, OP_EQ, RELAY_COMMAND_DATA);
+ tt_int_op(msg1->stream_id, OP_EQ, 0x250);
+ tt_int_op(msg1->length, OP_EQ, 488);
+ tt_assert(fast_mem_is_zero((char*)msg1->body, 488));
+ relay_msg_free(msg1);
+
+ done:
+ relay_msg_free(msg1);
+}
+
+static void
+test_cfmt_relay_msg_decoding_error(void *arg)
+{
+ (void) arg;
+ relay_msg_t *msg1 = NULL;
+ cell_t cell;
+ const char *s;
+ memset(&cell, 0, sizeof(cell));
+
+ // V0, too long.
+ cell.command = CELL_RELAY;
+ cell.relay_cell_proto = RELAY_CELL_FORMAT_V0;
+ s = "02" "0000" "0250" "00000000" "01F3";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V0, &cell);
+ tt_ptr_op(msg1, OP_EQ, NULL);
+
+ // V1, command unrecognized.
+ cell.relay_cell_proto = RELAY_CELL_FORMAT_V1;
+ s = "00000000000000000000000000000000"
+ "F0" "000C" "0250";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_ptr_op(msg1, OP_EQ, NULL);
+
+ // V1, too long (with stream ID)
+ s = "00000000000000000000000000000000"
+ "02" "01E9" "0250";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_ptr_op(msg1, OP_EQ, NULL);
+
+ // V1, too long (without stream ID)
+ s = "00000000000000000000000000000000"
+ "05" "01EB";
+ base16_decode((char*)cell.payload, sizeof(cell.payload), s, strlen(s));
+ msg1 = relay_msg_decode_cell(RELAY_CELL_FORMAT_V1, &cell);
+ tt_ptr_op(msg1, OP_EQ, NULL);
+
+ done:
+ relay_msg_free(msg1);
+}
+
#define TEST(name, flags) \
{ #name, test_cfmt_ ## name, flags, 0, NULL }
@@ -1213,5 +1620,10 @@ struct testcase_t cell_format_tests[] = {
TEST(extended_cells, 0),
TEST(resolved_cells, 0),
TEST(is_destroy, 0),
+ TEST(relay_msg_encoding_simple, 0),
+ TEST(relay_cell_padding, 0),
+ TEST(relay_msg_encoding_error, 0),
+ TEST(relay_msg_decoding_simple, 0),
+ TEST(relay_msg_decoding_error, 0),
END_OF_TESTCASES
};
diff --git a/src/test/test_sendme.c b/src/test/test_sendme.c
@@ -204,48 +204,6 @@ test_v1_build_cell(void *arg)
}
static void
-test_cell_payload_pad(void *arg)
-{
- size_t pad_offset, payload_len, expected_offset;
-
- (void) arg;
-
- /* Offset should be 0, not enough room for padding. */
- payload_len = RELAY_PAYLOAD_SIZE;
- pad_offset = get_pad_cell_offset(payload_len, 0);
- tt_int_op(pad_offset, OP_EQ, 0);
- tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
-
- /* Still no room because we keep 4 extra bytes. */
- pad_offset = get_pad_cell_offset(payload_len - 4, 0);
- tt_int_op(pad_offset, OP_EQ, 0);
- tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
-
- /* We should have 1 byte of padding. Meaning, the offset should be the
- * CELL_PAYLOAD_SIZE minus 1 byte. */
- expected_offset = CELL_PAYLOAD_SIZE - 1;
- pad_offset = get_pad_cell_offset(payload_len - 5, 0);
- tt_int_op(pad_offset, OP_EQ, expected_offset);
- tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
-
- /* Now some arbitrary small payload length. The cell size is header + 10 +
- * extra 4 bytes we keep so the offset should be there. */
- expected_offset = RELAY_HEADER_SIZE + 10 + 4;
- pad_offset = get_pad_cell_offset(10, 0);
- tt_int_op(pad_offset, OP_EQ, expected_offset);
- tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
-
- /* Data length of 0. */
- expected_offset = RELAY_HEADER_SIZE + 4;
- pad_offset = get_pad_cell_offset(0, 0);
- tt_int_op(pad_offset, OP_EQ, expected_offset);
- tt_int_op(CELL_PAYLOAD_SIZE - pad_offset, OP_LE, CELL_PAYLOAD_SIZE);
-
- done:
- ;
-}
-
-static void
test_cell_version_validation(void *arg)
{
(void) arg;
@@ -401,8 +359,6 @@ struct testcase_t sendme_tests[] = {
NULL, NULL },
{ "v1_build_cell", test_v1_build_cell, TT_FORK,
NULL, NULL },
- { "cell_payload_pad", test_cell_payload_pad, TT_FORK,
- NULL, NULL },
{ "cell_version_validation", test_cell_version_validation, TT_FORK,
NULL, NULL },
{ "package_payload_len", test_package_payload_len, 0, NULL, NULL },