Bug Summary

File:d/connchan.c
Warning:line 1109, column 13
Duplicate code detected
Note:line 1253, column 13
Similar code here

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name connchan.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/isvv/naviserver/nsd -resource-dir /usr/local/lib/clang/15.0.0 -D _FORTIFY_SOURCE=2 -D NDEBUG -D SYSTEM_MALLOC -I ../include -I /usr/include/tcl8.6 -D HAVE_CONFIG_H -internal-isystem /usr/local/lib/clang/15.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/11/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=c99 -fdebug-compilation-dir=/home/isvv/naviserver/nsd -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-checker alpha -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-07-23-130959-11103-1 -x c connchan.c
1/*
2 * The contents of this file are subject to the Mozilla Public License
3 * Version 1.1 (the "License"); you may not use this file except in
4 * compliance with the License. You may obtain a copy of the License at
5 * http://mozilla.org/.
6 *
7 * Software distributed under the License is distributed on an "AS IS"
8 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9 * the License for the specific language governing rights and limitations
10 * under the License.
11 *
12 * Alternatively, the contents of this file may be used under the terms
13 * of the GNU General Public License (the "GPL"), in which case the
14 * provisions of GPL are applicable instead of those above. If you wish
15 * to allow use of your version of this file only under the terms of the
16 * GPL and not to allow others to use your version of this file under the
17 * License, indicate your decision by deleting the provisions above and
18 * replace them with the notice and other provisions required by the GPL.
19 * If you do not delete the provisions above, a recipient may use your
20 * version of this file under either the License or the GPL.
21 */
22
23/*
24 * connchan.c --
25 *
26 * Support functions for connection channels
27 */
28
29#include "nsd.h"
30
31#ifdef HAVE_OPENSSL_EVP_H1
32# include "nsopenssl.h"
33# include <openssl/rand.h>
34#endif
35
36/*
37 * Handling of network byte order (big endian).
38 */
39#if defined(__linux__1)
40# include <endian.h>
41#elif defined(__FreeBSD__) || defined(__NetBSD__)
42# include <sys/endian.h>
43#elif defined(__OpenBSD__)
44# include <sys/types.h>
45# ifndef be16toh
46# define be16toh(x)__bswap_16 (x) betoh16(x)
47# define be32toh(x)__bswap_32 (x) betoh32(x)
48# define be64toh(x)__bswap_64 (x) betoh64(x)
49# endif
50#elif defined(__APPLE__) || defined(_WIN32)
51# define be16toh(x)__bswap_16 (x) ntohs(x)__bswap_16 (x)
52# define htobe16(x)__bswap_16 (x) htons(x)__bswap_16 (x)
53# define be32toh(x)__bswap_32 (x) ntonl(x)
54# define htobe32(x)__bswap_32 (x) htonl(x)__bswap_32 (x)
55# if defined(_WIN32)
56/*
57 * Not sure, why htonll() and ntohll() are undefined in Visual Studio 2019:
58 *
59 *# define be64toh(x) ntohll(x)
60 *# define htobe64(x) htonll(x)
61 */
62# define htobe64(x)__bswap_64 (x) ((1==htonl(1)__bswap_32 (1)) ? (x) : (((uint64_t)htonl((x) & 0xFFFFFFFFUL)__bswap_32 ((x) & 0xFFFFFFFFUL)) << 32) | htonl((uint32_t)((x) >> 32))__bswap_32 ((uint32_t)((x) >> 32)))
63# define be64toh(x)__bswap_64 (x) ((1==ntohl(1)__bswap_32 (1)) ? (x) : (((uint64_t)ntohl((x) & 0xFFFFFFFFUL)__bswap_32 ((x) & 0xFFFFFFFFUL)) << 32) | ntohl((uint32_t)((x) >> 32))__bswap_32 ((uint32_t)((x) >> 32)))
64# else
65# define be64toh(x)__bswap_64 (x) ntohll(x)
66# define htobe64(x)__bswap_64 (x) htonll(x)
67# endif
68#endif
69
70/*
71 * Structure handling one registered channel for the [ns_connchan]
72 * command.
73 */
74typedef struct {
75 const char *channelName;
76 char peer[NS_IPADDR_SIZE46]; /* Client peer address */
77 size_t rBytes;
78 size_t wBytes;
79 bool_Bool binary;
80 Ns_Time startTime;
81 Sock *sockPtr;
82 Ns_Time recvTimeout;
83 Ns_Time sendTimeout;
84 const char *clientData;
85 struct Callback *cbPtr;
86 Tcl_DString *sendBuffer; /* For unsent bytes in "ns_connchan write -buffered" */
87 Tcl_DString *frameBuffer; /* Buffer of for a single WebSocket frame */
88 Tcl_DString *fragmentsBuffer; /* Buffer for multiple WebSocket segments */
89 int fragmentsOpcode; /* Opcode of the first WebSocket segment */
90 bool_Bool frameNeedsData; /* Indicator, if additional reads are required */
91} NsConnChan;
92
93#define ConnChanBufferSize(connChanPtr, buf)((connChanPtr)->buf != ((void*)0) ? (connChanPtr)->buf->
length : 0)
((connChanPtr)->buf != NULL((void*)0) ? (connChanPtr)->buf->length : 0)
94
95typedef struct Callback {
96 NsConnChan *connChanPtr;
97 const char *threadName;
98 unsigned int when;
99 size_t scriptLength;
100 size_t scriptCmdNameLength;
101 char script[1];
102} Callback;
103
104/*
105 * The following structure is used for a socket listen callback.
106 */
107
108typedef struct ListenCallback {
109 const char *server;
110 const char *driverName;
111 char script[1];
112} ListenCallback;
113
114
115/*
116 * Local functions defined in this file
117 */
118static Ns_ArgProc ArgProc;
119
120static void CancelCallback(const NsConnChan *connChanPtr)
121 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
122
123static NsConnChan *ConnChanCreate(NsServer *servPtr, Sock *sockPtr,
124 const Ns_Time *startTime, const char *peer, bool_Bool binary,
125 const char *clientData)
126 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3)))
127 NS_GNUC_RETURNS_NONNULL;
128
129static void ConnChanFree(NsConnChan *connChanPtr, NsServer *servPtr)
130 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
131
132static ssize_t ConnChanReadBuffer(NsConnChan *connChanPtr, char *buffer, size_t bufferSize)
133 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2)));
134
135static NsConnChan *ConnChanGet(Tcl_Interp *interp, NsServer *servPtr, const char *name)
136 NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3)));
137
138static Ns_ReturnCode SockCallbackRegister(NsConnChan *connChanPtr, const char *script,
139 unsigned int when, const Ns_Time *timeoutPtr)
140 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2)));
141
142static ssize_t ConnchanDriverSend(Tcl_Interp *interp, const NsConnChan *connChanPtr,
143 struct iovec *bufs, int nbufs, unsigned int flags,
144 const Ns_Time *timeoutPtr
145 ) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(2)__attribute__((__nonnull__(2))) NS_GNUC_NONNULL(6)__attribute__((__nonnull__(6)));
146
147static char *WhenToString(char *buffer, unsigned int when)
148 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
149
150static bool_Bool SockListenCallback(NS_SOCKETint sock, void *arg, unsigned int UNUSED(why)UNUSED_why __attribute__((__unused__)));
151static void RequireDsBuffer(Tcl_DString **dsPtr) NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1)));
152static void WebsocketFrameSetCommonMembers(Tcl_Obj *resultObj, ssize_t nRead, const NsConnChan *connChanPtr)
153 NS_GNUC_NONNULL(1)__attribute__((__nonnull__(1))) NS_GNUC_NONNULL(3)__attribute__((__nonnull__(3)));
154
155static Ns_SockProc NsTclConnChanProc;
156
157static Tcl_ObjCmdProc ConnChanCallbackObjCmd;
158static Tcl_ObjCmdProc ConnChanCloseObjCmd;
159static Tcl_ObjCmdProc ConnChanDetachObjCmd;
160static Tcl_ObjCmdProc ConnChanExistsObjCmd;
161static Tcl_ObjCmdProc ConnChanListObjCmd;
162static Tcl_ObjCmdProc ConnChanListenObjCmd;
163static Tcl_ObjCmdProc ConnChanOpenObjCmd;
164static Tcl_ObjCmdProc ConnChanReadObjCmd;
165static Tcl_ObjCmdProc ConnChanWriteObjCmd;
166static Tcl_ObjCmdProc ConnChanWsencodeObjCmd;
167
168static Ns_SockProc CallbackFree;
169
170
171
172/*
173 *----------------------------------------------------------------------
174 *
175 * WhenToString --
176 *
177 * Convert socket condition to character string. The provided
178 * input buffer has to be at least 5 bytes long.
179 *
180 * Results:
181 * Pretty string.
182 *
183 * Side effects:
184 * None.
185 *
186 *----------------------------------------------------------------------
187 */
188static char*
189WhenToString(char *buffer, unsigned int when) {
190 char *p = buffer;
191
192 NS_NONNULL_ASSERT(buffer != NULL)((void) (0));
193
194 if ((when & (unsigned int)NS_SOCK_READ) != 0u) {
195 *p++ = 'r';
196 }
197 if ((when & (unsigned int)NS_SOCK_WRITE) != 0u) {
198 *p++ = 'w';
199 }
200 if ((when & (unsigned int)NS_SOCK_EXCEPTION) != 0u) {
201 *p++ = 'e';
202 }
203 if ((when & (unsigned int)NS_SOCK_EXIT) != 0u) {
204 *p++ = 'x';
205 }
206 *p = '\0';
207
208 return buffer;
209}
210
211
212/*
213 *----------------------------------------------------------------------
214 *
215 * CallbackFree --
216 *
217 * Free Callback structure and unregister socket callback.
218 *
219 * Results:
220 * None.
221 *
222 * Side effects:
223 * Freeing memory.
224 *
225 *----------------------------------------------------------------------
226 */
227
228static bool_Bool
229CallbackFree(NS_SOCKETint UNUSED(sock)UNUSED_sock __attribute__((__unused__)), void *arg, unsigned int why) {
230 bool_Bool result;
231
232 if (why != (unsigned int)NS_SOCK_CANCEL) {
233 Ns_Log(Warning, "connchan CallbackFree called with unexpected reason code %u",
234 why);
235 result = NS_FALSE0;
236
237 } else {
238 Callback *cbPtr = arg;
239
240 Ns_Log(Ns_LogConnchanDebug, "connchan: callbackCallbackFree cbPtr %p why %u",
241 (void*)cbPtr, why);
242 ns_free(cbPtr);
243 result = NS_TRUE1;
244 }
245
246 return result;
247}
248
249
250/*
251 *----------------------------------------------------------------------
252 *
253 * CancelCallback --
254 *
255 * Register socket callback cancel operation for unregistering
256 * the socket callback. Freeing is itself implemented as a
257 * callback (Ns_SockProc), which is called, whenever a callback
258 * is freed from the socket thread. Not that it is necessary to
259 * implement it as a callback, since all sock callbacks are
260 * implemented via a queue operation (in sockcallback.c).
261 *
262 * Results:
263 * None.
264 *
265 * Side effects:
266 * Freeing memory.
267 *
268 *----------------------------------------------------------------------
269 */
270
271static void
272CancelCallback(const NsConnChan *connChanPtr)
273{
274 NS_NONNULL_ASSERT(connChanPtr != NULL)((void) (0));
275 NS_NONNULL_ASSERT(connChanPtr->cbPtr != NULL)((void) (0));
276
277 Ns_Log(Ns_LogConnchanDebug, "%s connchan: CancelCallback %p",
278 connChanPtr->channelName, (void*)connChanPtr->cbPtr);
279
280 (void)Ns_SockCancelCallbackEx(connChanPtr->sockPtr->sock, CallbackFree,
281 connChanPtr->cbPtr, NULL((void*)0));
282}
283
284
285/*
286 *----------------------------------------------------------------------
287 *
288 * ConnChanCreate --
289 *
290 * Allocate a connection channel structure and initialize its
291 * fields. When the passed-in peer is NULL, determine peerAddr
292 * from the sockPtr.
293 *
294 * Results:
295 * Initialized connection channel structure.
296 *
297 * Side effects:
298 * Allocating memory.
299 *
300 *----------------------------------------------------------------------
301 */
302static NsConnChan *
303ConnChanCreate(NsServer *servPtr, Sock *sockPtr,
304 const Ns_Time *startTime, const char *peer, bool_Bool binary,
305 const char *clientData) {
306 static uint64_t connchanCount = 0u;
307 NsConnChan *connChanPtr;
308 Tcl_HashEntry *hPtr;
309 char name[5 + TCL_INTEGER_SPACE24];
310 int isNew;
311
312 NS_NONNULL_ASSERT(servPtr != NULL)((void) (0));
313 NS_NONNULL_ASSERT(sockPtr != NULL)((void) (0));
314 NS_NONNULL_ASSERT(startTime != NULL)((void) (0));
315
316 Ns_SockSetKeepalive(sockPtr->sock, 1);
317
318 /*
319 * Create a new NsConnChan structure and fill in the elements,
320 * which can be set without a lock. The intention is to keep the
321 * lock-times low.
322 */
323
324 connChanPtr = ns_malloc(sizeof(NsConnChan));
325 connChanPtr->cbPtr = NULL((void*)0);
326 connChanPtr->startTime = *startTime;
327 connChanPtr->rBytes = 0;
328 connChanPtr->wBytes = 0;
329 connChanPtr->recvTimeout.sec = 0;
330 connChanPtr->recvTimeout.usec = 0;
331 connChanPtr->sendTimeout.sec = 0;
332 connChanPtr->sendTimeout.usec = 0;
333 connChanPtr->clientData = clientData != NULL((void*)0) ? ns_strdup(clientData) : NULL((void*)0);
334 connChanPtr->sendBuffer = NULL((void*)0);
335 connChanPtr->frameBuffer = NULL((void*)0);
336 connChanPtr->fragmentsBuffer = NULL((void*)0);
337 connChanPtr->frameNeedsData = NS_TRUE1;
338
339 if (peer == NULL((void*)0)) {
340 (void)ns_inet_ntop((struct sockaddr *)&(sockPtr->sa), connChanPtr->peer, NS_IPADDR_SIZE46);
341 } else {
342 strncpy(connChanPtr->peer, peer, NS_IPADDR_SIZE46 - 1);
343 }
344 connChanPtr->sockPtr = sockPtr;
345 connChanPtr->binary = binary;
346 memcpy(name, "conn", 4);
347
348 /*
349 * Lock the channel table and create a new entry in the hash table
350 * for the connection. The counter-based name creation requires a
351 * lock to guarantee unique names.
352 */
353 Ns_RWLockWrLock(&servPtr->connchans.lock);
354 (void)ns_uint64toa(&name[4], connchanCount ++);
355 hPtr = Tcl_CreateHashEntry(&servPtr->connchans.table, name, &isNew)(*((&servPtr->connchans.table)->createProc))(&servPtr
->connchans.table, (const char *)(name), &isNew)
;
356
357 if (unlikely(isNew == 0)(__builtin_expect((isNew == 0), 0))) {
358 Ns_Log(Warning, "duplicate connchan name '%s'", name);
359 }
360
361 Tcl_SetHashValue(hPtr, connChanPtr)((hPtr)->clientData = (ClientData) (connChanPtr));
362
363 connChanPtr->channelName = ns_strdup(name);
364 Ns_RWLockUnlock(&servPtr->connchans.lock);
365
366 return connChanPtr;
367}
368
369
370/*
371 *----------------------------------------------------------------------
372 *
373 * ConnChanFree --
374 *
375 * Free NsConnChan structure and remove the entry from the hash
376 * table of open connection channel structures.
377 *
378 * Results:
379 * None.
380 *
381 * Side effects:
382 * Freeing memory.
383 *
384 *----------------------------------------------------------------------
385 */
386static void
387ConnChanFree(NsConnChan *connChanPtr, NsServer *servPtr) {
388 Tcl_HashEntry *hPtr;
389
390 NS_NONNULL_ASSERT(connChanPtr != NULL)((void) (0));
391 NS_NONNULL_ASSERT(servPtr != NULL)((void) (0));
392
393 //assert(connChanPtr->sockPtr != NULL);
394 //assert(connChanPtr->sockPtr->servPtr != NULL);
395
396 //servPtr = connChanPtr->sockPtr->servPtr;
397 /*
398 * Remove entry from hash table.
399 */
400 Ns_RWLockWrLock(&servPtr->connchans.lock);
401 hPtr = Tcl_FindHashEntry(&servPtr->connchans.table, connChanPtr->channelName)(*((&servPtr->connchans.table)->findProc))(&servPtr
->connchans.table, (const char *)(connChanPtr->channelName
))
;
402 if (hPtr != NULL((void*)0)) {
403 Tcl_DeleteHashEntry(hPtr);
404 } else {
405 Ns_Log(Error, "ns_connchan: could not delete hash entry for channel '%s'",
406 connChanPtr->channelName);
407 }
408 Ns_RWLockUnlock(&servPtr->connchans.lock);
409
410 if (hPtr != NULL((void*)0)) {
411 /*
412 * Only in cases, where we found the entry, we can free the
413 * connChanPtr content.
414 */
415 if (connChanPtr->cbPtr != NULL((void*)0)) {
416 /*
417 * Add CancelCallback() to the sock callback queue.
418 */
419 CancelCallback(connChanPtr);
420 /*
421 * There might be a race condition, when a previously
422 * registered callback is currently active (or going to be
423 * processed). So make sure, it won't access a stale
424 * connChanPtr member.
425 */
426 connChanPtr->cbPtr->connChanPtr = NULL((void*)0);
427 /*
428 * The cancel callback takes care about freeing the
429 * actual callback.
430 */
431 connChanPtr->cbPtr = NULL((void*)0);
432 }
433 ns_free((char *)connChanPtr->channelName);
434 if (connChanPtr->clientData != NULL((void*)0)) {
435 ns_free((char *)connChanPtr->clientData);
436 }
437
438 if (connChanPtr->sockPtr != NULL((void*)0)) {
439 NsSockClose(connChanPtr->sockPtr, (int)NS_FALSE0);
440 connChanPtr->sockPtr = NULL((void*)0);
441 }
442 if (connChanPtr->sendBuffer != NULL((void*)0)) {
443 Tcl_DStringFree(connChanPtr->sendBuffer);
444 ns_free((char *)connChanPtr->sendBuffer);
445 }
446 if (connChanPtr->frameBuffer != NULL((void*)0)) {
447 Tcl_DStringFree(connChanPtr->frameBuffer);
448 ns_free((char *)connChanPtr->frameBuffer);
449 }
450 if (connChanPtr->fragmentsBuffer != NULL((void*)0)) {
451 Tcl_DStringFree(connChanPtr->fragmentsBuffer);
452 ns_free((char *)connChanPtr->fragmentsBuffer);
453 }
454 ns_free((char *)connChanPtr);
455 } else {
456 Ns_Log(Bug, "ns_connchan: could not delete hash entry for channel '%s'",
457 connChanPtr->channelName);
458 }
459
460}
461
462
463/*
464 *----------------------------------------------------------------------
465 *
466 * ConnChanGet --
467 *
468 * Access an NsConnChan from the per-server table via its name.
469 *
470 * Results:
471 * ConnChan* or NULL if not found.
472 *
473 * Side effects:
474 * None.
475 *
476 *----------------------------------------------------------------------
477 */
478static NsConnChan *
479ConnChanGet(Tcl_Interp *interp, NsServer *servPtr, const char *name) {
480 const Tcl_HashEntry *hPtr;
481 NsConnChan *connChanPtr = NULL((void*)0);
482
483 NS_NONNULL_ASSERT(servPtr != NULL)((void) (0));
484 NS_NONNULL_ASSERT(name != NULL)((void) (0));
485 servPtr = NsGetServer(nsconf.defaultServer);
486
487 Ns_RWLockRdLock(&servPtr->connchans.lock);
488 hPtr = Tcl_FindHashEntry(&servPtr->connchans.table, name)(*((&servPtr->connchans.table)->findProc))(&servPtr
->connchans.table, (const char *)(name))
;
489 if (hPtr != NULL((void*)0)) {
490 connChanPtr = (NsConnChan *)Tcl_GetHashValue(hPtr)((hPtr)->clientData);
491 }
492 Ns_RWLockUnlock(&servPtr->connchans.lock);
493
494 if (connChanPtr == NULL((void*)0) && interp != NULL((void*)0)) {
495 Ns_TclPrintfResult(interp, "channel \"%s\" does not exist", name);
496 }
497
498 return connChanPtr;
499}
500
501
502/*
503 *----------------------------------------------------------------------
504 *
505 * NsTclConnChanProc --
506 *
507 * A wrapper function callback that is called, when the callback
508 * is fired. The function allocates an interpreter if necessary,
509 * builds the argument list for invocation and calls the
510 * registered Tcl script.
511 *
512 * Results:
513 * NS_TRUE or NS_FALSE on error.
514 *
515 * Side effects:
516 * Will run Tcl script.
517 *
518 *----------------------------------------------------------------------
519 */
520
521static bool_Bool
522NsTclConnChanProc(NS_SOCKETint UNUSED(sock)UNUSED_sock __attribute__((__unused__)), void *arg, unsigned int why)
523{
524 Callback *cbPtr;
525 bool_Bool success = NS_TRUE1;
526
527 NS_NONNULL_ASSERT(arg != NULL)((void) (0));
528
529 cbPtr = arg;
530
531 if (cbPtr->connChanPtr == NULL((void*)0)) {
532 /*
533 * Safety belt.
534 */
535 Ns_Log(Ns_LogConnchanDebug, "NsTclConnChanProc called on a probably deleted callback %p",
536 (void*)cbPtr);
537 success = NS_FALSE0;
538
539 } else {
540 char whenBuffer[6];
541 NsServer *servPtr;
542
543 /*
544 * We should have a valid callback structure that we test
545 * with asserts (cbPtr->connChanPtr != NULL):
546 */
547 Ns_Log(Ns_LogConnchanDebug, "%s NsTclConnChanProc why %s (%u)",
548 cbPtr->connChanPtr->channelName, WhenToString(whenBuffer, why), why);
549
550 assert(cbPtr->connChanPtr->sockPtr != NULL)((void) (0));
551 servPtr = cbPtr->connChanPtr->sockPtr->servPtr;
552
553 if (why == (unsigned int)NS_SOCK_EXIT) {
554 /*
555 * Treat the "exit" case like error cases and free in such
556 * cases the connChanPtr structure.
557 */
558 success = NS_FALSE0;
559
560 } else {
561 int result;
562 Tcl_DString script;
563 Tcl_Interp *interp;
564 const char *w, *channelName;
565 bool_Bool logEnabled;
566 size_t scriptCmdNameLength;
567 NS_SOCKETint localsock;
568
569 /*
570 * In all remaining cases, the Tcl callback is executed.
571 */
572 assert(servPtr != NULL)((void) (0));
573
574 Tcl_DStringInit(&script);
575 Tcl_DStringAppend(&script, cbPtr->script, (int)cbPtr->scriptLength);
576
577 if ((why & (unsigned int)NS_SOCK_TIMEOUT) != 0u) {
578 w = "t";
579 } else if ((why & (unsigned int)NS_SOCK_READ) != 0u) {
580 w = "r";
581 } else if ((why & (unsigned int)NS_SOCK_WRITE) != 0u) {
582 w = "w";
583 } else if ((why & (unsigned int)NS_SOCK_EXCEPTION) != 0u) {
584 w = "e";
585 } else {
586 w = "x";
587 }
588
589 if (Ns_LogSeverityEnabled(Ns_LogConnchanDebug)) {
590 logEnabled = NS_TRUE1;
591 channelName = ns_strdup(cbPtr->connChanPtr->channelName);
592 scriptCmdNameLength = cbPtr->scriptCmdNameLength;
593 } else {
594 logEnabled = NS_FALSE0;
595 channelName = NULL((void*)0);
596 scriptCmdNameLength = 0u;
597 }
598 Tcl_DStringAppendElement(&script, w);
599
600 localsock = cbPtr->connChanPtr->sockPtr->sock;
601 interp = NsTclAllocateInterp(servPtr);
602 result = Tcl_EvalEx(interp, script.string, script.length, 0);
603
604 if (result != TCL_OK0) {
605 (void) Ns_TclLogErrorInfo(interp, "\n(context: connchan proc)");
606 } else {
607 Tcl_DString ds;
608 Tcl_Obj *objPtr = Tcl_GetObjResult(interp);
609 int ok = 1;
610
611 /*
612 * Here we cannot trust the cbPtr structure anymore,
613 * since the call to Tcl_EvalEx might have deleted it
614 * via some script.
615 */
616
617 if (logEnabled) {
618 Tcl_DStringInit(&ds);
619 Ns_DStringNAppendTcl_DStringAppend(&ds, script.string, (int)scriptCmdNameLength);
620 Ns_Log(Ns_LogConnchanDebug,
621 "%s NsTclConnChanProc Tcl eval <%s> returned <%s>",
622 channelName, ds.string, Tcl_GetString(objPtr));
623 Tcl_DStringFree(&ds);
624 }
625
626 /*
627 * The Tcl callback can signal with the result "0",
628 * that the connection channel should be closed
629 * automatically. A result of "2" means to suspend
630 * (cancel) the callback, but not close the channel.
631 */
632 result = Tcl_GetIntFromObj(interp, objPtr, &ok);
633 if (result == TCL_OK0) {
634 Ns_Log(Ns_LogConnchanDebug, "NsTclConnChanProc <%s> numeric result %d", script.string, ok);
635 if (ok == 0) {
636 result = TCL_ERROR1;
637 } else if (ok == 2) {
638 if (logEnabled) {
639 Ns_Log(Ns_LogConnchanDebug, "%s NsTclConnChanProc client "
640 "requested to CANCEL (suspend) callback %p",
641 channelName, (void*)cbPtr);
642 }
643 /*
644 * Use the "raw" Ns_SockCancelCallbackEx() API
645 * call to just stop socket handling, while
646 * keeping the connchan specific structures
647 * alive (postponing cleanup to a "close"
648 * operation).
649 *
650 * We cannot pass "callbackFree" and "cbPtr"
651 * to Ns_SockCancelCallbackEx(), so the
652 * callback structure will stay around until
653 * the callback is reset or the channel is
654 * finally cleaned. We might consider changing
655 * the condition flags (also from the
656 * scripting level) to turn callbacks for
657 * certain conditions on or off.
658 */
659 (void) Ns_SockCancelCallbackEx(localsock, NULL((void*)0), NULL((void*)0), NULL((void*)0));
660
661 }
662 } else {
663 Tcl_DStringInit(&ds);
664 Ns_DStringNAppendTcl_DStringAppend(&ds, script.string, (int)scriptCmdNameLength);
665
666 Ns_Log(Warning, "%s callback <%s> returned unhandled result '%s' (must be 0, 1, or 2)",
667 channelName,
668 ds.string,
669 Tcl_GetString(objPtr));
670 Tcl_DStringFree(&ds);
671 }
672 }
673 if (channelName != NULL((void*)0)) {
674 ns_free((char *)channelName);
675 }
676 Ns_TclDeAllocateInterp(interp);
677 Tcl_DStringFree(&script);
678
679 if (result != TCL_OK0) {
680 success = NS_FALSE0;
681 }
682 }
683
684 if (!success) {
685 if (cbPtr->connChanPtr != NULL((void*)0)) {
686 Ns_Log(Ns_LogConnchanDebug, "%s NsTclConnChanProc free channel",
687 cbPtr->connChanPtr->channelName);
688 servPtr = NsGetServer(nsconf.defaultServer); // temporarily added
689 ConnChanFree(cbPtr->connChanPtr, servPtr);
690 cbPtr->connChanPtr = NULL((void*)0);
691 }
692 }
693 }
694 return success;
695}
696
697
698
699
700/*
701 *----------------------------------------------------------------------
702 *
703 * ArgProc --
704 *
705 * Append info for socket callback.
706 *
707 * Results:
708 * None.
709 *
710 * Side effects:
711 * None.
712 *
713 *----------------------------------------------------------------------
714 */
715
716static void
717ArgProc(Tcl_DString *dsPtr, const void *arg)
718{
719 const Callback *cbPtr = arg;
720
721 Tcl_DStringStartSublist(dsPtr);
722 if (cbPtr->connChanPtr != NULL((void*)0)) {
723 /*
724 * It might be the case that the connChanPtr was canceled, but
725 * the updatecmd not yet executed.
726 */
727 Ns_DStringNAppendTcl_DStringAppend(dsPtr, cbPtr->connChanPtr->channelName, -1);
728 Ns_DStringNAppendTcl_DStringAppend(dsPtr, " ", 1);
729 Ns_DStringNAppendTcl_DStringAppend(dsPtr, cbPtr->script, (int)cbPtr->scriptCmdNameLength);
730 } else {
731 Ns_Log(Notice, "connchan ArgProc cbPtr %p has no connChanPtr", (void*)cbPtr);
732 }
733 Tcl_DStringEndSublist(dsPtr);
734}
735
736/*
737 *----------------------------------------------------------------------
738 *
739 * SockCallbackRegister --
740 *
741 * Register a callback for the connection channel. Due to the
742 * underlying infrastructure, one socket has at most one callback
743 * registered at one time.
744 *
745 * Results:
746 * Standard NaviServer return code.
747 *
748 * Side effects:
749 * Memory management for the callback structure.
750 *
751 *----------------------------------------------------------------------
752 */
753
754static Ns_ReturnCode
755SockCallbackRegister(NsConnChan *connChanPtr, const char *script,
756 unsigned int when, const Ns_Time *timeoutPtr)
757{
758 Callback *cbPtr;
759 size_t scriptLength;
760 Ns_ReturnCode result;
761 const char *p;
762
763 NS_NONNULL_ASSERT(connChanPtr != NULL)((void) (0));
764 NS_NONNULL_ASSERT(script != NULL)((void) (0));
765
766 scriptLength = strlen(script);
767
768 /*
769 * If there is already a callback registered, free and cancel
770 * it. This has to be done as first step, since CancelCallback()
771 * calls finally Ns_SockCancelCallbackEx(), which deletes all
772 * callbacks registered for the associated socket.
773 */
774 if (connChanPtr->cbPtr != NULL((void*)0)) {
775 cbPtr = ns_realloc(connChanPtr->cbPtr, sizeof(Callback) + scriptLength);
776
777 } else {
778 cbPtr = ns_malloc(sizeof(Callback) + scriptLength);
779 }
780 memcpy(cbPtr->script, script, scriptLength + 1u);
781 cbPtr->scriptLength = scriptLength;
782
783 /*
784 * Keep the length of the cmd name for introspection and debugging
785 * purposes. Rationale: The callback often contains binary data,
786 * so outputting the full cmd mess up log files.
787 *
788 * Assumption: cmd name must not contain funny characters.
789 */
790 p = strchr(cbPtr->script, INTCHAR(' ')((int)((unsigned char)((' ')))));
791 if (p != NULL((void*)0)) {
792 cbPtr->scriptCmdNameLength = (size_t)(p - cbPtr->script);
793 } else {
794 cbPtr->scriptCmdNameLength = 0u;
795 }
796 cbPtr->when = when;
797 cbPtr->threadName = NULL((void*)0);
798 cbPtr->connChanPtr = connChanPtr;
799
800 result = Ns_SockCallbackEx(connChanPtr->sockPtr->sock, NsTclConnChanProc, cbPtr,
801 when | (unsigned int)NS_SOCK_EXIT,
802 timeoutPtr, &cbPtr->threadName);
803 if (result == NS_OK) {
804 connChanPtr->cbPtr = cbPtr;
805
806 Ns_RegisterProcInfo((ns_funcptr_t)NsTclConnChanProc, "ns_connchan", ArgProc);
807 } else {
808 /*
809 * The callback could not be registered, maybe the socket is
810 * not valid anymore. Free the callback.
811 */
812 (void) CallbackFree(connChanPtr->sockPtr->sock, cbPtr, (unsigned int)NS_SOCK_CANCEL);
813 connChanPtr->sockPtr = NULL((void*)0);
814 connChanPtr->cbPtr = NULL((void*)0);
815 }
816 return result;
817}
818
819/*
820 *----------------------------------------------------------------------
821 *
822 * ConnchanDriverSend --
823 *
824 * Write a vector of buffers to the socket via the driver callback.
825 *
826 * Results:
827 * Number of bytes written, or -1 on error.
828 *
829 * Side effects:
830 * Depends on driver.
831 *
832 *----------------------------------------------------------------------
833 */
834
835static ssize_t
836ConnchanDriverSend(Tcl_Interp *interp, const NsConnChan *connChanPtr,
837 struct iovec *bufs, int nbufs, unsigned int flags,
838 const Ns_Time *timeoutPtr)
839{
840 ssize_t result;
841 Sock *sockPtr;
842
843 NS_NONNULL_ASSERT(connChanPtr != NULL)((void) (0));
844 NS_NONNULL_ASSERT(timeoutPtr != NULL)((void) (0));
845
846 sockPtr = connChanPtr->sockPtr;
847
848 assert(sockPtr != NULL)((void) (0));
849 assert(sockPtr->drvPtr != NULL)((void) (0));
850
851 /*
852 * Call the driver's sendProc, but handle also partial write
853 * operations.
854 *
855 * In principle, we could call here simply
856 *
857 * result = Ns_SockSendBufs((Ns_Sock *)sockPtr, bufs, nbufs, timeoutPtr, flags);
858 *
859 * but this operation can block the thread in cases where not all
860 * of the buffer was sent. Therefore, the following block defines
861 * a logic, where on partial operations, the remaining data is
862 * passed to the caller, when no send timeout is specified.
863 */
864 if (likely(sockPtr->drvPtr->sendProc != NULL)(__builtin_expect((sockPtr->drvPtr->sendProc != ((void*
)0)), 1))
) {
865 bool_Bool haveTimeout = NS_FALSE0, partial;
866 ssize_t nSent = 0, toSend = (ssize_t)Ns_SumVec(bufs, nbufs), origLength = toSend, partialResult;
867
868 do {
869 ssize_t partialToSend = (ssize_t)Ns_SumVec(bufs, nbufs);
870
871 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend try to send [0] %" PRIdz"zd"
872 " bytes (total %" PRIdz"zd" ")",
873 connChanPtr->channelName,
874 bufs->iov_len, partialToSend);
875
876 partialResult = NsDriverSend(sockPtr, bufs, nbufs, flags);
877 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend NsDriverSend returned result %"
878 PRIdz"zd" " errorState %d --- %s",
879 connChanPtr->channelName, partialResult, sockPtr->recvSockState, Tcl_ErrnoMsg(errno(*__errno_location ())));
880
881 if (partialResult == 0) {
882 /*
883 * The resource is temporarily unavailable, we can an
884 * retry, when the socket is writable.
885 *
886 * If there is no timeout provided, return the bytes sent so far.
887 */
888 if (timeoutPtr->sec == 0 && timeoutPtr->usec == 0) {
889 Ns_Log(Ns_LogConnchanDebug,
890 "%s ConnchanDriverSend would block, no timeout configured, "
891 "origLength %" PRIdz"zd"" still to send %" PRIdz"zd" " already sent %" PRIdz"zd",
892 connChanPtr->channelName, origLength, toSend, nSent);
893 break;
894
895 } else {
896 /*
897 * A timeout was provided. Be aware that the timeout
898 * will suspend all sock-callback handlings for this
899 * time period.
900 */
901 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend recoverable "
902 "error before timeout (" NS_TIME_FMT"%" "l" "d" ".%06ld" ")",
903 connChanPtr->channelName, (int64_t)timeoutPtr->sec, timeoutPtr->usec);
904
905 if (Ns_SockTimedWait(sockPtr->sock, (unsigned int)NS_SOCK_WRITE, timeoutPtr) == NS_OK) {
906 partialResult = NsDriverSend(sockPtr, bufs, nbufs, flags);
907 } else {
908 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend timeout occurred",
909 connChanPtr->channelName);
910 haveTimeout = NS_TRUE1;
911 Ns_TclPrintfResult(interp, "channel %s timeout on send "
912 "operation (" NS_TIME_FMT"%" "l" "d" ".%06ld" ")",
913 connChanPtr->channelName,
914 (int64_t)timeoutPtr->sec, timeoutPtr->usec);
915 Tcl_SetErrorCode(interp, "NS_TIMEOUT", (char *)0L);
916 Ns_Log(Ns_LogTimeoutDebug, "connchan send on %s runs into timeout",
917 connChanPtr->channelName);
918 partialResult = -1;
919 }
920 }
921 }
922
923 partial = NS_FALSE0;
924
925 if (partialResult != -1) {
926 nSent += partialResult;
927 partialToSend -= partialResult;
928
929 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend check partialResult %" PRIdz"zd"
930 " nSent %" PRIdz"zd" " toSend %" PRIdz"zd" " partial ? %d",
931 connChanPtr->channelName, partialResult, nSent, partialToSend, (partialToSend > 0));
932 assert(partialToSend >= 0)((void) (0));
933
934 if (partialToSend > 0) {
935 /*
936 * Partial write operation: part of the iovec has
937 * been sent, we have to retransmit the rest.
938 */
939 Ns_Log(Notice,
940 "%s ConnchanDriverSend partial write operation, sent %" PRIdz"zd"
941 " (so far %" PRIdz"zd" ") remaining %" PRIdz"zd"
942 " bytes, full length %" PRIdz"zd",
943 connChanPtr->channelName, partialResult, nSent, partialToSend, origLength);
944 partial = NS_TRUE1;
945 }
946 (void) Ns_ResetVec(bufs, nbufs, (size_t)partialResult);
947 assert((size_t)partialToSend == Ns_SumVec(bufs, nbufs))((void) (0));
948
949 } else if (!haveTimeout) {
950 /*
951 * The "errno" variable might be 0 here (at least in
952 * https cases), when there are OpenSSL error cases
953 * not caused by the OS socket states. This can lead
954 * to weird looking exceptions, stating "Success"
955 * (errno == 0). Such errors happened e.g. before
956 * setting SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER. Hopefully,
957 * such error cases are eliminated already. In case
958 * we see such errors again, we should look for a way
959 * to get the error message directly from the driver
960 * (e.g. from OpenSSL) and not from the OS.
961 */
962 const char *errorMsg = Tcl_ErrnoMsg(errno(*__errno_location ()));
963 /*
964 * Timeout is handled above, all other errors ar
965 * handled here. Return these as POSIX errors.
966 */
967 Ns_TclPrintfResult(interp, "channel %s send operation failed: %s",
968 connChanPtr->channelName, errorMsg);
969 Tcl_SetErrorCode(interp, "POSIX", Tcl_ErrnoId(), errorMsg, (char *)0L);
970 }
971
972 Ns_Log(Ns_LogConnchanDebug, "%s ### check result %ld == -1 || %ld == %ld "
973 "(partial %d && ok %d) => try again %d",
974 connChanPtr->channelName,
975 partialResult, toSend, nSent,
976 partial, (partialResult != -1), (partial && (partialResult != -1)));
977
978 } while (partial && (partialResult != -1));
979
980 if (partialResult != -1) {
981 result = nSent;
982 } else {
983 result = -1;
984 }
985
986 } else {
987 Ns_TclPrintfResult(interp, "channel %s: no sendProc registered for driver %s",
988 connChanPtr->channelName, sockPtr->drvPtr->moduleName);
989 result = -1;
990 }
991
992 return result;
993}
994
995
996/*
997 *----------------------------------------------------------------------
998 *
999 * ConnChanDetachObjCmd --
1000 *
1001 * Implements "ns_connchan detach".
1002 *
1003 * Results:
1004 * A standard Tcl result.
1005 *
1006 * Side effects:
1007 * Depends on subcommand.
1008 *
1009 *----------------------------------------------------------------------
1010 */
1011static int
1012ConnChanDetachObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1013{
1014 const NsInterp *itPtr = clientData;
1015 Conn *connPtr = (Conn *)itPtr->conn;
1016 int result = TCL_OK0;
1017
1018 if (Ns_ParseObjv(NULL((void*)0), NULL((void*)0), interp, 2, objc, objv) != NS_OK) {
1019 result = TCL_ERROR1;
1020
1021 } else if (connPtr == NULL((void*)0)) {
1022 Ns_TclPrintfResult(interp, "no current connection");
1023 result = TCL_ERROR1;
1024
1025 } else {
1026 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1027 const NsConnChan *connChanPtr;
1028
1029 /*
1030 * Lock the channel table and create a new entry for the
1031 * connection. After this operation the channel is responsible
1032 * for managing the sockPtr, so we have to remove it from the
1033 * connection structure.
1034 */
1035 connChanPtr = ConnChanCreate(servPtr,
1036 connPtr->sockPtr,
1037 Ns_ConnStartTime((Ns_Conn *)connPtr),
1038 Ns_ConnConfiguredPeerAddr(itPtr->conn),
1039 ((connPtr->flags & NS_CONN_WRITE_ENCODED0x020u) == 0u),
1040 connPtr->clientData);
1041 Ns_Log(Ns_LogConnchanDebug, "%s ConnChanDetachObjCmd sock %d",
1042 connChanPtr->channelName,
1043 connPtr->sockPtr->sock);
1044
1045 connPtr->sockPtr = NULL((void*)0);
1046 /*
1047 * All commands responding the client via this connection
1048 * can't work now, since response handling is delegated to the
1049 * connchan machinery. Make this situation detectable at the
1050 * script level via "ns_conn isconnected" by setting the close
1051 * flag.
1052 */
1053 connPtr->flags |= NS_CONN_CLOSED0x001u;
1054
1055 Tcl_SetObjResult(interp, Tcl_NewStringObj(connChanPtr->channelName, -1));
1056 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan detach returns %d", connChanPtr->channelName, result);
1057 }
1058 return result;
1059}
1060
1061/*
1062 *----------------------------------------------------------------------
1063 *
1064 * ConnChanOpenObjCmd --
1065 *
1066 * Implements "ns_connchan open".
1067 *
1068 * Results:
1069 * A standard Tcl result.
1070 *
1071 * Side effects:
1072 * Depends on subcommand.
1073 *
1074 *----------------------------------------------------------------------
1075 */
1076static int
1077ConnChanOpenObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1078{
1079 int result;
1080 Sock *sockPtr = NULL((void*)0);
1081 Ns_Set *hdrPtr = NULL((void*)0);
1082 char *url, *method = (char *)"GET", *version = (char *)"1.0",
1083 *driverName = NULL((void*)0), *sniHostname = NULL((void*)0);
1084 Ns_Time timeout = {1, 0}, *timeoutPtr = &timeout;
1085 Ns_ObjvSpec lopts[] = {
1086 {"-driver", Ns_ObjvString, &driverName, NULL((void*)0)},
1087 {"-headers", Ns_ObjvSet, &hdrPtr, NULL((void*)0)},
1088 {"-hostname", Ns_ObjvString, &sniHostname, NULL((void*)0)},
1089 {"-method", Ns_ObjvString, &method, NULL((void*)0)},
1090 {"-timeout", Ns_ObjvTime, &timeoutPtr, NULL((void*)0)},
1091 {"-version", Ns_ObjvString, &version, NULL((void*)0)},
1092 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1093 };
1094 Ns_ObjvSpec largs[] = {
1095 {"url", Ns_ObjvString, &url, NULL((void*)0)},
1096 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1097 };
1098
1099 if (Ns_ParseObjv(lopts, largs, interp, 2, objc, objv) != NS_OK) {
1100 result = TCL_ERROR1;
1101 } else {
1102 //const NsInterp *itPtr = clientData;
1103 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1104 NsConnChan *connChanPtr;
1105
1106 result = NSDriverClientOpen(interp, driverName, url, method, version, timeoutPtr, &sockPtr);
1107 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1108
1109 if (STREQ(sockPtr->drvPtr->protocol, "https")(((*(sockPtr->drvPtr->protocol)) == (*("https"))) &&
(strcmp((sockPtr->drvPtr->protocol),("https")) == 0))
) {
Duplicate code detected
1110 NS_TLS_SSL_CTXSSL_CTX *ctx;
1111
1112 assert(sockPtr->drvPtr->clientInitProc != NULL)((void) (0));
1113
1114 /*
1115 * For the time being, just pass NULL
1116 * structures. Probably, we could create the
1117 * SSLcontext.
1118 */
1119 result = Ns_TLS_CtxClientCreate(interp,
1120 NULL((void*)0) /*cert*/, NULL((void*)0) /*caFile*/,
1121 NULL((void*)0) /* caPath*/, NS_FALSE0 /*verify*/,
1122 &ctx);
1123 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1124 Ns_DriverClientInitArg params = {ctx, sniHostname};
1125 result = (*sockPtr->drvPtr->clientInitProc)(interp, (Ns_Sock *)sockPtr, &params);
1126
1127 /*
1128 * For the time being, we create/delete the ctx in
1129 * an eager fashion. We could probably make it
1130 * reusable and keep it around.
1131 */
1132 if (ctx != NULL((void*)0)) {
1133 Ns_TLS_CtxFree(ctx);
1134 }
1135 }
1136 }
1137
1138 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1139 struct iovec buf[4];
1140 Ns_Time now;
1141 ssize_t nSent;
1142
1143 Ns_GetTime(&now);
1144 connChanPtr = ConnChanCreate(servPtr,
1145 sockPtr,
1146 &now,
1147 NULL((void*)0),
1148 NS_TRUE1 /* binary, fixed for the time being */,
1149 NULL((void*)0));
1150 if (hdrPtr != NULL((void*)0)) {
1151 size_t i;
1152
1153 for (i = 0u; i < Ns_SetSize(hdrPtr)((hdrPtr)->size); i++) {
1154 const char *key = Ns_SetKey(hdrPtr, i)((hdrPtr)->fields[(i)].name);
1155 Ns_DStringPrintf(&sockPtr->reqPtr->buffer, "%s: %s\r\n", key, Ns_SetValue(hdrPtr, i)((hdrPtr)->fields[(i)].value));
1156 }
1157 }
1158
1159 Ns_Log(Ns_LogConnchanDebug, "ns_connchan open %s => %s",
1160 url, connChanPtr->channelName);
1161
1162 /*
1163 * Write the request header via the "send" operation of
1164 * the driver.
1165 */
1166 buf[0].iov_base = (void *)sockPtr->reqPtr->request.line;
1167 buf[0].iov_len = strlen(buf[0].iov_base);
1168
1169 buf[1].iov_base = (void *)"\r\n";
1170 buf[1].iov_len = 2u;
1171
1172 buf[2].iov_base = (void *)sockPtr->reqPtr->buffer.string;
1173 buf[2].iov_len = (size_t)Tcl_DStringLength(&sockPtr->reqPtr->buffer)((&sockPtr->reqPtr->buffer)->length);
1174
1175 buf[3].iov_base = (void *)"\r\n";
1176 buf[3].iov_len = 2u;
1177
1178 nSent = ConnchanDriverSend(interp, connChanPtr, buf, 4, 0u, &connChanPtr->sendTimeout);
1179 Ns_Log(Ns_LogConnchanDebug, "%s ConnchanDriverSend sent %" PRIdz"zd" " bytes state %s",
1180 connChanPtr->channelName,
1181 nSent, errno(*__errno_location ()) != 0 ? strerror(errno(*__errno_location ())) : "ok");
1182
1183 if (nSent > -1) {
1184 connChanPtr->wBytes += (size_t)nSent;
1185 Tcl_SetObjResult(interp, Tcl_NewStringObj(connChanPtr->channelName, -1));
1186 } else {
1187 result = TCL_ERROR1;
1188 }
1189 }
1190 }
1191
1192 if (unlikely(result != TCL_OK && sockPtr != NULL && sockPtr->sock > 0)(__builtin_expect((result != 0 && sockPtr != ((void*)
0) && sockPtr->sock > 0), 0))
) {
1193 ns_sockcloseclose(sockPtr->sock);
1194 }
1195 Ns_Log(Ns_LogConnchanDebug, "ns_connchan open %s returns %d", url, result);
1196 }
1197 return result;
1198}
1199/*
1200 *----------------------------------------------------------------------
1201 *
1202 * ConnChanConnectObjCmd --
1203 *
1204 * Implements "ns_connchan connect".
1205 *
1206 * Results:
1207 * A standard Tcl result.
1208 *
1209 * Side effects:
1210 * Depends on subcommand.
1211 *
1212 *----------------------------------------------------------------------
1213 */
1214static int
1215ConnChanConnectObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1216{
1217 int result, doTLS = (int)NS_FALSE0;
1218 unsigned short portNr = 0u;
1219 char *host;
1220 Ns_Time timeout = {1, 0}, *timeoutPtr = &timeout;
1221 Ns_ObjvSpec lopts[] = {
1222 {"-tls", Ns_ObjvBool, &doTLS, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
1223 {"-timeout", Ns_ObjvTime, &timeoutPtr, NULL((void*)0)},
1224 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1225 };
1226 Ns_ObjvSpec largs[] = {
1227 {"host", Ns_ObjvString, &host, NULL((void*)0)},
1228 {"port", Ns_ObjvUShort, &portNr, NULL((void*)0)},
1229 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1230 };
1231
1232 if (Ns_ParseObjv(lopts, largs, interp, 2, objc, objv) != NS_OK) {
1233 result = TCL_ERROR1;
1234 } else {
1235 //const NsInterp *itPtr = clientData;
1236 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1237 Sock *sockPtr = NULL((void*)0);
1238 NS_SOCKETint sock;
1239 Ns_ReturnCode status;
1240
1241 sock = Ns_SockTimedConnect2(host, portNr, NULL((void*)0), 0u, timeoutPtr, &status);
1242
1243 if (sock == NS_INVALID_SOCKET(-1)) {
1244 Ns_SockConnectError(interp, host, portNr, status);
1245 result = TCL_ERROR1;
1246
1247 } else {
1248 result = NSDriverSockNew(interp, sock, doTLS ? "https" : "http", NULL((void*)0), "CONNECT", &sockPtr);
1249 }
1250
1251 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1252
1253 if (STREQ(sockPtr->drvPtr->protocol, "https")(((*(sockPtr->drvPtr->protocol)) == (*("https"))) &&
(strcmp((sockPtr->drvPtr->protocol),("https")) == 0))
) {
Similar code here
1254 NS_TLS_SSL_CTXSSL_CTX *ctx;
1255
1256 assert(sockPtr->drvPtr->clientInitProc != NULL)((void) (0));
1257
1258 /*
1259 * For the time being, just pass NULL
1260 * structures. Probably, we could create the
1261 * SSLcontext.
1262 */
1263 result = Ns_TLS_CtxClientCreate(interp,
1264 NULL((void*)0) /*cert*/, NULL((void*)0) /*caFile*/,
1265 NULL((void*)0) /* caPath*/, NS_FALSE0 /*verify*/,
1266 &ctx);
1267 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1268 Ns_DriverClientInitArg params = {ctx, host};
1269 result = (*sockPtr->drvPtr->clientInitProc)(interp, (Ns_Sock *)sockPtr, &params);
1270
1271 /*
1272 * For the time being, we create/delete the ctx in
1273 * an eager fashion. We could probably make it
1274 * reusable and keep it around.
1275 */
1276 if (ctx != NULL((void*)0)) {
1277 Ns_TLS_CtxFree(ctx);
1278 }
1279 }
1280 }
1281
1282 if (likely(result == TCL_OK)(__builtin_expect((result == 0), 1))) {
1283 Ns_Time now;
1284 NsConnChan *connChanPtr;
1285
1286 Ns_GetTime(&now);
1287 connChanPtr = ConnChanCreate(servPtr,
1288 sockPtr,
1289 &now,
1290 NULL((void*)0),
1291 NS_TRUE1 /* binary, fixed for the time being */,
1292 NULL((void*)0));
1293
1294 Tcl_SetObjResult(interp, Tcl_NewStringObj(connChanPtr->channelName, -1));
1295 }
1296
1297 }
1298 if (unlikely(result != TCL_OK && sockPtr != NULL && sockPtr->sock > 0)(__builtin_expect((result != 0 && sockPtr != ((void*)
0) && sockPtr->sock > 0), 0))
) {
1299 ns_sockcloseclose(sockPtr->sock);
1300 }
1301 Ns_Log(Ns_LogConnchanDebug, "ns_connchan connect %s %hu returns %d", host, portNr, result);
1302 }
1303 return result;
1304}
1305
1306
1307/*
1308 *----------------------------------------------------------------------
1309 *
1310 * ConnChanListenObjCmd --
1311 *
1312 * Implements "ns_connchan listen".
1313 *
1314 * Results:
1315 * A standard Tcl result.
1316 *
1317 * Side effects:
1318 * Depends on subcommand.
1319 *
1320 *----------------------------------------------------------------------
1321 */
1322
1323static int
1324ConnChanListenObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1325{
1326 //const NsInterp *itPtr = clientData;
1327 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1328 int result, doBind = (int)NS_FALSE0;
1329 unsigned short port = 0u;
1330 char *driverName = NULL((void*)0), *addr = (char*)NS_EMPTY_STRING, *script;
1331 Ns_ObjvSpec lopts[] = {
1332 {"-driver", Ns_ObjvString, &driverName, NULL((void*)0)},
1333 {"-server", Ns_ObjvServer, &servPtr, NULL((void*)0)},
1334 {"-bind", Ns_ObjvBool, &doBind, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
1335 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1336 };
1337 Ns_ObjvSpec largs[] = {
1338 {"address", Ns_ObjvString, &addr, NULL((void*)0)},
1339 {"port", Ns_ObjvUShort, &port, NULL((void*)0)},
1340 {"script", Ns_ObjvString, &script, NULL((void*)0)},
1341 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1342 };
1343
1344 if (Ns_ParseObjv(lopts, largs, interp, 2, objc, objv) != NS_OK) {
1345 result = TCL_ERROR1;
1346
1347 } else {
1348 ListenCallback *lcbPtr;
1349 size_t scriptLength;
1350 NS_SOCKETint sock;
1351
1352 if (STREQ(addr, "*")(((*(addr)) == (*("*"))) && (strcmp((addr),("*")) == 0
))
) {
1353 addr = NULL((void*)0);
1354 }
1355 scriptLength = strlen(script);
1356 lcbPtr = ns_malloc(sizeof(ListenCallback) + scriptLength);
1357 if (unlikely(lcbPtr == NULL)(__builtin_expect((lcbPtr == ((void*)0)), 0))) {
1358 return TCL_ERROR1;
1359 }
1360
1361 lcbPtr->server = servPtr->server;
1362 memcpy(lcbPtr->script, script, scriptLength + 1u);
1363 lcbPtr->driverName = ns_strcopy(driverName);
1364 sock = Ns_SockListenCallback(addr, port, SockListenCallback, doBind, lcbPtr);
1365
1366 /* Ns_Log(Notice, "ns_connchan listen calls Ns_SockListenCallback, returning %d", sock);*/
1367 if (sock == NS_INVALID_SOCKET(-1)) {
1368 Ns_TclPrintfResult(interp, "could not register callback");
1369 ns_free(lcbPtr);
1370 result = TCL_ERROR1;
1371 } else {
1372 struct NS_SOCKADDR_STORAGEsockaddr_storage sa;
1373 socklen_t len = (socklen_t)sizeof(sa);
1374 Sock *sockPtr = NULL((void*)0);
1375
1376 result = NSDriverSockNew(interp, sock, "http", lcbPtr->driverName, "CONNECT", &sockPtr);
1377 if (result == TCL_OK0 && sockPtr->servPtr != NULL((void*)0)) {
1378 Ns_Time now;
1379 NsConnChan *connChanPtr;
1380 int retVal;
1381
1382 Ns_GetTime(&now);
1383 connChanPtr = ConnChanCreate(sockPtr->servPtr,
1384 sockPtr,
1385 &now,
1386 NULL((void*)0),
1387 NS_TRUE1 /* binary, fixed for the time being */,
1388 NULL((void*)0));
1389 retVal = getsockname(sock, (struct sockaddr *) &sa, &len);
1390 if (retVal == -1) {
1391 Ns_TclPrintfResult(interp, "can't obtain socket info %s", ns_sockstrerrorstrerror(ns_sockerrno(*__errno_location ())));
1392 servPtr = NsGetServer(nsconf.defaultServer); // temporarily added
1393 ConnChanFree(connChanPtr, servPtr/*sockPtr->servPtr*/);
1394 result = TCL_ERROR1;
1395 } else {
1396 Tcl_Obj *listObj = Tcl_NewListObj(0, NULL((void*)0));
1397 char ipString[NS_IPADDR_SIZE46];
1398
1399 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("channel", 7));
1400 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(connChanPtr->channelName, -1));
1401
1402 port = Ns_SockaddrGetPort((struct sockaddr *) &sa);
1403 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("port", 4));
1404 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj((int)port));
1405
1406 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("sock", 4));
1407 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj((int)sock));
1408
1409 ns_inet_ntop((struct sockaddr *) &sa, ipString, sizeof(ipString));
1410 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj("address", 7));
1411 Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(ipString, -1));
1412
1413 Tcl_SetObjResult(interp, listObj);
1414 }
1415 }
1416 }
1417 }
1418 Ns_Log(Ns_LogConnchanDebug, "ns_connchan listen %s %hu returns %d", addr, port, result);
1419 return result;
1420}
1421
1422/*
1423 *----------------------------------------------------------------------
1424 *
1425 * SockListenCallback --
1426 *
1427 * This is the C wrapper callback that is registered from
1428 * ListenCallback.
1429 *
1430 * Results:
1431 * NS_TRUE or NS_FALSE on error.
1432 *
1433 * Side effects:
1434 * Will run Tcl script.
1435 *
1436 *----------------------------------------------------------------------
1437 */
1438
1439static bool_Bool
1440SockListenCallback(NS_SOCKETint sock, void *arg, unsigned int UNUSED(why)UNUSED_why __attribute__((__unused__)))
1441{
1442 const ListenCallback *lcbPtr;
1443 Tcl_Interp *interp;
1444 int result;
1445 Sock *sockPtr = NULL((void*)0);
1446 Ns_Time now;
1447 Tcl_Obj *listObj = Tcl_NewListObj(0, NULL((void*)0));
1448 NsConnChan *connChanPtr = NULL((void*)0);
1449
1450 assert(arg != NULL)((void) (0));
1451
1452 lcbPtr = arg;
1453 interp = Ns_TclAllocateInterp(lcbPtr->server);
1454
1455 result = NSDriverSockNew(interp, sock, "http", lcbPtr->driverName, "CONNECTED", &sockPtr);
1456
1457 if (result == TCL_OK0) {
1458 Ns_GetTime(&now);
1459 connChanPtr = ConnChanCreate(sockPtr->servPtr,
1460 sockPtr,
1461 &now,
1462 NULL((void*)0),
1463 NS_TRUE1 /* binary, fixed for the time being */,
1464 NULL((void*)0));
1465 Ns_Log(Notice, "SockListenCallback new connChan %s sock %d", connChanPtr->channelName, sock);
1466 }
1467
1468 if (connChanPtr != NULL((void*)0)) {
1469 Tcl_DString script;
1470
1471 Tcl_DStringInit(&script);
1472 Tcl_DStringAppend(&script, lcbPtr->script, -1);
1473 Tcl_DStringAppendElement(&script, connChanPtr->channelName);
1474 result = Tcl_EvalEx(interp, script.string, script.length, 0);
1475 Tcl_DStringFree(&script);
1476 if (result != TCL_OK0) {
1477 (void) Ns_TclLogErrorInfo(interp, "\n(context: connchan proc)");
1478 } else {
1479 Tcl_Obj *objPtr = Tcl_GetObjResult(interp);
1480 int ok = 1;
1481
1482 /*
1483 * The Tcl callback can signal with the result "0",
1484 * that the connection channel should be closed
1485 * automatically.
1486 */
1487 result = Tcl_GetBooleanFromObj(interp, objPtr, &ok);
1488 if ((result == TCL_OK0) && (ok == 0)) {
1489 result = TCL_ERROR1;
1490 }
1491 }
1492 }
1493
1494 Ns_TclDeAllocateInterp(interp);
1495 Tcl_DecrRefCount(listObj)do { Tcl_Obj *_objPtr = (listObj); if (_objPtr->refCount--
<= 1) { TclFreeObj(_objPtr); } } while(0)
;
1496
1497 return (result == TCL_OK0);
1498}
1499
1500
1501/*
1502 *----------------------------------------------------------------------
1503 *
1504 * ConnChanListObjCmd --
1505 *
1506 * Implements "ns_connchan list".
1507 *
1508 * Results:
1509 * A standard Tcl result.
1510 *
1511 * Side effects:
1512 * Depends on subcommand.
1513 *
1514 *----------------------------------------------------------------------
1515 */
1516static int
1517ConnChanListObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1518{
1519 //const NsInterp *itPtr = clientData;
1520 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1521 int result = TCL_OK0;
1522 Ns_ObjvSpec lopts[] = {
1523 {"-server", Ns_ObjvServer, &servPtr, NULL((void*)0)},
1524 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1525 };
1526
1527 if (Ns_ParseObjv(lopts, NULL((void*)0), interp, 2, objc, objv) != NS_OK) {
1528 result = TCL_ERROR1;
1529 }
1530
1531 if (result == TCL_OK0) {
1532 Tcl_HashSearch search;
1533 Tcl_HashEntry *hPtr;
1534 Tcl_DString ds, *dsPtr = &ds;
1535
1536 /*
1537 * The provided parameters appear to be valid. Lock the channel
1538 * table and return the infos for every existing entry in the
1539 * connection channel table.
1540 */
1541 Tcl_DStringInit(dsPtr);
1542
1543 Ns_RWLockRdLock(&servPtr->connchans.lock);
1544 hPtr = Tcl_FirstHashEntry(&servPtr->connchans.table, &search);
1545 while (hPtr != NULL((void*)0)) {
1546 NsConnChan *connChanPtr;
1547
1548 connChanPtr = (NsConnChan *)Tcl_GetHashValue(hPtr)((hPtr)->clientData);
1549 Ns_DStringPrintf(dsPtr, "{%s %s " NS_TIME_FMT"%" "l" "d" ".%06ld" " %s %s %" PRIdz"zd" " %" PRIdz"zd",
1550 (char *)Tcl_GetHashKey(&servPtr->connchans.table, hPtr)((void *) (((&servPtr->connchans.table)->keyType ==
(1) || (&servPtr->connchans.table)->keyType == (-1
)) ? (hPtr)->key.oneWordValue : (hPtr)->key.string))
,
1551 ((connChanPtr->cbPtr != NULL((void*)0) && connChanPtr->cbPtr->threadName != NULL((void*)0)) ?
1552 connChanPtr->cbPtr->threadName : "{}"),
1553 (int64_t) connChanPtr->startTime.sec, connChanPtr->startTime.usec,
1554 connChanPtr->sockPtr->drvPtr->moduleName,
1555 (*connChanPtr->peer == '\0' ? "{}" : connChanPtr->peer),
1556 connChanPtr->wBytes,
1557 connChanPtr->rBytes);
1558 Ns_DStringAppendElementTcl_DStringAppendElement(dsPtr,
1559 (connChanPtr->clientData != NULL((void*)0)) ? connChanPtr->clientData : NS_EMPTY_STRING);
1560 /*
1561 * If we have a callback, write the cmd name. Rationale:
1562 * next arguments might contain already binary
1563 * data. Limitation: cmd name must not contain funny
1564 * characters.
1565 */
1566 if (connChanPtr->cbPtr != NULL((void*)0)) {
1567 char whenBuffer[6];
1568
1569 Ns_DStringNAppendTcl_DStringAppend(dsPtr, " ", 1);
1570 Ns_DStringNAppendTcl_DStringAppend(dsPtr, connChanPtr->cbPtr->script, (int)connChanPtr->cbPtr->scriptCmdNameLength);
1571 Ns_DStringAppendElementTcl_DStringAppendElement(dsPtr, WhenToString(whenBuffer, connChanPtr->cbPtr->when));
1572 } else {
1573 Ns_DStringNAppendTcl_DStringAppend(dsPtr, " {} {}", 6);
1574 }
1575
1576 /*
1577 * Terminate the list.
1578 */
1579 Ns_DStringNAppendTcl_DStringAppend(dsPtr, "} ", 2);
1580 hPtr = Tcl_NextHashEntry(&search);
1581 }
1582 Ns_RWLockUnlock(&servPtr->connchans.lock);
1583
1584 Tcl_DStringResult(interp, dsPtr);
1585 }
1586 return result;
1587}
1588
1589/*
1590 *----------------------------------------------------------------------
1591 *
1592 * ConnChanStatusObjCmd --
1593 *
1594 * Implements "ns_connchan status".
1595 *
1596 * Results:
1597 * A standard Tcl result.
1598 *
1599 * Side effects:
1600 * None.
1601 *
1602 *----------------------------------------------------------------------
1603 */
1604static int
1605ConnChanStatusObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1606{
1607 //const NsInterp *itPtr = clientData;
1608 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1609 char *name = (char*)NS_EMPTY_STRING;
1610 int result = TCL_OK0;
1611 Ns_ObjvSpec lopts[] = {
1612 {"-server", Ns_ObjvServer, &servPtr, NULL((void*)0)},
1613 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1614 };
1615 Ns_ObjvSpec args[] = {
1616 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
1617 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1618 };
1619
1620 if (Ns_ParseObjv(lopts, args, interp, 2, objc, objv) != NS_OK) {
1621 result = TCL_ERROR1;
1622 }
1623
1624 if (result == TCL_OK0) {
1625 NsConnChan *connChanPtr = ConnChanGet(interp, servPtr, name);
1626
1627 if (connChanPtr != NULL((void*)0)) {
1628 Tcl_DString ds;
1629 Tcl_Obj *dictObj = Tcl_NewDictObj();
1630
1631 Tcl_DStringInit(&ds);
1632 Ns_DStringPrintf(&ds, NS_TIME_FMT"%" "l" "d" ".%06ld", (int64_t) connChanPtr->startTime.sec, connChanPtr->startTime.usec);
1633
1634 Tcl_DictObjPut(NULL((void*)0), dictObj,
1635 Tcl_NewStringObj("start", 5),
1636 Tcl_NewStringObj(ds.string, ds.length));
1637 Tcl_DictObjPut(NULL((void*)0), dictObj,
1638 Tcl_NewStringObj("driver", 6),
1639 Tcl_NewStringObj(connChanPtr->sockPtr->drvPtr->moduleName, -1));
1640 Tcl_DictObjPut(NULL((void*)0), dictObj,
1641 Tcl_NewStringObj("peer", 4),
1642 Tcl_NewStringObj(*connChanPtr->peer == '\0' ? "" : connChanPtr->peer, -1));
1643 Tcl_DictObjPut(NULL((void*)0), dictObj,
1644 Tcl_NewStringObj("sent", 4),
1645 Tcl_NewWideIntObj((Tcl_WideInt)connChanPtr->wBytes));
1646 Tcl_DictObjPut(NULL((void*)0), dictObj,
1647 Tcl_NewStringObj("reveived", 8),
1648 Tcl_NewWideIntObj((Tcl_WideInt)connChanPtr->rBytes));
1649 Tcl_DictObjPut(NULL((void*)0), dictObj,
1650 Tcl_NewStringObj("framebuffer", 8),
1651 Tcl_NewIntObj(ConnChanBufferSize(connChanPtr,frameBuffer)((connChanPtr)->frameBuffer != ((void*)0) ? (connChanPtr)->
frameBuffer->length : 0)
));
1652 Tcl_DictObjPut(NULL((void*)0), dictObj,
1653 Tcl_NewStringObj("sendbuffer", 10),
1654 Tcl_NewIntObj(ConnChanBufferSize(connChanPtr,sendBuffer)((connChanPtr)->sendBuffer != ((void*)0) ? (connChanPtr)->
sendBuffer->length : 0)
));
1655 Tcl_DictObjPut(NULL((void*)0), dictObj,
1656 Tcl_NewStringObj("fragments", 9),
1657 Tcl_NewIntObj(ConnChanBufferSize(connChanPtr,fragmentsBuffer)((connChanPtr)->fragmentsBuffer != ((void*)0) ? (connChanPtr
)->fragmentsBuffer->length : 0)
));
1658
1659 if (connChanPtr->cbPtr != NULL((void*)0)) {
1660 char whenBuffer[6];
1661
1662 Tcl_DictObjPut(NULL((void*)0), dictObj, Tcl_NewStringObj("callback", 8),
1663 Tcl_NewStringObj(connChanPtr->cbPtr->script, -1));
1664 Tcl_DictObjPut(NULL((void*)0), dictObj, Tcl_NewStringObj("condition", 9),
1665 Tcl_NewStringObj(WhenToString(whenBuffer, connChanPtr->cbPtr->when), -1));
1666 }
1667 Tcl_DStringFree(&ds);
1668 Tcl_SetObjResult(interp, dictObj);
1669
1670 } else {
1671 result = TCL_ERROR1;
1672 }
1673 }
1674 return result;
1675}
1676
1677
1678/*
1679 *----------------------------------------------------------------------
1680 *
1681 * ConnChanCloseObjCmd --
1682 *
1683 * Implements "ns_connchan close".
1684 *
1685 * Results:
1686 * A standard Tcl result.
1687 *
1688 * Side effects:
1689 * Depends on subcommand.
1690 *
1691 *----------------------------------------------------------------------
1692 */
1693static int
1694ConnChanCloseObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1695{
1696 //const NsInterp *itPtr = clientData;
1697 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1698 char *name = (char*)NS_EMPTY_STRING;
1699 int result = TCL_OK0;
1700 Ns_ObjvSpec lopts[] = {
1701 {"-server", Ns_ObjvServer, &servPtr, NULL((void*)0)},
1702 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1703 };
1704 Ns_ObjvSpec args[] = {
1705 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
1706 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1707 };
1708
1709 if (Ns_ParseObjv(lopts, args, interp, 2, objc, objv) != NS_OK) {
1710 result = TCL_ERROR1;
1711
1712 } else {
1713 NsConnChan *connChanPtr = ConnChanGet(interp, servPtr, name);
1714
1715 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan close connChanPtr %p", name, (void*)connChanPtr);
1716
1717 if (connChanPtr != NULL((void*)0)) {
1718 ConnChanFree(connChanPtr, servPtr);
1719 } else {
1720 result = TCL_ERROR1;
1721 }
1722
1723 }
1724 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan close returns %d", name, result);
1725 return result;
1726}
1727
1728/*
1729 *----------------------------------------------------------------------
1730 *
1731 * ConnChanCallbackObjCmd --
1732 *
1733 * Implements "ns_connchan callback".
1734 *
1735 * Results:
1736 * A standard Tcl result.
1737 *
1738 * Side effects:
1739 * Depends on subcommand.
1740 *
1741 *----------------------------------------------------------------------
1742 */
1743static int
1744ConnChanCallbackObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1745{
1746 int result = TCL_OK0;
1747 char *name = (char*)NS_EMPTY_STRING,
1748 *script = (char*)NS_EMPTY_STRING,
1749 *whenString = (char*)NS_EMPTY_STRING;
1750 Ns_Time *pollTimeoutPtr = NULL((void*)0), *recvTimeoutPtr = NULL((void*)0), *sendTimeoutPtr = NULL((void*)0);
1751
1752 Ns_ObjvSpec lopts[] = {
1753 {"-timeout", Ns_ObjvTime, &pollTimeoutPtr, NULL((void*)0)},
1754 {"-receivetimeout", Ns_ObjvTime, &recvTimeoutPtr, NULL((void*)0)},
1755 {"-sendtimeout", Ns_ObjvTime, &sendTimeoutPtr, NULL((void*)0)},
1756 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1757 };
1758 Ns_ObjvSpec args[] = {
1759 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
1760 {"script", Ns_ObjvString, &script, NULL((void*)0)},
1761 {"when", Ns_ObjvString, &whenString, NULL((void*)0)},
1762 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1763 };
1764
1765 if (Ns_ParseObjv(lopts, args, interp, 2, objc, objv) != NS_OK) {
1766 result = TCL_ERROR1;
1767 } else {
1768 //const NsInterp *itPtr = clientData;
1769 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1770 NsConnChan *connChanPtr = ConnChanGet(interp, servPtr, name);
1771 size_t whenStrlen = strlen(whenString);
1772
1773 assert(whenString != NULL)((void) (0));
1774
1775 if (unlikely(connChanPtr == NULL)(__builtin_expect((connChanPtr == ((void*)0)), 0))) {
1776 result = TCL_ERROR1;
1777 } else if (whenStrlen == 0 || whenStrlen > 4) {
1778
1779 Ns_TclPrintfResult(interp, "invalid when specification: \"%s\":"
1780 " should be one/more of r, w, e, or x", whenString);
1781 result = TCL_ERROR1;
1782
1783 } else {
1784 /*
1785 * The provided channel name exists. In a first step get
1786 * the flags from the when string.
1787 */
1788 unsigned int when = 0u;
1789 const char *s = whenString;
1790
1791 while (*s != '\0') {
1792 if (*s == 'r') {
1793 when |= (unsigned int)NS_SOCK_READ;
1794 } else if (*s == 'w') {
1795 when |= (unsigned int)NS_SOCK_WRITE;
1796 } else if (*s == 'e') {
1797 when |= (unsigned int)NS_SOCK_EXCEPTION;
1798 } else if (*s == 'x') {
1799 when |= (unsigned int)NS_SOCK_EXIT;
1800 } else {
1801 Ns_TclPrintfResult(interp, "invalid when specification: \"%s\":"
1802 " should be one/more of r, w, e, or x", whenString);
1803 result = TCL_ERROR1;
1804 break;
1805 }
1806 s++;
1807 }
1808
1809 if (result == TCL_OK0) {
1810 Ns_ReturnCode status;
1811
1812 Ns_RWLockWrLock(&servPtr->connchans.lock);
1813
1814 /*
1815 * Fill in the timeouts, when these are provided.
1816 */
1817 if (recvTimeoutPtr != NULL((void*)0)) {
1818 connChanPtr->recvTimeout = *recvTimeoutPtr;
1819 }
1820 if (sendTimeoutPtr != NULL((void*)0)) {
1821 connChanPtr->sendTimeout = *sendTimeoutPtr;
1822 }
1823
1824 /*
1825 * Register the callback. This function call might set
1826 * "connChanPtr->sockPtr = NULL;", therefore, we can't
1827 * derive always the servPtr from the
1828 * connChanPtr->sockPtr and we have to pass the
1829 * servPtr to ConnChanFree().
1830 */
1831 status = SockCallbackRegister(connChanPtr, script, when, pollTimeoutPtr);
1832
1833 if (unlikely(status != NS_OK)(__builtin_expect((status != NS_OK), 0))) {
1834 Ns_TclPrintfResult(interp, "could not register callback");
1835 servPtr = NsGetServer(nsconf.defaultServer); // temporarily added
1836 ConnChanFree(connChanPtr, servPtr);
1837 result = TCL_ERROR1;
1838 }
1839 Ns_RWLockUnlock(&servPtr->connchans.lock);
1840 }
1841 }
1842 }
1843 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan callback returns %d", name, result);
1844 return result;
1845}
1846
1847/*
1848 *----------------------------------------------------------------------
1849 *
1850 * ConnChanExistsObjCmd --
1851 *
1852 * Implements "ns_connchan exists".
1853 *
1854 * Results:
1855 * A standard Tcl result.
1856 *
1857 * Side effects:
1858 * Depends on subcommand.
1859 *
1860 *----------------------------------------------------------------------
1861 */
1862static int
1863ConnChanExistsObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
1864{
1865 char *name = (char*)NS_EMPTY_STRING;
1866 int result = TCL_OK0;
1867 Ns_ObjvSpec args[] = {
1868 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
1869 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
1870 };
1871
1872 if (Ns_ParseObjv(NULL((void*)0), args, interp, 2, objc, objv) != NS_OK) {
1873 result = TCL_ERROR1;
1874 } else {
1875 //const NsInterp *itPtr = clientData;
1876 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
1877 const NsConnChan *connChanPtr;
1878
1879 connChanPtr = ConnChanGet(interp, servPtr, name);
1880 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(connChanPtr != NULL)Tcl_NewIntObj((connChanPtr != ((void*)0))!=0));
1881 }
1882
1883 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan exists returns %d", name, result);
1884 return result;
1885}
1886
1887/*
1888 *----------------------------------------------------------------------
1889 *
1890 * ConnChanReadBuffer --
1891 *
1892 * Read from a connchan into a provided buffer. In essence, this
1893 * function performs timeout setup and handles NS_SOCK_AGAIN.
1894 *
1895 * Results:
1896 * Number of bytes read or -1 on error.
1897 *
1898 * Side effects:
1899 * None.
1900 *
1901 *----------------------------------------------------------------------
1902 */
1903static ssize_t
1904ConnChanReadBuffer(NsConnChan *connChanPtr, char *buffer, size_t bufferSize)
1905{
1906 ssize_t nRead = 0;
1907 struct iovec buf;
1908 Ns_Time *timeoutPtr, timeout;
1909
1910 /*
1911 * Read the data via the "receive" operation of the driver.
1912 */
1913 buf.iov_base = buffer;
1914 buf.iov_len = bufferSize;
1915
1916 timeoutPtr = &connChanPtr->recvTimeout;
1917 if (timeoutPtr->sec == 0 && timeoutPtr->usec == 0) {
1918 /*
1919 * No timeout was specified, use the configured receivewait of
1920 * the driver as timeout.
1921 */
1922 timeout.sec = connChanPtr->sockPtr->drvPtr->recvwait.sec;
1923 timeout.usec = connChanPtr->sockPtr->drvPtr->recvwait.usec;
1924 timeoutPtr = &timeout;
1925 }
1926
1927 /*
1928 * In case we see an NS_SOCK_AGAIN, retry. We could make
1929 * this behavior optional via argument, but with OpenSSL,
1930 * this seems to happen quite often.
1931 */
1932 for (;;) {
1933 nRead = NsDriverRecv(connChanPtr->sockPtr, &buf, 1, timeoutPtr);
1934 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan NsDriverRecv %" PRIdz"zd"
1935 " bytes recvSockState %.4x (driver %s)", connChanPtr->channelName, nRead,
1936 connChanPtr->sockPtr->recvSockState,
1937 connChanPtr->sockPtr->drvPtr->moduleName);
1938 if (nRead == 0 && connChanPtr->sockPtr->recvSockState == NS_SOCK_AGAIN) {
1939 continue;
1940 }
1941 break;
1942 }
1943 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan NsDriverRecv %" PRIdz"zd" " bytes",
1944 connChanPtr->channelName, nRead);
1945
1946 return nRead;
1947}
1948
1949/*
1950 *----------------------------------------------------------------------
1951 *
1952 * RequireDsBuffer --
1953 *
1954 * Make sure, the DS buffer is allocated.
1955 *
1956 * Results:
1957 * None.
1958 *
1959 * Side effects:
1960 * Potentially updates dsPtr which is passed as an argument
1961 *
1962 *----------------------------------------------------------------------
1963 */
1964static void
1965RequireDsBuffer(Tcl_DString **dsPtr) {
1966 NS_NONNULL_ASSERT(dsPtr != NULL)((void) (0));
1967
1968 if (*dsPtr == NULL((void*)0)) {
1969 *dsPtr = ns_malloc(sizeof(Tcl_DString));
1970 Tcl_DStringInit(*dsPtr);
1971 }
1972}
1973
1974/*
1975 *----------------------------------------------------------------------
1976 *
1977 * WebsocketFrameSetCommonMembers --
1978 *
1979 * Set common members of the dict, which are part of the result of
1980 * every GetWebsocketFrame() operation.
1981 *
1982 * Results:
1983 * None.
1984 *
1985 * Side effects:
1986 * None.
1987 *
1988 *----------------------------------------------------------------------
1989 */
1990static void
1991WebsocketFrameSetCommonMembers(Tcl_Obj *resultObj, ssize_t nRead, const NsConnChan *connChanPtr)
1992{
1993 NS_NONNULL_ASSERT(resultObj != NULL)((void) (0));
1994 NS_NONNULL_ASSERT(connChanPtr != NULL)((void) (0));
1995
1996 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("bytes", 5),
1997 Tcl_NewLongObj((long)nRead));
1998 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("unprocessed", 11),
1999 Tcl_NewIntObj(connChanPtr->frameBuffer->length));
2000 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("fragments", 9),
2001 Tcl_NewIntObj(ConnChanBufferSize(connChanPtr,fragmentsBuffer)((connChanPtr)->fragmentsBuffer != ((void*)0) ? (connChanPtr
)->fragmentsBuffer->length : 0)
));
2002 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("havedata", 8),
2003 Tcl_NewIntObj(!connChanPtr->frameNeedsData));
2004}
2005
2006/*
2007 *----------------------------------------------------------------------
2008 *
2009 * GetWebsocketFrame --
2010 *
2011 * Frame handling for incoming WebSockets. This function checks,
2012 * if the data read so far is a complete WebSocket frame
2013 * (potentially consisting of multiple fragments) and returns the
2014 * results in form of a Tcl dict. To handle partial frames or
2015 * surplus data, the command performs socket level buffering based
2016 * on Tcl_DStrings.
2017 *
2018 * Results:
2019 * Tcl dict containing "fin" status bit, "frame" state (incomplete
2020 * or complete), "unprocessed" (received data in buffer not
2021 * handled so far), "haveData" (boolean value to express that
2022 * unprocessed data might be sufficient for next frame.
2023 *
2024 * In case the frame is finished, the dict contains as well the
2025 * WebSocket "opcode" and "payload" of the frame.
2026 *
2027 * Side effects:
2028 * None.
2029 *
2030 *----------------------------------------------------------------------
2031 */
2032
2033static Tcl_Obj*
2034GetWebsocketFrame(NsConnChan *connChanPtr, char *buffer, ssize_t nRead)
2035{
2036 unsigned char *data;
2037 bool_Bool finished, masked;
2038 int opcode, frameLength, fragmentsBufferLength;
2039 size_t payloadLength, offset;
2040 unsigned char mask[4] = {0u,0u,0u,0u};
2041 Tcl_Obj *resultObj;
2042
2043 resultObj = Tcl_NewDictObj();
2044
2045 if (nRead < 0) {
2046 goto exception;
2047 }
2048
2049 Ns_Log(Ns_LogConnchanDebug, "WS: received %ld bytes, have already %d",
2050 nRead, ConnChanBufferSize(connChanPtr, frameBuffer)((connChanPtr)->frameBuffer != ((void*)0) ? (connChanPtr)->
frameBuffer->length : 0)
);
2051 /*
2052 * Make sure, the frame buffer exists.
2053 */
2054 RequireDsBuffer(&connChanPtr->frameBuffer);
2055
2056 /*
2057 * Append the newly read data.
2058 */
2059 Tcl_DStringAppend(connChanPtr->frameBuffer, buffer, (int)nRead);
2060
2061 /*
2062 * On very small buffers, the interpretation of the first bytes
2063 * does not make sense. We need at least 2 bytes (on the
2064 * connections 6, including the mask).
2065 */
2066 if (connChanPtr->frameBuffer->length < 3) {
2067 frameLength = 0;
2068 goto incomplete;
2069 }
2070
2071 /*
2072 * Check, if frame is complete.
2073 */
2074 data = (unsigned char *)connChanPtr->frameBuffer->string;
2075
2076 finished = ((data[0] & 0x80u) != 0);
2077 masked = ((data[1] & 0x80u) != 0);
2078 opcode = (data[0] & 0x0Fu);
2079 payloadLength = (data[1] & 0x7Fu);
2080
2081 if (payloadLength <= 125) {
2082 offset = 2;
2083 } else if (payloadLength == 126) {
2084 uint16_t len16 = 0;
2085
2086 memcpy(&len16, &data[2], 2);
2087 payloadLength = be16toh(len16)__bswap_16 (len16);
2088 offset = 4;
2089 } else {
2090 uint64_t len64 = 0;
2091
2092 memcpy(&len64, &data[2], 8);
2093 payloadLength = be64toh(len64)__bswap_64 (len64);
2094 offset = 10;
2095 }
2096
2097 if (masked) {
2098 /*
2099 * Initialize mask;
2100 */
2101 memcpy(&mask, &data[offset], 4);
2102 offset += 4;
2103 }
2104
2105 frameLength = (int)(offset + payloadLength);
2106 if (connChanPtr->frameBuffer->length < (int)frameLength) {
2107 goto incomplete;
2108 }
2109
2110 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("fin", 3), Tcl_NewIntObj(finished));
2111 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("frame", 5), Tcl_NewStringObj("complete", 8));
2112
2113 if (!finished) {
2114 Ns_Log(Warning, "WS: unfinished frame, bytes %ld payload length %zu offset %zu "
2115 "avail %d opcode %d fin %d, masked %d",
2116 nRead, payloadLength, offset, connChanPtr->frameBuffer->length,
2117 opcode, finished, masked);
2118
2119 /*{ int i; for(i=0; i<connChanPtr->frameBuffer->length; i++) {
2120 fprintf(stderr,"%.2x",connChanPtr->frameBuffer->string[i]&0xFF);
2121 }
2122 fprintf(stderr, "\n");
2123 }*/
2124 }
2125
2126 if (masked) {
2127 size_t i, j;
2128
2129 for( i = offset, j = 0u; j < payloadLength; i++, j++ ) {
2130 data[ i ] = data[ i ] ^ mask[ j % 4];
2131 }
2132 }
2133
2134 fragmentsBufferLength = ConnChanBufferSize(connChanPtr, fragmentsBuffer)((connChanPtr)->fragmentsBuffer != ((void*)0) ? (connChanPtr
)->fragmentsBuffer->length : 0)
;
2135
2136 if (finished) {
2137 Tcl_Obj *payloadObj;
2138 /*
2139 * The "fin" bit is set, this message is complete. If we have
2140 * fragments, append the new data to the fragments already
2141 * have received and clear the fragments buffer.
2142 */
2143
2144 if (fragmentsBufferLength == 0) {
2145 payloadObj = Tcl_NewByteArrayObj(&data[offset], (int)payloadLength);
2146 } else {
2147 Tcl_DStringAppend(connChanPtr->fragmentsBuffer,
2148 (const char *)&data[offset], (int)payloadLength);
2149 payloadObj = Tcl_NewByteArrayObj((const unsigned char *)connChanPtr->fragmentsBuffer->string,
2150 connChanPtr->fragmentsBuffer->length);
2151 Ns_Log(Ns_LogConnchanDebug,
2152 "WS: append final payload opcode %d (fragments opcode %d) %d bytes, "
2153 "totaling %d bytes, clear fragmentsBuffer",
2154 opcode, connChanPtr->fragmentsOpcode,
2155 (int)payloadLength, connChanPtr->fragmentsBuffer->length);
2156 Tcl_DStringSetLength(connChanPtr->fragmentsBuffer, 0);
2157 opcode = connChanPtr->fragmentsOpcode;
2158 }
2159 Tcl_DictObjPut(NULL((void*)0), resultObj,
2160 Tcl_NewStringObj("opcode", 6),
2161 Tcl_NewIntObj(opcode));
2162 Tcl_DictObjPut(NULL((void*)0), resultObj,
2163 Tcl_NewStringObj("payload", 7),
2164 payloadObj);
2165 } else {
2166 /*
2167 * The "fin" bit is not set, we have a segment, but not the
2168 * complete message. Append the reveived frame to the
2169 * fragments buffer.
2170 */
2171 RequireDsBuffer(&connChanPtr->fragmentsBuffer);
2172 /*
2173 * On the first fragment, keep the opcode since we will need
2174 * it for delivering the full message.
2175 */
2176 if (fragmentsBufferLength == 0) {
2177 connChanPtr->fragmentsOpcode = opcode;
2178 }
2179 Tcl_DStringAppend(connChanPtr->fragmentsBuffer,
2180 (const char *)&data[offset], (int)payloadLength);
2181 Ns_Log(Ns_LogConnchanDebug,
2182 "WS: fin 0 opcode %d (fragments opcode %d) "
2183 "append %d to bytes to the fragmentsBuffer, totaling %d bytes",
2184 opcode, connChanPtr->fragmentsOpcode,
2185 (int)payloadLength, connChanPtr->fragmentsBuffer->length);
2186 }
2187 /*
2188 * Finally, compact the frameBuffer.
2189 */
2190 if (connChanPtr->frameBuffer->length > frameLength) {
2191 int copyLength = connChanPtr->frameBuffer->length - frameLength;
2192
2193 memmove(connChanPtr->frameBuffer->string,
2194 connChanPtr->frameBuffer->string + frameLength,
2195 (size_t)copyLength);
2196 Tcl_DStringSetLength(connChanPtr->frameBuffer, copyLength);
2197 //fprintf(stderr, "WS: leftover %d bytes\n", connChanPtr->frameBuffer->length);
2198 connChanPtr->frameNeedsData = NS_FALSE0;
2199 } else {
2200 connChanPtr->frameNeedsData = NS_TRUE1;
2201 Tcl_DStringSetLength(connChanPtr->frameBuffer, 0);
2202 }
2203 WebsocketFrameSetCommonMembers(resultObj, nRead, connChanPtr);
2204 return resultObj;
2205
2206 incomplete:
2207 connChanPtr->frameNeedsData = NS_TRUE1;
2208 Ns_Log(Notice, "WS: incomplete frameLength %d avail %d",
2209 frameLength, connChanPtr->frameBuffer->length);
2210 Tcl_DictObjPut(NULL((void*)0), resultObj, Tcl_NewStringObj("frame", 5), Tcl_NewStringObj("incomplete", 10));
2211 WebsocketFrameSetCommonMembers(resultObj, nRead, connChanPtr);
2212 return resultObj;
2213
2214 exception:
2215 connChanPtr->frameNeedsData = NS_FALSE0;
2216 Tcl_DictObjPut(NULL((void*)0), resultObj,
2217 Tcl_NewStringObj("frame", 5),
2218 Tcl_NewStringObj("exception", 10));
2219 WebsocketFrameSetCommonMembers(resultObj, nRead, connChanPtr);
2220 return resultObj;
2221}
2222
2223
2224/*
2225 *----------------------------------------------------------------------
2226 *
2227 * ConnChanReadObjCmd --
2228 *
2229 * Implements "ns_connchan read".
2230 *
2231 * Results:
2232 * A standard Tcl result.
2233 *
2234 * Side effects:
2235 * Depends on subcommand.
2236 *
2237 *----------------------------------------------------------------------
2238 */
2239static int
2240ConnChanReadObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
2241{
2242 char *name = (char*)NS_EMPTY_STRING;
2243 int result = TCL_OK0, webSocketFrame = 0;
2244 Ns_ObjvSpec opts[] = {
2245 {"-websocket", Ns_ObjvBool, &webSocketFrame, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
2246 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2247 };
2248 Ns_ObjvSpec args[] = {
2249 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
2250 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2251 };
2252 if (Ns_ParseObjv(opts, args, interp, 2, objc, objv) != NS_OK) {
2253 result = TCL_ERROR1;
2254 } else {
2255 //const NsInterp *itPtr = clientData;
2256 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
2257 NsConnChan *connChanPtr = ConnChanGet(interp, servPtr, name);
2258
2259 if (unlikely(connChanPtr == NULL)(__builtin_expect((connChanPtr == ((void*)0)), 0))) {
2260 result = TCL_ERROR1;
2261 } else {
2262 /*
2263 * The provided channel exists.
2264 */
2265 ssize_t nRead;
2266 char buffer[16384]; //buffer[16384];
2267
2268 if (!connChanPtr->binary) {
2269 Ns_Log(Warning, "ns_connchan: only binary channels are currently supported. "
2270 "Channel %s is not binary", name);
2271 }
2272
2273 if ( webSocketFrame == 0 || connChanPtr->frameNeedsData) {
2274 nRead = ConnChanReadBuffer(connChanPtr, buffer, sizeof(buffer));
2275 if (nRead < 0) {
2276 const char *errorMsg;
2277
2278 errorMsg = NsSockSetRecvErrorCode(connChanPtr->sockPtr, interp);
2279 Tcl_SetObjResult(interp, Tcl_NewStringObj(errorMsg, -1));
2280 result = TCL_ERROR1;
2281
2282 } else if (webSocketFrame == 0 && nRead > 0) {
2283 connChanPtr->rBytes += (size_t)nRead;
2284 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)buffer, (int)nRead));
2285 } else if (webSocketFrame == 1) {
2286 connChanPtr->rBytes += (size_t)nRead;
2287 Tcl_SetObjResult(interp, GetWebsocketFrame(connChanPtr, buffer, nRead));
2288 } else {
2289 /*
2290 * The receive operation failed, maybe a receive
2291 * timeout happened. The read call will simply return
2292 * an empty string. We could notice this fact
2293 * internally by a timeout counter, but for the time
2294 * being no application has usage for it.
2295 */
2296 }
2297 } else {
2298 Tcl_SetObjResult(interp, GetWebsocketFrame(connChanPtr, buffer, 0));
2299 }
2300 }
2301 }
2302
2303 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan read returns %d", name, result);
2304
2305 return result;
2306}
2307
2308/*
2309 *----------------------------------------------------------------------
2310 *
2311 * ConnChanWriteObjCmd --
2312 *
2313 * Implements "ns_connchan write".
2314 *
2315 * Results:
2316 * A standard Tcl result.
2317 *
2318 * Side effects:
2319 * Depends on subcommand.
2320 *
2321 *----------------------------------------------------------------------
2322 */
2323static int
2324ConnChanWriteObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
2325{
2326 char *name = (char*)NS_EMPTY_STRING;
2327 int result = TCL_OK0, buffered = 0;
2328 Tcl_Obj *msgObj;
2329 Ns_ObjvSpec opts[] = {
2330 {"-buffered", Ns_ObjvBool, &buffered, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
2331 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2332 };
2333 Ns_ObjvSpec args[] = {
2334 {"channel", Ns_ObjvString, &name, NULL((void*)0)},
2335 {"msg", Ns_ObjvObj, &msgObj, NULL((void*)0)},
2336 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2337 };
2338
2339 if (Ns_ParseObjv(opts, args, interp, 2, objc, objv) != NS_OK) {
2340 result = TCL_ERROR1;
2341
2342 } else {
2343 //const NsInterp *itPtr = clientData;
2344 NsServer *servPtr = NsGetServer(nsconf.defaultServer); //itPtr->servPtr;
2345 NsConnChan *connChanPtr = ConnChanGet(interp, servPtr, name);
2346
2347 if (unlikely(connChanPtr == NULL)(__builtin_expect((connChanPtr == ((void*)0)), 0))) {
2348 result = TCL_ERROR1;
2349 } else {
2350 /*
2351 * The provided channel name exists.
2352 */
2353 struct iovec bufs[2];
2354 ssize_t nSent;
2355 int msgLen, nBufs = 1, toSend;
2356 const char *msgString = (const char *)Tcl_GetByteArrayFromObj(msgObj, &msgLen);
2357#ifdef WS_RECORD_OUTPUT
2358 static int FD;
2359 static char fnbuffer[100];
2360#endif
2361 if (!connChanPtr->binary) {
2362 Ns_Log(Warning, "ns_connchan: only binary channels are currently supported. "
2363 "Channel %s is not binary", name);
2364 }
2365
2366#ifdef WS_RECORD_OUTPUT
2367 if (connChanPtr->wBytes == 0) {
2368 snprintf(fnbuffer, sizeof(fnbuffer), "/tmp/OUT-XXXXXX")__builtin___snprintf_chk (fnbuffer, sizeof(fnbuffer), 2 - 1, __builtin_object_size
(fnbuffer, 2 > 1), "/tmp/OUT-XXXXXX")
;
2369 mktemp(fnbuffer);
2370 FD = open("/tmp/OUT", O_APPEND02000 | O_WRONLY01 | O_CREAT0100 | O_TRUNC01000, 0644);
2371 fprintf(stderr, "CREATED file %s fd %d\n", fnbuffer, FD)__fprintf_chk (stderr, 2 - 1, "CREATED file %s fd %d\n", fnbuffer
, FD)
;
2372 }
2373#endif
2374
2375 /*
2376 * When buffered was not specified, but we have a
2377 * sendbuffer, fall outmatically into buffered mode.
2378 */
2379 if (buffered == 0 && connChanPtr->sendBuffer != NULL((void*)0)) {
2380 Ns_Log(Notice, "ns_connchan send %s: force buffered", name);
2381 buffered = 1;
2382 }
2383
2384
2385 /*
2386 * Write the data via the "send" operation of the driver.
2387 */
2388 if (msgLen > 0 && buffered && ConnChanBufferSize(connChanPtr, sendBuffer)((connChanPtr)->sendBuffer != ((void*)0) ? (connChanPtr)->
sendBuffer->length : 0)
> 0) {
2389 bufs[0].iov_base = (void *)connChanPtr->sendBuffer->string;
2390 bufs[0].iov_len = (size_t)connChanPtr->sendBuffer->length;
2391 bufs[1].iov_base = (void *)msgString;
2392 bufs[1].iov_len = (size_t)msgLen;
2393 nBufs = 2;
2394 toSend = msgLen + connChanPtr->sendBuffer->length;
2395 } else if (msgLen == 0 && buffered && ConnChanBufferSize(connChanPtr, sendBuffer)((connChanPtr)->sendBuffer != ((void*)0) ? (connChanPtr)->
sendBuffer->length : 0)
> 0) {
2396 bufs[0].iov_base = (void *)connChanPtr->sendBuffer->string;
2397 bufs[0].iov_len = (size_t)connChanPtr->sendBuffer->length;
2398 bufs[1].iov_len = 0u;
2399 toSend = connChanPtr->sendBuffer->length;
2400 Ns_Log(Ns_LogConnchanDebug,
2401 "WS: send buffered only msgLen == 0, buf length %zu toSend %d",
2402 bufs[0].iov_len, toSend);
2403 } else {
2404 bufs[0].iov_base = (void *)msgString;
2405 bufs[0].iov_len = (size_t)msgLen;
2406 bufs[1].iov_len = 0u;
2407 Ns_Log(Ns_LogConnchanDebug, "WS: send msgLen toSend %ld", bufs[0].iov_len);
2408 toSend = msgLen;
2409 }
2410
2411 /*Ns_Log(Notice, "WS: send buffered before nbufs %d len[0] % " PRIdz
2412 ", len[1] %" PRIdz " len %d",
2413 nBufs, bufs[0].iov_len, bufs[1].iov_len, toSend);*/
2414
2415 if (toSend > 0) {
2416 nSent = ConnchanDriverSend(interp, connChanPtr, bufs, nBufs, 0u,
2417 &connChanPtr->sendTimeout);
2418 } else {
2419 nSent = 0;
2420 }
2421
2422 /*Ns_Log(Notice, "WS: send buffered after nbufs %d len[0] %" PRIdz
2423 ", len[1] %" PRIdz " sent %" PRIdz,
2424 nBufs, bufs[0].iov_len, bufs[1].iov_len, nSent);*/
2425
2426 if (nSent > -1) {
2427 int remaining = toSend - (int)nSent;
2428
2429 /*Ns_Log(Notice, "WS: send buffered %d msgLength %d nbufs %d to send %d sent %" PRIdz
2430 " remaining %d errno %d %s (BYTES from %" PRIdz " to %" PRIdz ")",
2431 buffered, msgLen, nBufs, toSend, nSent, remaining,
2432 ns_sockerrno, ns_sockstrerror(ns_sockerrno),
2433 connChanPtr->wBytes, connChanPtr->wBytes + (size_t)MIN(0,nSent));*/
2434
2435 connChanPtr->wBytes += (size_t)nSent;
2436 Tcl_SetObjResult(interp, Tcl_NewLongObj((long)nSent));
2437
2438 if (buffered && remaining > 0) {
2439 int freshDataRemaining;
2440
2441 RequireDsBuffer(&connChanPtr->sendBuffer);
2442 /*
2443 * Compact old data. How much of the (old) sendBuffer was sent?
2444 */
2445 if (nBufs == 2) {
2446 Ns_Log(Ns_LogConnchanDebug, "... two-buffer old buffer length %d + new %d"
2447 " = %d sent %ld (old not fully sent %d)",
2448 connChanPtr->sendBuffer->length, msgLen,
2449 connChanPtr->sendBuffer->length + msgLen,
2450 nSent, (connChanPtr->sendBuffer->length > nSent));
2451 if (connChanPtr->sendBuffer->length > nSent) {
2452 /*
2453 * The old send buffer was not completely
2454 * sent.
2455 *
2456 * bufs[0].len is the unsent length,
2457 * bufs[0].base points to the begin of the
2458 * unset buffer.
2459 */
2460 assert(bufs[0].iov_len > 0)((void) (0));
2461
2462 freshDataRemaining = msgLen;
2463
2464 if (nSent>0) {
2465 Ns_Log(Ns_LogConnchanDebug,
2466 "... have sent part of old buffer %ld "
2467 "(BYTES from %" PRIdz"zd" " to %" PRIdz"zd" ")",
2468 nSent,
2469 connChanPtr->wBytes - (size_t)nSent,
2470 connChanPtr->wBytes);
2471#ifdef WS_RECORD_OUTPUT
2472 write(FD, connChanPtr->sendBuffer->string, (size_t)nSent);
2473 //write(2, connChanPtr->sendBuffer->string, (size_t)nSent);
2474 fprintf(stderr, "\n")__fprintf_chk (stderr, 2 - 1, "\n");
2475#endif
2476 memmove(connChanPtr->sendBuffer->string,
2477 bufs[0].iov_base,
2478 bufs[0].iov_len);
2479 Tcl_DStringSetLength(connChanPtr->sendBuffer, (int)bufs[0].iov_len);
2480 }
2481 } else {
2482 /*
2483 * The old send buffer was fully sent, and
2484 * maybe some of the fresh data.
2485 */
2486 assert(bufs[0].iov_len == 0)((void) (0));
2487 Tcl_DStringSetLength(connChanPtr->sendBuffer, 0);
2488
2489 freshDataRemaining = msgLen - (int)(nSent - connChanPtr->sendBuffer->length);
2490 Ns_Log(Ns_LogConnchanDebug,
2491 "... have sent all of old buffer %d and %ld of new buffer "
2492 "(BYTES from %" PRIdz"zd" " to %" PRIdz"zd" ")",
2493 connChanPtr->sendBuffer->length,
2494 (nSent - connChanPtr->sendBuffer->length),
2495 connChanPtr->wBytes - (size_t)nSent, connChanPtr->wBytes);
2496#ifdef WS_RECORD_OUTPUT
2497 write(FD, connChanPtr->sendBuffer->string, (size_t)connChanPtr->sendBuffer->length);
2498 write(FD, msgString, (size_t)(nSent - connChanPtr->sendBuffer->length));
2499#endif
2500 }
2501 } else if (msgLen == 0) {
2502 /*
2503 * There was only some data from the sendBuffer, no new Data;
2504 */
2505 assert(bufs[0].iov_len > 0)((void) (0));
2506
2507 freshDataRemaining = 0;
2508 Ns_Log(Ns_LogConnchanDebug,
2509 "... have sent from old buffer %" PRIdz"zd" " no new data "
2510 "(BYTES from %" PRIdz"zd" " to %" PRIdz"zd" ")",
2511 nSent,
2512 connChanPtr->wBytes - (size_t)nSent, connChanPtr->wBytes);
2513#ifdef WS_RECORD_OUTPUT
2514 write(FD, connChanPtr->sendBuffer->string, (size_t)nSent);
2515#endif
2516 memmove(connChanPtr->sendBuffer->string,
2517 bufs[0].iov_base,
2518 bufs[0].iov_len);
2519 Tcl_DStringSetLength(connChanPtr->sendBuffer, (int)bufs[0].iov_len);
2520 } else {
2521 /*
2522 * There is only fresh data.
2523 */
2524 freshDataRemaining = msgLen - (int)nSent;
2525#ifdef WS_RECORD_OUTPUT
2526 if (nSent > 0) {
2527 write(FD, msgString, (size_t)nSent);
2528 Ns_Log(Ns_LogConnchanDebug, "... have sent only fresh data %" PRIdz"zd"
2529 " (BYTES from %" PRIdz"zd" " to %" PRIdz"zd" ")",
2530 nSent,
2531 connChanPtr->wBytes - (size_t)nSent, connChanPtr->wBytes);
2532 }
2533#endif
2534 }
2535
2536 if (freshDataRemaining > 0) {
2537 Tcl_DStringAppend(connChanPtr->sendBuffer,
2538 msgString + (msgLen - freshDataRemaining),
2539 freshDataRemaining);
2540 Ns_Log(Ns_LogConnchanDebug, "... keep for later %d bytes of %d "
2541 "(buffered %d) will be BYTES from %" PRIdz"zd" " to %" PRIdz"zd",
2542 freshDataRemaining, msgLen, connChanPtr->sendBuffer->length,
2543 connChanPtr->wBytes,
2544 connChanPtr->wBytes + (size_t)connChanPtr->sendBuffer->length);
2545 }
2546 } else {
2547 /*
2548 * not (buffered && remaining > 0)
2549 */
2550 if (buffered) {
2551 /*
2552 * Everything was sent
2553 */
2554 int buffedLen = ConnChanBufferSize(connChanPtr, sendBuffer)((connChanPtr)->sendBuffer != ((void*)0) ? (connChanPtr)->
sendBuffer->length : 0)
;
2555 Ns_Log(Ns_LogConnchanDebug, "... buffered %d buffedLen %d msgLength %d "
2556 "everything was sent, remaining %d, (BYTES from %" PRIdz"zd" " to %" PRIdz"zd" ")",
2557 buffered, buffedLen, msgLen, remaining,
2558 connChanPtr->wBytes - (size_t)nSent, connChanPtr->wBytes);
2559 assert(remaining == 0)((void) (0));
2560
2561 if (buffedLen > 0) {
2562#ifdef WS_RECORD_OUTPUT
2563 write(FD, connChanPtr->sendBuffer->string, (size_t)buffedLen);
2564#endif
2565 Tcl_DStringSetLength(connChanPtr->sendBuffer, 0);
2566 }
2567#ifdef WS_RECORD_OUTPUT
2568 if (msgLen > 0) {
2569 write(FD, msgString, (size_t)nSent);
2570 }
2571#endif
2572 } else {
2573 /*
2574 * Non-buffered case, there might be a partial send operation
2575 */
2576 if (remaining != 0) {
2577 Ns_Log(Notice, "... partial write: to send %d sent %" PRIdz"zd" " remaining %d",
2578 toSend, nSent, remaining);
2579 }
2580 }
2581 }
2582 } else {
2583 result = TCL_ERROR1;
2584 }
2585 }
2586 }
2587 Ns_Log(Ns_LogConnchanDebug, "%s ns_connchan write returns %d", name, result);
2588
2589 return result;
2590}
2591
2592
2593/*
2594 *----------------------------------------------------------------------
2595 *
2596 * ConnChanWsencodeObjCmd --
2597 *
2598 * Implements "ns_connchan wsencode". Returns a WebSocket frame in
2599 * form of binary data produced from the input parameters.
2600 *
2601 * Results:
2602 * A standard Tcl result.
2603 *
2604 * Side effects:
2605 * None.
2606 *
2607 *----------------------------------------------------------------------
2608 */
2609
2610static int
2611ConnChanWsencodeObjCmd(ClientData UNUSED(clientData)UNUSED_clientData __attribute__((__unused__)), Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
2612{
2613 int result = TCL_OK0, isBinary = 0, opcode = 1, fin = 1, masked = 0;
2614 static Ns_ObjvValueRange finRange = {0, 1};
2615 Tcl_Obj *messageObj;
2616 static Ns_ObjvTable opcodes[] = {
2617 {"continue", 0},
2618 {"text", 1},
2619 {"binary", 2},
2620 {"close", 8},
2621 {"ping", 9},
2622 {"pong", 10},
2623 {NULL((void*)0), 0u}
2624 };
2625 Ns_ObjvSpec opts[] = {
2626 {"-binary", Ns_ObjvBool, &isBinary, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
2627 {"-fin", Ns_ObjvInt, &fin, &finRange},
2628 {"-mask", Ns_ObjvBool, &masked, INT2PTR(NS_TRUE)((void *)(intptr_t)(1))},
2629 {"-opcode", Ns_ObjvIndex, &opcode, &opcodes},
2630 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2631 };
2632 Ns_ObjvSpec args[] = {
2633 {"message", Ns_ObjvObj, &messageObj, NULL((void*)0)},
2634 {NULL((void*)0), NULL((void*)0), NULL((void*)0), NULL((void*)0)}
2635 };
2636
2637 if (Ns_ParseObjv(opts, args, interp, 2, objc, objv) != NS_OK) {
2638 result = TCL_ERROR1;
2639
2640 } else {
2641 const unsigned char *messageString;
2642 unsigned char *data;
2643 int messageLength;
2644 Tcl_DString messageDs, frameDs;
2645 size_t offset;
2646
2647 Tcl_DStringInit(&messageDs);
2648 Tcl_DStringInit(&frameDs);
2649
2650 /*
2651 * When the binary opcode is used, get as well the data in
2652 * form of binary data.
2653 */
2654 if (opcode == 2) {
2655 isBinary = 1;
2656 }
2657 messageString = Ns_GetBinaryString(messageObj, isBinary == 1, &messageLength, &messageDs);
2658 data = (unsigned char *)frameDs.string;
2659
2660 Tcl_DStringSetLength(&frameDs, 2);
2661 /*
2662 * Initialize first two bytes, and then XOR flags into it.
2663 */
2664 data[0] = '\0';
2665 data[1] = '\0';
2666
2667 data[0] = (unsigned char)(data[0] | ((unsigned char)opcode & 0x0Fu));
2668 if (fin) {
2669 data[0] |= 0x80u;
2670 }
2671
2672 if ( messageLength <= 125 ) {
2673 data[1] = (unsigned char)(data[1] | ((unsigned char)messageLength & 0x7Fu));
2674 offset = 2;
2675 } else if ( messageLength <= 65535 ) {
2676 uint16_t len16;
2677 /*
2678 * Together with the first clause, this means:
2679 * messageLength > 125 && messageLength <= 65535
2680 */
2681
2682 Tcl_DStringSetLength(&frameDs, 4);
2683 data[1] |= (( unsigned char )126 & 0x7Fu);
2684 len16 = htobe16((short unsigned int)messageLength)__bswap_16 ((short unsigned int)messageLength);
2685 memcpy(&data[2], &len16, 2);
2686 offset = 4;
2687 } else {
2688 uint64_t len64;
2689 /*
2690 * Together with the first two clauses, this means:
2691 * messageLength > 65535
2692 */
2693
2694 Tcl_DStringSetLength(&frameDs, 10);
2695 data[1] |= (( unsigned char )127 & 0x7Fu);
2696 len64 = htobe64((uint64_t)messageLength)__bswap_64 ((uint64_t)messageLength);
2697 memcpy(&data[2], &len64, 8);
2698 offset = 10;
2699 }
2700
2701 if (masked) {
2702 unsigned char mask[4];
2703 size_t i, j;
2704
2705 data[1] |= 0x80u;
2706#ifdef HAVE_OPENSSL_EVP_H1
2707 (void) RAND_bytes(&mask[0], 4);
2708#else
2709 {
2710 double d = Ns_DRand();
2711 /*
2712 * In case double is 64-bits (which is the case on
2713 * most platforms) the first four bytes contains much
2714 * less randoness than the second 4 bytes.
2715 */
2716 if (sizeof(d) == 8) {
2717 const char *p = (const char *)&d;
2718 memcpy(&mask[0], p+4, 4);
2719 } else {
2720 memcpy(&mask[0], &d, 4);
2721 }
2722 }
2723#endif
2724 Tcl_DStringSetLength(&frameDs, (int)offset + 4 + messageLength);
2725 data = (unsigned char *)frameDs.string;
2726 memcpy(&data[offset], &mask[0], 4);
2727 offset += 4;
2728 for( i = offset, j = 0u; j < (size_t)messageLength; i++, j++ ) {
2729 data[ i ] = messageString[ j ] ^ mask[ j % 4];
2730 }
2731 } else {
2732 Tcl_DStringSetLength(&frameDs, (int)offset + messageLength);
2733 data = (unsigned char *)frameDs.string;
2734 memcpy(&data[offset], &messageString[0], (size_t)messageLength);
2735 }
2736
2737 Tcl_SetObjResult(interp, Tcl_NewByteArrayObj(data, frameDs.length));
2738
2739 Tcl_DStringFree(&messageDs);
2740 Tcl_DStringFree(&frameDs);
2741 }
2742 return result;
2743}
2744
2745
2746
2747
2748/*
2749 *----------------------------------------------------------------------
2750 *
2751 * NsTclConnChanObjCmd --
2752 *
2753 * Implements "ns_connchan".
2754 *
2755 * Results:
2756 * A standard Tcl result.
2757 *
2758 * Side effects:
2759 * Depends on subcommand.
2760 *
2761 *----------------------------------------------------------------------
2762 */
2763
2764int
2765NsTclConnChanObjCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const* objv)
2766{
2767 const Ns_SubCmdSpec subcmds[] = {
2768 {"callback", ConnChanCallbackObjCmd},
2769 {"connect", ConnChanConnectObjCmd},
2770 {"close", ConnChanCloseObjCmd},
2771 {"detach", ConnChanDetachObjCmd},
2772 {"exists", ConnChanExistsObjCmd},
2773 {"list", ConnChanListObjCmd},
2774 {"listen", ConnChanListenObjCmd},
2775 {"open", ConnChanOpenObjCmd},
2776 {"read", ConnChanReadObjCmd},
2777 {"status", ConnChanStatusObjCmd},
2778 {"write", ConnChanWriteObjCmd},
2779 {"wsencode", ConnChanWsencodeObjCmd},
2780 {NULL((void*)0), NULL((void*)0)}
2781 };
2782 return Ns_SubcmdObjv(subcmds, clientData, interp, objc, objv);
2783}
2784
2785/*
2786 * Local Variables:
2787 * mode: c
2788 * c-basic-offset: 4
2789 * fill-column: 70
2790 * indent-tabs-mode: nil
2791 * End:
2792 */