darwinssl: Fix send glitchiness with data > 32 or so KB
authorNick Zitzmann <nickzman@gmail.com>
Tue, 12 Feb 2013 20:02:36 +0000 (13:02 -0700)
committerNick Zitzmann <nickzman@gmail.com>
Tue, 12 Feb 2013 20:02:36 +0000 (13:02 -0700)
An ambiguity in the SSLWrite() documentation lead to a bad inference in the
code where we assumed SSLWrite() returned the amount of bytes written to
the socket, when that is not actually true; it returns the amount of data
that is buffered for writing to the socket if it returns errSSLWouldBlock.
Now darwinssl_send() returns CURLE_AGAIN if data is buffered but not written.

Reference URL: http://curl.haxx.se/mail/lib-2013-02/0145.html

lib/curl_darwinssl.c
lib/urldata.h

index d660deb..523370e 100644 (file)
@@ -739,6 +739,7 @@ static CURLcode darwinssl_connect_step1(struct connectdata *conn,
     return CURLE_OUT_OF_MEMORY;
   }
 #endif /* defined(__MAC_10_8) || defined(__IPHONE_5_0) */
+  connssl->ssl_write_buffered_length = 0UL; /* reset buffered write length */
 
   /* check to see if we've been told to use an explicit SSL/TLS version */
 #if defined(__MAC_10_8) || defined(__IPHONE_5_0)
@@ -1424,22 +1425,58 @@ static ssize_t darwinssl_send(struct connectdata *conn,
   /*struct SessionHandle *data = conn->data;*/
   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
   size_t processed = 0UL;
-  OSStatus err = SSLWrite(connssl->ssl_ctx, mem, len, &processed);
+  OSStatus err;
 
-  if(err != noErr) {
+  /* The SSLWrite() function works a little differently than expected. The
+     fourth argument (processed) is currently documented in Apple's
+     documentation as: "On return, the length, in bytes, of the data actually
+     written."
+
+     Now, one could interpret that as "written to the socket," but actually,
+     it returns the amount of data that was written to a buffer internal to
+     the SSLContextRef instead. So it's possible for SSLWrite() to return
+     errSSLWouldBlock and a number of bytes "written" because those bytes were
+     encrypted and written to a buffer, not to the socket.
+
+     So if this happens, then we need to keep calling SSLWrite() over and
+     over again with no new data until it quits returning errSSLWouldBlock. */
+
+  /* Do we have buffered data to write from the last time we were called? */
+  if(connssl->ssl_write_buffered_length) {
+    /* Write the buffered data: */
+    err = SSLWrite(connssl->ssl_ctx, NULL, 0UL, &processed);
     switch (err) {
-      case errSSLWouldBlock:  /* return how much we sent (if anything) */
-        if(processed)
-          return (ssize_t)processed;
-        *curlcode = CURLE_AGAIN;
-        return -1;
+      case noErr:
+        /* processed is always going to be 0 because we didn't write to
+           the buffer, so return how much was written to the socket */
+        processed = connssl->ssl_write_buffered_length;
+        connssl->ssl_write_buffered_length = 0UL;
         break;
-
+      case errSSLWouldBlock: /* argh, try again */
+        *curlcode = CURLE_AGAIN;
+        return -1L;
       default:
-        failf(conn->data, "SSLWrite() return error %d", err);
+        failf(conn->data, "SSLWrite() returned error %d", err);
         *curlcode = CURLE_SEND_ERROR;
-        return -1;
-        break;
+        return -1L;
+    }
+  }
+  else {
+    /* We've got new data to write: */
+    err = SSLWrite(connssl->ssl_ctx, mem, len, &processed);
+    if(err != noErr) {
+      switch (err) {
+        case errSSLWouldBlock:
+          /* Data was buffered but not sent, we have to tell the caller
+             to try sending again, and remember how much was buffered */
+          connssl->ssl_write_buffered_length = len;
+          *curlcode = CURLE_AGAIN;
+          return -1L;
+        default:
+          failf(conn->data, "SSLWrite() returned error %d", err);
+          *curlcode = CURLE_SEND_ERROR;
+          return -1L;
+      }
     }
   }
   return (ssize_t)processed;
@@ -1462,18 +1499,18 @@ static ssize_t darwinssl_recv(struct connectdata *conn,
         if(processed)
           return (ssize_t)processed;
         *curlcode = CURLE_AGAIN;
-        return -1;
+        return -1L;
         break;
 
       case errSSLClosedGraceful: /* they're done; fail gracefully */
         *curlcode = CURLE_OK;
-        return -1;
+        return -1L;
         break;
 
       default:
         failf(conn->data, "SSLRead() return error %d", err);
         *curlcode = CURLE_RECV_ERROR;
-        return -1;
+        return -1L;
         break;
     }
   }
index 7a275da..4849dae 100644 (file)
@@ -326,6 +326,7 @@ struct ssl_connect_data {
   curl_socket_t ssl_sockfd;
   ssl_connect_state connecting_state;
   bool ssl_direction; /* true if writing, false if reading */
+  size_t ssl_write_buffered_length;
 #endif /* USE_DARWINSSL */
 };