packet: support Retry with older versions too

I realized that until other implementations update to draft-29 we will
fail the Retry tests, so we might as well do this now.

It's mostly just a matter of propagating the version from the public API
exposed to applications, down to where the retry integrity tag is
generated.
diff --git a/examples/http3-server.c b/examples/http3-server.c
index fbef6f5..9ca1a7f 100644
--- a/examples/http3-server.c
+++ b/examples/http3-server.c
@@ -290,7 +290,7 @@
                                                dcid, dcid_len,
                                                dcid, dcid_len,
                                                token, token_len,
-                                               out, sizeof(out));
+                                               version, out, sizeof(out));
 
                 if (written < 0) {
                     fprintf(stderr, "failed to create retry packet: %zd\n",
diff --git a/examples/http3-server.rs b/examples/http3-server.rs
index 194ca00..2cd0e62 100644
--- a/examples/http3-server.rs
+++ b/examples/http3-server.rs
@@ -219,9 +219,15 @@
                     let new_token = mint_token(&hdr, &src);
 
                     let len = quiche::retry(
-                        &hdr.scid, &hdr.dcid, &scid, &new_token, &mut out,
+                        &hdr.scid,
+                        &hdr.dcid,
+                        &scid,
+                        &new_token,
+                        hdr.version,
+                        &mut out,
                     )
                     .unwrap();
+
                     let out = &out[..len];
 
                     if let Err(e) = socket.send_to(out, &src) {
diff --git a/examples/server.c b/examples/server.c
index 7068824..71a52d7 100644
--- a/examples/server.c
+++ b/examples/server.c
@@ -278,7 +278,7 @@
                                                dcid, dcid_len,
                                                dcid, dcid_len,
                                                token, token_len,
-                                               out, sizeof(out));
+                                               version, out, sizeof(out));
 
                 if (written < 0) {
                     fprintf(stderr, "failed to create retry packet: %zd\n",
diff --git a/examples/server.rs b/examples/server.rs
index abe16ca..8213d95 100644
--- a/examples/server.rs
+++ b/examples/server.rs
@@ -213,7 +213,12 @@
                     let new_token = mint_token(&hdr, &src);
 
                     let len = quiche::retry(
-                        &hdr.scid, &hdr.dcid, &scid, &new_token, &mut out,
+                        &hdr.scid,
+                        &hdr.dcid,
+                        &scid,
+                        &new_token,
+                        hdr.version,
+                        &mut out,
                     )
                     .unwrap();
 
diff --git a/include/quiche.h b/include/quiche.h
index 0eb3117..b63fc2e 100644
--- a/include/quiche.h
+++ b/include/quiche.h
@@ -216,7 +216,7 @@
                      const uint8_t *dcid, size_t dcid_len,
                      const uint8_t *new_scid, size_t new_scid_len,
                      const uint8_t *token, size_t token_len,
-                     uint8_t *out, size_t out_len);
+                     uint32_t version, uint8_t *out, size_t out_len);
 
 // Returns true if the given protocol version is supported.
 bool quiche_version_is_supported(uint32_t version);
diff --git a/src/ffi.rs b/src/ffi.rs
index 42376f9..362c6f7 100644
--- a/src/ffi.rs
+++ b/src/ffi.rs
@@ -376,7 +376,7 @@
 pub extern fn quiche_retry(
     scid: *const u8, scid_len: size_t, dcid: *const u8, dcid_len: size_t,
     new_scid: *const u8, new_scid_len: size_t, token: *const u8,
-    token_len: size_t, out: *mut u8, out_len: size_t,
+    token_len: size_t, version: u32, out: *mut u8, out_len: size_t,
 ) -> ssize_t {
     let scid = unsafe { slice::from_raw_parts(scid, scid_len) };
     let dcid = unsafe { slice::from_raw_parts(dcid, dcid_len) };
@@ -384,7 +384,7 @@
     let token = unsafe { slice::from_raw_parts(token, token_len) };
     let out = unsafe { slice::from_raw_parts_mut(out, out_len) };
 
-    match retry(scid, dcid, new_scid, token, out) {
+    match retry(scid, dcid, new_scid, token, version, out) {
         Ok(v) => v as ssize_t,
 
         Err(e) => e.to_c(),
diff --git a/src/lib.rs b/src/lib.rs
index 84177ef..28c8646 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1004,7 +1004,7 @@
 ///     let new_token = mint_token(&hdr, &src);
 ///
 ///     let len = quiche::retry(
-///         &hdr.scid, &hdr.dcid, &scid, &new_token, &mut out,
+///         &hdr.scid, &hdr.dcid, &scid, &new_token, hdr.version, &mut out,
 ///     )?;
 ///
 ///     socket.send_to(&out[..len], &src).unwrap();
@@ -1023,9 +1023,10 @@
 /// # Ok::<(), quiche::Error>(())
 /// ```
 pub fn retry(
-    scid: &[u8], dcid: &[u8], new_scid: &[u8], token: &[u8], out: &mut [u8],
+    scid: &[u8], dcid: &[u8], new_scid: &[u8], token: &[u8], version: u32,
+    out: &mut [u8],
 ) -> Result<usize> {
-    packet::retry(scid, dcid, new_scid, token, out)
+    packet::retry(scid, dcid, new_scid, token, version, out)
 }
 
 /// Returns true if the given protocol version is supported.
@@ -1497,7 +1498,9 @@
             }
 
             // Check if Retry packet is valid.
-            if packet::verify_retry_integrity(&b, &self.dcid).is_err() {
+            if packet::verify_retry_integrity(&b, &self.dcid, self.version)
+                .is_err()
+            {
                 return Err(Error::Done);
             }
 
@@ -6021,8 +6024,15 @@
 
         let token = b"quiche test retry token";
 
-        len =
-            packet::retry(&hdr.scid, &hdr.dcid, &scid, token, &mut buf).unwrap();
+        len = packet::retry(
+            &hdr.scid,
+            &hdr.dcid,
+            &scid,
+            token,
+            hdr.version,
+            &mut buf,
+        )
+        .unwrap();
 
         // Client receives Retry and sends new Initial.
         assert_eq!(pipe.client.recv(&mut buf[..len]), Ok(len));
@@ -6071,8 +6081,15 @@
 
         let token = b"quiche test retry token";
 
-        len =
-            packet::retry(&hdr.scid, &hdr.dcid, &scid, token, &mut buf).unwrap();
+        len = packet::retry(
+            &hdr.scid,
+            &hdr.dcid,
+            &scid,
+            token,
+            hdr.version,
+            &mut buf,
+        )
+        .unwrap();
 
         // Client receives Retry and sends new Initial.
         assert_eq!(pipe.client.recv(&mut buf[..len]), Ok(len));
@@ -6119,8 +6136,15 @@
 
         let token = b"quiche test retry token";
 
-        len =
-            packet::retry(&hdr.scid, &hdr.dcid, &scid, token, &mut buf).unwrap();
+        len = packet::retry(
+            &hdr.scid,
+            &hdr.dcid,
+            &scid,
+            token,
+            hdr.version,
+            &mut buf,
+        )
+        .unwrap();
 
         // Client receives Retry and sends new Initial.
         assert_eq!(pipe.client.recv(&mut buf[..len]), Ok(len));
diff --git a/src/packet.rs b/src/packet.rs
index 3ef3661..e21bc2d 100644
--- a/src/packet.rs
+++ b/src/packet.rs
@@ -596,13 +596,18 @@
 }
 
 pub fn retry(
-    scid: &[u8], dcid: &[u8], new_scid: &[u8], token: &[u8], out: &mut [u8],
+    scid: &[u8], dcid: &[u8], new_scid: &[u8], token: &[u8], version: u32,
+    out: &mut [u8],
 ) -> Result<usize> {
     let mut b = octets::OctetsMut::with_slice(out);
 
+    if !crate::version_is_supported(version) {
+        return Err(Error::UnknownVersion);
+    }
+
     let hdr = Header {
         ty: Type::Retry,
-        version: crate::PROTOCOL_VERSION,
+        version,
         dcid: scid.to_vec(),
         scid: new_scid.to_vec(),
         pkt_num: 0,
@@ -614,15 +619,17 @@
 
     hdr.to_bytes(&mut b)?;
 
-    let tag = compute_retry_integrity_tag(&b, dcid)?;
+    let tag = compute_retry_integrity_tag(&b, dcid, version)?;
 
     b.put_bytes(tag.as_ref())?;
 
     Ok(b.off())
 }
 
-pub fn verify_retry_integrity(b: &octets::OctetsMut, odcid: &[u8]) -> Result<()> {
-    let tag = compute_retry_integrity_tag(b, odcid)?;
+pub fn verify_retry_integrity(
+    b: &octets::OctetsMut, odcid: &[u8], version: u32,
+) -> Result<()> {
+    let tag = compute_retry_integrity_tag(b, odcid, version)?;
 
     ring::constant_time::verify_slices_are_equal(
         &b.as_ref()[..aead::AES_128_GCM.tag_len()],
@@ -634,7 +641,7 @@
 }
 
 fn compute_retry_integrity_tag(
-    b: &octets::OctetsMut, odcid: &[u8],
+    b: &octets::OctetsMut, odcid: &[u8], version: u32,
 ) -> Result<aead::Tag> {
     const RETRY_INTEGRITY_KEY: [u8; 16] = [
         0xcc, 0xce, 0x18, 0x7e, 0xd0, 0x9a, 0x09, 0xd0, 0x57, 0x28, 0x15, 0x5a,
@@ -645,6 +652,22 @@
         0xe5, 0x49, 0x30, 0xf9, 0x7f, 0x21, 0x36, 0xf0, 0x53, 0x0a, 0x8c, 0x1c,
     ];
 
+    const RETRY_INTEGRITY_KEY_OLD: [u8; 16] = [
+        0x4d, 0x32, 0xec, 0xdb, 0x2a, 0x21, 0x33, 0xc8, 0x41, 0xe4, 0x04, 0x3d,
+        0xf2, 0x7d, 0x44, 0x30,
+    ];
+
+    const RETRY_INTEGRITY_NONCE_OLD: [u8; aead::NONCE_LEN] = [
+        0x4d, 0x16, 0x11, 0xd0, 0x55, 0x13, 0xa5, 0x52, 0xc5, 0x87, 0xd5, 0x75,
+    ];
+
+    let (key, nonce) = match version {
+        crate::PROTOCOL_VERSION_DRAFT27 | crate::PROTOCOL_VERSION_DRAFT28 =>
+            (&RETRY_INTEGRITY_KEY_OLD, RETRY_INTEGRITY_NONCE_OLD),
+
+        _ => (&RETRY_INTEGRITY_KEY, RETRY_INTEGRITY_NONCE),
+    };
+
     let hdr_len = b.off();
 
     let mut pseudo = vec![0; 1 + odcid.len() + hdr_len];
@@ -656,11 +679,11 @@
     pb.put_bytes(&b.buf()[..hdr_len])?;
 
     let key = aead::LessSafeKey::new(
-        aead::UnboundKey::new(&aead::AES_128_GCM, &RETRY_INTEGRITY_KEY)
+        aead::UnboundKey::new(&aead::AES_128_GCM, key)
             .map_err(|_| Error::CryptoFail)?,
     );
 
-    let nonce = aead::Nonce::assume_unique_for_key(RETRY_INTEGRITY_NONCE);
+    let nonce = aead::Nonce::assume_unique_for_key(nonce);
 
     let aad = aead::Aad::from(&pseudo);
 
diff --git a/tools/apps/src/bin/quiche-server.rs b/tools/apps/src/bin/quiche-server.rs
index 49f944c..54d6dab 100644
--- a/tools/apps/src/bin/quiche-server.rs
+++ b/tools/apps/src/bin/quiche-server.rs
@@ -266,9 +266,15 @@
                         let new_token = mint_token(&hdr, &src);
 
                         let len = quiche::retry(
-                            &hdr.scid, &hdr.dcid, &scid, &new_token, &mut out,
+                            &hdr.scid,
+                            &hdr.dcid,
+                            &scid,
+                            &new_token,
+                            hdr.version,
+                            &mut out,
                         )
                         .unwrap();
+
                         let out = &out[..len];
 
                         if let Err(e) = socket.send_to(out, &src) {