[tor-commits] [tor/master] reply lines structures

teor at torproject.org teor at torproject.org
Sun Dec 15 22:17:37 UTC 2019


commit 1a68a18093d38f9f5b7ed66c8d42e41a565febe0
Author: Taylor Yu <catalyst at torproject.org>
Date:   Wed Jul 3 23:16:23 2019 -0500

    reply lines structures
    
    Part of #30984.
---
 src/feature/control/control_proto.c | 157 ++++++++++++++++++++++++++++++++++++
 src/feature/control/control_proto.h |  72 +++++++++++++++++
 src/test/test_controller.c          |  71 ++++++++++++++++
 3 files changed, 300 insertions(+)

diff --git a/src/feature/control/control_proto.c b/src/feature/control/control_proto.c
index 5dec87491..4efe735c7 100644
--- a/src/feature/control/control_proto.c
+++ b/src/feature/control/control_proto.c
@@ -22,6 +22,8 @@
 #include "core/or/origin_circuit_st.h"
 #include "core/or/socks_request_st.h"
 #include "feature/control/control_connection_st.h"
+#include "lib/container/smartlist.h"
+#include "lib/encoding/kvline.h"
 
 /** Append a NUL-terminated string <b>s</b> to the end of
  * <b>conn</b>-\>outbuf.
@@ -275,3 +277,158 @@ control_write_data(control_connection_t *conn, const char *data)
   connection_buf_add(esc, esc_len, TO_CONN(conn));
   tor_free(esc);
 }
+
+/** Write a single reply line to @a conn.
+ *
+ * @param conn control connection
+ * @param line control reply line to write
+ * @param lastone true if this is the last reply line of a multi-line reply
+ */
+void
+control_write_reply_line(control_connection_t *conn,
+                         const control_reply_line_t *line, bool lastone)
+{
+  const config_line_t *kvline = line->kvline;
+  char *s = NULL;
+
+  if (strpbrk(kvline->value, "\r\n") != NULL) {
+    /* If a key-value pair needs to be encoded as CmdData, it can be
+       the only key-value pair in that reply line */
+    tor_assert(kvline->next == NULL);
+    control_printf_datareply(conn, line->code, "%s=", kvline->key);
+    control_write_data(conn, kvline->value);
+    return;
+  }
+  s = kvline_encode(kvline, line->flags);
+  if (lastone) {
+    control_write_endreply(conn, line->code, s);
+  } else {
+    control_write_midreply(conn, line->code, s);
+  }
+  tor_free(s);
+}
+
+/** Write a set of reply lines to @a conn.
+ *
+ * @param conn control connection
+ * @param lines smartlist of pointers to control_reply_line_t to write
+ */
+void
+control_write_reply_lines(control_connection_t *conn, smartlist_t *lines)
+{
+  bool lastone = false;
+
+  SMARTLIST_FOREACH_BEGIN(lines, control_reply_line_t *, line) {
+    if (line_sl_idx >= line_sl_len - 1)
+      lastone = true;
+    control_write_reply_line(conn, line, lastone);
+  } SMARTLIST_FOREACH_END(line);
+}
+
+/** Add a single key-value pair as a new reply line to a control reply
+ * line list.
+ *
+ * @param reply smartlist of pointers to control_reply_line_t
+ * @param code numeric control reply code
+ * @param flags kvline encoding flags
+ * @param key key
+ * @param val value
+ */
+void
+control_reply_add_1kv(smartlist_t *reply, int code, int flags,
+                     const char *key, const char *val)
+{
+  control_reply_line_t *line = tor_malloc_zero(sizeof(*line));
+
+  line->code = code;
+  line->flags = flags;
+  config_line_append(&line->kvline, key, val);
+  smartlist_add(reply, line);
+}
+
+/** Append a single key-value pair to last reply line in a control
+ * reply line list.
+ *
+ * @param reply smartlist of pointers to control_reply_line_t
+ * @param key key
+ * @param val value
+ */
+void
+control_reply_append_kv(smartlist_t *reply, const char *key, const char *val)
+{
+  int len = smartlist_len(reply);
+  control_reply_line_t *line;
+
+  tor_assert(len > 0);
+
+  line = smartlist_get(reply, len - 1);
+  config_line_append(&line->kvline, key, val);
+}
+
+/** Add new reply line consisting of the string @a s
+ *
+ * @param reply smartlist of pointers to control_reply_line_t
+ * @param code numeric control reply code
+ * @param s string containing the rest of the reply line
+ */
+void
+control_reply_add_str(smartlist_t *reply, int code, const char *s)
+{
+  control_reply_add_1kv(reply, code, KV_OMIT_KEYS|KV_RAW, "", s);
+}
+
+/** Format a new reply line
+ *
+ * @param reply smartlist of pointers to control_reply_line_t
+ * @param code numeric control reply code
+ * @param fmt format string
+ */
+void
+control_reply_add_printf(smartlist_t *reply, int code, const char *fmt, ...)
+{
+  va_list ap;
+  char *buf = NULL;
+
+  va_start(ap, fmt);
+  (void)tor_vasprintf(&buf, fmt, ap);
+  va_end(ap);
+  control_reply_add_str(reply, code, buf);
+  tor_free(buf);
+}
+
+/** Add a "250 OK" line to a set of control reply lines */
+void
+control_reply_add_done(smartlist_t *reply)
+{
+  control_reply_add_str(reply, 250, "OK");
+}
+
+/** Free a control_reply_line_t.  Don't call this directly; use the
+ * control_reply_line_free() macro instead. */
+void
+control_reply_line_free_(control_reply_line_t *line)
+{
+  if (!line)
+    return;
+  config_free_lines(line->kvline);
+  tor_free_(line);
+}
+
+/** Clear a smartlist of control_reply_line_t.  Doesn't free the
+ * smartlist, but does free each individual line. */
+void
+control_reply_clear(smartlist_t *reply)
+{
+  SMARTLIST_FOREACH(reply, control_reply_line_t *, line,
+                    control_reply_line_free(line));
+  smartlist_clear(reply);
+}
+
+/** Free a smartlist of control_reply_line_t. Don't call this
+ * directly; use the control_reply_free() macro instead. */
+void
+control_reply_free_(smartlist_t *reply)
+{
+  control_reply_clear(reply);
+  smartlist_free_(reply);
+}
diff --git a/src/feature/control/control_proto.h b/src/feature/control/control_proto.h
index 3182f3d41..65c81924f 100644
--- a/src/feature/control/control_proto.h
+++ b/src/feature/control/control_proto.h
@@ -7,11 +7,56 @@
 /**
  * \file control_proto.h
  * \brief Header file for control_proto.c.
+ *
+ * See @ref replylines for details about the key-value abstraction for
+ * generating reply lines.
  **/
 
 #ifndef TOR_CONTROL_PROTO_H
 #define TOR_CONTROL_PROTO_H
 
+#include "lib/encoding/confline.h"
+
+/**
+ * @defgroup replylines Control reply lines
+ * @brief Key-value structures for control reply lines
+ *
+ * Control reply lines are config_line_t key-value structures with
+ * some additional information to help formatting, such as the numeric
+ * result code specified in the control protocol and flags affecting
+ * the way kvline_encode() formats the @a kvline.
+ *
+ * Generally, modules implementing control commands will work with
+ * smartlists of these structures, using functions like
+ * control_reply_add_str() for adding a reply line consisting of a
+ * single string, or control_reply_add_1kv() and
+ * control_reply_append_kv() for composing a line containing one or
+ * more key-value pairs.
+ *
+ * @{
+ */
+/** @brief A reply line for the control protocol.
+ *
+ * This wraps config_line_t with some additional information that's
+ * useful when generating control reply lines.
+ */
+typedef struct control_reply_line_t {
+  int code;                     /**< numeric code */
+  int flags;                    /**< kvline encoding flags */
+  config_line_t *kvline;        /**< kvline */
+} control_reply_line_t;
+
+void control_reply_line_free_(control_reply_line_t *line);
+/**
+ * @brief Free and null a control_reply_line_t
+ *
+ * @param line pointer to control_reply_line_t to free
+ */
+#define control_reply_line_free(line)                   \
+  FREE_AND_NULL(control_reply_line_t,                   \
+                control_reply_line_free_, (line))
+/** @} */
+
 void connection_write_str_to_buf(const char *s, control_connection_t *conn);
 void connection_printf_to_buf(control_connection_t *conn,
                                      const char *format, ...)
@@ -45,4 +90,31 @@ void control_printf_datareply(control_connection_t *conn, int code,
   CHECK_PRINTF(3, 4);
 void control_write_data(control_connection_t *conn, const char *data);
 
+/** @addtogroup replylines
+ * @{
+ */
+void control_write_reply_line(control_connection_t *conn,
+                              const control_reply_line_t *line, bool lastone);
+void control_write_reply_lines(control_connection_t *conn, smartlist_t *lines);
+
+void control_reply_add_1kv(smartlist_t *reply, int code, int flags,
+                           const char *key, const char *val);
+void control_reply_append_kv(smartlist_t *reply, const char *key,
+                             const char *val);
+void control_reply_add_str(smartlist_t *reply, int code, const char *s);
+void control_reply_add_printf(smartlist_t *reply, int code,
+                              const char *fmt, ...)
+  CHECK_PRINTF(3, 4);
+void control_reply_add_done(smartlist_t *reply);
+
+void control_reply_clear(smartlist_t *reply);
+void control_reply_free_(smartlist_t *reply);
+
+/** @brief Free and null a smartlist of control_reply_line_t.
+ *
+ * @param r pointer to smartlist_t of control_reply_line_t to free */
+#define control_reply_free(r) \
+  FREE_AND_NULL(smartlist_t, control_reply_free_, (r))
+/** @} */
+
 #endif /* !defined(TOR_CONTROL_PROTO_H) */
diff --git a/src/test/test_controller.c b/src/test/test_controller.c
index d07ec5d0f..eb68e4847 100644
--- a/src/test/test_controller.c
+++ b/src/test/test_controller.c
@@ -1957,6 +1957,76 @@ test_getinfo_md_all(void *arg)
   return;
 }
 
+static smartlist_t *reply_strs;
+
+static void
+mock_control_write_reply_list(control_connection_t *conn, int code, int c,
+                              const char *s)
+{
+  (void)conn;
+  /* To make matching easier, don't append "\r\n" */
+  smartlist_add_asprintf(reply_strs, "%03d%c%s", code, c, s);
+}
+
+static void
+test_control_reply(void *arg)
+{
+  (void)arg;
+  smartlist_t *lines = smartlist_new();
+
+  MOCK(control_write_reply, mock_control_write_reply);
+
+  tor_free(reply_str);
+  control_reply_clear(lines);
+  control_reply_add_str(lines, 250, "FOO");
+  control_write_reply_lines(NULL, lines);
+  tt_str_op(reply_str, OP_EQ, "FOO");
+
+  tor_free(reply_str);
+  control_reply_clear(lines);
+  control_reply_add_done(lines);
+  control_write_reply_lines(NULL, lines);
+  tt_str_op(reply_str, OP_EQ, "OK");
+
+  tor_free(reply_str);
+  control_reply_clear(lines);
+  UNMOCK(control_write_reply);
+  MOCK(control_write_reply, mock_control_write_reply_list);
+  reply_strs = smartlist_new();
+  control_reply_add_1kv(lines, 250, 0, "A", "B");
+  control_reply_add_1kv(lines, 250, 0, "C", "D");
+  control_write_reply_lines(NULL, lines);
+  tt_int_op(smartlist_len(reply_strs), OP_EQ, 2);
+  tt_str_op((char *)smartlist_get(reply_strs, 0), OP_EQ, "250-A=B");
+  tt_str_op((char *)smartlist_get(reply_strs, 1), OP_EQ, "250 C=D");
+
+  control_reply_clear(lines);
+  SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+  smartlist_clear(reply_strs);
+  control_reply_add_printf(lines, 250, "PROTOCOLINFO %d", 1);
+  control_reply_add_1kv(lines, 250, KV_OMIT_VALS|KV_RAW, "AUTH", "");
+  control_reply_append_kv(lines, "METHODS", "COOKIE");
+  control_reply_append_kv(lines, "COOKIEFILE", escaped("/tmp/cookie"));
+  control_reply_add_done(lines);
+  control_write_reply_lines(NULL, lines);
+  tt_int_op(smartlist_len(reply_strs), OP_EQ, 3);
+  tt_str_op((char *)smartlist_get(reply_strs, 0),
+            OP_EQ, "250-PROTOCOLINFO 1");
+  tt_str_op((char *)smartlist_get(reply_strs, 1),
+            OP_EQ, "250-AUTH METHODS=COOKIE COOKIEFILE=\"/tmp/cookie\"");
+  tt_str_op((char *)smartlist_get(reply_strs, 2),
+            OP_EQ, "250 OK");
+
+ done:
+  UNMOCK(control_write_reply);
+  tor_free(reply_str);
+  control_reply_free(lines);
+  if (reply_strs)
+    SMARTLIST_FOREACH(reply_strs, char *, p, tor_free(p));
+  smartlist_free(reply_strs);
+  return;
+}
+
 #ifndef COCCI
 #define PARSER_TEST(type)                                             \
   { "parse/" #type, test_controller_parse_cmd, 0, &passthrough_setup, \
@@ -1989,5 +2059,6 @@ struct testcase_t controller_tests[] = {
   { "download_status_bridge", test_download_status_bridge, 0, NULL, NULL },
   { "current_time", test_current_time, 0, NULL, NULL },
   { "getinfo_md_all", test_getinfo_md_all, 0, NULL, NULL },
+  { "control_reply", test_control_reply, 0, NULL, NULL },
   END_OF_TESTCASES
 };





More information about the tor-commits mailing list