| Index: net/base/ssl_client_socket_mac.cc
|
| ===================================================================
|
| --- net/base/ssl_client_socket_mac.cc (revision 18948)
|
| +++ net/base/ssl_client_socket_mac.cc (working copy)
|
| @@ -1,762 +0,0 @@
|
| -// Copyright (c) 2008-2009 The Chromium Authors. All rights reserved.
|
| -// Use of this source code is governed by a BSD-style license that can be
|
| -// found in the LICENSE file.
|
| -
|
| -#include "net/base/ssl_client_socket_mac.h"
|
| -
|
| -#include "base/singleton.h"
|
| -#include "base/string_util.h"
|
| -#include "net/base/io_buffer.h"
|
| -#include "net/base/net_errors.h"
|
| -#include "net/base/ssl_info.h"
|
| -
|
| -// Welcome to Mac SSL. We've been waiting for you.
|
| -//
|
| -// The Mac SSL implementation is, like the Windows and NSS implementations, a
|
| -// giant state machine. This design constraint is due to the asynchronous nature
|
| -// of our underlying transport mechanism. We can call down to read/write on the
|
| -// network, but what happens is that either it completes immediately or returns
|
| -// saying that we'll get a callback sometime in the future. In that case, we
|
| -// have to return to our caller but pick up where we left off when we
|
| -// resume. Thus the fun.
|
| -//
|
| -// On Windows, we use Security Contexts, which are driven by us. We fetch data
|
| -// from the network, we call the context to decrypt the data, and so on. On the
|
| -// Mac, however, we provide Secure Transport with callbacks to get data from the
|
| -// network, and it calls us back to fetch the data from the network for
|
| -// it. Therefore, there are different sets of states in our respective state
|
| -// machines, fewer on the Mac because Secure Transport keeps a lot of its own
|
| -// state. The discussion about what each of the states means lives in comments
|
| -// in the DoLoop() function.
|
| -//
|
| -// Secure Transport is designed for use by either blocking or non-blocking
|
| -// network I/O. If, for example, you called SSLRead() to fetch data, Secure
|
| -// Transport will, unless it has some cached data, issue a read to your network
|
| -// callback read function to fetch it some more encrypted data. It's expecting
|
| -// one of two things. If your function is hooked up to a blocking source, then
|
| -// it'll block pending receipt of the data from the other end. That's fine, as
|
| -// when you return with the data, Secure Transport will do its thing. On the
|
| -// other hand, suppose that your socket is non-blocking and tells your function
|
| -// that it would block. Then you let Secure Transport know, and it'll tell the
|
| -// original caller that it would have blocked and that they need to call it
|
| -// "later."
|
| -//
|
| -// When's "later," though? We have fully-asynchronous networking, so we get a
|
| -// callback when our data's ready. But Secure Transport has no way for us to
|
| -// tell it that data has arrived, so we must re-execute the call that triggered
|
| -// the I/O (we rely on our state machine to do this). When we do so Secure
|
| -// Transport will ask once again for the data. Chances are that it'll be the
|
| -// same request as the previous time, but that's not actually guaranteed. But as
|
| -// long as we buffer what we have and keep track of where we were, it works
|
| -// quite well.
|
| -//
|
| -// Except for network writes. They shoot this plan straight to hell.
|
| -//
|
| -// Faking a blocking connection with an asynchronous connection (theoretically
|
| -// more powerful) simply doesn't work for writing. Suppose that Secure Transport
|
| -// requests a write of data to the network. With blocking I/O, we'd just block
|
| -// until the write completed, and with non-blocking I/O we'd know how many bytes
|
| -// we wrote before we would have blocked. But with the asynchronous I/O, the
|
| -// transport underneath us can tell us that it'll let us know sometime "later"
|
| -// whether or not things succeeded, and how many bytes were written. What do we
|
| -// return to Secure Transport? We can't return a byte count, but we can't return
|
| -// "later" as we're not guaranteed to be called in the future with the same data
|
| -// to write.
|
| -//
|
| -// So, like in any good relationship, we're forced to lie. Whenever Secure
|
| -// Transport asks for data to be written, we take it all and lie about it always
|
| -// being written. We spin in a loop (see SSLWriteCallback() and
|
| -// OnWriteComplete()) independent of the main state machine writing the data to
|
| -// the network, and get the data out. The main consequence of this independence
|
| -// from the state machine is that we require a full-duplex transport underneath
|
| -// us since we can't use it to keep our reading and writing
|
| -// straight. Fortunately, the NSS implementation also has this issue to deal
|
| -// with, so we share the same Libevent-based full-duplex TCP socket.
|
| -//
|
| -// A side comment on return values might be in order. Those who haven't taken
|
| -// the time to read the documentation (ahem, header comments) in our various
|
| -// files might be a bit surprised to see result values being treated as both
|
| -// lengths and errors. Like Shimmer, they are both. In both the case of
|
| -// immediate results as well as results returned in callbacks, a negative return
|
| -// value indicates an error, a zero return value indicates end-of-stream (for
|
| -// reads), and a positive return value indicates the number of bytes read or
|
| -// written. Thus, many functions start off with |if (result < 0) return
|
| -// result;|. That gets the error condition out of the way, and from that point
|
| -// forward the result can be treated as a length.
|
| -
|
| -namespace net {
|
| -
|
| -namespace {
|
| -
|
| -int NetErrorFromOSStatus(OSStatus status) {
|
| - switch (status) {
|
| - case errSSLWouldBlock:
|
| - return ERR_IO_PENDING;
|
| - case errSSLIllegalParam:
|
| - case errSSLBadCipherSuite:
|
| - case errSSLBadConfiguration:
|
| - return ERR_INVALID_ARGUMENT;
|
| - case errSSLClosedNoNotify:
|
| - return ERR_CONNECTION_RESET;
|
| - case errSSLConnectionRefused:
|
| - return ERR_CONNECTION_REFUSED;
|
| - case errSSLClosedAbort:
|
| - return ERR_CONNECTION_ABORTED;
|
| - case errSSLInternal:
|
| - case errSSLCrypto:
|
| - case errSSLFatalAlert:
|
| - case errSSLProtocol:
|
| - return ERR_SSL_PROTOCOL_ERROR;
|
| - case errSSLHostNameMismatch:
|
| - return ERR_CERT_COMMON_NAME_INVALID;
|
| - case errSSLCertExpired:
|
| - case errSSLCertNotYetValid:
|
| - return ERR_CERT_DATE_INVALID;
|
| - case errSSLNoRootCert:
|
| - case errSSLUnknownRootCert:
|
| - return ERR_CERT_AUTHORITY_INVALID;
|
| - case errSSLXCertChainInvalid:
|
| - case errSSLBadCert:
|
| - return ERR_CERT_INVALID;
|
| - case errSSLPeerCertRevoked:
|
| - return ERR_CERT_REVOKED;
|
| -
|
| - case errSSLClosedGraceful:
|
| - case noErr:
|
| - return OK;
|
| -
|
| - case errSSLBadRecordMac:
|
| - case errSSLBufferOverflow:
|
| - case errSSLDecryptionFail:
|
| - case errSSLModuleAttach:
|
| - case errSSLNegotiation:
|
| - case errSSLRecordOverflow:
|
| - case errSSLSessionNotFound:
|
| - default:
|
| - LOG(WARNING) << "Unknown error " << status <<
|
| - " mapped to net::ERR_FAILED";
|
| - return ERR_FAILED;
|
| - }
|
| -}
|
| -
|
| -OSStatus OSStatusFromNetError(int net_error) {
|
| - switch (net_error) {
|
| - case ERR_IO_PENDING:
|
| - return errSSLWouldBlock;
|
| - case ERR_INTERNET_DISCONNECTED:
|
| - case ERR_TIMED_OUT:
|
| - case ERR_CONNECTION_ABORTED:
|
| - case ERR_CONNECTION_RESET:
|
| - case ERR_CONNECTION_REFUSED:
|
| - case ERR_ADDRESS_UNREACHABLE:
|
| - case ERR_ADDRESS_INVALID:
|
| - return errSSLClosedAbort;
|
| - case OK:
|
| - return noErr;
|
| - default:
|
| - LOG(WARNING) << "Unknown error " << net_error <<
|
| - " mapped to errSSLIllegalParam";
|
| - return errSSLIllegalParam;
|
| - }
|
| -}
|
| -
|
| -// Converts from a cipher suite to its key size. If the suite is marked with a
|
| -// **, it's not actually implemented in Secure Transport and won't be returned
|
| -// (but we'll code for it anyway). The reference here is
|
| -// http://www.opensource.apple.com/darwinsource/10.5.5/libsecurity_ssl-32463/lib/cipherSpecs.c
|
| -// Seriously, though, there has to be an API for this, but I can't find one.
|
| -// Anybody?
|
| -int KeySizeOfCipherSuite(SSLCipherSuite suite) {
|
| - switch (suite) {
|
| - // SSL 2 only
|
| -
|
| - case SSL_RSA_WITH_DES_CBC_MD5:
|
| - return 56;
|
| - case SSL_RSA_WITH_3DES_EDE_CBC_MD5:
|
| - return 112;
|
| - case SSL_RSA_WITH_RC2_CBC_MD5:
|
| - case SSL_RSA_WITH_IDEA_CBC_MD5: // **
|
| - return 128;
|
| - case SSL_NO_SUCH_CIPHERSUITE: // **
|
| - return 0;
|
| -
|
| - // SSL 2, 3, TLS
|
| -
|
| - case SSL_NULL_WITH_NULL_NULL:
|
| - case SSL_RSA_WITH_NULL_MD5:
|
| - case SSL_RSA_WITH_NULL_SHA: // **
|
| - case SSL_FORTEZZA_DMS_WITH_NULL_SHA: // **
|
| - return 0;
|
| - case SSL_RSA_EXPORT_WITH_RC4_40_MD5:
|
| - case SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5:
|
| - case SSL_RSA_EXPORT_WITH_DES40_CBC_SHA:
|
| - case SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA: // **
|
| - case SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA: // **
|
| - case SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA:
|
| - case SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA:
|
| - case SSL_DH_anon_EXPORT_WITH_RC4_40_MD5:
|
| - case SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA:
|
| - return 40;
|
| - case SSL_RSA_WITH_DES_CBC_SHA:
|
| - case SSL_DH_DSS_WITH_DES_CBC_SHA: // **
|
| - case SSL_DH_RSA_WITH_DES_CBC_SHA: // **
|
| - case SSL_DHE_DSS_WITH_DES_CBC_SHA:
|
| - case SSL_DHE_RSA_WITH_DES_CBC_SHA:
|
| - case SSL_DH_anon_WITH_DES_CBC_SHA:
|
| - return 56;
|
| - case SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA: // **
|
| - return 80;
|
| - case SSL_RSA_WITH_3DES_EDE_CBC_SHA:
|
| - case SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA: // **
|
| - case SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA: // **
|
| - case SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
|
| - case SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
|
| - case SSL_DH_anon_WITH_3DES_EDE_CBC_SHA:
|
| - return 112;
|
| - case SSL_RSA_WITH_RC4_128_MD5:
|
| - case SSL_RSA_WITH_RC4_128_SHA:
|
| - case SSL_RSA_WITH_IDEA_CBC_SHA: // **
|
| - case SSL_DH_anon_WITH_RC4_128_MD5:
|
| - return 128;
|
| -
|
| - // TLS AES options (see RFC 3268)
|
| -
|
| - case TLS_RSA_WITH_AES_128_CBC_SHA:
|
| - case TLS_DH_DSS_WITH_AES_128_CBC_SHA: // **
|
| - case TLS_DH_RSA_WITH_AES_128_CBC_SHA: // **
|
| - case TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
|
| - case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
|
| - case TLS_DH_anon_WITH_AES_128_CBC_SHA:
|
| - return 128;
|
| - case TLS_RSA_WITH_AES_256_CBC_SHA:
|
| - case TLS_DH_DSS_WITH_AES_256_CBC_SHA: // **
|
| - case TLS_DH_RSA_WITH_AES_256_CBC_SHA: // **
|
| - case TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
|
| - case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
|
| - case TLS_DH_anon_WITH_AES_256_CBC_SHA:
|
| - return 256;
|
| -
|
| - default:
|
| - return -1;
|
| - }
|
| -}
|
| -
|
| -} // namespace
|
| -
|
| -//-----------------------------------------------------------------------------
|
| -
|
| -SSLClientSocketMac::SSLClientSocketMac(ClientSocket* transport_socket,
|
| - const std::string& hostname,
|
| - const SSLConfig& ssl_config)
|
| - : io_callback_(this, &SSLClientSocketMac::OnIOComplete),
|
| - write_callback_(this, &SSLClientSocketMac::OnWriteComplete),
|
| - transport_(transport_socket),
|
| - hostname_(hostname),
|
| - ssl_config_(ssl_config),
|
| - user_callback_(NULL),
|
| - next_state_(STATE_NONE),
|
| - next_io_state_(STATE_NONE),
|
| - server_cert_status_(0),
|
| - completed_handshake_(false),
|
| - ssl_context_(NULL),
|
| - pending_send_error_(OK),
|
| - recv_buffer_head_slop_(0),
|
| - recv_buffer_tail_slop_(0) {
|
| -}
|
| -
|
| -SSLClientSocketMac::~SSLClientSocketMac() {
|
| - Disconnect();
|
| -}
|
| -
|
| -int SSLClientSocketMac::Connect(CompletionCallback* callback) {
|
| - DCHECK(transport_.get());
|
| - DCHECK(next_state_ == STATE_NONE);
|
| - DCHECK(!user_callback_);
|
| -
|
| - OSStatus status = noErr;
|
| -
|
| - status = SSLNewContext(false, &ssl_context_);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetProtocolVersionEnabled(ssl_context_,
|
| - kSSLProtocol2,
|
| - ssl_config_.ssl2_enabled);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetProtocolVersionEnabled(ssl_context_,
|
| - kSSLProtocol3,
|
| - ssl_config_.ssl3_enabled);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetProtocolVersionEnabled(ssl_context_,
|
| - kTLSProtocol1,
|
| - ssl_config_.tls1_enabled);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetIOFuncs(ssl_context_, SSLReadCallback, SSLWriteCallback);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetConnection(ssl_context_, this);
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - status = SSLSetPeerDomainName(ssl_context_, hostname_.c_str(),
|
| - hostname_.length());
|
| - if (status)
|
| - return NetErrorFromOSStatus(status);
|
| -
|
| - next_state_ = STATE_HANDSHAKE;
|
| - int rv = DoLoop(OK);
|
| - if (rv == ERR_IO_PENDING)
|
| - user_callback_ = callback;
|
| - return rv;
|
| -}
|
| -
|
| -void SSLClientSocketMac::Disconnect() {
|
| - completed_handshake_ = false;
|
| -
|
| - if (ssl_context_) {
|
| - SSLClose(ssl_context_);
|
| - SSLDisposeContext(ssl_context_);
|
| - ssl_context_ = NULL;
|
| - }
|
| -
|
| - transport_->Disconnect();
|
| -}
|
| -
|
| -bool SSLClientSocketMac::IsConnected() const {
|
| - // Ideally, we should also check if we have received the close_notify alert
|
| - // message from the server, and return false in that case. We're not doing
|
| - // that, so this function may return a false positive. Since the upper
|
| - // layer (HttpNetworkTransaction) needs to handle a persistent connection
|
| - // closed by the server when we send a request anyway, a false positive in
|
| - // exchange for simpler code is a good trade-off.
|
| - return completed_handshake_ && transport_->IsConnected();
|
| -}
|
| -
|
| -bool SSLClientSocketMac::IsConnectedAndIdle() const {
|
| - // Unlike IsConnected, this method doesn't return a false positive.
|
| - //
|
| - // Strictly speaking, we should check if we have received the close_notify
|
| - // alert message from the server, and return false in that case. Although
|
| - // the close_notify alert message means EOF in the SSL layer, it is just
|
| - // bytes to the transport layer below, so transport_->IsConnectedAndIdle()
|
| - // returns the desired false when we receive close_notify.
|
| - return completed_handshake_ && transport_->IsConnectedAndIdle();
|
| -}
|
| -
|
| -int SSLClientSocketMac::Read(IOBuffer* buf, int buf_len,
|
| - CompletionCallback* callback) {
|
| - DCHECK(completed_handshake_);
|
| - DCHECK(next_state_ == STATE_NONE);
|
| - DCHECK(!user_callback_);
|
| - DCHECK(!user_buf_);
|
| -
|
| - user_buf_ = buf;
|
| - user_buf_len_ = buf_len;
|
| -
|
| - next_state_ = STATE_PAYLOAD_READ;
|
| - int rv = DoLoop(OK);
|
| - if (rv == ERR_IO_PENDING) {
|
| - user_callback_ = callback;
|
| - } else {
|
| - user_buf_ = NULL;
|
| - }
|
| - return rv;
|
| -}
|
| -
|
| -int SSLClientSocketMac::Write(IOBuffer* buf, int buf_len,
|
| - CompletionCallback* callback) {
|
| - DCHECK(completed_handshake_);
|
| - DCHECK(next_state_ == STATE_NONE);
|
| - DCHECK(!user_callback_);
|
| - DCHECK(!user_buf_);
|
| -
|
| - user_buf_ = buf;
|
| - user_buf_len_ = buf_len;
|
| -
|
| - next_state_ = STATE_PAYLOAD_WRITE;
|
| - int rv = DoLoop(OK);
|
| - if (rv == ERR_IO_PENDING) {
|
| - user_callback_ = callback;
|
| - } else {
|
| - user_buf_ = NULL;
|
| - }
|
| - return rv;
|
| -}
|
| -
|
| -void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) {
|
| - ssl_info->Reset();
|
| -
|
| - // set cert
|
| - CFArrayRef certs;
|
| - OSStatus status = SSLCopyPeerCertificates(ssl_context_, &certs);
|
| - if (!status) {
|
| - DCHECK(CFArrayGetCount(certs) > 0);
|
| -
|
| - SecCertificateRef client_cert =
|
| - static_cast<SecCertificateRef>(
|
| - const_cast<void*>(CFArrayGetValueAtIndex(certs, 0)));
|
| - CFRetain(client_cert);
|
| - ssl_info->cert = X509Certificate::CreateFromHandle(
|
| - client_cert, X509Certificate::SOURCE_FROM_NETWORK);
|
| - CFRelease(certs);
|
| - }
|
| -
|
| - // update status
|
| - ssl_info->cert_status = server_cert_status_;
|
| -
|
| - // security info
|
| - SSLCipherSuite suite;
|
| - status = SSLGetNegotiatedCipher(ssl_context_, &suite);
|
| - if (!status)
|
| - ssl_info->security_bits = KeySizeOfCipherSuite(suite);
|
| -}
|
| -
|
| -void SSLClientSocketMac::GetSSLCertRequestInfo(
|
| - SSLCertRequestInfo* cert_request_info) {
|
| - // TODO(wtc): implement this.
|
| -}
|
| -
|
| -void SSLClientSocketMac::DoCallback(int rv) {
|
| - DCHECK(rv != ERR_IO_PENDING);
|
| - DCHECK(user_callback_);
|
| -
|
| - // since Run may result in Read being called, clear user_callback_ up front.
|
| - CompletionCallback* c = user_callback_;
|
| - user_callback_ = NULL;
|
| - user_buf_ = NULL;
|
| - c->Run(rv);
|
| -}
|
| -
|
| -void SSLClientSocketMac::OnIOComplete(int result) {
|
| - if (next_io_state_ != STATE_NONE) {
|
| - State next_state = next_state_;
|
| - next_state_ = next_io_state_;
|
| - next_io_state_ = STATE_NONE;
|
| - result = DoLoop(result);
|
| - next_state_ = next_state;
|
| - }
|
| - if (next_state_ != STATE_NONE) {
|
| - int rv = DoLoop(result);
|
| - if (rv != ERR_IO_PENDING)
|
| - DoCallback(rv);
|
| - }
|
| -}
|
| -
|
| -// This is the main loop driving the state machine. Most calls coming from the
|
| -// outside just set up a few variables and jump into here.
|
| -int SSLClientSocketMac::DoLoop(int last_io_result) {
|
| - DCHECK(next_state_ != STATE_NONE);
|
| - int rv = last_io_result;
|
| - do {
|
| - State state = next_state_;
|
| - next_state_ = STATE_NONE;
|
| - switch (state) {
|
| - case STATE_HANDSHAKE:
|
| - // Do the SSL/TLS handshake.
|
| - rv = DoHandshake();
|
| - break;
|
| - case STATE_READ_COMPLETE:
|
| - // A read off the network is complete; do the paperwork.
|
| - rv = DoReadComplete(rv);
|
| - break;
|
| - case STATE_PAYLOAD_READ:
|
| - // Do a read of data from the network.
|
| - rv = DoPayloadRead();
|
| - break;
|
| - case STATE_PAYLOAD_WRITE:
|
| - // Do a write of data to the network.
|
| - rv = DoPayloadWrite();
|
| - break;
|
| - default:
|
| - rv = ERR_UNEXPECTED;
|
| - NOTREACHED() << "unexpected state";
|
| - break;
|
| - }
|
| - } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
| - return rv;
|
| -}
|
| -
|
| -int SSLClientSocketMac::DoHandshake() {
|
| - OSStatus status = SSLHandshake(ssl_context_);
|
| -
|
| - if (status == errSSLWouldBlock)
|
| - next_state_ = STATE_HANDSHAKE;
|
| -
|
| - if (status == noErr)
|
| - completed_handshake_ = true;
|
| -
|
| - int net_error = NetErrorFromOSStatus(status);
|
| -
|
| - // At this point we have a connection. For now, we're going to use the default
|
| - // certificate verification that the system does, and accept its answer for
|
| - // the cert status. In the future, we'll need to call SSLSetEnableCertVerify
|
| - // to disable cert verification and do the verification ourselves. This allows
|
| - // very fine-grained control over what we'll accept for certification.
|
| - // TODO(avi): ditto
|
| -
|
| - // TODO(wtc): for now, always check revocation.
|
| - server_cert_status_ = CERT_STATUS_REV_CHECKING_ENABLED;
|
| - if (net_error)
|
| - server_cert_status_ |= MapNetErrorToCertStatus(net_error);
|
| -
|
| - return net_error;
|
| -}
|
| -
|
| -int SSLClientSocketMac::DoReadComplete(int result) {
|
| - if (result < 0) {
|
| - read_io_buf_ = NULL;
|
| - return result;
|
| - }
|
| -
|
| - char* buffer = &recv_buffer_[recv_buffer_.size() - recv_buffer_tail_slop_];
|
| - memcpy(buffer, read_io_buf_->data(), result);
|
| - read_io_buf_ = NULL;
|
| -
|
| - recv_buffer_tail_slop_ -= result;
|
| -
|
| - return result;
|
| -}
|
| -
|
| -void SSLClientSocketMac::OnWriteComplete(int result) {
|
| - if (result < 0) {
|
| - pending_send_error_ = result;
|
| - return;
|
| - }
|
| -
|
| - send_buffer_.erase(send_buffer_.begin(),
|
| - send_buffer_.begin() + result);
|
| -
|
| - if (!send_buffer_.empty())
|
| - SSLWriteCallback(this, NULL, NULL);
|
| -}
|
| -
|
| -int SSLClientSocketMac::DoPayloadRead() {
|
| - size_t processed;
|
| - OSStatus status = SSLRead(ssl_context_,
|
| - user_buf_->data(),
|
| - user_buf_len_,
|
| - &processed);
|
| -
|
| - // There's a subtle difference here in semantics of the "would block" errors.
|
| - // In our code, ERR_IO_PENDING means the whole operation is async, while
|
| - // errSSLWouldBlock means that the stream isn't ending (and is often returned
|
| - // along with partial data). So even though "would block" is returned, if we
|
| - // have data, let's just return it.
|
| -
|
| - if (processed > 0) {
|
| - next_state_ = STATE_NONE;
|
| - return processed;
|
| - }
|
| -
|
| - if (status == errSSLWouldBlock) {
|
| - next_state_ = STATE_PAYLOAD_READ;
|
| - }
|
| -
|
| - return NetErrorFromOSStatus(status);
|
| -}
|
| -
|
| -int SSLClientSocketMac::DoPayloadWrite() {
|
| - size_t processed;
|
| - OSStatus status = SSLWrite(ssl_context_,
|
| - user_buf_->data(),
|
| - user_buf_len_,
|
| - &processed);
|
| -
|
| - if (processed > 0)
|
| - return processed;
|
| -
|
| - return NetErrorFromOSStatus(status);
|
| -}
|
| -
|
| -// Handling the reading from the network is one of those things that should be
|
| -// simpler than it is. Ideally, we'd have some kind of ring buffer. For now, a
|
| -// std::vector<char> will have to do.
|
| -//
|
| -// The need for a buffer at all comes from the difference between an
|
| -// asynchronous connection (which is what we have) and a non-blocking connection
|
| -// (which is what we fake for Secure Transport). When Secure Transport calls us
|
| -// to read data, we call our underlying transport, which will likely tell us
|
| -// that it'll do a callback. When that happens, we need to tell Secure Transport
|
| -// that we've "blocked". When the callback happens, we have a chunk of data that
|
| -// we need to feed to Secure Transport, but it's not interested. It'll ask for
|
| -// it again when we call it again, so we need to hold on to the data.
|
| -//
|
| -// Why keep our own buffer? Well, when we execute a read and the underlying
|
| -// transport says that it'll do a callback, it keeps the pointer to the
|
| -// buffer. We can't pass it the buffer that Secure Transport gave us to fill, as
|
| -// we can't guarantee its lifetime.
|
| -//
|
| -// The basic idea, then, is this: we have a buffer filled with the data that
|
| -// we've read from the network but haven't given to Secure Transport
|
| -// yet. Whenever we read from the network the first thing we do is ensure we
|
| -// have enough room in the buffer for the read. We enlarge the buffer to be big
|
| -// enough to hold both our existing data and the new data, and then we mark the
|
| -// extra space at the end as "tail slop." Slop is just space at the ends of the
|
| -// buffer that's going to be used for data but isn't (yet). A diagram:
|
| -//
|
| -// +--------------------------------------+--------------------------------+
|
| -// | existing good data ~~~~~~~~~~~~~~~~~ | tail slop area ~~~~~~~~~~~~~~~ |
|
| -// +--------------------------------------+--------------------------------+
|
| -//
|
| -// When executing a read, we pass a pointer to the beginning of the tail slop
|
| -// area (guaranteed to be contiguous space because it's a vector, unlike, say, a
|
| -// deque (sigh)) and the size of the tail slop. When we get data (either here in
|
| -// SSLReadCallback() or above in DoReadComplete()) we subtract the number of
|
| -// bytes received from the tail slop value. That moves those bytes
|
| -// (conceptually, not physically) from the tail slop area to the area containing
|
| -// real data.
|
| -//
|
| -// The idea is still pretty simple. We enlarge the tail slop, call our
|
| -// underlying network, get data, shrink the slop area to match, copy requested
|
| -// data back into our caller's buffer, and delete the data from the head of the
|
| -// vector.
|
| -//
|
| -// Except for a nasty little problem. Asynchronous I/O calls keep the buffer
|
| -// pointer.
|
| -//
|
| -// This leads to the following scenario: we have a few bytes of good data in our
|
| -// buffer. But our caller requests more than that. We oblige by enlarging the
|
| -// tail slop, and calling our underlying provider, but the provider says that
|
| -// it'll call us back later. So we shrug our shoulders, copy what we do have
|
| -// into our caller's buffer and...
|
| -//
|
| -// Wait. We can't delete the data from the head of our vector. That would
|
| -// invalidate the pointer that we just gave to our provider. So instead, in that
|
| -// case we keep track of where the good data starts by keeping a "head slop"
|
| -// value, which just notes what data we've already sent and that is useless to
|
| -// us but that we can't delete because we have I/O in flight depending on us
|
| -// leaving the buffer alone.
|
| -//
|
| -// I hear what you're saying. "We need to use a ring buffer!" You write it,
|
| -// then, and I'll use it. Here are the features it needs. First, it needs to be
|
| -// able to have contiguous segments of arbitrary length attached to it to create
|
| -// read buffers. Second, each of those segments must have a "used" length
|
| -// indicator, so if it was half-filled by a previous data read, but the next
|
| -// data read is for more than there's space left, a new segment can be created
|
| -// for the new read without leaving an internal gap.
|
| -//
|
| -// Get to it.
|
| -//
|
| -// (sigh) Who am I kidding? TODO(avi): write the aforementioned ring buffer
|
| -
|
| -// static
|
| -OSStatus SSLClientSocketMac::SSLReadCallback(SSLConnectionRef connection,
|
| - void* data,
|
| - size_t* data_length) {
|
| - DCHECK(data);
|
| - DCHECK(data_length);
|
| - SSLClientSocketMac* us =
|
| - const_cast<SSLClientSocketMac*>(
|
| - static_cast<const SSLClientSocketMac*>(connection));
|
| -
|
| - // If we have I/O in flight, promise we'll get back to them and use the
|
| - // existing callback to do so
|
| -
|
| - if (us->next_io_state_ == STATE_READ_COMPLETE) {
|
| - *data_length = 0;
|
| - return errSSLWouldBlock;
|
| - }
|
| -
|
| - // Start with what's in the buffer
|
| -
|
| - size_t total_read = us->recv_buffer_.size() - us->recv_buffer_head_slop_ -
|
| - us->recv_buffer_tail_slop_;
|
| -
|
| - // Resize the buffer if needed
|
| -
|
| - if (us->recv_buffer_.size() - us->recv_buffer_head_slop_ < *data_length) {
|
| - us->recv_buffer_.resize(us->recv_buffer_head_slop_ + *data_length);
|
| - us->recv_buffer_tail_slop_ = *data_length - total_read;
|
| - }
|
| -
|
| - int rv = 1; // any old value to spin the loop below
|
| - while (rv > 0 && total_read < *data_length) {
|
| - char* buffer = &us->recv_buffer_[us->recv_buffer_head_slop_ + total_read];
|
| - us->read_io_buf_ = new IOBuffer(*data_length - total_read);
|
| - rv = us->transport_->Read(us->read_io_buf_,
|
| - *data_length - total_read,
|
| - &us->io_callback_);
|
| -
|
| - if (rv >= 0) {
|
| - memcpy(buffer, us->read_io_buf_->data(), rv);
|
| - us->read_io_buf_ = NULL;
|
| - total_read += rv;
|
| - us->recv_buffer_tail_slop_ -= rv;
|
| - }
|
| - }
|
| -
|
| - *data_length = total_read;
|
| - if (total_read) {
|
| - memcpy(data, &us->recv_buffer_[us->recv_buffer_head_slop_], total_read);
|
| - if (rv == ERR_IO_PENDING) {
|
| - // We have I/O in flight which is going to land in our buffer. We can't
|
| - // shuffle things around, so we need to just fiddle with pointers.
|
| - us->recv_buffer_head_slop_ += total_read;
|
| - } else {
|
| - us->recv_buffer_.erase(us->recv_buffer_.begin(),
|
| - us->recv_buffer_.begin() +
|
| - total_read +
|
| - us->recv_buffer_head_slop_);
|
| - us->recv_buffer_head_slop_ = 0;
|
| - }
|
| - }
|
| -
|
| - if (rv == ERR_IO_PENDING) {
|
| - us->next_io_state_ = STATE_READ_COMPLETE;
|
| - } else {
|
| - us->read_io_buf_ = NULL;
|
| - }
|
| -
|
| - if (rv < 0)
|
| - return OSStatusFromNetError(rv);
|
| -
|
| - return noErr;
|
| -}
|
| -
|
| -// static
|
| -OSStatus SSLClientSocketMac::SSLWriteCallback(SSLConnectionRef connection,
|
| - const void* data,
|
| - size_t* data_length) {
|
| - SSLClientSocketMac* us =
|
| - const_cast<SSLClientSocketMac*>(
|
| - static_cast<const SSLClientSocketMac*>(connection));
|
| -
|
| - if (us->pending_send_error_ != OK) {
|
| - OSStatus status = OSStatusFromNetError(us->pending_send_error_);
|
| - us->pending_send_error_ = OK;
|
| - return status;
|
| - }
|
| -
|
| - if (data)
|
| - us->send_buffer_.insert(us->send_buffer_.end(),
|
| - static_cast<const char*>(data),
|
| - static_cast<const char*>(data) + *data_length);
|
| - int rv;
|
| - do {
|
| - scoped_refptr<IOBuffer> buffer = new IOBuffer(us->send_buffer_.size());
|
| - memcpy(buffer->data(), &us->send_buffer_[0], us->send_buffer_.size());
|
| - rv = us->transport_->Write(buffer,
|
| - us->send_buffer_.size(),
|
| - &us->write_callback_);
|
| - if (rv > 0) {
|
| - us->send_buffer_.erase(us->send_buffer_.begin(),
|
| - us->send_buffer_.begin() + rv);
|
| - }
|
| - } while (rv > 0 && !us->send_buffer_.empty());
|
| -
|
| - if (rv < 0 && rv != ERR_IO_PENDING) {
|
| - return OSStatusFromNetError(rv);
|
| - }
|
| -
|
| - // always lie to our caller
|
| - return noErr;
|
| -}
|
| -
|
| -} // namespace net
|
|
|