| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2008 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 // Written in NSPR style to also be suitable for adding to the NSS demo suite | |
| 5 | |
| 6 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 7 // for details. All rights reserved. Use of this source code is governed by a | |
| 8 // BSD-style license that can be found in the LICENSE file. | |
| 9 | |
| 10 // This file is a modified copy of Chromium's src/net/base/nss_memio.c. | |
| 11 // char* has been changed to uint8_t* everywhere, and C++ casts are used. | |
| 12 // Revision 291806 (this should agree with "nss_rev" in DEPS). | |
| 13 | |
| 14 | |
| 15 /* memio is a simple NSPR I/O layer that lets you decouple NSS from | |
| 16 * the real network. It's rather like openssl's memory bio, | |
| 17 * and is useful when your app absolutely, positively doesn't | |
| 18 * want to let NSS do its own networking. | |
| 19 */ | |
| 20 #include "bin/net/nss_memio.h" | |
| 21 | |
| 22 #include <stdio.h> | |
| 23 #include <stdlib.h> | |
| 24 #include <string.h> | |
| 25 | |
| 26 #include "prerror.h" | |
| 27 #include "prinit.h" | |
| 28 #include "prlog.h" | |
| 29 | |
| 30 /*--------------- private memio types -----------------------*/ | |
| 31 | |
| 32 /*---------------------------------------------------------------------- | |
| 33 Simple private circular buffer class. Size cannot be changed once allocated. | |
| 34 ----------------------------------------------------------------------*/ | |
| 35 | |
| 36 struct memio_buffer { | |
| 37 int head; /* where to take next byte out of buf */ | |
| 38 int tail; /* where to put next byte into buf */ | |
| 39 int bufsize; /* number of bytes allocated to buf */ | |
| 40 /* TODO(port): error handling is pessimistic right now. | |
| 41 * Once an error is set, the socket is considered broken | |
| 42 * (PR_WOULD_BLOCK_ERROR not included). | |
| 43 */ | |
| 44 PRErrorCode last_err; | |
| 45 uint8_t* buf; | |
| 46 }; | |
| 47 | |
| 48 | |
| 49 /* The 'secret' field of a PRFileDesc created by memio_CreateIOLayer points | |
| 50 * to one of these. | |
| 51 * In the public header, we use struct memio_Private as a typesafe alias | |
| 52 * for this. This causes a few ugly typecasts in the private file, but | |
| 53 * seems safer. | |
| 54 */ | |
| 55 struct PRFilePrivate { | |
| 56 /* read requests are satisfied from this buffer */ | |
| 57 struct memio_buffer readbuf; | |
| 58 | |
| 59 /* write requests are satisfied from this buffer */ | |
| 60 struct memio_buffer writebuf; | |
| 61 | |
| 62 /* SSL needs to know socket peer's name */ | |
| 63 PRNetAddr peername; | |
| 64 | |
| 65 /* if set, empty I/O returns EOF instead of EWOULDBLOCK */ | |
| 66 int eof; | |
| 67 | |
| 68 /* if set, the number of bytes requested from readbuf that were not | |
| 69 * fulfilled (due to readbuf being empty) */ | |
| 70 int read_requested; | |
| 71 }; | |
| 72 | |
| 73 /*--------------- private memio_buffer functions ---------------------*/ | |
| 74 | |
| 75 /* Forward declarations. */ | |
| 76 | |
| 77 /* Allocate a memio_buffer of given size. */ | |
| 78 static void memio_buffer_new(struct memio_buffer *mb, int size); | |
| 79 | |
| 80 /* Deallocate a memio_buffer allocated by memio_buffer_new. */ | |
| 81 static void memio_buffer_destroy(struct memio_buffer *mb); | |
| 82 | |
| 83 /* How many bytes can be read out of the buffer without wrapping */ | |
| 84 static int memio_buffer_used_contiguous(const struct memio_buffer *mb); | |
| 85 | |
| 86 /* How many bytes exist after the wrap? */ | |
| 87 static int memio_buffer_wrapped_bytes(const struct memio_buffer *mb); | |
| 88 | |
| 89 /* How many bytes can be written into the buffer without wrapping */ | |
| 90 static int memio_buffer_unused_contiguous(const struct memio_buffer *mb); | |
| 91 | |
| 92 /* Write n bytes into the buffer. Returns number of bytes written. */ | |
| 93 static int memio_buffer_put(struct memio_buffer *mb, const uint8_t* buf, int n); | |
| 94 | |
| 95 /* Read n bytes from the buffer. Returns number of bytes read. */ | |
| 96 static int memio_buffer_get(struct memio_buffer *mb, uint8_t* buf, int n); | |
| 97 | |
| 98 /* Allocate a memio_buffer of given size. */ | |
| 99 static void memio_buffer_new(struct memio_buffer *mb, int size) { | |
| 100 mb->head = 0; | |
| 101 mb->tail = 0; | |
| 102 mb->bufsize = size; | |
| 103 mb->buf = static_cast<uint8_t*>(malloc(size)); | |
| 104 } | |
| 105 | |
| 106 /* Deallocate a memio_buffer allocated by memio_buffer_new. */ | |
| 107 static void memio_buffer_destroy(struct memio_buffer *mb) { | |
| 108 free(mb->buf); | |
| 109 mb->buf = NULL; | |
| 110 mb->bufsize = 0; | |
| 111 mb->head = 0; | |
| 112 mb->tail = 0; | |
| 113 } | |
| 114 | |
| 115 /* How many bytes can be read out of the buffer without wrapping */ | |
| 116 static int memio_buffer_used_contiguous(const struct memio_buffer *mb) { | |
| 117 return (((mb->tail >= mb->head) ? mb->tail : mb->bufsize) - mb->head); | |
| 118 } | |
| 119 | |
| 120 /* How many bytes exist after the wrap? */ | |
| 121 static int memio_buffer_wrapped_bytes(const struct memio_buffer *mb) { | |
| 122 return (mb->tail >= mb->head) ? 0 : mb->tail; | |
| 123 } | |
| 124 | |
| 125 /* How many bytes can be written into the buffer without wrapping */ | |
| 126 static int memio_buffer_unused_contiguous(const struct memio_buffer *mb) { | |
| 127 if (mb->head > mb->tail) return mb->head - mb->tail - 1; | |
| 128 return mb->bufsize - mb->tail - (mb->head == 0); | |
| 129 } | |
| 130 | |
| 131 /* Write n bytes into the buffer. Returns number of bytes written. */ | |
| 132 static int memio_buffer_put(struct memio_buffer *mb, | |
| 133 const uint8_t* buf, | |
| 134 int n) { | |
| 135 int len; | |
| 136 int transferred = 0; | |
| 137 | |
| 138 /* Handle part before wrap */ | |
| 139 len = PR_MIN(n, memio_buffer_unused_contiguous(mb)); | |
| 140 if (len > 0) { | |
| 141 /* Buffer not full */ | |
| 142 memmove(&mb->buf[mb->tail], buf, len); | |
| 143 mb->tail += len; | |
| 144 if (mb->tail == mb->bufsize) | |
| 145 mb->tail = 0; | |
| 146 n -= len; | |
| 147 buf += len; | |
| 148 transferred += len; | |
| 149 | |
| 150 /* Handle part after wrap */ | |
| 151 len = PR_MIN(n, memio_buffer_unused_contiguous(mb)); | |
| 152 if (len > 0) { | |
| 153 /* Output buffer still not full, input buffer still not empty */ | |
| 154 memmove(&mb->buf[mb->tail], buf, len); | |
| 155 mb->tail += len; | |
| 156 if (mb->tail == mb->bufsize) | |
| 157 mb->tail = 0; | |
| 158 transferred += len; | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 return transferred; | |
| 163 } | |
| 164 | |
| 165 | |
| 166 /* Read n bytes from the buffer. Returns number of bytes read. */ | |
| 167 static int memio_buffer_get(struct memio_buffer *mb, uint8_t* buf, int n) { | |
| 168 int len; | |
| 169 int transferred = 0; | |
| 170 | |
| 171 /* Handle part before wrap */ | |
| 172 len = PR_MIN(n, memio_buffer_used_contiguous(mb)); | |
| 173 if (len) { | |
| 174 memmove(buf, &mb->buf[mb->head], len); | |
| 175 mb->head += len; | |
| 176 if (mb->head == mb->bufsize) | |
| 177 mb->head = 0; | |
| 178 n -= len; | |
| 179 buf += len; | |
| 180 transferred += len; | |
| 181 | |
| 182 /* Handle part after wrap */ | |
| 183 len = PR_MIN(n, memio_buffer_used_contiguous(mb)); | |
| 184 if (len) { | |
| 185 memmove(buf, &mb->buf[mb->head], len); | |
| 186 mb->head += len; | |
| 187 if (mb->head == mb->bufsize) | |
| 188 mb->head = 0; | |
| 189 transferred += len; | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 return transferred; | |
| 194 } | |
| 195 | |
| 196 /*--------------- private memio functions -----------------------*/ | |
| 197 | |
| 198 static PRStatus PR_CALLBACK memio_Close(PRFileDesc *fd) { | |
| 199 struct PRFilePrivate *secret = fd->secret; | |
| 200 memio_buffer_destroy(&secret->readbuf); | |
| 201 memio_buffer_destroy(&secret->writebuf); | |
| 202 free(secret); | |
| 203 fd->dtor(fd); | |
| 204 return PR_SUCCESS; | |
| 205 } | |
| 206 | |
| 207 static PRStatus PR_CALLBACK memio_Shutdown(PRFileDesc *fd, PRIntn how) { | |
| 208 /* TODO: pass shutdown status to app somehow */ | |
| 209 return PR_SUCCESS; | |
| 210 } | |
| 211 | |
| 212 /* If there was a network error in the past taking bytes | |
| 213 * out of the buffer, return it to the next call that | |
| 214 * tries to read from an empty buffer. | |
| 215 */ | |
| 216 static int PR_CALLBACK memio_Recv(PRFileDesc *fd, | |
| 217 uint8_t *buf, | |
| 218 PRInt32 len, | |
| 219 PRIntn flags, | |
| 220 PRIntervalTime timeout) { | |
| 221 struct PRFilePrivate *secret; | |
| 222 struct memio_buffer *mb; | |
| 223 int rv; | |
| 224 | |
| 225 if (flags) { | |
| 226 PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); | |
| 227 return -1; | |
| 228 } | |
| 229 | |
| 230 secret = fd->secret; | |
| 231 mb = &secret->readbuf; | |
| 232 PR_ASSERT(mb->bufsize); | |
| 233 rv = memio_buffer_get(mb, buf, len); | |
| 234 if (rv == 0 && !secret->eof) { | |
| 235 secret->read_requested = len; | |
| 236 /* If there is no more data in the buffer, report any pending errors | |
| 237 * that were previously observed. Note that both the readbuf and the | |
| 238 * writebuf are checked for errors, since the application may have | |
| 239 * encountered a socket error while writing that would otherwise not | |
| 240 * be reported until the application attempted to write again - which | |
| 241 * it may never do. | |
| 242 */ | |
| 243 if (mb->last_err) | |
| 244 PR_SetError(mb->last_err, 0); | |
| 245 else if (secret->writebuf.last_err) | |
| 246 PR_SetError(secret->writebuf.last_err, 0); | |
| 247 else | |
| 248 PR_SetError(PR_WOULD_BLOCK_ERROR, 0); | |
| 249 return -1; | |
| 250 } | |
| 251 | |
| 252 secret->read_requested = 0; | |
| 253 return rv; | |
| 254 } | |
| 255 | |
| 256 static int PR_CALLBACK memio_Read(PRFileDesc *fd, uint8_t *buf, PRInt32 len) { | |
| 257 /* pull bytes from buffer */ | |
| 258 return memio_Recv(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT); | |
| 259 } | |
| 260 | |
| 261 static int PR_CALLBACK memio_Send(PRFileDesc *fd, | |
| 262 const uint8_t *buf, | |
| 263 PRInt32 len, | |
| 264 PRIntn flags, | |
| 265 PRIntervalTime timeout) { | |
| 266 struct PRFilePrivate *secret; | |
| 267 struct memio_buffer *mb; | |
| 268 int rv; | |
| 269 | |
| 270 secret = fd->secret; | |
| 271 mb = &secret->writebuf; | |
| 272 PR_ASSERT(mb->bufsize); | |
| 273 | |
| 274 /* Note that the read error state is not reported, because it cannot be | |
| 275 * reported until all buffered data has been read. If there is an error | |
| 276 * with the next layer, attempting to call Send again will report the | |
| 277 * error appropriately. | |
| 278 */ | |
| 279 if (mb->last_err) { | |
| 280 PR_SetError(mb->last_err, 0); | |
| 281 return -1; | |
| 282 } | |
| 283 rv = memio_buffer_put(mb, buf, len); | |
| 284 if (rv == 0) { | |
| 285 PR_SetError(PR_WOULD_BLOCK_ERROR, 0); | |
| 286 return -1; | |
| 287 } | |
| 288 return rv; | |
| 289 } | |
| 290 | |
| 291 static int PR_CALLBACK memio_Write(PRFileDesc *fd, | |
| 292 const uint8_t *buf, | |
| 293 PRInt32 len) { | |
| 294 /* append bytes to buffer */ | |
| 295 return memio_Send(fd, buf, len, 0, PR_INTERVAL_NO_TIMEOUT); | |
| 296 } | |
| 297 | |
| 298 static PRStatus PR_CALLBACK memio_GetPeerName(PRFileDesc *fd, PRNetAddr *addr) { | |
| 299 /* TODO: fail if memio_SetPeerName has not been called */ | |
| 300 struct PRFilePrivate *secret = fd->secret; | |
| 301 *addr = secret->peername; | |
| 302 return PR_SUCCESS; | |
| 303 } | |
| 304 | |
| 305 static PRStatus memio_GetSocketOption(PRFileDesc *fd, | |
| 306 PRSocketOptionData *data) { | |
| 307 /* | |
| 308 * Even in the original version for real tcp sockets, | |
| 309 * PR_SockOpt_Nonblocking is a special case that does not | |
| 310 * translate to a getsockopt() call | |
| 311 */ | |
| 312 if (PR_SockOpt_Nonblocking == data->option) { | |
| 313 data->value.non_blocking = PR_TRUE; | |
| 314 return PR_SUCCESS; | |
| 315 } | |
| 316 PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0); | |
| 317 return PR_FAILURE; | |
| 318 } | |
| 319 | |
| 320 /*--------------- private memio data -----------------------*/ | |
| 321 | |
| 322 /* | |
| 323 * Implement just the bare minimum number of methods needed to make ssl happy. | |
| 324 * | |
| 325 * Oddly, PR_Recv calls ssl_Recv calls ssl_SocketIsBlocking calls | |
| 326 * PR_GetSocketOption, so we have to provide an implementation of | |
| 327 * PR_GetSocketOption that just says "I'm nonblocking". | |
| 328 */ | |
| 329 | |
| 330 static struct PRIOMethods memio_layer_methods = { | |
| 331 PR_DESC_LAYERED, | |
| 332 memio_Close, | |
| 333 (PRReadFN)memio_Read, | |
| 334 (PRWriteFN)memio_Write, | |
| 335 NULL, | |
| 336 NULL, | |
| 337 NULL, | |
| 338 NULL, | |
| 339 NULL, | |
| 340 NULL, | |
| 341 NULL, | |
| 342 NULL, | |
| 343 NULL, | |
| 344 NULL, | |
| 345 NULL, | |
| 346 NULL, | |
| 347 memio_Shutdown, | |
| 348 (PRRecvFN)memio_Recv, | |
| 349 (PRSendFN)memio_Send, | |
| 350 NULL, | |
| 351 NULL, | |
| 352 NULL, | |
| 353 NULL, | |
| 354 NULL, | |
| 355 NULL, | |
| 356 memio_GetPeerName, | |
| 357 NULL, | |
| 358 NULL, | |
| 359 memio_GetSocketOption, | |
| 360 NULL, | |
| 361 NULL, | |
| 362 NULL, | |
| 363 NULL, | |
| 364 NULL, | |
| 365 NULL, | |
| 366 NULL, | |
| 367 }; | |
| 368 | |
| 369 static PRDescIdentity memio_identity = PR_INVALID_IO_LAYER; | |
| 370 | |
| 371 static PRStatus memio_InitializeLayerName(void) { | |
| 372 memio_identity = PR_GetUniqueIdentity("memio"); | |
| 373 return PR_SUCCESS; | |
| 374 } | |
| 375 | |
| 376 /*--------------- public memio functions -----------------------*/ | |
| 377 | |
| 378 PRFileDesc *memio_CreateIOLayer(int readbufsize, int writebufsize) { | |
| 379 PRFileDesc *fd; | |
| 380 struct PRFilePrivate *secret; | |
| 381 static PRCallOnceType once; | |
| 382 | |
| 383 PR_CallOnce(&once, memio_InitializeLayerName); | |
| 384 | |
| 385 fd = PR_CreateIOLayerStub(memio_identity, &memio_layer_methods); | |
| 386 secret = static_cast<PRFilePrivate*>(malloc(sizeof(struct PRFilePrivate))); | |
| 387 memset(secret, 0, sizeof(*secret)); | |
| 388 | |
| 389 memio_buffer_new(&secret->readbuf, readbufsize); | |
| 390 memio_buffer_new(&secret->writebuf, writebufsize); | |
| 391 fd->secret = secret; | |
| 392 return fd; | |
| 393 } | |
| 394 | |
| 395 void memio_SetPeerName(PRFileDesc* fd, const PRNetAddr* peername) { | |
| 396 PRFileDesc *memiofd = PR_GetIdentitiesLayer(fd, memio_identity); | |
| 397 struct PRFilePrivate *secret = memiofd->secret; | |
| 398 secret->peername = *peername; | |
| 399 } | |
| 400 | |
| 401 memio_Private* memio_GetSecret(PRFileDesc* fd) { | |
| 402 PRFileDesc* memiofd = PR_GetIdentitiesLayer(fd, memio_identity); | |
| 403 struct PRFilePrivate *secret = memiofd->secret; | |
| 404 return reinterpret_cast<memio_Private*>(secret); | |
| 405 } | |
| 406 | |
| 407 int memio_GetReadRequest(memio_Private *secret) { | |
| 408 return reinterpret_cast<PRFilePrivate*>(secret)->read_requested; | |
| 409 } | |
| 410 | |
| 411 int memio_GetReadParams(memio_Private* secret, uint8_t** buf) { | |
| 412 struct memio_buffer* mb = | |
| 413 &(reinterpret_cast<PRFilePrivate*>(secret)->readbuf); | |
| 414 PR_ASSERT(mb->bufsize); | |
| 415 | |
| 416 *buf = &mb->buf[mb->tail]; | |
| 417 return memio_buffer_unused_contiguous(mb); | |
| 418 } | |
| 419 | |
| 420 int memio_GetReadableBufferSize(memio_Private *secret) { | |
| 421 struct memio_buffer* mb = | |
| 422 &(reinterpret_cast<PRFilePrivate*>(secret)->readbuf); | |
| 423 PR_ASSERT(mb->bufsize); | |
| 424 | |
| 425 return memio_buffer_used_contiguous(mb); | |
| 426 } | |
| 427 | |
| 428 void memio_PutReadResult(memio_Private *secret, int bytes_read) { | |
| 429 struct memio_buffer* mb = | |
| 430 &(reinterpret_cast<PRFilePrivate*>(secret)->readbuf); | |
| 431 PR_ASSERT(mb->bufsize); | |
| 432 | |
| 433 if (bytes_read > 0) { | |
| 434 mb->tail += bytes_read; | |
| 435 if (mb->tail == mb->bufsize) | |
| 436 mb->tail = 0; | |
| 437 } else if (bytes_read == 0) { | |
| 438 /* Record EOF condition and report to caller when buffer runs dry */ | |
| 439 reinterpret_cast<PRFilePrivate*>(secret)->eof = PR_TRUE; | |
| 440 } else /* if (bytes_read < 0) */ { | |
| 441 mb->last_err = bytes_read; | |
| 442 } | |
| 443 } | |
| 444 | |
| 445 int memio_GetWriteParams(memio_Private *secret, | |
| 446 const uint8_t** buf1, unsigned int *len1, | |
| 447 const uint8_t** buf2, unsigned int *len2) { | |
| 448 struct memio_buffer* mb = | |
| 449 &(reinterpret_cast<PRFilePrivate*>(secret)->writebuf); | |
| 450 PR_ASSERT(mb->bufsize); | |
| 451 | |
| 452 if (mb->last_err) | |
| 453 return mb->last_err; | |
| 454 | |
| 455 *buf1 = &mb->buf[mb->head]; | |
| 456 *len1 = memio_buffer_used_contiguous(mb); | |
| 457 *buf2 = mb->buf; | |
| 458 *len2 = memio_buffer_wrapped_bytes(mb); | |
| 459 return 0; | |
| 460 } | |
| 461 | |
| 462 void memio_PutWriteResult(memio_Private *secret, int bytes_written) { | |
| 463 struct memio_buffer* mb = | |
| 464 &(reinterpret_cast<PRFilePrivate*>(secret)->writebuf); | |
| 465 PR_ASSERT(mb->bufsize); | |
| 466 | |
| 467 if (bytes_written > 0) { | |
| 468 mb->head += bytes_written; | |
| 469 if (mb->head >= mb->bufsize) | |
| 470 mb->head -= mb->bufsize; | |
| 471 } else if (bytes_written < 0) { | |
| 472 mb->last_err = bytes_written; | |
| 473 } | |
| 474 } | |
| 475 | |
| 476 /*--------------- private memio_buffer self-test -----------------*/ | |
| 477 | |
| 478 /* Even a trivial unit test is very helpful when doing circular buffers. */ | |
| 479 /*#define TRIVIAL_SELF_TEST*/ | |
| 480 #ifdef TRIVIAL_SELF_TEST | |
| 481 | |
| 482 #define TEST_BUFLEN 7 | |
| 483 | |
| 484 #define CHECKEQ(a, b) { \ | |
| 485 if ((a) != (b)) { \ | |
| 486 printf("%d != %d, Test failed line %d\n", a, b, __LINE__); \ | |
| 487 exit(1); \ | |
| 488 } \ | |
| 489 } | |
| 490 | |
| 491 #define FROM_STR(a) reinterpret_cast<const uint8_t*>(a) | |
| 492 | |
| 493 int main() { | |
| 494 struct memio_buffer mb; | |
| 495 uint8_t buf[100]; | |
| 496 int i; | |
| 497 | |
| 498 memio_buffer_new(&mb, TEST_BUFLEN); | |
| 499 | |
| 500 CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1); | |
| 501 CHECKEQ(memio_buffer_used_contiguous(&mb), 0); | |
| 502 | |
| 503 CHECKEQ(memio_buffer_put(&mb, FROM_STR("howdy"), 5), 5); | |
| 504 | |
| 505 CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); | |
| 506 CHECKEQ(memio_buffer_used_contiguous(&mb), 5); | |
| 507 CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0); | |
| 508 | |
| 509 CHECKEQ(memio_buffer_put(&mb, FROM_STR("!"), 1), 1); | |
| 510 | |
| 511 CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); | |
| 512 CHECKEQ(memio_buffer_used_contiguous(&mb), 6); | |
| 513 CHECKEQ(memio_buffer_wrapped_bytes(&mb), 0); | |
| 514 | |
| 515 CHECKEQ(memio_buffer_get(&mb, buf, 6), 6); | |
| 516 CHECKEQ(memcmp(buf, FROM_STR("howdy!"), 6), 0); | |
| 517 | |
| 518 CHECKEQ(memio_buffer_unused_contiguous(&mb), 1); | |
| 519 CHECKEQ(memio_buffer_used_contiguous(&mb), 0); | |
| 520 | |
| 521 CHECKEQ(memio_buffer_put(&mb, FROM_STR("01234"), 5), 5); | |
| 522 | |
| 523 CHECKEQ(memio_buffer_used_contiguous(&mb), 1); | |
| 524 CHECKEQ(memio_buffer_wrapped_bytes(&mb), 4); | |
| 525 CHECKEQ(memio_buffer_unused_contiguous(&mb), TEST_BUFLEN-1-5); | |
| 526 | |
| 527 CHECKEQ(memio_buffer_put(&mb, FROM_STR("5"), 1), 1); | |
| 528 | |
| 529 CHECKEQ(memio_buffer_unused_contiguous(&mb), 0); | |
| 530 CHECKEQ(memio_buffer_used_contiguous(&mb), 1); | |
| 531 | |
| 532 /* TODO: add more cases */ | |
| 533 | |
| 534 printf("Test passed\n"); | |
| 535 exit(0); | |
| 536 } | |
| 537 | |
| 538 #endif | |
| OLD | NEW |