PHP  
 PHP: Test and Code Coverage Analysis
downloads | QA | documentation | faq | getting help | mailing lists | reporting bugs | php.net sites | links | my php.net 
 

LCOV - code coverage report
Current view: top level - main/streams - transports.c (source / functions) Hit Total Coverage
Test: PHP Code Coverage Lines: 136 210 64.8 %
Date: 2014-11-22 Functions: 11 14 78.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :   +----------------------------------------------------------------------+
       3             :   | PHP Version 7                                                        |
       4             :   +----------------------------------------------------------------------+
       5             :   | Copyright (c) 1997-2014 The PHP Group                                |
       6             :   +----------------------------------------------------------------------+
       7             :   | This source file is subject to version 3.01 of the PHP license,      |
       8             :   | that is bundled with this package in the file LICENSE, and is        |
       9             :   | available through the world-wide-web at the following url:           |
      10             :   | http://www.php.net/license/3_01.txt                                  |
      11             :   | If you did not receive a copy of the PHP license and are unable to   |
      12             :   | obtain it through the world-wide-web, please send a note to          |
      13             :   | license@php.net so we can mail you a copy immediately.               |
      14             :   +----------------------------------------------------------------------+
      15             :   | Author: Wez Furlong <wez@thebrainroom.com>                           |
      16             :   +----------------------------------------------------------------------+
      17             : */
      18             : 
      19             : /* $Id$ */
      20             : 
      21             : #include "php.h"
      22             : #include "php_streams_int.h"
      23             : #include "ext/standard/file.h"
      24             : 
      25             : static HashTable xport_hash;
      26             : 
      27       41189 : PHPAPI HashTable *php_stream_xport_get_hash(void)
      28             : {
      29       41189 :         return &xport_hash;
      30             : }
      31             : 
      32      266625 : PHPAPI int php_stream_xport_register(const char *protocol, php_stream_transport_factory factory TSRMLS_DC)
      33             : {
      34      533250 :         return zend_hash_str_update_ptr(&xport_hash, protocol, strlen(protocol), factory) ? SUCCESS : FAILURE;
      35             : }
      36             : 
      37      143787 : PHPAPI int php_stream_xport_unregister(const char *protocol TSRMLS_DC)
      38             : {
      39      143787 :         return zend_hash_str_del(&xport_hash, protocol, strlen(protocol));
      40             : }
      41             : 
      42             : #define ERR_REPORT(out_err, fmt, arg) \
      43             :         if (out_err) { *out_err = strpprintf(0, fmt, arg); } \
      44             :         else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, arg); }
      45             : 
      46             : #define ERR_RETURN(out_err, local_err, fmt) \
      47             :         if (out_err) { *out_err = local_err; } \
      48             :         else { php_error_docref(NULL TSRMLS_CC, E_WARNING, fmt, local_err ? local_err->val : "Unspecified error"); \
      49             :                 if (local_err) { zend_string_release(local_err); local_err = NULL; } \
      50             :         }
      51             :         
      52        3693 : PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, int options,
      53             :                 int flags, const char *persistent_id,
      54             :                 struct timeval *timeout,
      55             :                 php_stream_context *context,
      56             :                 zend_string **error_string,
      57             :                 int *error_code
      58             :                 STREAMS_DC TSRMLS_DC)
      59             : {
      60        3693 :         php_stream *stream = NULL;
      61        3693 :         php_stream_transport_factory factory = NULL;
      62        3693 :         const char *p, *protocol = NULL;
      63        3693 :         int n = 0, failed = 0;
      64        3693 :         zend_string *error_text = NULL;
      65        3693 :         struct timeval default_timeout = { 0, 0 };
      66             :         
      67        3693 :         default_timeout.tv_sec = FG(default_socket_timeout);
      68             : 
      69        3693 :         if (timeout == NULL) {
      70        1764 :                 timeout = &default_timeout;
      71             :         }
      72             :         
      73             :         /* check for a cached persistent socket */
      74        3693 :         if (persistent_id) {
      75        1141 :                 switch(php_stream_from_persistent_id(persistent_id, &stream TSRMLS_CC)) {
      76             :                         case PHP_STREAM_PERSISTENT_SUCCESS:
      77             :                                 /* use a 0 second timeout when checking if the socket
      78             :                                  * has already died */
      79           1 :                                 if (PHP_STREAM_OPTION_RETURN_OK == php_stream_set_option(stream, PHP_STREAM_OPTION_CHECK_LIVENESS, 0, NULL)) {
      80           0 :                                         return stream;
      81             :                                 }
      82             :                                 /* dead - kill it */
      83           1 :                                 php_stream_pclose(stream);
      84           1 :                                 stream = NULL;
      85             : 
      86             :                                 /* fall through */
      87             : 
      88             :                         case PHP_STREAM_PERSISTENT_FAILURE:
      89             :                         default:
      90             :                                 /* failed; get a new one */
      91             :                                 ;
      92             :                 }
      93             :         }
      94             : 
      95       22070 :         for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) {
      96       18377 :                 n++;
      97             :         }
      98             : 
      99        6481 :         if ((*p == ':') && (n > 1) && !strncmp("://", p, 3)) {
     100        2788 :                 protocol = name;
     101        2788 :                 name = p + 3;
     102        2788 :                 namelen -= n + 3;
     103             :         } else {
     104         905 :                 protocol = "tcp";
     105         905 :                 n = 3;
     106             :         }
     107             : 
     108        3693 :         if (protocol) {
     109        3693 :                 char *tmp = estrndup(protocol, n);
     110        7386 :                 if (NULL == (factory = zend_hash_str_find_ptr(&xport_hash, tmp, n))) {
     111             :                         char wrapper_name[32];
     112             : 
     113           1 :                         if (n >= sizeof(wrapper_name))
     114           0 :                                 n = sizeof(wrapper_name) - 1;
     115           1 :                         PHP_STRLCPY(wrapper_name, protocol, sizeof(wrapper_name), n);
     116             :                 
     117           1 :                         ERR_REPORT(error_string, "Unable to find the socket transport \"%s\" - did you forget to enable it when you configured PHP?",
     118             :                                         wrapper_name);
     119             : 
     120           1 :                         efree(tmp);
     121           1 :                         return NULL;
     122             :                 }
     123        3692 :                 efree(tmp);
     124             :         }
     125             : 
     126        3692 :         if (factory == NULL) {
     127             :                 /* should never happen */
     128           0 :                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not find a factory !?");
     129           0 :                 return NULL;
     130             :         }
     131             : 
     132        3692 :         stream = (factory)(protocol, n,
     133             :                         (char*)name, namelen, persistent_id, options, flags, timeout,
     134             :                         context STREAMS_REL_CC TSRMLS_CC);
     135             : 
     136        3692 :         if (stream) {
     137        3692 :                 php_stream_context_set(stream, context TSRMLS_CC);
     138             : 
     139        3692 :                 if ((flags & STREAM_XPORT_SERVER) == 0) {
     140             :                         /* client */
     141             : 
     142        3568 :                         if (flags & (STREAM_XPORT_CONNECT|STREAM_XPORT_CONNECT_ASYNC)) {
     143        3567 :                                 if (-1 == php_stream_xport_connect(stream, name, namelen,
     144        3567 :                                                         flags & STREAM_XPORT_CONNECT_ASYNC ? 1 : 0,
     145             :                                                         timeout, &error_text, error_code TSRMLS_CC)) {
     146             : 
     147         976 :                                         ERR_RETURN(error_string, error_text, "connect() failed: %s");
     148             : 
     149         974 :                                         failed = 1;
     150             :                                 }
     151             :                         }
     152             : 
     153             :                 } else {
     154             :                         /* server */
     155         124 :                         if (flags & STREAM_XPORT_BIND) {
     156         124 :                                 if (0 != php_stream_xport_bind(stream, name, namelen, &error_text TSRMLS_CC)) {
     157           0 :                                         ERR_RETURN(error_string, error_text, "bind() failed: %s");
     158           0 :                                         failed = 1;
     159         124 :                                 } else if (flags & STREAM_XPORT_LISTEN) {
     160         113 :                                         zval *zbacklog = NULL;
     161         113 :                                         int backlog = 32;
     162             :                                         
     163         113 :                                         if (PHP_STREAM_CONTEXT(stream) && (zbacklog = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), "socket", "backlog")) != NULL) {
     164           0 :                                                 zval *ztmp = zbacklog;
     165             :                                                 
     166           0 :                                                 convert_to_long_ex(ztmp);
     167           0 :                                                 backlog = Z_LVAL_P(ztmp);
     168           0 :                                                 if (ztmp != zbacklog) {
     169           0 :                                                         zval_ptr_dtor(ztmp);
     170             :                                                 }
     171             :                                         }
     172             :                                         
     173         113 :                                         if (0 != php_stream_xport_listen(stream, backlog, &error_text TSRMLS_CC)) {
     174           0 :                                                 ERR_RETURN(error_string, error_text, "listen() failed: %s");
     175           0 :                                                 failed = 1;
     176             :                                         }
     177             :                                 }
     178             :                         }
     179             :                 }
     180             :         }
     181             : 
     182        3692 :         if (failed) {
     183             :                 /* failure means that they don't get a stream to play with */
     184         974 :                 if (persistent_id) {
     185           2 :                         php_stream_pclose(stream);
     186             :                 } else {
     187         972 :                         php_stream_close(stream);
     188             :                 }
     189         974 :                 stream = NULL;
     190             :         }
     191             : 
     192        3692 :         return stream;
     193             : }
     194             : 
     195             : /* Bind the stream to a local address */
     196         124 : PHPAPI int php_stream_xport_bind(php_stream *stream,
     197             :                 const char *name, size_t namelen,
     198             :                 zend_string **error_text
     199             :                 TSRMLS_DC)
     200             : {
     201             :         php_stream_xport_param param;
     202             :         int ret;
     203             :         
     204         124 :         memset(&param, 0, sizeof(param));
     205         124 :         param.op = STREAM_XPORT_OP_BIND;
     206         124 :         param.inputs.name = (char*)name;
     207         124 :         param.inputs.namelen = namelen;
     208         124 :         param.want_errortext = error_text ? 1 : 0;
     209             : 
     210         124 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     211             : 
     212         124 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     213         124 :                 if (error_text) {
     214         124 :                         *error_text = param.outputs.error_text;
     215             :                 }
     216             : 
     217         124 :                 return param.outputs.returncode;
     218             :         }
     219             : 
     220           0 :         return ret;
     221             : }
     222             : 
     223             : /* Connect to a remote address */
     224        3567 : PHPAPI int php_stream_xport_connect(php_stream *stream,
     225             :                 const char *name, size_t namelen,
     226             :                 int asynchronous,
     227             :                 struct timeval *timeout,
     228             :                 zend_string **error_text,
     229             :                 int *error_code
     230             :                 TSRMLS_DC)
     231             : {
     232             :         php_stream_xport_param param;
     233             :         int ret;
     234             :         
     235        3567 :         memset(&param, 0, sizeof(param));
     236        3567 :         param.op = asynchronous ? STREAM_XPORT_OP_CONNECT_ASYNC: STREAM_XPORT_OP_CONNECT;
     237        3567 :         param.inputs.name = (char*)name;
     238        3567 :         param.inputs.namelen = namelen;
     239        3567 :         param.inputs.timeout = timeout;
     240             : 
     241        3567 :         param.want_errortext = error_text ? 1 : 0;
     242             :         
     243        3567 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     244             : 
     245        3567 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     246        3567 :                 if (error_text) {
     247        3567 :                         *error_text = param.outputs.error_text;
     248             :                 }
     249        3567 :                 if (error_code) {
     250        3497 :                         *error_code = param.outputs.error_code;
     251             :                 }
     252        3567 :                 return param.outputs.returncode;
     253             :         }
     254             : 
     255           0 :         return ret;
     256             : 
     257             : }
     258             : 
     259             : /* Prepare to listen */
     260         113 : PHPAPI int php_stream_xport_listen(php_stream *stream, int backlog, zend_string **error_text TSRMLS_DC)
     261             : {
     262             :         php_stream_xport_param param;
     263             :         int ret;
     264             :         
     265         113 :         memset(&param, 0, sizeof(param));
     266         113 :         param.op = STREAM_XPORT_OP_LISTEN;
     267         113 :         param.inputs.backlog = backlog;
     268         113 :         param.want_errortext = error_text ? 1 : 0;
     269             :         
     270         113 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     271             : 
     272         113 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     273         113 :                 if (error_text) {
     274         113 :                         *error_text = param.outputs.error_text;
     275             :                 }
     276             : 
     277         113 :                 return param.outputs.returncode;
     278             :         }
     279             : 
     280           0 :         return ret;
     281             : }
     282             : 
     283             : /* Get the next client and their address (as a string) */
     284          97 : PHPAPI int php_stream_xport_accept(php_stream *stream, php_stream **client,
     285             :                 zend_string **textaddr,
     286             :                 void **addr, socklen_t *addrlen,
     287             :                 struct timeval *timeout,
     288             :                 zend_string **error_text
     289             :                 TSRMLS_DC)
     290             : {
     291             :         php_stream_xport_param param;
     292             :         int ret;
     293             : 
     294          97 :         memset(&param, 0, sizeof(param));
     295             : 
     296          97 :         param.op = STREAM_XPORT_OP_ACCEPT;
     297          97 :         param.inputs.timeout = timeout;
     298          97 :         param.want_addr = addr ? 1 : 0;
     299          97 :         param.want_textaddr = textaddr ? 1 : 0;
     300          97 :         param.want_errortext = error_text ? 1 : 0;
     301             :         
     302          97 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     303             : 
     304          96 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     305          96 :                 *client = param.outputs.client;
     306          96 :                 if (addr) {
     307           0 :                         *addr = param.outputs.addr;
     308           0 :                         *addrlen = param.outputs.addrlen;
     309             :                 }
     310          96 :                 if (textaddr) {
     311           0 :                         *textaddr = param.outputs.textaddr;
     312             :                 }
     313          96 :                 if (error_text) {
     314          96 :                         *error_text = param.outputs.error_text;
     315             :                 }
     316             : 
     317          96 :                 return param.outputs.returncode;
     318             :         }
     319           0 :         return ret;
     320             : }
     321             : 
     322           0 : PHPAPI int php_stream_xport_get_name(php_stream *stream, int want_peer,
     323             :                 zend_string **textaddr,
     324             :                 void **addr, socklen_t *addrlen
     325             :                 TSRMLS_DC)
     326             : {
     327             :         php_stream_xport_param param;
     328             :         int ret;
     329             : 
     330           0 :         memset(&param, 0, sizeof(param));
     331             : 
     332           0 :         param.op = want_peer ? STREAM_XPORT_OP_GET_PEER_NAME : STREAM_XPORT_OP_GET_NAME;
     333           0 :         param.want_addr = addr ? 1 : 0;
     334           0 :         param.want_textaddr = textaddr ? 1 : 0;
     335             :         
     336           0 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     337             : 
     338           0 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     339           0 :                 if (addr) {
     340           0 :                         *addr = param.outputs.addr;
     341           0 :                         *addrlen = param.outputs.addrlen;
     342             :                 }
     343           0 :                 if (textaddr) {
     344           0 :                         *textaddr = param.outputs.textaddr;
     345             :                 }
     346             : 
     347           0 :                 return param.outputs.returncode;
     348             :         }
     349           0 :         return ret;
     350             : }
     351             : 
     352         126 : PHPAPI int php_stream_xport_crypto_setup(php_stream *stream, php_stream_xport_crypt_method_t crypto_method, php_stream *session_stream TSRMLS_DC)
     353             : {
     354             :         php_stream_xport_crypto_param param;
     355             :         int ret;
     356             : 
     357         126 :         memset(&param, 0, sizeof(param));
     358         126 :         param.op = STREAM_XPORT_CRYPTO_OP_SETUP;
     359         126 :         param.inputs.method = crypto_method;
     360         126 :         param.inputs.session = session_stream;
     361             :         
     362         126 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
     363             : 
     364         126 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     365         123 :                 return param.outputs.returncode;
     366             :         }
     367             : 
     368           3 :         php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto");
     369             :         
     370           3 :         return ret;
     371             : }
     372             : 
     373         122 : PHPAPI int php_stream_xport_crypto_enable(php_stream *stream, int activate TSRMLS_DC)
     374             : {
     375             :         php_stream_xport_crypto_param param;
     376             :         int ret;
     377             : 
     378         122 :         memset(&param, 0, sizeof(param));
     379         122 :         param.op = STREAM_XPORT_CRYPTO_OP_ENABLE;
     380         122 :         param.inputs.activate = activate;
     381             :         
     382         122 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_CRYPTO_API, 0, &param);
     383             : 
     384         122 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     385         122 :                 return param.outputs.returncode;
     386             :         }
     387             : 
     388           0 :         php_error_docref("streams.crypto" TSRMLS_CC, E_WARNING, "this stream does not support SSL/crypto");
     389             :         
     390           0 :         return ret;
     391             : }
     392             : 
     393             : /* Similar to recv() system call; read data from the stream, optionally
     394             :  * peeking, optionally retrieving OOB data */
     395           0 : PHPAPI int php_stream_xport_recvfrom(php_stream *stream, char *buf, size_t buflen,
     396             :                 int flags, void **addr, socklen_t *addrlen, zend_string **textaddr
     397             :                 TSRMLS_DC)
     398             : {
     399             :         php_stream_xport_param param;
     400           0 :         int ret = 0;
     401           0 :         int recvd_len = 0;
     402             : #if 0
     403             :         int oob;
     404             : 
     405             :         if (flags == 0 && addr == NULL) {
     406             :                 return php_stream_read(stream, buf, buflen);
     407             :         }
     408             : 
     409             :         if (stream->readfilters.head) {
     410             :                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot peek or fetch OOB data from a filtered stream");
     411             :                 return -1;
     412             :         }
     413             :         
     414             :         oob = (flags & STREAM_OOB) == STREAM_OOB;
     415             : 
     416             :         if (!oob && addr == NULL) {
     417             :                 /* must be peeking at regular data; copy content from the buffer
     418             :                  * first, then adjust the pointer/len before handing off to the
     419             :                  * stream */
     420             :                 recvd_len = stream->writepos - stream->readpos;
     421             :                 if (recvd_len > buflen) {
     422             :                         recvd_len = buflen;
     423             :                 }
     424             :                 if (recvd_len) {
     425             :                         memcpy(buf, stream->readbuf, recvd_len);
     426             :                         buf += recvd_len;
     427             :                         buflen -= recvd_len;
     428             :                 }
     429             :                 /* if we filled their buffer, return */
     430             :                 if (buflen == 0) {
     431             :                         return recvd_len;
     432             :                 }
     433             :         }
     434             : #endif
     435             : 
     436             :         /* otherwise, we are going to bypass the buffer */
     437             :         
     438           0 :         memset(&param, 0, sizeof(param));
     439             : 
     440           0 :         param.op = STREAM_XPORT_OP_RECV;
     441           0 :         param.want_addr = addr ? 1 : 0;
     442           0 :         param.want_textaddr = textaddr ? 1 : 0;
     443           0 :         param.inputs.buf = buf;
     444           0 :         param.inputs.buflen = buflen;
     445           0 :         param.inputs.flags = flags;
     446             :         
     447           0 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     448             : 
     449           0 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     450           0 :                 if (addr) {
     451           0 :                         *addr = param.outputs.addr;
     452           0 :                         *addrlen = param.outputs.addrlen;
     453             :                 }
     454           0 :                 if (textaddr) {
     455           0 :                         *textaddr = param.outputs.textaddr;
     456             :                 }
     457           0 :                 return recvd_len + param.outputs.returncode;
     458             :         }
     459           0 :         return recvd_len ? recvd_len : -1;
     460             : }
     461             : 
     462             : /* Similar to send() system call; send data to the stream, optionally
     463             :  * sending it as OOB data */
     464           0 : PHPAPI int php_stream_xport_sendto(php_stream *stream, const char *buf, size_t buflen,
     465             :                 int flags, void *addr, socklen_t addrlen TSRMLS_DC)
     466             : {
     467             :         php_stream_xport_param param;
     468           0 :         int ret = 0;
     469             :         int oob;
     470             : 
     471             : #if 0
     472             :         if (flags == 0 && addr == NULL) {
     473             :                 return php_stream_write(stream, buf, buflen);
     474             :         }
     475             : #endif
     476             :         
     477           0 :         oob = (flags & STREAM_OOB) == STREAM_OOB;
     478             : 
     479           0 :         if ((oob || addr) && stream->writefilters.head) {
     480           0 :                 php_error_docref(NULL TSRMLS_CC, E_WARNING, "cannot write OOB data, or data to a targeted address on a filtered stream");
     481           0 :                 return -1;
     482             :         }
     483             :         
     484           0 :         memset(&param, 0, sizeof(param));
     485             : 
     486           0 :         param.op = STREAM_XPORT_OP_SEND;
     487           0 :         param.want_addr = addr ? 1 : 0;
     488           0 :         param.inputs.buf = (char*)buf;
     489           0 :         param.inputs.buflen = buflen;
     490           0 :         param.inputs.flags = flags;
     491           0 :         param.inputs.addr = addr;
     492           0 :         param.inputs.addrlen = addrlen;
     493             :         
     494           0 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     495             : 
     496           0 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     497           0 :                 return param.outputs.returncode;
     498             :         }
     499           0 :         return -1;
     500             : }
     501             : 
     502             : /* Similar to shutdown() system call; shut down part of a full-duplex
     503             :  * connection */
     504           5 : PHPAPI int php_stream_xport_shutdown(php_stream *stream, stream_shutdown_t how TSRMLS_DC)
     505             : {
     506             :         php_stream_xport_param param;
     507           5 :         int ret = 0;
     508             : 
     509           5 :         memset(&param, 0, sizeof(param));
     510             : 
     511           5 :         param.op = STREAM_XPORT_OP_SHUTDOWN;
     512           5 :         param.how = how;
     513             :         
     514           5 :         ret = php_stream_set_option(stream, PHP_STREAM_OPTION_XPORT_API, 0, &param);
     515             : 
     516           5 :         if (ret == PHP_STREAM_OPTION_RETURN_OK) {
     517           5 :                 return param.outputs.returncode;
     518             :         }
     519           0 :         return -1;
     520             : }
     521             : 
     522             : /*
     523             :  * Local variables:
     524             :  * tab-width: 4
     525             :  * c-basic-offset: 4
     526             :  * End:
     527             :  * vim600: noet sw=4 ts=4 fdm=marker
     528             :  * vim<600: noet sw=4 ts=4
     529             :  */

Generated by: LCOV version 1.10

Generated at Sat, 22 Nov 2014 23:01:30 +0000 (5 days ago)

Copyright © 2005-2014 The PHP Group
All rights reserved.