Browse Source

Merge branch 'protover-rust-impl_squashed'

Nick Mathewson 6 years ago
parent
commit
f5e9e2748f

+ 1 - 1
Makefile.am

@@ -26,7 +26,7 @@ TESTING_TOR_BINARY=$(top_builddir)/src/or/tor$(EXEEXT)
 endif
 endif
 
 
 if USE_RUST
 if USE_RUST
-rust_ldadd=$(top_builddir)/src/rust/target/release/@TOR_RUST_UTIL_STATIC_NAME@
+rust_ldadd=$(top_builddir)/src/rust/target/release/@TOR_RUST_STATIC_NAME@
 else
 else
 rust_ldadd=
 rust_ldadd=
 endif
 endif

+ 8 - 0
changes/ticket22840

@@ -0,0 +1,8 @@
+  o Major features (Rust experimentation):
+    - Tor now ships with an optional implementation of one of its smaller
+      modules (protover.c) in the Rust programming language.  To try it
+      out, install a Rust build environment, and configure Tor with
+      "--enable-rust --enable-cargo-online-mode".  This should not
+      cause any user-visible changes, but should help us gain more experience
+      with Rust, and plan future Rust integration work.
+      Implementation by Chelsea Komlo. Closes ticket 22840.

+ 3 - 3
configure.ac

@@ -439,12 +439,12 @@ if test "x$enable_rust" = "xyes"; then
   dnl For now both MSVC and MinGW rust libraries will output static libs with
   dnl For now both MSVC and MinGW rust libraries will output static libs with
   dnl the MSVC naming convention.
   dnl the MSVC naming convention.
   if test "$bwin32" = "true"; then
   if test "$bwin32" = "true"; then
-    TOR_RUST_UTIL_STATIC_NAME=tor_util.lib
+    TOR_RUST_STATIC_NAME=tor_rust.lib
   else
   else
-    TOR_RUST_UTIL_STATIC_NAME=libtor_util.a
+    TOR_RUST_STATIC_NAME=libtor_rust.a
   fi
   fi
 
 
-  AC_SUBST(TOR_RUST_UTIL_STATIC_NAME)
+  AC_SUBST(TOR_RUST_STATIC_NAME)
   AC_SUBST(CARGO_ONLINE)
   AC_SUBST(CARGO_ONLINE)
   AC_SUBST(RUST_DL)
   AC_SUBST(RUST_DL)
 
 

+ 0 - 39
src/common/compat_rust.c

@@ -1,39 +0,0 @@
-/* Copyright (c) 2017, The Tor Project, Inc. */
-/* See LICENSE for licensing information */
-
-/**
- * \file rust_compat.c
- * \brief Rust FFI compatibility functions and helpers. This file is only built
- * if Rust is not used.
- **/
-
-#include "compat_rust.h"
-#include "util.h"
-
-/**
- * Free storage pointed to by <b>str</b>, and itself.
- */
-void
-rust_str_free(rust_str_t str)
-{
-    char *s = (char *)str;
-    tor_free(s);
-}
-
-/**
- * Return zero-terminated contained string.
- */
-const char *
-rust_str_get(const rust_str_t str)
-{
-    return (const char *)str;
-}
-
-/* If we were using Rust, we'd say so on startup. */
-rust_str_t
-rust_welcome_string(void)
-{
-    char *s = tor_malloc_zero(1);
-    return (rust_str_t)s;
-}
-

+ 0 - 28
src/common/compat_rust.h

@@ -1,28 +0,0 @@
-/* Copyright (c) 2017, The Tor Project, Inc. */
-/* See LICENSE for licensing information */
-
-/**
- * \file rust_compat.h
- * \brief Headers for rust_compat.c
- **/
-
-#ifndef TOR_RUST_COMPAT_H
-#define TOR_RUST_COMPAT_H
-
-#include "torint.h"
-
-/**
- * Strings allocated in Rust must be freed from Rust code again. Let's make
- * it less likely to accidentally mess up and call tor_free() on it, because
- * currently it'll just work but might break at any time.
- */
-typedef uintptr_t rust_str_t;
-
-void rust_str_free(rust_str_t);
-
-const char *rust_str_get(const rust_str_t);
-
-rust_str_t rust_welcome_string(void);
-
-#endif /* !defined(TOR_RUST_COMPAT_H) */
-

+ 0 - 6
src/common/include.am

@@ -101,11 +101,6 @@ LIBOR_A_SRC = \
   $(threads_impl_source)				\
   $(threads_impl_source)				\
   $(readpassphrase_source)
   $(readpassphrase_source)
 
 
-if USE_RUST
-else
-LIBOR_A_SRC += src/common/compat_rust.c
-endif
-
 src/common/src_common_libor_testing_a-log.$(OBJEXT) \
 src/common/src_common_libor_testing_a-log.$(OBJEXT) \
   src/common/log.$(OBJEXT): micro-revision.i
   src/common/log.$(OBJEXT): micro-revision.i
 
 
@@ -156,7 +151,6 @@ COMMONHEADERS = \
   src/common/compat.h				\
   src/common/compat.h				\
   src/common/compat_libevent.h			\
   src/common/compat_libevent.h			\
   src/common/compat_openssl.h			\
   src/common/compat_openssl.h			\
-  src/common/compat_rust.h			\
   src/common/compat_threads.h			\
   src/common/compat_threads.h			\
   src/common/compat_time.h			\
   src/common/compat_time.h			\
   src/common/compress.h				\
   src/common/compress.h				\

+ 1 - 0
src/or/include.am

@@ -78,6 +78,7 @@ LIBTOR_A_SOURCES = \
 	src/or/parsecommon.c			\
 	src/or/parsecommon.c			\
 	src/or/periodic.c				\
 	src/or/periodic.c				\
 	src/or/protover.c				\
 	src/or/protover.c				\
+	src/or/protover_rust.c				\
 	src/or/proto_cell.c				\
 	src/or/proto_cell.c				\
 	src/or/proto_control0.c				\
 	src/or/proto_control0.c				\
 	src/or/proto_ext_or.c				\
 	src/or/proto_ext_or.c				\

+ 10 - 8
src/or/main.c

@@ -60,7 +60,6 @@
 #include "circuitlist.h"
 #include "circuitlist.h"
 #include "circuituse.h"
 #include "circuituse.h"
 #include "command.h"
 #include "command.h"
-#include "compat_rust.h"
 #include "compress.h"
 #include "compress.h"
 #include "config.h"
 #include "config.h"
 #include "confparse.h"
 #include "confparse.h"
@@ -128,6 +127,10 @@
 
 
 void evdns_shutdown(int);
 void evdns_shutdown(int);
 
 
+// helper function defined in Rust to output a log message indicating if tor is
+// running with Rust enabled. See src/rust/tor_util
+char *rust_welcome_string(void);
+
 /********* PROTOTYPES **********/
 /********* PROTOTYPES **********/
 
 
 static void dumpmemusage(int severity);
 static void dumpmemusage(int severity);
@@ -3111,14 +3114,13 @@ tor_init(int argc, char *argv[])
                  "Expect more bugs than usual.");
                  "Expect more bugs than usual.");
   }
   }
 
 
-  {
-    rust_str_t rust_str = rust_welcome_string();
-    const char *s = rust_str_get(rust_str);
-    if (strlen(s) > 0) {
-      log_notice(LD_GENERAL, "%s", s);
-    }
-    rust_str_free(rust_str);
+#ifdef HAVE_RUST
+  char *rust_str = rust_welcome_string();
+  if (rust_str != NULL && strlen(rust_str) > 0) {
+    log_notice(LD_GENERAL, "%s", rust_str);
   }
   }
+  tor_free(rust_str);
+#endif
 
 
   if (network_init()<0) {
   if (network_init()<0) {
     log_err(LD_BUG,"Error initializing network; exiting.");
     log_err(LD_BUG,"Error initializing network; exiting.");

+ 4 - 0
src/or/protover.c

@@ -27,6 +27,8 @@
 #include "protover.h"
 #include "protover.h"
 #include "routerparse.h"
 #include "routerparse.h"
 
 
+#ifndef HAVE_RUST
+
 static const smartlist_t *get_supported_protocol_list(void);
 static const smartlist_t *get_supported_protocol_list(void);
 static int protocol_list_contains(const smartlist_t *protos,
 static int protocol_list_contains(const smartlist_t *protos,
                                   protocol_type_t pr, uint32_t ver);
                                   protocol_type_t pr, uint32_t ver);
@@ -735,3 +737,5 @@ protover_free_all(void)
   }
   }
 }
 }
 
 
+#endif
+

+ 5 - 1
src/or/protover.h

@@ -70,11 +70,15 @@ typedef struct proto_entry_t {
   smartlist_t *ranges;
   smartlist_t *ranges;
 } proto_entry_t;
 } proto_entry_t;
 
 
+#if !defined(HAVE_RUST) && defined(TOR_UNIT_TESTS)
 STATIC smartlist_t *parse_protocol_list(const char *s);
 STATIC smartlist_t *parse_protocol_list(const char *s);
-STATIC void proto_entry_free(proto_entry_t *entry);
 STATIC char *encode_protocol_list(const smartlist_t *sl);
 STATIC char *encode_protocol_list(const smartlist_t *sl);
 STATIC const char *protocol_type_to_str(protocol_type_t pr);
 STATIC const char *protocol_type_to_str(protocol_type_t pr);
 STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out);
 STATIC int str_to_protocol_type(const char *s, protocol_type_t *pr_out);
+STATIC void proto_entry_free(proto_entry_t *entry);
+
+#endif
+
 #endif /* defined(PROTOVER_PRIVATE) */
 #endif /* defined(PROTOVER_PRIVATE) */
 
 
 #endif /* !defined(TOR_PROTOVER_H) */
 #endif /* !defined(TOR_PROTOVER_H) */

+ 19 - 0
src/or/protover_rust.c

@@ -0,0 +1,19 @@
+/* Copyright (c) 2016-2017, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/*
+ * \file protover_rust.c
+ * \brief Provide a C wrapper for functions exposed in /src/rust/protover,
+ * and safe translation/handling between the Rust/C boundary.
+ */
+
+#include "or.h"
+#include "protover.h"
+
+#ifdef HAVE_RUST
+
+/* Define for compatibility, used in main.c */
+void protover_free_all(void) {};
+
+#endif
+

+ 41 - 0
src/rust/Cargo.lock

@@ -1,6 +1,14 @@
 [root]
 [root]
 name = "tor_util"
 name = "tor_util"
 version = "0.0.1"
 version = "0.0.1"
+dependencies = [
+ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tor_allocate 0.0.1",
+]
+
+[[package]]
+name = "external"
+version = "0.0.1"
 dependencies = [
 dependencies = [
  "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 ]
@@ -10,5 +18,38 @@ name = "libc"
 version = "0.2.22"
 version = "0.2.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 
+[[package]]
+name = "protover"
+version = "0.0.1"
+dependencies = [
+ "external 0.0.1",
+ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
+ "smartlist 0.0.1",
+ "tor_allocate 0.0.1",
+ "tor_util 0.0.1",
+]
+
+[[package]]
+name = "smartlist"
+version = "0.0.1"
+dependencies = [
+ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tor_allocate"
+version = "0.0.1"
+dependencies = [
+ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tor_rust"
+version = "0.1.0"
+dependencies = [
+ "protover 0.0.1",
+ "tor_util 0.0.1",
+]
+
 [metadata]
 [metadata]
 "checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"
 "checksum libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)" = "babb8281da88cba992fa1f4ddec7d63ed96280a1a53ec9b919fd37b53d71e502"

+ 1 - 1
src/rust/Cargo.toml

@@ -1,5 +1,5 @@
 [workspace]
 [workspace]
-members = ["tor_util"]
+members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", "tor_rust"]
 
 
 [profile.release]
 [profile.release]
 debug = true
 debug = true

+ 13 - 0
src/rust/external/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+authors = ["The Tor Project"]
+version = "0.0.1"
+name = "external"
+
+[dependencies]
+libc = "0.2.22"
+
+[lib]
+name = "external"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+

+ 33 - 0
src/rust/external/external.rs

@@ -0,0 +1,33 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+use libc::{c_char, c_int};
+use std::ffi::CString;
+
+extern "C" {
+    fn tor_version_as_new_as(
+        platform: *const c_char,
+        cutoff: *const c_char,
+    ) -> c_int;
+}
+
+/// Wrap calls to tor_version_as_new_as, defined in src/or/routerparse.c
+pub fn c_tor_version_as_new_as(platform: &str, cutoff: &str) -> bool {
+    // CHK: These functions should log a warning if an error occurs. This
+    // can be added when integration with tor's logger is added to rust
+    let c_platform = match CString::new(platform) {
+        Ok(n) => n,
+        Err(_) => return false,
+    };
+
+    let c_cutoff = match CString::new(cutoff) {
+        Ok(n) => n,
+        Err(_) => return false,
+    };
+
+    let result: c_int = unsafe {
+        tor_version_as_new_as(c_platform.as_ptr(), c_cutoff.as_ptr())
+    };
+
+    result == 1
+}

+ 14 - 0
src/rust/external/lib.rs

@@ -0,0 +1,14 @@
+//! Copyright (c) 2016-2017, The Tor Project, Inc. */
+//! See LICENSE for licensing information */
+
+//! Interface for external calls to tor C ABI
+//!
+//! The purpose of this module is to provide a clean interface for when Rust
+//! modules need to interact with functionality in tor C code rather than each
+//! module implementing this functionality repeatedly.
+
+extern crate libc;
+
+mod external;
+
+pub use external::*;

+ 1 - 1
src/rust/include.am

@@ -1,4 +1,4 @@
-include src/rust/tor_util/include.am
+include src/rust/tor_rust/include.am
 
 
 EXTRA_DIST +=\
 EXTRA_DIST +=\
 	src/rust/Cargo.toml \
 	src/rust/Cargo.toml \

+ 25 - 0
src/rust/protover/Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+authors = ["The Tor Project"]
+version = "0.0.1"
+name = "protover"
+
+[dependencies]
+libc = "0.2.22"
+
+[dependencies.smartlist]
+path = "../smartlist"
+
+[dependencies.external]
+path = "../external"
+
+[dependencies.tor_util]
+path = "../tor_util"
+
+[dependencies.tor_allocate]
+path = "../tor_allocate"
+
+[lib]
+name = "protover"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+

+ 185 - 0
src/rust/protover/ffi.rs

@@ -0,0 +1,185 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+//! FFI functions, only to be called from C.
+//!
+//! Equivalent C versions of this api are in `src/or/protover.c`
+
+use libc::{c_char, c_int, uint32_t};
+use std::ffi::CStr;
+use std::ffi::CString;
+
+use protover::*;
+use smartlist::*;
+use tor_allocate::allocate_and_copy_string;
+
+/// Translate C enums to Rust Proto enums, using the integer value of the C
+/// enum to map to its associated Rust enum
+/// This is dependant on the associated C enum preserving ordering.
+fn translate_to_rust(c_proto: uint32_t) -> Result<Proto, &'static str> {
+    match c_proto {
+        0 => Ok(Proto::Link),
+        1 => Ok(Proto::LinkAuth),
+        2 => Ok(Proto::Relay),
+        3 => Ok(Proto::DirCache),
+        4 => Ok(Proto::HSDir),
+        5 => Ok(Proto::HSIntro),
+        6 => Ok(Proto::HSRend),
+        7 => Ok(Proto::Desc),
+        8 => Ok(Proto::Microdesc),
+        9 => Ok(Proto::Cons),
+        _ => Err("Invalid protocol type"),
+    }
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::all_supported
+#[no_mangle]
+pub extern "C" fn protover_all_supported(
+    c_relay_version: *const c_char,
+    missing_out: *mut *mut c_char,
+) -> c_int {
+
+    if c_relay_version.is_null() {
+        return 1;
+    }
+
+    // Require an unsafe block to read the version from a C string. The pointer
+    // is checked above to ensure it is not null.
+    let c_str: &CStr = unsafe { CStr::from_ptr(c_relay_version) };
+
+    let relay_version = match c_str.to_str() {
+        Ok(n) => n,
+        Err(_) => return 1,
+    };
+
+    let (is_supported, unsupported) = all_supported(relay_version);
+
+    if unsupported.len() > 0 {
+        let c_unsupported = match CString::new(unsupported) {
+            Ok(n) => n,
+            Err(_) => return 1,
+        };
+
+        let ptr = c_unsupported.into_raw();
+        unsafe { *missing_out = ptr };
+    }
+
+    return if is_supported { 1 } else { 0 };
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::list_supports_protocol
+#[no_mangle]
+pub extern "C" fn protocol_list_supports_protocol(
+    c_protocol_list: *const c_char,
+    c_protocol: uint32_t,
+    version: uint32_t,
+) -> c_int {
+    if c_protocol_list.is_null() {
+        return 1;
+    }
+
+    // Require an unsafe block to read the version from a C string. The pointer
+    // is checked above to ensure it is not null.
+    let c_str: &CStr = unsafe { CStr::from_ptr(c_protocol_list) };
+
+    let protocol_list = match c_str.to_str() {
+        Ok(n) => n,
+        Err(_) => return 1,
+    };
+
+    let protocol = match translate_to_rust(c_protocol) {
+        Ok(n) => n,
+        Err(_) => return 0,
+    };
+
+    let is_supported =
+        protover_string_supports_protocol(protocol_list, protocol, version);
+
+    return if is_supported { 1 } else { 0 };
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::get_supported_protocols
+#[no_mangle]
+pub extern "C" fn protover_get_supported_protocols() -> *mut c_char {
+    // Not handling errors when unwrapping as the content is controlled
+    // and is an empty string
+    let empty = CString::new("").unwrap();
+
+    let supported = get_supported_protocols();
+    let c_supported = match CString::new(supported) {
+        Ok(n) => n,
+        Err(_) => return empty.into_raw(),
+    };
+
+    c_supported.into_raw()
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::compute_vote
+#[no_mangle]
+pub extern "C" fn protover_compute_vote(
+    list: *const Stringlist,
+    threshold: c_int,
+) -> *mut c_char {
+
+    if list.is_null() {
+        let empty = String::new();
+        return allocate_and_copy_string(&empty);
+    }
+
+    // Dereference of raw pointer requires an unsafe block. The pointer is
+    // checked above to ensure it is not null.
+    let data: Vec<String> = unsafe { (*list).get_list() };
+
+    let vote = compute_vote(data, threshold);
+
+    allocate_and_copy_string(&vote)
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::is_supported_here
+#[no_mangle]
+pub extern "C" fn protover_is_supported_here(
+    c_protocol: uint32_t,
+    version: uint32_t,
+) -> c_int {
+    let protocol = match translate_to_rust(c_protocol) {
+        Ok(n) => n,
+        Err(_) => return 0,
+    };
+
+    let is_supported = is_supported_here(protocol, version);
+
+    return if is_supported { 1 } else { 0 };
+}
+
+/// Provide an interface for C to translate arguments and return types for
+/// protover::compute_for_old_tor
+#[no_mangle]
+pub extern "C" fn protover_compute_for_old_tor(
+    version: *const c_char,
+) -> *mut c_char {
+    // Not handling errors when unwrapping as the content is controlled
+    // and is an empty string
+    let empty = String::new();
+
+    if version.is_null() {
+        return allocate_and_copy_string(&empty);
+    }
+
+    // Require an unsafe block to read the version from a C string. The pointer
+    // is checked above to ensure it is not null.
+    let c_str: &CStr = unsafe { CStr::from_ptr(version) };
+
+    let version = match c_str.to_str() {
+        Ok(n) => n,
+        Err(_) => return allocate_and_copy_string(&empty),
+    };
+
+    let supported = compute_for_old_tor(&version);
+
+    allocate_and_copy_string(&supported)
+}

+ 33 - 0
src/rust/protover/lib.rs

@@ -0,0 +1,33 @@
+//! Copyright (c) 2016-2017, The Tor Project, Inc. */
+//! See LICENSE for licensing information */
+
+//! Versioning information for different pieces of the Tor protocol.
+//!
+//! The below description is taken from src/rust/protover.c, which is currently
+//! enabled by default. We are in the process of experimenting with Rust in
+//! tor, and this protover module is implemented to help achieve this goal.
+//!
+//! Starting in version 0.2.9.3-alpha, Tor places separate version numbers on
+//! each of the different components of its protocol. Relays use these numbers
+//! to advertise what versions of the protocols they can support, and clients
+//! use them to find what they can ask a given relay to do.  Authorities vote
+//! on the supported protocol versions for each relay, and also vote on the
+//! which protocols you should have to support in order to be on the Tor
+//! network. All Tor instances use these required/recommended protocol versions
+//! to tell what level of support for recent protocols each relay has, and
+//! to decide whether they should be running given their current protocols.
+//!
+//! The main advantage of these protocol versions numbers over using Tor
+//! version numbers is that they allow different implementations of the Tor
+//! protocols to develop independently, without having to claim compatibility
+//! with specific versions of Tor.
+
+extern crate libc;
+extern crate smartlist;
+extern crate external;
+extern crate tor_allocate;
+
+mod protover;
+pub mod ffi;
+
+pub use protover::*;

+ 851 - 0
src/rust/protover/protover.rs

@@ -0,0 +1,851 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+use external::c_tor_version_as_new_as;
+
+use std::str::FromStr;
+use std::fmt;
+use std::collections::{HashMap, HashSet};
+use std::string::String;
+
+/// The first version of Tor that included "proto" entries in its descriptors.
+/// Authorities should use this to decide whether to guess proto lines.
+const FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS: &'static str = "0.2.9.3-alpha";
+
+/// The maximum number of subprotocol version numbers we will attempt to expand
+/// before concluding that someone is trying to DoS us
+const MAX_PROTOCOLS_TO_EXPAND: u32 = 500;
+
+/// Currently supported protocols and their versions
+const SUPPORTED_PROTOCOLS: &'static [&'static str] = &[
+    "Cons=1-2",
+    "Desc=1-2",
+    "DirCache=1-2",
+    "HSDir=1-2",
+    "HSIntro=3-4",
+    "HSRend=1-2",
+    "Link=1-4",
+    "LinkAuth=1,3",
+    "Microdesc=1-2",
+    "Relay=1-2",
+];
+
+/// Known subprotocols in Tor. Indicates which subprotocol a relay supports.
+#[derive(Hash, Eq, PartialEq, Debug)]
+pub enum Proto {
+    Cons,
+    Desc,
+    DirCache,
+    HSDir,
+    HSIntro,
+    HSRend,
+    Link,
+    LinkAuth,
+    Microdesc,
+    Relay,
+}
+
+impl fmt::Display for Proto {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+/// Translates a string representation of a protocol into a Proto type.
+/// Error if the string is an unrecognized protocol name.
+impl FromStr for Proto {
+    type Err = &'static str;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "Cons" => Ok(Proto::Cons),
+            "Desc" => Ok(Proto::Desc),
+            "DirCache" => Ok(Proto::DirCache),
+            "HSDir" => Ok(Proto::HSDir),
+            "HSIntro" => Ok(Proto::HSIntro),
+            "HSRend" => Ok(Proto::HSRend),
+            "Link" => Ok(Proto::Link),
+            "LinkAuth" => Ok(Proto::LinkAuth),
+            "Microdesc" => Ok(Proto::Microdesc),
+            "Relay" => Ok(Proto::Relay),
+            _ => Err("Not a valid protocol type"),
+        }
+    }
+}
+
+/// Get the string representation of current supported protocols
+///
+/// # Returns
+///
+/// A `String` whose value is the existing protocols supported by tor.
+/// Returned data is in the format as follows:
+///
+/// "HSDir=1-1 LinkAuth=1"
+///
+pub fn get_supported_protocols() -> String {
+    SUPPORTED_PROTOCOLS.join(" ")
+}
+
+/// Translates a vector representation of a protocol list into a HashMap
+fn parse_protocols(
+    protocols: &[&str],
+) -> Result<HashMap<Proto, HashSet<u32>>, &'static str> {
+    let mut parsed = HashMap::new();
+
+    for subproto in protocols {
+        let (name, version) = get_proto_and_vers(subproto)?;
+        parsed.insert(name, version);
+    }
+    Ok(parsed)
+}
+
+/// Translates a string representation of a protocol list to a HashMap
+fn parse_protocols_from_string<'a>(
+    protocol_string: &'a str,
+) -> Result<HashMap<Proto, HashSet<u32>>, &'static str> {
+    let protocols: &[&'a str] =
+        &protocol_string.split(" ").collect::<Vec<&'a str>>()[..];
+
+    parse_protocols(protocols)
+}
+
+/// Translates supported tor versions from  a string into a HashMap, which is
+/// useful when looking up a specific subprotocol.
+///
+/// # Returns
+///
+/// A `Result` whose `Ok` value is a `HashMap<Proto, <u32>>` holding all
+/// subprotocols and versions currently supported by tor.
+///
+/// The returned `Result`'s `Err` value is an `&'static str` with a description
+/// of the error.
+///
+fn tor_supported() -> Result<HashMap<Proto, HashSet<u32>>, &'static str> {
+    parse_protocols(&SUPPORTED_PROTOCOLS)
+}
+
+/// Get the unique version numbers supported by a subprotocol.
+///
+/// # Inputs
+///
+/// * `version_string`, a string comprised of "[0-9,-]"
+///
+/// # Returns
+///
+/// A `Result` whose `Ok` value is a `HashSet<u32>` holding all of the unique
+/// version numbers.  If there were ranges in the `version_string`, then these
+/// are expanded, i.e. `"1-3"` would expand to `HashSet<u32>::new([1, 2, 3])`.
+/// The returned HashSet is *unordered*.
+///
+/// The returned `Result`'s `Err` value is an `&'static str` with a description
+/// of the error.
+///
+/// # Errors
+///
+/// This function will error if:
+///
+/// * the `version_string` is empty or contains an equals (`"="`) sign,
+/// * the expansion of a version range produces an error (see
+///  `expand_version_range`),
+/// * any single version number is not parseable as an `u32` in radix 10, or
+/// * there are greater than 2^16 version numbers to expand.
+///
+fn get_versions(version_string: &str) -> Result<HashSet<u32>, &'static str> {
+    if version_string.is_empty() {
+        return Err("version string is empty");
+    }
+
+    let mut versions = HashSet::<u32>::new();
+
+    for piece in version_string.split(",") {
+        if piece.contains("-") {
+            for p in expand_version_range(piece)? {
+                versions.insert(p);
+            }
+        } else {
+            versions.insert(u32::from_str(piece).or(
+                Err("invalid protocol entry"),
+            )?);
+        }
+
+        if versions.len() > MAX_PROTOCOLS_TO_EXPAND as usize {
+            return Err("Too many versions to expand");
+        }
+    }
+    Ok(versions)
+}
+
+
+/// Parse the subprotocol type and its version numbers.
+///
+/// # Inputs
+///
+/// * A `protocol_entry` string, comprised of a keyword, an "=" sign, and one
+/// or more version numbers.
+///
+/// # Returns
+///
+/// A `Result` whose `Ok` value is a tuple of `(Proto, HashSet<u32>)`, where the
+/// first element is the subprotocol type (see `protover::Proto`) and the last
+/// element is a(n unordered) set of unique version numbers which are supported.
+/// Otherwise, the `Err` value of this `Result` is a description of the error
+///
+fn get_proto_and_vers<'a>(
+    protocol_entry: &'a str,
+) -> Result<(Proto, HashSet<u32>), &'static str> {
+    let mut parts = protocol_entry.splitn(2, "=");
+
+    let proto = match parts.next() {
+        Some(n) => n,
+        None => return Err("invalid protover entry"),
+    };
+
+    let vers = match parts.next() {
+        Some(n) => n,
+        None => return Err("invalid protover entry"),
+    };
+
+    let versions = get_versions(vers)?;
+    let proto_name = proto.parse()?;
+
+    Ok((proto_name, versions))
+}
+
+/// Parses a single subprotocol entry string into subprotocol and version
+/// parts, and then checks whether any of those versions are unsupported.
+/// Helper for protover::all_supported
+///
+/// # Inputs
+///
+/// Accepted data is in the string format as follows:
+///
+/// "HSDir=1-1"
+///
+/// # Returns
+///
+/// Returns `true` if the protocol entry is well-formatted and only contains
+/// versions that are also supported in tor. Otherwise, returns false
+///
+fn contains_only_supported_protocols(proto_entry: &str) -> bool {
+    let (name, mut vers) = match get_proto_and_vers(proto_entry) {
+        Ok(n) => n,
+        Err(_) => return false,
+    };
+
+    let currently_supported: HashMap<Proto, HashSet<u32>> =
+        match tor_supported() {
+            Ok(n) => n,
+            Err(_) => return false,
+        };
+
+    let supported_versions = match currently_supported.get(&name) {
+        Some(n) => n,
+        None => return false,
+    };
+
+    vers.retain(|x| !supported_versions.contains(x));
+    vers.is_empty()
+}
+
+/// Determine if we support every protocol a client supports, and if not,
+/// determine which protocols we do not have support for.
+///
+/// # Inputs
+///
+/// Accepted data is in the string format as follows:
+///
+/// "HSDir=1-1 LinkAuth=1-2"
+///
+/// # Returns
+///
+/// Return `true` if every protocol version is one that we support.
+/// Otherwise, return `false`.
+/// Optionally, return parameters which the client supports but which we do not
+///
+/// # Examples
+/// ```
+/// use protover::all_supported;
+///
+/// let (is_supported, unsupported)  = all_supported("Link=1");
+/// assert_eq!(true, is_supported);
+///
+/// let (is_supported, unsupported)  = all_supported("Link=5-6");
+/// assert_eq!(false, is_supported);
+/// assert_eq!("Link=5-6", unsupported);
+///
+pub fn all_supported(protocols: &str) -> (bool, String) {
+    let unsupported = protocols
+        .split_whitespace()
+        .filter(|v| !contains_only_supported_protocols(v))
+        .collect::<Vec<&str>>();
+
+    (unsupported.is_empty(), unsupported.join(" "))
+}
+
+/// Return true iff the provided protocol list includes support for the
+/// indicated protocol and version.
+/// Otherwise, return false
+///
+/// # Inputs
+///
+/// * `list`, a string representation of a list of protocol entries.
+/// * `proto`, a `Proto` to test support for
+/// * `vers`, a `u32` version which we will go on to determine whether the
+/// specified protocol supports.
+///
+/// # Examples
+/// ```
+/// use protover::*;
+///
+/// let is_supported = protover_string_supports_protocol("Link=3-4 Cons=1",
+///                                                      Proto::Cons,1);
+/// assert_eq!(true, is_supported);
+///
+/// let is_not_supported = protover_string_supports_protocol("Link=3-4 Cons=1",
+///                                                           Proto::Cons,5);
+/// assert_eq!(false, is_not_supported)
+/// ```
+pub fn protover_string_supports_protocol(
+    list: &str,
+    proto: Proto,
+    vers: u32,
+) -> bool {
+    let supported: HashMap<Proto, HashSet<u32>>;
+
+    match parse_protocols_from_string(list) {
+        Ok(result) => supported = result,
+        Err(_) => return false,
+    }
+
+    let supported_versions = match supported.get(&proto) {
+        Some(n) => n,
+        None => return false,
+    };
+
+    supported_versions.contains(&vers)
+}
+
+/// Fully expand a version range. For example, 1-3 expands to 1,2,3
+/// Helper for get_versions
+///
+/// # Inputs
+///
+/// `range`, a string comprised of "[0-9,-]"
+///
+/// # Returns
+///
+/// A `Result` whose `Ok` value a vector of unsigned integers representing the
+/// expanded range of supported versions by a single protocol.
+/// Otherwise, the `Err` value of this `Result` is a description of the error
+///
+/// # Errors
+///
+/// This function will error if:
+///
+/// * the specified range is empty
+/// * the version range does not contain both a valid lower and upper bound.
+///
+fn expand_version_range(range: &str) -> Result<Vec<u32>, &'static str> {
+    if range.is_empty() {
+        return Err("version string empty");
+    }
+
+    let mut parts = range.split("-");
+
+    let lower_string = parts.next().ok_or(
+        "cannot parse protocol range lower bound",
+    )?;
+
+    let lower = u32::from_str_radix(lower_string, 10).or(Err(
+        "cannot parse protocol range lower bound",
+    ))?;
+
+    let higher_string = parts.next().ok_or(
+        "cannot parse protocol range upper bound",
+    )?;
+
+    let higher = u32::from_str_radix(higher_string, 10).or(Err(
+        "cannot parse protocol range upper bound",
+    ))?;
+
+    // We can use inclusive range syntax when it becomes stable.
+    Ok((lower..higher + 1).collect())
+}
+
+/// Checks to see if there is a continuous range of integers, starting at the
+/// first in the list. Returns the last integer in the range if a range exists.
+/// Helper for compute_vote
+///
+/// # Inputs
+///
+/// `list`, an ordered  vector of `u32` integers of "[0-9,-]" representing the
+/// supported versions for a single protocol.
+///
+/// # Returns
+///
+/// A `bool` indicating whether the list contains a range, starting at the
+/// first in the list, and an `u32` of the last integer in the range.
+///
+/// For example, if given vec![1, 2, 3, 5], find_range will return true,
+/// as there is a continuous range, and 3, which is the last number in the
+/// continuous range.
+///
+fn find_range(list: &Vec<u32>) -> (bool, u32) {
+    if list.len() == 0 {
+        return (false, 0);
+    }
+
+    let mut iterable = list.iter().peekable();
+    let mut range_end = match iterable.next() {
+        Some(n) => *n,
+        None => return (false, 0),
+    };
+
+    let mut has_range = false;
+
+    while iterable.peek().is_some() {
+        let n = *iterable.next().unwrap();
+        if n != range_end + 1 {
+            break;
+        }
+
+        has_range = true;
+        range_end = n;
+    }
+
+    (has_range, range_end)
+}
+
+/// Contracts a HashSet representation of supported versions into a string.
+/// Helper for compute_vote
+///
+/// # Inputs
+///
+/// `supported_set`, a set of integers of "[0-9,-]" representing the
+/// supported versions for a single protocol.
+///
+/// # Returns
+///
+/// A `String` representation of this set in ascending order.
+///
+fn contract_protocol_list<'a>(supported_set: &'a HashSet<u32>) -> String {
+    let mut supported_clone = supported_set.clone();
+    let mut supported: Vec<u32> = supported_clone.drain().collect();
+    supported.sort();
+
+    let mut final_output: Vec<String> = Vec::new();
+
+    while supported.len() != 0 {
+        let (has_range, end) = find_range(&supported);
+        let current = supported.remove(0);
+
+        if has_range {
+            final_output.push(format!(
+                "{}-{}",
+                current.to_string(),
+                &end.to_string(),
+            ));
+            supported.retain(|&x| x > end);
+        } else {
+            final_output.push(current.to_string());
+        }
+    }
+
+    final_output.join(",")
+}
+
+/// Parses a protocol list without validating the protocol names
+///
+/// # Inputs
+///
+/// * `protocol_string`, a string comprised of keys and values, both which are
+/// strings. The keys are the protocol names while values are a string
+/// representation of the supported versions.
+///
+/// The input is _not_ expected to be a subset of the Proto types
+///
+/// # Returns
+///
+/// A `Result` whose `Ok` value is a `HashSet<u32>` holding all of the unique
+/// version numbers.
+///
+/// The returned `Result`'s `Err` value is an `&'static str` with a description
+/// of the error.
+///
+/// # Errors
+///
+/// This function will error if:
+///
+/// * The protocol string does not follow the "protocol_name=version_list"
+/// expected format
+/// * If the version string is malformed. See `get_versions`.
+///
+fn parse_protocols_from_string_with_no_validation<'a>(
+    protocol_string: &'a str,
+) -> Result<HashMap<String, HashSet<u32>>, &'static str> {
+    let protocols = &protocol_string.split(" ").collect::<Vec<&'a str>>()[..];
+
+    let mut parsed: HashMap<String, HashSet<u32>> = HashMap::new();
+
+    for subproto in protocols {
+        let mut parts = subproto.splitn(2, "=");
+
+        let name = match parts.next() {
+            Some(n) => n,
+            None => return Err("invalid protover entry"),
+        };
+
+        let vers = match parts.next() {
+            Some(n) => n,
+            None => return Err("invalid protover entry"),
+        };
+
+        let versions = get_versions(vers)?;
+
+        parsed.insert(String::from(name), versions);
+    }
+    Ok(parsed)
+}
+
+/// Protocol voting implementation.
+///
+/// Given a list of strings describing protocol versions, return a new
+/// string encoding all of the protocols that are listed by at
+/// least threshold of the inputs.
+///
+/// The string is sorted according to the following conventions:
+///   - Protocols names are alphabetized
+///   - Protocols are in order low to high
+///   - Individual and ranges are listed together. For example,
+///     "3, 5-10,13"
+///   - All entries are unique
+///
+/// # Examples
+/// ```
+/// use protover::compute_vote;
+///
+/// let protos = vec![String::from("Link=3-4"), String::from("Link=3")];
+/// let vote = compute_vote(protos, 2);
+/// assert_eq!("Link=3", vote)
+/// ```
+pub fn compute_vote(
+    list_of_proto_strings: Vec<String>,
+    threshold: i32,
+) -> String {
+    let empty = String::from("");
+
+    if list_of_proto_strings.is_empty() {
+        return empty;
+    }
+
+    // all_count is a structure to represent the count of the number of
+    // supported versions for a specific protocol. For example, in JSON format:
+    // {
+    //  "FirstSupportedProtocol": {
+    //      "1": "3",
+    //      "2": "1"
+    //  }
+    // }
+    // means that FirstSupportedProtocol has three votes which support version
+    // 1, and one vote that supports version 2
+    let mut all_count: HashMap<String, HashMap<u32, usize>> = HashMap::new();
+
+    // parse and collect all of the protos and their versions and collect them
+    for vote in list_of_proto_strings {
+        let this_vote: HashMap<String, HashSet<u32>> =
+            match parse_protocols_from_string_with_no_validation(&vote) {
+                Ok(result) => result,
+                Err(_) => continue,
+            };
+
+        for (protocol, versions) in this_vote {
+            let supported_vers: &mut HashMap<u32, usize> =
+                all_count.entry(protocol).or_insert(HashMap::new());
+
+            for version in versions {
+                let counter: &mut usize =
+                    supported_vers.entry(version).or_insert(0);
+                *counter += 1;
+            }
+        }
+    }
+
+    let mut final_output: HashMap<String, String> =
+        HashMap::with_capacity(SUPPORTED_PROTOCOLS.len());
+
+    // Go through and remove verstions that are less than the threshold
+    for (protocol, versions) in all_count {
+        let mut meets_threshold = HashSet::new();
+        for (version, count) in versions {
+            if count >= threshold as usize {
+                meets_threshold.insert(version);
+            }
+        }
+
+        // For each protocol, compress its version list into the expected
+        // protocol version string format
+        let contracted = contract_protocol_list(&meets_threshold);
+        if !contracted.is_empty() {
+            final_output.insert(protocol, contracted);
+        }
+    }
+
+    write_vote_to_string(&final_output)
+}
+
+/// Return a String comprised of protocol entries in alphabetical order
+///
+/// # Inputs
+///
+/// * `vote`, a `HashMap` comprised of keys and values, both which are strings.
+/// The keys are the protocol names while values are a string representation of
+/// the supported versions.
+///
+/// # Returns
+///
+/// A `String` whose value is series of pairs, comprising of the protocol name
+/// and versions that it supports. The string takes the following format:
+///
+/// "first_protocol_name=1,2-5, second_protocol_name=4,5"
+///
+/// Sorts the keys in alphabetical order and creates the expected subprotocol
+/// entry format.
+///
+fn write_vote_to_string(vote: &HashMap<String, String>) -> String {
+    let mut keys: Vec<&String> = vote.keys().collect();
+    keys.sort();
+
+    let mut output = Vec::new();
+    for key in keys {
+        // TODO error in indexing here?
+        output.push(format!("{}={}", key, vote[key]));
+    }
+    output.join(" ")
+}
+
+/// Returns a boolean indicating whether the given protocol and version is
+/// supported in any of the existing Tor protocols
+///
+/// # Examples
+/// ```
+/// use protover::*;
+///
+/// let is_supported = is_supported_here(Proto::Link, 5);
+/// assert_eq!(false, is_supported);
+///
+/// let is_supported = is_supported_here(Proto::Link, 1);
+/// assert_eq!(true, is_supported);
+/// ```
+pub fn is_supported_here(proto: Proto, vers: u32) -> bool {
+    let currently_supported: HashMap<Proto, HashSet<u32>>;
+
+    match tor_supported() {
+        Ok(result) => currently_supported = result,
+        Err(_) => return false,
+    }
+
+    let supported_versions = match currently_supported.get(&proto) {
+        Some(n) => n,
+        None => return false,
+    };
+
+    supported_versions.contains(&vers)
+}
+
+/// Older versions of Tor cannot infer their own subprotocols
+/// Used to determine which subprotocols are supported by older Tor versions.
+///
+/// # Inputs
+///
+/// * `version`, a string comprised of "[0-9,-]"
+///
+/// # Returns
+///
+/// A `String` whose value is series of pairs, comprising of the protocol name
+/// and versions that it supports. The string takes the following format:
+///
+/// "HSDir=1-1 LinkAuth=1"
+///
+/// This function returns the protocols that are supported by the version input,
+/// only for tor versions older than FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS.
+///
+pub fn compute_for_old_tor(version: &str) -> String {
+    if c_tor_version_as_new_as(
+        version,
+        FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS,
+    )
+    {
+        return String::new();
+    }
+
+    if c_tor_version_as_new_as(version, "0.2.9.1-alpha") {
+        let ret = "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \
+                   Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2";
+        return String::from(ret);
+    }
+
+    if c_tor_version_as_new_as(version, "0.2.7.5") {
+        let ret = "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \
+                   Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2";
+        return String::from(ret);
+    }
+
+    if c_tor_version_as_new_as(version, "0.2.4.19") {
+        let ret = "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \
+                   Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2";
+        return String::from(ret);
+    }
+    String::new()
+}
+
+#[cfg(test)]
+mod test {
+    #[test]
+    fn test_get_versions() {
+        use std::collections::HashSet;
+
+        use super::get_versions;
+
+        assert_eq!(Err("version string is empty"), get_versions(""));
+        assert_eq!(Err("invalid protocol entry"), get_versions("a,b"));
+        assert_eq!(Err("invalid protocol entry"), get_versions("1,!"));
+
+        {
+            let mut versions: HashSet<u32> = HashSet::new();
+            versions.insert(1);
+            assert_eq!(Ok(versions), get_versions("1"));
+        }
+        {
+            let mut versions: HashSet<u32> = HashSet::new();
+            versions.insert(1);
+            versions.insert(2);
+            assert_eq!(Ok(versions), get_versions("1,2"));
+        }
+        {
+            let mut versions: HashSet<u32> = HashSet::new();
+            versions.insert(1);
+            versions.insert(2);
+            versions.insert(3);
+            assert_eq!(Ok(versions), get_versions("1-3"));
+        }
+        {
+            let mut versions: HashSet<u32> = HashSet::new();
+            versions.insert(1);
+            versions.insert(2);
+            versions.insert(5);
+            assert_eq!(Ok(versions), get_versions("1-2,5"));
+        }
+        {
+            let mut versions: HashSet<u32> = HashSet::new();
+            versions.insert(1);
+            versions.insert(3);
+            versions.insert(4);
+            versions.insert(5);
+            assert_eq!(Ok(versions), get_versions("1,3-5"));
+        }
+    }
+
+    #[test]
+    fn test_contains_only_supported_protocols() {
+        use super::contains_only_supported_protocols;
+
+        assert_eq!(false, contains_only_supported_protocols(""));
+        assert_eq!(false, contains_only_supported_protocols("Cons="));
+        assert_eq!(true, contains_only_supported_protocols("Cons=1"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=0"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=0-1"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=5"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=1-5"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=1,5"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=5,6"));
+        assert_eq!(false, contains_only_supported_protocols("Cons=1,5,6"));
+        assert_eq!(true, contains_only_supported_protocols("Cons=1,2"));
+        assert_eq!(true, contains_only_supported_protocols("Cons=1-2"));
+    }
+
+    #[test]
+    fn test_find_range() {
+        use super::find_range;
+
+        assert_eq!((false, 0), find_range(&vec![]));
+        assert_eq!((false, 1), find_range(&vec![1]));
+        assert_eq!((true, 2), find_range(&vec![1, 2]));
+        assert_eq!((true, 3), find_range(&vec![1, 2, 3]));
+        assert_eq!((true, 3), find_range(&vec![1, 2, 3, 5]));
+    }
+
+    #[test]
+    fn test_expand_version_range() {
+        use super::expand_version_range;
+
+        assert_eq!(Err("version string empty"), expand_version_range(""));
+        assert_eq!(Ok(vec![1, 2]), expand_version_range("1-2"));
+        assert_eq!(Ok(vec![1, 2, 3, 4]), expand_version_range("1-4"));
+        assert_eq!(
+            Err("cannot parse protocol range lower bound"),
+            expand_version_range("a")
+        );
+        assert_eq!(
+            Err("cannot parse protocol range upper bound"),
+            expand_version_range("1-a")
+        );
+    }
+
+    #[test]
+    fn test_contract_protocol_list() {
+        use std::collections::HashSet;
+        use super::contract_protocol_list;
+
+        {
+            let mut versions = HashSet::<u32>::new();
+            assert_eq!(String::from(""), contract_protocol_list(&versions));
+
+            versions.insert(1);
+            assert_eq!(String::from("1"), contract_protocol_list(&versions));
+
+            versions.insert(2);
+            assert_eq!(String::from("1-2"), contract_protocol_list(&versions));
+        }
+
+        {
+            let mut versions = HashSet::<u32>::new();
+            versions.insert(1);
+            versions.insert(3);
+            assert_eq!(String::from("1,3"), contract_protocol_list(&versions));
+        }
+
+        {
+            let mut versions = HashSet::<u32>::new();
+            versions.insert(1);
+            versions.insert(2);
+            versions.insert(3);
+            versions.insert(4);
+            assert_eq!(String::from("1-4"), contract_protocol_list(&versions));
+        }
+
+        {
+            let mut versions = HashSet::<u32>::new();
+            versions.insert(1);
+            versions.insert(3);
+            versions.insert(5);
+            versions.insert(6);
+            versions.insert(7);
+            assert_eq!(
+                String::from("1,3,5-7"),
+                contract_protocol_list(&versions)
+            );
+        }
+
+        {
+            let mut versions = HashSet::<u32>::new();
+            versions.insert(1);
+            versions.insert(2);
+            versions.insert(3);
+            versions.insert(500);
+            assert_eq!(
+                String::from("1-3,500"),
+                contract_protocol_list(&versions)
+            );
+        }
+    }
+}

+ 291 - 0
src/rust/protover/tests/protover.rs

@@ -0,0 +1,291 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+extern crate protover;
+
+#[test]
+fn parse_protocol_list_with_single_proto_and_single_version() {
+    let protocol = "Cons=1";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_multiple_versions() {
+    let protocol = "Cons=1-2";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_different_single_protocol_and_single_version() {
+    let protocol = "HSDir=1";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_supported_version() {
+    let protocol = "Desc=2";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_two_protocols_and_single_version() {
+    let protocols = "Cons=1 HSDir=1";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_two_nonsequential_versions() {
+    let protocol = "Desc=1,2";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_two_sequential_versions() {
+    let protocol = "Desc=1-2";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_protocol_range_returns_set() {
+    let protocol = "Link=1-4";
+    let (is_supported, unsupported) = protover::all_supported(protocol);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn parse_protocol_list_with_single_protocol_and_protocol_set() {
+    let protocols = "Link=3-4 Desc=2";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_two_values() {
+    let protocols = "Microdesc=1-2 Relay=2";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!("", &unsupported);
+    assert_eq!(true, is_supported);
+}
+
+#[test]
+fn protover_all_supported_with_one_value() {
+    let protocols = "Microdesc=1-2";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!("", &unsupported);
+    assert_eq!(true, is_supported);
+}
+
+#[test]
+fn protover_all_supported_with_empty() {
+    let protocols = "";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(true, is_supported);
+    assert_eq!("", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_three_values() {
+    let protocols = "LinkAuth=1 Microdesc=1-2 Relay=2";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!("", &unsupported);
+    assert_eq!(true, is_supported);
+}
+
+#[test]
+fn protover_all_supported_with_unsupported_protocol() {
+    let protocols = "Wombat=9";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Wombat=9", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_unsupported_versions() {
+    let protocols = "Link=3-999";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Link=3-999", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_unsupported_low_version() {
+    let protocols = "Cons=0-1";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Cons=0-1", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_unsupported_high_version() {
+    let protocols = "Cons=1-3";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Cons=1-3", &unsupported);
+}
+
+#[test]
+fn protover_all_supported_with_mix_of_supported_and_unsupproted() {
+    let protocols = "Link=3-4 Wombat=9";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Wombat=9", &unsupported);
+}
+
+#[test]
+fn protover_string_supports_protocol_returns_true_for_single_supported() {
+    let protocols = "Link=3-4 Cons=1";
+    let is_supported = protover::protover_string_supports_protocol(
+        protocols,
+        protover::Proto::Cons,
+        1,
+    );
+    assert_eq!(true, is_supported);
+}
+
+#[test]
+fn protover_string_supports_protocol_returns_false_for_single_unsupported() {
+    let protocols = "Link=3-4 Cons=1";
+    let is_supported = protover::protover_string_supports_protocol(
+        protocols,
+        protover::Proto::Cons,
+        2,
+    );
+    assert_eq!(false, is_supported);
+}
+
+#[test]
+fn protover_string_supports_protocol_returns_false_for_unsupported() {
+    let protocols = "Link=3-4";
+    let is_supported = protover::protover_string_supports_protocol(
+        protocols,
+        protover::Proto::Cons,
+        2,
+    );
+    assert_eq!(false, is_supported);
+}
+
+#[test]
+fn protover_all_supported_with_unexpected_characters() {
+    let protocols = "Cons=*-%";
+    let (is_supported, unsupported) = protover::all_supported(protocols);
+    assert_eq!(false, is_supported);
+    assert_eq!("Cons=*-%", &unsupported);
+}
+
+#[test]
+fn protover_compute_vote_returns_empty_for_empty_string() {
+    let protocols = vec![String::from("")];
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_single_protocol_for_matching() {
+    let protocols = vec![String::from("Cons=1")];
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("Cons=1", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_two_protocols_for_two_matching() {
+    let protocols = vec![String::from("Link=1 Cons=1")];
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("Cons=1 Link=1", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_one_protocol_when_one_out_of_two_matches() {
+    let protocols = vec![String::from("Cons=1 Link=2"), String::from("Cons=1")];
+    let listed = protover::compute_vote(protocols, 2);
+    assert_eq!("Cons=1", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_protocols_that_it_doesnt_currently_support() {
+    let protocols = vec![String::from("Foo=1 Cons=2"), String::from("Bar=1")];
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("Bar=1 Cons=2 Foo=1", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_matching_for_mix() {
+    let protocols = vec![String::from("Link=1-10,500 Cons=1,3-7,8")];
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("Cons=1,3-8 Link=1-10,500", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_matching_for_longer_mix() {
+    let protocols = vec![
+        String::from("Desc=1-10,500 Cons=1,3-7,8"),
+        String::from("Link=123-456,78 Cons=2-6,8 Desc=9"),
+    ];
+
+    let listed = protover::compute_vote(protocols, 1);
+    assert_eq!("Cons=1-8 Desc=1-10,500 Link=78,123-456", listed);
+}
+
+#[test]
+fn protover_compute_vote_returns_matching_for_longer_mix_with_threshold_two() {
+    let protocols = vec![
+        String::from("Desc=1-10,500 Cons=1,3-7,8"),
+        String::from("Link=123-456,78 Cons=2-6,8 Desc=9"),
+    ];
+
+    let listed = protover::compute_vote(protocols, 2);
+    assert_eq!("Cons=3-6,8 Desc=9", listed);
+}
+
+#[test]
+fn protover_compute_vote_handles_duplicated_versions() {
+    let protocols = vec![String::from("Cons=1"), String::from("Cons=1")];
+    assert_eq!("Cons=1", protover::compute_vote(protocols, 2));
+
+    let protocols = vec![String::from("Cons=1-2"), String::from("Cons=1-2")];
+    assert_eq!("Cons=1-2", protover::compute_vote(protocols, 2));
+}
+
+#[test]
+fn protover_compute_vote_handles_invalid_proto_entries() {
+    let protocols = vec![
+        String::from("Cons=1"),
+        String::from("Cons=1"),
+        String::from("Link=a"),
+    ];
+    assert_eq!("Cons=1", protover::compute_vote(protocols, 2));
+
+    let protocols = vec![
+        String::from("Cons=1"),
+        String::from("Cons=1"),
+        String::from("Link=1-%"),
+    ];
+    assert_eq!("Cons=1", protover::compute_vote(protocols, 2));
+}
+
+#[test]
+fn protover_is_supported_here_returns_true_for_supported_protocol() {
+    assert_eq!(true, protover::is_supported_here(protover::Proto::Cons, 1));
+}
+
+#[test]
+fn protover_is_supported_here_returns_false_for_unsupported_protocol() {
+    assert_eq!(false, protover::is_supported_here(protover::Proto::Cons, 5));
+}

+ 13 - 0
src/rust/smartlist/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+authors = ["The Tor Project"]
+version = "0.0.1"
+name = "smartlist"
+
+[dependencies]
+libc = "0.2.22"
+
+[lib]
+name = "smartlist"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+

+ 8 - 0
src/rust/smartlist/lib.rs

@@ -0,0 +1,8 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+extern crate libc;
+
+mod smartlist;
+
+pub use smartlist::*;

+ 115 - 0
src/rust/smartlist/smartlist.rs

@@ -0,0 +1,115 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+use std::slice;
+use libc::{c_char, c_int};
+use std::ffi::CStr;
+
+/// Smartlists are a type used in C code in tor to define a collection of a
+/// generic type, which has a capacity and a number used. Each Smartlist
+/// defines how to extract the list of values from the underlying C structure
+///
+/// Implementations are required to have a C representation, as this module
+/// serves purely to translate smartlists as defined in tor to vectors in Rust.
+pub trait Smartlist<T> {
+    fn get_list(&self) -> Vec<T>;
+}
+
+#[repr(C)]
+pub struct Stringlist {
+    pub list: *const *const c_char,
+    pub num_used: c_int,
+    pub capacity: c_int,
+}
+
+impl Smartlist<String> for Stringlist {
+    fn get_list(&self) -> Vec<String> {
+        let empty: Vec<String> = Vec::new();
+        let mut rust_list: Vec<String> = Vec::new();
+
+        if self.list.is_null() || self.num_used == 0 {
+            return empty;
+        }
+
+        // unsafe, as we need to extract the smartlist list into a vector of
+        // pointers, and then transform each element into a Rust string.
+        let elems: &[*const i8] =
+            unsafe { slice::from_raw_parts(self.list, self.num_used as usize) };
+
+        for elem in elems.iter() {
+            if elem.is_null() {
+                continue;
+            }
+
+            // unsafe, as we need to create a cstring from the referenced
+            // element
+            let c_string = unsafe { CStr::from_ptr(*elem) };
+
+            let r_string = match c_string.to_str() {
+                Ok(n) => n,
+                Err(_) => return empty,
+            };
+
+            rust_list.push(String::from(r_string));
+        }
+
+        rust_list
+    }
+}
+
+// TODO: CHK: this module maybe should be tested from a test in C with a
+// smartlist as defined in tor.
+#[cfg(test)]
+mod test {
+    #[test]
+    fn test_get_list_of_strings() {
+        extern crate libc;
+
+        use std::ffi::CString;
+        use libc::c_char;
+
+        use super::Smartlist;
+        use super::Stringlist;
+
+        {
+            // test to verify that null pointers are gracefully handled
+            use std::ptr;
+
+            let sl = Stringlist {
+                list: ptr::null(),
+                num_used: 0,
+                capacity: 0,
+            };
+
+            let data = sl.get_list();
+            assert_eq!(0, data.len());
+        }
+
+        {
+            let args = vec![String::from("a"), String::from("b")];
+
+            // for each string, transform  it into a CString
+            let c_strings: Vec<_> = args.iter()
+                .map(|arg| CString::new(arg.as_str()).unwrap())
+                .collect();
+
+            // then, collect a pointer for each CString
+            let p_args: Vec<_> =
+                c_strings.iter().map(|arg| arg.as_ptr()).collect();
+
+            let p: *const *const c_char = p_args.as_ptr();
+
+            // This is the representation that we expect when receiving a
+            // smartlist at the Rust/C FFI layer.
+            let sl = Stringlist {
+                list: p,
+                num_used: 2,
+                capacity: 2,
+            };
+
+            let data = sl.get_list();
+            assert_eq!("a", &data[0]);
+            assert_eq!("b", &data[1]);
+        }
+    }
+}

+ 13 - 0
src/rust/tor_allocate/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+authors = ["The Tor Project"]
+version = "0.0.1"
+name = "tor_allocate"
+
+[dependencies]
+libc = "0.2.22"
+
+[lib]
+name = "tor_allocate"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+

+ 15 - 0
src/rust/tor_allocate/lib.rs

@@ -0,0 +1,15 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+//! Allocation helper functions that allow data to be allocated in Rust
+//! using tor's specified allocator. In doing so, this can be later freed
+//! from C.
+//!
+//! This is currently a temporary solution, we will later use tor's allocator
+//! by default for any allocation that occurs in Rust. However, as this will
+//! stabalize in 2018, we can use this as a temporary measure.
+
+extern crate libc;
+
+mod tor_allocate;
+pub use tor_allocate::*;

+ 101 - 0
src/rust/tor_allocate/tor_allocate.rs

@@ -0,0 +1,101 @@
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+use libc::{c_char, c_void};
+use std::{ptr, slice, mem};
+
+#[cfg(not(test))]
+extern "C" {
+    fn tor_malloc_(size: usize) -> *mut c_void;
+}
+
+// Defined only for tests, used for testing purposes, so that we don't need
+// to link to tor C files. Uses the system allocator
+#[cfg(test)]
+extern "C" fn tor_malloc_(size: usize) -> *mut c_void {
+    use libc::malloc;
+    unsafe { malloc(size) }
+}
+
+/// Allocate memory using tor_malloc_ and copy an existing string into the
+/// allocated buffer, returning a pointer that can later be called in C.
+///
+/// # Inputs
+///
+/// * `src`, a reference to a String.
+///
+/// # Returns
+///
+/// A `*mut c_char` that should be freed by tor_free in C
+///
+/// Allow unused unsafe as at compile-time, we get warnings that unsafe is not
+/// needed even though this calls tor_malloc in C.
+///
+#[allow(unused_unsafe)]
+pub fn allocate_and_copy_string(src: &String) -> *mut c_char {
+    let bytes: &[u8] = src.as_bytes();
+
+    let size = mem::size_of_val::<[u8]>(bytes);
+    let size_one_byte = mem::size_of::<u8>();
+
+    // handle integer overflow when adding one to the calculated length
+    let size_with_null_byte = match size.checked_add(size_one_byte) {
+        Some(n) => n,
+        None => return ptr::null_mut(),
+    };
+
+    let dest = unsafe { tor_malloc_(size_with_null_byte) as *mut u8 };
+
+    if dest.is_null() {
+        return ptr::null_mut();
+    }
+
+    unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), dest, size) };
+
+    // set the last byte as null, using the ability to index into a slice
+    // rather than doing pointer arithmatic
+    let slice = unsafe { slice::from_raw_parts_mut(dest, size_with_null_byte) };
+    slice[size] = 0; // add a null terminator
+
+    dest as *mut c_char
+}
+
+#[cfg(test)]
+mod test {
+
+    #[test]
+    fn test_allocate_and_copy_string_with_empty() {
+        use std::ffi::CStr;
+        use libc::{free, c_void};
+
+        use tor_allocate::allocate_and_copy_string;
+
+        let empty = String::new();
+        let allocated_empty = allocate_and_copy_string(&empty);
+
+        let allocated_empty_rust =
+            unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() };
+
+        assert_eq!("", allocated_empty_rust);
+
+        unsafe { free(allocated_empty as *mut c_void) };
+    }
+
+    #[test]
+    fn test_allocate_and_copy_string_with_not_empty_string() {
+        use std::ffi::CStr;
+        use libc::{free, c_void};
+
+        use tor_allocate::allocate_and_copy_string;
+
+        let empty = String::from("foo bar biz");
+        let allocated_empty = allocate_and_copy_string(&empty);
+
+        let allocated_empty_rust =
+            unsafe { CStr::from_ptr(allocated_empty).to_str().unwrap() };
+
+        assert_eq!("foo bar biz", allocated_empty_rust);
+
+        unsafe { free(allocated_empty as *mut c_void) };
+    }
+}

+ 16 - 0
src/rust/tor_rust/Cargo.toml

@@ -0,0 +1,16 @@
+[package]
+authors = ["The Tor Project"]
+name = "tor_rust"
+version = "0.1.0"
+
+[lib]
+name = "tor_rust"
+path = "lib.rs"
+crate_type = ["rlib", "staticlib"]
+
+[dependencies.tor_util]
+path = "../tor_util"
+
+[dependencies.protover]
+path = "../protover"
+

+ 12 - 0
src/rust/tor_rust/include.am

@@ -0,0 +1,12 @@
+EXTRA_DIST +=\
+	src/rust/tor_rust/Cargo.toml \
+	src/rust/tor_rust/lib.rs \
+	src/rust/tor_rust/tor_rust.rs
+
+src/rust/target/release/@TOR_RUST_STATIC_NAME@: FORCE
+	( cd "$(abs_top_srcdir)/src/rust/tor_rust" ; \
+		CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \
+		CARGO_HOME="$(abs_top_builddir)/src/rust" \
+		$(CARGO) build --release --quiet $(CARGO_ONLINE) )
+
+FORCE:

+ 5 - 0
src/rust/tor_rust/lib.rs

@@ -0,0 +1,5 @@
+extern crate tor_util;
+extern crate protover;
+
+pub use tor_util::*;
+pub use protover::*;

+ 4 - 1
src/rust/tor_util/Cargo.toml

@@ -8,6 +8,9 @@ name = "tor_util"
 path = "lib.rs"
 path = "lib.rs"
 crate_type = ["rlib", "staticlib"]
 crate_type = ["rlib", "staticlib"]
 
 
+[dependencies.tor_allocate]
+path = "../tor_allocate"
+
 [dependencies]
 [dependencies]
-libc = "*"
+libc = "0.2.22"
 
 

+ 16 - 46
src/rust/tor_util/ffi.rs

@@ -1,56 +1,26 @@
-//! FFI functions, only to be called from C.
-//!
-//! Equivalent C versions of these live in `src/common/compat_rust.c`
-
-use std::mem::forget;
-use std::ffi::CString;
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
 
 
-use libc;
-use rust_string::RustString;
-
-/// Free the passed `RustString` (`rust_str_t` in C), to be used in place of
-/// `tor_free`().
-///
-/// # Examples
-/// ```c
-/// rust_str_t r_s = rust_welcome_string();
-/// rust_str_free(r_s);
-/// ```
-#[no_mangle]
-#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
-pub unsafe extern "C" fn rust_str_free(_str: RustString) {
-    // Empty body: Just drop _str and we're done (Drop takes care of it).
-}
+//! FFI functions to announce Rust support during tor startup, only to be
+//! called from C.
+//!
 
 
-/// Lends an immutable, NUL-terminated C String.
-///
-/// # Examples
-/// ```c
-/// rust_str_t r_s = rust_welcome_string();
-/// const char *s = rust_str_get(r_s);
-/// printf("%s", s);
-/// rust_str_free(r_s);
-/// ```
-#[no_mangle]
-pub unsafe extern "C" fn rust_str_get(str: RustString) -> *const libc::c_char {
-    let res = str.as_ptr();
-    forget(str);
-    res
-}
+use libc::c_char;
+use tor_allocate::allocate_and_copy_string;
 
 
 /// Returns a short string to announce Rust support during startup.
 /// Returns a short string to announce Rust support during startup.
 ///
 ///
 /// # Examples
 /// # Examples
 /// ```c
 /// ```c
-/// rust_str_t r_s = rust_welcome_string();
-/// const char *s = rust_str_get(r_s);
-/// printf("%s", s);
-/// rust_str_free(r_s);
+/// char *rust_str = rust_welcome_string();
+/// printf("%s", rust_str);
+/// tor_free(rust_str);
 /// ```
 /// ```
 #[no_mangle]
 #[no_mangle]
-pub extern "C" fn rust_welcome_string() -> RustString {
-    let s = CString::new("Tor is running with Rust integration. Please report \
-                          any bugs you encouter.")
-            .unwrap();
-    RustString::from(s)
+pub extern "C" fn rust_welcome_string() -> *mut c_char {
+    let rust_welcome = String::from(
+        "Tor is running with Rust integration. Please report \
+         any bugs you encouter.",
+    );
+    allocate_and_copy_string(&rust_welcome)
 }
 }

+ 0 - 13
src/rust/tor_util/include.am

@@ -1,13 +0,0 @@
-EXTRA_DIST +=\
-	src/rust/tor_util/Cargo.toml \
-	src/rust/tor_util/lib.rs \
-	src/rust/tor_util/ffi.rs \
-	src/rust/tor_util/rust_string.rs
-
-src/rust/target/release/@TOR_RUST_UTIL_STATIC_NAME@: FORCE
-	( cd "$(abs_top_srcdir)/src/rust/tor_util" ; \
-		CARGO_TARGET_DIR="$(abs_top_builddir)/src/rust/target" \
-		CARGO_HOME="$(abs_top_builddir)/src/rust" \
-		$(CARGO) build --release --quiet $(CARGO_ONLINE) )
-
-FORCE:

+ 6 - 8
src/rust/tor_util/lib.rs

@@ -1,13 +1,11 @@
-//! C <-> Rust compatibility helpers and types.
+// Copyright (c) 2016-2017, The Tor Project, Inc. */
+// See LICENSE for licensing information */
+
+//! Small module to announce Rust support during startup for demonstration
+//! purposes.
 //!
 //!
-//! Generically useful, small scale helpers should go here. This goes for both
-//! the C side (in the form of the ffi module) as well as the Rust side
-//! (individual modules per functionality). The corresponding C stuff lives in
-//! `src/common/compat_rust.{c,h}`.
 
 
 extern crate libc;
 extern crate libc;
+extern crate tor_allocate;
 
 
-mod rust_string;
 pub mod ffi;
 pub mod ffi;
-
-pub use rust_string::*;

+ 0 - 101
src/rust/tor_util/rust_string.rs

@@ -1,101 +0,0 @@
-use std::ffi::CString;
-use std::mem::forget;
-use libc;
-
-/// Compatibility wrapper for strings allocated in Rust and passed to C.
-///
-/// Rust doesn't ensure the safety of freeing memory across an FFI boundary, so
-/// we need to take special care to ensure we're not accidentally calling
-/// `tor_free`() on any string allocated in Rust. To more easily differentiate
-/// between strings that possibly (if Rust support is enabled) were allocated
-/// in Rust, C has the `rust_str_t` helper type. The equivalent on the Rust
-/// side is `RustString`.
-///
-/// Note: This type must not be used for strings allocated in C.
-#[repr(C)]
-#[derive(Debug)]
-pub struct RustString(*mut libc::c_char);
-
-impl RustString {
-    /// Returns a pointer to the underlying NUL-terminated byte array.
-    ///
-    /// Note that this function is not typically useful for Rust callers,
-    /// except in a direct FFI context.
-    ///
-    /// # Examples
-    /// ```
-    /// # use tor_util::RustString;
-    /// use std::ffi::CString;
-    ///
-    /// let r = RustString::from(CString::new("asdf").unwrap());
-    /// let c_str = r.as_ptr();
-    /// assert_eq!(b'a', unsafe { *c_str as u8});
-    /// ```
-    pub fn as_ptr(&self) -> *const libc::c_char {
-        self.0 as *const libc::c_char
-    }
-}
-
-impl From<CString> for RustString {
-    /// Constructs a new `RustString`
-    ///
-    /// # Examples
-    /// ```
-    /// # use tor_util::RustString;
-    /// use std::ffi::CString;
-    ///
-    /// let r = RustString::from(CString::new("asdf").unwrap());
-    /// ```
-    fn from(str: CString) -> RustString {
-        RustString(str.into_raw())
-    }
-}
-
-impl Into<CString> for RustString {
-    /// Reconstructs a `CString` from this `RustString`.
-    ///
-    /// Useful to take ownership back from a `RustString` that was given to C
-    /// code.
-    ///
-    /// # Examples
-    /// ```
-    /// # use tor_util::RustString;
-    /// use std::ffi::CString;
-    ///
-    /// let cs = CString::new("asdf").unwrap();
-    /// let r = RustString::from(cs.clone());
-    /// let cs2 = r.into();
-    /// assert_eq!(cs, cs2);
-    /// ```
-    fn into(self) -> CString {
-        // Calling from_raw is always OK here: We only construct self using
-        // valid CStrings and don't expose anything that could mutate it
-        let ret = unsafe { CString::from_raw(self.0) };
-        forget(self);
-        ret
-    }
-}
-
-impl Drop for RustString {
-    fn drop(&mut self) {
-        // Don't use into() here, because we would need to move out of
-        // self. Same safety consideration. Immediately drop the created
-        // CString, which takes care of freeing the wrapped string.
-        unsafe { CString::from_raw(self.0) };
-    }
-}
-
-#[cfg(test)]
-mod test {
-    use std::mem;
-    use super::*;
-
-    use libc;
-
-    /// Ensures we're not adding overhead by using RustString.
-    #[test]
-    fn size_of() {
-        assert_eq!(mem::size_of::<*mut libc::c_char>(),
-                   mem::size_of::<RustString>())
-    }
-}

+ 0 - 37
src/rust/tor_util/tests/rust_string.rs

@@ -1,37 +0,0 @@
-extern crate tor_util;
-extern crate libc;
-
-use std::ffi::CString;
-use tor_util::RustString;
-
-#[test]
-fn rust_string_conversions_preserve_c_string() {
-    let s = CString::new("asdf foo").unwrap();
-    let r = RustString::from(s.clone());
-    let r2 = RustString::from(s.clone());
-    let c = r2.as_ptr();
-    assert_eq!(unsafe { libc::strlen(c) }, 8);
-    let c_str = r.into();
-    assert_eq!(s, c_str);
-}
-
-#[test]
-fn empty_string() {
-    let s = CString::new("").unwrap();
-    let r = RustString::from(s.clone());
-    let c = r.as_ptr();
-    assert_eq!(unsafe { libc::strlen(c) }, 0);
-    let c_str = r.into();
-    assert_eq!(s, c_str);
-}
-
-#[test]
-fn c_string_with_unicode() {
-    // The euro sign is three bytes
-    let s = CString::new("asd€asd").unwrap();
-    let r = RustString::from(s.clone());
-    let c = r.as_ptr();
-    assert_eq!(unsafe { libc::strlen(c) }, 9);
-    let c_str = r.into();
-    assert_eq!(s, c_str);
-}

+ 0 - 1
src/test/include.am

@@ -150,7 +150,6 @@ src_test_test_SOURCES = \
 	src/test/test_routerkeys.c \
 	src/test/test_routerkeys.c \
 	src/test/test_routerlist.c \
 	src/test/test_routerlist.c \
 	src/test/test_routerset.c \
 	src/test/test_routerset.c \
-	src/test/test_rust.c \
 	src/test/test_scheduler.c \
 	src/test/test_scheduler.c \
 	src/test/test_shared_random.c \
 	src/test/test_shared_random.c \
 	src/test/test_socks.c \
 	src/test/test_socks.c \

+ 0 - 1
src/test/test.c

@@ -1227,7 +1227,6 @@ struct testgroup_t testgroups[] = {
   { "routerkeys/", routerkeys_tests },
   { "routerkeys/", routerkeys_tests },
   { "routerlist/", routerlist_tests },
   { "routerlist/", routerlist_tests },
   { "routerset/" , routerset_tests },
   { "routerset/" , routerset_tests },
-  { "rust/", rust_tests },
   { "scheduler/", scheduler_tests },
   { "scheduler/", scheduler_tests },
   { "socks/", socks_tests },
   { "socks/", socks_tests },
   { "shared-random/", sr_tests },
   { "shared-random/", sr_tests },

+ 0 - 1
src/test/test.h

@@ -239,7 +239,6 @@ extern struct testcase_t router_tests[];
 extern struct testcase_t routerkeys_tests[];
 extern struct testcase_t routerkeys_tests[];
 extern struct testcase_t routerlist_tests[];
 extern struct testcase_t routerlist_tests[];
 extern struct testcase_t routerset_tests[];
 extern struct testcase_t routerset_tests[];
-extern struct testcase_t rust_tests[];
 extern struct testcase_t scheduler_tests[];
 extern struct testcase_t scheduler_tests[];
 extern struct testcase_t storagedir_tests[];
 extern struct testcase_t storagedir_tests[];
 extern struct testcase_t socks_tests[];
 extern struct testcase_t socks_tests[];

+ 42 - 1
src/test/test_protover.c

@@ -12,6 +12,13 @@ static void
 test_protover_parse(void *arg)
 test_protover_parse(void *arg)
 {
 {
   (void) arg;
   (void) arg;
+#ifdef HAVE_RUST
+  /** This test is disabled on rust builds, because it only exists to test
+   * internal C functions. */
+  tt_skip();
+ done:
+  ;
+#else
   char *re_encoded = NULL;
   char *re_encoded = NULL;
 
 
   const char *orig = "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900";
   const char *orig = "Foo=1,3 Bar=3 Baz= Quux=9-12,14,15-16,900";
@@ -78,12 +85,18 @@ test_protover_parse(void *arg)
     SMARTLIST_FOREACH(elts, proto_entry_t *, ent, proto_entry_free(ent));
     SMARTLIST_FOREACH(elts, proto_entry_t *, ent, proto_entry_free(ent));
   smartlist_free(elts);
   smartlist_free(elts);
   tor_free(re_encoded);
   tor_free(re_encoded);
+#endif
 }
 }
 
 
 static void
 static void
 test_protover_parse_fail(void *arg)
 test_protover_parse_fail(void *arg)
 {
 {
   (void)arg;
   (void)arg;
+#ifdef HAVE_RUST
+  /** This test is disabled on rust builds, because it only exists to test
+   * internal C functions. */
+  tt_skip();
+#else
   smartlist_t *elts;
   smartlist_t *elts;
 
 
   /* random junk */
   /* random junk */
@@ -109,7 +122,7 @@ test_protover_parse_fail(void *arg)
   /* Broken range */
   /* Broken range */
   elts = parse_protocol_list("Link=1,9-8,3");
   elts = parse_protocol_list("Link=1,9-8,3");
   tt_ptr_op(elts, OP_EQ, NULL);
   tt_ptr_op(elts, OP_EQ, NULL);
-
+#endif
  done:
  done:
   ;
   ;
 }
 }
@@ -182,6 +195,32 @@ test_protover_all_supported(void *arg)
   tor_free(msg);
   tor_free(msg);
 }
 }
 
 
+static void
+test_protover_list_supports_protocol_returns_true(void *arg)
+{
+  (void)arg;
+
+  const char *protocols = "Link=1";
+  int is_supported = protocol_list_supports_protocol(protocols, PRT_LINK, 1);
+  tt_int_op(is_supported, OP_EQ, 1);
+
+ done:
+  ;
+}
+
+static void
+test_protover_list_supports_protocol_for_unsupported_returns_false(void *arg)
+{
+  (void)arg;
+
+  const char *protocols = "Link=1";
+  int is_supported = protocol_list_supports_protocol(protocols, PRT_LINK, 10);
+  tt_int_op(is_supported, OP_EQ, 0);
+
+ done:
+  ;
+}
+
 #define PV_TEST(name, flags)                       \
 #define PV_TEST(name, flags)                       \
   { #name, test_protover_ ##name, (flags), NULL, NULL }
   { #name, test_protover_ ##name, (flags), NULL, NULL }
 
 
@@ -190,6 +229,8 @@ struct testcase_t protover_tests[] = {
   PV_TEST(parse_fail, 0),
   PV_TEST(parse_fail, 0),
   PV_TEST(vote, 0),
   PV_TEST(vote, 0),
   PV_TEST(all_supported, 0),
   PV_TEST(all_supported, 0),
+  PV_TEST(list_supports_protocol_for_unsupported_returns_false, 0),
+  PV_TEST(list_supports_protocol_returns_true, 0),
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 0 - 31
src/test/test_rust.c

@@ -1,31 +0,0 @@
-/* Copyright (c) 2017, The Tor Project, Inc. */
-/* See LICENSE for licensing information */
-
-#include "orconfig.h"
-#include "compat_rust.h"
-#include "test.h"
-#include "util.h"
-
-static void
-test_welcome_string(void *arg)
-{
-  (void)arg;
-  rust_str_t s = rust_welcome_string();
-  const char *c_str = rust_str_get(s);
-  tt_assert(c_str);
-  size_t len = strlen(c_str);
-#ifdef HAVE_RUST
-  tt_assert(len > 0);
-#else
-  tt_assert(len == 0);
-#endif
-
- done:
-  rust_str_free(s);
-}
-
-struct testcase_t rust_tests[] = {
-  { "welcome_string", test_welcome_string, 0, NULL, NULL },
-  END_OF_TESTCASES
-};
-

+ 3 - 2
src/test/test_rust.sh

@@ -1,13 +1,14 @@
 #!/bin/sh
 #!/bin/sh
-# Test all the Rust crates we're using
+# Test all Rust crates
 
 
-crates=tor_util
+crates="protover tor_util smartlist tor_allocate"
 
 
 exitcode=0
 exitcode=0
 
 
 for crate in $crates; do
 for crate in $crates; do
     cd "${abs_top_srcdir:-.}/src/rust/${crate}"
     cd "${abs_top_srcdir:-.}/src/rust/${crate}"
     CARGO_TARGET_DIR="${abs_top_builddir}/src/rust/target" CARGO_HOME="${abs_top_builddir}/src/rust" "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} || exitcode=1
     CARGO_TARGET_DIR="${abs_top_builddir}/src/rust/target" CARGO_HOME="${abs_top_builddir}/src/rust" "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} || exitcode=1
+    cd -
 done
 done
 
 
 exit $exitcode
 exit $exitcode