]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
prop359: Implement relay cell encoder/decoders
authorNick Mathewson <nickm@torproject.org>
Thu, 17 Apr 2025 17:15:04 +0000 (13:15 -0400)
committerNick Mathewson <nickm@torproject.org>
Mon, 5 May 2025 17:07:02 +0000 (13:07 -0400)
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.

src/core/or/include.am
src/core/or/or.h
src/core/or/relay_cell.c
src/core/or/relay_cell.h
src/core/or/relay_msg.c
src/core/or/relay_msg.h
src/test/test_cell_formats.c
src/test/test_sendme.c

index b4a1ce66537bb92832c35c20ac40fe7f799ad4dc..09ec617ce2196f4db80e7ad5fa052d4c9a17b124 100644 (file)
@@ -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         \
index 1b3accfa3828a0f423523a9ae3d04aff87cce22a..8204554245e6ad65f8bd32a1ed0e4746f94778f5 100644 (file)
@@ -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 */
index 219b105b245d7559e44750adce10c12112a6c34c..32fcfae0f92a7b8765dfd2e98bed749deaba79af 100644 (file)
 #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);
-}
index 05f0e9f10da3f1f362d375fb51dd087eaf1daa37..9468355196035a0fd635ffc414b807e5d976fc87 100644 (file)
 
 #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 */
index fa25019c5ecf87f61c251667253349142fc24b0d..2fe1d1c62271586c9f61e75cca40d401655cbf3d 100644 (file)
 
 #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;
+  }
+}
index 4dc6c109f754d2d40957331482ba89d5abc51118..a0647330e7118c4ed3fd34c54d97550ad44c94fc 100644 (file)
 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))
 
index e01a3461fcdc6ade6df53f3dae08154e2e349133..d3ad68f89add95e26cbe01e4dea5f5bbd6dbcdc7 100644 (file)
 #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
 };
index 18e2bde164a18b911df5bddc632536e8ec8d7271..6dd18425019afe240238dd07d3a50ab893313f2b 100644 (file)
@@ -203,48 +203,6 @@ test_v1_build_cell(void *arg)
   circuit_free_(circ);
 }
 
-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)
 {
@@ -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 },