12b15cb3dSCy Schubert /*
22b15cb3dSCy Schubert * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
32b15cb3dSCy Schubert * Copyright (c) 2002-2006 Niels Provos <provos@citi.umich.edu>
42b15cb3dSCy Schubert * All rights reserved.
52b15cb3dSCy Schubert *
62b15cb3dSCy Schubert * Redistribution and use in source and binary forms, with or without
72b15cb3dSCy Schubert * modification, are permitted provided that the following conditions
82b15cb3dSCy Schubert * are met:
92b15cb3dSCy Schubert * 1. Redistributions of source code must retain the above copyright
102b15cb3dSCy Schubert * notice, this list of conditions and the following disclaimer.
112b15cb3dSCy Schubert * 2. Redistributions in binary form must reproduce the above copyright
122b15cb3dSCy Schubert * notice, this list of conditions and the following disclaimer in the
132b15cb3dSCy Schubert * documentation and/or other materials provided with the distribution.
142b15cb3dSCy Schubert * 3. The name of the author may not be used to endorse or promote products
152b15cb3dSCy Schubert * derived from this software without specific prior written permission.
162b15cb3dSCy Schubert *
172b15cb3dSCy Schubert * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
182b15cb3dSCy Schubert * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
192b15cb3dSCy Schubert * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
202b15cb3dSCy Schubert * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
212b15cb3dSCy Schubert * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
222b15cb3dSCy Schubert * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
232b15cb3dSCy Schubert * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
242b15cb3dSCy Schubert * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
252b15cb3dSCy Schubert * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
262b15cb3dSCy Schubert * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
272b15cb3dSCy Schubert */
282b15cb3dSCy Schubert
292b15cb3dSCy Schubert #include "event2/event-config.h"
302b15cb3dSCy Schubert #include "evconfig-private.h"
312b15cb3dSCy Schubert
322b15cb3dSCy Schubert #include <sys/types.h>
332b15cb3dSCy Schubert
342b15cb3dSCy Schubert #ifdef EVENT__HAVE_SYS_TIME_H
352b15cb3dSCy Schubert #include <sys/time.h>
362b15cb3dSCy Schubert #endif
372b15cb3dSCy Schubert
382b15cb3dSCy Schubert #include <errno.h>
392b15cb3dSCy Schubert #include <stdio.h>
402b15cb3dSCy Schubert #include <stdlib.h>
412b15cb3dSCy Schubert #include <string.h>
422b15cb3dSCy Schubert #ifdef EVENT__HAVE_STDARG_H
432b15cb3dSCy Schubert #include <stdarg.h>
442b15cb3dSCy Schubert #endif
452b15cb3dSCy Schubert #ifdef EVENT__HAVE_UNISTD_H
462b15cb3dSCy Schubert #include <unistd.h>
472b15cb3dSCy Schubert #endif
482b15cb3dSCy Schubert
492b15cb3dSCy Schubert #ifdef _WIN32
502b15cb3dSCy Schubert #include <winsock2.h>
512b15cb3dSCy Schubert #include <ws2tcpip.h>
522b15cb3dSCy Schubert #endif
532b15cb3dSCy Schubert
542b15cb3dSCy Schubert #ifdef EVENT__HAVE_SYS_SOCKET_H
552b15cb3dSCy Schubert #include <sys/socket.h>
562b15cb3dSCy Schubert #endif
572b15cb3dSCy Schubert #ifdef EVENT__HAVE_NETINET_IN_H
582b15cb3dSCy Schubert #include <netinet/in.h>
592b15cb3dSCy Schubert #endif
602b15cb3dSCy Schubert #ifdef EVENT__HAVE_NETINET_IN6_H
612b15cb3dSCy Schubert #include <netinet/in6.h>
622b15cb3dSCy Schubert #endif
632b15cb3dSCy Schubert
642b15cb3dSCy Schubert #include "event2/util.h"
652b15cb3dSCy Schubert #include "event2/bufferevent.h"
662b15cb3dSCy Schubert #include "event2/buffer.h"
672b15cb3dSCy Schubert #include "event2/bufferevent_struct.h"
682b15cb3dSCy Schubert #include "event2/bufferevent_compat.h"
692b15cb3dSCy Schubert #include "event2/event.h"
702b15cb3dSCy Schubert #include "log-internal.h"
712b15cb3dSCy Schubert #include "mm-internal.h"
722b15cb3dSCy Schubert #include "bufferevent-internal.h"
732b15cb3dSCy Schubert #include "util-internal.h"
742b15cb3dSCy Schubert #ifdef _WIN32
752b15cb3dSCy Schubert #include "iocp-internal.h"
762b15cb3dSCy Schubert #endif
772b15cb3dSCy Schubert
782b15cb3dSCy Schubert /* prototypes */
792b15cb3dSCy Schubert static int be_socket_enable(struct bufferevent *, short);
802b15cb3dSCy Schubert static int be_socket_disable(struct bufferevent *, short);
812b15cb3dSCy Schubert static void be_socket_destruct(struct bufferevent *);
822b15cb3dSCy Schubert static int be_socket_flush(struct bufferevent *, short, enum bufferevent_flush_mode);
832b15cb3dSCy Schubert static int be_socket_ctrl(struct bufferevent *, enum bufferevent_ctrl_op, union bufferevent_ctrl_data *);
842b15cb3dSCy Schubert
852b15cb3dSCy Schubert static void be_socket_setfd(struct bufferevent *, evutil_socket_t);
862b15cb3dSCy Schubert
872b15cb3dSCy Schubert const struct bufferevent_ops bufferevent_ops_socket = {
882b15cb3dSCy Schubert "socket",
892b15cb3dSCy Schubert evutil_offsetof(struct bufferevent_private, bev),
902b15cb3dSCy Schubert be_socket_enable,
912b15cb3dSCy Schubert be_socket_disable,
922b15cb3dSCy Schubert NULL, /* unlink */
932b15cb3dSCy Schubert be_socket_destruct,
94*a466cc55SCy Schubert bufferevent_generic_adj_existing_timeouts_,
952b15cb3dSCy Schubert be_socket_flush,
962b15cb3dSCy Schubert be_socket_ctrl,
972b15cb3dSCy Schubert };
982b15cb3dSCy Schubert
99*a466cc55SCy Schubert const struct sockaddr*
bufferevent_socket_get_conn_address_(struct bufferevent * bev)100*a466cc55SCy Schubert bufferevent_socket_get_conn_address_(struct bufferevent *bev)
101*a466cc55SCy Schubert {
102*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
103*a466cc55SCy Schubert return (struct sockaddr *)&bev_p->conn_address;
104*a466cc55SCy Schubert }
105*a466cc55SCy Schubert
106*a466cc55SCy Schubert void
bufferevent_socket_set_conn_address_fd_(struct bufferevent * bev,evutil_socket_t fd)107*a466cc55SCy Schubert bufferevent_socket_set_conn_address_fd_(struct bufferevent *bev,
108*a466cc55SCy Schubert evutil_socket_t fd)
109*a466cc55SCy Schubert {
110*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
111*a466cc55SCy Schubert
112*a466cc55SCy Schubert socklen_t len = sizeof(bev_p->conn_address);
113*a466cc55SCy Schubert
114*a466cc55SCy Schubert struct sockaddr *addr = (struct sockaddr *)&bev_p->conn_address;
115*a466cc55SCy Schubert if (addr->sa_family != AF_UNSPEC)
116*a466cc55SCy Schubert getpeername(fd, addr, &len);
117*a466cc55SCy Schubert }
118*a466cc55SCy Schubert
119*a466cc55SCy Schubert void
bufferevent_socket_set_conn_address_(struct bufferevent * bev,struct sockaddr * addr,size_t addrlen)120*a466cc55SCy Schubert bufferevent_socket_set_conn_address_(struct bufferevent *bev,
121*a466cc55SCy Schubert struct sockaddr *addr, size_t addrlen)
122*a466cc55SCy Schubert {
123*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
124*a466cc55SCy Schubert EVUTIL_ASSERT(addrlen <= sizeof(bev_p->conn_address));
125*a466cc55SCy Schubert memcpy(&bev_p->conn_address, addr, addrlen);
126*a466cc55SCy Schubert }
1272b15cb3dSCy Schubert
1282b15cb3dSCy Schubert static void
bufferevent_socket_outbuf_cb(struct evbuffer * buf,const struct evbuffer_cb_info * cbinfo,void * arg)1292b15cb3dSCy Schubert bufferevent_socket_outbuf_cb(struct evbuffer *buf,
1302b15cb3dSCy Schubert const struct evbuffer_cb_info *cbinfo,
1312b15cb3dSCy Schubert void *arg)
1322b15cb3dSCy Schubert {
1332b15cb3dSCy Schubert struct bufferevent *bufev = arg;
134*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
1352b15cb3dSCy Schubert
1362b15cb3dSCy Schubert if (cbinfo->n_added &&
1372b15cb3dSCy Schubert (bufev->enabled & EV_WRITE) &&
1382b15cb3dSCy Schubert !event_pending(&bufev->ev_write, EV_WRITE, NULL) &&
1392b15cb3dSCy Schubert !bufev_p->write_suspended) {
1402b15cb3dSCy Schubert /* Somebody added data to the buffer, and we would like to
1412b15cb3dSCy Schubert * write, and we were not writing. So, start writing. */
142*a466cc55SCy Schubert if (bufferevent_add_event_(&bufev->ev_write, &bufev->timeout_write) == -1) {
1432b15cb3dSCy Schubert /* Should we log this? */
1442b15cb3dSCy Schubert }
1452b15cb3dSCy Schubert }
1462b15cb3dSCy Schubert }
1472b15cb3dSCy Schubert
1482b15cb3dSCy Schubert static void
bufferevent_readcb(evutil_socket_t fd,short event,void * arg)1492b15cb3dSCy Schubert bufferevent_readcb(evutil_socket_t fd, short event, void *arg)
1502b15cb3dSCy Schubert {
1512b15cb3dSCy Schubert struct bufferevent *bufev = arg;
152*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
1532b15cb3dSCy Schubert struct evbuffer *input;
1542b15cb3dSCy Schubert int res = 0;
1552b15cb3dSCy Schubert short what = BEV_EVENT_READING;
1562b15cb3dSCy Schubert ev_ssize_t howmuch = -1, readmax=-1;
1572b15cb3dSCy Schubert
1582b15cb3dSCy Schubert bufferevent_incref_and_lock_(bufev);
1592b15cb3dSCy Schubert
1602b15cb3dSCy Schubert if (event == EV_TIMEOUT) {
1612b15cb3dSCy Schubert /* Note that we only check for event==EV_TIMEOUT. If
1622b15cb3dSCy Schubert * event==EV_TIMEOUT|EV_READ, we can safely ignore the
1632b15cb3dSCy Schubert * timeout, since a read has occurred */
1642b15cb3dSCy Schubert what |= BEV_EVENT_TIMEOUT;
1652b15cb3dSCy Schubert goto error;
1662b15cb3dSCy Schubert }
1672b15cb3dSCy Schubert
1682b15cb3dSCy Schubert input = bufev->input;
1692b15cb3dSCy Schubert
1702b15cb3dSCy Schubert /*
1712b15cb3dSCy Schubert * If we have a high watermark configured then we don't want to
1722b15cb3dSCy Schubert * read more data than would make us reach the watermark.
1732b15cb3dSCy Schubert */
1742b15cb3dSCy Schubert if (bufev->wm_read.high != 0) {
1752b15cb3dSCy Schubert howmuch = bufev->wm_read.high - evbuffer_get_length(input);
1762b15cb3dSCy Schubert /* we somehow lowered the watermark, stop reading */
1772b15cb3dSCy Schubert if (howmuch <= 0) {
1782b15cb3dSCy Schubert bufferevent_wm_suspend_read(bufev);
1792b15cb3dSCy Schubert goto done;
1802b15cb3dSCy Schubert }
1812b15cb3dSCy Schubert }
1822b15cb3dSCy Schubert readmax = bufferevent_get_read_max_(bufev_p);
1832b15cb3dSCy Schubert if (howmuch < 0 || howmuch > readmax) /* The use of -1 for "unlimited"
1842b15cb3dSCy Schubert * uglifies this code. XXXX */
1852b15cb3dSCy Schubert howmuch = readmax;
1862b15cb3dSCy Schubert if (bufev_p->read_suspended)
1872b15cb3dSCy Schubert goto done;
1882b15cb3dSCy Schubert
1892b15cb3dSCy Schubert evbuffer_unfreeze(input, 0);
1902b15cb3dSCy Schubert res = evbuffer_read(input, fd, (int)howmuch); /* XXXX evbuffer_read would do better to take and return ev_ssize_t */
1912b15cb3dSCy Schubert evbuffer_freeze(input, 0);
1922b15cb3dSCy Schubert
1932b15cb3dSCy Schubert if (res == -1) {
1942b15cb3dSCy Schubert int err = evutil_socket_geterror(fd);
1952b15cb3dSCy Schubert if (EVUTIL_ERR_RW_RETRIABLE(err))
1962b15cb3dSCy Schubert goto reschedule;
197*a466cc55SCy Schubert if (EVUTIL_ERR_CONNECT_REFUSED(err)) {
198*a466cc55SCy Schubert bufev_p->connection_refused = 1;
199*a466cc55SCy Schubert goto done;
200*a466cc55SCy Schubert }
2012b15cb3dSCy Schubert /* error case */
2022b15cb3dSCy Schubert what |= BEV_EVENT_ERROR;
2032b15cb3dSCy Schubert } else if (res == 0) {
2042b15cb3dSCy Schubert /* eof case */
2052b15cb3dSCy Schubert what |= BEV_EVENT_EOF;
2062b15cb3dSCy Schubert }
2072b15cb3dSCy Schubert
2082b15cb3dSCy Schubert if (res <= 0)
2092b15cb3dSCy Schubert goto error;
2102b15cb3dSCy Schubert
2112b15cb3dSCy Schubert bufferevent_decrement_read_buckets_(bufev_p, res);
2122b15cb3dSCy Schubert
2132b15cb3dSCy Schubert /* Invoke the user callback - must always be called last */
2142b15cb3dSCy Schubert bufferevent_trigger_nolock_(bufev, EV_READ, 0);
2152b15cb3dSCy Schubert
2162b15cb3dSCy Schubert goto done;
2172b15cb3dSCy Schubert
2182b15cb3dSCy Schubert reschedule:
2192b15cb3dSCy Schubert goto done;
2202b15cb3dSCy Schubert
2212b15cb3dSCy Schubert error:
2222b15cb3dSCy Schubert bufferevent_disable(bufev, EV_READ);
2232b15cb3dSCy Schubert bufferevent_run_eventcb_(bufev, what, 0);
2242b15cb3dSCy Schubert
2252b15cb3dSCy Schubert done:
2262b15cb3dSCy Schubert bufferevent_decref_and_unlock_(bufev);
2272b15cb3dSCy Schubert }
2282b15cb3dSCy Schubert
2292b15cb3dSCy Schubert static void
bufferevent_writecb(evutil_socket_t fd,short event,void * arg)2302b15cb3dSCy Schubert bufferevent_writecb(evutil_socket_t fd, short event, void *arg)
2312b15cb3dSCy Schubert {
2322b15cb3dSCy Schubert struct bufferevent *bufev = arg;
233*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
2342b15cb3dSCy Schubert int res = 0;
2352b15cb3dSCy Schubert short what = BEV_EVENT_WRITING;
2362b15cb3dSCy Schubert int connected = 0;
2372b15cb3dSCy Schubert ev_ssize_t atmost = -1;
2382b15cb3dSCy Schubert
2392b15cb3dSCy Schubert bufferevent_incref_and_lock_(bufev);
2402b15cb3dSCy Schubert
2412b15cb3dSCy Schubert if (event == EV_TIMEOUT) {
2422b15cb3dSCy Schubert /* Note that we only check for event==EV_TIMEOUT. If
2432b15cb3dSCy Schubert * event==EV_TIMEOUT|EV_WRITE, we can safely ignore the
2442b15cb3dSCy Schubert * timeout, since a read has occurred */
2452b15cb3dSCy Schubert what |= BEV_EVENT_TIMEOUT;
2462b15cb3dSCy Schubert goto error;
2472b15cb3dSCy Schubert }
2482b15cb3dSCy Schubert if (bufev_p->connecting) {
2492b15cb3dSCy Schubert int c = evutil_socket_finished_connecting_(fd);
2502b15cb3dSCy Schubert /* we need to fake the error if the connection was refused
2512b15cb3dSCy Schubert * immediately - usually connection to localhost on BSD */
2522b15cb3dSCy Schubert if (bufev_p->connection_refused) {
2532b15cb3dSCy Schubert bufev_p->connection_refused = 0;
2542b15cb3dSCy Schubert c = -1;
2552b15cb3dSCy Schubert }
2562b15cb3dSCy Schubert
2572b15cb3dSCy Schubert if (c == 0)
2582b15cb3dSCy Schubert goto done;
2592b15cb3dSCy Schubert
2602b15cb3dSCy Schubert bufev_p->connecting = 0;
2612b15cb3dSCy Schubert if (c < 0) {
2622b15cb3dSCy Schubert event_del(&bufev->ev_write);
2632b15cb3dSCy Schubert event_del(&bufev->ev_read);
2642b15cb3dSCy Schubert bufferevent_run_eventcb_(bufev, BEV_EVENT_ERROR, 0);
2652b15cb3dSCy Schubert goto done;
2662b15cb3dSCy Schubert } else {
2672b15cb3dSCy Schubert connected = 1;
268*a466cc55SCy Schubert bufferevent_socket_set_conn_address_fd_(bufev, fd);
2692b15cb3dSCy Schubert #ifdef _WIN32
2702b15cb3dSCy Schubert if (BEV_IS_ASYNC(bufev)) {
2712b15cb3dSCy Schubert event_del(&bufev->ev_write);
2722b15cb3dSCy Schubert bufferevent_async_set_connected_(bufev);
2732b15cb3dSCy Schubert bufferevent_run_eventcb_(bufev,
2742b15cb3dSCy Schubert BEV_EVENT_CONNECTED, 0);
2752b15cb3dSCy Schubert goto done;
2762b15cb3dSCy Schubert }
2772b15cb3dSCy Schubert #endif
2782b15cb3dSCy Schubert bufferevent_run_eventcb_(bufev,
2792b15cb3dSCy Schubert BEV_EVENT_CONNECTED, 0);
2802b15cb3dSCy Schubert if (!(bufev->enabled & EV_WRITE) ||
2812b15cb3dSCy Schubert bufev_p->write_suspended) {
2822b15cb3dSCy Schubert event_del(&bufev->ev_write);
2832b15cb3dSCy Schubert goto done;
2842b15cb3dSCy Schubert }
2852b15cb3dSCy Schubert }
2862b15cb3dSCy Schubert }
2872b15cb3dSCy Schubert
2882b15cb3dSCy Schubert atmost = bufferevent_get_write_max_(bufev_p);
2892b15cb3dSCy Schubert
2902b15cb3dSCy Schubert if (bufev_p->write_suspended)
2912b15cb3dSCy Schubert goto done;
2922b15cb3dSCy Schubert
2932b15cb3dSCy Schubert if (evbuffer_get_length(bufev->output)) {
2942b15cb3dSCy Schubert evbuffer_unfreeze(bufev->output, 1);
2952b15cb3dSCy Schubert res = evbuffer_write_atmost(bufev->output, fd, atmost);
2962b15cb3dSCy Schubert evbuffer_freeze(bufev->output, 1);
2972b15cb3dSCy Schubert if (res == -1) {
2982b15cb3dSCy Schubert int err = evutil_socket_geterror(fd);
2992b15cb3dSCy Schubert if (EVUTIL_ERR_RW_RETRIABLE(err))
3002b15cb3dSCy Schubert goto reschedule;
3012b15cb3dSCy Schubert what |= BEV_EVENT_ERROR;
3022b15cb3dSCy Schubert } else if (res == 0) {
3032b15cb3dSCy Schubert /* eof case
3042b15cb3dSCy Schubert XXXX Actually, a 0 on write doesn't indicate
3052b15cb3dSCy Schubert an EOF. An ECONNRESET might be more typical.
3062b15cb3dSCy Schubert */
3072b15cb3dSCy Schubert what |= BEV_EVENT_EOF;
3082b15cb3dSCy Schubert }
3092b15cb3dSCy Schubert if (res <= 0)
3102b15cb3dSCy Schubert goto error;
3112b15cb3dSCy Schubert
3122b15cb3dSCy Schubert bufferevent_decrement_write_buckets_(bufev_p, res);
3132b15cb3dSCy Schubert }
3142b15cb3dSCy Schubert
3152b15cb3dSCy Schubert if (evbuffer_get_length(bufev->output) == 0) {
3162b15cb3dSCy Schubert event_del(&bufev->ev_write);
3172b15cb3dSCy Schubert }
3182b15cb3dSCy Schubert
3192b15cb3dSCy Schubert /*
3202b15cb3dSCy Schubert * Invoke the user callback if our buffer is drained or below the
3212b15cb3dSCy Schubert * low watermark.
3222b15cb3dSCy Schubert */
3232b15cb3dSCy Schubert if (res || !connected) {
3242b15cb3dSCy Schubert bufferevent_trigger_nolock_(bufev, EV_WRITE, 0);
3252b15cb3dSCy Schubert }
3262b15cb3dSCy Schubert
3272b15cb3dSCy Schubert goto done;
3282b15cb3dSCy Schubert
3292b15cb3dSCy Schubert reschedule:
3302b15cb3dSCy Schubert if (evbuffer_get_length(bufev->output) == 0) {
3312b15cb3dSCy Schubert event_del(&bufev->ev_write);
3322b15cb3dSCy Schubert }
3332b15cb3dSCy Schubert goto done;
3342b15cb3dSCy Schubert
3352b15cb3dSCy Schubert error:
3362b15cb3dSCy Schubert bufferevent_disable(bufev, EV_WRITE);
3372b15cb3dSCy Schubert bufferevent_run_eventcb_(bufev, what, 0);
3382b15cb3dSCy Schubert
3392b15cb3dSCy Schubert done:
3402b15cb3dSCy Schubert bufferevent_decref_and_unlock_(bufev);
3412b15cb3dSCy Schubert }
3422b15cb3dSCy Schubert
3432b15cb3dSCy Schubert struct bufferevent *
bufferevent_socket_new(struct event_base * base,evutil_socket_t fd,int options)3442b15cb3dSCy Schubert bufferevent_socket_new(struct event_base *base, evutil_socket_t fd,
3452b15cb3dSCy Schubert int options)
3462b15cb3dSCy Schubert {
3472b15cb3dSCy Schubert struct bufferevent_private *bufev_p;
3482b15cb3dSCy Schubert struct bufferevent *bufev;
3492b15cb3dSCy Schubert
3502b15cb3dSCy Schubert #ifdef _WIN32
3512b15cb3dSCy Schubert if (base && event_base_get_iocp_(base))
3522b15cb3dSCy Schubert return bufferevent_async_new_(base, fd, options);
3532b15cb3dSCy Schubert #endif
3542b15cb3dSCy Schubert
3552b15cb3dSCy Schubert if ((bufev_p = mm_calloc(1, sizeof(struct bufferevent_private)))== NULL)
3562b15cb3dSCy Schubert return NULL;
3572b15cb3dSCy Schubert
3582b15cb3dSCy Schubert if (bufferevent_init_common_(bufev_p, base, &bufferevent_ops_socket,
3592b15cb3dSCy Schubert options) < 0) {
3602b15cb3dSCy Schubert mm_free(bufev_p);
3612b15cb3dSCy Schubert return NULL;
3622b15cb3dSCy Schubert }
3632b15cb3dSCy Schubert bufev = &bufev_p->bev;
3642b15cb3dSCy Schubert evbuffer_set_flags(bufev->output, EVBUFFER_FLAG_DRAINS_TO_FD);
3652b15cb3dSCy Schubert
3662b15cb3dSCy Schubert event_assign(&bufev->ev_read, bufev->ev_base, fd,
3672b15cb3dSCy Schubert EV_READ|EV_PERSIST|EV_FINALIZE, bufferevent_readcb, bufev);
3682b15cb3dSCy Schubert event_assign(&bufev->ev_write, bufev->ev_base, fd,
3692b15cb3dSCy Schubert EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bufev);
3702b15cb3dSCy Schubert
3712b15cb3dSCy Schubert evbuffer_add_cb(bufev->output, bufferevent_socket_outbuf_cb, bufev);
3722b15cb3dSCy Schubert
3732b15cb3dSCy Schubert evbuffer_freeze(bufev->input, 0);
3742b15cb3dSCy Schubert evbuffer_freeze(bufev->output, 1);
3752b15cb3dSCy Schubert
3762b15cb3dSCy Schubert return bufev;
3772b15cb3dSCy Schubert }
3782b15cb3dSCy Schubert
3792b15cb3dSCy Schubert int
bufferevent_socket_connect(struct bufferevent * bev,const struct sockaddr * sa,int socklen)3802b15cb3dSCy Schubert bufferevent_socket_connect(struct bufferevent *bev,
381*a466cc55SCy Schubert const struct sockaddr *sa, int socklen)
3822b15cb3dSCy Schubert {
383*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bev);
3842b15cb3dSCy Schubert
3852b15cb3dSCy Schubert evutil_socket_t fd;
3862b15cb3dSCy Schubert int r = 0;
3872b15cb3dSCy Schubert int result=-1;
3882b15cb3dSCy Schubert int ownfd = 0;
3892b15cb3dSCy Schubert
3902b15cb3dSCy Schubert bufferevent_incref_and_lock_(bev);
3912b15cb3dSCy Schubert
3922b15cb3dSCy Schubert fd = bufferevent_getfd(bev);
3932b15cb3dSCy Schubert if (fd < 0) {
3942b15cb3dSCy Schubert if (!sa)
3952b15cb3dSCy Schubert goto done;
3962b15cb3dSCy Schubert fd = evutil_socket_(sa->sa_family,
3972b15cb3dSCy Schubert SOCK_STREAM|EVUTIL_SOCK_NONBLOCK, 0);
3982b15cb3dSCy Schubert if (fd < 0)
399*a466cc55SCy Schubert goto freesock;
4002b15cb3dSCy Schubert ownfd = 1;
4012b15cb3dSCy Schubert }
4022b15cb3dSCy Schubert if (sa) {
4032b15cb3dSCy Schubert #ifdef _WIN32
4042b15cb3dSCy Schubert if (bufferevent_async_can_connect_(bev)) {
4052b15cb3dSCy Schubert bufferevent_setfd(bev, fd);
4062b15cb3dSCy Schubert r = bufferevent_async_connect_(bev, fd, sa, socklen);
4072b15cb3dSCy Schubert if (r < 0)
4082b15cb3dSCy Schubert goto freesock;
4092b15cb3dSCy Schubert bufev_p->connecting = 1;
4102b15cb3dSCy Schubert result = 0;
4112b15cb3dSCy Schubert goto done;
4122b15cb3dSCy Schubert } else
4132b15cb3dSCy Schubert #endif
4142b15cb3dSCy Schubert r = evutil_socket_connect_(&fd, sa, socklen);
4152b15cb3dSCy Schubert if (r < 0)
4162b15cb3dSCy Schubert goto freesock;
4172b15cb3dSCy Schubert }
4182b15cb3dSCy Schubert #ifdef _WIN32
4192b15cb3dSCy Schubert /* ConnectEx() isn't always around, even when IOCP is enabled.
4202b15cb3dSCy Schubert * Here, we borrow the socket object's write handler to fall back
4212b15cb3dSCy Schubert * on a non-blocking connect() when ConnectEx() is unavailable. */
4222b15cb3dSCy Schubert if (BEV_IS_ASYNC(bev)) {
4232b15cb3dSCy Schubert event_assign(&bev->ev_write, bev->ev_base, fd,
4242b15cb3dSCy Schubert EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bev);
4252b15cb3dSCy Schubert }
4262b15cb3dSCy Schubert #endif
4272b15cb3dSCy Schubert bufferevent_setfd(bev, fd);
4282b15cb3dSCy Schubert if (r == 0) {
4292b15cb3dSCy Schubert if (! be_socket_enable(bev, EV_WRITE)) {
4302b15cb3dSCy Schubert bufev_p->connecting = 1;
4312b15cb3dSCy Schubert result = 0;
4322b15cb3dSCy Schubert goto done;
4332b15cb3dSCy Schubert }
4342b15cb3dSCy Schubert } else if (r == 1) {
4352b15cb3dSCy Schubert /* The connect succeeded already. How very BSD of it. */
4362b15cb3dSCy Schubert result = 0;
4372b15cb3dSCy Schubert bufev_p->connecting = 1;
438*a466cc55SCy Schubert bufferevent_trigger_nolock_(bev, EV_WRITE, BEV_OPT_DEFER_CALLBACKS);
4392b15cb3dSCy Schubert } else {
4402b15cb3dSCy Schubert /* The connect failed already. How very BSD of it. */
4412b15cb3dSCy Schubert result = 0;
442*a466cc55SCy Schubert bufferevent_run_eventcb_(bev, BEV_EVENT_ERROR, BEV_OPT_DEFER_CALLBACKS);
443*a466cc55SCy Schubert bufferevent_disable(bev, EV_WRITE|EV_READ);
4442b15cb3dSCy Schubert }
4452b15cb3dSCy Schubert
4462b15cb3dSCy Schubert goto done;
4472b15cb3dSCy Schubert
4482b15cb3dSCy Schubert freesock:
4492b15cb3dSCy Schubert if (ownfd)
4502b15cb3dSCy Schubert evutil_closesocket(fd);
4512b15cb3dSCy Schubert done:
4522b15cb3dSCy Schubert bufferevent_decref_and_unlock_(bev);
4532b15cb3dSCy Schubert return result;
4542b15cb3dSCy Schubert }
4552b15cb3dSCy Schubert
4562b15cb3dSCy Schubert static void
bufferevent_connect_getaddrinfo_cb(int result,struct evutil_addrinfo * ai,void * arg)4572b15cb3dSCy Schubert bufferevent_connect_getaddrinfo_cb(int result, struct evutil_addrinfo *ai,
4582b15cb3dSCy Schubert void *arg)
4592b15cb3dSCy Schubert {
4602b15cb3dSCy Schubert struct bufferevent *bev = arg;
461*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
4622b15cb3dSCy Schubert int r;
4632b15cb3dSCy Schubert BEV_LOCK(bev);
4642b15cb3dSCy Schubert
4652b15cb3dSCy Schubert bufferevent_unsuspend_write_(bev, BEV_SUSPEND_LOOKUP);
4662b15cb3dSCy Schubert bufferevent_unsuspend_read_(bev, BEV_SUSPEND_LOOKUP);
4672b15cb3dSCy Schubert
468*a466cc55SCy Schubert bev_p->dns_request = NULL;
469*a466cc55SCy Schubert
470*a466cc55SCy Schubert if (result == EVUTIL_EAI_CANCEL) {
471*a466cc55SCy Schubert bev_p->dns_error = result;
472*a466cc55SCy Schubert bufferevent_decref_and_unlock_(bev);
473*a466cc55SCy Schubert return;
474*a466cc55SCy Schubert }
4752b15cb3dSCy Schubert if (result != 0) {
4762b15cb3dSCy Schubert bev_p->dns_error = result;
4772b15cb3dSCy Schubert bufferevent_run_eventcb_(bev, BEV_EVENT_ERROR, 0);
4782b15cb3dSCy Schubert bufferevent_decref_and_unlock_(bev);
4792b15cb3dSCy Schubert if (ai)
4802b15cb3dSCy Schubert evutil_freeaddrinfo(ai);
4812b15cb3dSCy Schubert return;
4822b15cb3dSCy Schubert }
4832b15cb3dSCy Schubert
4842b15cb3dSCy Schubert /* XXX use the other addrinfos? */
485*a466cc55SCy Schubert bufferevent_socket_set_conn_address_(bev, ai->ai_addr, (int)ai->ai_addrlen);
4862b15cb3dSCy Schubert r = bufferevent_socket_connect(bev, ai->ai_addr, (int)ai->ai_addrlen);
487*a466cc55SCy Schubert if (r < 0)
488*a466cc55SCy Schubert bufferevent_run_eventcb_(bev, BEV_EVENT_ERROR, 0);
4892b15cb3dSCy Schubert bufferevent_decref_and_unlock_(bev);
4902b15cb3dSCy Schubert evutil_freeaddrinfo(ai);
4912b15cb3dSCy Schubert }
4922b15cb3dSCy Schubert
4932b15cb3dSCy Schubert int
bufferevent_socket_connect_hostname(struct bufferevent * bev,struct evdns_base * evdns_base,int family,const char * hostname,int port)4942b15cb3dSCy Schubert bufferevent_socket_connect_hostname(struct bufferevent *bev,
4952b15cb3dSCy Schubert struct evdns_base *evdns_base, int family, const char *hostname, int port)
4962b15cb3dSCy Schubert {
4972b15cb3dSCy Schubert char portbuf[10];
4982b15cb3dSCy Schubert struct evutil_addrinfo hint;
499*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
5002b15cb3dSCy Schubert
5012b15cb3dSCy Schubert if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC)
5022b15cb3dSCy Schubert return -1;
5032b15cb3dSCy Schubert if (port < 1 || port > 65535)
5042b15cb3dSCy Schubert return -1;
5052b15cb3dSCy Schubert
5062b15cb3dSCy Schubert memset(&hint, 0, sizeof(hint));
5072b15cb3dSCy Schubert hint.ai_family = family;
5082b15cb3dSCy Schubert hint.ai_protocol = IPPROTO_TCP;
5092b15cb3dSCy Schubert hint.ai_socktype = SOCK_STREAM;
5102b15cb3dSCy Schubert
511*a466cc55SCy Schubert evutil_snprintf(portbuf, sizeof(portbuf), "%d", port);
512*a466cc55SCy Schubert
513*a466cc55SCy Schubert BEV_LOCK(bev);
514*a466cc55SCy Schubert bev_p->dns_error = 0;
515*a466cc55SCy Schubert
5162b15cb3dSCy Schubert bufferevent_suspend_write_(bev, BEV_SUSPEND_LOOKUP);
5172b15cb3dSCy Schubert bufferevent_suspend_read_(bev, BEV_SUSPEND_LOOKUP);
5182b15cb3dSCy Schubert
5192b15cb3dSCy Schubert bufferevent_incref_(bev);
520*a466cc55SCy Schubert bev_p->dns_request = evutil_getaddrinfo_async_(evdns_base, hostname,
521*a466cc55SCy Schubert portbuf, &hint, bufferevent_connect_getaddrinfo_cb, bev);
522*a466cc55SCy Schubert BEV_UNLOCK(bev);
5232b15cb3dSCy Schubert
5242b15cb3dSCy Schubert return 0;
5252b15cb3dSCy Schubert }
5262b15cb3dSCy Schubert
5272b15cb3dSCy Schubert int
bufferevent_socket_get_dns_error(struct bufferevent * bev)5282b15cb3dSCy Schubert bufferevent_socket_get_dns_error(struct bufferevent *bev)
5292b15cb3dSCy Schubert {
5302b15cb3dSCy Schubert int rv;
531*a466cc55SCy Schubert struct bufferevent_private *bev_p = BEV_UPCAST(bev);
5322b15cb3dSCy Schubert
5332b15cb3dSCy Schubert BEV_LOCK(bev);
5342b15cb3dSCy Schubert rv = bev_p->dns_error;
5352b15cb3dSCy Schubert BEV_UNLOCK(bev);
5362b15cb3dSCy Schubert
5372b15cb3dSCy Schubert return rv;
5382b15cb3dSCy Schubert }
5392b15cb3dSCy Schubert
5402b15cb3dSCy Schubert /*
5412b15cb3dSCy Schubert * Create a new buffered event object.
5422b15cb3dSCy Schubert *
5432b15cb3dSCy Schubert * The read callback is invoked whenever we read new data.
5442b15cb3dSCy Schubert * The write callback is invoked whenever the output buffer is drained.
5452b15cb3dSCy Schubert * The error callback is invoked on a write/read error or on EOF.
5462b15cb3dSCy Schubert *
5472b15cb3dSCy Schubert * Both read and write callbacks maybe NULL. The error callback is not
5482b15cb3dSCy Schubert * allowed to be NULL and have to be provided always.
5492b15cb3dSCy Schubert */
5502b15cb3dSCy Schubert
5512b15cb3dSCy Schubert struct bufferevent *
bufferevent_new(evutil_socket_t fd,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void * cbarg)5522b15cb3dSCy Schubert bufferevent_new(evutil_socket_t fd,
5532b15cb3dSCy Schubert bufferevent_data_cb readcb, bufferevent_data_cb writecb,
5542b15cb3dSCy Schubert bufferevent_event_cb eventcb, void *cbarg)
5552b15cb3dSCy Schubert {
5562b15cb3dSCy Schubert struct bufferevent *bufev;
5572b15cb3dSCy Schubert
5582b15cb3dSCy Schubert if (!(bufev = bufferevent_socket_new(NULL, fd, 0)))
5592b15cb3dSCy Schubert return NULL;
5602b15cb3dSCy Schubert
5612b15cb3dSCy Schubert bufferevent_setcb(bufev, readcb, writecb, eventcb, cbarg);
5622b15cb3dSCy Schubert
5632b15cb3dSCy Schubert return bufev;
5642b15cb3dSCy Schubert }
5652b15cb3dSCy Schubert
5662b15cb3dSCy Schubert
5672b15cb3dSCy Schubert static int
be_socket_enable(struct bufferevent * bufev,short event)5682b15cb3dSCy Schubert be_socket_enable(struct bufferevent *bufev, short event)
5692b15cb3dSCy Schubert {
570*a466cc55SCy Schubert if (event & EV_READ &&
571*a466cc55SCy Schubert bufferevent_add_event_(&bufev->ev_read, &bufev->timeout_read) == -1)
5722b15cb3dSCy Schubert return -1;
573*a466cc55SCy Schubert if (event & EV_WRITE &&
574*a466cc55SCy Schubert bufferevent_add_event_(&bufev->ev_write, &bufev->timeout_write) == -1)
5752b15cb3dSCy Schubert return -1;
5762b15cb3dSCy Schubert return 0;
5772b15cb3dSCy Schubert }
5782b15cb3dSCy Schubert
5792b15cb3dSCy Schubert static int
be_socket_disable(struct bufferevent * bufev,short event)5802b15cb3dSCy Schubert be_socket_disable(struct bufferevent *bufev, short event)
5812b15cb3dSCy Schubert {
582*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
5832b15cb3dSCy Schubert if (event & EV_READ) {
5842b15cb3dSCy Schubert if (event_del(&bufev->ev_read) == -1)
5852b15cb3dSCy Schubert return -1;
5862b15cb3dSCy Schubert }
5872b15cb3dSCy Schubert /* Don't actually disable the write if we are trying to connect. */
5882b15cb3dSCy Schubert if ((event & EV_WRITE) && ! bufev_p->connecting) {
5892b15cb3dSCy Schubert if (event_del(&bufev->ev_write) == -1)
5902b15cb3dSCy Schubert return -1;
5912b15cb3dSCy Schubert }
5922b15cb3dSCy Schubert return 0;
5932b15cb3dSCy Schubert }
5942b15cb3dSCy Schubert
5952b15cb3dSCy Schubert static void
be_socket_destruct(struct bufferevent * bufev)5962b15cb3dSCy Schubert be_socket_destruct(struct bufferevent *bufev)
5972b15cb3dSCy Schubert {
598*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
5992b15cb3dSCy Schubert evutil_socket_t fd;
600*a466cc55SCy Schubert EVUTIL_ASSERT(BEV_IS_SOCKET(bufev));
6012b15cb3dSCy Schubert
6022b15cb3dSCy Schubert fd = event_get_fd(&bufev->ev_read);
6032b15cb3dSCy Schubert
6042b15cb3dSCy Schubert if ((bufev_p->options & BEV_OPT_CLOSE_ON_FREE) && fd >= 0)
6052b15cb3dSCy Schubert EVUTIL_CLOSESOCKET(fd);
6062b15cb3dSCy Schubert
607*a466cc55SCy Schubert evutil_getaddrinfo_cancel_async_(bufev_p->dns_request);
6082b15cb3dSCy Schubert }
6092b15cb3dSCy Schubert
6102b15cb3dSCy Schubert static int
be_socket_flush(struct bufferevent * bev,short iotype,enum bufferevent_flush_mode mode)6112b15cb3dSCy Schubert be_socket_flush(struct bufferevent *bev, short iotype,
6122b15cb3dSCy Schubert enum bufferevent_flush_mode mode)
6132b15cb3dSCy Schubert {
6142b15cb3dSCy Schubert return 0;
6152b15cb3dSCy Schubert }
6162b15cb3dSCy Schubert
6172b15cb3dSCy Schubert
6182b15cb3dSCy Schubert static void
be_socket_setfd(struct bufferevent * bufev,evutil_socket_t fd)6192b15cb3dSCy Schubert be_socket_setfd(struct bufferevent *bufev, evutil_socket_t fd)
6202b15cb3dSCy Schubert {
621*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
622*a466cc55SCy Schubert
6232b15cb3dSCy Schubert BEV_LOCK(bufev);
624*a466cc55SCy Schubert EVUTIL_ASSERT(BEV_IS_SOCKET(bufev));
6252b15cb3dSCy Schubert
6262b15cb3dSCy Schubert event_del(&bufev->ev_read);
6272b15cb3dSCy Schubert event_del(&bufev->ev_write);
6282b15cb3dSCy Schubert
629*a466cc55SCy Schubert evbuffer_unfreeze(bufev->input, 0);
630*a466cc55SCy Schubert evbuffer_unfreeze(bufev->output, 1);
631*a466cc55SCy Schubert
6322b15cb3dSCy Schubert event_assign(&bufev->ev_read, bufev->ev_base, fd,
6332b15cb3dSCy Schubert EV_READ|EV_PERSIST|EV_FINALIZE, bufferevent_readcb, bufev);
6342b15cb3dSCy Schubert event_assign(&bufev->ev_write, bufev->ev_base, fd,
6352b15cb3dSCy Schubert EV_WRITE|EV_PERSIST|EV_FINALIZE, bufferevent_writecb, bufev);
6362b15cb3dSCy Schubert
6372b15cb3dSCy Schubert if (fd >= 0)
6382b15cb3dSCy Schubert bufferevent_enable(bufev, bufev->enabled);
6392b15cb3dSCy Schubert
640*a466cc55SCy Schubert evutil_getaddrinfo_cancel_async_(bufev_p->dns_request);
641*a466cc55SCy Schubert
6422b15cb3dSCy Schubert BEV_UNLOCK(bufev);
6432b15cb3dSCy Schubert }
6442b15cb3dSCy Schubert
6452b15cb3dSCy Schubert /* XXXX Should non-socket bufferevents support this? */
6462b15cb3dSCy Schubert int
bufferevent_priority_set(struct bufferevent * bufev,int priority)6472b15cb3dSCy Schubert bufferevent_priority_set(struct bufferevent *bufev, int priority)
6482b15cb3dSCy Schubert {
6492b15cb3dSCy Schubert int r = -1;
650*a466cc55SCy Schubert struct bufferevent_private *bufev_p = BEV_UPCAST(bufev);
6512b15cb3dSCy Schubert
6522b15cb3dSCy Schubert BEV_LOCK(bufev);
653*a466cc55SCy Schubert if (BEV_IS_ASYNC(bufev) || BEV_IS_FILTER(bufev) || BEV_IS_PAIR(bufev))
6542b15cb3dSCy Schubert goto done;
6552b15cb3dSCy Schubert
6562b15cb3dSCy Schubert if (event_priority_set(&bufev->ev_read, priority) == -1)
6572b15cb3dSCy Schubert goto done;
6582b15cb3dSCy Schubert if (event_priority_set(&bufev->ev_write, priority) == -1)
6592b15cb3dSCy Schubert goto done;
6602b15cb3dSCy Schubert
6612b15cb3dSCy Schubert event_deferred_cb_set_priority_(&bufev_p->deferred, priority);
6622b15cb3dSCy Schubert
6632b15cb3dSCy Schubert r = 0;
6642b15cb3dSCy Schubert done:
6652b15cb3dSCy Schubert BEV_UNLOCK(bufev);
6662b15cb3dSCy Schubert return r;
6672b15cb3dSCy Schubert }
6682b15cb3dSCy Schubert
6692b15cb3dSCy Schubert /* XXXX Should non-socket bufferevents support this? */
6702b15cb3dSCy Schubert int
bufferevent_base_set(struct event_base * base,struct bufferevent * bufev)6712b15cb3dSCy Schubert bufferevent_base_set(struct event_base *base, struct bufferevent *bufev)
6722b15cb3dSCy Schubert {
6732b15cb3dSCy Schubert int res = -1;
6742b15cb3dSCy Schubert
6752b15cb3dSCy Schubert BEV_LOCK(bufev);
676*a466cc55SCy Schubert if (!BEV_IS_SOCKET(bufev))
6772b15cb3dSCy Schubert goto done;
6782b15cb3dSCy Schubert
6792b15cb3dSCy Schubert bufev->ev_base = base;
6802b15cb3dSCy Schubert
6812b15cb3dSCy Schubert res = event_base_set(base, &bufev->ev_read);
6822b15cb3dSCy Schubert if (res == -1)
6832b15cb3dSCy Schubert goto done;
6842b15cb3dSCy Schubert
6852b15cb3dSCy Schubert res = event_base_set(base, &bufev->ev_write);
6862b15cb3dSCy Schubert done:
6872b15cb3dSCy Schubert BEV_UNLOCK(bufev);
6882b15cb3dSCy Schubert return res;
6892b15cb3dSCy Schubert }
6902b15cb3dSCy Schubert
6912b15cb3dSCy Schubert static int
be_socket_ctrl(struct bufferevent * bev,enum bufferevent_ctrl_op op,union bufferevent_ctrl_data * data)6922b15cb3dSCy Schubert be_socket_ctrl(struct bufferevent *bev, enum bufferevent_ctrl_op op,
6932b15cb3dSCy Schubert union bufferevent_ctrl_data *data)
6942b15cb3dSCy Schubert {
6952b15cb3dSCy Schubert switch (op) {
6962b15cb3dSCy Schubert case BEV_CTRL_SET_FD:
6972b15cb3dSCy Schubert be_socket_setfd(bev, data->fd);
6982b15cb3dSCy Schubert return 0;
6992b15cb3dSCy Schubert case BEV_CTRL_GET_FD:
7002b15cb3dSCy Schubert data->fd = event_get_fd(&bev->ev_read);
7012b15cb3dSCy Schubert return 0;
7022b15cb3dSCy Schubert case BEV_CTRL_GET_UNDERLYING:
7032b15cb3dSCy Schubert case BEV_CTRL_CANCEL_ALL:
7042b15cb3dSCy Schubert default:
7052b15cb3dSCy Schubert return -1;
7062b15cb3dSCy Schubert }
7072b15cb3dSCy Schubert }
708