// Copyright (c) 2008 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/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; } } // Shared with the Windows code. TODO(avi): merge to a common place int CertStatusFromNetError(int error) { switch (error) { case ERR_CERT_COMMON_NAME_INVALID: return CERT_STATUS_COMMON_NAME_INVALID; case ERR_CERT_DATE_INVALID: return CERT_STATUS_DATE_INVALID; case ERR_CERT_AUTHORITY_INVALID: return CERT_STATUS_AUTHORITY_INVALID; case ERR_CERT_NO_REVOCATION_MECHANISM: return CERT_STATUS_NO_REVOCATION_MECHANISM; case ERR_CERT_UNABLE_TO_CHECK_REVOCATION: return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION; case ERR_CERT_REVOKED: return CERT_STATUS_REVOKED; case ERR_CERT_CONTAINS_ERRORS: NOTREACHED(); // Falls through. case ERR_CERT_INVALID: return CERT_STATUS_INVALID; default: return 0; } } // 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_); next_state_ = STATE_CONNECT; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) user_callback_ = callback; return rv; } int SSLClientSocketMac::ReconnectIgnoringLastError( CompletionCallback* callback) { // TODO(darin): implement me! return ERR_FAILED; } 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(); } int SSLClientSocketMac::Read(char* buf, int buf_len, CompletionCallback* callback) { DCHECK(completed_handshake_); DCHECK(next_state_ == STATE_NONE); DCHECK(!user_callback_); 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; return rv; } int SSLClientSocketMac::Write(const char* buf, int buf_len, CompletionCallback* callback) { DCHECK(completed_handshake_); DCHECK(next_state_ == STATE_NONE); DCHECK(!user_callback_); user_buf_ = const_cast(buf); user_buf_len_ = buf_len; next_state_ = STATE_PAYLOAD_WRITE; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) user_callback_ = callback; return rv; } void SSLClientSocketMac::GetSSLInfo(SSLInfo* ssl_info) { DCHECK(completed_handshake_); OSStatus status; ssl_info->Reset(); // set cert CFArrayRef certs; status = SSLCopyPeerCertificates(ssl_context_, &certs); if (!status) { DCHECK(CFArrayGetCount(certs) > 0); SecCertificateRef client_cert = static_cast( const_cast(CFArrayGetValueAtIndex(certs, 0))); CFRetain(client_cert); ssl_info->cert = X509Certificate::CreateFromHandle(client_cert); 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::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; 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_CONNECT: // We must establish a connection to the other side using our // lower-level transport. rv = DoConnect(); break; case STATE_CONNECT_COMPLETE: // We have a connection to the other side; initialize our SSL engine. rv = DoConnectComplete(rv); break; 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::DoConnect() { next_state_ = STATE_CONNECT_COMPLETE; return transport_->Connect(&io_callback_); } int SSLClientSocketMac::DoConnectComplete(int result) { if (result < 0) return result; 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; return OK; } 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_ |= CertStatusFromNetError(net_error); return net_error; } int SSLClientSocketMac::DoReadComplete(int result) { if (result < 0) return result; 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_, 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_, 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 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( static_cast(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) { rv = us->transport_->Read(&us->recv_buffer_[us->recv_buffer_head_slop_ + total_read], us->recv_buffer_tail_slop_, &us->io_callback_); if (rv > 0) { 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; } 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( static_cast(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(data), static_cast(data) + *data_length); int rv; do { rv = us->transport_->Write(&us->send_buffer_[0], 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