1 /*
2  * include/proto/stream_interface.h
3  * This file contains stream_interface function prototypes
4  *
5  * Copyright (C) 2000-2014 Willy Tarreau - w@1wt.eu
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation, version 2.1
10  * exclusively.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #ifndef _PROTO_STREAM_INTERFACE_H
23 #define _PROTO_STREAM_INTERFACE_H
24 
25 #include <stdlib.h>
26 
27 #include <common/config.h>
28 #include <types/server.h>
29 #include <types/stream.h>
30 #include <types/stream_interface.h>
31 #include <proto/applet.h>
32 #include <proto/channel.h>
33 #include <proto/connection.h>
34 
35 
36 extern struct si_ops si_embedded_ops;
37 extern struct si_ops si_conn_ops;
38 extern struct si_ops si_applet_ops;
39 extern struct data_cb si_conn_cb;
40 
41 /* main event functions used to move data between sockets and buffers */
42 int si_check_timeouts(struct stream_interface *si);
43 void si_report_error(struct stream_interface *si);
44 void si_retnclose(struct stream_interface *si, const struct buffer *msg);
45 int conn_si_send_proxy(struct connection *conn, unsigned int flag);
46 struct appctx *si_register_handler(struct stream_interface *si, struct applet *app);
47 void si_applet_wake_cb(struct stream_interface *si);
48 void si_update_rx(struct stream_interface *si);
49 void si_update_tx(struct stream_interface *si);
50 int si_cs_recv(struct conn_stream *cs);
51 struct task *si_cs_io_cb(struct task *t, void *ctx, unsigned short state);
52 void si_update_both(struct stream_interface *si_f, struct stream_interface *si_b);
53 void si_sync_send(struct stream_interface *si);
54 
55 /* returns the channel which receives data from this stream interface (input channel) */
si_ic(struct stream_interface * si)56 static inline struct channel *si_ic(struct stream_interface *si)
57 {
58 	if (si->flags & SI_FL_ISBACK)
59 		return &LIST_ELEM(si, struct stream *, si[1])->res;
60 	else
61 		return &LIST_ELEM(si, struct stream *, si[0])->req;
62 }
63 
64 /* returns the channel which feeds data to this stream interface (output channel) */
si_oc(struct stream_interface * si)65 static inline struct channel *si_oc(struct stream_interface *si)
66 {
67 	if (si->flags & SI_FL_ISBACK)
68 		return &LIST_ELEM(si, struct stream *, si[1])->req;
69 	else
70 		return &LIST_ELEM(si, struct stream *, si[0])->res;
71 }
72 
73 /* returns the buffer which receives data from this stream interface (input channel's buffer) */
si_ib(struct stream_interface * si)74 static inline struct buffer *si_ib(struct stream_interface *si)
75 {
76 	return &si_ic(si)->buf;
77 }
78 
79 /* returns the buffer which feeds data to this stream interface (output channel's buffer) */
si_ob(struct stream_interface * si)80 static inline struct buffer *si_ob(struct stream_interface *si)
81 {
82 	return &si_oc(si)->buf;
83 }
84 
85 /* returns the stream associated to a stream interface */
si_strm(struct stream_interface * si)86 static inline struct stream *si_strm(struct stream_interface *si)
87 {
88 	if (si->flags & SI_FL_ISBACK)
89 		return LIST_ELEM(si, struct stream *, si[1]);
90 	else
91 		return LIST_ELEM(si, struct stream *, si[0]);
92 }
93 
94 /* returns the task associated to this stream interface */
si_task(struct stream_interface * si)95 static inline struct task *si_task(struct stream_interface *si)
96 {
97 	if (si->flags & SI_FL_ISBACK)
98 		return LIST_ELEM(si, struct stream *, si[1])->task;
99 	else
100 		return LIST_ELEM(si, struct stream *, si[0])->task;
101 }
102 
103 /* returns the stream interface on the other side. Used during forwarding. */
si_opposite(struct stream_interface * si)104 static inline struct stream_interface *si_opposite(struct stream_interface *si)
105 {
106 	if (si->flags & SI_FL_ISBACK)
107 		return &LIST_ELEM(si, struct stream *, si[1])->si[0];
108 	else
109 		return &LIST_ELEM(si, struct stream *, si[0])->si[1];
110 }
111 
112 /* initializes a stream interface in the SI_ST_INI state. It's detached from
113  * any endpoint and only keeps its side which is expected to have already been
114  * set.
115  */
si_reset(struct stream_interface * si)116 static inline int si_reset(struct stream_interface *si)
117 {
118 	si->err_type       = SI_ET_NONE;
119 	si->conn_retries   = 0;  /* used for logging too */
120 	si->exp            = TICK_ETERNITY;
121 	si->flags         &= SI_FL_ISBACK;
122 	si->end            = NULL;
123 	si->state          = si->prev_state = SI_ST_INI;
124 	si->ops            = &si_embedded_ops;
125 	si->wait_event.tasklet = tasklet_new();
126 	if (!si->wait_event.tasklet)
127 		return -1;
128 	si->wait_event.tasklet->process = si_cs_io_cb;
129 	si->wait_event.tasklet->context = si;
130 	si->wait_event.events = 0;
131 	return 0;
132 }
133 
134 /* sets the current and previous state of a stream interface to <state>. This
135  * is mainly used to create one in the established state on incoming
136  * conncetions.
137  */
si_set_state(struct stream_interface * si,int state)138 static inline void si_set_state(struct stream_interface *si, int state)
139 {
140 	si->state = si->prev_state = state;
141 }
142 
143 /* returns a bit for a stream-int state, to match against SI_SB_* */
si_state_bit(enum si_state state)144 static inline enum si_state_bit si_state_bit(enum si_state state)
145 {
146 	BUG_ON(state > SI_ST_CLO);
147 	return 1U << state;
148 }
149 
150 /* returns true if <state> matches one of the SI_SB_* bits in <mask> */
si_state_in(enum si_state state,enum si_state_bit mask)151 static inline int si_state_in(enum si_state state, enum si_state_bit mask)
152 {
153 	BUG_ON(mask & ~SI_SB_ALL);
154 	return !!(si_state_bit(state) & mask);
155 }
156 
157 /* only detaches the endpoint from the SI, which means that it's set to
158  * NULL and that ->ops is mapped to si_embedded_ops. The previous endpoint
159  * is returned.
160  */
si_detach_endpoint(struct stream_interface * si)161 static inline enum obj_type *si_detach_endpoint(struct stream_interface *si)
162 {
163 	enum obj_type *prev = si->end;
164 
165 	si->end = NULL;
166 	si->ops = &si_embedded_ops;
167 	return prev;
168 }
169 
170 /* Release the endpoint if it's a connection or an applet, then nullify it.
171  * Note: released connections are closed then freed.
172  */
si_release_endpoint(struct stream_interface * si)173 static inline void si_release_endpoint(struct stream_interface *si)
174 {
175 	struct connection *conn;
176 	struct conn_stream *cs;
177 	struct appctx *appctx;
178 
179 	if (!si->end)
180 		return;
181 
182 	if ((cs = objt_cs(si->end))) {
183 		if (si->wait_event.events != 0)
184 			cs->conn->mux->unsubscribe(cs, si->wait_event.events,
185 			    &si->wait_event);
186 		cs_destroy(cs);
187 	}
188 	else if ((appctx = objt_appctx(si->end))) {
189 		if (appctx->applet->release && !si_state_in(si->state, SI_SB_DIS|SI_SB_CLO))
190 			appctx->applet->release(appctx);
191 		appctx_free(appctx); /* we share the connection pool */
192 	} else if ((conn = objt_conn(si->end))) {
193 		conn_stop_tracking(conn);
194 		conn_full_close(conn);
195 		conn_free(conn);
196 	}
197 	si_detach_endpoint(si);
198 }
199 
200 /* Attach conn_stream <cs> to the stream interface <si>. The stream interface
201  * is configured to work with a connection and the connection it configured
202  * with a stream interface data layer.
203  */
si_attach_cs(struct stream_interface * si,struct conn_stream * cs)204 static inline void si_attach_cs(struct stream_interface *si, struct conn_stream *cs)
205 {
206 	si->ops = &si_conn_ops;
207 	si->end = &cs->obj_type;
208 	cs_attach(cs, si, &si_conn_cb);
209 }
210 
211 /* Returns true if a connection is attached to the stream interface <si> and
212  * if this connection is ready.
213  */
si_conn_ready(struct stream_interface * si)214 static inline int si_conn_ready(struct stream_interface *si)
215 {
216 	struct connection *conn = cs_conn(objt_cs(si->end));
217 
218 	return conn && conn_ctrl_ready(conn) && conn_xprt_ready(conn);
219 }
220 
221 /* Attach appctx <appctx> to the stream interface <si>. The stream interface
222  * is configured to work with an applet context.
223  */
si_attach_appctx(struct stream_interface * si,struct appctx * appctx)224 static inline void si_attach_appctx(struct stream_interface *si, struct appctx *appctx)
225 {
226 	si->ops = &si_applet_ops;
227 	si->end = &appctx->obj_type;
228 	appctx->owner = si;
229 }
230 
231 /* returns a pointer to the appctx being run in the SI, which must be valid */
si_appctx(struct stream_interface * si)232 static inline struct appctx *si_appctx(struct stream_interface *si)
233 {
234 	return __objt_appctx(si->end);
235 }
236 
237 /* call the applet's release function if any. Needs to be called upon close() */
si_applet_release(struct stream_interface * si)238 static inline void si_applet_release(struct stream_interface *si)
239 {
240 	struct appctx *appctx;
241 
242 	appctx = objt_appctx(si->end);
243 	if (appctx && appctx->applet->release && !si_state_in(si->state, SI_SB_DIS|SI_SB_CLO))
244 		appctx->applet->release(appctx);
245 }
246 
247 /* Returns non-zero if the stream interface's Rx path is blocked */
si_rx_blocked(const struct stream_interface * si)248 static inline int si_rx_blocked(const struct stream_interface *si)
249 {
250 	return !!(si->flags & SI_FL_RXBLK_ANY);
251 }
252 
253 
254 /* Returns non-zero if the stream interface's Rx path is blocked because of lack
255  * of room in the input buffer.
256  */
si_rx_blocked_room(const struct stream_interface * si)257 static inline int si_rx_blocked_room(const struct stream_interface *si)
258 {
259 	return !!(si->flags & SI_FL_RXBLK_ROOM);
260 }
261 
262 /* Returns non-zero if the stream interface's endpoint is ready to receive */
si_rx_endp_ready(const struct stream_interface * si)263 static inline int si_rx_endp_ready(const struct stream_interface *si)
264 {
265 	return !(si->flags & SI_FL_RX_WAIT_EP);
266 }
267 
268 /* The stream interface announces it is ready to try to deliver more data to the input buffer */
si_rx_endp_more(struct stream_interface * si)269 static inline void si_rx_endp_more(struct stream_interface *si)
270 {
271 	si->flags &= ~SI_FL_RX_WAIT_EP;
272 }
273 
274 /* The stream interface announces it doesn't have more data for the input buffer */
si_rx_endp_done(struct stream_interface * si)275 static inline void si_rx_endp_done(struct stream_interface *si)
276 {
277 	si->flags |=  SI_FL_RX_WAIT_EP;
278 }
279 
280 /* Tell a stream interface the input channel is OK with it sending it some data */
si_rx_chan_rdy(struct stream_interface * si)281 static inline void si_rx_chan_rdy(struct stream_interface *si)
282 {
283 	si->flags &= ~SI_FL_RXBLK_CHAN;
284 }
285 
286 /* Tell a stream interface the input channel is not OK with it sending it some data */
si_rx_chan_blk(struct stream_interface * si)287 static inline void si_rx_chan_blk(struct stream_interface *si)
288 {
289 	si->flags |=  SI_FL_RXBLK_CHAN;
290 }
291 
292 /* Tell a stream interface the other side is connected */
si_rx_conn_rdy(struct stream_interface * si)293 static inline void si_rx_conn_rdy(struct stream_interface *si)
294 {
295 	si->flags &= ~SI_FL_RXBLK_CONN;
296 }
297 
298 /* Tell a stream interface it must wait for the other side to connect */
si_rx_conn_blk(struct stream_interface * si)299 static inline void si_rx_conn_blk(struct stream_interface *si)
300 {
301 	si->flags |=  SI_FL_RXBLK_CONN;
302 }
303 
304 /* The stream interface just got the input buffer it was waiting for */
si_rx_buff_rdy(struct stream_interface * si)305 static inline void si_rx_buff_rdy(struct stream_interface *si)
306 {
307 	si->flags &= ~SI_FL_RXBLK_BUFF;
308 }
309 
310 /* The stream interface failed to get an input buffer and is waiting for it.
311  * Since it indicates a willingness to deliver data to the buffer that will
312  * have to be retried, we automatically clear RXBLK_ENDP to be called again
313  * as soon as RXBLK_BUFF is cleared.
314  */
si_rx_buff_blk(struct stream_interface * si)315 static inline void si_rx_buff_blk(struct stream_interface *si)
316 {
317 	si->flags |=  SI_FL_RXBLK_BUFF;
318 }
319 
320 /* Tell a stream interface some room was made in the input buffer */
si_rx_room_rdy(struct stream_interface * si)321 static inline void si_rx_room_rdy(struct stream_interface *si)
322 {
323 	si->flags &= ~SI_FL_RXBLK_ROOM;
324 }
325 
326 /* The stream interface announces it failed to put data into the input buffer
327  * by lack of room. Since it indicates a willingness to deliver data to the
328  * buffer that will have to be retried, we automatically clear RXBLK_ENDP to
329  * be called again as soon as RXBLK_ROOM is cleared.
330  */
si_rx_room_blk(struct stream_interface * si)331 static inline void si_rx_room_blk(struct stream_interface *si)
332 {
333 	si->flags |=  SI_FL_RXBLK_ROOM;
334 }
335 
336 /* The stream interface announces it will never put new data into the input
337  * buffer and that it's not waiting for its endpoint to deliver anything else.
338  * This function obviously doesn't have a _rdy equivalent.
339  */
si_rx_shut_blk(struct stream_interface * si)340 static inline void si_rx_shut_blk(struct stream_interface *si)
341 {
342 	si->flags |=  SI_FL_RXBLK_SHUT;
343 }
344 
345 /* Returns non-zero if the stream interface's Rx path is blocked */
si_tx_blocked(const struct stream_interface * si)346 static inline int si_tx_blocked(const struct stream_interface *si)
347 {
348 	return !!(si->flags & SI_FL_WAIT_DATA);
349 }
350 
351 /* Returns non-zero if the stream interface's endpoint is ready to transmit */
si_tx_endp_ready(const struct stream_interface * si)352 static inline int si_tx_endp_ready(const struct stream_interface *si)
353 {
354 	return (si->flags & SI_FL_WANT_GET);
355 }
356 
357 /* Report that a stream interface wants to get some data from the output buffer */
si_want_get(struct stream_interface * si)358 static inline void si_want_get(struct stream_interface *si)
359 {
360 	si->flags |= SI_FL_WANT_GET;
361 }
362 
363 /* Report that a stream interface failed to get some data from the output buffer */
si_cant_get(struct stream_interface * si)364 static inline void si_cant_get(struct stream_interface *si)
365 {
366 	si->flags |= SI_FL_WANT_GET | SI_FL_WAIT_DATA;
367 }
368 
369 /* Report that a stream interface doesn't want to get data from the output buffer */
si_stop_get(struct stream_interface * si)370 static inline void si_stop_get(struct stream_interface *si)
371 {
372 	si->flags &= ~SI_FL_WANT_GET;
373 }
374 
375 /* Report that a stream interface won't get any more data from the output buffer */
si_done_get(struct stream_interface * si)376 static inline void si_done_get(struct stream_interface *si)
377 {
378 	si->flags &= ~(SI_FL_WANT_GET | SI_FL_WAIT_DATA);
379 }
380 
381 /* Try to allocate a new conn_stream and assign it to the interface. If
382  * an endpoint was previously allocated, it is released first. The newly
383  * allocated conn_stream is initialized, assigned to the stream interface,
384  * and returned.
385  */
si_alloc_cs(struct stream_interface * si,struct connection * conn)386 static inline struct conn_stream *si_alloc_cs(struct stream_interface *si, struct connection *conn)
387 {
388 	struct conn_stream *cs;
389 
390 	si_release_endpoint(si);
391 
392 	cs = cs_new(conn);
393 	if (cs)
394 		si_attach_cs(si, cs);
395 
396 	return cs;
397 }
398 
399 /* Try to allocate a buffer for the stream-int's input channel. It relies on
400  * channel_alloc_buffer() for this so it abides by its rules. It returns 0 on
401  * failure, non-zero otherwise. If no buffer is available, the requester,
402  * represented by the <wait> pointer, will be added in the list of objects
403  * waiting for an available buffer, and SI_FL_RXBLK_BUFF will be set on the
404  * stream-int and SI_FL_RX_WAIT_EP cleared. The requester will be responsible
405  * for calling this function to try again once woken up.
406  */
si_alloc_ibuf(struct stream_interface * si,struct buffer_wait * wait)407 static inline int si_alloc_ibuf(struct stream_interface *si, struct buffer_wait *wait)
408 {
409 	int ret;
410 
411 	ret = channel_alloc_buffer(si_ic(si), wait);
412 	if (!ret)
413 		si_rx_buff_blk(si);
414 	return ret;
415 }
416 
417 /* Release the interface's existing endpoint (connection or appctx) and
418  * allocate then initialize a new appctx which is assigned to the interface
419  * and returned. NULL may be returned upon memory shortage. Applet <applet>
420  * is assigned to the appctx, but it may be NULL.
421  */
si_alloc_appctx(struct stream_interface * si,struct applet * applet)422 static inline struct appctx *si_alloc_appctx(struct stream_interface *si, struct applet *applet)
423 {
424 	struct appctx *appctx;
425 
426 	si_release_endpoint(si);
427 	appctx = appctx_new(applet, tid_bit);
428 	if (appctx) {
429 		si_attach_appctx(si, appctx);
430 		appctx->t->nice = si_strm(si)->task->nice;
431 	}
432 
433 	return appctx;
434 }
435 
436 /* Sends a shutr to the connection using the data layer */
si_shutr(struct stream_interface * si)437 static inline void si_shutr(struct stream_interface *si)
438 {
439 	si->ops->shutr(si);
440 }
441 
442 /* Sends a shutw to the connection using the data layer */
si_shutw(struct stream_interface * si)443 static inline void si_shutw(struct stream_interface *si)
444 {
445 	si->ops->shutw(si);
446 }
447 
448 /* Marks on the stream-interface that next shutw must kill the whole connection */
si_must_kill_conn(struct stream_interface * si)449 static inline void si_must_kill_conn(struct stream_interface *si)
450 {
451 	si->flags |= SI_FL_KILL_CONN;
452 }
453 
454 /* This is to be used after making some room available in a channel. It will
455  * return without doing anything if the stream interface's RX path is blocked.
456  * It will automatically mark the stream interface as busy processing the end
457  * point in order to avoid useless repeated wakeups.
458  * It will then call ->chk_rcv() to enable receipt of new data.
459  */
si_chk_rcv(struct stream_interface * si)460 static inline void si_chk_rcv(struct stream_interface *si)
461 {
462 	if (si->flags & SI_FL_RXBLK_CONN && si_state_in(si_opposite(si)->state, SI_SB_RDY|SI_SB_EST|SI_SB_DIS|SI_SB_CLO))
463 		si_rx_conn_rdy(si);
464 
465 	if (si_rx_blocked(si) || !si_rx_endp_ready(si))
466 		return;
467 
468 	if (!si_state_in(si->state, SI_SB_RDY|SI_SB_EST))
469 		return;
470 
471 	si->flags |= SI_FL_RX_WAIT_EP;
472 	si->ops->chk_rcv(si);
473 }
474 
475 /* This tries to perform a synchronous receive on the stream interface to
476  * try to collect last arrived data. In practice it's only implemented on
477  * conn_streams. Returns 0 if nothing was done, non-zero if new data or a
478  * shutdown were collected. This may result on some delayed receive calls
479  * to be programmed and performed later, though it doesn't provide any
480  * such guarantee.
481  */
si_sync_recv(struct stream_interface * si)482 static inline int si_sync_recv(struct stream_interface *si)
483 {
484 	struct conn_stream *cs;
485 
486 	if (!si_state_in(si->state, SI_SB_RDY|SI_SB_EST))
487 		return 0;
488 
489 	cs = objt_cs(si->end);
490 	if (!cs)
491 		return 0; // only conn_streams are supported
492 
493 	if (si->wait_event.events & SUB_RETRY_RECV)
494 		return 0; // already subscribed
495 
496 	if (!si_rx_endp_ready(si) || si_rx_blocked(si))
497 		return 0; // already failed
498 
499 	return si_cs_recv(cs);
500 }
501 
502 /* Calls chk_snd on the connection using the data layer */
si_chk_snd(struct stream_interface * si)503 static inline void si_chk_snd(struct stream_interface *si)
504 {
505 	si->ops->chk_snd(si);
506 }
507 
508 /* Calls chk_snd on the connection using the ctrl layer */
si_connect(struct stream_interface * si,struct connection * conn)509 static inline int si_connect(struct stream_interface *si, struct connection *conn)
510 {
511 	int ret = SF_ERR_NONE;
512 	int conn_flags = 0;
513 
514 	if (unlikely(!conn || !conn->ctrl || !conn->ctrl->connect))
515 		return SF_ERR_INTERNAL;
516 
517 	if (!channel_is_empty(si_oc(si)))
518 		conn_flags |= CONNECT_HAS_DATA;
519 	if (si->conn_retries == si_strm(si)->be->conn_retries)
520 		conn_flags |= CONNECT_CAN_USE_TFO;
521 	if (!conn_ctrl_ready(conn) || !conn_xprt_ready(conn)) {
522 		ret = conn->ctrl->connect(conn, conn_flags);
523 		if (ret != SF_ERR_NONE)
524 			return ret;
525 
526 		/* we're in the process of establishing a connection */
527 		si->state = SI_ST_CON;
528 	}
529 	else {
530 		/* try to reuse the existing connection, it will be
531 		 * confirmed once we can send on it.
532 		 */
533 		/* Is the connection really ready ? */
534 		if (conn->mux->ctl(conn, MUX_STATUS, NULL) & MUX_STATUS_READY)
535 			si->state = SI_ST_RDY;
536 		else
537 			si->state = SI_ST_CON;
538 	}
539 
540 	/* needs src ip/port for logging */
541 	if (si->flags & SI_FL_SRC_ADDR)
542 		conn_get_from_addr(conn);
543 
544 	return ret;
545 }
546 
547 /* Combines both si_update_rx() and si_update_tx() at once */
si_update(struct stream_interface * si)548 static inline void si_update(struct stream_interface *si)
549 {
550 	si_update_rx(si);
551 	si_update_tx(si);
552 }
553 
554 /* Returns info about the conn_stream <cs>, if not NULL. It call the mux layer's
555  * get_cs_info() function, if it exists. On success, it returns a cs_info
556  * structure. Otherwise, on error, if the mux does not implement get_cs_info()
557  * or if conn_stream is NULL, NULL is returned.
558  */
si_get_cs_info(struct conn_stream * cs)559 static inline const struct cs_info *si_get_cs_info(struct conn_stream *cs)
560 {
561 	if (cs && cs->conn->mux->get_cs_info)
562 		return cs->conn->mux->get_cs_info(cs);
563 	return NULL;
564 }
565 
566 /* for debugging, reports the stream interface state name */
si_state_str(int state)567 static inline const char *si_state_str(int state)
568 {
569 	switch (state) {
570 	case SI_ST_INI: return "INI";
571 	case SI_ST_REQ: return "REQ";
572 	case SI_ST_QUE: return "QUE";
573 	case SI_ST_TAR: return "TAR";
574 	case SI_ST_ASS: return "ASS";
575 	case SI_ST_CON: return "CON";
576 	case SI_ST_CER: return "CER";
577 	case SI_ST_RDY: return "RDY";
578 	case SI_ST_EST: return "EST";
579 	case SI_ST_DIS: return "DIS";
580 	case SI_ST_CLO: return "CLO";
581 	default:        return "???";
582 	}
583 }
584 
585 #endif /* _PROTO_STREAM_INTERFACE_H */
586 
587 /*
588  * Local variables:
589  *  c-indent-level: 8
590  *  c-basic-offset: 8
591  * End:
592  */
593