Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 #include "bin/tls_socket.h" | |
| 6 | |
| 7 #include <errno.h> | |
| 8 #include <fcntl.h> | |
| 9 #include <sys/stat.h> | |
| 10 #include <unistd.h> | |
| 11 #include <libgen.h> | |
| 12 #include <stdio.h> | |
| 13 #include <string.h> | |
| 14 | |
| 15 #include <prinit.h> | |
| 16 #include <prerror.h> | |
| 17 #include <prnetdb.h> | |
| 18 #include <nss.h> | |
| 19 #include <ssl.h> | |
| 20 | |
| 21 #include "bin/builtin.h" | |
| 22 #include "bin/dartutils.h" | |
| 23 #include "bin/thread.h" | |
| 24 #include "bin/utils.h" | |
| 25 #include "bin/net/nss_memio.h" | |
| 26 #include "platform/utils.h" | |
| 27 | |
| 28 #include "include/dart_api.h" | |
| 29 | |
| 30 bool TlsFilter::library_initialized_ = false; | |
| 31 | |
| 32 static TlsFilter* NativePeer(Dart_NativeArguments args) { | |
|
Mads Ager (google)
2012/09/28 12:03:56
Maybe call this GetTlsFiler?
Bill Hesse
2012/10/31 16:33:29
Done.
| |
| 33 TlsFilter* native_peer; | |
| 34 | |
| 35 Dart_Handle dart_this = HandleError(Dart_GetNativeArgument(args, 0)); | |
| 36 ASSERT(Dart_IsInstance(dart_this)); | |
| 37 HandleError(Dart_GetNativeInstanceField(dart_this, 0, | |
|
Mads Ager (google)
2012/09/28 12:03:56
Introduce a named constant for the 0.
static con
Bill Hesse
2012/10/31 16:33:29
Done.
| |
| 38 reinterpret_cast<intptr_t*>(&native_peer))); | |
| 39 return native_peer; | |
| 40 } | |
| 41 | |
| 42 | |
| 43 static void SetNativePeer(Dart_NativeArguments args, TlsFilter* native_peer) { | |
|
Mads Ager (google)
2012/09/28 12:03:56
SetTlsFilter?
Bill Hesse
2012/10/31 16:33:29
Done.
| |
| 44 Dart_Handle dart_this = HandleError(Dart_GetNativeArgument(args, 0)); | |
|
Mads Ager (google)
2012/09/28 12:03:56
Ditto on HandleError.
| |
| 45 ASSERT(Dart_IsInstance(dart_this)); | |
| 46 HandleError(Dart_SetNativeInstanceField(dart_this, 0, | |
| 47 reinterpret_cast<intptr_t>(native_peer))); | |
| 48 } | |
| 49 | |
| 50 | |
| 51 void FUNCTION_NAME(TlsSocket_Init)(Dart_NativeArguments args) { | |
| 52 Dart_EnterScope(); | |
| 53 TlsFilter* native_peer = new TlsFilter; | |
|
Mads Ager (google)
2012/09/28 12:03:57
native_peer -> filter?
Bill Hesse
2012/10/31 16:33:29
Done.
| |
| 54 Dart_Handle dart_this = Dart_GetNativeArgument(args, 0); | |
| 55 if (Dart_IsError(dart_this)) { | |
| 56 delete native_peer; | |
| 57 Dart_PropagateError(dart_this); | |
| 58 } | |
| 59 SetNativePeer(args, native_peer); | |
| 60 native_peer->Init(dart_this); | |
| 61 | |
| 62 Dart_SetReturnValue(args, Dart_Null()); | |
|
Mads Ager (google)
2012/09/28 12:03:57
This is the default behavior so you do not need th
Bill Hesse
2012/10/31 16:33:29
Removed everywhere.
On 2012/09/28 12:03:57, Mads
| |
| 63 Dart_ExitScope(); | |
| 64 } | |
| 65 | |
| 66 | |
| 67 void FUNCTION_NAME(TlsSocket_Connect)(Dart_NativeArguments args) { | |
| 68 Dart_EnterScope(); | |
| 69 NativePeer(args)->Connect(); | |
| 70 Dart_SetReturnValue(args, Dart_Null()); | |
| 71 Dart_ExitScope(); | |
| 72 } | |
| 73 | |
| 74 | |
| 75 void FUNCTION_NAME(TlsSocket_Destroy)(Dart_NativeArguments args) { | |
| 76 Dart_EnterScope(); | |
| 77 TlsFilter* native_peer = NativePeer(args); | |
| 78 SetNativePeer(args, NULL); | |
| 79 native_peer->Destroy(); | |
| 80 delete native_peer; | |
| 81 Dart_SetReturnValue(args, Dart_Null()); | |
| 82 Dart_ExitScope(); | |
| 83 } | |
| 84 | |
| 85 | |
| 86 void FUNCTION_NAME(TlsSocket_RegisterHandshakeCallbacks)( | |
| 87 Dart_NativeArguments args) { | |
| 88 Dart_EnterScope(); | |
| 89 Dart_Handle handshake_start = HandleError(Dart_GetNativeArgument(args, 1)); | |
| 90 Dart_Handle handshake_finish = HandleError(Dart_GetNativeArgument(args, 2)); | |
| 91 if (!Dart_IsClosure(handshake_start) || | |
| 92 !Dart_IsClosure(handshake_finish)) { | |
| 93 Dart_ThrowException(DartUtils::NewDartIllegalArgumentException( | |
| 94 "Illegal argument to RegisterHandshakeCallbacks")); | |
| 95 } | |
| 96 NativePeer(args)->RegisterHandshakeCallbacks(handshake_start, | |
| 97 handshake_finish); | |
| 98 Dart_SetReturnValue(args, Dart_Null()); | |
| 99 Dart_ExitScope(); | |
| 100 } | |
| 101 | |
| 102 | |
| 103 void FUNCTION_NAME(TlsSocket_ProcessBuffer)(Dart_NativeArguments args) { | |
| 104 Dart_EnterScope(); | |
| 105 Dart_Handle buffer_id_object = HandleError(Dart_GetNativeArgument(args, 1)); | |
| 106 int64_t buffer_id = DartUtils::GetIntegerValue(buffer_id_object); | |
| 107 if (buffer_id < 0 || buffer_id >= TlsFilter::kNumBuffers) { | |
| 108 Dart_ThrowException(DartUtils::NewDartIllegalArgumentException( | |
| 109 "Illegal argument to ProcessBuffer")); | |
| 110 } | |
| 111 | |
| 112 intptr_t bytes_read = | |
| 113 NativePeer(args)->ProcessBuffer(static_cast<int>(buffer_id)); | |
| 114 Dart_SetReturnValue(args, Dart_NewInteger(bytes_read)); | |
| 115 Dart_ExitScope(); | |
| 116 } | |
| 117 | |
| 118 | |
| 119 void FUNCTION_NAME(TlsSocket_SetCertificateDatabase) | |
| 120 (Dart_NativeArguments args) { | |
| 121 Dart_EnterScope(); | |
| 122 Dart_Handle dart_pkcert_dir = HandleError(Dart_GetNativeArgument(args, 0)); | |
| 123 // Check that the type is string, and get the UTF-8 C string value from it. | |
| 124 if (Dart_IsString(dart_pkcert_dir)) { | |
| 125 const char* pkcert_dir = NULL; | |
| 126 HandleError(Dart_StringToCString(dart_pkcert_dir, &pkcert_dir)); | |
| 127 TlsFilter::InitializeLibrary(pkcert_dir); | |
| 128 } else { | |
| 129 Dart_ThrowException(DartUtils::NewDartIllegalArgumentException( | |
| 130 "Non-String argument to SetCertificateDatabase")); | |
| 131 } | |
| 132 Dart_SetReturnValue(args, Dart_Null()); | |
| 133 Dart_ExitScope(); | |
| 134 } | |
| 135 | |
| 136 void TlsFilter::Init(Dart_Handle dart_this) { | |
| 137 stringStart_ = HandleError( | |
| 138 Dart_NewPersistentHandle(Dart_NewString("start"))); | |
| 139 stringLength_ = HandleError( | |
| 140 Dart_NewPersistentHandle(Dart_NewString("length"))); | |
| 141 | |
| 142 InitializeBuffers(dart_this); | |
| 143 InitializePlatformData(); | |
| 144 } | |
| 145 | |
| 146 | |
| 147 void TlsFilter::InitializeBuffers(Dart_Handle dart_this) { | |
| 148 // Create TlsFilter buffers as ExternalUint8Array objects. | |
| 149 Dart_Handle dart_buffers_object = HandleError( | |
| 150 Dart_GetField(dart_this, Dart_NewString("buffers"))); | |
| 151 Dart_Handle dart_buffer_object = HandleError( | |
| 152 Dart_ListGetAt(dart_buffers_object, kReadPlaintext)); | |
| 153 Dart_Handle tlsExternalBuffer_class = HandleError( | |
|
Mads Ager (google)
2012/09/28 12:03:57
Please use C++ naming convention: tls_external_buf
Bill Hesse
2012/10/31 16:33:29
Done.
| |
| 154 Dart_InstanceGetClass(dart_buffer_object)); | |
| 155 Dart_Handle dart_buffer_size = HandleError( | |
| 156 Dart_GetField(tlsExternalBuffer_class, Dart_NewString("kSize"))); | |
| 157 buffer_size_ = DartUtils::GetIntegerValue(dart_buffer_size); | |
| 158 if (buffer_size_ <= 0 || buffer_size_ > 1024 * 1024) { | |
| 159 Dart_ThrowException( | |
| 160 Dart_NewString("Invalid buffer size in _TlsExternalBuffer")); | |
| 161 } | |
| 162 | |
| 163 for (int i = 0; i < kNumBuffers; ++i) { | |
| 164 dart_buffer_objects_[i] = HandleError( | |
| 165 Dart_NewPersistentHandle(Dart_ListGetAt(dart_buffers_object, i))); | |
| 166 buffers_[i] = new uint8_t[buffer_size_]; | |
| 167 Dart_Handle data = HandleError( | |
| 168 Dart_NewExternalByteArray(buffers_[i], | |
| 169 buffer_size_, NULL, NULL)); | |
| 170 HandleError( | |
| 171 Dart_SetField(dart_buffer_objects_[i], Dart_NewString("data"), data)); | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 | |
| 176 void TlsFilter::RegisterHandshakeCallbacks(Dart_Handle start, | |
| 177 Dart_Handle finish) { | |
| 178 handshake_start_ = HandleError(Dart_NewPersistentHandle(start)); | |
| 179 handshake_finish_ = HandleError(Dart_NewPersistentHandle(finish)); | |
| 180 } | |
| 181 | |
| 182 | |
| 183 void TlsFilter::DestroyPlatformIndependent() { | |
| 184 for (int i = 0; i < kNumBuffers; ++i) { | |
| 185 Dart_DeletePersistentHandle(dart_buffer_objects_[i]); | |
| 186 } | |
| 187 Dart_DeletePersistentHandle(stringStart_); | |
| 188 Dart_DeletePersistentHandle(stringLength_); | |
| 189 } | |
| 190 | |
| 191 | |
| 192 class TlsFilterPlatformData { | |
| 193 public: | |
| 194 PRFileDesc* memio; | |
| 195 }; | |
| 196 | |
| 197 | |
| 198 TlsFilter::TlsFilter() : in_handshake_(false) { } | |
| 199 | |
| 200 | |
| 201 void TlsFilter::InitializeLibrary(const char* pkcert_database) { | |
| 202 printf("Entering InitializeLibrary\n"); | |
| 203 LockInitMutex(); | |
| 204 if (!library_initialized_) { | |
| 205 PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); | |
| 206 printf("%s\n", pkcert_database); | |
|
Mads Ager (google)
2012/09/28 12:03:57
Please remove debug printing.
| |
| 207 #ifdef CHROMIUM_NSS | |
|
Søren Gjesse
2012/09/28 09:17:31
We should avoid #ifdefs like this. How about havin
Mads Ager (google)
2012/09/28 12:03:57
I'm not convinced that our build should have to su
Bill Hesse
2012/10/31 16:33:29
All #ifdefs removed, only Chromium NSS supported,
| |
| 208 // The Chromium version of the NSS libraries does not allow read-only here. | |
| 209 SECStatus status = | |
| 210 NSS_InitReadWrite(pkcert_database); | |
| 211 #else | |
| 212 SECStatus status = | |
| 213 NSS_Init(pkcert_database); | |
| 214 #endif | |
| 215 if (status == SECSuccess) { | |
| 216 printf("Successful NSS_Init call\n"); | |
|
Mads Ager (google)
2012/09/28 12:03:57
Please remove debug printing. You should probably
| |
| 217 } else { | |
| 218 PRErrorCode pr_error = PR_GetError(); | |
| 219 printf("Unsuccessful NSS_Init call. Error %d\n", pr_error); | |
| 220 } | |
| 221 | |
| 222 status = NSS_SetDomesticPolicy(); | |
| 223 if (status == SECSuccess) { | |
| 224 printf("Successful NSS_SetDomesticPolicy call\n"); | |
| 225 } else { | |
| 226 PRErrorCode pr_error = PR_GetError(); | |
| 227 printf("Unsuccessful NSS_SetDomesticPolicy call. Error %d\n", pr_error); | |
| 228 } | |
| 229 } else { | |
| 230 printf("Called TlsFilter::InitializeLibrary more than once.\n"); | |
| 231 } | |
| 232 UnlockInitMutex(); | |
| 233 printf("Exiting InitializeLibrary\n"); | |
| 234 } | |
| 235 | |
| 236 | |
| 237 void TlsFilter::InitializePlatformData() { | |
| 238 data_ = new TlsFilterPlatformData; | |
| 239 data_->memio = memio_CreateIOLayer(30000); | |
|
Mads Ager (google)
2012/09/28 12:03:57
Maybe we can create a named constant for this valu
| |
| 240 } | |
| 241 | |
| 242 | |
| 243 char host_entry_buffer[PR_NETDB_BUF_SIZE]; | |
| 244 PRHostEnt host_entry; | |
| 245 PRNetAddr host_address; | |
| 246 PRFileDesc* my_socket; | |
| 247 | |
| 248 bool SECURITY = true; | |
|
Mads Ager (google)
2012/09/28 12:03:57
Please remove.
| |
| 249 SECStatus status; | |
| 250 | |
| 251 // Callback that always returns SECSuccess. | |
| 252 static SECStatus AlwaysSucceed(void* arg, PRFileDesc *fd, | |
| 253 PRBool check, PRBool server) { | |
| 254 return SECSuccess; | |
| 255 } | |
| 256 | |
| 257 void TlsFilter::Connect() { | |
| 258 printf("Entering InitSocket\n"); | |
| 259 if (!in_handshake_) { | |
| 260 my_socket = data_->memio; | |
| 261 | |
| 262 my_socket = SSL_ImportFD(NULL, my_socket); | |
| 263 if (my_socket == NULL) { | |
| 264 printf("SSL_ImportFD failed.\n"); | |
|
Mads Ager (google)
2012/09/28 12:03:57
Similarly here. We should report this error back t
| |
| 265 } | |
| 266 | |
| 267 if (SSL_SetURL(my_socket, "www.google.dk") == -1) { | |
| 268 printf("SetURL call failed\n"); | |
| 269 } | |
| 270 | |
| 271 if (!SECURITY) { | |
| 272 status = SSL_OptionSet(my_socket, SSL_SECURITY, PR_FALSE); | |
| 273 } | |
| 274 if (status == SECSuccess) { | |
| 275 printf("Successful SSL_OptionSetDefault call\n"); | |
| 276 } else { | |
| 277 PRErrorCode pr_error = PR_GetError(); | |
| 278 printf("Unsuccessful SSL_OptionSetDefault call. Error %d\n", pr_error); | |
| 279 } | |
| 280 | |
| 281 #ifdef CHROMIUM_NSS | |
|
Mads Ager (google)
2012/09/28 12:03:57
Since we always use CHROMIUM_NSS we can get rid of
| |
| 282 // Certificate chain verification is not yet working with Chromium NSS. | |
| 283 status = SSL_AuthCertificateHook(my_socket, AlwaysSucceed, NULL); | |
| 284 if (status == SECSuccess) { | |
| 285 printf("Successful SSL_AuthCertificateHook call\n"); | |
| 286 } else { | |
| 287 PRErrorCode pr_error = PR_GetError(); | |
| 288 printf("Unsuccessful SSL_AuthCertificateHook call. Error %d\n", pr_error); | |
| 289 } | |
| 290 #endif | |
| 291 | |
| 292 status = SSL_ResetHandshake(my_socket, PR_FALSE); | |
| 293 if (status == SECSuccess) { | |
| 294 printf("Successful SSL_ResetHandshake call\n"); | |
| 295 } else { | |
| 296 PRErrorCode pr_error = PR_GetError(); | |
| 297 printf("Unsuccessful SSL_ResetHandshake call. Error %d\n", pr_error); | |
| 298 } | |
| 299 | |
| 300 // SetPeerAddress | |
| 301 PRNetAddr host_address; | |
| 302 char host_entry_buffer[PR_NETDB_BUF_SIZE]; | |
| 303 PRHostEnt host_entry; | |
| 304 PRStatus rv = PR_GetHostByName("www.google.dk", host_entry_buffer, | |
| 305 PR_NETDB_BUF_SIZE, &host_entry); | |
| 306 if (rv == PR_SUCCESS) { | |
| 307 printf("Successful PR_GetHostByName call\n"); | |
| 308 } else { | |
| 309 PRErrorCode pr_error = PR_GetError(); | |
| 310 printf("Unsuccessful PR_GetHostByName call. Error %d\n", pr_error); | |
| 311 } | |
| 312 | |
| 313 int index = PR_EnumerateHostEnt(0, &host_entry, 443, &host_address); | |
| 314 if (index == -1 || index == 0) { | |
| 315 printf("Unsuccessful PR_EnumerateHostEnt call.\n"); | |
| 316 } | |
| 317 if (host_address.inet.family == PR_AF_INET) { | |
| 318 printf("Got IP V4 address\n"); | |
| 319 } | |
| 320 | |
| 321 memio_SetPeerName(my_socket, &host_address); | |
| 322 | |
| 323 data_->memio = my_socket; | |
| 324 } | |
| 325 | |
| 326 // Handshake | |
| 327 int er; | |
| 328 if ((er = SSL_ForceHandshake(my_socket)) == SECSuccess) { | |
| 329 printf("Successful SSL_ForceHandshake call\n"); | |
| 330 if (in_handshake_) { | |
| 331 printf("Exiting handshake state\n"); | |
| 332 HandleError(Dart_InvokeClosure(handshake_finish_, 0, NULL)); | |
| 333 in_handshake_ = false; | |
| 334 } | |
| 335 } else { | |
| 336 printf("Unsuccessful SSL_ForceHandshake call\n"); | |
| 337 PRErrorCode pr_error = PR_GetError(); | |
| 338 printf("Error code %d %d\n", er, pr_error); | |
| 339 if (!in_handshake_) { | |
| 340 printf("Entering handshake state\n"); | |
| 341 // int writable = ProcessBuffer(kWriteEncrypted); | |
| 342 // printf("ProcessBuffer(kWriteEncrypted) returns %d\n", writable); | |
| 343 HandleError(Dart_InvokeClosure(handshake_start_, 0, NULL)); | |
| 344 in_handshake_ = true; | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 if (!SECURITY) { | |
| 349 // Handshake doesn't happen. | |
| 350 HandleError(Dart_InvokeClosure(handshake_start_, 0, NULL)); | |
| 351 HandleError(Dart_InvokeClosure(handshake_finish_, 0, NULL)); | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 | |
| 356 void TlsFilter::Destroy() { | |
| 357 DestroyPlatformIndependent(); | |
| 358 // TODO(whesse): Destroy OpenSSL objects here. | |
|
Mads Ager (google)
2012/09/28 12:03:57
OpenSSL?
| |
| 359 } | |
| 360 | |
| 361 intptr_t TlsFilter::ProcessBuffer(int buffer_index) { | |
| 362 printf("Entering processBuffer(%d)\n", buffer_index); | |
| 363 Dart_Handle buffer_object = dart_buffer_objects_[buffer_index]; | |
| 364 Dart_Handle start_object = HandleError( | |
| 365 Dart_GetField(buffer_object, stringStart_)); | |
| 366 Dart_Handle length_object = HandleError( | |
| 367 Dart_GetField(buffer_object, stringLength_)); | |
| 368 int64_t unsafe_start = DartUtils::GetIntegerValue(start_object); | |
| 369 int64_t unsafe_length = DartUtils::GetIntegerValue(length_object); | |
| 370 if (unsafe_start < 0 || unsafe_start >= buffer_size_ || | |
| 371 unsafe_length < 0 || unsafe_length > buffer_size_) { | |
| 372 Dart_ThrowException(DartUtils::NewDartIllegalArgumentException( | |
| 373 "Illegal .start or .length on a _TlsExternalBuffer")); | |
| 374 } | |
| 375 intptr_t start = static_cast<intptr_t>(unsafe_start); | |
| 376 intptr_t length = static_cast<intptr_t>(unsafe_length); | |
| 377 | |
| 378 int bytes_sent = 0; | |
| 379 switch (buffer_index) { | |
| 380 case kReadPlaintext: { | |
| 381 int bytes_free = buffer_size_ - start - length; | |
| 382 bytes_sent = PR_Read(data_->memio, | |
| 383 buffers_[buffer_index] + start + length, | |
| 384 bytes_free); | |
| 385 printf(" plaintext bytes read %d (into %d)\n", | |
| 386 bytes_sent, bytes_free); | |
| 387 if (bytes_sent < 0) bytes_sent = 0; | |
| 388 break; | |
| 389 } | |
| 390 | |
| 391 case kWriteEncrypted: { | |
| 392 const uint8_t* buf1; | |
| 393 const uint8_t* buf2; | |
| 394 unsigned int len1; | |
| 395 unsigned int len2; | |
| 396 int bytes_free = buffer_size_ - start - length; | |
| 397 memio_Private* secret = memio_GetSecret(data_->memio); | |
| 398 memio_GetWriteParams(secret, &buf1, &len1, &buf2, &len2); | |
| 399 int bytes_to_send = | |
| 400 dart::Utils::Minimum(len1, static_cast<unsigned>(bytes_free)); | |
| 401 if (bytes_to_send > 0) { | |
| 402 memmove(buffers_[buffer_index] + start + length, buf1, bytes_to_send); | |
| 403 bytes_sent = bytes_to_send; | |
| 404 } | |
| 405 bytes_to_send = dart::Utils::Minimum(len2, | |
| 406 static_cast<unsigned>(bytes_free - bytes_sent)); | |
| 407 if (bytes_to_send > 0) { | |
| 408 memmove(buffers_[buffer_index] + start + length + bytes_sent, buf2, | |
| 409 bytes_to_send); | |
| 410 bytes_sent += bytes_to_send; | |
| 411 } | |
| 412 printf(" encrypted bytes written %d (into %d)\n", | |
| 413 bytes_sent, bytes_free); | |
| 414 if (bytes_sent > 0) { | |
| 415 memio_PutWriteResult(secret, bytes_sent); | |
| 416 } | |
| 417 break; | |
| 418 } | |
| 419 | |
| 420 case kReadEncrypted: { | |
| 421 if (length > 0) { | |
| 422 bytes_sent = length; | |
| 423 // NEW | |
| 424 memio_Private* secret = memio_GetSecret(data_->memio); | |
| 425 uint8_t* memio_buf; | |
| 426 int free_bytes = memio_GetReadParams(secret, &memio_buf); | |
| 427 if (free_bytes < bytes_sent) bytes_sent = free_bytes; | |
| 428 printf(" encrypted bytes read %d (out of %d)\n", | |
| 429 bytes_sent, static_cast<int>(length)); | |
| 430 memmove(memio_buf, | |
| 431 buffers_[buffer_index] + start, | |
| 432 bytes_sent); | |
| 433 memio_PutReadResult(secret, bytes_sent); | |
| 434 } | |
| 435 break; | |
| 436 } | |
| 437 | |
| 438 case kWritePlaintext: { | |
| 439 if (length > 0) { | |
| 440 bytes_sent = PR_Write(data_->memio, | |
| 441 buffers_[buffer_index] + start, | |
| 442 length); | |
| 443 printf(" plaintext bytes written %d out of %d\n", | |
| 444 static_cast<int>(bytes_sent), static_cast<int>(length)); | |
| 445 } else { | |
| 446 printf(" no plaintext bytes to write.\n"); | |
| 447 } | |
| 448 if (bytes_sent < 0) bytes_sent = 0; | |
| 449 break; | |
| 450 } | |
| 451 } | |
| 452 return bytes_sent; | |
| 453 } | |
| OLD | NEW |