[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [stegotorus/master] Revise chop.cc to be closer to what the paper says it does:



commit 49302db0cd095bbc02c631e1628542f4d8b003a9
Author: Zack Weinberg <zackw@xxxxxxxxx>
Date:   Sat Mar 24 20:27:50 2012 -0700

    Revise chop.cc to be closer to what the paper says it does:
    
    * new block format
    * 32-bit circuit IDs
    * separate header and payload encryption
    * sequence numbers count by block, not byte
    
    Still to do:
    
    * proper handshakes
    * rekeying
    * "exactly what steg wants" block padding
---
 src/crypt.cc         |    4 +-
 src/protocol/chop.cc | 2059 +++++++++++++++++++++++++-------------------------
 2 files changed, 1038 insertions(+), 1025 deletions(-)

diff --git a/src/crypt.cc b/src/crypt.cc
index d6eed49..e81e8d4 100644
--- a/src/crypt.cc
+++ b/src/crypt.cc
@@ -273,9 +273,7 @@ key_generator::from_passphrase(const uint8_t *phra, size_t plen,
     // to just feed its output directly to the HKDF-Expand phase; an
     // alternative would be to run PBKDF2 on the passphrase without a
     // salt, then put the result through HKDF-Extract with the salt.
-    //
-    // 1000 iterations or 50 ms, whichever is more
-    extractor.DeriveKey(prk, SHA256_LEN, 0, phra, plen, salt, slen, 1000, 0.05);
+    extractor.DeriveKey(prk, SHA256_LEN, 0, phra, plen, salt, slen, 1000);
 
     key_generator *r = new key_generator_impl(prk, ctxt, clen);
     memset(prk, 0, SHA256_LEN);
diff --git a/src/protocol/chop.cc b/src/protocol/chop.cc
index b4ec692..e81214a 100644
--- a/src/protocol/chop.cc
+++ b/src/protocol/chop.cc
@@ -1,10 +1,9 @@
-/* Copyright 2011 Zack Weinberg
+/* Copyright 2011, 2012 Zack Weinberg
    See LICENSE for other credits and copying information
 
    The chopper is the core StegoTorus protocol implementation.
-   For its design, see doc/chopper.tex.  Note that it is still
-   being implemented, and many things that are *intended* to change
-   from the toy "roundrobin" protocol have not yet changed.  */
+   For its design, see doc/chopper.txt.  Note that it is still
+   being implemented, and may change incompatibly.  */
 
 #include "util.h"
 #include "connections.h"
@@ -20,805 +19,363 @@
 #include <event2/event.h>
 #include <event2/buffer.h>
 
+#ifdef HAVE_EXECINFO_H
+#include <execinfo.h>
+#endif
+
 using std::tr1::unordered_map;
 using std::tr1::unordered_set;
 using std::vector;
+using std::make_pair;
 
-/* Header serialization and deserialization */
-
-struct chop_header
+namespace
 {
-  uint64_t ckt_id;
-  uint8_t  pkt_iv[8];
-  uint32_t offset;
-  uint16_t length;
-  uint16_t flags;
-};
-
-#define CHOP_WIRE_HDR_LEN (sizeof(struct chop_header))
-#define CHOP_BLOCK_OVERHD (CHOP_WIRE_HDR_LEN + GCM_TAG_LEN)
-#define CHOP_MAX_DATA (65535 - CHOP_BLOCK_OVERHD)
-#define CHOP_MAX_CHAFF 2048
 
-#define CHOP_F_SYN   0x0001
-#define CHOP_F_FIN   0x0002
-#define CHOP_F_CHAFF 0x0004
-/* further flags values are reserved */
-
-/* Reassembly queue.  This is a doubly-linked circular list with a
-   sentinel element at the head (identified by data == 0).  List
-   entries are sorted by offset.  Gaps in so-far-received data
-   are "in between" entries in the list.  */
-
-struct chop_reassembly_elt
+/* Packets on the wire have a 16-byte header, consisting of a 32-bit
+   sequence number, two 16-bit length fields ("D" and "P"), an 8-bit
+   opcode ("F"), and a 56-bit check field.  All numbers in this header
+   are serialized in network byte order.
+
+   | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
+   |Sequence Number|   D   |   P   | F |           Check           |
+
+   The header is encrypted with AES in ECB mode: this is safe because
+   the header is exactly one AES block long, the sequence number is
+   never repeated, the header-encryption key is not used for anything
+   else, and the high 24 bits of the sequence number, plus the check
+   field, constitute an 80-bit MAC.  The receiver maintains a
+   256-element sliding window of acceptable sequence numbers, which
+   begins one after the highest sequence number so far _processed_
+   (not received).  If the sequence number is outside this window, or
+   the check field is not all-bits-zero, the packet is discarded.  An
+   attacker's odds of being able to manipulate the D, P, or F fields
+   or the low bits of the sequence number are therefore less than one
+   in 2^80.  Unlike TCP, our sequence numbers always start at zero on
+   a new (or freshly rekeyed) circuit, and increment by one per
+   _block_, not per byte of data.  Furthermore, they do not wrap: a
+   rekeying cycle (which resets the sequence number) is required to
+   occur before the highest-received sequence number reaches 2^32.
+
+   Following the header are two variable-length payload sections,
+   "data" and "padding", whose length in bytes are given by the D and
+   P fields, respectively.  These sections are encrypted, using a
+   different key, with AES in GCM mode.  The *encrypted* packet header
+   doubles as the GCM nonce.  The semantics of the "data" section's
+   contents, if any, are defined by the opcode F.  The "padding"
+   section SHOULD be filled with zeroes by the sender; regardless, its
+   contents MUST be ignored by the receiver.  Following these sections
+   is a 16-byte GCM authentication tag, computed over the data and
+   padding sections only, NOT the message header.  */
+
+const size_t HEADER_LEN = 16;
+const size_t TRAILER_LEN = 16;
+const size_t SECTION_LEN = UINT16_MAX;
+const size_t MIN_BLOCK_SIZE = HEADER_LEN + TRAILER_LEN;
+const size_t MAX_BLOCK_SIZE = MIN_BLOCK_SIZE + SECTION_LEN*2;
+
+enum opcode_t
 {
-  struct chop_reassembly_elt *prev;
-  struct chop_reassembly_elt *next;
-  struct evbuffer *data;
-  uint32_t offset;
-  uint16_t length;
-  uint16_t flags;
+  op_DAT = 0,       // Pass data section along to upstream
+  op_FIN = 1,       // No further transmissions (pass data along if any)
+  op_RST = 2,       // Protocol error, close circuit now
+  op_RK1 = 3,       // Commence rekeying
+  op_RK2 = 4,       // Continue rekeying
+  op_RK3 = 5,       // Conclude rekeying
+  op_RESERVED0 = 6, // 6 -- 127 reserved for future definition
+  op_STEG0 = 128,   // 128 -- 255 reserved for steganography modules
+  op_LAST = 255
 };
 
-/* Horrifically crude "encryption".  Uses a compiled-in pair of
-   encryption keys, no MAC, and recycles the circuit ID as a
-   partial IV.  To be replaced with something less laughable ASAP. */
-
-static const uint8_t c2s_key[] =
-  "\x44\x69\x5f\x45\x41\x67\xe9\x69\x14\x6c\x5f\xd2\x41\x63\xc4\x02";
-static const uint8_t s2c_key[] =
-  "\xfa\x31\x78\x6c\xb9\x4c\x66\x2a\xd0\x30\x59\xf7\x28\x22\x2f\x22";
-
-/* Connections and circuits */
-
-namespace {
-  struct chop_config_t;
-  struct chop_circuit_t;
-  typedef unordered_map<uint64_t, chop_circuit_t *> chop_circuit_table;
-
-  struct chop_conn_t : conn_t
-  {
-    chop_config_t *config;
-    chop_circuit_t *upstream;
-    steg_t *steg;
-    struct evbuffer *recv_pending;
-    struct event *must_transmit_timer;
-    bool no_more_transmissions : 1;
-
-    CONN_DECLARE_METHODS(chop);
-  };
+class block_header
+{
+  uint8_t clear[16];
+  uint8_t ciphr[16];
 
-  struct chop_circuit_t : circuit_t
+public:
+  block_header(uint32_t s, uint16_t d, uint16_t p, opcode_t f,
+               ecb_encryptor &ec)
   {
-    chop_reassembly_elt reassembly_queue;
-    unordered_set<chop_conn_t *> downstreams;
-    gcm_encryptor *send_crypt;
-    gcm_decryptor *recv_crypt;
-    chop_config_t *config;
-
-    uint64_t circuit_id;
-    uint32_t send_offset;
-    uint32_t recv_offset;
-    uint32_t dead_cycles;
-    bool received_syn : 1;
-    bool received_fin : 1;
-    bool sent_syn : 1;
-    bool sent_fin : 1;
-    bool upstream_eof : 1;
-
-    CIRCUIT_DECLARE_METHODS(chop);
-
-    uint32_t axe_interval() {
-      // This function must always return a number which is larger than
-      // the maximum possible number that *our peer's* flush_interval()
-      // could have returned; otherwise, we might axe the connection when
-      // it was just that there was nothing to say for a while.
-      // For simplicity's sake, right now we hardwire this to be 30 minutes.
-      return 30 * 60 * 1000;
-    }
-    uint32_t flush_interval() {
-      // 10*60*1000 lies between 2^19 and 2^20.
-      uint32_t shift = std::max(1u, std::min(19u, dead_cycles));
-      uint32_t xv = std::max(1u, std::min(10u * 60 * 1000, 1u << shift));
-      return rng_range_geom(20 * 60 * 1000, xv) + 100;
+    if (f > op_LAST || (f >= op_RESERVED0 && f < op_STEG0)) {
+      memset(clear, 0xFF, sizeof clear); // invalid!
+      memset(ciphr, 0xFF, sizeof ciphr);
+      return;
     }
-  };
-
-  struct chop_config_t : config_t
-  {
-    struct evutil_addrinfo *up_address;
-    vector<struct evutil_addrinfo *> down_addresses;
-    vector<const char *> steg_targets;
-    chop_circuit_table circuits;
 
-    CONFIG_DECLARE_METHODS(chop);
-  };
-}
-
-PROTO_DEFINE_MODULE(chop);
+    // sequence number
+    clear[0] = (s >> 24) & 0xFF;
+    clear[1] = (s >> 16) & 0xFF;
+    clear[2] = (s >>  8) & 0xFF;
+    clear[3] = (s      ) & 0xFF;
 
-/* Header serialization and deserialization */
+    // D field
+    clear[4] = (d >>  8) & 0xFF;
+    clear[5] = (d      ) & 0xFF;
 
-static void
-chop_write_header(uint8_t *wire_header, const struct chop_header *hdr)
-{
-  /* bits on the wire are in network byte order */
-  wire_header[ 0] = (hdr->ckt_id & 0xFF00000000000000ull) >> 56;
-  wire_header[ 1] = (hdr->ckt_id & 0x00FF000000000000ull) >> 48;
-  wire_header[ 2] = (hdr->ckt_id & 0x0000FF0000000000ull) >> 40;
-  wire_header[ 3] = (hdr->ckt_id & 0x000000FF00000000ull) >> 32;
-  wire_header[ 4] = (hdr->ckt_id & 0x00000000FF000000ull) >> 24;
-  wire_header[ 5] = (hdr->ckt_id & 0x0000000000FF0000ull) >> 16;
-  wire_header[ 6] = (hdr->ckt_id & 0x000000000000FF00ull) >>  8;
-  wire_header[ 7] = (hdr->ckt_id & 0x00000000000000FFull) >>  0;
-
-  wire_header[ 8] = hdr->pkt_iv[0];
-  wire_header[ 9] = hdr->pkt_iv[1];
-  wire_header[10] = hdr->pkt_iv[2];
-  wire_header[11] = hdr->pkt_iv[3];
-  wire_header[12] = hdr->pkt_iv[4];
-  wire_header[13] = hdr->pkt_iv[5];
-  wire_header[14] = hdr->pkt_iv[6];
-  wire_header[15] = hdr->pkt_iv[7];
-
-  wire_header[16] = (hdr->offset & 0xFF000000u) >> 24;
-  wire_header[17] = (hdr->offset & 0x00FF0000u) >> 16;
-  wire_header[18] = (hdr->offset & 0x0000FF00u) >>  8;
-  wire_header[19] = (hdr->offset & 0x000000FFu) >>  0;
-
-  wire_header[20] = (hdr->length & 0xFF00u) >> 8;
-  wire_header[21] = (hdr->length & 0x00FFu) >> 0;
-  wire_header[22] = (hdr->flags  & 0xFF00u) >> 8;
-  wire_header[23] = (hdr->flags  & 0x00FFu) >> 0;
-}
+    // P field
+    clear[6] = (p >>  8) & 0xFF;
+    clear[7] = (p      ) & 0xFF;
 
-static int
-chop_peek_circuit_id(struct evbuffer *buf, struct chop_header *hdr)
-{
-  uint8_t wire_id[8];
-  if (evbuffer_copyout(buf, wire_id, 8) != 8)
-    return -1;
-  hdr->ckt_id = ((((uint64_t)wire_id[ 0]) << 56) +
-                 (((uint64_t)wire_id[ 1]) << 48) +
-                 (((uint64_t)wire_id[ 2]) << 40) +
-                 (((uint64_t)wire_id[ 3]) << 32) +
-                 (((uint64_t)wire_id[ 4]) << 24) +
-                 (((uint64_t)wire_id[ 5]) << 16) +
-                 (((uint64_t)wire_id[ 6]) <<  8) +
-                 (((uint64_t)wire_id[ 7]) <<  0));
-  return 0;
-}
+    // F field
+    clear[8] = uint8_t(f);
 
-static int
-chop_decrypt_header(chop_circuit_t *ckt,
-                    struct evbuffer *buf,
-                    struct chop_header *hdr)
-{
-  uint8_t wire_header[CHOP_WIRE_HDR_LEN];
-  uint8_t decoded_header[CHOP_WIRE_HDR_LEN-16];
+    // Check field
+    memset(clear + 9, 0, 7);
 
-  if (evbuffer_copyout(buf, wire_header, CHOP_WIRE_HDR_LEN)
-      != CHOP_WIRE_HDR_LEN) {
-    log_warn("not enough data copied out");
-    return -1;
+    ec.encrypt(ciphr, clear);
   }
 
-  hdr->ckt_id = ((((uint64_t)wire_header[ 0]) << 56) +
-                 (((uint64_t)wire_header[ 1]) << 48) +
-                 (((uint64_t)wire_header[ 2]) << 40) +
-                 (((uint64_t)wire_header[ 3]) << 32) +
-                 (((uint64_t)wire_header[ 4]) << 24) +
-                 (((uint64_t)wire_header[ 5]) << 16) +
-                 (((uint64_t)wire_header[ 6]) <<  8) +
-                 (((uint64_t)wire_header[ 7]) <<  0));
-
-  hdr->pkt_iv[0] = wire_header[ 8];
-  hdr->pkt_iv[1] = wire_header[ 9];
-  hdr->pkt_iv[2] = wire_header[10];
-  hdr->pkt_iv[3] = wire_header[11];
-  hdr->pkt_iv[4] = wire_header[12];
-  hdr->pkt_iv[5] = wire_header[13];
-  hdr->pkt_iv[6] = wire_header[14];
-  hdr->pkt_iv[7] = wire_header[15];
-
-  /* The full IV is the circuit ID plus packet ID *as it is on the
-     wire*. */
-  ckt->recv_crypt->decrypt_unchecked(decoded_header,
-                                     wire_header + 16, CHOP_WIRE_HDR_LEN - 16,
-                                     wire_header, 16);
-
-  hdr->offset = ((((uint32_t)decoded_header[0]) << 24) +
-                 (((uint32_t)decoded_header[1]) << 16) +
-                 (((uint32_t)decoded_header[2]) <<  8) +
-                 (((uint32_t)decoded_header[3]) <<  0));
-
-  hdr->length = ((((uint16_t)decoded_header[4]) << 8) +
-                 (((uint16_t)decoded_header[5]) << 0));
-
-  hdr->flags  = ((((uint16_t)decoded_header[6]) <<  8) +
-                 (((uint16_t)decoded_header[7]) <<  0));
-
-  log_debug("decoded offset %u length %hu flags %04hx",
-            hdr->offset, hdr->length, hdr->flags);
-  return 0;
-}
-
-/* Transmit subroutines. */
-
-static chop_conn_t *
-chop_pick_connection(chop_circuit_t *ckt, size_t desired, size_t *blocksize)
-{
-  size_t maxbelow = 0;
-  size_t minabove = SIZE_MAX;
-  chop_conn_t *targbelow = NULL;
-  chop_conn_t *targabove = NULL;
-
-  if (desired > CHOP_MAX_DATA)
-    desired = CHOP_MAX_DATA;
-
-  /* Find the best fit for the desired transmission from all the
-     outbound connections' transmit rooms. */
-  for (unordered_set<chop_conn_t *>::iterator i = ckt->downstreams.begin();
-       i != ckt->downstreams.end(); i++) {
-    chop_conn_t *conn = *i;
-    /* We can only use candidates that have a steg target already. */
-    if (conn->steg) {
-      /* Find the connections whose transmit rooms are closest to the
-         desired transmission length from both directions. */
-      size_t room = conn->steg->transmit_room(conn);
-      log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room,
-                conn->steg->name());
-
-      if (room <= CHOP_BLOCK_OVERHD)
-	room = 0;
-      else
-	room -= CHOP_BLOCK_OVERHD;
-
-      if (room > CHOP_MAX_DATA)
-        room = CHOP_MAX_DATA;
-
-      if (room >= desired) {
-        if (room < minabove) {
-          minabove = room;
-          targabove = conn;
-        }
-      } else {
-        if (room > maxbelow) {
-          maxbelow = room;
-          targbelow = conn;
-        }
-      }
-    } else {
-      log_debug(conn, "offers 0 bytes (no steg)");
+  block_header(evbuffer *buf, ecb_decryptor &dc)
+  {
+    if (evbuffer_copyout(buf, ciphr, sizeof ciphr) != sizeof ciphr) {
+      memset(clear, 0xFF, sizeof clear);
+      memset(ciphr, 0xFF, sizeof ciphr);
+      return;
     }
+    dc.decrypt(clear, ciphr);
   }
 
-  /* If we have a connection that can take all the data, use it.
-     Otherwise, use the connection that can take as much of the data
-     as possible.  As a special case, if no connection can take data,
-     targbelow, targabove, maxbelow, and minabove will all still have
-     their initial values, so we'll return NULL and set blocksize to 0,
-     which callers know how to handle. */
-  if (targabove) {
-    *blocksize = minabove;
-    return targabove;
-  } else {
-    *blocksize = maxbelow;
-    return targbelow;
-  }
-}
-
-static int
-chop_send_block(chop_conn_t *dest,
-                chop_circuit_t *ckt,
-                struct evbuffer *source,
-                struct evbuffer *block,
-                uint16_t length,
-                uint16_t flags)
-{
-  chop_header hdr;
-  struct evbuffer_iovec v;
-  uint8_t *p;
-
-  log_assert(evbuffer_get_length(block) == 0);
-  log_assert(evbuffer_get_length(source) >= length);
-  log_assert(dest->steg);
-
-  /* We take special care not to modify 'source' if any step fails. */
-  if (evbuffer_reserve_space(block,
-                             length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN,
-                             &v, 1) != 1)
-    return -1;
-  if (v.iov_len < length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN)
-    goto fail;
-
-  v.iov_len = length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN;
-
-  hdr.ckt_id = ckt->circuit_id;
-  hdr.offset = ckt->send_offset;
-  hdr.length = length;
-  hdr.flags = flags;
-  rng_bytes(hdr.pkt_iv, 8);
-  chop_write_header((uint8_t*)v.iov_base, &hdr);
-
-  if (evbuffer_copyout(source, (uint8_t *)v.iov_base + CHOP_WIRE_HDR_LEN,
-                       length) != length)
-    goto fail;
-
-  p = (uint8_t *)v.iov_base;
-  ckt->send_crypt->encrypt(p + 16, p + 16, length + CHOP_WIRE_HDR_LEN - 16,
-                           p, 16);
-
-  if (evbuffer_commit_space(block, &v, 1))
-    goto fail;
-
-  if (dest->steg->transmit(block, dest))
-    goto fail_committed;
-
-  if (evbuffer_drain(source, length))
-    /* this really should never happen, and we can't recover from it */
-    log_abort(dest, "evbuffer_drain failed"); /* does not return */
-
-  /* Cancel the must-transmit timer if it's pending; we have transmitted. */
-  if (dest->must_transmit_timer)
-    evtimer_del(dest->must_transmit_timer);
-
-  if (!(flags & CHOP_F_CHAFF))
-    ckt->send_offset += length;
-  if (flags & CHOP_F_SYN)
-    ckt->sent_syn = true;
-  if (flags & CHOP_F_FIN)
-    ckt->sent_fin = true;
-  log_debug(dest, "sent %lu+%u byte block [flags %04hx]",
-            (unsigned long)CHOP_WIRE_HDR_LEN, length, flags);
-
-  return 0;
-
- fail:
-  v.iov_len = 0;
-  evbuffer_commit_space(block, &v, 1);
- fail_committed:
-  evbuffer_drain(block, evbuffer_get_length(block));
-  log_warn(dest, "allocation or buffer copy failed");
-
-  return -1;
-}
-
-static int
-chop_send_blocks(chop_circuit_t *ckt)
-{
-  struct evbuffer *xmit_pending = bufferevent_get_input(ckt->up_buffer);
-  struct evbuffer *block;
-  chop_conn_t *target;
-  size_t avail;
-  size_t blocksize;
-  uint16_t flags;
+  uint32_t seqno() const
+  {
+    return ((uint32_t(clear[0]) << 24) |
+            (uint32_t(clear[1]) << 16) |
+            (uint32_t(clear[2]) <<  8) |
+            (uint32_t(clear[3])      ));
 
-  if (!(block = evbuffer_new())) {
-    log_warn(ckt, "allocation failure");
-    return -1;
   }
 
-  for (;;) {
-    avail = evbuffer_get_length(xmit_pending);
-    flags = ckt->sent_syn ? 0 : CHOP_F_SYN;
-
-    log_debug(ckt, "%lu bytes to send", (unsigned long)avail);
-
-    if (avail == 0)
-      break;
-
-    target = chop_pick_connection(ckt, avail, &blocksize);
-    if (!target) {
-      log_debug(ckt, "no target connection available");
-      /* this is not an error; it can happen e.g. when the server has
-         something to send immediately and the client hasn't spoken yet */
-      break;
-    }
-
-    if (avail <= blocksize) {
-      blocksize = avail;
-      if (ckt->upstream_eof && !ckt->sent_fin)
-        flags |= CHOP_F_FIN;
-    }
-
-    if (chop_send_block(target, ckt, xmit_pending, block, blocksize, flags)) {
-      evbuffer_free(block);
-      return -1;
-    }
+  size_t dlen() const
+  {
+    return ((uint16_t(clear[4]) << 8) |
+            (uint16_t(clear[5])     ));
   }
 
-  evbuffer_free(block);
-  avail = evbuffer_get_length(xmit_pending);
-  if (avail)
-    log_debug(ckt, "%lu bytes still waiting to be sent", (unsigned long)avail);
-  return 0;
-}
-
-static int
-chop_send_targeted(chop_circuit_t *ckt, chop_conn_t *target, size_t blocksize)
-{
-  struct evbuffer *xmit_pending = bufferevent_get_input(ckt->up_buffer);
-  size_t avail = evbuffer_get_length(xmit_pending);
-  struct evbuffer *block = evbuffer_new();
-  uint16_t flags = 0;
-
-  log_debug(target, "%lu bytes available, %lu bytes room",
-            (unsigned long)avail, (unsigned long)blocksize);
-  if (!block) {
-    log_warn(target, "allocation failure");
-    return -1;
+  size_t plen() const
+  {
+    return ((uint16_t(clear[6]) << 8) |
+            (uint16_t(clear[7])     ));
   }
 
-  if (!ckt->sent_syn)
-    flags |= CHOP_F_SYN;
-
-  if (avail) {
-    if (avail <= blocksize) {
-      blocksize = avail;
-      if (ckt->upstream_eof && !ckt->sent_fin)
-        flags |= CHOP_F_FIN;
-    }
-
-
-    if (chop_send_block(target, ckt, xmit_pending, block, blocksize, flags)) {
-      evbuffer_free(block);
-      return -1;
-    }
-
-    evbuffer_free(block);
-    avail = evbuffer_get_length(xmit_pending);
-    if (avail)
-      log_debug(ckt, "%lu bytes still waiting to be sent",
-                (unsigned long)avail);
-    return 0;
-
-  } else {
-    struct evbuffer *chaff;
-    struct evbuffer_iovec v;
-
-    if (blocksize > CHOP_MAX_CHAFF)
-      blocksize = CHOP_MAX_CHAFF;
-
-    blocksize = rng_range(1, blocksize + 1);
-    log_debug(target, "generating %lu bytes chaff", (unsigned long)blocksize);
-
-    chaff = evbuffer_new();
-    if (!chaff ||
-        evbuffer_reserve_space(chaff, blocksize, &v, 1) != 1 ||
-        v.iov_len < blocksize)
-      goto fail;
-
-    v.iov_len = blocksize;
-    memset(v.iov_base, 0, v.iov_len);
-    if (evbuffer_commit_space(chaff, &v, 1))
-      goto fail;
-
-    flags |= CHOP_F_CHAFF;
-    if (ckt->upstream_eof && !ckt->sent_fin)
-      flags |= CHOP_F_FIN;
-    if (chop_send_block(target, ckt, chaff, block, blocksize, flags))
-      goto fail;
-
-    evbuffer_free(chaff);
-    evbuffer_free(block);
-    return 0;
-
-  fail:
-    log_warn(target, "failed to construct chaff block");
-    if (chaff) evbuffer_free(chaff);
-    if (block) evbuffer_free(block);
-    return -1;
+  size_t total_len() const
+  {
+    return HEADER_LEN + TRAILER_LEN + dlen() + plen();
   }
-}
-
-static int
-chop_send_chaff(chop_circuit_t *ckt)
-{
-  size_t room;
 
-  chop_conn_t *target = chop_pick_connection(ckt, 1, &room);
-  if (!target) {
-    /* If we have connections and we can't send, that means we're waiting
-       for the server to respond.  Just wait. */
-    return 0;
+  opcode_t opcode() const
+  {
+    return opcode_t(clear[8]);
   }
-  return chop_send_targeted(ckt, target, room);
-}
-
-static void
-must_transmit_timer_cb(evutil_socket_t, short, void *arg)
-{
-  chop_conn_t *conn = static_cast<chop_conn_t*>(arg);
-  size_t room;
 
-  if (!conn->upstream) {
-    log_debug(conn, "must transmit, but no circuit (stale connection)");
-    conn_do_flush(conn);
-    return;
+  bool valid(uint64_t window) const
+  {
+    // This check must run in constant time.
+    uint8_t ck = (clear[ 9] | clear[10] | clear[11] | clear[12] |
+                  clear[13] | clear[14] | clear[15]);
+    uint32_t delta = seqno() - window;
+    ck |= !!(delta & ~uint32_t(0xFF));
+    return !ck;
   }
 
-  if (!conn->steg) {
-    log_warn(conn, "must transmit, but no steg module available");
-    return;
-  }
-  room = conn->steg->transmit_room(conn);
-  if (room <= CHOP_BLOCK_OVERHD) {
-    log_warn(conn, "must transmit, but no transmit room");
-    return;
+  const uint8_t *nonce() const
+  {
+    return ciphr;
   }
 
-  log_debug(conn, "must transmit");
-  chop_send_targeted(conn->upstream, conn, room - CHOP_BLOCK_OVERHD);
-}
+  const uint8_t *cleartext() const
+  {
+    return clear;
+  }
+};
 
-/* Receive subroutines. */
+/* Most of a block's header information is processed before it reaches
+   the reassembly queue; the only things the queue needs to record are
+   the sequence number (which is stored implictly), the opcode, and an
+   evbuffer holding the data section.  Zero-data blocks still get an
+   evbuffer, for simplicity's sake: a reassembly queue element holds a
+   received block if and only if its data pointer is non-null.
 
-/* True if s < t (mod 2**32). */
-static inline bool
-mod32_lt(uint32_t s, uint32_t t)
-{
-  uint32_t d = t - s;
-  return 0 < d && d < 0x80000000u;
-}
+   The reassembly queue is a 256-element circular buffer of
+   'reassembly_elt' structs.  This corresponds to the 256-element
+   sliding window of sequence numbers which may legitimately be
+   received at any time.  */
 
-/* True if s <= t (mod 2**32). */
-static inline bool
-mod32_le(uint32_t s, uint32_t t)
+struct reassembly_elt
 {
-  uint32_t d = t - s;
-  return d < 0x80000000u;
-}
+  evbuffer *data;
+  opcode_t op;
+};
 
-/** Add BLOCK to the reassembly queue at the appropriate location
-    and merge adjacent blocks to the extent possible. */
-static int
-chop_reassemble_block(chop_circuit_t *ckt, struct evbuffer *block,
-                      chop_header *hdr)
+class reassembly_queue
 {
-  chop_reassembly_elt *queue = &ckt->reassembly_queue;
-  chop_reassembly_elt *p, *q;
-
-  if (hdr->flags & CHOP_F_CHAFF) {
-    /* Chaff goes on the reassembly queue if it carries any flags that
-       must be processed in sequence (SYN, FIN), but we throw away its
-       contents.  Doing all chaff-handling here simplifies the caller
-       at the expense of slightly more buffer-management overhead. */
-    if (!(hdr->flags & (CHOP_F_SYN|CHOP_F_FIN))) {
-      log_debug(ckt, "discarding chaff with no flags");
-      evbuffer_free(block);
-      return 0;
-    }
+  reassembly_elt cbuf[256];
+  uint32_t next_to_process;
 
-    hdr->length = 0;
-    evbuffer_drain(block, evbuffer_get_length(block));
-    log_debug(ckt, "chaff with flags, treating length as 0");
-  }
+  reassembly_queue(const reassembly_queue&) DELETE_METHOD;
+  reassembly_queue& operator=(const reassembly_queue&) DELETE_METHOD;
 
-  /* SYN must occur at offset zero, may not be duplicated, and if we
-     already have anything on the reassembly queue, it must come
-     logically after this block. */
-  if ((hdr->flags & CHOP_F_SYN) &&
-      (hdr->offset > 0 ||
-       (queue->next != queue &&
-        ((queue->next->flags & CHOP_F_SYN) ||
-         !mod32_le(hdr->offset + hdr->length, queue->next->offset))))) {
-    log_warn(ckt, "protocol error: inappropriate SYN block");
-    return -1;
+public:
+  reassembly_queue()
+    : next_to_process(0)
+  {
+    memset(cbuf, 0, sizeof cbuf);
   }
 
-  /* FIN may not be duplicated and must occur logically after everything
-     we've already received. */
-  if ((hdr->flags & CHOP_F_FIN) && queue->prev != queue &&
-      ((queue->prev->flags & CHOP_F_FIN) ||
-       !mod32_le(queue->prev->offset + queue->prev->length, hdr->offset))) {
-    log_warn(ckt, "protocol error: inappropriate FIN block");
-    return -1;
+  ~reassembly_queue()
+  {
+    for (int i = 0; i < 256; i++)
+      if (cbuf[i].data)
+        evbuffer_free(cbuf[i].data);
   }
 
-  /* Non-SYN/FIN must come after any SYN block presently in the queue
-     and before any FIN block presently in the queue. */
-  if (!(hdr->flags & (CHOP_F_SYN|CHOP_F_FIN)) && queue->next != queue &&
-      (((queue->next->flags & CHOP_F_SYN) &&
-        !mod32_le(queue->next->offset + queue->next->length, hdr->offset)) ||
-       ((queue->prev->flags & CHOP_F_FIN) &&
-        !mod32_le(hdr->offset + hdr->length, queue->prev->offset)))) {
-    log_warn(ckt, "protocol error: inappropriate normal block");
-    return -1;
+  // Remove the next block to be processed from the reassembly queue
+  // and return it.  If we are out of blocks or the next block to
+  // process has not yet arrived, return an empty reassembly_elt.
+  // Caller is responsible for freeing the evbuffer in the
+  // reassembly_elt, if any.
+  reassembly_elt
+  remove_next()
+  {
+    reassembly_elt rv = { 0, op_DAT };
+    uint8_t front = next_to_process & 0xFF;
+    if (cbuf[front].data) {
+      rv = cbuf[front];
+      cbuf[front].data = 0;
+      cbuf[front].op   = op_DAT;
+      next_to_process++;
+    }
+    return rv;
   }
 
-  for (p = queue->next; p != queue; p = p->next) {
-    /* Try first to merge the new block into an existing one. */
-    if (hdr->offset + hdr->length == p->offset)
-      goto grow_front;
-
-    if (hdr->offset == p->offset + p->length)
-      goto grow_back;
-
-    /* Does this block fit in between 'p->prev' and 'p'?
-       Note: if 'p->prev->data' is NULL, it is the sentinel,
-       and p->prev->offset is meaningless. */
-    if (mod32_lt(hdr->offset + hdr->length, p->offset)) {
-      if (!p->prev->data ||
-          mod32_lt(p->prev->offset + p->prev->length, hdr->offset))
-        break;
-
-      /* protocol error: this block goes before 'p' but does not fit
-         after 'p->prev' */
-      log_warn(ckt, "protocol error: %u byte block does not fit at offset %u",
-               hdr->length, hdr->offset);
-      return -1;
+  // Insert a block into the reassembly queue at sequence number
+  // SEQNO, with opcode OP and data section DATA.  Returns true if the
+  // block was successfully added to the queue, false if it is either
+  // outside the acceptable window or duplicates a block already on
+  // the queue (both of these cases indicate protocol errors).
+  // DATA is consumed no matter what the return value is.
+  bool
+  insert(uint32_t seqno, opcode_t op, evbuffer *data, conn_t *conn)
+  {
+    if (seqno - window() > 255) {
+      log_warn(conn, "block outside receive window");
+      evbuffer_free(data);
+      return false;
+    }
+    uint8_t front = next_to_process & 0xFF;
+    uint8_t pos = front + (seqno - window());
+    if (cbuf[pos].data) {
+      log_warn(conn, "duplicate block");
+      evbuffer_free(data);
+      return false;
     }
-  }
 
-  /* This block goes before, but does not merge with, 'p'.
-     Special case: if 'p' is the sentinel, we have not yet checked
-     that this block goes after the last block in the list (aka p->prev). */
-  if (!p->data && p->prev->data &&
-      !mod32_lt(p->prev->offset + p->prev->length, hdr->offset)) {
-    log_warn(ckt, "protocol error: %u byte block does not fit at offset %u "
-                "(sentinel case)",
-             hdr->length, hdr->offset);
-    return -1;
+    cbuf[pos].data = data;
+    cbuf[pos].op   = op;
+    return true;
   }
 
-  q = (chop_reassembly_elt *)xzalloc(sizeof(chop_reassembly_elt));
-  q->data = block;
-  q->offset = hdr->offset;
-  q->length = hdr->length;
-  q->flags = hdr->flags;
+  // Return the current lowest acceptable sequence number in the
+  // receive window. This is the value to be passed to
+  // block_header::valid().
+  uint32_t window() const { return next_to_process; }
 
-  q->prev = p->prev;
-  q->next = p;
-  q->prev->next = q;
-  q->next->prev = q;
-  return 0;
-
- grow_back:
-  if (evbuffer_add_buffer(p->data, block)) {
-    log_warn(ckt, "failed to append to existing buffer");
-    return -1;
-  }
-  evbuffer_free(block);
-  p->length += hdr->length;
-  p->flags |= hdr->flags;
-
-  /* Can we now combine 'p' with its successor? */
-  while (p->next->data && p->offset + p->length == p->next->offset) {
-    q = p->next;
-    if (evbuffer_add_buffer(p->data, q->data)) {
-      log_warn(ckt, "failed to merge buffers");
-      return -1;
+  // As the last step of a rekeying cycle, the expected next sequence number
+  // is reset to zero.
+  void reset()
+  {
+    for (int i = 0; i < 256; i++) {
+      log_assert(!cbuf[i].data);
     }
-    p->length += q->length;
-    p->flags |= q->flags;
-
-    evbuffer_free(q->data);
-    q->next->prev = q->prev;
-    q->prev->next = q->next;
-    free(q);
+    next_to_process = 0;
   }
-  return 0;
+};
 
- grow_front:
-  if (evbuffer_prepend_buffer(p->data, block)) {
-    log_warn(ckt, "failed to prepend to existing buffer");
-    return -1;
-  }
-  evbuffer_free(block);
-  p->length += hdr->length;
-  p->offset -= hdr->length;
-  p->flags |= hdr->flags;
-
-  /* Can we now combine 'p' with its predecessor? */
-  while (p->prev->data && p->offset == p->prev->offset + p->prev->length) {
-    q = p->prev;
-    if (evbuffer_prepend_buffer(p->data, q->data)) {
-      log_warn(ckt, "failed to merge buffers");
-      return -1;
-    }
-    p->length += q->length;
-    p->offset -= q->length;
-    p->flags |= q->flags;
-
-    evbuffer_free(q->data);
-    q->next->prev = q->prev;
-    q->prev->next = q->next;
-    free(q);
-  }
+// Protocol objects
 
-  return 0;
-}
+struct chop_config_t;
+struct chop_circuit_t;
 
-/* Flush as much data toward upstream as we can. */
-static int
-chop_push_to_upstream(chop_circuit_t *ckt)
-{
-  /* Only the first reassembly queue entry, if any, can possibly be
-     ready to flush (because chop_reassemble_block ensures that there
-     are gaps between all queue elements).  */
-  chop_reassembly_elt *ready = ckt->reassembly_queue.next;
-  if (!ready->data || ckt->recv_offset != ready->offset) {
-    log_debug(ckt, "no data pushable to upstream yet");
-    return 0;
-  }
+typedef unordered_map<uint32_t, chop_circuit_t *> chop_circuit_table;
 
-  if (!ckt->received_syn) {
-    if (!(ready->flags & CHOP_F_SYN)) {
-      log_debug(ckt, "waiting for SYN");
-      return 0;
-    }
-    log_debug(ckt, "processed SYN");
-    ckt->received_syn = true;
-  }
+struct chop_conn_t : conn_t
+{
+  chop_config_t *config;
+  chop_circuit_t *upstream;
+  steg_t *steg;
+  struct evbuffer *recv_pending;
+  struct event *must_send_timer;
+  bool sent_handshake : 1;
+  bool no_more_transmissions : 1;
+
+  CONN_DECLARE_METHODS(chop);
+
+  int recv_handshake();
+  int send(struct evbuffer *block);
+
+  void send();
+  bool must_send_p() const;
+  static void must_send_timeout(evutil_socket_t, short, void *arg);
+};
 
-  log_debug(ckt, "can push %lu bytes to upstream",
-            (unsigned long)evbuffer_get_length(ready->data));
-  if (evbuffer_add_buffer(bufferevent_get_output(ckt->up_buffer),
-                          ready->data)) {
-    log_warn(ckt, "failure pushing data to upstream");
-    return -1;
+struct chop_circuit_t : circuit_t
+{
+  reassembly_queue recv_queue;
+  unordered_set<chop_conn_t *> downstreams;
+  gcm_encryptor *send_crypt;
+  ecb_encryptor *send_hdr_crypt;
+  gcm_decryptor *recv_crypt;
+  ecb_decryptor *recv_hdr_crypt;
+  chop_config_t *config;
+
+  uint32_t circuit_id;
+  uint32_t send_seq;
+  uint32_t dead_cycles;
+  bool received_fin : 1;
+  bool sent_fin : 1;
+  bool upstream_eof : 1;
+
+  CIRCUIT_DECLARE_METHODS(chop);
+
+  // Shortcut some unnecessary conversions for callers within this file.
+  void add_downstream(chop_conn_t *conn);
+  void drop_downstream(chop_conn_t *conn);
+
+  int send_special(opcode_t f, struct evbuffer *payload);
+  int send_targeted(chop_conn_t *conn);
+  int send_targeted(chop_conn_t *conn, size_t blocksize);
+  int send_targeted(chop_conn_t *conn, size_t d, size_t p, opcode_t f,
+                    struct evbuffer *payload);
+
+  chop_conn_t *pick_connection(size_t desired, size_t *blocksize);
+
+  int process_queue();
+  int check_for_eof();
+
+  uint32_t axe_interval() {
+    // This function must always return a number which is larger than
+    // the maximum possible number that *our peer's* flush_interval()
+    // could have returned; otherwise, we might axe the connection when
+    // it was just that there was nothing to say for a while.
+    // For simplicity's sake, right now we hardwire this to be 30 minutes.
+    return 30 * 60 * 1000;
   }
-
-  ckt->dead_cycles = 0;
-  ckt->recv_offset += ready->length;
-
-  if (ready->flags & CHOP_F_FIN) {
-    log_assert(!ckt->received_fin);
-    log_assert(ready->next == &ckt->reassembly_queue);
-    ckt->received_fin = true;
-    log_debug(ckt, "processed FIN");
-    circuit_recv_eof(ckt);
+  uint32_t flush_interval() {
+    // 10*60*1000 lies between 2^19 and 2^20.
+    uint32_t shift = std::max(1u, std::min(19u, dead_cycles));
+    uint32_t xv = std::max(1u, std::min(10u * 60 * 1000, 1u << shift));
+    return rng_range_geom(20 * 60 * 1000, xv) + 100;
   }
+};
 
-  log_assert(ready->next == &ckt->reassembly_queue ||
-              ready->next->offset != ckt->recv_offset);
-  ready->next->prev = ready->prev;
-  ready->prev->next = ready->next;
-
-  evbuffer_free(ready->data);
-  free(ready);
-  return 0;
-}
-
-/* Circuit handling */
-
-static int
-chop_find_or_make_circuit(chop_conn_t *conn, uint64_t circuit_id)
+struct chop_config_t : config_t
 {
-  chop_circuit_table::value_type in(circuit_id, 0);
-  std::pair<chop_circuit_table::iterator, bool> out
-    = conn->config->circuits.insert(in);
-  chop_circuit_t *ck;
-
-  if (!out.second) { // element already exists
-    if (!out.first->second) {
-      log_debug(conn, "stale circuit");
-      return 0;
-    }
-    ck = out.first->second;
-    log_debug(conn, "found circuit to %s", ck->up_peer);
-  } else {
-    ck = dynamic_cast<chop_circuit_t *>(circuit_create(conn->config, 0));
-    if (!ck) {
-      log_warn(conn, "failed to create new circuit");
-      return -1;
-    }
-    if (circuit_open_upstream(ck)) {
-      log_warn(conn, "failed to begin upstream connection");
-      delete ck;
-      return -1;
-    }
-    log_debug(conn, "created new circuit to %s", ck->up_peer);
-    ck->circuit_id = circuit_id;
-    out.first->second = ck;
-  }
+  struct evutil_addrinfo *up_address;
+  vector<struct evutil_addrinfo *> down_addresses;
+  vector<const char *> steg_targets;
+  chop_circuit_table circuits;
 
-  ck->add_downstream(conn);
-  return 0;
-}
+  CONFIG_DECLARE_METHODS(chop);
+};
 
-/* Protocol methods */
+// Configuration methods
 
 chop_config_t::chop_config_t()
 {
@@ -833,7 +390,7 @@ chop_config_t::~chop_config_t()
        i != down_addresses.end(); i++)
     evutil_freeaddrinfo(*i);
 
-  /* The strings in steg_targets are not on the heap. */
+  // The strings in steg_targets are not on the heap.
 
   for (chop_circuit_table::iterator i = circuits.begin();
        i != circuits.end(); i++)
@@ -854,28 +411,28 @@ chop_config_t::init(int n_options, const char *const *options)
   }
 
   if (!strcmp(options[0], "client")) {
-    defport = "48988"; /* bf5c */
-    this->mode = LSN_SIMPLE_CLIENT;
+    defport = "48988"; // bf5c
+    mode = LSN_SIMPLE_CLIENT;
     listen_up = 1;
   } else if (!strcmp(options[0], "socks")) {
-    defport = "23548"; /* 5bf5 */
-    this->mode = LSN_SOCKS_CLIENT;
+    defport = "23548"; // 5bf5
+    mode = LSN_SOCKS_CLIENT;
     listen_up = 1;
   } else if (!strcmp(options[0], "server")) {
-    defport = "11253"; /* 2bf5 */
-    this->mode = LSN_SIMPLE_SERVER;
+    defport = "11253"; // 2bf5
+    mode = LSN_SIMPLE_SERVER;
     listen_up = 0;
   } else
     goto usage;
 
-  this->up_address = resolve_address_port(options[1], 1, listen_up, defport);
-  if (!this->up_address) {
+  up_address = resolve_address_port(options[1], 1, listen_up, defport);
+  if (!up_address) {
     log_warn("chop: invalid up address: %s", options[1]);
     goto usage;
   }
 
-  /* From here on out, arguments alternate between downstream
-     addresses and steg targets. */
+  // From here on out, arguments alternate between downstream
+  // addresses and steg targets.
   for (i = 2; i < n_options; i++) {
     struct evutil_addrinfo *addr =
       resolve_address_port(options[i], 1, !listen_up, NULL);
@@ -883,7 +440,7 @@ chop_config_t::init(int n_options, const char *const *options)
       log_warn("chop: invalid down address: %s", options[i]);
       goto usage;
     }
-    this->down_addresses.push_back(addr);
+    down_addresses.push_back(addr);
 
     i++;
     if (i == n_options) {
@@ -895,7 +452,7 @@ chop_config_t::init(int n_options, const char *const *options)
       log_warn("chop: steganographer '%s' not supported", options[i]);
       goto usage;
     }
-    this->steg_targets.push_back(options[i]);
+    steg_targets.push_back(options[i]);
   }
   return true;
 
@@ -917,12 +474,12 @@ chop_config_t::init(int n_options, const char *const *options)
 struct evutil_addrinfo *
 chop_config_t::get_listen_addrs(size_t n)
 {
-  if (this->mode == LSN_SIMPLE_SERVER) {
-    if (n < this->down_addresses.size())
-      return this->down_addresses[n];
+  if (mode == LSN_SIMPLE_SERVER) {
+    if (n < down_addresses.size())
+      return down_addresses[n];
   } else {
     if (n == 0)
-      return this->up_address;
+      return up_address;
   }
   return 0;
 }
@@ -930,58 +487,96 @@ chop_config_t::get_listen_addrs(size_t n)
 struct evutil_addrinfo *
 chop_config_t::get_target_addrs(size_t n)
 {
-  if (this->mode == LSN_SIMPLE_SERVER) {
+  if (mode == LSN_SIMPLE_SERVER) {
     if (n == 0)
-      return this->up_address;
+      return up_address;
   } else {
-    if (n < this->down_addresses.size())
-      return this->down_addresses[n];
+    if (n < down_addresses.size())
+      return down_addresses[n];
   }
   return NULL;
 }
 
+// Circuit methods
+
+const char passphrase[] =
+  "did you buy one of therapist reawaken chemists continually gamma pacifies?";
+
 circuit_t *
 chop_config_t::circuit_create(size_t)
 {
   chop_circuit_t *ckt = new chop_circuit_t;
   ckt->config = this;
 
-  if (this->mode == LSN_SIMPLE_SERVER) {
-    ckt->send_crypt = gcm_encryptor::create(s2c_key, 16);
-    ckt->recv_crypt = gcm_decryptor::create(c2s_key, 16);
-  } else {
-    ckt->send_crypt = gcm_encryptor::create(c2s_key, 16);
-    ckt->recv_crypt = gcm_decryptor::create(s2c_key, 16);
-    while (!ckt->circuit_id)
-      rng_bytes((uint8_t *)&ckt->circuit_id, sizeof(uint64_t));
-
-    chop_circuit_table::value_type in(ckt->circuit_id, 0);
-    std::pair<chop_circuit_table::iterator, bool> out = circuits.insert(in);
-    log_assert(out.second);
-    out.first->second = ckt;
-  }
-  return ckt;
-}
+  key_generator *kgen =
+    key_generator::from_passphrase((const uint8_t *)passphrase,
+                                   sizeof(passphrase) - 1,
+                                   0, 0, 0, 0);
+  uint8_t kbuf[16];
 
-chop_circuit_t::chop_circuit_t()
-{
-  this->reassembly_queue.next = &this->reassembly_queue;
-  this->reassembly_queue.prev = &this->reassembly_queue;
+  if (mode == LSN_SIMPLE_SERVER) {
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->send_crypt = gcm_encryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->send_hdr_crypt = ecb_encryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->recv_crypt = gcm_decryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->recv_hdr_crypt = ecb_decryptor::create(kbuf, 16);
+  } else {
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->recv_crypt = gcm_decryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->recv_hdr_crypt = ecb_decryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->send_crypt = gcm_encryptor::create(kbuf, 16);
+
+    log_assert(kgen->generate(kbuf, 16) == 16);
+    ckt->send_hdr_crypt = ecb_encryptor::create(kbuf, 16);
+
+    std::pair<chop_circuit_table::iterator, bool> out;
+    do {
+      do {
+        rng_bytes((uint8_t *)&ckt->circuit_id, sizeof(ckt->circuit_id));
+      } while (!ckt->circuit_id);
+
+      out = circuits.insert(make_pair(ckt->circuit_id, (chop_circuit_t *)0));
+    } while (!out.second);
+
+    out.first->second = ckt;
+  }
+
+  memset(kbuf, 0, 16);
+  delete kgen;
+  return ckt;
 }
 
-chop_circuit_t::~chop_circuit_t()
+chop_circuit_t::chop_circuit_t()
 {
-  chop_reassembly_elt *p, *q, *queue;
-  chop_circuit_table::iterator out;
+}
 
-  log_debug(this, "syn%c%c fin%c%c eof%c ds=%lu",
-            sent_syn ? '+' : '-', received_syn ? '+' : '-',
-            sent_fin ? '+' : '-', received_fin ? '+' : '-',
-            upstream_eof ? '+' : '-',
-            (unsigned long)downstreams.size());
+chop_circuit_t::~chop_circuit_t()
+{
+  if (!sent_fin || !received_fin || !upstream_eof) {
+    log_warn(this, "destroying active circuit: fin%c%c eof%c ds=%lu",
+             sent_fin ? '+' : '-', received_fin ? '+' : '-',
+             upstream_eof ? '+' : '-',
+             (unsigned long)downstreams.size());
+#ifdef HAVE_EXECINFO_H
+    int n;
+    void *backtracebuf[256];
+    n = backtrace(backtracebuf, sizeof backtracebuf / sizeof(void*));
+    backtrace_symbols_fd(backtracebuf, n, 2);
+#endif
+  }
 
-  for (unordered_set<chop_conn_t *>::iterator i = this->downstreams.begin();
-       i != this->downstreams.end(); i++) {
+  for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin();
+       i != downstreams.end(); i++) {
     chop_conn_t *conn = *i;
     conn->upstream = NULL;
     if (evbuffer_get_length(conn->outbound()) > 0)
@@ -990,28 +585,23 @@ chop_circuit_t::~chop_circuit_t()
       delete conn;
   }
 
-  delete this->send_crypt;
-  delete this->recv_crypt;
-
-  queue = &this->reassembly_queue;
-  for (q = p = queue->next; p != queue; p = q) {
-    q = p->next;
-    if (p->data)
-      evbuffer_free(p->data);
-    free(p);
-  }
-
-  /* The IDs for old circuits are preserved for a while (at present,
-     indefinitely; FIXME: purge them on a timer) against the
-     possibility that we'll get a junk connection for one of them
-     right after we close it (same deal as the TIME_WAIT state in
-     TCP).  Note that we can hit this case for the *client* if the
-     cover protocol includes a mandatory reply to every client
-     message and the hidden channel closed s->c before c->s: the
-     circuit will get destroyed on the client side after the c->s FIN,
-     and the mandatory reply will be to a stale circuit. */
-  out = this->config->circuits.find(this->circuit_id);
-  log_assert(out != this->config->circuits.end());
+  delete send_crypt;
+  delete send_hdr_crypt;
+  delete recv_crypt;
+  delete recv_hdr_crypt;
+
+  // The IDs for old circuits are preserved for a while (at present,
+  // indefinitely; FIXME: purge them on a timer) against the
+  // possibility that we'll get a junk connection for one of them
+  // right after we close it (same deal as the TIME_WAIT state in
+  // TCP).  Note that we can hit this case for the *client* if the
+  // cover protocol includes a mandatory reply to every client message
+  // and the hidden channel closed s->c before c->s: the circuit will
+  // get destroyed on the client side after the c->s FIN, and the
+  // mandatory reply will be to a stale circuit.
+  chop_circuit_table::iterator out;
+  out = config->circuits.find(circuit_id);
+  log_assert(out != config->circuits.end());
   log_assert(out->second == this);
   out->second = NULL;
 }
@@ -1019,69 +609,442 @@ chop_circuit_t::~chop_circuit_t()
 config_t *
 chop_circuit_t::cfg() const
 {
-  return this->config;
+  return config;
 }
 
 void
-chop_circuit_t::add_downstream(conn_t *cn)
+chop_circuit_t::add_downstream(chop_conn_t *conn)
 {
-  chop_conn_t *conn = dynamic_cast<chop_conn_t *>(cn);
   log_assert(conn);
   log_assert(!conn->upstream);
-
   conn->upstream = this;
-  this->downstreams.insert(conn);
+  downstreams.insert(conn);
 
   log_debug(this, "added connection <%d.%d> to %s, now %lu",
-            this->serial, conn->serial, conn->peername,
-            (unsigned long)this->downstreams.size());
+            serial, conn->serial, conn->peername,
+            (unsigned long)downstreams.size());
 
   circuit_disarm_axe_timer(this);
 }
 
 void
-chop_circuit_t::drop_downstream(conn_t *cn)
+chop_circuit_t::add_downstream(conn_t *cn)
+{
+  add_downstream(dynamic_cast<chop_conn_t *>(cn));
+}
+
+void
+chop_circuit_t::drop_downstream(chop_conn_t *conn)
 {
-  chop_conn_t *conn = dynamic_cast<chop_conn_t *>(cn);
   log_assert(conn);
   log_assert(conn->upstream == this);
 
   conn->upstream = NULL;
-  this->downstreams.erase(conn);
+  downstreams.erase(conn);
 
   log_debug(this, "dropped connection <%d.%d> to %s, now %lu",
-            this->serial, conn->serial, conn->peername,
-            (unsigned long)this->downstreams.size());
-  /* If that was the last connection on this circuit AND we've both
-     received and sent a FIN, close the circuit.  Otherwise, if we're
-     the server, arm a timer that will kill off this circuit in a
-     little while if no new connections happen (we might've lost all
-     our connections to protocol errors, or because the steg modules
-     wanted them closed); if we're the client, send chaff in a bit,
-     to enable further transmissions from the server. */
-  if (this->downstreams.empty()) {
-    if (this->sent_fin && this->received_fin) {
-      if (evbuffer_get_length(bufferevent_get_output(this->up_buffer)) > 0)
-        /* this may already have happened, but there's no harm in
-           doing it again */
+            serial, conn->serial, conn->peername,
+            (unsigned long)downstreams.size());
+  // If that was the last connection on this circuit AND we've both
+  // received and sent a FIN, close the circuit.  Otherwise, if we're
+  // the server, arm a timer that will kill off this circuit in a
+  // little while if no new connections happen (we might've lost all
+  // our connections to protocol errors, or because the steg modules
+  // wanted them closed); if we're the client, send chaff in a bit,
+  // to enable further transmissions from the server.
+  if (downstreams.empty()) {
+    if (sent_fin && received_fin) {
+      if (evbuffer_get_length(bufferevent_get_output(up_buffer)) > 0)
+        // this may already have happened, but there's no harm in
+        // doing it again
         circuit_do_flush(this);
       else
         delete this;
-    } else if (this->config->mode == LSN_SIMPLE_SERVER) {
-      circuit_arm_axe_timer(this, this->axe_interval());
+    } else if (config->mode == LSN_SIMPLE_SERVER) {
+      circuit_arm_axe_timer(this, axe_interval());
     } else {
-      circuit_arm_flush_timer(this, this->flush_interval());
+      circuit_arm_flush_timer(this, flush_interval());
+    }
+  }
+}
+
+void
+chop_circuit_t::drop_downstream(conn_t *cn)
+{
+  drop_downstream(dynamic_cast<chop_conn_t *>(cn));
+}
+
+int
+chop_circuit_t::send()
+{
+  circuit_disarm_flush_timer(this);
+
+  if (downstreams.empty()) {
+    // We have no connections, but we must send.  If we're the client,
+    // reopen our outbound connections; the on-connection event will
+    // bring us back here.  If we're the server, we have to just
+    // twiddle our thumbs and hope the client reconnects.
+    log_debug(this, "no downstream connections");
+    if (config->mode != LSN_SIMPLE_SERVER)
+      circuit_reopen_downstreams(this);
+    else
+      circuit_arm_axe_timer(this, axe_interval());
+    return 0;
+  }
+
+  struct evbuffer *xmit_pending = bufferevent_get_input(up_buffer);
+  size_t avail = evbuffer_get_length(xmit_pending);
+  size_t avail0 = avail;
+
+  // Send at least one block, even if there is no real data to send.
+  do {
+    log_debug(this, "%lu bytes to send", (unsigned long)avail);
+    size_t blocksize;
+    chop_conn_t *target = pick_connection(avail, &blocksize);
+    if (!target) {
+      // this is not an error; it can happen e.g. when the server has
+      // something to send immediately and the client hasn't spoken yet
+      log_debug(this, "no target connection available");
+      break;
+    }
+
+    if (send_targeted(target, blocksize))
+      return -1;
+
+    avail = evbuffer_get_length(xmit_pending);
+  } while (avail > 0);
+
+  if (avail0 > avail) // we transmitted some real data
+    dead_cycles = 0;
+  else {
+    dead_cycles++;
+    log_debug(this, "%u dead cycles", dead_cycles);
+  }
+
+  return check_for_eof();
+}
+
+int
+chop_circuit_t::send_eof()
+{
+  upstream_eof = true;
+  return send();
+}
+
+int
+chop_circuit_t::send_special(opcode_t f, struct evbuffer *payload)
+{
+  size_t d = payload ? evbuffer_get_length(payload) : 0;
+  size_t blocksize;
+  log_assert(d <= SECTION_LEN);
+  chop_conn_t *conn = pick_connection(d, &blocksize);
+
+  if (!conn || (blocksize - MIN_BLOCK_SIZE < d)) {
+    log_warn("no usable connection for special block "
+             "(opcode %02x, need %lu bytes, have %lu)",
+             (unsigned int)f, (unsigned long)(d + MIN_BLOCK_SIZE),
+             (unsigned long)blocksize);
+    return -1;
+  }
+
+  return send_targeted(conn, d, (blocksize - MIN_BLOCK_SIZE) - d, f, payload);
+}
+
+int
+chop_circuit_t::send_targeted(chop_conn_t *conn)
+{
+  size_t avail = evbuffer_get_length(bufferevent_get_input(up_buffer));
+  if (avail > SECTION_LEN)
+    avail = SECTION_LEN;
+  avail += MIN_BLOCK_SIZE;
+
+  size_t room = conn->steg->transmit_room(conn);
+  if (room < MIN_BLOCK_SIZE) {
+    log_warn(conn, "send() called without enough transmit room "
+             "(have %lu, need %lu)", (unsigned long)room,
+             (unsigned long)MIN_BLOCK_SIZE);
+    return -1;
+  }
+  log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room,
+            conn->steg->name());
+
+  if (room < avail)
+    avail = room;
+
+  return send_targeted(conn, avail);
+}
+
+int
+chop_circuit_t::send_targeted(chop_conn_t *conn, size_t blocksize)
+{
+  log_assert(blocksize >= MIN_BLOCK_SIZE && blocksize <= MAX_BLOCK_SIZE);
+
+  struct evbuffer *xmit_pending = bufferevent_get_input(up_buffer);
+  size_t avail = evbuffer_get_length(xmit_pending);
+  opcode_t op = op_DAT;
+
+  if (avail > SECTION_LEN)
+    avail = SECTION_LEN;
+  else if (upstream_eof && !sent_fin)
+    // this block will carry the last byte of real data to be sent in
+    // this direction; mark it as such
+    op = op_FIN;
+
+  return send_targeted(conn, avail, (blocksize - MIN_BLOCK_SIZE) - avail,
+                       op, xmit_pending);
+}
+
+int
+chop_circuit_t::send_targeted(chop_conn_t *conn, size_t d, size_t p, opcode_t f,
+                              struct evbuffer *payload)
+{
+  log_assert(payload || d == 0);
+  log_assert(d <= SECTION_LEN);
+  log_assert(p <= SECTION_LEN);
+
+  struct evbuffer *block = evbuffer_new();
+  if (!block) {
+    log_warn(conn, "memory allocation failure");
+    return -1;
+  }
+
+  size_t blocksize = d + p + MIN_BLOCK_SIZE;
+  struct evbuffer_iovec v;
+  if (evbuffer_reserve_space(block, blocksize, &v, 1) != 1 ||
+      v.iov_len < blocksize) {
+    log_warn(conn, "memory allocation failure");
+    return -1;
+  }
+  v.iov_len = blocksize;
+
+  block_header hdr(send_seq, d, p, f, *send_hdr_crypt);
+  log_assert(hdr.valid(send_seq));
+  memcpy(v.iov_base, hdr.nonce(), HEADER_LEN);
+
+  uint8_t encodebuf[SECTION_LEN*2];
+  if (payload) {
+    if (evbuffer_copyout(payload, encodebuf, d) != (ssize_t)d) {
+      log_warn(conn, "failed to extract payload");
+      evbuffer_free(block);
+      return -1;
+    }
+  }
+  memset(encodebuf + d, 0, p);
+  send_crypt->encrypt((uint8_t *)v.iov_base + HEADER_LEN, encodebuf,
+                      d + p, hdr.nonce(), HEADER_LEN);
+  if (evbuffer_commit_space(block, &v, 1)) {
+    log_warn(conn, "failed to commit block buffer");
+    evbuffer_free(block);
+    return -1;
+  }
+
+  log_debug(conn, "transmitting block %u <d=%lu p=%lu f=%02x>",
+            hdr.seqno(), (unsigned long)hdr.dlen(), (unsigned long)hdr.plen(),
+            (uint8_t)hdr.opcode());
+
+  if (conn->send(block)) {
+    evbuffer_free(block);
+    return -1;
+  }
+
+  evbuffer_free(block);
+  evbuffer_drain(payload, d);
+
+  send_seq++;
+  if (f == op_FIN)
+    sent_fin = true;
+  return 0;
+}
+
+// N.B. 'desired' is the desired size of the _data section_, and
+// 'blocksize' on output is the size to make the _entire block_.
+chop_conn_t *
+chop_circuit_t::pick_connection(size_t desired, size_t *blocksize)
+{
+  size_t maxbelow = 0;
+  size_t minabove = MAX_BLOCK_SIZE + 1;
+  chop_conn_t *targbelow = 0;
+  chop_conn_t *targabove = 0;
+
+  if (desired > SECTION_LEN)
+    desired = SECTION_LEN;
+
+  desired += MIN_BLOCK_SIZE;
+
+  log_debug(this, "target block size %lu bytes", (unsigned long)desired);
+
+  // Find the best fit for the desired transmission from all the
+  // outbound connections' transmit rooms.
+  for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin();
+       i != downstreams.end(); i++) {
+    chop_conn_t *conn = *i;
+    // We can only use candidates that have a steg target already.
+    if (conn->steg) {
+      // Find the connections whose transmit rooms are closest to the
+      // desired transmission length from both directions.
+      size_t room = conn->steg->transmit_room(conn);
+
+      if (room <= MIN_BLOCK_SIZE)
+	room = 0;
+
+      if (room > MAX_BLOCK_SIZE)
+        room = MAX_BLOCK_SIZE;
+
+      log_debug(conn, "offers %lu bytes (%s)", (unsigned long)room,
+                conn->steg->name());
+
+      if (room >= desired) {
+        if (room < minabove) {
+          minabove = room;
+          targabove = conn;
+        }
+      } else {
+        if (room > maxbelow) {
+          maxbelow = room;
+          targbelow = conn;
+        }
+      }
+    } else {
+      log_debug(conn, "offers 0 bytes (no steg)");
+    }
+  }
+
+  log_debug(this, "minabove %lu for <%u.%u> maxbelow %lu for <%u.%u>",
+            (unsigned long)minabove, serial, targabove ? targabove->serial :0,
+            (unsigned long)maxbelow, serial, targbelow ? targbelow->serial :0);
+
+  // If we have a connection that can take all the data, use it.
+  // Otherwise, use the connection that can take as much of the data
+  // as possible.  As a special case, if no connection can take data,
+  // targbelow, targabove, maxbelow, and minabove will all still have
+  // their initial values, so we'll return NULL and set blocksize to 0,
+  // which callers know how to handle.
+  if (targabove) {
+    *blocksize = desired;
+    return targabove;
+  } else {
+    *blocksize = maxbelow;
+    return targbelow;
+  }
+}
+
+int
+chop_circuit_t::process_queue()
+{
+  reassembly_elt blk;
+  unsigned int count = 0;
+  bool pending_fin = false;
+  bool pending_error = false;
+  bool sent_error = false;
+  while ((blk = recv_queue.remove_next()).data) {
+    switch (blk.op) {
+    case op_FIN:
+      if (received_fin) {
+        log_info(this, "protocol error: duplicate FIN");
+        pending_error = true;
+        break;
+      }
+      log_debug(this, "received FIN");
+      pending_fin = true;
+      // fall through - block may have data
+    case op_DAT:
+      if (evbuffer_get_length(blk.data)) {
+        if (received_fin) {
+          log_info(this, "protocol error: data after FIN");
+          pending_error = true;
+        } else {
+          if (evbuffer_add_buffer(bufferevent_get_output(up_buffer),
+                                  blk.data)) {
+            log_warn(this, "buffer transfer failure");
+            pending_error = true;
+          }
+        }
+      }
+      break;
+
+    case op_RST:
+      log_info(this, "received RST; disconnecting circuit");
+      circuit_recv_eof(this);
+      pending_error = true;
+      break;
+
+    case op_RK1:
+    case op_RK2:
+    case op_RK3:
+      log_warn(this, "rekeying not yet implemented");
+      pending_error = true;
+      break;
+
+    default:
+      log_warn(this, "protocol error: unknown block opcode %x",
+               (unsigned int)blk.op);
+      pending_error = true;
+      break;
+    }
+
+    evbuffer_free(blk.data);
+
+    if (pending_fin && !received_fin) {
+      circuit_recv_eof(this);
+      received_fin = true;
     }
+    if (pending_error && !sent_error) {
+      // there's no point sending an RST in response to an RST or a
+      // duplicate FIN
+      if (blk.op != op_RST && blk.op != op_FIN)
+        send_special(op_RST, 0);
+      sent_error = true;
+    }
+    count++;
   }
+
+  log_debug(this, "processed %u blocks", count);
+  if (count > 0)
+    dead_cycles = 0;
+  if (sent_error)
+    return -1;
+
+  // It may have become possible to send queued data or a FIN.
+  if (evbuffer_get_length(bufferevent_get_input(up_buffer))
+      || (upstream_eof && !sent_fin))
+    return send();
+
+  return check_for_eof();
 }
 
+int
+chop_circuit_t::check_for_eof()
+{
+  // If we're at EOF both ways, close all connections, sending first
+  // if necessary.
+  if (sent_fin && received_fin) {
+    circuit_disarm_flush_timer(this);
+    for (unordered_set<chop_conn_t *>::iterator i = downstreams.begin();
+         i != downstreams.end(); i++) {
+      chop_conn_t *conn = *i;
+      if (conn->must_send_p())
+        conn->send();
+      conn_send_eof(conn);
+    }
+  }
+  
+
+  // If we're the client we have to keep trying to talk as long as we
+  // haven't both sent and received a FIN, or we might deadlock.
+  else if (config->mode != LSN_SIMPLE_SERVER)
+    circuit_arm_flush_timer(this, flush_interval());
+
+  return 0;
+}
+
+// Connection methods
+
 conn_t *
 chop_config_t::conn_create(size_t index)
 {
   chop_conn_t *conn = new chop_conn_t;
   conn->config = this;
-  conn->steg = steg_new(this->steg_targets.at(index),
-                        this->mode != LSN_SIMPLE_SERVER);
+  conn->steg = steg_new(steg_targets.at(index), mode != LSN_SIMPLE_SERVER);
   if (!conn->steg) {
     free(conn);
     return 0;
@@ -1097,271 +1060,239 @@ chop_conn_t::chop_conn_t()
 
 chop_conn_t::~chop_conn_t()
 {
-  if (this->upstream)
-    this->upstream->drop_downstream(this);
-  if (this->steg)
-    delete this->steg;
-  if (this->must_transmit_timer)
-    event_free(this->must_transmit_timer);
-  evbuffer_free(this->recv_pending);
+  if (upstream)
+    upstream->drop_downstream(this);
+  if (steg)
+    delete steg;
+  if (must_send_timer)
+    event_free(must_send_timer);
+  evbuffer_free(recv_pending);
 }
 
 circuit_t *
 chop_conn_t::circuit() const
 {
-  return this->upstream;
+  return upstream;
 }
 
 int
 chop_conn_t::maybe_open_upstream()
 {
-  /* We can't open the upstream until we have a circuit ID. */
+  // We can't open the upstream until we have a circuit ID.
+  return 0;
+}
+
+int
+chop_conn_t::send(struct evbuffer *block)
+{
+  if (!sent_handshake && config->mode != LSN_SIMPLE_SERVER) {
+    if (!upstream || upstream->circuit_id == 0)
+      log_abort(this, "handshake: can't happen: up%c cid=%u",
+                upstream ? '+' : '-',
+                upstream ? upstream->circuit_id : 0);
+    if (evbuffer_prepend(block, (void *)&upstream->circuit_id,
+                         sizeof(upstream->circuit_id))) {
+      log_warn(this, "failed to prepend handshake to first block");
+      return -1;
+    }
+  }
+
+  if (steg->transmit(block, this)) {
+    log_warn(this, "failed to transmit block");
+    return -1;
+  }
+  sent_handshake = true;
+  if (must_send_timer)
+    evtimer_del(must_send_timer);
   return 0;
 }
 
 int
 chop_conn_t::handshake()
 {
-  /* Chop has no handshake as such, but like dsteg, we need to send
-     _something_ from the client on at least one of the channels
-     shortly after connection, because the server doesn't know which
-     connections go with which circuits till it hears from us, _and_
-     it doesn't know what steganography to use.  We use a 1ms timeout
-     instead of a 10ms timeout as in dsteg, because unlike there, the
-     server can't even _connect to its upstream_ till it gets the
-     first packet from the client. */
-  if (this->config->mode != LSN_SIMPLE_SERVER)
-    circuit_arm_flush_timer(this->upstream, 1);
+  // The actual handshake is generated in chop_conn_t::send so that it
+  // can be merged with a block if possible; however, we use this hook
+  // to ensure that the client sends _something_ ASAP after each new
+  // connection, because the server can't forward traffic, or even
+  // open a socket to its own upstream, until it knows which circuit
+  // to associate this new connection with.  Note that in some cases
+  // it's possible for us to have _already_ sent something on this
+  // connection by the time we get called back!  Don't do it twice.
+  if (config->mode != LSN_SIMPLE_SERVER && !sent_handshake)
+    send();
   return 0;
 }
 
 int
-chop_circuit_t::send()
+chop_conn_t::recv_handshake()
 {
-  circuit_disarm_flush_timer(this);
+  log_assert(!upstream);
+  log_assert(config->mode == LSN_SIMPLE_SERVER);
 
-  if (this->downstreams.empty()) {
-    /* We have no connections, but we must send.  If we're the client,
-       reopen our outbound connections; the on-connection event will
-       bring us back here.  If we're the server, we have to just
-       twiddle our thumbs and hope the client reconnects. */
-    log_debug(this, "no downstream connections");
-    if (this->config->mode != LSN_SIMPLE_SERVER)
-      circuit_reopen_downstreams(this);
-    else
-      circuit_arm_axe_timer(this, this->axe_interval());
-    return 0;
-  }
+  uint32_t circuit_id;
+  if (evbuffer_remove(recv_pending, (void *)&circuit_id,
+                      sizeof circuit_id) != sizeof circuit_id)
+    return -1;
 
-  if (evbuffer_get_length(bufferevent_get_input(this->up_buffer)) == 0) {
-    /* must-send timer expired and we still have nothing to say; send chaff */
-    if (chop_send_chaff(this))
-      return -1;
-    this->dead_cycles++;
-    log_debug(this, "%u dead cycles", this->dead_cycles);
-  } else {
-    if (chop_send_blocks(this))
-      return -1;
-    this->dead_cycles = 0;
-  }
+  chop_circuit_table::value_type in(circuit_id, 0);
+  std::pair<chop_circuit_table::iterator, bool> out
+    = this->config->circuits.insert(in);
+  chop_circuit_t *ck;
 
-  /* If we're at EOF, close all connections (sending first if
-     necessary).  If we're the client we have to keep trying to talk
-     as long as we haven't both sent and received a FIN, or we might
-     deadlock. */
-  if (this->sent_fin && this->received_fin) {
-    for (unordered_set<chop_conn_t *>::iterator i = this->downstreams.begin();
-         i != this->downstreams.end(); i++) {
-      chop_conn_t *conn = *i;
-      if (conn->must_transmit_timer &&
-          evtimer_pending(conn->must_transmit_timer, NULL))
-        must_transmit_timer_cb(-1, 0, conn);
-      conn_send_eof(conn);
+  if (!out.second) { // element already exists
+    if (!out.first->second) {
+      log_debug(this, "stale circuit");
+      return 0;
     }
+    ck = out.first->second;
+    log_debug(this, "found circuit to %s", ck->up_peer);
   } else {
-    if (this->config->mode != LSN_SIMPLE_SERVER)
-      circuit_arm_flush_timer(this, this->flush_interval());
+    ck = dynamic_cast<chop_circuit_t *>(circuit_create(this->config, 0));
+    if (!ck) {
+      log_warn(this, "failed to create new circuit");
+      return -1;
+    }
+    if (circuit_open_upstream(ck)) {
+      log_warn(this, "failed to begin upstream connection");
+      delete ck;
+      return -1;
+    }
+    log_debug(this, "created new circuit to %s", ck->up_peer);
+    ck->circuit_id = circuit_id;
+    out.first->second = ck;
   }
-  return 0;
-}
 
-int
-chop_circuit_t::send_eof()
-{
-  this->upstream_eof = true;
-  return this->send();
+  ck->add_downstream(this);
+  return 0;
 }
 
 int
 chop_conn_t::recv()
 {
-  chop_circuit_t *ckt;
-  chop_header hdr;
-  struct evbuffer *block;
-  size_t avail;
-  uint8_t decodebuf[CHOP_MAX_DATA + CHOP_WIRE_HDR_LEN];
-
-  if (this->steg->receive(this, this->recv_pending))
+  if (steg->receive(this, recv_pending))
     return -1;
 
-  if (!this->upstream) {
-    log_debug(this, "finding circuit");
-    if (chop_peek_circuit_id(this->recv_pending, &hdr)) {
-      log_debug(this, "not enough data to find circuit yet");
-      return 0;
-    }
-    if (chop_find_or_make_circuit(this, hdr.ckt_id))
+  if (!upstream) {
+    // Try to receive a handshake.
+    if (recv_handshake())
       return -1;
-    /* If we get here and this->circuit is not set, this is a connection
-       for a stale circuit: that is, a new connection made by the
-       client (to draw more data down from the server) that crossed
-       with a server-to-client FIN.  We can't decrypt the packet, but
-       it's either chaff or a protocol error; either way we can just
-       discard it.  Since we will never reply, call conn_do_flush so
-       the connection will be dropped as soon as we receive an EOF. */
-    if (!this->upstream) {
-      evbuffer_drain(this->recv_pending,
-                     evbuffer_get_length(this->recv_pending));
+
+    // If we get here and ->upstream is not set, this is a connection
+    // for a stale circuit: that is, a new connection made by the
+    // client (to draw more data down from the server) that crossed
+    // with a server-to-client FIN, the client-to-server FIN already
+    // having been received and processed.  We no longer have the keys
+    // to decrypt anything after the handshake, but it's either chaff
+    // or a protocol error.  Either way, we can just drop the
+    // connection, possibly sending a response if the cover protocol
+    // requires one.
+    if (!upstream) {
+      evbuffer_drain(recv_pending, evbuffer_get_length(recv_pending));
+      if (must_send_p())
+        send();
       conn_do_flush(this);
       return 0;
     }
   }
 
-  ckt = this->upstream;
-  log_debug(this, "circuit to %s", ckt->up_peer);
-
+  log_debug(this, "circuit to %s", upstream->up_peer);
   for (;;) {
-    avail = evbuffer_get_length(this->recv_pending);
+    size_t avail = evbuffer_get_length(recv_pending);
     if (avail == 0)
       break;
 
     log_debug(this, "%lu bytes available", (unsigned long)avail);
-    if (avail < CHOP_WIRE_HDR_LEN) {
-      log_debug(this, "incomplete block");
+    if (avail < MIN_BLOCK_SIZE) {
+      log_debug(this, "incomplete block framing");
       break;
     }
 
-    if (chop_decrypt_header(ckt, this->recv_pending, &hdr))
+    block_header hdr(recv_pending, *upstream->recv_hdr_crypt);
+    if (!hdr.valid(upstream->recv_queue.window())) {
+      const uint8_t *c = hdr.cleartext();
+      log_warn(this, "invalid block header: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+               c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7],
+               c[8], c[9], c[10], c[11], c[12], c[13], c[14], c[15]);
       return -1;
-
-    if (avail < CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length) {
+    }
+    if (avail < hdr.total_len()) {
       log_debug(this, "incomplete block (need %lu bytes)",
-                (unsigned long)(CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length));
+                (unsigned long)hdr.total_len());
       break;
     }
 
-    if (ckt->circuit_id != hdr.ckt_id) {
-      log_warn(this, "protocol error: circuit id mismatch");
-      return -1;
-    }
-
-    log_debug(this, "receiving block of %lu+%u bytes "
-                 "[offset %u flags %04hx]",
-                 (unsigned long)CHOP_WIRE_HDR_LEN + GCM_TAG_LEN,
-                 hdr.length, hdr.offset, hdr.flags);
-
-    if (evbuffer_copyout(this->recv_pending, decodebuf,
-                         CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)
-        != (ssize_t)(CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)) {
+    uint8_t decodebuf[MAX_BLOCK_SIZE];
+    if (evbuffer_drain(recv_pending, HEADER_LEN) ||
+        evbuffer_remove(recv_pending, decodebuf, hdr.total_len() - HEADER_LEN)
+        != (ssize_t)(hdr.total_len() - HEADER_LEN)) {
       log_warn(this, "failed to copy block to decode buffer");
       return -1;
     }
-    block = evbuffer_new();
-    if (!block || evbuffer_expand(block, hdr.length)) {
-      log_warn(this, "allocation failure");
+    if (upstream->recv_crypt->decrypt(decodebuf,
+                                      decodebuf, hdr.total_len() - HEADER_LEN,
+                                      hdr.nonce(), HEADER_LEN)) {
+      log_warn("MAC verification failure");
       return -1;
     }
 
-    if (ckt->recv_crypt
-        ->decrypt(decodebuf + 16, decodebuf + 16,
-                  hdr.length + CHOP_WIRE_HDR_LEN + GCM_TAG_LEN - 16,
-                  decodebuf, 16)) {
-      log_warn(this, "MAC verification failure");
-      evbuffer_free(block);
-      return -1;
-    }
-
-    if (evbuffer_add(block, decodebuf + CHOP_WIRE_HDR_LEN, hdr.length)) {
-      log_warn(this, "failed to transfer block to reassembly queue");
-      evbuffer_free(block);
-      return -1;
-    }
-
-    if (evbuffer_drain(this->recv_pending,
-                       CHOP_WIRE_HDR_LEN + GCM_TAG_LEN + hdr.length)) {
-      log_warn(this, "failed to consume block from wire");
-      evbuffer_free(block);
-      return -1;
-    }
+    log_debug(this, "receiving block %u <d=%lu p=%lu f=%u>",
+              hdr.seqno(), (unsigned long)hdr.dlen(), (unsigned long)hdr.plen(),
+              (unsigned int)hdr.opcode());
 
-    if (chop_reassemble_block(ckt, block, &hdr)) {
-      evbuffer_free(block);
+    evbuffer *data = evbuffer_new();
+    if (!data || (hdr.dlen() && evbuffer_add(data, decodebuf, hdr.dlen()))) {
+      log_warn(this, "failed to extract data from decode buffer");
+      evbuffer_free(data);
       return -1;
     }
-  }
-
-  if (chop_push_to_upstream(ckt))
-    return -1;
 
-  /* It may have now become possible to send queued data. */
-  if (evbuffer_get_length(bufferevent_get_input(ckt->up_buffer)))
-    ckt->send();
-
-  /* If we're at EOF, close all connections (sending first if
-     necessary).  If we're the client we have to keep trying to talk
-     as long as we haven't both sent and received a FIN, or we might
-     deadlock. */
-  else if (ckt->sent_fin && ckt->received_fin) {
-    circuit_disarm_flush_timer(ckt);
-    for (unordered_set<chop_conn_t *>::iterator i = ckt->downstreams.begin();
-         i != ckt->downstreams.end(); i++) {
-      chop_conn_t *conn = *i;
-      if (conn->must_transmit_timer &&
-          evtimer_pending(conn->must_transmit_timer, NULL))
-        must_transmit_timer_cb(-1, 0, conn);
-      conn_send_eof(conn);
-    }
-  } else {
-    if (ckt->config->mode != LSN_SIMPLE_SERVER)
-      circuit_arm_flush_timer(ckt, ckt->flush_interval());
+    if (!upstream->recv_queue.insert(hdr.seqno(), hdr.opcode(), data, this))
+      return -1; // insert() logs an error
   }
 
-  return 0;
+  return upstream->process_queue();
 }
 
 int
 chop_conn_t::recv_eof()
 {
-  /* EOF on a _connection_ does not mean EOF on a _circuit_.
-     EOF on a _circuit_ occurs when chop_push_to_upstream processes a FIN.
-     We should only drop the connection from the circuit if we're no
-     longer sending in the opposite direction.  Also, we should not
-     drop the connection if its must-transmit timer is still pending.  */
-  if (this->upstream) {
-    chop_circuit_t *ckt = this->upstream;
-
-    if (evbuffer_get_length(this->inbound()) > 0)
-      if (this->recv())
-        return -1;
-
-    if ((ckt->sent_fin || this->no_more_transmissions) &&
-        (!this->must_transmit_timer ||
-         !evtimer_pending(this->must_transmit_timer, NULL)))
-      ckt->drop_downstream(this);
+  // Consume any not-yet-processed incoming data.  It's possible for
+  // us to get here before we've processed _any_ data -- including the
+  // handshake! -- from a new connection, so we have to do this before
+  // we look at ->upstream.  */
+  if (evbuffer_get_length(inbound()) > 0) {
+    if (recv())
+      return -1;
+    // If there's anything left in the buffer at this point, it's a
+    // protocol error.
+    if (evbuffer_get_length(inbound()) > 0)
+      return -1;
   }
+
+  // We should only drop the connection from the circuit if we're no
+  // longer sending covert data in the opposite direction _and_ the
+  // cover protocol does not need us to send a reply (i.e. the
+  // must_send_timer is not pending).
+  if (upstream && (upstream->sent_fin || no_more_transmissions) &&
+      !must_send_p())
+    upstream->drop_downstream(this);
+
   return 0;
 }
 
 void
 chop_conn_t::expect_close()
 {
-  /* do we need to do something here? */
+  // We currently don't need to do anything here.
+  // FIXME: figure out if this hook is _ever_ useful, and if not, remove it.
 }
 
 void
 chop_conn_t::cease_transmission()
 {
-  this->no_more_transmissions = true;
+  no_more_transmissions = true;
+  if (must_send_timer)
+    evtimer_del(must_send_timer);
   conn_do_flush(this);
 }
 
@@ -1370,13 +1301,97 @@ chop_conn_t::transmit_soon(unsigned long milliseconds)
 {
   struct timeval tv;
 
-  log_debug(this, "must transmit within %lu milliseconds", milliseconds);
+  log_debug(this, "must send within %lu milliseconds", milliseconds);
 
   tv.tv_sec = milliseconds / 1000;
   tv.tv_usec = (milliseconds % 1000) * 1000;
 
-  if (!this->must_transmit_timer)
-    this->must_transmit_timer = evtimer_new(this->config->base,
-                                            must_transmit_timer_cb, this);
-  evtimer_add(this->must_transmit_timer, &tv);
+  if (!must_send_timer)
+    must_send_timer = evtimer_new(config->base, must_send_timeout, this);
+  evtimer_add(must_send_timer, &tv);
+}
+
+void
+chop_conn_t::send()
+{
+  if (must_send_timer)
+    evtimer_del(must_send_timer);
+
+  if (!steg) {
+    log_warn(this, "send() called with no steg module available");
+    conn_do_flush(this);
+    return;
+  }
+
+  // When this happens, we must send _even if_ we have no upstream to
+  // provide us with data.  For instance, to preserve the cover
+  // protocol, we must send an HTTP reply to each HTTP query that
+  // comes in for a stale circuit.
+  if (upstream) {
+    log_debug(this, "must send");
+    if (upstream->send_targeted(this))
+      conn_do_flush(this);
+
+  } else {
+    log_debug(this, "must send (no upstream)");
+
+    size_t room = steg->transmit_room(this);
+    if (room < MIN_BLOCK_SIZE) {
+      log_warn(this, "send() called without enough transmit room "
+               "(have %lu, need %lu)", (unsigned long)room,
+               (unsigned long)MIN_BLOCK_SIZE);
+      conn_do_flush(this);
+      return;
+    }
+
+    // Since we have no upstream, we can't encrypt anything; instead,
+    // generate random bytes and feed them straight to steg_transmit.
+    struct evbuffer *chaff = evbuffer_new();
+    struct evbuffer_iovec v;
+    if (!chaff || evbuffer_reserve_space(chaff, MIN_BLOCK_SIZE, &v, 1) != 1 ||
+        v.iov_len < MIN_BLOCK_SIZE) {
+      log_warn(this, "memory allocation failed");
+      if (chaff)
+        evbuffer_free(chaff);
+      conn_do_flush(this);
+      return;
+    }
+    v.iov_len = MIN_BLOCK_SIZE;
+    rng_bytes((uint8_t *)v.iov_base, MIN_BLOCK_SIZE);
+    if (evbuffer_commit_space(chaff, &v, 1)) {
+      log_warn(this, "evbuffer_commit_space failed");
+      if (chaff)
+        evbuffer_free(chaff);
+      conn_do_flush(this);
+      return;
+    }
+
+    if (steg->transmit(chaff, this))
+      conn_do_flush(this);
+
+    evbuffer_free(chaff);
+  }
+}
+
+bool
+chop_conn_t::must_send_p() const
+{
+  return must_send_timer && evtimer_pending(must_send_timer, 0);
+}
+
+/* static */ void
+chop_conn_t::must_send_timeout(evutil_socket_t, short, void *arg)
+{
+  static_cast<chop_conn_t *>(arg)->send();
 }
+
+} // anonymous namespace
+
+PROTO_DEFINE_MODULE(chop);
+
+// Local Variables:
+// mode: c++
+// c-basic-offset: 2
+// c-file-style: "gnu"
+// c-file-offsets: ((innamespace . 0) (brace-list-open . 0))
+// End:



_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits