[tor-commits] [tor/master] Merge branch 'ticket17238_029_02-resquash'

nickm at torproject.org nickm at torproject.org
Fri Nov 4 18:48:12 UTC 2016


commit c35c43d7d9df793c890deb14800e24327fbca452
Merge: bd6aa4f c189cb5
Author: Nick Mathewson <nickm at torproject.org>
Date:   Fri Nov 4 13:26:37 2016 -0400

    Merge branch 'ticket17238_029_02-resquash'
    
    Conflicts:
    	src/or/rendclient.c
    	src/or/rendcommon.c
    	src/or/routerparse.c
    	src/test/test_dir.c
    	src/trunnel/ed25519_cert.h

 src/or/circuitlist.c             |   38 +-
 src/or/circuitlist.h             |    2 +-
 src/or/circuituse.c              |    9 +-
 src/or/connection.c              |    5 +-
 src/or/connection_edge.c         |   10 +-
 src/or/control.c                 |   21 +-
 src/or/directory.c               |  179 +++-
 src/or/directory.h               |   15 +-
 src/or/hs_cache.c                |  384 ++++++++
 src/or/hs_cache.h                |   61 ++
 src/or/hs_common.c               |  280 ++++++
 src/or/hs_common.h               |   39 +
 src/or/hs_descriptor.c           | 1939 ++++++++++++++++++++++++++++++++++++++
 src/or/hs_descriptor.h           |  238 +++++
 src/or/include.am                |    8 +
 src/or/main.c                    |    4 +-
 src/or/or.h                      |   42 +-
 src/or/parsecommon.c             |  450 +++++++++
 src/or/parsecommon.h             |  314 ++++++
 src/or/relay.c                   |    5 +-
 src/or/rendcache.c               |   79 +-
 src/or/rendcache.h               |   13 +-
 src/or/rendclient.c              |  148 +--
 src/or/rendclient.h              |    2 +-
 src/or/rendcommon.c              |  149 +--
 src/or/rendcommon.h              |   24 +-
 src/or/rendservice.c             |   87 +-
 src/or/routerparse.c             |  723 +-------------
 src/or/torcert.h                 |   15 +-
 src/test/include.am              |    2 +
 src/test/test.c                  |    2 +
 src/test/test.h                  |    2 +
 src/test/test_connection.c       |   12 +-
 src/test/test_dir.c              |   62 ++
 src/test/test_dir_handle_get.c   |   13 -
 src/test/test_helpers.c          |   13 +
 src/test/test_helpers.h          |    4 +
 src/test/test_hs.c               |  103 +-
 src/test/test_hs_cache.c         |  479 ++++++++++
 src/test/test_hs_descriptor.c    | 1130 ++++++++++++++++++++++
 src/test/test_rendcache.c        |   57 +-
 src/trunnel/ed25519_cert.c       |  881 +++++++++++++++++
 src/trunnel/ed25519_cert.h       |  300 ++++++
 src/trunnel/ed25519_cert.trunnel |    7 +-
 44 files changed, 7168 insertions(+), 1182 deletions(-)

diff --cc src/or/connection_edge.c
index 0c18de0,2726349..875c911
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@@ -72,9 -27,9 +72,10 @@@
  #include "control.h"
  #include "dns.h"
  #include "dnsserv.h"
 +#include "directory.h"
  #include "dirserv.h"
  #include "hibernate.h"
+ #include "hs_common.h"
  #include "main.h"
  #include "nodelist.h"
  #include "policies.h"
@@@ -1861,11 -1708,12 +1862,12 @@@ connection_ap_handshake_rewrite_and_att
      if (rend_data == NULL) {
        return -1;
      }
+     const char *onion_address = rend_data_get_address(rend_data);
      log_info(LD_REND,"Got a hidden service request for ID '%s'",
-              safe_str_client(rend_data->onion_address));
+              safe_str_client(onion_address));
  
 -    /* Lookup the given onion address. If invalid, stop right now else we
 -     * might have it in the cache or not, it will be tested later on. */
 +    /* Lookup the given onion address. If invalid, stop right now.
 +     * Otherwise, we might have it in the cache or not. */
      unsigned int refetch_desc = 0;
      rend_cache_entry_t *entry = NULL;
      const int rend_cache_lookup_result =
diff --cc src/or/directory.h
index f1cdd9f,210d623..acb7394
--- a/src/or/directory.h
+++ b/src/or/directory.h
@@@ -132,11 -132,18 +132,19 @@@ int download_status_get_n_failures(cons
  int download_status_get_n_attempts(const download_status_t *dls);
  time_t download_status_get_next_attempt_at(const download_status_t *dls);
  
 -int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose);
 +int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose,
 +                            const char *resource);
  
+ #ifdef DIRECTORY_PRIVATE
+ 
+ struct get_handler_args_t;
+ STATIC int handle_get_hs_descriptor_v3(dir_connection_t *conn,
+                                        const struct get_handler_args_t *args);
+ 
+ #endif
+ 
  #ifdef TOR_UNIT_TESTS
- /* Used only by directory.c and test_dir.c */
+ /* Used only by test_dir.c */
  
  STATIC int parse_http_url(const char *headers, char **url);
  STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose,
diff --cc src/or/include.am
index b4554aa,02d67fa..10f8b85
--- a/src/or/include.am
+++ b/src/or/include.am
@@@ -59,8 -62,8 +62,9 @@@ LIBTOR_A_SOURCES = 
  	src/or/shared_random.c			\
  	src/or/shared_random_state.c		\
  	src/or/transports.c				\
+ 	src/or/parsecommon.c			\
  	src/or/periodic.c				\
 +	src/or/protover.c				\
  	src/or/policies.c				\
  	src/or/reasons.c				\
  	src/or/relay.c					\
@@@ -171,9 -177,9 +178,10 @@@ ORHEADERS = 
  	src/or/shared_random.h			\
  	src/or/shared_random_state.h		\
  	src/or/transports.h				\
+ 	src/or/parsecommon.h			\
  	src/or/periodic.h				\
  	src/or/policies.h				\
 +	src/or/protover.h				\
  	src/or/reasons.h				\
  	src/or/relay.h					\
  	src/or/rendcache.h				\
diff --cc src/or/parsecommon.h
index 0000000,be7cba4..3a86c52
mode 000000,100644..100644
--- a/src/or/parsecommon.h
+++ b/src/or/parsecommon.h
@@@ -1,0 -1,306 +1,314 @@@
+ /* Copyright (c) 2016, The Tor Project, Inc. */
+ /* See LICENSE for licensing information */
+ 
+ /**
+  * \file parsecommon.h
+  * \brief Header file for parsecommon.c
+  **/
+ 
+ #ifndef TOR_PARSECOMMON_H
+ #define TOR_PARSECOMMON_H
+ 
+ #include "container.h"
+ #include "crypto.h"
+ #include "memarea.h"
+ 
+ /** Enumeration of possible token types.  The ones starting with K_ correspond
+ * to directory 'keywords'. A_ is for an annotation, R or C is related to
+ * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an
+ * end-of-file marker, and NIL_ is used to encode not-a-token.
+ */
+ typedef enum {
+   K_ACCEPT = 0,
+   K_ACCEPT6,
+   K_DIRECTORY_SIGNATURE,
+   K_RECOMMENDED_SOFTWARE,
+   K_REJECT,
+   K_REJECT6,
+   K_ROUTER,
+   K_SIGNED_DIRECTORY,
+   K_SIGNING_KEY,
+   K_ONION_KEY,
+   K_ONION_KEY_NTOR,
+   K_ROUTER_SIGNATURE,
+   K_PUBLISHED,
+   K_RUNNING_ROUTERS,
+   K_ROUTER_STATUS,
+   K_PLATFORM,
++  K_PROTO,
+   K_OPT,
+   K_BANDWIDTH,
+   K_CONTACT,
+   K_NETWORK_STATUS,
+   K_UPTIME,
+   K_DIR_SIGNING_KEY,
+   K_FAMILY,
+   K_FINGERPRINT,
+   K_HIBERNATING,
+   K_READ_HISTORY,
+   K_WRITE_HISTORY,
+   K_NETWORK_STATUS_VERSION,
+   K_DIR_SOURCE,
+   K_DIR_OPTIONS,
+   K_CLIENT_VERSIONS,
+   K_SERVER_VERSIONS,
++  K_RECOMMENDED_CLIENT_PROTOCOLS,
++  K_RECOMMENDED_RELAY_PROTOCOLS,
++  K_REQUIRED_CLIENT_PROTOCOLS,
++  K_REQUIRED_RELAY_PROTOCOLS,
+   K_OR_ADDRESS,
+   K_ID,
+   K_P,
+   K_P6,
+   K_R,
+   K_A,
+   K_S,
+   K_V,
+   K_W,
+   K_M,
+   K_EXTRA_INFO,
+   K_EXTRA_INFO_DIGEST,
+   K_CACHES_EXTRA_INFO,
+   K_HIDDEN_SERVICE_DIR,
+   K_ALLOW_SINGLE_HOP_EXITS,
+   K_IPV6_POLICY,
+   K_ROUTER_SIG_ED25519,
+   K_IDENTITY_ED25519,
+   K_MASTER_KEY_ED25519,
+   K_ONION_KEY_CROSSCERT,
+   K_NTOR_ONION_KEY_CROSSCERT,
+ 
+   K_DIRREQ_END,
+   K_DIRREQ_V2_IPS,
+   K_DIRREQ_V3_IPS,
+   K_DIRREQ_V2_REQS,
+   K_DIRREQ_V3_REQS,
+   K_DIRREQ_V2_SHARE,
+   K_DIRREQ_V3_SHARE,
+   K_DIRREQ_V2_RESP,
+   K_DIRREQ_V3_RESP,
+   K_DIRREQ_V2_DIR,
+   K_DIRREQ_V3_DIR,
+   K_DIRREQ_V2_TUN,
+   K_DIRREQ_V3_TUN,
+   K_ENTRY_END,
+   K_ENTRY_IPS,
+   K_CELL_END,
+   K_CELL_PROCESSED,
+   K_CELL_QUEUED,
+   K_CELL_TIME,
+   K_CELL_CIRCS,
+   K_EXIT_END,
+   K_EXIT_WRITTEN,
+   K_EXIT_READ,
+   K_EXIT_OPENED,
+ 
+   K_DIR_KEY_CERTIFICATE_VERSION,
+   K_DIR_IDENTITY_KEY,
+   K_DIR_KEY_PUBLISHED,
+   K_DIR_KEY_EXPIRES,
+   K_DIR_KEY_CERTIFICATION,
+   K_DIR_KEY_CROSSCERT,
+   K_DIR_ADDRESS,
+   K_DIR_TUNNELLED,
+ 
+   K_VOTE_STATUS,
+   K_VALID_AFTER,
+   K_FRESH_UNTIL,
+   K_VALID_UNTIL,
+   K_VOTING_DELAY,
+ 
+   K_KNOWN_FLAGS,
+   K_PARAMS,
+   K_BW_WEIGHTS,
+   K_VOTE_DIGEST,
+   K_CONSENSUS_DIGEST,
+   K_ADDITIONAL_DIGEST,
+   K_ADDITIONAL_SIGNATURE,
+   K_CONSENSUS_METHODS,
+   K_CONSENSUS_METHOD,
+   K_LEGACY_DIR_KEY,
+   K_DIRECTORY_FOOTER,
+   K_SIGNING_CERT_ED,
+   K_SR_FLAG,
+   K_COMMIT,
+   K_PREVIOUS_SRV,
+   K_CURRENT_SRV,
+   K_PACKAGE,
+ 
+   A_PURPOSE,
+   A_LAST_LISTED,
+   A_UNKNOWN_,
+ 
+   R_RENDEZVOUS_SERVICE_DESCRIPTOR,
+   R_VERSION,
+   R_PERMANENT_KEY,
+   R_SECRET_ID_PART,
+   R_PUBLICATION_TIME,
+   R_PROTOCOL_VERSIONS,
+   R_INTRODUCTION_POINTS,
+   R_SIGNATURE,
+ 
+   R_HS_DESCRIPTOR, /* From version 3, this MUST be generic to all future
+                       descriptor versions thus making it R_. */
+   R3_DESC_LIFETIME,
+   R3_DESC_SIGNING_CERT,
+   R3_REVISION_COUNTER,
+   R3_ENCRYPTED,
+   R3_SIGNATURE,
+   R3_CREATE2_FORMATS,
+   R3_AUTHENTICATION_REQUIRED,
+   R3_INTRODUCTION_POINT,
+   R3_INTRO_AUTH_KEY,
+   R3_INTRO_ENC_KEY,
+   R3_INTRO_ENC_KEY_CERTIFICATION,
+ 
+   R_IPO_IDENTIFIER,
+   R_IPO_IP_ADDRESS,
+   R_IPO_ONION_PORT,
+   R_IPO_ONION_KEY,
+   R_IPO_SERVICE_KEY,
+ 
+   C_CLIENT_NAME,
+   C_DESCRIPTOR_COOKIE,
+   C_CLIENT_KEY,
+ 
+   ERR_,
+   EOF_,
+   NIL_
+ } directory_keyword;
+ 
+ /** Structure to hold a single directory token.
+  *
+  * We parse a directory by breaking it into "tokens", each consisting
+  * of a keyword, a line full of arguments, and a binary object.  The
+  * arguments and object are both optional, depending on the keyword
+  * type.
+  *
+  * This structure is only allocated in memareas; do not allocate it on
+  * the heap, or token_clear() won't work.
+  */
+ typedef struct directory_token_t {
+   directory_keyword tp;        /**< Type of the token. */
+   int n_args:30;               /**< Number of elements in args */
+   char **args;                 /**< Array of arguments from keyword line. */
+ 
+   char *object_type;           /**< -----BEGIN [object_type]-----*/
+   size_t object_size;          /**< Bytes in object_body */
+   char *object_body;           /**< Contents of object, base64-decoded. */
+ 
+   crypto_pk_t *key;        /**< For public keys only.  Heap-allocated. */
+ 
+   char *error;                 /**< For ERR_ tokens only. */
+ } directory_token_t;
+ 
+ /** We use a table of rules to decide how to parse each token type. */
+ 
+ /** Rules for whether the keyword needs an object. */
+ typedef enum {
+   NO_OBJ,        /**< No object, ever. */
+   NEED_OBJ,      /**< Object is required. */
+   NEED_SKEY_1024,/**< Object is required, and must be a 1024 bit private key */
+   NEED_KEY_1024, /**< Object is required, and must be a 1024 bit public key */
+   NEED_KEY,      /**< Object is required, and must be a public key. */
+   OBJ_OK,        /**< Object is optional. */
+ } obj_syntax;
+ 
+ #define AT_START 1
+ #define AT_END 2
+ 
+ #define TS_ANNOTATIONS_OK 1
+ #define TS_NOCHECK 2
+ #define TS_NO_NEW_ANNOTATIONS 4
+ 
 -/*
++/**
++ * @name macros for defining token rules
++ *
+  * Helper macros to define token tables.  's' is a string, 't' is a
+  * directory_keyword, 'a' is a trio of argument multiplicities, and 'o' is an
+  * object syntax.
 - *
+  */
++/**@{*/
+ 
+ /** Appears to indicate the end of a table. */
+ #define END_OF_TABLE { NULL, NIL_, 0,0,0, NO_OBJ, 0, INT_MAX, 0, 0 }
+ /** An item with no restrictions: used for obsolete document types */
+ #define T(s,t,a,o)    { s, t, a, o, 0, INT_MAX, 0, 0 }
+ /** An item with no restrictions on multiplicity or location. */
+ #define T0N(s,t,a,o)  { s, t, a, o, 0, INT_MAX, 0, 0 }
+ /** An item that must appear exactly once */
+ #define T1(s,t,a,o)   { s, t, a, o, 1, 1, 0, 0 }
+ /** An item that must appear exactly once, at the start of the document */
+ #define T1_START(s,t,a,o)   { s, t, a, o, 1, 1, AT_START, 0 }
+ /** An item that must appear exactly once, at the end of the document */
+ #define T1_END(s,t,a,o)   { s, t, a, o, 1, 1, AT_END, 0 }
+ /** An item that must appear one or more times */
+ #define T1N(s,t,a,o)  { s, t, a, o, 1, INT_MAX, 0, 0 }
+ /** An item that must appear no more than once */
+ #define T01(s,t,a,o)  { s, t, a, o, 0, 1, 0, 0 }
+ /** An annotation that must appear no more than once */
+ #define A01(s,t,a,o)  { s, t, a, o, 0, 1, 0, 1 }
+ 
 -/* Argument multiplicity: any number of arguments. */
++/** Argument multiplicity: any number of arguments. */
+ #define ARGS        0,INT_MAX,0
 -/* Argument multiplicity: no arguments. */
++/** Argument multiplicity: no arguments. */
+ #define NO_ARGS     0,0,0
 -/* Argument multiplicity: concatenate all arguments. */
++/** Argument multiplicity: concatenate all arguments. */
+ #define CONCAT_ARGS 1,1,1
 -/* Argument multiplicity: at least <b>n</b> arguments. */
++/** Argument multiplicity: at least <b>n</b> arguments. */
+ #define GE(n)       n,INT_MAX,0
 -/* Argument multiplicity: exactly <b>n</b> arguments. */
++/** Argument multiplicity: exactly <b>n</b> arguments. */
+ #define EQ(n)       n,n,0
++/**@}*/
+ 
+ /** Determines the parsing rules for a single token type. */
+ typedef struct token_rule_t {
+   /** The string value of the keyword identifying the type of item. */
+   const char *t;
+   /** The corresponding directory_keyword enum. */
+   directory_keyword v;
+   /** Minimum number of arguments for this item */
+   int min_args;
+   /** Maximum number of arguments for this item */
+   int max_args;
+   /** If true, we concatenate all arguments for this item into a single
+    * string. */
+   int concat_args;
+   /** Requirements on object syntax for this item. */
+   obj_syntax os;
+   /** Lowest number of times this item may appear in a document. */
+   int min_cnt;
+   /** Highest number of times this item may appear in a document. */
+   int max_cnt;
+   /** One or more of AT_START/AT_END to limit where the item may appear in a
+    * document. */
+   int pos;
+   /** True iff this token is an annotation. */
+   int is_annotation;
+ } token_rule_t;
+ 
+ void token_clear(directory_token_t *tok);
+ 
+ int tokenize_string(memarea_t *area,
+                     const char *start, const char *end,
+                     smartlist_t *out,
+                     token_rule_t *table,
+                     int flags);
+ directory_token_t *get_next_token(memarea_t *area,
+                                   const char **s,
+                                   const char *eos,
+                                   token_rule_t *table);
+ 
+ directory_token_t *find_by_keyword_(smartlist_t *s,
+                                     directory_keyword keyword,
+                                     const char *keyword_str);
+ 
+ #define find_by_keyword(s, keyword) \
+   find_by_keyword_((s), (keyword), #keyword)
+ 
+ directory_token_t *find_opt_by_keyword(smartlist_t *s,
+                                        directory_keyword keyword);
+ smartlist_t * find_all_by_keyword(smartlist_t *s, directory_keyword k);
+ 
+ #endif /* TOR_PARSECOMMON_H */
+ 
diff --cc src/or/rendclient.c
index a93bc94,0bfc1a1..b0dcf52
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@@ -149,13 -150,15 +151,13 @@@ rend_client_send_introduction(origin_ci
    tor_assert(rendcirc->base_.purpose == CIRCUIT_PURPOSE_C_REND_READY);
    tor_assert(introcirc->rend_data);
    tor_assert(rendcirc->rend_data);
-   tor_assert(!rend_cmp_service_ids(introcirc->rend_data->onion_address,
-                                    rendcirc->rend_data->onion_address));
+   tor_assert(!rend_cmp_service_ids(rend_data_get_address(introcirc->rend_data),
+                                   rend_data_get_address(rendcirc->rend_data)));
 -#ifndef NON_ANONYMOUS_MODE_ENABLED
 -  tor_assert(!(introcirc->build_state->onehop_tunnel));
 -  tor_assert(!(rendcirc->build_state->onehop_tunnel));
 -#endif
 +  assert_circ_anonymity_ok(introcirc, options);
 +  assert_circ_anonymity_ok(rendcirc, options);
+   onion_address = rend_data_get_address(introcirc->rend_data);
  
-   r = rend_cache_lookup_entry(introcirc->rend_data->onion_address, -1,
-                               &entry);
+   r = rend_cache_lookup_entry(onion_address, -1, &entry);
    /* An invalid onion address is not possible else we have a big issue. */
    tor_assert(r != -EINVAL);
    if (r < 0 || !rend_client_any_intro_points_usable(entry)) {
diff --cc src/or/rendcommon.c
index d9d39b1,125aa0f..1e5d1ab
--- a/src/or/rendcommon.c
+++ b/src/or/rendcommon.c
@@@ -1067,52 -950,32 +950,82 @@@ rend_auth_decode_cookie(const char *coo
    return res;
  }
  
 +/* Is this a rend client or server that allows direct (non-anonymous)
 + * connections?
 + * Clients must be specifically compiled and configured in this mode.
 + * Onion services can be configured to start in this mode.
 + * Prefer rend_client_allow_non_anonymous_connection() or
 + * rend_service_allow_non_anonymous_connection() whenever possible, so that
 + * checks are specific to Single Onion Services or Tor2web. */
 +int
 +rend_allow_non_anonymous_connection(const or_options_t* options)
 +{
 +  return (rend_client_allow_non_anonymous_connection(options)
 +          || rend_service_allow_non_anonymous_connection(options));
 +}
 +
 +/* Is this a rend client or server in non-anonymous mode?
 + * Clients must be specifically compiled in this mode.
 + * Onion services can be configured to start in this mode.
 + * Prefer rend_client_non_anonymous_mode_enabled() or
 + * rend_service_non_anonymous_mode_enabled() whenever possible, so that checks
 + * are specific to Single Onion Services or Tor2web. */
 +int
 +rend_non_anonymous_mode_enabled(const or_options_t *options)
 +{
 +  return (rend_client_non_anonymous_mode_enabled(options)
 +          || rend_service_non_anonymous_mode_enabled(options));
 +}
 +
 +/* Make sure that tor only builds one-hop circuits when they would not
 + * compromise user anonymity.
 + *
 + * One-hop circuits are permitted in Tor2web or Single Onion modes.
 + *
 + * Tor2web or Single Onion modes are also allowed to make multi-hop circuits.
 + * For example, single onion HSDir circuits are 3-hop to prevent denial of
 + * service.
 + */
 +void
 +assert_circ_anonymity_ok(origin_circuit_t *circ,
 +                         const or_options_t *options)
 +{
 +  tor_assert(options);
 +  tor_assert(circ);
 +  tor_assert(circ->build_state);
 +
 +  if (circ->build_state->onehop_tunnel) {
 +    tor_assert(rend_allow_non_anonymous_connection(options));
 +  }
 +}
 +
+ /* Return 1 iff the given <b>digest</b> of a permenanent hidden service key is
+  * equal to the digest in the origin circuit <b>ocirc</b> of its rend data .
+  * If the rend data doesn't exist, 0 is returned. This function is agnostic to
+  * the rend data version. */
+ int
+ rend_circuit_pk_digest_eq(const origin_circuit_t *ocirc,
+                           const uint8_t *digest)
+ {
+   size_t rend_pk_digest_len;
+   const uint8_t *rend_pk_digest;
+ 
+   tor_assert(ocirc);
+   tor_assert(digest);
+ 
+   if (ocirc->rend_data == NULL) {
+     goto no_match;
+   }
+ 
+   rend_pk_digest = rend_data_get_pk_digest(ocirc->rend_data,
+                                            &rend_pk_digest_len);
+   if (tor_memeq(rend_pk_digest, digest, rend_pk_digest_len)) {
+     goto match;
+   }
+  no_match:
+   return 0;
+  match:
+   return 1;
+ }
+ 
++
diff --cc src/or/rendservice.c
index 7083051,21e88eb..b6bf63d
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@@ -1681,8 -1474,12 +1682,10 @@@ rend_service_receive_introduction(origi
      goto err;
    }
  
 -#ifndef NON_ANONYMOUS_MODE_ENABLED
 -  tor_assert(!(circuit->build_state->onehop_tunnel));
 -#endif
 +  assert_circ_anonymity_ok(circuit, options);
    tor_assert(circuit->rend_data);
+   /* XXX: This is version 2 specific (only one supported). */
+   rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
  
    /* We'll use this in a bazillion log messages */
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
@@@ -2970,17 -2702,21 +2971,19 @@@ rend_service_intro_has_opened(origin_ci
    char auth[DIGEST_LEN + 9];
    char serviceid[REND_SERVICE_ID_LEN_BASE32+1];
    int reason = END_CIRC_REASON_TORPROTOCOL;
+   const char *rend_pk_digest;
  
    tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO);
 -#ifndef NON_ANONYMOUS_MODE_ENABLED
 -  tor_assert(!(circuit->build_state->onehop_tunnel));
 -#endif
 +  assert_circ_anonymity_ok(circuit, get_options());
    tor_assert(circuit->cpath);
    tor_assert(circuit->rend_data);
+   /* XXX: This is version 2 specific (only on supported). */
+   rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data, NULL);
  
    base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1,
-                 circuit->rend_data->rend_pk_digest, REND_SERVICE_ID_LEN);
+                 rend_pk_digest, REND_SERVICE_ID_LEN);
  
-   service = rend_service_get_by_pk_digest(
-                 circuit->rend_data->rend_pk_digest);
+   service = rend_service_get_by_pk_digest(rend_pk_digest);
    if (!service) {
      log_warn(LD_REND, "Unrecognized service ID %s on introduction circuit %u.",
               safe_str_client(serviceid), (unsigned)circuit->base_.n_circ_id);
@@@ -3168,9 -2906,16 +3173,14 @@@ rend_service_rendezvous_has_opened(orig
    tor_assert(circuit->base_.purpose == CIRCUIT_PURPOSE_S_CONNECT_REND);
    tor_assert(circuit->cpath);
    tor_assert(circuit->build_state);
 -#ifndef NON_ANONYMOUS_MODE_ENABLED
 -  tor_assert(!(circuit->build_state->onehop_tunnel));
 -#endif
 +  assert_circ_anonymity_ok(circuit, get_options());
    tor_assert(circuit->rend_data);
  
+   /* XXX: This is version 2 specific (only one supported). */
+   rend_pk_digest = (char *) rend_data_get_pk_digest(circuit->rend_data,
+                                                     NULL);
+   rend_cookie = circuit->rend_data->rend_cookie;
+ 
    /* Declare the circuit dirty to avoid reuse, and for path-bias */
    if (!circuit->base_.timestamp_dirty)
      circuit->base_.timestamp_dirty = time(NULL);
diff --cc src/or/routerparse.c
index cb2bc72,ef6273b..5bc2d39
--- a/src/or/routerparse.c
+++ b/src/or/routerparse.c
@@@ -60,8 -16,8 +60,9 @@@
  #include "circuitstats.h"
  #include "dirserv.h"
  #include "dirvote.h"
+ #include "parsecommon.h"
  #include "policies.h"
 +#include "protover.h"
  #include "rendcommon.h"
  #include "router.h"
  #include "routerlist.h"
diff --cc src/test/test_dir.c
index cf0b94c,4a2538b..c8daa18
--- a/src/test/test_dir.c
+++ b/src/test/test_dir.c
@@@ -5698,48 -5346,66 +5698,109 @@@ test_dir_find_dl_schedule(void* data
  }
  
  static void
 +test_dir_assumed_flags(void *arg)
 +{
 +  (void)arg;
 +  smartlist_t *tokens = smartlist_new();
 +  memarea_t *area = memarea_new();
 +  routerstatus_t *rs = NULL;
 +
 +  /* First, we should always assume that the Running flag is set, even
 +   * when it isn't listed, since the consensus method is always
 +   * higher than 4. */
 +  const char *str1 =
 +    "r example hereiswhereyouridentitygoes 2015-08-30 12:00:00 "
 +       "192.168.0.1 9001 0\n"
 +    "m thisoneislongerbecauseitisa256bitmddigest33\n"
 +    "s Fast Guard Stable\n";
 +
 +  const char *cp = str1;
 +  rs = routerstatus_parse_entry_from_string(area, &cp, tokens, NULL, NULL,
 +                                            23, FLAV_MICRODESC);
 +  tt_assert(rs);
 +  tt_assert(rs->is_flagged_running);
 +  tt_assert(! rs->is_valid);
 +  tt_assert(! rs->is_exit);
 +  tt_assert(rs->is_fast);
 +  routerstatus_free(rs);
 +
 +  /* With method 24 or later, we can assume "valid" is set. */
 +  cp = str1;
 +  rs = routerstatus_parse_entry_from_string(area, &cp, tokens, NULL, NULL,
 +                                            24, FLAV_MICRODESC);
 +  tt_assert(rs);
 +  tt_assert(rs->is_flagged_running);
 +  tt_assert(rs->is_valid);
 +  tt_assert(! rs->is_exit);
 +  tt_assert(rs->is_fast);
 +
 + done:
 +  smartlist_free(tokens);
 +  memarea_drop_all(area);
 +  routerstatus_free(rs);
 +}
 +
++static void
+ test_dir_post_parsing(void *arg)
+ {
+   (void) arg;
+ 
+   /* Test the version parsing from an HS descriptor publish request. */
+   {
+     const char *end;
+     const char *prefix = "/tor/hs/";
+     int version = parse_hs_version_from_post("/tor/hs//publish", prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     version = parse_hs_version_from_post("/tor/hs/a/publish", prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     version = parse_hs_version_from_post("/tor/hs/3/publish", prefix, &end);
+     tt_int_op(version, OP_EQ, 3);
+     tt_str_op(end, OP_EQ, "/publish");
+     version = parse_hs_version_from_post("/tor/hs/42/publish", prefix, &end);
+     tt_int_op(version, OP_EQ, 42);
+     tt_str_op(end, OP_EQ, "/publish");
+     version = parse_hs_version_from_post("/tor/hs/18163/publish", prefix, &end);
+     tt_int_op(version, OP_EQ, 18163);
+     tt_str_op(end, OP_EQ, "/publish");
+     version = parse_hs_version_from_post("JUNKJUNKJUNK", prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     version = parse_hs_version_from_post("/tor/hs/3/publish", "blah", &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     /* Missing the '/' at the end of the prefix. */
+     version = parse_hs_version_from_post("/tor/hs/3/publish", "/tor/hs", &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     version = parse_hs_version_from_post("/random/blah/tor/hs/3/publish",
+                                          prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     version = parse_hs_version_from_post("/tor/hs/3/publish/random/junk",
+                                          prefix, &end);
+     tt_int_op(version, OP_EQ, 3);
+     tt_str_op(end, OP_EQ, "/publish/random/junk");
+     version = parse_hs_version_from_post("/tor/hs/-1/publish", prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+     /* INT_MAX */
+     version = parse_hs_version_from_post("/tor/hs/2147483647/publish",
+                                          prefix, &end);
+     tt_int_op(version, OP_EQ, INT_MAX);
+     tt_str_op(end, OP_EQ, "/publish");
+     /* INT_MAX + 1*/
+     version = parse_hs_version_from_post("/tor/hs/2147483648/publish",
+                                          prefix, &end);
+     tt_int_op(version, OP_EQ, -1);
+     tt_ptr_op(end, OP_EQ, NULL);
+   }
+ 
+  done:
+   ;
+ }
+ 
  #define DIR_LEGACY(name)                             \
    { #name, test_dir_ ## name , TT_FORK, NULL, NULL }
  
@@@ -5773,11 -5439,8 +5834,12 @@@ struct testcase_t dir_tests[] = 
    DIR(fmt_control_ns, 0),
    DIR(dirserv_set_routerstatus_testing, 0),
    DIR(http_handling, 0),
 +  DIR(purpose_needs_anonymity_returns_true_for_bridges, 0),
 +  DIR(purpose_needs_anonymity_returns_false_for_own_bridge_desc, 0),
 +  DIR(purpose_needs_anonymity_returns_true_by_default, 0),
 +  DIR(purpose_needs_anonymity_returns_true_for_sensitive_purpose, 0),
 +  DIR(purpose_needs_anonymity_ret_false_for_non_sensitive_conn, 0),
+   DIR(post_parsing, 0),
 -  DIR(purpose_needs_anonymity, 0),
    DIR(fetch_type, 0),
    DIR(packages, 0),
    DIR(download_status_schedule, 0),
diff --cc src/test/test_hs.c
index fd5ab15,3bf4e6d..67ce1cf
--- a/src/test/test_hs.c
+++ b/src/test/test_hs.c
@@@ -14,8 -13,8 +14,9 @@@
  #include "test.h"
  #include "control.h"
  #include "config.h"
+ #include "hs_common.h"
  #include "rendcommon.h"
 +#include "rendservice.h"
  #include "routerset.h"
  #include "circuitbuild.h"
  #include "test_helpers.h"
diff --cc src/test/test_rendcache.c
index a5d3f35,afcd117..7f72e44
--- a/src/test/test_rendcache.c
+++ b/src/test/test_rendcache.c
@@@ -10,9 -10,9 +10,10 @@@
  #include "router.h"
  #include "routerlist.h"
  #include "config.h"
+ #include "hs_common.h"
  #include <openssl/rsa.h>
  #include "rend_test_helpers.h"
 +#include "log_test_helpers.h"
  
  #define NS_MODULE rend_cache
  
diff --cc src/trunnel/ed25519_cert.c
index a492ada,dc1485d..dd5088b
--- a/src/trunnel/ed25519_cert.c
+++ b/src/trunnel/ed25519_cert.c
@@@ -430,6 -410,557 +430,597 @@@ ed25519_cert_extension_parse(ed25519_ce
    }
    return result;
  }
+ link_specifier_t *
+ link_specifier_new(void)
+ {
+   link_specifier_t *val = trunnel_calloc(1, sizeof(link_specifier_t));
+   if (NULL == val)
+     return NULL;
+   return val;
+ }
+ 
+ /** Release all storage held inside 'obj', but do not free 'obj'.
+  */
+ static void
+ link_specifier_clear(link_specifier_t *obj)
+ {
+   (void) obj;
+   TRUNNEL_DYNARRAY_WIPE(&obj->un_unrecognized);
+   TRUNNEL_DYNARRAY_CLEAR(&obj->un_unrecognized);
+ }
+ 
+ void
+ link_specifier_free(link_specifier_t *obj)
+ {
+   if (obj == NULL)
+     return;
+   link_specifier_clear(obj);
+   trunnel_memwipe(obj, sizeof(link_specifier_t));
+   trunnel_free_(obj);
+ }
+ 
+ uint8_t
+ link_specifier_get_ls_type(link_specifier_t *inp)
+ {
+   return inp->ls_type;
+ }
+ int
+ link_specifier_set_ls_type(link_specifier_t *inp, uint8_t val)
+ {
+   inp->ls_type = val;
+   return 0;
+ }
+ uint8_t
+ link_specifier_get_ls_len(link_specifier_t *inp)
+ {
+   return inp->ls_len;
+ }
+ int
+ link_specifier_set_ls_len(link_specifier_t *inp, uint8_t val)
+ {
+   inp->ls_len = val;
+   return 0;
+ }
+ uint32_t
+ link_specifier_get_un_ipv4_addr(link_specifier_t *inp)
+ {
+   return inp->un_ipv4_addr;
+ }
+ int
+ link_specifier_set_un_ipv4_addr(link_specifier_t *inp, uint32_t val)
+ {
+   inp->un_ipv4_addr = val;
+   return 0;
+ }
+ uint16_t
+ link_specifier_get_un_ipv4_port(link_specifier_t *inp)
+ {
+   return inp->un_ipv4_port;
+ }
+ int
+ link_specifier_set_un_ipv4_port(link_specifier_t *inp, uint16_t val)
+ {
+   inp->un_ipv4_port = val;
+   return 0;
+ }
+ size_t
+ link_specifier_getlen_un_ipv6_addr(const link_specifier_t *inp)
+ {
+   (void)inp;  return 16;
+ }
+ 
+ uint8_t
 -link_specifier_get_un_ipv6_addr(const link_specifier_t *inp, size_t idx)
++link_specifier_get_un_ipv6_addr(link_specifier_t *inp, size_t idx)
+ {
+   trunnel_assert(idx < 16);
+   return inp->un_ipv6_addr[idx];
+ }
+ 
++uint8_t
++link_specifier_getconst_un_ipv6_addr(const link_specifier_t *inp, size_t idx)
++{
++  return link_specifier_get_un_ipv6_addr((link_specifier_t*)inp, idx);
++}
+ int
+ link_specifier_set_un_ipv6_addr(link_specifier_t *inp, size_t idx, uint8_t elt)
+ {
+   trunnel_assert(idx < 16);
+   inp->un_ipv6_addr[idx] = elt;
+   return 0;
+ }
+ 
+ uint8_t *
+ link_specifier_getarray_un_ipv6_addr(link_specifier_t *inp)
+ {
+   return inp->un_ipv6_addr;
+ }
++const uint8_t  *
++link_specifier_getconstarray_un_ipv6_addr(const link_specifier_t *inp)
++{
++  return (const uint8_t  *)link_specifier_getarray_un_ipv6_addr((link_specifier_t*)inp);
++}
+ uint16_t
+ link_specifier_get_un_ipv6_port(link_specifier_t *inp)
+ {
+   return inp->un_ipv6_port;
+ }
+ int
+ link_specifier_set_un_ipv6_port(link_specifier_t *inp, uint16_t val)
+ {
+   inp->un_ipv6_port = val;
+   return 0;
+ }
+ size_t
+ link_specifier_getlen_un_legacy_id(const link_specifier_t *inp)
+ {
+   (void)inp;  return 20;
+ }
+ 
+ uint8_t
 -link_specifier_get_un_legacy_id(const link_specifier_t *inp, size_t idx)
++link_specifier_get_un_legacy_id(link_specifier_t *inp, size_t idx)
+ {
+   trunnel_assert(idx < 20);
+   return inp->un_legacy_id[idx];
+ }
+ 
++uint8_t
++link_specifier_getconst_un_legacy_id(const link_specifier_t *inp, size_t idx)
++{
++  return link_specifier_get_un_legacy_id((link_specifier_t*)inp, idx);
++}
+ int
+ link_specifier_set_un_legacy_id(link_specifier_t *inp, size_t idx, uint8_t elt)
+ {
+   trunnel_assert(idx < 20);
+   inp->un_legacy_id[idx] = elt;
+   return 0;
+ }
+ 
+ uint8_t *
+ link_specifier_getarray_un_legacy_id(link_specifier_t *inp)
+ {
+   return inp->un_legacy_id;
+ }
++const uint8_t  *
++link_specifier_getconstarray_un_legacy_id(const link_specifier_t *inp)
++{
++  return (const uint8_t  *)link_specifier_getarray_un_legacy_id((link_specifier_t*)inp);
++}
+ size_t
+ link_specifier_getlen_un_ed25519_id(const link_specifier_t *inp)
+ {
+   (void)inp;  return 32;
+ }
+ 
+ uint8_t
 -link_specifier_get_un_ed25519_id(const link_specifier_t *inp, size_t idx)
++link_specifier_get_un_ed25519_id(link_specifier_t *inp, size_t idx)
+ {
+   trunnel_assert(idx < 32);
+   return inp->un_ed25519_id[idx];
+ }
+ 
++uint8_t
++link_specifier_getconst_un_ed25519_id(const link_specifier_t *inp, size_t idx)
++{
++  return link_specifier_get_un_ed25519_id((link_specifier_t*)inp, idx);
++}
+ int
+ link_specifier_set_un_ed25519_id(link_specifier_t *inp, size_t idx, uint8_t elt)
+ {
+   trunnel_assert(idx < 32);
+   inp->un_ed25519_id[idx] = elt;
+   return 0;
+ }
+ 
+ uint8_t *
+ link_specifier_getarray_un_ed25519_id(link_specifier_t *inp)
+ {
+   return inp->un_ed25519_id;
+ }
++const uint8_t  *
++link_specifier_getconstarray_un_ed25519_id(const link_specifier_t *inp)
++{
++  return (const uint8_t  *)link_specifier_getarray_un_ed25519_id((link_specifier_t*)inp);
++}
+ size_t
+ link_specifier_getlen_un_unrecognized(const link_specifier_t *inp)
+ {
+   return TRUNNEL_DYNARRAY_LEN(&inp->un_unrecognized);
+ }
+ 
+ uint8_t
+ link_specifier_get_un_unrecognized(link_specifier_t *inp, size_t idx)
+ {
+   return TRUNNEL_DYNARRAY_GET(&inp->un_unrecognized, idx);
+ }
+ 
++uint8_t
++link_specifier_getconst_un_unrecognized(const link_specifier_t *inp, size_t idx)
++{
++  return link_specifier_get_un_unrecognized((link_specifier_t*)inp, idx);
++}
+ int
+ link_specifier_set_un_unrecognized(link_specifier_t *inp, size_t idx, uint8_t elt)
+ {
+   TRUNNEL_DYNARRAY_SET(&inp->un_unrecognized, idx, elt);
+   return 0;
+ }
+ int
+ link_specifier_add_un_unrecognized(link_specifier_t *inp, uint8_t elt)
+ {
+   TRUNNEL_DYNARRAY_ADD(uint8_t, &inp->un_unrecognized, elt, {});
+   return 0;
+  trunnel_alloc_failed:
+   TRUNNEL_SET_ERROR_CODE(inp);
+   return -1;
+ }
+ 
+ uint8_t *
+ link_specifier_getarray_un_unrecognized(link_specifier_t *inp)
+ {
+   return inp->un_unrecognized.elts_;
+ }
++const uint8_t  *
++link_specifier_getconstarray_un_unrecognized(const link_specifier_t *inp)
++{
++  return (const uint8_t  *)link_specifier_getarray_un_unrecognized((link_specifier_t*)inp);
++}
+ int
+ link_specifier_setlen_un_unrecognized(link_specifier_t *inp, size_t newlen)
+ {
+   uint8_t *newptr;
+   newptr = trunnel_dynarray_setlen(&inp->un_unrecognized.allocated_,
+                  &inp->un_unrecognized.n_, inp->un_unrecognized.elts_, newlen,
+                  sizeof(inp->un_unrecognized.elts_[0]), (trunnel_free_fn_t) NULL,
+                  &inp->trunnel_error_code_);
 -  if (newptr == NULL)
++  if (newlen != 0 && newptr == NULL)
+     goto trunnel_alloc_failed;
+   inp->un_unrecognized.elts_ = newptr;
+   return 0;
+  trunnel_alloc_failed:
+   TRUNNEL_SET_ERROR_CODE(inp);
+   return -1;
+ }
+ const char *
+ link_specifier_check(const link_specifier_t *obj)
+ {
+   if (obj == NULL)
+     return "Object was NULL";
+   if (obj->trunnel_error_code_)
+     return "A set function failed on this object";
+   switch (obj->ls_type) {
+ 
+     case LS_IPV4:
+       break;
+ 
+     case LS_IPV6:
+       break;
+ 
+     case LS_LEGACY_ID:
+       break;
+ 
+     case LS_ED25519_ID:
+       break;
+ 
+     default:
+       break;
+   }
+   return NULL;
+ }
+ 
+ ssize_t
+ link_specifier_encoded_len(const link_specifier_t *obj)
+ {
+   ssize_t result = 0;
+ 
+   if (NULL != link_specifier_check(obj))
+      return -1;
+ 
+ 
+   /* Length of u8 ls_type */
+   result += 1;
+ 
+   /* Length of u8 ls_len */
+   result += 1;
+   switch (obj->ls_type) {
+ 
+     case LS_IPV4:
+ 
+       /* Length of u32 un_ipv4_addr */
+       result += 4;
+ 
+       /* Length of u16 un_ipv4_port */
+       result += 2;
+       break;
+ 
+     case LS_IPV6:
+ 
+       /* Length of u8 un_ipv6_addr[16] */
+       result += 16;
+ 
+       /* Length of u16 un_ipv6_port */
+       result += 2;
+       break;
+ 
+     case LS_LEGACY_ID:
+ 
+       /* Length of u8 un_legacy_id[20] */
+       result += 20;
+       break;
+ 
+     case LS_ED25519_ID:
+ 
+       /* Length of u8 un_ed25519_id[32] */
+       result += 32;
+       break;
+ 
+     default:
+ 
+       /* Length of u8 un_unrecognized[] */
+       result += TRUNNEL_DYNARRAY_LEN(&obj->un_unrecognized);
+       break;
+   }
+   return result;
+ }
+ int
+ link_specifier_clear_errors(link_specifier_t *obj)
+ {
+   int r = obj->trunnel_error_code_;
+   obj->trunnel_error_code_ = 0;
+   return r;
+ }
+ ssize_t
+ link_specifier_encode(uint8_t *output, const size_t avail, const link_specifier_t *obj)
+ {
+   ssize_t result = 0;
+   size_t written = 0;
+   uint8_t *ptr = output;
+   const char *msg;
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   const ssize_t encoded_len = link_specifier_encoded_len(obj);
+ #endif
+ 
+   uint8_t *backptr_ls_len = NULL;
+ 
+   if (NULL != (msg = link_specifier_check(obj)))
+     goto check_failed;
+ 
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   trunnel_assert(encoded_len >= 0);
+ #endif
+ 
+   /* Encode u8 ls_type */
+   trunnel_assert(written <= avail);
+   if (avail - written < 1)
+     goto truncated;
+   trunnel_set_uint8(ptr, (obj->ls_type));
+   written += 1; ptr += 1;
+ 
+   /* Encode u8 ls_len */
+   backptr_ls_len = ptr;
+   trunnel_assert(written <= avail);
+   if (avail - written < 1)
+     goto truncated;
+   trunnel_set_uint8(ptr, (obj->ls_len));
+   written += 1; ptr += 1;
+   {
+     size_t written_before_union = written;
+ 
+     /* Encode union un[ls_type] */
+     trunnel_assert(written <= avail);
+     switch (obj->ls_type) {
+ 
+       case LS_IPV4:
+ 
+         /* Encode u32 un_ipv4_addr */
+         trunnel_assert(written <= avail);
+         if (avail - written < 4)
+           goto truncated;
+         trunnel_set_uint32(ptr, trunnel_htonl(obj->un_ipv4_addr));
+         written += 4; ptr += 4;
+ 
+         /* Encode u16 un_ipv4_port */
+         trunnel_assert(written <= avail);
+         if (avail - written < 2)
+           goto truncated;
+         trunnel_set_uint16(ptr, trunnel_htons(obj->un_ipv4_port));
+         written += 2; ptr += 2;
+         break;
+ 
+       case LS_IPV6:
+ 
+         /* Encode u8 un_ipv6_addr[16] */
+         trunnel_assert(written <= avail);
+         if (avail - written < 16)
+           goto truncated;
+         memcpy(ptr, obj->un_ipv6_addr, 16);
+         written += 16; ptr += 16;
+ 
+         /* Encode u16 un_ipv6_port */
+         trunnel_assert(written <= avail);
+         if (avail - written < 2)
+           goto truncated;
+         trunnel_set_uint16(ptr, trunnel_htons(obj->un_ipv6_port));
+         written += 2; ptr += 2;
+         break;
+ 
+       case LS_LEGACY_ID:
+ 
+         /* Encode u8 un_legacy_id[20] */
+         trunnel_assert(written <= avail);
+         if (avail - written < 20)
+           goto truncated;
+         memcpy(ptr, obj->un_legacy_id, 20);
+         written += 20; ptr += 20;
+         break;
+ 
+       case LS_ED25519_ID:
+ 
+         /* Encode u8 un_ed25519_id[32] */
+         trunnel_assert(written <= avail);
+         if (avail - written < 32)
+           goto truncated;
+         memcpy(ptr, obj->un_ed25519_id, 32);
+         written += 32; ptr += 32;
+         break;
+ 
+       default:
+ 
+         /* Encode u8 un_unrecognized[] */
+         {
+           size_t elt_len = TRUNNEL_DYNARRAY_LEN(&obj->un_unrecognized);
+           trunnel_assert(written <= avail);
+           if (avail - written < elt_len)
+             goto truncated;
+           if (elt_len)
+             memcpy(ptr, obj->un_unrecognized.elts_, elt_len);
+           written += elt_len; ptr += elt_len;
+         }
+         break;
+     }
+     /* Write the length field back to ls_len */
+     trunnel_assert(written >= written_before_union);
+ #if UINT8_MAX < SIZE_MAX
+     if (written - written_before_union > UINT8_MAX)
+       goto check_failed;
+ #endif
+     trunnel_set_uint8(backptr_ls_len, (written - written_before_union));
+   }
+ 
+ 
+   trunnel_assert(ptr == output + written);
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   {
+     trunnel_assert(encoded_len >= 0);
+     trunnel_assert((size_t)encoded_len == written);
+   }
+ 
+ #endif
+ 
+   return written;
+ 
+  truncated:
+   result = -2;
+   goto fail;
+  check_failed:
+   (void)msg;
+   result = -1;
+   goto fail;
+  fail:
+   trunnel_assert(result < 0);
+   return result;
+ }
+ 
+ /** As link_specifier_parse(), but do not allocate the output object.
+  */
+ static ssize_t
+ link_specifier_parse_into(link_specifier_t *obj, const uint8_t *input, const size_t len_in)
+ {
+   const uint8_t *ptr = input;
+   size_t remaining = len_in;
+   ssize_t result = 0;
+   (void)result;
+ 
+   /* Parse u8 ls_type */
+   CHECK_REMAINING(1, truncated);
+   obj->ls_type = (trunnel_get_uint8(ptr));
+   remaining -= 1; ptr += 1;
+ 
+   /* Parse u8 ls_len */
+   CHECK_REMAINING(1, truncated);
+   obj->ls_len = (trunnel_get_uint8(ptr));
+   remaining -= 1; ptr += 1;
+   {
+     size_t remaining_after;
+     CHECK_REMAINING(obj->ls_len, truncated);
+     remaining_after = remaining - obj->ls_len;
+     remaining = obj->ls_len;
+ 
+     /* Parse union un[ls_type] */
+     switch (obj->ls_type) {
+ 
+       case LS_IPV4:
+ 
+         /* Parse u32 un_ipv4_addr */
+         CHECK_REMAINING(4, fail);
+         obj->un_ipv4_addr = trunnel_ntohl(trunnel_get_uint32(ptr));
+         remaining -= 4; ptr += 4;
+ 
+         /* Parse u16 un_ipv4_port */
+         CHECK_REMAINING(2, fail);
+         obj->un_ipv4_port = trunnel_ntohs(trunnel_get_uint16(ptr));
+         remaining -= 2; ptr += 2;
+         break;
+ 
+       case LS_IPV6:
+ 
+         /* Parse u8 un_ipv6_addr[16] */
+         CHECK_REMAINING(16, fail);
+         memcpy(obj->un_ipv6_addr, ptr, 16);
+         remaining -= 16; ptr += 16;
+ 
+         /* Parse u16 un_ipv6_port */
+         CHECK_REMAINING(2, fail);
+         obj->un_ipv6_port = trunnel_ntohs(trunnel_get_uint16(ptr));
+         remaining -= 2; ptr += 2;
+         break;
+ 
+       case LS_LEGACY_ID:
+ 
+         /* Parse u8 un_legacy_id[20] */
+         CHECK_REMAINING(20, fail);
+         memcpy(obj->un_legacy_id, ptr, 20);
+         remaining -= 20; ptr += 20;
+         break;
+ 
+       case LS_ED25519_ID:
+ 
+         /* Parse u8 un_ed25519_id[32] */
+         CHECK_REMAINING(32, fail);
+         memcpy(obj->un_ed25519_id, ptr, 32);
+         remaining -= 32; ptr += 32;
+         break;
+ 
+       default:
+ 
+         /* Parse u8 un_unrecognized[] */
+         TRUNNEL_DYNARRAY_EXPAND(uint8_t, &obj->un_unrecognized, remaining, {});
+         obj->un_unrecognized.n_ = remaining;
+         if (remaining)
+           memcpy(obj->un_unrecognized.elts_, ptr, remaining);
+         ptr += remaining; remaining -= remaining;
+         break;
+     }
+     if (remaining != 0)
+       goto fail;
+     remaining = remaining_after;
+   }
+   trunnel_assert(ptr + remaining == input + len_in);
+   return len_in - remaining;
+ 
+  truncated:
+   return -2;
+  trunnel_alloc_failed:
+   return -1;
+  fail:
+   result = -1;
+   return result;
+ }
+ 
+ ssize_t
+ link_specifier_parse(link_specifier_t **output, const uint8_t *input, const size_t len_in)
+ {
+   ssize_t result;
+   *output = link_specifier_new();
+   if (NULL == *output)
+     return -1;
+   result = link_specifier_parse_into(*output, input, len_in);
+   if (result < 0) {
+     link_specifier_free(*output);
+     *output = NULL;
+   }
+   return result;
+ }
  ed25519_cert_t *
  ed25519_cert_new(void)
  {
@@@ -937,3 -1438,283 +1528,293 @@@ ed25519_cert_parse(ed25519_cert_t **out
    }
    return result;
  }
+ link_specifier_list_t *
+ link_specifier_list_new(void)
+ {
+   link_specifier_list_t *val = trunnel_calloc(1, sizeof(link_specifier_list_t));
+   if (NULL == val)
+     return NULL;
+   return val;
+ }
+ 
+ /** Release all storage held inside 'obj', but do not free 'obj'.
+  */
+ static void
+ link_specifier_list_clear(link_specifier_list_t *obj)
+ {
+   (void) obj;
+   {
+ 
+     unsigned idx;
+     for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+       link_specifier_free(TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+     }
+   }
+   TRUNNEL_DYNARRAY_WIPE(&obj->spec);
+   TRUNNEL_DYNARRAY_CLEAR(&obj->spec);
+ }
+ 
+ void
+ link_specifier_list_free(link_specifier_list_t *obj)
+ {
+   if (obj == NULL)
+     return;
+   link_specifier_list_clear(obj);
+   trunnel_memwipe(obj, sizeof(link_specifier_list_t));
+   trunnel_free_(obj);
+ }
+ 
+ uint8_t
+ link_specifier_list_get_n_spec(link_specifier_list_t *inp)
+ {
+   return inp->n_spec;
+ }
+ int
+ link_specifier_list_set_n_spec(link_specifier_list_t *inp, uint8_t val)
+ {
+   inp->n_spec = val;
+   return 0;
+ }
+ size_t
+ link_specifier_list_getlen_spec(const link_specifier_list_t *inp)
+ {
+   return TRUNNEL_DYNARRAY_LEN(&inp->spec);
+ }
+ 
+ struct link_specifier_st *
+ link_specifier_list_get_spec(link_specifier_list_t *inp, size_t idx)
+ {
+   return TRUNNEL_DYNARRAY_GET(&inp->spec, idx);
+ }
+ 
++ const struct link_specifier_st *
++link_specifier_list_getconst_spec(const link_specifier_list_t *inp, size_t idx)
++{
++  return link_specifier_list_get_spec((link_specifier_list_t*)inp, idx);
++}
+ int
+ link_specifier_list_set_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt)
+ {
+   link_specifier_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->spec, idx);
+   if (oldval && oldval != elt)
+     link_specifier_free(oldval);
+   return link_specifier_list_set0_spec(inp, idx, elt);
+ }
+ int
+ link_specifier_list_set0_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt)
+ {
+   TRUNNEL_DYNARRAY_SET(&inp->spec, idx, elt);
+   return 0;
+ }
+ int
+ link_specifier_list_add_spec(link_specifier_list_t *inp, struct link_specifier_st * elt)
+ {
+ #if SIZE_MAX >= UINT8_MAX
+   if (inp->spec.n_ == UINT8_MAX)
+     goto trunnel_alloc_failed;
+ #endif
+   TRUNNEL_DYNARRAY_ADD(struct link_specifier_st *, &inp->spec, elt, {});
+   return 0;
+  trunnel_alloc_failed:
+   TRUNNEL_SET_ERROR_CODE(inp);
+   return -1;
+ }
+ 
+ struct link_specifier_st * *
+ link_specifier_list_getarray_spec(link_specifier_list_t *inp)
+ {
+   return inp->spec.elts_;
+ }
++const struct link_specifier_st *  const  *
++link_specifier_list_getconstarray_spec(const link_specifier_list_t *inp)
++{
++  return (const struct link_specifier_st *  const  *)link_specifier_list_getarray_spec((link_specifier_list_t*)inp);
++}
+ int
+ link_specifier_list_setlen_spec(link_specifier_list_t *inp, size_t newlen)
+ {
+   struct link_specifier_st * *newptr;
+ #if UINT8_MAX < SIZE_MAX
+   if (newlen > UINT8_MAX)
+     goto trunnel_alloc_failed;
+ #endif
+   newptr = trunnel_dynarray_setlen(&inp->spec.allocated_,
+                  &inp->spec.n_, inp->spec.elts_, newlen,
+                  sizeof(inp->spec.elts_[0]), (trunnel_free_fn_t) link_specifier_free,
+                  &inp->trunnel_error_code_);
 -  if (newptr == NULL)
++  if (newlen != 0 && newptr == NULL)
+     goto trunnel_alloc_failed;
+   inp->spec.elts_ = newptr;
+   return 0;
+  trunnel_alloc_failed:
+   TRUNNEL_SET_ERROR_CODE(inp);
+   return -1;
+ }
+ const char *
+ link_specifier_list_check(const link_specifier_list_t *obj)
+ {
+   if (obj == NULL)
+     return "Object was NULL";
+   if (obj->trunnel_error_code_)
+     return "A set function failed on this object";
+   {
+     const char *msg;
+ 
+     unsigned idx;
+     for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+       if (NULL != (msg = link_specifier_check(TRUNNEL_DYNARRAY_GET(&obj->spec, idx))))
+         return msg;
+     }
+   }
+   if (TRUNNEL_DYNARRAY_LEN(&obj->spec) != obj->n_spec)
+     return "Length mismatch for spec";
+   return NULL;
+ }
+ 
+ ssize_t
+ link_specifier_list_encoded_len(const link_specifier_list_t *obj)
+ {
+   ssize_t result = 0;
+ 
+   if (NULL != link_specifier_list_check(obj))
+      return -1;
+ 
+ 
+   /* Length of u8 n_spec */
+   result += 1;
+ 
+   /* Length of struct link_specifier spec[n_spec] */
+   {
+ 
+     unsigned idx;
+     for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+       result += link_specifier_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+     }
+   }
+   return result;
+ }
+ int
+ link_specifier_list_clear_errors(link_specifier_list_t *obj)
+ {
+   int r = obj->trunnel_error_code_;
+   obj->trunnel_error_code_ = 0;
+   return r;
+ }
+ ssize_t
+ link_specifier_list_encode(uint8_t *output, const size_t avail, const link_specifier_list_t *obj)
+ {
+   ssize_t result = 0;
+   size_t written = 0;
+   uint8_t *ptr = output;
+   const char *msg;
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   const ssize_t encoded_len = link_specifier_list_encoded_len(obj);
+ #endif
+ 
+   if (NULL != (msg = link_specifier_list_check(obj)))
+     goto check_failed;
+ 
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   trunnel_assert(encoded_len >= 0);
+ #endif
+ 
+   /* Encode u8 n_spec */
+   trunnel_assert(written <= avail);
+   if (avail - written < 1)
+     goto truncated;
+   trunnel_set_uint8(ptr, (obj->n_spec));
+   written += 1; ptr += 1;
+ 
+   /* Encode struct link_specifier spec[n_spec] */
+   {
+ 
+     unsigned idx;
+     for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->spec); ++idx) {
+       trunnel_assert(written <= avail);
+       result = link_specifier_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->spec, idx));
+       if (result < 0)
+         goto fail; /* XXXXXXX !*/
+       written += result; ptr += result;
+     }
+   }
+ 
+ 
+   trunnel_assert(ptr == output + written);
+ #ifdef TRUNNEL_CHECK_ENCODED_LEN
+   {
+     trunnel_assert(encoded_len >= 0);
+     trunnel_assert((size_t)encoded_len == written);
+   }
+ 
+ #endif
+ 
+   return written;
+ 
+  truncated:
+   result = -2;
+   goto fail;
+  check_failed:
+   (void)msg;
+   result = -1;
+   goto fail;
+  fail:
+   trunnel_assert(result < 0);
+   return result;
+ }
+ 
+ /** As link_specifier_list_parse(), but do not allocate the output
+  * object.
+  */
+ static ssize_t
+ link_specifier_list_parse_into(link_specifier_list_t *obj, const uint8_t *input, const size_t len_in)
+ {
+   const uint8_t *ptr = input;
+   size_t remaining = len_in;
+   ssize_t result = 0;
+   (void)result;
+ 
+   /* Parse u8 n_spec */
+   CHECK_REMAINING(1, truncated);
+   obj->n_spec = (trunnel_get_uint8(ptr));
+   remaining -= 1; ptr += 1;
+ 
+   /* Parse struct link_specifier spec[n_spec] */
+   TRUNNEL_DYNARRAY_EXPAND(link_specifier_t *, &obj->spec, obj->n_spec, {});
+   {
+     link_specifier_t * elt;
+     unsigned idx;
+     for (idx = 0; idx < obj->n_spec; ++idx) {
+       result = link_specifier_parse(&elt, ptr, remaining);
+       if (result < 0)
+         goto relay_fail;
+       trunnel_assert((size_t)result <= remaining);
+       remaining -= result; ptr += result;
+       TRUNNEL_DYNARRAY_ADD(link_specifier_t *, &obj->spec, elt, {link_specifier_free(elt);});
+     }
+   }
+   trunnel_assert(ptr + remaining == input + len_in);
+   return len_in - remaining;
+ 
+  truncated:
+   return -2;
+  relay_fail:
+   trunnel_assert(result < 0);
+   return result;
+  trunnel_alloc_failed:
+   return -1;
+ }
+ 
+ ssize_t
+ link_specifier_list_parse(link_specifier_list_t **output, const uint8_t *input, const size_t len_in)
+ {
+   ssize_t result;
+   *output = link_specifier_list_new();
+   if (NULL == *output)
+     return -1;
+   result = link_specifier_list_parse_into(*output, input, len_in);
+   if (result < 0) {
+     link_specifier_list_free(*output);
+     *output = NULL;
+   }
+   return result;
+ }
diff --cc src/trunnel/ed25519_cert.h
index 9804d84,1893957..571e6d1
--- a/src/trunnel/ed25519_cert.h
+++ b/src/trunnel/ed25519_cert.h
@@@ -157,6 -168,164 +184,196 @@@ const uint8_t  * ed25519_cert_extension
   * success; return -1 and set the error code on 'inp' on failure.
   */
  int ed25519_cert_extension_setlen_un_unparsed(ed25519_cert_extension_t *inp, size_t newlen);
+ /** Return a newly allocated link_specifier with all elements set to
+  * zero.
+  */
+ link_specifier_t *link_specifier_new(void);
+ /** Release all storage held by the link_specifier in 'victim'. (Do
+  * nothing if 'victim' is NULL.)
+  */
+ void link_specifier_free(link_specifier_t *victim);
+ /** Try to parse a link_specifier from the buffer in 'input', using up
+  * to 'len_in' bytes from the input buffer. On success, return the
+  * number of bytes consumed and set *output to the newly allocated
+  * link_specifier_t. On failure, return -2 if the input appears
+  * truncated, and -1 if the input is otherwise invalid.
+  */
+ ssize_t link_specifier_parse(link_specifier_t **output, const uint8_t *input, const size_t len_in);
+ /** Return the number of bytes we expect to need to encode the
+  * link_specifier in 'obj'. On failure, return a negative value. Note
+  * that this value may be an overestimate, and can even be an
+  * underestimate for certain unencodeable objects.
+  */
+ ssize_t link_specifier_encoded_len(const link_specifier_t *obj);
+ /** Try to encode the link_specifier from 'input' into the buffer at
+  * 'output', using up to 'avail' bytes of the output buffer. On
+  * success, return the number of bytes used. On failure, return -2 if
+  * the buffer was not long enough, and -1 if the input was invalid.
+  */
+ ssize_t link_specifier_encode(uint8_t *output, size_t avail, const link_specifier_t *input);
+ /** Check whether the internal state of the link_specifier in 'obj' is
+  * consistent. Return NULL if it is, and a short message if it is not.
+  */
+ const char *link_specifier_check(const link_specifier_t *obj);
+ /** Clear any errors that were set on the object 'obj' by its setter
+  * functions. Return true iff errors were cleared.
+  */
+ int link_specifier_clear_errors(link_specifier_t *obj);
+ /** Return the value of the ls_type field of the link_specifier_t in
+  * 'inp'
+  */
+ uint8_t link_specifier_get_ls_type(link_specifier_t *inp);
+ /** Set the value of the ls_type field of the link_specifier_t in
+  * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+  * code on 'inp' on failure.
+  */
+ int link_specifier_set_ls_type(link_specifier_t *inp, uint8_t val);
+ /** Return the value of the ls_len field of the link_specifier_t in
+  * 'inp'
+  */
+ uint8_t link_specifier_get_ls_len(link_specifier_t *inp);
+ /** Set the value of the ls_len field of the link_specifier_t in 'inp'
+  * to 'val'. Return 0 on success; return -1 and set the error code on
+  * 'inp' on failure.
+  */
+ int link_specifier_set_ls_len(link_specifier_t *inp, uint8_t val);
+ /** Return the value of the un_ipv4_addr field of the link_specifier_t
+  * in 'inp'
+  */
+ uint32_t link_specifier_get_un_ipv4_addr(link_specifier_t *inp);
+ /** Set the value of the un_ipv4_addr field of the link_specifier_t in
+  * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+  * code on 'inp' on failure.
+  */
+ int link_specifier_set_un_ipv4_addr(link_specifier_t *inp, uint32_t val);
+ /** Return the value of the un_ipv4_port field of the link_specifier_t
+  * in 'inp'
+  */
+ uint16_t link_specifier_get_un_ipv4_port(link_specifier_t *inp);
+ /** Set the value of the un_ipv4_port field of the link_specifier_t in
+  * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+  * code on 'inp' on failure.
+  */
+ int link_specifier_set_un_ipv4_port(link_specifier_t *inp, uint16_t val);
+ /** Return the (constant) length of the array holding the un_ipv6_addr
+  * field of the link_specifier_t in 'inp'.
+  */
+ size_t link_specifier_getlen_un_ipv6_addr(const link_specifier_t *inp);
+ /** Return the element at position 'idx' of the fixed array field
+  * un_ipv6_addr of the link_specifier_t in 'inp'.
+  */
 -uint8_t link_specifier_get_un_ipv6_addr(const link_specifier_t *inp, size_t idx);
++uint8_t link_specifier_get_un_ipv6_addr(link_specifier_t *inp, size_t idx);
++/** As link_specifier_get_un_ipv6_addr, but take and return a const
++ * pointer
++ */
++uint8_t link_specifier_getconst_un_ipv6_addr(const link_specifier_t *inp, size_t idx);
+ /** Change the element at position 'idx' of the fixed array field
+  * un_ipv6_addr of the link_specifier_t in 'inp', so that it will hold
+  * the value 'elt'.
+  */
+ int link_specifier_set_un_ipv6_addr(link_specifier_t *inp, size_t idx, uint8_t elt);
+ /** Return a pointer to the 16-element array field un_ipv6_addr of
+  * 'inp'.
+  */
+ uint8_t * link_specifier_getarray_un_ipv6_addr(link_specifier_t *inp);
++/** As link_specifier_get_un_ipv6_addr, but take and return a const
++ * pointer
++ */
++const uint8_t  * link_specifier_getconstarray_un_ipv6_addr(const link_specifier_t *inp);
+ /** Return the value of the un_ipv6_port field of the link_specifier_t
+  * in 'inp'
+  */
+ uint16_t link_specifier_get_un_ipv6_port(link_specifier_t *inp);
+ /** Set the value of the un_ipv6_port field of the link_specifier_t in
+  * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+  * code on 'inp' on failure.
+  */
+ int link_specifier_set_un_ipv6_port(link_specifier_t *inp, uint16_t val);
+ /** Return the (constant) length of the array holding the un_legacy_id
+  * field of the link_specifier_t in 'inp'.
+  */
+ size_t link_specifier_getlen_un_legacy_id(const link_specifier_t *inp);
+ /** Return the element at position 'idx' of the fixed array field
+  * un_legacy_id of the link_specifier_t in 'inp'.
+  */
 -uint8_t link_specifier_get_un_legacy_id(const link_specifier_t *inp, size_t idx);
++uint8_t link_specifier_get_un_legacy_id(link_specifier_t *inp, size_t idx);
++/** As link_specifier_get_un_legacy_id, but take and return a const
++ * pointer
++ */
++uint8_t link_specifier_getconst_un_legacy_id(const link_specifier_t *inp, size_t idx);
+ /** Change the element at position 'idx' of the fixed array field
+  * un_legacy_id of the link_specifier_t in 'inp', so that it will hold
+  * the value 'elt'.
+  */
+ int link_specifier_set_un_legacy_id(link_specifier_t *inp, size_t idx, uint8_t elt);
+ /** Return a pointer to the 20-element array field un_legacy_id of
+  * 'inp'.
+  */
+ uint8_t * link_specifier_getarray_un_legacy_id(link_specifier_t *inp);
++/** As link_specifier_get_un_legacy_id, but take and return a const
++ * pointer
++ */
++const uint8_t  * link_specifier_getconstarray_un_legacy_id(const link_specifier_t *inp);
+ /** Return the (constant) length of the array holding the
+  * un_ed25519_id field of the link_specifier_t in 'inp'.
+  */
+ size_t link_specifier_getlen_un_ed25519_id(const link_specifier_t *inp);
+ /** Return the element at position 'idx' of the fixed array field
+  * un_ed25519_id of the link_specifier_t in 'inp'.
+  */
 -uint8_t link_specifier_get_un_ed25519_id(const link_specifier_t *inp, size_t idx);
++uint8_t link_specifier_get_un_ed25519_id(link_specifier_t *inp, size_t idx);
++/** As link_specifier_get_un_ed25519_id, but take and return a const
++ * pointer
++ */
++uint8_t link_specifier_getconst_un_ed25519_id(const link_specifier_t *inp, size_t idx);
+ /** Change the element at position 'idx' of the fixed array field
+  * un_ed25519_id of the link_specifier_t in 'inp', so that it will
+  * hold the value 'elt'.
+  */
+ int link_specifier_set_un_ed25519_id(link_specifier_t *inp, size_t idx, uint8_t elt);
+ /** Return a pointer to the 32-element array field un_ed25519_id of
+  * 'inp'.
+  */
+ uint8_t * link_specifier_getarray_un_ed25519_id(link_specifier_t *inp);
++/** As link_specifier_get_un_ed25519_id, but take and return a const
++ * pointer
++ */
++const uint8_t  * link_specifier_getconstarray_un_ed25519_id(const link_specifier_t *inp);
+ /** Return the length of the dynamic array holding the un_unrecognized
+  * field of the link_specifier_t in 'inp'.
+  */
+ size_t link_specifier_getlen_un_unrecognized(const link_specifier_t *inp);
+ /** Return the element at position 'idx' of the dynamic array field
+  * un_unrecognized of the link_specifier_t in 'inp'.
+  */
+ uint8_t link_specifier_get_un_unrecognized(link_specifier_t *inp, size_t idx);
++/** As link_specifier_get_un_unrecognized, but take and return a const
++ * pointer
++ */
++uint8_t link_specifier_getconst_un_unrecognized(const link_specifier_t *inp, size_t idx);
+ /** Change the element at position 'idx' of the dynamic array field
+  * un_unrecognized of the link_specifier_t in 'inp', so that it will
+  * hold the value 'elt'.
+  */
+ int link_specifier_set_un_unrecognized(link_specifier_t *inp, size_t idx, uint8_t elt);
+ /** Append a new element 'elt' to the dynamic array field
+  * un_unrecognized of the link_specifier_t in 'inp'.
+  */
+ int link_specifier_add_un_unrecognized(link_specifier_t *inp, uint8_t elt);
+ /** Return a pointer to the variable-length array field
+  * un_unrecognized of 'inp'.
+  */
+ uint8_t * link_specifier_getarray_un_unrecognized(link_specifier_t *inp);
++/** As link_specifier_get_un_unrecognized, but take and return a const
++ * pointer
++ */
++const uint8_t  * link_specifier_getconstarray_un_unrecognized(const link_specifier_t *inp);
+ /** Change the length of the variable-length array field
+  * un_unrecognized of 'inp' to 'newlen'.Fill extra elements with 0.
+  * Return 0 on success; return -1 and set the error code on 'inp' on
+  * failure.
+  */
+ int link_specifier_setlen_un_unrecognized(link_specifier_t *inp, size_t newlen);
  /** Return a newly allocated ed25519_cert with all elements set to
   * zero.
   */
@@@ -316,9 -468,81 +533,92 @@@ int ed25519_cert_set_signature(ed25519_
  /** Return a pointer to the 64-element array field signature of 'inp'.
   */
  uint8_t * ed25519_cert_getarray_signature(ed25519_cert_t *inp);
 +/** As ed25519_cert_get_signature, but take and return a const pointer
 + */
 +const uint8_t  * ed25519_cert_getconstarray_signature(const ed25519_cert_t *inp);
+ /** Return a newly allocated link_specifier_list with all elements set
+  * to zero.
+  */
+ link_specifier_list_t *link_specifier_list_new(void);
+ /** Release all storage held by the link_specifier_list in 'victim'.
+  * (Do nothing if 'victim' is NULL.)
+  */
+ void link_specifier_list_free(link_specifier_list_t *victim);
+ /** Try to parse a link_specifier_list from the buffer in 'input',
+  * using up to 'len_in' bytes from the input buffer. On success,
+  * return the number of bytes consumed and set *output to the newly
+  * allocated link_specifier_list_t. On failure, return -2 if the input
+  * appears truncated, and -1 if the input is otherwise invalid.
+  */
+ ssize_t link_specifier_list_parse(link_specifier_list_t **output, const uint8_t *input, const size_t len_in);
+ /** Return the number of bytes we expect to need to encode the
+  * link_specifier_list in 'obj'. On failure, return a negative value.
+  * Note that this value may be an overestimate, and can even be an
+  * underestimate for certain unencodeable objects.
+  */
+ ssize_t link_specifier_list_encoded_len(const link_specifier_list_t *obj);
+ /** Try to encode the link_specifier_list from 'input' into the buffer
+  * at 'output', using up to 'avail' bytes of the output buffer. On
+  * success, return the number of bytes used. On failure, return -2 if
+  * the buffer was not long enough, and -1 if the input was invalid.
+  */
+ ssize_t link_specifier_list_encode(uint8_t *output, size_t avail, const link_specifier_list_t *input);
+ /** Check whether the internal state of the link_specifier_list in
+  * 'obj' is consistent. Return NULL if it is, and a short message if
+  * it is not.
+  */
+ const char *link_specifier_list_check(const link_specifier_list_t *obj);
+ /** Clear any errors that were set on the object 'obj' by its setter
+  * functions. Return true iff errors were cleared.
+  */
+ int link_specifier_list_clear_errors(link_specifier_list_t *obj);
+ /** Return the value of the n_spec field of the link_specifier_list_t
+  * in 'inp'
+  */
+ uint8_t link_specifier_list_get_n_spec(link_specifier_list_t *inp);
+ /** Set the value of the n_spec field of the link_specifier_list_t in
+  * 'inp' to 'val'. Return 0 on success; return -1 and set the error
+  * code on 'inp' on failure.
+  */
+ int link_specifier_list_set_n_spec(link_specifier_list_t *inp, uint8_t val);
+ /** Return the length of the dynamic array holding the spec field of
+  * the link_specifier_list_t in 'inp'.
+  */
+ size_t link_specifier_list_getlen_spec(const link_specifier_list_t *inp);
+ /** Return the element at position 'idx' of the dynamic array field
+  * spec of the link_specifier_list_t in 'inp'.
+  */
+ struct link_specifier_st * link_specifier_list_get_spec(link_specifier_list_t *inp, size_t idx);
++/** As link_specifier_list_get_spec, but take and return a const
++ * pointer
++ */
++ const struct link_specifier_st * link_specifier_list_getconst_spec(const link_specifier_list_t *inp, size_t idx);
+ /** Change the element at position 'idx' of the dynamic array field
+  * spec of the link_specifier_list_t in 'inp', so that it will hold
+  * the value 'elt'. Free the previous value, if any.
+  */
+ int link_specifier_list_set_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt);
+ /** As link_specifier_list_set_spec, but does not free the previous
+  * value.
+  */
+ int link_specifier_list_set0_spec(link_specifier_list_t *inp, size_t idx, struct link_specifier_st * elt);
+ /** Append a new element 'elt' to the dynamic array field spec of the
+  * link_specifier_list_t in 'inp'.
+  */
+ int link_specifier_list_add_spec(link_specifier_list_t *inp, struct link_specifier_st * elt);
+ /** Return a pointer to the variable-length array field spec of 'inp'.
+  */
+ struct link_specifier_st * * link_specifier_list_getarray_spec(link_specifier_list_t *inp);
++/** As link_specifier_list_get_spec, but take and return a const
++ * pointer
++ */
++const struct link_specifier_st *  const  * link_specifier_list_getconstarray_spec(const link_specifier_list_t *inp);
+ /** Change the length of the variable-length array field spec of 'inp'
+  * to 'newlen'.Fill extra elements with NULL; free removed elements.
+  * Return 0 on success; return -1 and set the error code on 'inp' on
+  * failure.
+  */
+ int link_specifier_list_setlen_spec(link_specifier_list_t *inp, size_t newlen);
  
  
  #endif





More information about the tor-commits mailing list