Merge pull request #345 from h2o/kazuho/server-cipher-preference

knob to select cipher-suite based on server's preference
diff --git a/include/picotls.h b/include/picotls.h
index a3050c1..b7f13d2 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -736,6 +736,10 @@
      */
     unsigned use_raw_public_keys : 1;
     /**
+     * boolean indicating if the cipher-suite should be chosen based on server's preference
+     */
+    unsigned server_cipher_preference : 1;
+    /**
      *
      */
     ptls_encrypt_ticket_t *encrypt_ticket;
diff --git a/lib/picotls.c b/lib/picotls.c
index 210d530..aef5530 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -1571,24 +1571,37 @@
 }
 
 static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **candidates, const uint8_t *src,
-                         const uint8_t *const end)
+                         const uint8_t *const end, int server_preference)
 {
+    size_t found_index = SIZE_MAX;
     int ret;
 
     while (src != end) {
         uint16_t id;
         if ((ret = ptls_decode16(&id, &src, end)) != 0)
             goto Exit;
-        ptls_cipher_suite_t **c = candidates;
-        for (; *c != NULL; ++c) {
-            if ((*c)->id == id) {
-                *selected = *c;
-                return 0;
+        for (size_t i = 0; candidates[i] != NULL; ++i) {
+            if (candidates[i]->id == id) {
+                if (server_preference) {
+                    /* preserve smallest matching index, and proceed to the next input */
+                    if (i < found_index) {
+                        found_index = i;
+                        break;
+                    }
+                } else {
+                    /* return the pointer matching to the first input that can be used */
+                    *selected = candidates[i];
+                    goto Exit;
+                }
             }
         }
     }
-
-    ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+    if (found_index != SIZE_MAX) {
+        *selected = candidates[found_index];
+        ret = 0;
+    } else {
+        ret = PTLS_ALERT_HANDSHAKE_FAILURE;
+    }
 
 Exit:
     return ret;
@@ -1731,7 +1744,7 @@
     });
     /* cipher-suite */
     ptls_decode_open_block(src, end, 2, {
-        if ((ret = select_cipher(selected_cipher, ctx->cipher_suites, src, end)) != 0)
+        if ((ret = select_cipher(selected_cipher, ctx->cipher_suites, src, end, ctx->server_cipher_preference)) != 0)
             goto Exit;
         src = end;
     });
@@ -3816,7 +3829,7 @@
     { /* select (or check) cipher-suite, create key_schedule */
         ptls_cipher_suite_t *cs;
         if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base,
-                                 ch->cipher_suites.base + ch->cipher_suites.len)) != 0)
+                                 ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference)) != 0)
             goto Exit;
         if (!is_second_flight) {
             tls->cipher_suite = cs;
diff --git a/t/picotls.c b/t/picotls.c
index 74e24eb..5390a66 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -44,6 +44,37 @@
     ok(ptls_server_name_is_ipaddr("2001:db8::2:1"));
 }
 
+static void test_select_cipher(void)
+{
+#define C(x) ((x) >> 8) & 0xff, (x)&0xff
+
+    ptls_cipher_suite_t *selected,
+        *candidates[] = {&ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL};
+
+    {
+        static const uint8_t input[] = {};
+        ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0) == PTLS_ALERT_HANDSHAKE_FAILURE);
+    }
+
+    {
+        static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256), C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)};
+        ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0) == 0);
+        ok(selected == &ptls_minicrypto_aes128gcmsha256);
+        ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1) == 0);
+        ok(selected == &ptls_minicrypto_chacha20poly1305sha256);
+    }
+
+    {
+        static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_AES_256_GCM_SHA384), C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256)};
+        ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0) == 0);
+        ok(selected == &ptls_minicrypto_aes128gcmsha256);
+        ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1) == 0);
+        ok(selected == &ptls_minicrypto_aes128gcmsha256);
+    }
+
+#undef C
+}
+
 ptls_context_t *ctx, *ctx_peer;
 ptls_verify_certificate_t *verify_certificate;
 struct st_ptls_ffx_test_variants_t ffx_variants[7];
@@ -1662,6 +1693,7 @@
 void test_picotls(void)
 {
     subtest("is_ipaddr", test_is_ipaddr);
+    subtest("select_cypher", test_select_cipher);
     subtest("sha256", test_sha256);
     subtest("sha384", test_sha384);
     subtest("hmac-sha256", test_hmac_sha256);