static ssize_t mailstream_low_cfstream_write(mailstream_low * s,
                                             const void * buf, size_t count)
{
#if HAVE_CFNETWORK
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) s->data;
  cfstream_data->writeBuffer = buf;
  cfstream_data->writeBufferSize = count;
  
  if (cfstream_data->cancelled)
    return -1;
  
  if (CFWriteStreamCanAcceptBytes(cfstream_data->writeStream)) {
    writeDataToStream(s);
    return cfstream_data->writeResult;
  }
  
  r = wait_runloop(s, STATE_WAIT_WRITE);
  if (r != WAIT_RUNLOOP_EXIT_NO_ERROR) {
    return -1;
  }
  
  return cfstream_data->writeResult;
#else
  return -1;
#endif
}
static ssize_t mailstream_low_cfstream_read(mailstream_low * s,
                                            void * buf, size_t count)
{
#if HAVE_CFNETWORK
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) s->data;
  cfstream_data->readBuffer = buf;
  cfstream_data->readBufferSize = count;
  
  if (cfstream_data->cancelled) {
    return -1;
  }
  
  if (CFReadStreamHasBytesAvailable(cfstream_data->readStream)) {
    readDataFromStream(s);
    return cfstream_data->readResult;
  }
  
  r = wait_runloop(s, STATE_WAIT_READ);
  if (r != WAIT_RUNLOOP_EXIT_NO_ERROR) {
    return -1;
  }
  
  return cfstream_data->readResult;
#else
  return -1;
#endif
}
static int low_open(mailstream_low * s)
{
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) s->data;
  
  CFReadStreamOpen(cfstream_data->readStream);
  CFWriteStreamOpen(cfstream_data->writeStream);
  
  r = wait_runloop(s, STATE_WAIT_OPEN);
  if (r != WAIT_RUNLOOP_EXIT_NO_ERROR) {
    return -1;
  }
  
  if (cfstream_data->writeOpenResult < 0)
    return -1;
  if (cfstream_data->readOpenResult < 0)
    return -1;
  
  return 0;
}
int mailstream_low_cfstream_wait_idle(mailstream_low * low, int max_idle_delay)
{
#if HAVE_CFNETWORK
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) low->data;
  cfstream_data->idleMaxDelay = max_idle_delay;
  
  r = wait_runloop(low, STATE_WAIT_IDLE);
  switch (r) {
    case WAIT_RUNLOOP_EXIT_TIMEOUT:
      return MAILSTREAM_IDLE_TIMEOUT;
    case WAIT_RUNLOOP_EXIT_INTERRUPTED:
      return MAILSTREAM_IDLE_INTERRUPTED;
    case WAIT_RUNLOOP_EXIT_CANCELLED:
      return MAILSTREAM_IDLE_CANCELLED;
  }
  return MAILSTREAM_IDLE_HASDATA;
#else
  return MAILSTREAM_IDLE_ERROR;
#endif
}
int mailstream_cfstream_set_ssl_enabled(mailstream * s, int ssl_enabled)
{
#if HAVE_CFNETWORK
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) s->low->data;
  cfstream_data->ssl_enabled = ssl_enabled;
  if (ssl_enabled) {
    CFMutableDictionaryRef settings;
    
    settings = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    switch (cfstream_data->ssl_level) {
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_NONE:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNone);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_SSLv2:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv2);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_SSLv3:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv3);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_TLSv1:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelTLSv1);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_NEGOCIATED_SSL:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);
        break;
    }
    
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_EXPIRED_CERTIFICATES) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_EXPIRED_ROOTS) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_ANY_ROOT) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_DISABLE_VALIDATES_CERTIFICATE_CHAIN) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
    }
    
    CFReadStreamSetProperty(cfstream_data->readStream, kCFStreamPropertySSLSettings, settings);
    CFWriteStreamSetProperty(cfstream_data->writeStream, kCFStreamPropertySSLSettings, settings);
    CFRelease(settings);
  }
  else {
    CFMutableDictionaryRef settings;
    
    settings = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNone);
		CFReadStreamSetProperty(cfstream_data->readStream, kCFStreamPropertySSLSettings, settings);
		CFWriteStreamSetProperty(cfstream_data->writeStream, kCFStreamPropertySSLSettings, settings);
    CFRelease(settings);
    
    //fprintf(stderr, "is not ssl\n");
  }
  
  // We need to investigate more about how to establish a STARTTLS connection.
  // For now, wait until we get the certificate chain.
  while (1) {
    r = wait_runloop(s->low, STATE_WAIT_SSL);
    if (r != WAIT_RUNLOOP_EXIT_NO_ERROR) {
      return -1;
    }
    if (cfstream_data->writeSSLResult < 0)
      return -1;
    if (cfstream_data->readSSLResult < 0)
      return -1;
    
    SecTrustRef secTrust = (SecTrustRef)CFReadStreamCopyProperty(cfstream_data->readStream, kCFStreamPropertySSLPeerTrust);
    if (secTrust == NULL) {
      // No trust, wait more.
      continue;
    }
    
    CFIndex count = SecTrustGetCertificateCount(secTrust);
    CFRelease(secTrust);
    
    if (count == 0) {
      // No certificates, wait more.
      continue;
    }
    break;
  }
  
  return 0;
#else
  return -1;
#endif
}
int mailstream_cfstream_set_ssl_enabled(mailstream * s, int ssl_enabled)
{
#if HAVE_CFNETWORK
  struct mailstream_cfstream_data * cfstream_data;
  int r;
  
  cfstream_data = (struct mailstream_cfstream_data *) s->low->data;
  cfstream_data->ssl_enabled = ssl_enabled;
  if (ssl_enabled) {
    CFMutableDictionaryRef settings;
    
    settings = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    switch (cfstream_data->ssl_level) {
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_NONE:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNone);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_SSLv2:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv2);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_SSLv3:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelSSLv3);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_TLSv1:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelTLSv1);
        break;
      case MAILSTREAM_CFSTREAM_SSL_LEVEL_NEGOCIATED_SSL:
        CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNegotiatedSSL);
        break;
    }
    
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_EXPIRED_CERTIFICATES) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_EXPIRED_ROOTS) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_ALLOWS_ANY_ROOT) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
    }
    if ((cfstream_data->ssl_certificate_verification_mask & MAILSTREAM_CFSTREAM_SSL_DISABLE_VALIDATES_CERTIFICATE_CHAIN) != 0) {
      CFDictionarySetValue(settings, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
    }
    
    CFReadStreamSetProperty(cfstream_data->readStream, kCFStreamPropertySSLSettings, settings);
    CFWriteStreamSetProperty(cfstream_data->writeStream, kCFStreamPropertySSLSettings, settings);
    CFRelease(settings);
  }
  else {
    CFMutableDictionaryRef settings;
    
    settings = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(settings, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelNone);
		CFReadStreamSetProperty(cfstream_data->readStream, kCFStreamPropertySSLSettings, settings);
		CFWriteStreamSetProperty(cfstream_data->writeStream, kCFStreamPropertySSLSettings, settings);
    CFRelease(settings);
    
    //fprintf(stderr, "is not ssl\n");
  }
  
  r = wait_runloop(s->low, STATE_WAIT_SSL);
  if (r != WAIT_RUNLOOP_EXIT_NO_ERROR) {
    return -1;
  }
  
  if (cfstream_data->writeSSLResult < 0)
    return -1;
  if (cfstream_data->readSSLResult < 0)
    return -1;
  
  return 0;
#else
  return -1;
#endif
}